From b87100b67269f90432d407e9657bf9c1c510e1c4 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Mon, 18 Nov 2024 16:23:35 -0800 Subject: [PATCH] markdown: tables --- ci/release/changelogs/next.md | 1 + .../txtar/md-tables/dagre/board.exp.json | 954 ++++++++++++++ .../txtar/md-tables/dagre/sketch.exp.svg | 1174 +++++++++++++++++ .../txtar/md-tables/elk/board.exp.json | 879 ++++++++++++ .../txtar/md-tables/elk/sketch.exp.svg | 1174 +++++++++++++++++ e2etests/txtar.txt | 160 +++ lib/textmeasure/markdown.go | 156 ++- 7 files changed, 4496 insertions(+), 2 deletions(-) create mode 100644 e2etests/testdata/txtar/md-tables/dagre/board.exp.json create mode 100644 e2etests/testdata/txtar/md-tables/dagre/sketch.exp.svg create mode 100644 e2etests/testdata/txtar/md-tables/elk/board.exp.json create mode 100644 e2etests/testdata/txtar/md-tables/elk/sketch.exp.svg diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 56f152973..74d61119b 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,6 +1,7 @@ #### Features ๐Ÿš€ - Vars: vars in markdown blocks are substituted [#2218](https://github.com/terrastruct/d2/pull/2218) +- Markdown: Github-flavored tables work in `md` blocks [#2221](https://github.com/terrastruct/d2/pull/2221) #### Improvements ๐Ÿงน diff --git a/e2etests/testdata/txtar/md-tables/dagre/board.exp.json b/e2etests/testdata/txtar/md-tables/dagre/board.exp.json new file mode 100644 index 000000000..c93eefc67 --- /dev/null +++ b/e2etests/testdata/txtar/md-tables/dagre/board.exp.json @@ -0,0 +1,954 @@ +{ + "name": "", + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "savings", + "type": "text", + "pos": { + "x": 169, + "y": 50 + }, + "width": 343, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#e8f4f8", + "stroke": "#4a90e2", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Month | Savings | Expenses | Balance |\n| -------- | ------- | -------- | ------- |\n| January | $250 | $150 | $100 |\n| February | $80 | $200 | -$120 |\n| March | $420 | $180 | $240 |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 343, + "labelHeight": 150, + "zIndex": 0, + "level": 1 + }, + { + "id": "status", + "type": "text", + "pos": { + "x": 270, + "y": 321 + }, + "width": 142, + "height": 113, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#f8e8e8", + "stroke": "#e24a4a", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Status | Count |\n| ------ | ----- |\n| Done | 42 |\n| Todo | 17 |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 142, + "labelHeight": 113, + "zIndex": 0, + "level": 1 + }, + { + "id": "metrics", + "type": "text", + "pos": { + "x": 254, + "y": 655 + }, + "width": 173, + "height": 335, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#f0f8e8", + "stroke": "#82e24a", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Metric | Value |\n| --------- | ------- |\n| Uptime | 99.9% |\n| Latency | 150ms |\n| Errors | 0.01% |\n| Requests | 15k/min |\n| CPU | 45% |\n| Memory | 68% |\n| Disk | 72% |\n| Network | 33% |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 173, + "labelHeight": 335, + "zIndex": 0, + "level": 1 + }, + { + "id": "financial", + "type": "rectangle", + "pos": { + "x": 562, + "y": 20 + }, + "width": 389, + "height": 444, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#e8f4f8", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Financial Overview", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 220, + "labelHeight": 36, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "financial.monthly", + "type": "text", + "pos": { + "x": 592, + "y": 50 + }, + "width": 329, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Month | Revenue | Costs | Margin |\n| --------- | -------- | -------- | ------ |\n| January | $25,000 | $18,000 | 28% |\n| February | $28,500 | $19,200 | 33% |\n| March | $31,200 | $21,500 | 31% |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 329, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "financial.quarterly", + "type": "text", + "pos": { + "x": 599, + "y": 321 + }, + "width": 315, + "height": 113, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Quarter | Target | Actual | Variance |\n|:--------|-------:|:------:|:---------|\n| Q1 2024 | $75K | $84.7K | +12.9% |\n| Q2 2024 | $82K | - | - |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 315, + "labelHeight": 113, + "zIndex": 0, + "level": 2 + }, + { + "id": "monitoring", + "type": "rectangle", + "pos": { + "x": 548, + "y": 718 + }, + "width": 417, + "height": 573, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#f8e8e8", + "stroke": "B1", + "shadow": false, + "3d": true, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "System Health", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 168, + "labelHeight": 36, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "monitoring.availability", + "type": "text", + "pos": { + "x": 578, + "y": 748 + }, + "width": 357, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Service | Status | Uptime | Last Incident |\n|:-----------|:------:|:------:|:--------------|\n| API | โœ… | 99.9% | 15 days ago |\n| Database | โœ… | 99.8% | 3 days ago |\n| Cache | โš ๏ธ | 98.5% | 1 hour ago |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 357, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "monitoring.performance", + "type": "text", + "pos": { + "x": 585, + "y": 1111 + }, + "width": 344, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Metric | P50 | P90 | P99 |\n|:----------------|:-----:|:-----:|:-----:|\n| Response Time | 120ms | 350ms | 750ms |\n| DB Query Time | 45ms | 180ms | 450ms |\n| Cache Latency | 5ms | 12ms | 30ms |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 344, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "projects", + "type": "rectangle", + "pos": { + "x": 10, + "y": 1452 + }, + "width": 1008, + "height": 210, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#e8f8e8", + "stroke": "#4a90e2", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": true, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Project Tracking", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 190, + "labelHeight": 36, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "projects.status", + "type": "text", + "pos": { + "x": 40, + "y": 1482 + }, + "width": 425, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Project | Priority | Progress | Due Date | Owner |\n|---------|:--------:|:--------:|:---------|:------|\n| Alpha | HIGH | ![p](https://progress.com/80) 80% | 2024-04-01 | Alice |\n| Beta | MEDIUM | ![p](https://progress.com/45) 45% | 2024-05-15 | Bob |\n| Gamma | LOW | ![p](https://progress.com/20) 20% | 2024-06-30 | Carol |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 425, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "projects.risks", + "type": "text", + "pos": { + "x": 525, + "y": 1482 + }, + "width": 463, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Risk ID | Description | Impact | Mitigation |\n|:-------:|:------------|:------:|:-----------|\n| R1 | Resource shortage | ๐Ÿ”ด High | Hire contractors |\n| R2 | Technical debt | ๐ŸŸก Med | Code review |\n| R3 | Scope creep | ๐ŸŸข Low | Clear requirements |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 463, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "team", + "type": "rectangle", + "pos": { + "x": 551, + "y": 1823 + }, + "width": 411, + "height": 247, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#f8f0e8", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Team Analytics", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 177, + "labelHeight": 36, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "team.velocity", + "type": "text", + "pos": { + "x": 581, + "y": 1853 + }, + "width": 351, + "height": 187, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Sprint | Points | Completed | Carryover |\n|:-------|:------:|:---------:|:---------:|\n| SP-1 | 34 | 30 | 4 |\n| SP-2 | 38 | 35 | 3 |\n| SP-3 | 42 | 40 | 2 |\n| Average| 38 | 35 | 3 |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 351, + "labelHeight": 187, + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "(savings -> status)[0]", + "src": "savings", + "srcArrow": "none", + "dst": "status", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 5, + "strokeWidth": 2, + "stroke": "B2", + "borderRadius": 10, + "label": "Triggers", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 54, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 340.5, + "y": 199.5 + }, + { + "x": 340.5, + "y": 248.3000030517578 + }, + { + "x": 340.5, + "y": 272.70001220703125 + }, + { + "x": 340.5, + "y": 321.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(status -> metrics)[0]", + "src": "status", + "srcArrow": "none", + "dst": "metrics", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Monitors", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 58, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 340.5, + "y": 434 + }, + { + "x": 340.5, + "y": 474 + }, + { + "x": 340.5, + "y": 496.1000061035156 + }, + { + "x": 340.5, + "y": 514.25 + }, + { + "x": 340.5, + "y": 532.4000244140625 + }, + { + "x": 340.5, + "y": 615 + }, + { + "x": 340.5, + "y": 655 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "financial.(monthly -> quarterly)[0]", + "src": "financial.monthly", + "srcArrow": "none", + "dst": "financial.quarterly", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Aggregates", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 756.5, + "y": 199.5 + }, + { + "x": 756.5, + "y": 248.3000030517578 + }, + { + "x": 756.5, + "y": 272.70001220703125 + }, + { + "x": 756.5, + "y": 321.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "monitoring.(availability -> performance)[0]", + "src": "monitoring.availability", + "srcArrow": "none", + "dst": "monitoring.performance", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Affects", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 46, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 756.5, + "y": 898.5 + }, + { + "x": 756.5, + "y": 1020.0999755859375 + }, + { + "x": 756.5, + "y": 1062.699951171875 + }, + { + "x": 756.5, + "y": 1111.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(financial -> monitoring)[0]", + "src": "financial", + "srcArrow": "none", + "dst": "monitoring", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Impacts", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 54, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 756.5, + "y": 463.5 + }, + { + "x": 756.5, + "y": 528.2999877929688 + }, + { + "x": 756.5, + "y": 568.0999755859375 + }, + { + "x": 756.5, + "y": 662.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(monitoring -> projects)[0]", + "src": "monitoring", + "srcArrow": "none", + "dst": "projects", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 5, + "strokeWidth": 2, + "stroke": "B2", + "borderRadius": 10, + "label": "Informs", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 51, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 756.5, + "y": 1290.5 + }, + { + "x": 756.5, + "y": 1355.300048828125 + }, + { + "x": 756.5, + "y": 1387.699951171875 + }, + { + "x": 756.5, + "y": 1452.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(projects -> team)[0]", + "src": "projects", + "srcArrow": "none", + "dst": "team", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Assigns", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 50, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 756.5, + "y": 1661.5 + }, + { + "x": 756.5, + "y": 1726.300048828125 + }, + { + "x": 756.5, + "y": 1750.5 + }, + { + "x": 756.5, + "y": 1782.5 + } + ], + "isCurve": true, + "animated": true, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/md-tables/dagre/sketch.exp.svg b/e2etests/testdata/txtar/md-tables/dagre/sketch.exp.svg new file mode 100644 index 000000000..72c7bd136 --- /dev/null +++ b/e2etests/testdata/txtar/md-tables/dagre/sketch.exp.svg @@ -0,0 +1,1174 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MonthSavingsExpensesBalance
January$250$150$100
February$80$200-$120
March$420$180$240
+
+ + + + + + + + + + + + + + + + +
StatusCount
Done42
Todo17
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricValue
Uptime99.9%
Latency150ms
Errors0.01%
Requests15k/min
CPU45%
Memory68%
Disk72%
Network33%
+
Financial Overview + +System HealthProject TrackingTeam Analytics
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MonthRevenueCostsMargin
January$25,000$18,00028%
February$28,500$19,20033%
March$31,200$21,50031%
+
+ + + + + + + + + + + + + + + + + + + + + + +
QuarterTargetActualVariance
Q1 2024$75K$84.7K+12.9%
Q2 2024$82K--
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ServiceStatusUptimeLast Incident
APIโœ…99.9%15 days ago
Databaseโœ…99.8%3 days ago
Cacheโš ๏ธ98.5%1 hour ago
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricP50P90P99
Response Time120ms350ms750ms
DB Query Time45ms180ms450ms
Cache Latency5ms12ms30ms
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProjectPriorityProgressDue DateOwner
AlphaHIGHp 80%2024-04-01Alice
BetaMEDIUMp 45%2024-05-15Bob
GammaLOWp 20%2024-06-30Carol
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Risk IDDescriptionImpactMitigation
R1Resource shortage๐Ÿ”ด HighHire contractors
R2Technical debt๐ŸŸก MedCode review
R3Scope creep๐ŸŸข LowClear requirements
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SprintPointsCompletedCarryover
SP-134304
SP-238353
SP-342402
Average38353
+
Triggers MonitorsAggregatesAffectsImpactsInformsAssigns + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/e2etests/testdata/txtar/md-tables/elk/board.exp.json b/e2etests/testdata/txtar/md-tables/elk/board.exp.json new file mode 100644 index 000000000..150eee838 --- /dev/null +++ b/e2etests/testdata/txtar/md-tables/elk/board.exp.json @@ -0,0 +1,879 @@ +{ + "name": "", + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "savings", + "type": "text", + "pos": { + "x": 12, + "y": 386 + }, + "width": 343, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#e8f4f8", + "stroke": "#4a90e2", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Month | Savings | Expenses | Balance |\n| -------- | ------- | -------- | ------- |\n| January | $250 | $150 | $100 |\n| February | $80 | $200 | -$120 |\n| March | $420 | $180 | $240 |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 343, + "labelHeight": 150, + "zIndex": 0, + "level": 1 + }, + { + "id": "status", + "type": "text", + "pos": { + "x": 112, + "y": 921 + }, + "width": 142, + "height": 113, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#f8e8e8", + "stroke": "#e24a4a", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Status | Count |\n| ------ | ----- |\n| Done | 42 |\n| Todo | 17 |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 142, + "labelHeight": 113, + "zIndex": 0, + "level": 1 + }, + { + "id": "metrics", + "type": "text", + "pos": { + "x": 97, + "y": 1419 + }, + "width": 173, + "height": 335, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#f0f8e8", + "stroke": "#82e24a", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Metric | Value |\n| --------- | ------- |\n| Uptime | 99.9% |\n| Latency | 150ms |\n| Errors | 0.01% |\n| Requests | 15k/min |\n| CPU | 45% |\n| Memory | 68% |\n| Disk | 72% |\n| Network | 33% |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 173, + "labelHeight": 335, + "zIndex": 0, + "level": 1 + }, + { + "id": "financial", + "type": "rectangle", + "pos": { + "x": 579, + "y": 12 + }, + "width": 429, + "height": 524, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#e8f4f8", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Financial Overview", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 220, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "financial.monthly", + "type": "text", + "pos": { + "x": 629, + "y": 62 + }, + "width": 329, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Month | Revenue | Costs | Margin |\n| --------- | -------- | -------- | ------ |\n| January | $25,000 | $18,000 | 28% |\n| February | $28,500 | $19,200 | 33% |\n| March | $31,200 | $21,500 | 31% |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 329, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "financial.quarterly", + "type": "text", + "pos": { + "x": 636, + "y": 373 + }, + "width": 315, + "height": 113, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Quarter | Target | Actual | Variance |\n|:--------|-------:|:------:|:---------|\n| Q1 2024 | $75K | $84.7K | +12.9% |\n| Q2 2024 | $82K | - | - |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 315, + "labelHeight": 113, + "zIndex": 0, + "level": 2 + }, + { + "id": "monitoring", + "type": "rectangle", + "pos": { + "x": 565, + "y": 712 + }, + "width": 442, + "height": 546, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#f8e8e8", + "stroke": "B1", + "shadow": false, + "3d": true, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "System Health", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 168, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "monitoring.availability", + "type": "text", + "pos": { + "x": 608, + "y": 754 + }, + "width": 357, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Service | Status | Uptime | Last Incident |\n|:-----------|:------:|:------:|:--------------|\n| API | โœ… | 99.9% | 15 days ago |\n| Database | โœ… | 99.8% | 3 days ago |\n| Cache | โš ๏ธ | 98.5% | 1 hour ago |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 357, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "monitoring.performance", + "type": "text", + "pos": { + "x": 614, + "y": 1065 + }, + "width": 344, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Metric | P50 | P90 | P99 |\n|:----------------|:-----:|:-----:|:-----:|\n| Response Time | 120ms | 350ms | 750ms |\n| DB Query Time | 45ms | 180ms | 450ms |\n| Cache Latency | 5ms | 12ms | 30ms |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 344, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "projects", + "type": "rectangle", + "pos": { + "x": 290, + "y": 1461 + }, + "width": 1008, + "height": 250, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#e8f8e8", + "stroke": "#4a90e2", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": true, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Project Tracking", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 190, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "projects.status", + "type": "text", + "pos": { + "x": 340, + "y": 1511 + }, + "width": 425, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Project | Priority | Progress | Due Date | Owner |\n|---------|:--------:|:--------:|:---------|:------|\n| Alpha | HIGH | ![p](https://progress.com/80) 80% | 2024-04-01 | Alice |\n| Beta | MEDIUM | ![p](https://progress.com/45) 45% | 2024-05-15 | Bob |\n| Gamma | LOW | ![p](https://progress.com/20) 20% | 2024-06-30 | Carol |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 425, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "projects.risks", + "type": "text", + "pos": { + "x": 785, + "y": 1511 + }, + "width": 463, + "height": 150, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Risk ID | Description | Impact | Mitigation |\n|:-------:|:------------|:------:|:-----------|\n| R1 | Resource shortage | ๐Ÿ”ด High | Hire contractors |\n| R2 | Technical debt | ๐ŸŸก Med | Code review |\n| R3 | Scope creep | ๐ŸŸข Low | Clear requirements |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 463, + "labelHeight": 150, + "zIndex": 0, + "level": 2 + }, + { + "id": "team", + "type": "rectangle", + "pos": { + "x": 568, + "y": 1915 + }, + "width": 451, + "height": 287, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#f8f0e8", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "Team Analytics", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 177, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "team.velocity", + "type": "text", + "pos": { + "x": 618, + "y": 1965 + }, + "width": 351, + "height": 187, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "N1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "| Sprint | Points | Completed | Carryover |\n|:-------|:------:|:---------:|:---------:|\n| SP-1 | 34 | 30 | 4 |\n| SP-2 | 38 | 35 | 3 |\n| SP-3 | 42 | 40 | 2 |\n| Average| 38 | 35 | 3 |", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 351, + "labelHeight": 187, + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "(savings -> status)[0]", + "src": "savings", + "srcArrow": "none", + "dst": "status", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 5, + "strokeWidth": 2, + "stroke": "B2", + "borderRadius": 10, + "label": "Triggers", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 54, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 183.5, + "y": 536 + }, + { + "x": 183.5, + "y": 921 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(status -> metrics)[0]", + "src": "status", + "srcArrow": "none", + "dst": "metrics", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Monitors", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 58, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 183.5, + "y": 1034 + }, + { + "x": 183.5, + "y": 1419 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "financial.(monthly -> quarterly)[0]", + "src": "financial.monthly", + "srcArrow": "none", + "dst": "financial.quarterly", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Aggregates", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 77, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 794, + "y": 212 + }, + { + "x": 794, + "y": 373 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "monitoring.(availability -> performance)[0]", + "src": "monitoring.availability", + "srcArrow": "none", + "dst": "monitoring.performance", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Affects", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 46, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 786.5, + "y": 904.5 + }, + { + "x": 786.5, + "y": 1065.5 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(financial -> monitoring)[0]", + "src": "financial", + "srcArrow": "none", + "dst": "monitoring", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Impacts", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 54, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 794, + "y": 536 + }, + { + "x": 794, + "y": 697 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(monitoring -> projects)[0]", + "src": "monitoring", + "srcArrow": "none", + "dst": "projects", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 5, + "strokeWidth": 2, + "stroke": "B2", + "borderRadius": 10, + "label": "Informs", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 51, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 794, + "y": 1257.5 + }, + { + "x": 794, + "y": 1461.5 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(projects -> team)[0]", + "src": "projects", + "srcArrow": "none", + "dst": "team", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "Assigns", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 50, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 794, + "y": 1711 + }, + { + "x": 794, + "y": 1915 + } + ], + "animated": true, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/md-tables/elk/sketch.exp.svg b/e2etests/testdata/txtar/md-tables/elk/sketch.exp.svg new file mode 100644 index 000000000..d63ebdc30 --- /dev/null +++ b/e2etests/testdata/txtar/md-tables/elk/sketch.exp.svg @@ -0,0 +1,1174 @@ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MonthSavingsExpensesBalance
January$250$150$100
February$80$200-$120
March$420$180$240
+
+ + + + + + + + + + + + + + + + +
StatusCount
Done42
Todo17
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricValue
Uptime99.9%
Latency150ms
Errors0.01%
Requests15k/min
CPU45%
Memory68%
Disk72%
Network33%
+
Financial Overview + +System HealthProject TrackingTeam Analytics
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MonthRevenueCostsMargin
January$25,000$18,00028%
February$28,500$19,20033%
March$31,200$21,50031%
+
+ + + + + + + + + + + + + + + + + + + + + + +
QuarterTargetActualVariance
Q1 2024$75K$84.7K+12.9%
Q2 2024$82K--
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ServiceStatusUptimeLast Incident
APIโœ…99.9%15 days ago
Databaseโœ…99.8%3 days ago
Cacheโš ๏ธ98.5%1 hour ago
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MetricP50P90P99
Response Time120ms350ms750ms
DB Query Time45ms180ms450ms
Cache Latency5ms12ms30ms
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ProjectPriorityProgressDue DateOwner
AlphaHIGHp 80%2024-04-01Alice
BetaMEDIUMp 45%2024-05-15Bob
GammaLOWp 20%2024-06-30Carol
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Risk IDDescriptionImpactMitigation
R1Resource shortage๐Ÿ”ด HighHire contractors
R2Technical debt๐ŸŸก MedCode review
R3Scope creep๐ŸŸข LowClear requirements
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
SprintPointsCompletedCarryover
SP-134304
SP-238353
SP-342402
Average38353
+
Triggers MonitorsAggregatesAffectsImpactsInformsAssigns + + + + + + + + + + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt index 193acb48e..a9d4bffa8 100644 --- a/e2etests/txtar.txt +++ b/e2etests/txtar.txt @@ -483,3 +483,163 @@ description: |md ``` | b -> description -> a + +-- md-tables -- +# Financial table +savings: ||md + | Month | Savings | Expenses | Balance | + | -------- | ------- | -------- | ------- | + | January | $250 | $150 | $100 | + | February | $80 | $200 | -$120 | + | March | $420 | $180 | $240 | +|| + +# Simple 2x2 table +status: ||md + | Status | Count | + | ------ | ----- | + | Done | 42 | + | Todo | 17 | +|| + +# Long table with many rows +metrics: ||md + | Metric | Value | + | --------- | ------- | + | Uptime | 99.9% | + | Latency | 150ms | + | Errors | 0.01% | + | Requests | 15k/min | + | CPU | 45% | + | Memory | 68% | + | Disk | 72% | + | Network | 33% | +|| + +# Connect tables with labeled edges +savings -> status: Triggers { + style: { + stroke-dash: 5 + } +} +status -> metrics: Monitors { + style: { + stroke-width: 2 + } +} + +# Add some styling +savings.style: { + fill: "#e8f4f8" + stroke: "#4a90e2" +} + +status.style: { + fill: "#f8e8e8" + stroke: "#e24a4a" +} + +metrics.style: { + fill: "#f0f8e8" + stroke: "#82e24a" +} + +# Container for financial data +financial: { + label: "Financial Overview" + style.stroke-width: 2 + + monthly: ||md + | Month | Revenue | Costs | Margin | + | --------- | -------- | -------- | ------ | + | January | $25,000 | $18,000 | 28% | + | February | $28,500 | $19,200 | 33% | + | March | $31,200 | $21,500 | 31% | + || + + quarterly: ||md + | Quarter | Target | Actual | Variance | + |:--------|-------:|:------:|:---------| + | Q1 2024 | $75K | $84.7K | +12.9% | + | Q2 2024 | $82K | - | - | + || + + monthly -> quarterly: "Aggregates" +} + +# Container for system metrics +monitoring: { + label: "System Health" + style.3d: true + + availability: ||md + | Service | Status | Uptime | Last Incident | + |:-----------|:------:|:------:|:--------------| + | API | โœ… | 99.9% | 15 days ago | + | Database | โœ… | 99.8% | 3 days ago | + | Cache | โš ๏ธ | 98.5% | 1 hour ago | + || + + performance: ||md + | Metric | P50 | P90 | P99 | + |:----------------|:-----:|:-----:|:-----:| + | Response Time | 120ms | 350ms | 750ms | + | DB Query Time | 45ms | 180ms | 450ms | + | Cache Latency | 5ms | 12ms | 30ms | + || + + availability -> performance: "Affects" +} + +# Container for project status +projects: { + label: "Project Tracking" + style.stroke: "#4a90e2" + style.double-border: true + + status: ||md + | Project | Priority | Progress | Due Date | Owner | + |---------|:--------:|:--------:|:---------|:------| + | Alpha | HIGH | ![p](https://progress.com/80) 80% | 2024-04-01 | Alice | + | Beta | MEDIUM | ![p](https://progress.com/45) 45% | 2024-05-15 | Bob | + | Gamma | LOW | ![p](https://progress.com/20) 20% | 2024-06-30 | Carol | + || + + risks: ||md + | Risk ID | Description | Impact | Mitigation | + |:-------:|:------------|:------:|:-----------| + | R1 | Resource shortage | ๐Ÿ”ด High | Hire contractors | + | R2 | Technical debt | ๐ŸŸก Med | Code review | + | R3 | Scope creep | ๐ŸŸข Low | Clear requirements | + || +} + +# Container for team stats +team: { + label: "Team Analytics" + style.fill: "#f5f5f5" + + velocity: ||md + | Sprint | Points | Completed | Carryover | + |:-------|:------:|:---------:|:---------:| + | SP-1 | 34 | 30 | 4 | + | SP-2 | 38 | 35 | 3 | + | SP-3 | 42 | 40 | 2 | + | Average| 38 | 35 | 3 | + || +} + +# Connect containers +financial -> monitoring: "Impacts" +monitoring -> projects: "Informs" { + style.stroke-dash: 5 +} +projects -> team: "Assigns" { + style.animated: true +} + +# Styling +financial.style.fill: "#e8f4f8" +monitoring.style.fill: "#f8e8e8" +projects.style.fill: "#e8f8e8" +team.style.fill: "#f8f0e8" diff --git a/lib/textmeasure/markdown.go b/lib/textmeasure/markdown.go index 41e2ea135..f85423988 100644 --- a/lib/textmeasure/markdown.go +++ b/lib/textmeasure/markdown.go @@ -98,6 +98,7 @@ func init() { ), goldmark.WithExtensions( extension.Strikethrough, + extension.Table, ), ) } @@ -185,7 +186,8 @@ func isBlockElement(elType string) bool { "ol", "p", "pre", - "ul": + "ul", + "table", "thead", "tbody", "tr", "td", "th": // Added table elements here return true default: return false @@ -204,6 +206,7 @@ func hasAncestorElement(n *html.Node, elType string) bool { type blockAttrs struct { width, height, marginTop, marginBottom float64 + extraData interface{} } func (b *blockAttrs) isNotEmpty() bool { @@ -280,7 +283,7 @@ func (ruler *Ruler) measureNode(depth int, n *html.Node, fontFamily *d2fonts.Fon if debugMeasure { fmt.Printf("%stext(%v,%v)\n", depthStr, w, h) } - return blockAttrs{w + spaceWidths, h, 0, 0} + return blockAttrs{w + spaceWidths, h, 0, 0, 0} case html.ElementNode: isCode := false switch n.Data { @@ -429,6 +432,142 @@ func (ruler *Ruler) measureNode(depth int, n *html.Node, fontFamily *d2fonts.Fon block.height += Height_hr_em * float64(fontSize) block.marginTop = go2.Max(block.marginTop, MarginTopBottom_hr) block.marginBottom = go2.Max(block.marginBottom, MarginTopBottom_hr) + case "table": + var columnWidths []float64 + var tableHeight float64 + + // Border width for table (outer border) + tableBorder := 1.0 + + // Iterate over child nodes (tbody, thead, tr) + for child := n.FirstChild; child != nil; child = child.NextSibling { + if child.Type == html.ElementNode && (child.Data == "tbody" || child.Data == "thead" || child.Data == "tfoot") { + childAttrs := ruler.measureNode(depth+1, child, fontFamily, fontSize, fontStyle) + tableHeight += childAttrs.height + + if childColumnWidths, ok := childAttrs.extraData.([][]float64); ok { + columnWidths = mergeColumnWidths(columnWidths, childColumnWidths) + } + } else if child.Type == html.ElementNode && child.Data == "tr" { + rowAttrs := ruler.measureNode(depth+1, child, fontFamily, fontSize, fontStyle) + tableHeight += rowAttrs.height + + if rowCellWidths, ok := rowAttrs.extraData.([]float64); ok { + columnWidths = mergeColumnWidths(columnWidths, [][]float64{rowCellWidths}) + } + } + } + + // Calculate total table width including ALL borders + tableWidth := 0.0 + if len(columnWidths) > 0 { + // Add widths of all columns + for _, colWidth := range columnWidths { + tableWidth += colWidth + } + + // Add border for every column division (including outer borders) + tableWidth += float64(len(columnWidths)+1) * tableBorder + } + + // Add outer borders to height + tableHeight += 2 * tableBorder + + block.width = tableWidth + block.height = tableHeight + + case "thead", "tbody", "tfoot": + var sectionWidth, sectionHeight float64 + var sectionColumnWidths [][]float64 + + // Iterate over tr elements + for child := n.FirstChild; child != nil; child = child.NextSibling { + if child.Type == html.ElementNode && child.Data == "tr" { + childAttrs := ruler.measureNode(depth+1, child, fontFamily, fontSize, fontStyle) + sectionHeight += childAttrs.height + sectionWidth = go2.Max(sectionWidth, childAttrs.width) + + if rowCellWidths, ok := childAttrs.extraData.([]float64); ok { + sectionColumnWidths = append(sectionColumnWidths, rowCellWidths) + } + } + } + + block.width = sectionWidth + block.height = sectionHeight + block.extraData = sectionColumnWidths // Pass column widths back to table + + case "td", "th": + // Apply semibold style to header cells + cellFontStyle := fontStyle + if n.Data == "th" { + cellFontStyle = d2fonts.FONT_STYLE_SEMIBOLD + } + + // Measure cell content with appropriate font style + var cellContentWidth, cellContentHeight float64 + + for child := n.FirstChild; child != nil; child = child.NextSibling { + // Pass the header-specific font style to child measurements + childAttrs := ruler.measureNode(depth+1, child, fontFamily, fontSize, cellFontStyle) + cellContentWidth = go2.Max(cellContentWidth, childAttrs.width) + cellContentHeight += childAttrs.height + } + + block.width = cellContentWidth + block.height = cellContentHeight + + case "tr": + var rowWidth, rowHeight float64 + var cellWidths []float64 + + cellBorder := 1.0 + rowBorder := 1.0 + + maxCellHeight := 0.0 + cellCount := 0 + + // Check if this row is in a thead to determine default font style for cells + inHeader := hasAncestorElement(n, "thead") + rowFontStyle := fontStyle + if inHeader { + rowFontStyle = d2fonts.FONT_STYLE_SEMIBOLD + } + + for child := n.FirstChild; child != nil; child = child.NextSibling { + if child.Type == html.ElementNode && (child.Data == "td" || child.Data == "th") { + cellCount++ + + // Use semibold for th elements regardless of location + childFontStyle := rowFontStyle + if child.Data == "th" { + childFontStyle = d2fonts.FONT_STYLE_SEMIBOLD + } + + childAttrs := ruler.measureNode(depth+1, child, fontFamily, fontSize, childFontStyle) + cellPaddingH := 13.0 * 2 + cellPaddingV := 6.0 * 2 + + cellWidth := childAttrs.width + cellPaddingH + cellHeight := childAttrs.height + cellPaddingV + + cellWidths = append(cellWidths, cellWidth) + maxCellHeight = go2.Max(maxCellHeight, cellHeight) + } + } + + if cellCount > 0 { + for _, w := range cellWidths { + rowWidth += w + } + rowWidth += float64(cellCount+1) * cellBorder + } + + rowHeight = maxCellHeight + rowBorder + + block.width = rowWidth + block.height = rowHeight + block.extraData = cellWidths } if block.height > 0 && block.height < lineHeightPx { block.height = lineHeightPx @@ -440,3 +579,16 @@ func (ruler *Ruler) measureNode(depth int, n *html.Node, fontFamily *d2fonts.Fon } return blockAttrs{} } + +func mergeColumnWidths(existing []float64, new [][]float64) []float64 { + for _, rowWidths := range new { + for i, width := range rowWidths { + if i >= len(existing) { + existing = append(existing, width) + } else { + existing[i] = go2.Max(existing[i], width) + } + } + } + return existing +}