Numbers here are historical; see bench/results_v*.txt.
| stage | file | notes |
|---|---|---|
| arena + views | src/base.[ch] |
chunked bump arena, in-place growth of last alloc, zero-copy Str views, typed VEC |
| lexer/parser | src/flow_parse.c |
single pass, table-driven char classes, open-addressing intern table |
| ranking | src/nsimplex.c |
greedy FAS (Eades) cycle removal; network simplex (dagre formulation) with graphviz-style incremental cut values / dfs_range |
| ordering | src/layout.c |
DFS init order, up/down barycenter sweeps (≤24, stop after 4 non-improving), Fenwick-tree crossing count, cluster-contiguous keys + dagre-style border chains |
| x coords | src/layout.c |
Brandes–Köpf: type-1 conflicts, 4 alignments, compaction, balance |
| output | src/flow_svg.c |
d3 curveBasis paths, boundary clipping per shape, markers, styles; fixed-point number formatting (no printf) |
| sequence | src/seq.c |
participants/actors, all arrows, activations, notes, blocks, autonumber, A->>()B / A()->>B central-connection dots |
| diagram registry | src/d_registry.c, src/diagrams.h |
keyword → renderer table; new types live in src/d_*.c |
| shared helpers | src/dgraph.[ch] |
graph building on top of flow_layout, line scanner, SVG primitives |
| stateDiagram(-v2) | src/d_state.c |
composites (recursive per-scope layout), -- regions, [*], fork/join/choice, notes, classDef |
| classDiagram | src/d_class.c |
members, ~generics~, visibility/$/*, annotations, all relations + lollipop, cardinality, namespaces, notes |
| erDiagram | src/d_er.c |
attribute tables (type/name/keys/comment), aliases, all crow's-foot cardinalities (symbolic + word forms), :::cls / classDef / class / style, subgraph clusters (relations may target a subgraph id) |
| pie | src/d_pie.c |
slices, showData, title, legend |
| gantt | src/d_gantt.c |
dayjs-style dateFormat (incl. Z/ZZ offsets), todayMarker (MMD_TODAY override), weekday/weekend friday, strftime axisFormat, tickInterval, excludes (weekends/dates), after/until deps, done/active/crit/milestone/vert, invalid tickInterval ignored, gantt.useWidth |
| journey | src/d_journey.c |
sections, scored tasks with faces, actor legend/dots |
| mindmap | src/d_mindmap.c |
indentation tree, all node shapes, radial layout (tidy-tree opt-in), ::icon(fa ...) drawn as real Font Awesome paths (badge fallback), :::class, section colours |
| timeline | src/d_timeline.c |
sections, periods, multi-event / continuation lines, wrapping |
| quadrantChart | src/d_quadrant.c |
axes, quadrant labels, points with inline style and classDef |
| xychart(-beta) | src/d_xychart.c |
vertical/horizontal, categories or numeric range, bar + line series, nice ticks, legend for titled series |
| gitGraph | src/d_gitgraph.c |
LR/TB/BT, branch order, merge, cherry-pick, tags, NORMAL/REVERSE/HIGHLIGHT, lane-routed merges, mermaid lane order (unordered branches = 0.<index>), hashed auto ids |
| sankey(-beta) | src/d_sankey.c |
RFC-4180 CSV, longest-path columns (sinks justified), barycentric relaxation, value labels with collision avoidance |
| block(-beta) | src/d_block.c |
columns, spans, space, nested blocks, 10 shapes, labelled edges clipped to the true shape outline; grid layout |
| requirementDiagram | src/d_requirement.c |
6 requirement kinds + element, all 7 relations (grey label plates), direction; layered layout |
| C4* | src/d_c4.c |
Context/Container/Component/Dynamic/Deployment, Person/System/Container/Component(+Db/Queue/_Ext), boundaries, Rel variants, c4ShapeInRow/c4BoundaryInRow grid, UpdateLayoutConfig/UpdateElementStyle/UpdateRelStyle (colours whitelisted), mermaid-11 person shape + [Type: techn] line |
| architecture(-beta) | src/d_architecture.c |
groups (nested), services, junctions, port-constraint grid, orthogonal edges, non-overlapping groups, built-in icons + embedded logos:/fa: subset (src/icons_data.h via tools/genicons.js: simple-icons CC0, FA Free CC-BY-4.0) |
| packet(-beta) | src/d_packet.c |
bit ranges, +N, row splitting, bit numbers, title below (as mermaid) |
| radar(-beta) | src/d_radar.c |
axes, positional/named curves, max/min/ticks, polygon graticule, legend |
| treemap(-beta) | src/d_treemap.c |
squarified layout, sections, classDef/::: |
| kanban | src/d_kanban.c |
columns, cards, @{assigned, ticket, priority}; named themes colour columns like mermaid (cScale[n+2] lightened/darkened 10%, cScaleLabel titles) |
| ishikawa | src/d_ishikawa.c |
fishbone port of mermaid's renderer: alternating up/down causes, nested sub-causes along bones, wrapped head |
| cynefin | src/d_cynefin.c |
five domains, items (confusion capped at 3 + overflow badge), transitions; seeded wavy boundaries match mermaid-cli byte-for-byte |
| ZenUML | src/d_zenuml.c |
native renderer in ZenUML's look: titled outer frame, boxed participant headers with @Actor/@Database/@Boundary/@Control/@Entity/@Collections/@Queue icons, unnamed starter column (or @Starter(X)), hierarchical message numbers (1, 2.1, 2.1.1), // comments above the next message, sync calls with {} bodies (activations, explicit return plus x = assignment return), new X() creating an x:X box mid-diagram, async A->B: msg / dashed A-->B, Alt/Loop/Opt/Par/Try/Critical/Section fragments (if/else if/else, while/for/forEach/loop, try/catch/finally) with numbered headers, ZenUML-style header icons and condition rows, title |
| config / a11y | src/d_cfg.c, src/dcfg.h |
%%{init}%% (relaxed JSON) + YAML frontmatter config: flattened to dotted keys; ~85 themeVariables mapped to CSS per type (values whitelisted, url( rejected); named themes dark/forest/neutral for every non-flowchart type (presets generated by tools/gen_themes.mjs from mermaid's own theme objects into src/d_themes.h, incl. pie1..12/cScale/git palettes; user themeVariables override); per-diagram keys (pie.textPosition, timeline.disableMulticolor, sankey.showValues, quadrantChart.chartHeight, gantt.axisFormat/tickInterval, gantt.barHeight/barGap/topPadding/leftPadding, quadrantChart.chartWidth, xyChart.width/height, sankey.width/height, packet.bitsPerRow); accTitle/accDescr (incl. {} block) → <title>/<desc> + aria-labelledby/describedby |
| text metrics | src/text.c |
embedded Helvetica-compatible advance table, Unicode East Asian Width for CJK, 8-byte ASCII fast path |
| venn | src/d_venn.c |
sets, unions with sizes, indented text nodes, style; venn.js-style area-proportional layout (lens-area bisection + stress descent), labels at each region's deepest point |
| treeView | src/d_treeview.c |
indentation or box-drawing (├──/└──/│) trees, :::highlight, icon(pack:name) / showIcons file+folder icons, filename/extension icon maps, ## descriptions |
| swimlane-beta | src/d_swimlanes.c (+ flowchart parser/drawer) |
full-width lanes from top-level subgraphs (LR/RL rows with rotated titles, TB/BT columns), lane-aware ranking (unlabelled cross-lane hops share a column), orthogonal edge routing, all flowchart node shapes/styles |
| eventmodeling | src/d_eventmodeling.c |
tf/timeframe, rf/resetframe frames (ui, cmd, evt, rmo, pcr + long names), Ns.Name stream/namespace lanes, ->> id sources, inline { ... } and [[data]] blocks, mermaid box sizing and relation geometry, em* theme variables |
| railroad | src/d_railroad.c |
railroad-ebnf-beta (=/::=, |, [ ], { }, ?*+, - exception, ? special ?, (* *)), railroad-abnf-beta (/, n*m repeats, %x values), railroad-peg-beta (<-, /, &/! lookahead, .), railroad-beta function DSL; mermaid arc geometry |
| wardley | src/d_wardley.c |
wardley-beta: title, size, evolution stages (@bounds, A / B labels), anchor, component (label [x, y], (build|buy|outsource|market|inertia)), pipeline { }, evolve, note, (de)accelerator, annotation(s), links -> +> +< +<> -.-> +'lbl'> ; label |
tests/docs/<doc>/NN.mmd: all 432 ```mermaid examples from mermaid's packages/mermaid/src/docs/syntax/*.md (mermaid 6d8921f, extracted by tools/extract_docs.py). python3 tools/docs_audit.py [-v] [--mmdc] reports per-type pass counts against mermaid-cli (cached in tests/docs/mermaid_status.txt); tools/docs_sbs.py renders side-by-sides. Every type passes 100% of the examples mermaid itself renders (396/396; 36 examples mermaid itself rejects are excluded). make test runs the audit and fails on any regression.
Side-by-side renders (left: libmmd, right: mermaid 11.17) live in samples/sbs/*.png.
Already fixed:
- C4: grid layout, person shape, centred boundary titles, relation labels placed to avoid boxes and other labels;
- gitGraph: lane order, vivid palette, commit labels rotated -45 degrees;
- treemap and radar: cScale palettes;
- gantt: excluded days/weekends shaded, light grid;
- kanban: mermaid column/card widths;
- mindmap: compacted with a spring relaxation;
- pie: declaration order and palette;
- timeline: cScale section colours;
- sequence: participant
@{type}shapes; - packet: title position;
- requirement: label plates.
Known remaining differences:
- mindmap: section colours, and the layout is still not cose-bilkent;
- gantt: mermaid draws denser ticks;
- state: concurrent regions are not drawn as grey boxes;
- class: wide single-row layout, same as mermaid's;
- journey: legend order;
- quadrant: mermaid 11.17 renders points with a broken
hsl(NaN)fill, so libmmd keeps blue; - C4: arbitrary bgColor strings are rejected.
mermaid itself fails to render
block_fullandradar_full(partial named curve).
directioninside subgraphs: clusters with their own direction are laid out recursively and placed as fixed-size proxy nodes; edges to a subgraph id attach to the cluster box; cluster titles are moved sideways to avoid edges crossing the title band.- Crossing reduction: barycenter sweeps + keep-best + budgeted transpose polish (dirty-layer skipping).
- Port ordering for fan-in/fan-out and parallel edges; nested multiple self-loops.
mmd --avoid(MMD_F_ROUTE_AVOID): detours polyline segments around nodes they pass through.- Themes:
default,dark,forest,neutralvia%%{init: {"theme": ...}}%%, frontmatter, or-t. - Markdown strings (
"bold italic") with auto-wrap at 200px,fa:fa-nameicons (Font Awesome glyphs; needs the font installed to render), edge idsA e1@--> B,e1@{ animate: true }. - Golden tests in
tests/golden(UPDATE_GOLDEN=1 bash tests/run_tests.shto refresh). - Benchmarks:
bench/results_v2.txt.
- Build:
make(portable-O2), opt-inmake MARCH=native,make LTO=1,make pgo [LTO=1 MARCH=native](output inbuild-pgo/). - Shared library:
build/libmmd.so.0.3.0(sonamelibmmd.so.0, onlymmd_*exported) plusbuild/mmd.pc;make install PREFIX=/usr DESTDIR=.... - Batch rendering:
mmd_context_new()/mmd_context_render_svg()/mmd_context_free()reuse the arena and output buffer between calls. - Obstacle routing is on by default: edges that cross a non-endpoint node get a local visibility-graph (A*) detour. Use
--no-avoid/MMD_F_NO_AVOIDfor the fastest layout. Edge labels sit on the rendered curve. - SVG: stroke widths and text anchoring are now CSS classes, and collinear points are dropped (about 20% smaller on medium, about 50% on large vs v1).
- Numbers:
bench/results_v3.txt.
- Subgraph boxes never overlap: ordering uses one global key per cluster (mean member slot over all layers), so clusters stay contiguous in every layer; Brandes-Köpf keeps cluster border chains rigid (borders always align with their chain partner, segments crossing a chain are conflicts). Overlapping cluster pairs over the mmdr fixtures, bench inputs and tests: 12 → 0.
- Router: corners buried in obstacles are pruned with a single grid-cell lookup, and side detours are tested shortest-first (the first clear one wins).
- Timings (µs, one core; v1 = original): medium 404 (v1) / 534 (avoid on) / 406 (
--no-avoid), xlarge 54862 / 54061 / 48932. Full numbers and the mmdr comparison:bench/results_v4.txt,bench/results_v5.txt. - Known gap: long edges between rectangular clusters can take wide detours around them
(e.g.
mega_event_mesh); this comes from layout, not the router (--no-avoidshows the same).
- Medium avoid-on 534 -> ~420 µs (v1 404); no-avoid 317 µs; large 8.3 ms (v1 8.9).
- xlarge is ~200 ms vs v1 54 ms: the round-5 DFS feedback arc set (mermaid's default acyclicer) gives far longer back-edge chains on the random xlarge input. Fidelity was chosen over speed here.
- Flowcharts now honour
accTitle/accDescr(svg<title>/<desc>+ aria) and%%{init}%%/frontmatterthemeVariables(primaryColor, primaryBorderColor, primaryTextColor, lineColor, edgeLabelBackground, clusterBkg, clusterBorder, titleColor, background, fontFamily, ...) through the same d_cfg system as the other diagrams. - Numbers and the mmdr comparison over all diagram types:
bench/results_v6.txt.
- Hybrid cycle removal: DFS FAS by default, greedy (Eades) when N ≥ 1500 and DFS reverses ≥ N/20 edges.
flowchart.cycleRemoval: dfs|greedy|auto, flagsMMD_F_FAS_DFS/MMD_F_FAS_GREEDY. xlarge 50.5 ms (v1 53.8). flowchart.nodeSpacing,flowchart.rankSpacing,flowchart.curve(basis, linear, step, stepBefore, stepAfter, cardinal/catmullRom).- Self-loops reserve side room in BK placement; multiple loop labels stack outside the outermost loop.
- Subgraph
directionis ignored when member nodes link outside the subgraph (mermaid rule). - Numbers: bench/results_v7.txt.
- Medium avoid-on 394.7 µs (v1 404.1): single ordering sweep for mid-size graphs (512 < segs ≤ 4096).
tests/bounds.py: every case and repo fixture must keep all geometry (path control points, exact arcs, shapes, text anchors) inside the root viewBox; self-loop curves now included in the flowchart viewBox.- Compound ordering seeded with dagre's tie order; same-rank order agreement with mermaid 0.175 → 0.936.
- Numbers: bench/results_v8.txt.
- Faithful dagre compound ranking in dagre mode (
src/drank.c): nesting graph (border top/bottom nodes, nodeSep weights), network simplex (graphlib iteration order), label proxies, removeEmptyRanks, cleanup, border segments. - Ordering agreement with mermaid: 1051/1051. Rank-axis position ordering vs mermaid SVGs: 100%. Cross-axis: 99.1% (
bench/posagree.py). - Small graphs use arena scratch in
dagre_order. Small takes 16.9 µs in dagre mode, or 12.8 µs in fast mode. Seebench/results_v10.txt.
- Faithful port of dagre's order step (
src/dorder.c: initOrder DFS, layer graphs with cluster borders, barycenter/sortSubgraph/resolveConflicts with subgraph constraints, biased down/up sweeps, keep best by crossCount), reproducing graphlib key order. Default for graphs with V ≤ 512; the fast ordering is kept above. Override:flowchart.order: dagre|fastor envMMD_ORDER. - Same-rank order agreement with mermaid 0.936 → 0.961 (macro 0.658 → 0.813). Numbers: bench/results_v9.txt.
Mermaid-exact text metrics (Liberation/Arial advances+kerning), mermaid node padding, self-loop helper nodes. See bench/results_v11.txt.
- Node-centre error vs mermaid: 67.7 px -> 1.61 px mean (median 0, p90 6.1). Sources fixed: double cluster padding in dagre mode, dagre ordering threshold (now N<=256), exact edge-label bbox sizes, and dagre's labelRank (proxy rank on nesting-graph ranks before removeEmptyRanks).
- Small render 15.0 us (target 11.7 us not met); medium 590 us (dagre-exact ordering now applies).
- bench/eq/: byte-identity harness. See bench/results_v12.txt.
Byte-identical output. medium 590.5 → 481.5 µs, small 15.1 → 14.6 µs (min, this box). v1 is at 407 / 11.9, so the targets are not met yet.
Main gains came from incremental network simplex (rank cycles 449k → 231k) and from dorder sort/crossing shortcuts.
Build with -DMMD_PROFILE to get per-phase cycle counters. See bench/results_v13.txt and bench/prof13/.
- Builds use
-std=gnu2y -fms-extensions -Wno-microsoft-anon-tagwith clang 23 (default) and gcc-16 (make both). Code must compile warning-free under both. - Byte-identity gate:
bench/eq/cmp.sh build/mmdmust printbad=0. - Before writing a string helper, check
src/base.h(str_sub/str_from/str_span,str_split,str_cat/STR_CAT,buf_cstr),src/text.h(label_br_len) andsrc/dgraph.h(dg_unquote,dg_css_ok,dg_arrowhead). Literal config keys go throughdcfg_get_str(S("...")). restrictis a caller contract, not decoration: use it only where every caller provably passes disjoint objects (Buf appends vs. their source, independent scratch arrays). Never append a Buf into itself —buf_reservemay move it.str_cat,str_splitandarena_growstay unqualified (their inputs and results may legitimately share storage), and the publicinclude/mmd.hstays unrestricted.- Resource release uses
[[gnu::cleanup]]viasrc/base.h:AUTOFREE(free),AUTO_BUF(Buf),AUTO_ARENA(Arena),AUTOCLOSE(FILE). No hand-written frees or goto-cleanup; return ownership withTAKE_PTR(p)/buf_take(&b). Nevergotopast a cleanup variable's declaration (clang errors, gcc silently skips it): declare them before the first jump.