Skip to content

Give each diagram its own SVG, listeners and ids - #1156

Merged
raphael merged 2 commits into
mainfrom
renderer-instances
Nov 10, 2026
Merged

raphael merged 2 commits into
mainfrom
renderer-instances

Conversation

@raphael

@raphael raphael commented Nov 10, 2026 •

Copy link
Copy Markdown
Member

Problem

The diagram renderer behind mdl serve, mdl svg and the static page keeps one SVG element in a module-level variable. Every new graph replaces it, every graph's SVG has the id graph, and the renderer also sets window.graph and window.gdata.

That is fine while a page draws one diagram. The static page, however, draws every .c4-diagram element in the document, and its diagrams are laid out asynchronously. Once the second graph is built, the first graph's layout, zoom and export act on the second graph's SVG. Ids collide too: each graph writes its own grid-pattern, arrow and icon-* definitions under the same names, so a url(#…) reference in one diagram can resolve to another diagram's definition.

A test added here shows the failure on the previous code: with two views in one document, the first view's nodes are no longer in the page when its layout runs.

What changes

Each graph now owns what it draws:

  • GraphData holds its own SVG element and its selection listener. The module-level SVG and listeners are gone, and so are window.graph and window.gdata.
  • Each graph prefixes the ids it writes into the live page (g1-arrow, g2-grid-pattern, node, edge and vertex ids) and the references to them, so two diagrams never share an id.
  • exportSVG removes the prefix on the copy it already makes and gives the root the id graph. Exported files are unchanged: mdl svg output for both views of examples/basic is byte-identical before and after. Saved layouts load as before.
  • User-authored node links are never rewritten on export, including a link whose fragment happens to start with a graph's prefix.

The helpers that read the shared element take the graph as their first argument: getZoom, setZoom, setZoomCentered, getZoomAuto, addCursorInteraction, saveViewState, restoreViewState and clearViewState. buildGraph and buildGraphView keep their signatures; the static and headless entry points are not edited.

Two style rules selected by id. The relationship-point preview shown on Alt-hover is now selected by class, and the #icon-cube rule, which repeated the .icon rule on the same element, is removed.

Tests

  • TestTwoGraphsInOneDocument puts two views with different shapes and layout directions in one document, turns the grid on in both and lays them out at the same time. It checks that each SVG holds only its own nodes, that no id appears twice in the page, that every url(#…) reference resolves inside its own SVG, that each graph is fitted inside its own container, and that each export carries the saved ids.
  • TestEdgePointPreviewStyle moves the mouse over a relationship with Alt held and compares the preview dot's computed colors with the style sheet's rule.
  • The existing browser tests no longer read window.gdata. They use the editor's toolbar (Auto layout, Save) where that is enough. Two tests read the editor's graph from React's component data, so they depend on React internals: TestManualEditAfterAutoLayout needs a value no control exposes, and TestTwoGraphsInOneDocument needs both graph objects.

Limits

  • Several diagrams can now be drawn, laid out and exported in one document. Editing two diagrams interactively in one page is not supported: keyboard shortcuts are still handled for the whole window.
  • The static page bundle (webpack.config.static.js) loads its layout chunk from a wrong path, so automatic layout fails there. That defect predates this change and is not fixed here; no test builds that bundle.
  • This change adds no embedding API and does not change the version.

The rebuilt files under cmd/mdl/webapp/dist come from make build-ui.

The diagram renderer kept one module-level SVG element that every new
graph replaced, plus window globals and fixed element ids. When a page
drew more than one diagram, one graph's layout, zoom and export could
act on another graph's SVG, and ids such as the grid pattern collided.

Each GraphData now owns its SVG element, its selection listener and an
id prefix for the definitions and references it creates. Helpers that
read the shared element take the graph instead. exportSVG restores the
existing names on its clone, so exported files and saved layouts are
unchanged. The window.graph and window.gdata globals are removed; the
browser tests drive the editor through its toolbar instead.
The preview dot shown on Alt-hover is now created with a per-graph id,
so the editor style sheet's circle#prj rule no longer matched and the
dot was drawn black. The dot carries a class and the rule selects it.
The #icon-cube rule repeated the .icon rule on the same element and is
removed.

Export no longer rewrites href values and leaves user-authored node
links untouched. The unused dragging flag and its callback are removed.
Browser tests check the preview dot's colors after a real Alt-hover,
that each graph is fitted inside its own container, and that a node
link starting with the live id prefix survives export unchanged.
@raphael
raphael merged commit cee7217 into main Nov 10, 2026
3 checks passed
@raphael
raphael deleted the renderer-instances branch November 10, 2026 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant