Diagram Widget#

The same renderer that powers the Diagram Document can be used as a computable Jupyter Widget, which offers even more power than the Diagram Rich Display.

if __name__ == "__main__" and "pyodide" in __import__("sys").modules:
    %pip install -q ipydrawio-widgets
from ipydrawio_widgets import Diagram
from ipywidgets import (
    Accordion,
    Checkbox,
    FloatSlider,
    HBox,
    IntSlider,
    SelectMultiple,
    Text,
    Textarea,
    VBox,
    jslink,
)
from traitlets import dlink, link

diagram = Diagram(layout={"min_height": "80vh", "flex": "1"})
box = HBox([diagram])
box

value#

A Diagram.source’s value trait is the raw drawio XML. You can use one document for multiple diagrams.

graphviz2drawio is recommended for getting to give me some drawio XML from my data right now.

Diagram(source=diagram.source, layout={"min_height": "400px"})
diagram.source.value = """<mxfile version="13.6.10">
  <diagram id="x" name="Page-1">
    <mxGraphModel dx="1164" dy="293" grid="1" gridSize="10" guides="1" tooltips="1" connect="1" arrows="1" fold="1" page="1" pageScale="1" pageWidth="850" pageHeight="1100" math="0" shadow="0">
      <root>
        <mxCell id="0"/>
        <mxCell id="1" parent="0"/>
        <mxCell id="2" value="" style="edgeStyle=entityRelationEdgeStyle;startArrow=none;endArrow=none;segment=10;curved=1;" parent="1" source="4" target="5" edge="1">
          <mxGeometry relative="1" as="geometry"/>
        </mxCell>
        <mxCell id="3" value="" style="edgeStyle=entityRelationEdgeStyle;startArrow=none;endArrow=none;segment=10;curved=1;" parent="1" source="4" target="6" edge="1">
          <mxGeometry relative="1" as="geometry">
            <mxPoint x="260" y="160" as="sourcePoint"/>
          </mxGeometry>
        </mxCell>
        <UserObject label="The Big Idea" treeRoot="1" id="4">
          <mxCell style="ellipse;whiteSpace=wrap;html=1;align=center;collapsible=0;container=1;recursiveResize=0;" parent="1" vertex="1">
            <mxGeometry x="300" y="140" width="100" height="40" as="geometry"/>
          </mxCell>
        </UserObject>
        <mxCell id="5" value="Branch" style="whiteSpace=wrap;html=1;shape=partialRectangle;top=0;left=0;bottom=1;right=0;points=[[0,1],[1,1]];strokeColor=#000000;fillColor=none;align=center;verticalAlign=bottom;routingCenterY=0.5;snapToPoint=1;collapsible=0;container=1;recursiveResize=0;autosize=1;" parent="1" vertex="1">
          <mxGeometry x="460" y="120" width="80" height="20" as="geometry"/>
        </mxCell>
        <mxCell id="6" value="Sub Topic" style="whiteSpace=wrap;html=1;rounded=1;arcSize=50;align=center;verticalAlign=middle;collapsible=0;container=1;recursiveResize=0;strokeWidth=1;autosize=1;spacing=4;" parent="1" vertex="1">
          <mxGeometry x="460" y="160" width="72" height="26" as="geometry"/>
        </mxCell>
      </root>
    </mxGraphModel>
  </diagram>
</mxfile>"""
value = Textarea(description="value", rows=20)
controls = Accordion([value])
controls.set_title(0, "value")
jslink((diagram.source, "value"), (value, "value"))
box.children = [controls, diagram]

There are a number of challenges in using it as a protocol:

  • includes hostname (ick!)

  • includes etag

  • stripping these out creates flicker when updating

At present, tools like jinja2, which work directly with XML, or lxml, which can work at a higher level, with e.g. XPath.

Stay tuned for better tools for working with this format with e.g. networkx

Interactive state#

A Diagram exposes a number of parts of both the content and interactive state of the editor.

zoom = FloatSlider(description="zoom", min=0.01)
scroll_x, scroll_y = (
    FloatSlider(description=f"scroll {x}", min=-1e5, max=1e5) for x in "xy"
)
current_page = IntSlider(description="page")
jslink((diagram, "zoom"), (zoom, "value"))
jslink((diagram, "scroll_x"), (scroll_x, "value"))
jslink((diagram, "scroll_y"), (scroll_y, "value"))
jslink((diagram, "current_page"), (current_page, "value"))

controls.children = [VBox([zoom, scroll_x, scroll_y, current_page]), value]
controls._titles = {"0": "ui", "1": "value"}
selected_cells = SelectMultiple(description="selected")

dlink((diagram, "cell_ids"), (selected_cells, "options"))
link((diagram, "selected_cells"), (selected_cells, "value"))

controls.children = [
    VBox([zoom, scroll_x, scroll_y, current_page]),
    VBox([selected_cells]),
    value,
]
controls._titles = {"0": "ui", "1": "selection", "2": "value"}

HBox([selected_cells])

Page Information#

Diagrams actually describe a “real thing”, measured in inches.

page_format = {
    k: IntSlider(description=k, value=v, min=0, max=1e5)
    for k, v in diagram.page_format.items()
}


def update_format(*_):
    diagram.page_format = {k: v.value for k, v in page_format.items()}


def update_sliders(*_):
    for k, v in page_format.items():
        v.value = diagram.page_format[k]


[v.observe(update_format, "value") for k, v in page_format.items()]
[diagram.observe(update_sliders, "page_format")]


controls.children = [
    VBox([zoom, scroll_x, scroll_y, current_page]),
    VBox([selected_cells]),
    VBox([*page_format.values()]),
    value,
]
controls._titles = {"0": "ui", "1": "selection", "2": "page", "3": "value"}

Grid#

The styling of the on-screen grid is cutomizable. This typically won’t be included in export to e.g. SVG.

grid_enabled = Checkbox(description="grid")
grid_size = FloatSlider(description="grid size")
grid_color = Text("#66666666", description="grid color")
jslink((diagram, "grid_enabled"), (grid_enabled, "value"))
jslink((diagram, "grid_size"), (grid_size, "value"))
jslink((diagram, "grid_color"), (grid_color, "value"))

controls.children = [
    VBox([zoom, scroll_x, scroll_y, current_page]),
    VBox([selected_cells]),
    VBox([*page_format.values()]),
    VBox([grid_enabled, grid_size, grid_color]),
    value,
]
controls._titles = {"0": "ui", "1": "selection", "2": "page", "3": "grid", "4": "value"}