📐 disposition

Diagrams to SVG.

Decisions:

  • Diagrams: Represents things or a process.
  • SVG: Portable, supports styling, interactivity, can be generated in-memory -- browser not needed.

This is a new library / app intended to take the place of dot_ix.

Background

dot_ix is a useful tool to generate diagrams backing on to GraphViz and Tailwind CSS.

The following learnings have come from using dot_ix for 2 years:

  1. Requiring GraphViz to be installed / a browser to run the WASM version limits the ability to write a headless application and how cleanly we can write a web UI.
  2. GraphViz's layout engine is not predictable -- positioning of nodes is unstable when edges are added -- and this requires the input to be tuned to get an "expected" positioning of nodes / edges.
  3. The dot_ix input structure is relatively good, but can be further refined with better top-level concepts (node_type, better tag support).
  4. Native markdown support is desired.

Alternatives

  • dot_ix: This is/was the previous project, so we're creating the next evolution of it.

  • Browser web driver: We generate HTML, browser renders it, and we generate SVG off the browser DOM element positions.

    Doesn't work great for CLI -- need to have a headless browser.

Design

There are multiple parts to generating a diagram:

  1. Diagram Structure: Capturing the information that the diagram represents in a suitable data structure.
  2. Layout Document Object Model (DOM): Calculating the DOM element structure (visual hierarchy) of that information.
  3. Layout: Calculating the positions of the layout DOM on a viewport with fixed dimensions.
  4. Full Document Object Model (DOM): Adding edges after the elements are positioned, and adding the attributes that the layout DOM doesn't have.
  5. Rendering: Producing the visual representation from the full DOM elements.

1. High Level Diagram Structure / Capturing Information

Capturing the information for the diagram, in a structure that is easy to reason about and work with. Ideally easy for both humans and computers to read and write.

  • Input: Input formats, e.g. JSON, YAML, in-memory objects, etc.
  • Output: High Level diagram data structure.

1.1. Nodes / Clusters

  1. Stable IDs
  2. Display names
  3. Descriptions / additional detail
  4. "type"(s) -- the primary way how this node / cluster should be rendered
  5. Tags -- what groups it is part of / affected by.
  6. Hierarchy / Nesting / Level of detail

Kinds of diagrams we want to support:

  1. Things: Shows things, where they are, and their relationship with other things.
  2. Process: Shows steps in a process, and status / progress of each step.

We don't want to use the names "entity diagram" or "sequence diagram", because it can cause confusion with those terms in the software development context.

1.2. Edges

  1. From / to which node / cluster.
  2. Direction
  3. Type
  4. Multiple edges between nodes
  5. Edges on the correct point (north, south, east, west) on the node.

2. Intermediate Representation (IR) Diagram Structure

  • Input: High Level Diagram Structure, or serialized IR, e.g. JSON, YAML.
  • Output: IR Diagram data structure.

Similar to 1., except we want to define everything in terms of nodes and edges.

Technically we can begin at this step instead of 1., and define the high level diagram structure later, as long as we can represent the complex diagram in this intermediate representation.

3. Document Object Model (DOM)

Turn the diagram data structure into DOM elements.

  • Input: Diagram data structure.
  • Output: Layout DOM elements which are not viewport bound.

We need to choose one or a combination of:

ℹ️ Note: we also need to consider edge descriptions -- how do we place these in the DOM? taffy will be used for flex layout, but where would we place edge DOM elements?

4. Layout

Placement of nodes, padding, reflowing text, etc.

  • Input: Layout DOM elements which are not viewport bound.
  • Output: DOM elements / text with XY coordinates in a fixed viewport, with tailwind classes.

If we use:

  • taffy: We need to translate its output into the SVG DOM.
  • blitz: We need to translate HTML elements into SVG DOM.
  • Headless browser: We need to translate HTML elements into SVG DOM, and we'd need a headless browser, which isn't convenient for CI.

ℹ️ Note: we also need to consider edge descriptions -- if there is a lot of text, should we have spacing for those labels?

4.1. DOM representation

Because we want node descriptions to be markdown, we need to convert them to an appropriate DOM structure that can represent the rendered markdown, as well as encode the layout and styling information.

4.1.1. Option 1: SVG
  1. We have to calculate the positions of nodes and text ourselves, including padding etc.
  2. Markdown is converted to HTML, then we use those to position the text.
  3. i.e. we'd have to know / calculate the font metrics for bold/italicized text.
  4. taffy is what servo uses, and does element layouting.
  5. cosmic-text is needed for text width calculations.
  6. See the cosmic_text example -- you need font metrics to know how text renders.
4.1.2. Option 2: HTML + HTML to SVG
  1. HTML rendering engine does the layout of text positioning for us.
  2. Markdown will easily be supported here, because we can convert to HTML, then the rendering engine takes care of the rest.

4.2. Images

Images can be inlined in markdown, and based on the image data or a provided value, we can pass that to taffy to calculate the position. If we use comrak, then we need to wait for comrak#586 to be resolved to get the passed in dimensions of the image.

5. Full Document Object Model (DOM)

Adding edges after the elements are positioned, and adding the attributes that the layout DOM doesn't have.

  • Input: Layout DOM elements with fixed coordinates.
  • Output: Render DOM elements (including text) with XY coordinates in a fixed viewport, with tailwind classes.

5.1. Edges

For flex type layouts / non-rank layouts, edges are intended to be hidden until a process / a step in a process is selected. This means there is no need to consider edges crossing each other, and they should generally have their start and end points in the middle of a thing's border. Multiple edges may be offset from the middle by a few points so that it's clear there are multiple edges.

For rank type layouts, edges may be always visible, and are highlighted when a thing is focused. The highlighting makes it clearer when things are related, and layout stability is a goal of this tool, so edge crossing minimization will not be done at the expense of stable node positions.

kurbo may be useful to calculate the coordinates along the curve for the path. Check how SVG paths take in input for curved lines -- we might not need to use kurbo if the SVG renderer calculates the curves.

6. Rendering

Rendering of the DOM into a visual and interactive format.

  • Input: Render DOM elements with XY coordinates in a fixed viewport, with tailwind classes.
  • Output: Visual and interactive diagram.

Any browser could render HTML / SVG. If we want a non-browser solution, look at:

  1. stylo is the CSS engine servo uses. Do we need it? If we do, there's stylo_taffy
  2. blitz seems to be doing what we want (and more), but in HTML.
  3. blitz#260 is where they'd add SVG support.

Solution

Probably:

  1. Define high level diagram structure based on concepts we want to display.
  2. Define intermediate diagram structure based on dot_ix's learnings.
  3. Map the structure to taffy's elements.
  4. Use taffy to lay out the diagram.
  5. Convert to SVG, adding edges and attributes from the input structure. kurbo may be useful to compute the edge path coordinates.
  6. Return that to the caller -- SVG can be rendered in a browser. In the future, we might use blitz to render the SVG.

Ideas / Learnings from dot_ix

  1. Ability to combine both Thing diagrams and Process diagrams. i.e. a process diagram whose steps show what is happening on the things. Maybe we just have one kind of diagram that does both.

    1. Maybe we just have one kind of diagram that does both.
    2. What dot_ix has as tags can be distinguished as Processes or TagGroups.
  2. When a node is styled with certain colours, apply it to all child nodes.

  3. Light / Medium / Dark presets for shading.

  4. Dependency diagrams: is it possible to select a node, and a menu appears, with buttons each to highlight:

    1. All transitive dependencies this depends on.
    2. All transitive dependents that depend on this.
    3. Immediate neighbours. Need to experiment with group-focus-within from tailwind.

    Looks like it may be possible with different CSS pseudo classes / pseudo elements.

    e.g. when an element is clicked on, it becomes the :target element in the document, and the css selector #element-id:target ~ #other allows you to style #other when #element-id was clicked, presumably when the focus is changed from #element-id to something else.

  5. Use async-lsp to provide context-aware completions.

Example Input

See example_input.md for an example.

Example Intermediate Representation (IR)

See example_intermediate_representation.md for an example.

Experiments

Group Focus


Peer Data Attribute