From 152af6ac16de081d48174ebe89d0c06ec2d38af1 Mon Sep 17 00:00:00 2001 From: Parman Mohammadalizadeh Date: Sat, 8 Aug 2026 19:37:28 +0200 Subject: [PATCH] feat(diagrams): add vector diagram fixtures and DOT ground truth Node/edge recovery from vector diagrams (xberg-io/xberg#579) had nothing to calibrate against. The four SVGs in `xml/` carry no transform chain, no arrowheads, no double borders and no curved connectors, so a recogniser can pass all four and still be wrong about every file a user would bring. Four fixtures are rendered from committed `.dot` sources with Graphviz, which makes the correct answer part of the corpus rather than something a recogniser gets to assert about itself. A fifth is hand-written and puts every shape and label under a chain of `translate`/`scale` groups with a viewBox that differs from the viewport. Ground truth is keyed by node label rather than by generated id, so it does not depend on any one recogniser's numbering, and it records what each file draws rather than what any implementation currently returns. `data_dashboard` and `simple_svg` get deliberately empty ground truth: a bar chart and a two-shape drawing are not diagrams, and recovering a graph from either is a false positive worth testing for. A vector PDF of the same graph would exercise the PDF path, but `*.pdf` is excluded from git here and lives in the corpus bucket, so it needs `scripts/publish_corpus.py`. --- ATTRIBUTIONS.md | 7 +++ diagrams/README.md | 61 +++++++++++++++++++ diagrams/graphviz_bidirectional.svg | 50 ++++++++++++++++ diagrams/graphviz_flow.svg | 63 ++++++++++++++++++++ diagrams/graphviz_network.svg | 63 ++++++++++++++++++++ diagrams/graphviz_states.svg | 66 +++++++++++++++++++++ diagrams/nested_transforms.svg | 43 ++++++++++++++ diagrams/src/graphviz_bidirectional.dot | 10 ++++ diagrams/src/graphviz_flow.dot | 11 ++++ diagrams/src/graphviz_network.dot | 13 ++++ diagrams/src/graphviz_states.dot | 9 +++ ground_truth/dot/data_dashboard.dot | 0 ground_truth/dot/flowchart.dot | 9 +++ ground_truth/dot/graphviz_bidirectional.dot | 8 +++ ground_truth/dot/graphviz_flow.dot | 10 ++++ ground_truth/dot/graphviz_network.dot | 11 ++++ ground_truth/dot/graphviz_states.dot | 10 ++++ ground_truth/dot/nested_transforms.dot | 9 +++ ground_truth/dot/org_chart.dot | 14 +++++ ground_truth/dot/simple_svg.dot | 0 ground_truth/ground_truth_mapping.json | 9 +++ 21 files changed, 476 insertions(+) create mode 100644 diagrams/README.md create mode 100644 diagrams/graphviz_bidirectional.svg create mode 100644 diagrams/graphviz_flow.svg create mode 100644 diagrams/graphviz_network.svg create mode 100644 diagrams/graphviz_states.svg create mode 100644 diagrams/nested_transforms.svg create mode 100644 diagrams/src/graphviz_bidirectional.dot create mode 100644 diagrams/src/graphviz_flow.dot create mode 100644 diagrams/src/graphviz_network.dot create mode 100644 diagrams/src/graphviz_states.dot create mode 100644 ground_truth/dot/data_dashboard.dot create mode 100644 ground_truth/dot/flowchart.dot create mode 100644 ground_truth/dot/graphviz_bidirectional.dot create mode 100644 ground_truth/dot/graphviz_flow.dot create mode 100644 ground_truth/dot/graphviz_network.dot create mode 100644 ground_truth/dot/graphviz_states.dot create mode 100644 ground_truth/dot/nested_transforms.dot create mode 100644 ground_truth/dot/org_chart.dot create mode 100644 ground_truth/dot/simple_svg.dot diff --git a/ATTRIBUTIONS.md b/ATTRIBUTIONS.md index dd0744f..85ab7c7 100644 --- a/ATTRIBUTIONS.md +++ b/ATTRIBUTIONS.md @@ -133,3 +133,10 @@ Vendored WordPerfect-family test files. Per-file provenance (upstream path, revi - **License:** CDLA-Permissive-1.0 - **Used here:** 2 financial-report pages (`images/doclaynet_page_01..02.jpg`) from the `test` split (`NASDAQ_ATRI_2003.pdf` p24, `NYSE_MGM_2004.pdf` p49). - **Modifications:** authoritative PDF text cells linearized in reading order (top-to-bottom, then left-to-right). No OCR and no model used; text is DocLayNet's verbatim PDF-layer cell text. + +## Diagram fixtures (this repository) + +- **Source:** authored here, not derived from any third-party dataset. +- **License:** same as this repository. +- **Used here:** `diagrams/*.svg` plus their `diagrams/src/*.dot` sources and `ground_truth/dot/*.dot`. +- **Modifications:** four SVGs are rendered from the committed `.dot` sources with Graphviz 15.1.1 (`dot`/`neato`), which is EPL-1.0 licensed; its output is not a derivative of the tool. `nested_transforms.svg` is hand-written. Ground truth is the source graph restated by node label, so it is independent of any recogniser's numbering. diff --git a/diagrams/README.md b/diagrams/README.md new file mode 100644 index 0000000..14821bd --- /dev/null +++ b/diagrams/README.md @@ -0,0 +1,61 @@ +# Diagram fixtures + +Vector diagrams for node/edge recovery (xberg-io/xberg#579). A vector diagram +carries its own graph, so recovery from one is exact rather than probabilistic, +and these fixtures are what say whether it actually is. + +## Why generated, not hand-drawn + +Hand-written SVG exercises almost none of what a real diagram tool emits. The +four SVGs already in `xml/` have no transform chain, no arrowheads, no double +borders and no curved connectors, and a recogniser can pass all four while being +wrong about every file a user would actually bring. + +Rendering a known graph through Graphviz fixes that, and it also gives ground +truth for free: the source `.dot` **is** the correct answer, so the corpus +measures recovery rather than freezing whatever it currently produces. + +## Layout + +| file | exercises | +|---|---| +| `graphviz_flow.svg` | box, diamond and ellipse nodes; arrowheads; edge labels; a dashed edge; root `translate` with negative coordinates | +| `graphviz_states.svg` | `doublecircle` (one node drawn as two concentric outlines); a pair of antiparallel edges between adjacent nodes | +| `graphviz_network.svg` | undirected `--` edges, so no arrowhead anywhere; `neato` layout | +| `graphviz_bidirectional.svg` | `dir=both` and `dir=back` | +| `nested_transforms.svg` | hand-authored: nested `translate`/`scale` groups plus a viewBox that differs from the viewport, so nothing sits at the coordinate it is written at | +| `src/*.dot` | the Graphviz sources, so every SVG above is regenerable | + +`ground_truth/dot/.dot` holds the graph each fixture draws, keyed by node +label rather than by generated id so it does not depend on any one recogniser's +numbering. Two entries are deliberately empty, `data_dashboard` and +`simple_svg`: those files are a bar chart and a two-shape drawing, they are not +diagrams, and recovering a graph from either would be a false positive. + +Ground truth also covers `xml/org_chart.svg` and `xml/flowchart.svg`, which stay +where they are. + +## Regenerate + +``` +brew install graphviz # or apt-get install graphviz +cd diagrams/src +for f in graphviz_flow graphviz_states graphviz_bidirectional; do + dot -Tsvg "$f.dot" -o "../$f.svg" +done +neato -Tsvg graphviz_network.dot -o ../graphviz_network.svg +``` + +Output is stable for a given Graphviz version. This was built with 15.1.1; +a different version may lay the graphs out differently, which changes +coordinates but not the graph, and the ground truth is written in terms of the +graph. + +`nested_transforms.svg` is hand-authored and is not regenerated. + +## Not included + +A vector PDF of the same graph (`dot -Tpdf`) would exercise the PDF path, but +`*.pdf` is excluded from git here and lives in the corpus bucket, so it has to +go through `scripts/publish_corpus.py`. Happy to supply the file for a +maintainer to publish. diff --git a/diagrams/graphviz_bidirectional.svg b/diagrams/graphviz_bidirectional.svg new file mode 100644 index 0000000..8b95e24 --- /dev/null +++ b/diagrams/graphviz_bidirectional.svg @@ -0,0 +1,50 @@ + + + + + + +graphviz_bidirectional + + + +api + +API + + + +db + +Database + + + +api->db + + + + + + +cache + +Cache + + + +api->cache + + + + + +cache->db + + + + + diff --git a/diagrams/graphviz_flow.svg b/diagrams/graphviz_flow.svg new file mode 100644 index 0000000..aea1e67 --- /dev/null +++ b/diagrams/graphviz_flow.svg @@ -0,0 +1,63 @@ + + + + + + +graphviz_flow + + + +a + +Start + + + +b + +Validate + + + +a->b + + + + + +c + +Process + + + +b->c + + +ok + + + +d + +Done + + + +b->d + + +skip + + + +c->d + + + + + diff --git a/diagrams/graphviz_network.svg b/diagrams/graphviz_network.svg new file mode 100644 index 0000000..4807a3d --- /dev/null +++ b/diagrams/graphviz_network.svg @@ -0,0 +1,63 @@ + + + + + + +graphviz_network + + + +gw + +Gateway + + + +s1 + +Switch A + + + +gw--s1 + + + + +s2 + +Switch B + + + +gw--s2 + + + + +h1 + +Host 1 + + + +s1--h1 + + + + +h2 + +Host 2 + + + +s2--h2 + + + + diff --git a/diagrams/graphviz_states.svg b/diagrams/graphviz_states.svg new file mode 100644 index 0000000..9146004 --- /dev/null +++ b/diagrams/graphviz_states.svg @@ -0,0 +1,66 @@ + + + + + + +graphviz_states + + + +idle + +idle + + + +running + +running + + + +idle->running + + +start + + + +paused + +paused + + + +running->paused + + +pause + + + +done + + +done + + + +running->done + + +finish + + + +paused->running + + +resume + + + diff --git a/diagrams/nested_transforms.svg b/diagrams/nested_transforms.svg new file mode 100644 index 0000000..31a01e3 --- /dev/null +++ b/diagrams/nested_transforms.svg @@ -0,0 +1,43 @@ + + + + Nested Transforms + Three-stage pipeline drawn inside translated, scaled and rotated groups + + + + + + + Ingest + + + + + Transform + + + + + Publish + + + + + + + + Quarantine + + + + on error + + + + diff --git a/diagrams/src/graphviz_bidirectional.dot b/diagrams/src/graphviz_bidirectional.dot new file mode 100644 index 0000000..54b6150 --- /dev/null +++ b/diagrams/src/graphviz_bidirectional.dot @@ -0,0 +1,10 @@ +digraph graphviz_bidirectional { + rankdir=LR; + node [shape=box style=filled fillcolor="#fdb462"]; + api [label="API"]; + db [label="Database"]; + cache [label="Cache"]; + api -> db [dir=both]; + api -> cache; + cache -> db [dir=back]; +} diff --git a/diagrams/src/graphviz_flow.dot b/diagrams/src/graphviz_flow.dot new file mode 100644 index 0000000..f00b5e3 --- /dev/null +++ b/diagrams/src/graphviz_flow.dot @@ -0,0 +1,11 @@ +digraph graphviz_flow { + rankdir=TB; + a [label="Start" shape=box style=filled fillcolor="#a6cee3"]; + b [label="Validate" shape=diamond style=filled fillcolor="#b2df8a"]; + c [label="Process" shape=box style=filled fillcolor="#fb9a99"]; + d [label="Done" shape=ellipse style=filled fillcolor="#fdbf6f"]; + a -> b; + b -> c [label="ok"]; + b -> d [label="skip" style=dashed]; + c -> d; +} diff --git a/diagrams/src/graphviz_network.dot b/diagrams/src/graphviz_network.dot new file mode 100644 index 0000000..aed4a52 --- /dev/null +++ b/diagrams/src/graphviz_network.dot @@ -0,0 +1,13 @@ +graph graphviz_network { + layout=neato; + node [shape=ellipse style=filled fillcolor="#cccccc"]; + gw [label="Gateway" fillcolor="#8dd3c7"]; + s1 [label="Switch A" fillcolor="#ffffb3"]; + s2 [label="Switch B" fillcolor="#ffffb3"]; + h1 [label="Host 1"]; + h2 [label="Host 2"]; + gw -- s1; + gw -- s2; + s1 -- h1; + s2 -- h2; +} diff --git a/diagrams/src/graphviz_states.dot b/diagrams/src/graphviz_states.dot new file mode 100644 index 0000000..ecdaa0f --- /dev/null +++ b/diagrams/src/graphviz_states.dot @@ -0,0 +1,9 @@ +digraph graphviz_states { + rankdir=LR; + node [shape=circle style=filled fillcolor="#b3de69"]; + idle -> running [label="start"]; + running -> paused [label="pause"]; + paused -> running [label="resume"]; + running -> done [label="finish"]; + done [shape=doublecircle fillcolor="#fb8072"]; +} diff --git a/ground_truth/dot/data_dashboard.dot b/ground_truth/dot/data_dashboard.dot new file mode 100644 index 0000000..e69de29 diff --git a/ground_truth/dot/flowchart.dot b/ground_truth/dot/flowchart.dot new file mode 100644 index 0000000..8a8f8b4 --- /dev/null +++ b/ground_truth/dot/flowchart.dot @@ -0,0 +1,9 @@ +digraph flowchart { + "Requirements" [shape=box fillcolor="#4a90d9"]; + "Design" [shape=box fillcolor="#50b848"]; + "Implementation" [shape=box fillcolor="#f5a623"]; + "Testing" [shape=box fillcolor="#d0021b"]; + "Requirements" -> "Design"; + "Design" -> "Implementation"; + "Implementation" -> "Testing"; +} diff --git a/ground_truth/dot/graphviz_bidirectional.dot b/ground_truth/dot/graphviz_bidirectional.dot new file mode 100644 index 0000000..cb95d4b --- /dev/null +++ b/ground_truth/dot/graphviz_bidirectional.dot @@ -0,0 +1,8 @@ +digraph graphviz_bidirectional { + "API" [shape=box fillcolor="#fdb462"]; + "Database" [shape=box fillcolor="#fdb462"]; + "Cache" [shape=box fillcolor="#fdb462"]; + "API" -> "Database" [dir=both]; + "API" -> "Cache"; + "Database" -> "Cache"; +} diff --git a/ground_truth/dot/graphviz_flow.dot b/ground_truth/dot/graphviz_flow.dot new file mode 100644 index 0000000..520333f --- /dev/null +++ b/ground_truth/dot/graphviz_flow.dot @@ -0,0 +1,10 @@ +digraph graphviz_flow { + "Start" [shape=box fillcolor="#a6cee3"]; + "Validate" [shape=diamond fillcolor="#b2df8a"]; + "Process" [shape=box fillcolor="#fb9a99"]; + "Done" [shape=ellipse fillcolor="#fdbf6f"]; + "Start" -> "Validate"; + "Validate" -> "Process" [label="ok"]; + "Validate" -> "Done" [label="skip" style=dashed]; + "Process" -> "Done"; +} diff --git a/ground_truth/dot/graphviz_network.dot b/ground_truth/dot/graphviz_network.dot new file mode 100644 index 0000000..e7642b4 --- /dev/null +++ b/ground_truth/dot/graphviz_network.dot @@ -0,0 +1,11 @@ +digraph graphviz_network { + "Gateway" [shape=ellipse fillcolor="#8dd3c7"]; + "Switch A" [shape=ellipse fillcolor="#ffffb3"]; + "Switch B" [shape=ellipse fillcolor="#ffffb3"]; + "Host 1" [shape=ellipse fillcolor="#cccccc"]; + "Host 2" [shape=ellipse fillcolor="#cccccc"]; + "Gateway" -> "Switch A"; + "Gateway" -> "Switch B"; + "Switch A" -> "Host 1"; + "Switch B" -> "Host 2"; +} diff --git a/ground_truth/dot/graphviz_states.dot b/ground_truth/dot/graphviz_states.dot new file mode 100644 index 0000000..b68677b --- /dev/null +++ b/ground_truth/dot/graphviz_states.dot @@ -0,0 +1,10 @@ +digraph graphviz_states { + "idle" [shape=ellipse fillcolor="#b3de69"]; + "running" [shape=ellipse fillcolor="#b3de69"]; + "paused" [shape=ellipse fillcolor="#b3de69"]; + "done" [shape=ellipse fillcolor="#fb8072"]; + "idle" -> "running" [label="start"]; + "running" -> "paused" [label="pause"]; + "paused" -> "running" [label="resume"]; + "running" -> "done" [label="finish"]; +} diff --git a/ground_truth/dot/nested_transforms.dot b/ground_truth/dot/nested_transforms.dot new file mode 100644 index 0000000..18a1822 --- /dev/null +++ b/ground_truth/dot/nested_transforms.dot @@ -0,0 +1,9 @@ +digraph nested_transforms { + "Ingest" [shape=box fillcolor="#4e79a7"]; + "Transform" [shape=box fillcolor="#f28e2b"]; + "Publish" [shape=box fillcolor="#59a14f"]; + "Quarantine" [shape=box fillcolor="#e15759" style=dashed]; + "Ingest" -> "Transform"; + "Transform" -> "Publish"; + "Transform" -> "Quarantine" [label="on error" style=dashed]; +} diff --git a/ground_truth/dot/org_chart.dot b/ground_truth/dot/org_chart.dot new file mode 100644 index 0000000..a0f0d72 --- /dev/null +++ b/ground_truth/dot/org_chart.dot @@ -0,0 +1,14 @@ +digraph org_chart { + "Jane Smith\nChief Executive Officer" [shape=box fillcolor="#2c3e50"]; + "Bob Chen\nChief Technology Officer" [shape=box fillcolor="#2980b9"]; + "Maria Garcia\nChief Financial Officer" [shape=box fillcolor="#27ae60"]; + "Alex Johnson\nChief Operating Officer" [shape=box fillcolor="#8e44ad"]; + "Engineering" [shape=box fillcolor="#3498db"]; + "Product" [shape=box fillcolor="#3498db"]; + "Finance" [shape=box fillcolor="#2ecc71"]; + "Sales" [shape=box fillcolor="#9b59b6"]; + "Operations" [shape=box fillcolor="#9b59b6"]; + "Jane Smith\nChief Executive Officer" -> "Bob Chen\nChief Technology Officer"; + "Jane Smith\nChief Executive Officer" -> "Maria Garcia\nChief Financial Officer"; + "Jane Smith\nChief Executive Officer" -> "Alex Johnson\nChief Operating Officer"; +} diff --git a/ground_truth/dot/simple_svg.dot b/ground_truth/dot/simple_svg.dot new file mode 100644 index 0000000..e69de29 diff --git a/ground_truth/ground_truth_mapping.json b/ground_truth/ground_truth_mapping.json index d5ce093..6344531 100644 --- a/ground_truth/ground_truth_mapping.json +++ b/ground_truth/ground_truth_mapping.json @@ -102,6 +102,15 @@ "docx_rich_cells.docx": "test_documents/ground_truth/json/docx_rich_cells.docx.txt", "docx_simple": "test_documents/ground_truth/docx/docx_simple.txt", "docx_tables": "test_documents/ground_truth/docx/docx_tables.txt", + "dot_data_dashboard": "test_documents/ground_truth/dot/data_dashboard.dot", + "dot_flowchart": "test_documents/ground_truth/dot/flowchart.dot", + "dot_graphviz_bidirectional": "test_documents/ground_truth/dot/graphviz_bidirectional.dot", + "dot_graphviz_flow": "test_documents/ground_truth/dot/graphviz_flow.dot", + "dot_graphviz_network": "test_documents/ground_truth/dot/graphviz_network.dot", + "dot_graphviz_states": "test_documents/ground_truth/dot/graphviz_states.dot", + "dot_nested_transforms": "test_documents/ground_truth/dot/nested_transforms.dot", + "dot_org_chart": "test_documents/ground_truth/dot/org_chart.dot", + "dot_simple_svg": "test_documents/ground_truth/dot/simple_svg.dot", "drawingml.docx": "test_documents/ground_truth/json/drawingml.docx.txt", "duck.md": "test_documents/ground_truth/md/duck.md.txt", "duplicate-paragraphs": "test_documents/ground_truth/doc/duplicate-paragraphs.txt",