From 47ff6101ac76e5f70521b35552c9eb46021ea2d9 Mon Sep 17 00:00:00 2001 From: leverimmy Date: Wed, 5 Feb 2025 19:53:34 +0800 Subject: [PATCH 01/14] chore: fix a typo --- d2cli/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/d2cli/main.go b/d2cli/main.go index 3e9475149..dbf1ae2c4 100644 --- a/d2cli/main.go +++ b/d2cli/main.go @@ -50,7 +50,7 @@ import ( func Run(ctx context.Context, ms *xmain.State) (err error) { ctx = log.WithDefault(ctx) // These should be kept up-to-date with the d2 man page - watchFlag, err := ms.Opts.Bool("D2_WATCH", "watch", "w", false, "watch for changes to input and live reload. Use $HOST and $PORT to specify the listening address.\n(default localhost:0, which is will open on a randomly available local port).") + watchFlag, err := ms.Opts.Bool("D2_WATCH", "watch", "w", false, "watch for changes to input and live reload. Use $HOST and $PORT to specify the listening address.\n(default localhost:0, which will open on a randomly available local port).") if err != nil { return err } From 6b37d73bce752f70f7464f7d3f823e27a283b2d4 Mon Sep 17 00:00:00 2001 From: Ze-en Xiong Date: Thu, 13 Feb 2025 13:51:27 +0800 Subject: [PATCH 02/14] feat: update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index e465f01a5..7650c01b9 100644 --- a/README.md +++ b/README.md @@ -263,6 +263,7 @@ let us know and we'll be happy to include it here! - **Remark Plugin**: [https://github.com/mech-a/remark-d2](https://github.com/mech-a/remark-d2) - **VitePress Plugin**: [https://github.com/BadgerHobbs/vitepress-plugin-d2](https://github.com/BadgerHobbs/vitepress-plugin-d2) - **Zed extension**: [https://github.com/gabeidx/zed-d2](https://github.com/gabeidx/zed-d2) +- **Hexo blog extension**: [https://github.com/leverimmy/hexo-d2](https://github.com/leverimmy/hexo-d2) ### Misc From 9d31a41c1e402381f5174cd6dd356e4df230ffa6 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 13 Feb 2025 10:08:04 -0700 Subject: [PATCH 03/14] add test --- e2etests/txtar.txt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt index 46e7e9dfe..a7ee56453 100644 --- a/e2etests/txtar.txt +++ b/e2etests/txtar.txt @@ -759,3 +759,10 @@ b: { a.b -> b.c b.c -> a.a: {style.font-color: red; style.stroke: red; style.fill: mistyrose} + +-- sql-casing-panic -- + +asdf:{ + shape:sQl_table + zxcv +} From 029c17caac05bc515f6a46e31db011f97c225a9d Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 13 Feb 2025 10:26:59 -0700 Subject: [PATCH 04/14] d2compiler: fix shape value casing --- d2compiler/compile.go | 12 +- .../sql-casing-panic/dagre/board.exp.json | 130 ++++++++++++++++++ .../sql-casing-panic/dagre/sketch.exp.svg | 95 +++++++++++++ .../txtar/sql-casing-panic/elk/board.exp.json | 130 ++++++++++++++++++ .../txtar/sql-casing-panic/elk/sketch.exp.svg | 95 +++++++++++++ 5 files changed, 457 insertions(+), 5 deletions(-) create mode 100644 e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json create mode 100644 e2etests/testdata/txtar/sql-casing-panic/dagre/sketch.exp.svg create mode 100644 e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json create mode 100644 e2etests/testdata/txtar/sql-casing-panic/elk/sketch.exp.svg diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 1fa28054e..4a5e22c53 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -514,13 +514,14 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { c.compileLabel(attrs, f) c.compilePosition(attrs, f) case "shape": - in := d2target.IsShape(scalar.ScalarString()) - _, isArrowhead := d2target.Arrowheads[scalar.ScalarString()] + shapeVal := strings.ToLower(scalar.ScalarString()) + in := d2target.IsShape(shapeVal) + _, isArrowhead := d2target.Arrowheads[shapeVal] if !in && !isArrowhead { c.errorf(scalar, "unknown shape %q", scalar.ScalarString()) return } - attrs.Shape.Value = scalar.ScalarString() + attrs.Shape.Value = shapeVal if strings.EqualFold(attrs.Shape.Value, d2target.ShapeCode) { // Explicit code shape is plaintext. attrs.Language = d2target.ShapeText @@ -596,11 +597,12 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { attrs.Link.MapKey = f.LastPrimaryKey() case "direction": dirs := []string{"up", "down", "right", "left"} - if !go2.Contains(dirs, scalar.ScalarString()) { + val := strings.ToLower(scalar.ScalarString()) + if !go2.Contains(dirs, val) { c.errorf(scalar, `direction must be one of %v, got %q`, strings.Join(dirs, ", "), scalar.ScalarString()) return } - attrs.Direction.Value = scalar.ScalarString() + attrs.Direction.Value = val attrs.Direction.MapKey = f.LastPrimaryKey() case "constraint": if _, ok := scalar.(d2ast.String); !ok { diff --git a/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json b/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json new file mode 100644 index 000000000..4832c3200 --- /dev/null +++ b/e2etests/testdata/txtar/sql-casing-panic/dagre/board.exp.json @@ -0,0 +1,130 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "asdf", + "type": "sql_table", + "pos": { + "x": 0, + "y": 0 + }, + "width": 87, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "zxcv", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 37, + "labelHeight": 26 + }, + "type": { + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": null, + "reference": "" + } + ], + "label": "asdf", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 46, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + } + ], + "connections": [], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "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/sql-casing-panic/dagre/sketch.exp.svg b/e2etests/testdata/txtar/sql-casing-panic/dagre/sketch.exp.svg new file mode 100644 index 000000000..b0fe23859 --- /dev/null +++ b/e2etests/testdata/txtar/sql-casing-panic/dagre/sketch.exp.svg @@ -0,0 +1,95 @@ +asdfzxcv + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json b/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json new file mode 100644 index 000000000..ea369be7b --- /dev/null +++ b/e2etests/testdata/txtar/sql-casing-panic/elk/board.exp.json @@ -0,0 +1,130 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "asdf", + "type": "sql_table", + "pos": { + "x": 12, + "y": 12 + }, + "width": 87, + "height": 72, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N1", + "stroke": "N7", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": [ + { + "name": { + "label": "zxcv", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 37, + "labelHeight": 26 + }, + "type": { + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0 + }, + "constraint": null, + "reference": "" + } + ], + "label": "asdf", + "fontSize": 20, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 46, + "labelHeight": 31, + "zIndex": 0, + "level": 1, + "primaryAccentColor": "B2", + "secondaryAccentColor": "AA2", + "neutralAccentColor": "N2" + } + ], + "connections": [], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "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/sql-casing-panic/elk/sketch.exp.svg b/e2etests/testdata/txtar/sql-casing-panic/elk/sketch.exp.svg new file mode 100644 index 000000000..0b3e91c06 --- /dev/null +++ b/e2etests/testdata/txtar/sql-casing-panic/elk/sketch.exp.svg @@ -0,0 +1,95 @@ +asdfzxcv + + + \ No newline at end of file From 0613d52dacd7c6f19839c8987d49120a89eb2e8b Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 13 Feb 2025 10:38:09 -0700 Subject: [PATCH 05/14] ta --- .../d2compiler/TestCompile2/vars/basic/double-border.exp.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json b/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json index 8b7f30d5d..c2ef1281b 100644 --- a/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json +++ b/testdata/d2compiler/TestCompile2/vars/basic/double-border.exp.json @@ -219,7 +219,7 @@ }, "near_key": null, "shape": { - "value": "Circle" + "value": "circle" }, "direction": { "value": "" From 68bdf03a0c597ff608ef05e3ef6c12bda0fc5741 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 13 Feb 2025 10:39:13 -0700 Subject: [PATCH 06/14] next --- ci/release/changelogs/next.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index f3c0d2a77..dccf32f20 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -3,3 +3,5 @@ #### Improvements ๐Ÿงน #### Bugfixes โ›‘๏ธ + +- Compiler: fixes panic when `sql_shape` shape value had mixed casing [#2349](https://github.com/terrastruct/d2/pull/2349) From b005ae4c94a3b30e22ba4436057cf2a91b967579 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 13 Feb 2025 10:44:27 -0700 Subject: [PATCH 07/14] update util-go --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c1229c23d..2fa9a3620 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( golang.org/x/tools v0.25.0 golang.org/x/xerrors v0.0.0-20240903120638-7835f813f4da gonum.org/v1/plot v0.14.0 - oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896 + oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a ) require ( diff --git a/go.sum b/go.sum index 1cb060641..3aeb231d1 100644 --- a/go.sum +++ b/go.sum @@ -205,7 +205,7 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896 h1:g752s1ECv9FD8GunFOZRGWzjeR0cr/TZdSsjAnFEmL8= -oss.terrastruct.com/util-go v0.0.0-20241005222610-44c011a04896/go.mod h1:eMWv0sOtD9T2RUl90DLWfuShZCYp4NrsqNpI8eqO6U4= +oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a h1:UXF/Z9i9tOx/wqGUOn/T12wZeez1Gg0sAVKKl7YUDwM= +oss.terrastruct.com/util-go v0.0.0-20250213174338-243d8661088a/go.mod h1:eMWv0sOtD9T2RUl90DLWfuShZCYp4NrsqNpI8eqO6U4= rsc.io/pdf v0.1.1 h1:k1MczvYDUvJBe93bYd7wrZLLUEcLZAuF824/I4e5Xr4= rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= From 2c9695315c25c1ec2e5dfb82db0ba5223ab2099b Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 13 Feb 2025 10:56:54 -0700 Subject: [PATCH 08/14] upgrade upload-artifact in ci daily --- .github/workflows/daily.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/daily.yml b/.github/workflows/daily.yml index 3883537f4..1a7075a1f 100644 --- a/.github/workflows/daily.yml +++ b/.github/workflows/daily.yml @@ -20,7 +20,7 @@ jobs: env: GITHUB_TOKEN: ${{ secrets._GITHUB_TOKEN }} DISCORD_WEBHOOK_URL: ${{ secrets.DISCORD_WEBHOOK_URL }} - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 if: always() with: name: d2chaos From 7ac918fc37ba2f1f37ad372311bcfcfbb3524ec9 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Thu, 13 Feb 2025 11:22:47 -0700 Subject: [PATCH 09/14] fmt example --- docs/examples/wcc/wcc.d2 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/examples/wcc/wcc.d2 b/docs/examples/wcc/wcc.d2 index f47593792..689f18b5b 100644 --- a/docs/examples/wcc/wcc.d2 +++ b/docs/examples/wcc/wcc.d2 @@ -34,7 +34,7 @@ layers: { link: steps.1 style.font-size: 24 } - + steps: { 1: { titled: Earn pre-requisite titles (IM) @@ -150,7 +150,7 @@ layers: { best of 14 games -> tiebreaks: if needed tiebreaks.link: layers.tiebreaks - + layers: { tiebreaks: { description: |md From 20c2c592ebe8f66f9a51fe59753d1cdc5552d2dc Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Sun, 16 Feb 2025 21:49:49 -0700 Subject: [PATCH 10/14] d2svg: implement connection icons --- ci/release/changelogs/next.md | 2 + d2exporter/export.go | 11 +- d2renderers/d2svg/d2svg.go | 39 ++- d2target/d2target.go | 35 +- .../connection-icons/dagre/board.exp.json | 306 ++++++++++++++++++ .../connection-icons/dagre/sketch.exp.svg | 106 ++++++ .../txtar/connection-icons/elk/board.exp.json | 288 +++++++++++++++++ .../txtar/connection-icons/elk/sketch.exp.svg | 106 ++++++ e2etests/txtar.txt | 9 + 9 files changed, 896 insertions(+), 6 deletions(-) create mode 100644 e2etests/testdata/txtar/connection-icons/dagre/board.exp.json create mode 100644 e2etests/testdata/txtar/connection-icons/dagre/sketch.exp.svg create mode 100644 e2etests/testdata/txtar/connection-icons/elk/board.exp.json create mode 100644 e2etests/testdata/txtar/connection-icons/elk/sketch.exp.svg diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index dccf32f20..a3776ba8d 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,5 +1,7 @@ #### Features ๐Ÿš€ +- Icons: connections can include icons [#12](https://github.com/terrastruct/d2/issues/12) + #### Improvements ๐Ÿงน #### Bugfixes โ›‘๏ธ diff --git a/d2exporter/export.go b/d2exporter/export.go index 8b676e554..672bae188 100644 --- a/d2exporter/export.go +++ b/d2exporter/export.go @@ -8,6 +8,7 @@ import ( "oss.terrastruct.com/util-go/go2" + "oss.terrastruct.com/d2/d2ast" "oss.terrastruct.com/d2/d2graph" "oss.terrastruct.com/d2/d2parser" "oss.terrastruct.com/d2/d2renderers/d2fonts" @@ -15,6 +16,7 @@ import ( "oss.terrastruct.com/d2/d2themes" "oss.terrastruct.com/d2/lib/color" "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/label" ) func Export(ctx context.Context, g *d2graph.Graph, fontFamily *d2fonts.FontFamily) (*d2target.Diagram, error) { @@ -335,7 +337,14 @@ func toConnection(edge *d2graph.Edge, theme *d2themes.Theme) d2target.Connection if edge.Tooltip != nil { connection.Tooltip = edge.Tooltip.Value } - connection.Icon = edge.Icon + if edge.Icon != nil { + connection.Icon = edge.Icon + if edge.IconPosition != nil { + connection.IconPosition = (d2ast.LabelPositionsMapping[edge.IconPosition.Value]).String() + } else { + connection.IconPosition = label.InsideMiddleCenter.String() + } + } if edge.Style.Italic != nil { connection.Italic, _ = strconv.ParseBool(edge.Style.Italic.Value) diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index f4ece3b24..153b7b345 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -587,16 +587,51 @@ func drawConnection(writer io.Writer, diagramHash string, connection d2target.Co markerEnd = fmt.Sprintf(`marker-end="url(#%s)" `, id) } + if connection.Icon != nil { + iconPos := connection.GetIconPosition() + if iconPos != nil { + fmt.Fprintf(writer, ``, + html.EscapeString(connection.Icon.String()), + iconPos.X, + iconPos.Y, + d2target.DEFAULT_ICON_SIZE, + d2target.DEFAULT_ICON_SIZE, + ) + } + } + var labelTL *geo.Point if connection.Label != "" { labelTL = connection.GetLabelTopLeft() labelTL.X = math.Round(labelTL.X) labelTL.Y = math.Round(labelTL.Y) + maskTL := labelTL.Copy() + width := connection.LabelWidth + height := connection.LabelHeight + + if connection.Icon != nil { + width += d2target.CONNECTION_ICON_LABEL_GAP + d2target.DEFAULT_ICON_SIZE + maskTL.X -= float64(d2target.CONNECTION_ICON_LABEL_GAP + d2target.DEFAULT_ICON_SIZE) + } + if label.FromString(connection.LabelPosition).IsOnEdge() { - labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight, 1) + labelMask = makeLabelMask(maskTL, width, height, 1) } else { - labelMask = makeLabelMask(labelTL, connection.LabelWidth, connection.LabelHeight, 0.75) + labelMask = makeLabelMask(maskTL, width, height, 0.75) + } + } else if connection.Icon != nil { + iconPos := connection.GetIconPosition() + if iconPos != nil { + maskTL := &geo.Point{ + X: iconPos.X, + Y: iconPos.Y, + } + if label.FromString(connection.IconPosition).IsOnEdge() { + labelMask = makeLabelMask(maskTL, d2target.DEFAULT_ICON_SIZE, d2target.DEFAULT_ICON_SIZE, 1) + } else { + labelMask = makeLabelMask(maskTL, d2target.DEFAULT_ICON_SIZE, d2target.DEFAULT_ICON_SIZE, 0.75) + } } } diff --git a/d2target/d2target.go b/d2target/d2target.go index 54c1f2bb4..266e0f184 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -34,6 +34,8 @@ const ( MIN_ARROWHEAD_STROKE_WIDTH = 2 ARROWHEAD_PADDING = 2. + + CONNECTION_ICON_LABEL_GAP = 8 ) var BorderOffset = geo.NewVector(5, 5) @@ -609,13 +611,40 @@ type Connection struct { Route []*geo.Point `json:"route"` IsCurve bool `json:"isCurve,omitempty"` - Animated bool `json:"animated"` - Tooltip string `json:"tooltip"` - Icon *url.URL `json:"icon"` + Animated bool `json:"animated"` + Tooltip string `json:"tooltip"` + Icon *url.URL `json:"icon"` + IconPosition string `json:"iconPosition,omitempty"` ZIndex int `json:"zIndex"` } +func (c *Connection) GetIconPosition() *geo.Point { + if c.Icon == nil { + return nil + } + + if c.Label != "" { + labelTL := c.GetLabelTopLeft() + if labelTL != nil { + // Position icon to the left of the label with a small gap + return &geo.Point{ + X: labelTL.X - CONNECTION_ICON_LABEL_GAP - DEFAULT_ICON_SIZE, + Y: labelTL.Y + float64(c.LabelHeight)/2 - DEFAULT_ICON_SIZE/2, + } + } + } + + point, _ := label.FromString(c.IconPosition).GetPointOnRoute( + c.Route, + float64(c.StrokeWidth), + -1, + float64(DEFAULT_ICON_SIZE), + float64(DEFAULT_ICON_SIZE), + ) + return point +} + func BaseConnection() *Connection { return &Connection{ SrcArrow: NoArrowhead, diff --git a/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json b/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json new file mode 100644 index 000000000..96c74348f --- /dev/null +++ b/e2etests/testdata/txtar/connection-icons/dagre/board.exp.json @@ -0,0 +1,306 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "a", + "type": "rectangle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b", + "type": "rectangle", + "pos": { + "x": 186, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "c", + "type": "rectangle", + "pos": { + "x": 339, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(a -> b)[0]", + "src": "a", + "srcArrow": "none", + "dst": "b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "hello", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 52.5, + "y": 33 + }, + { + "x": 106.0999984741211, + "y": 33 + }, + { + "x": 132.89999389648438, + "y": 33 + }, + { + "x": 186.5, + "y": 33 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0 + }, + { + "id": "(b -> c)[0]", + "src": "b", + "srcArrow": "none", + "dst": "c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 239, + "y": 33 + }, + { + "x": 279, + "y": 33 + }, + { + "x": 299, + "y": 33 + }, + { + "x": 339, + "y": 33 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "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": "", + "animated": false, + "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/connection-icons/dagre/sketch.exp.svg b/e2etests/testdata/txtar/connection-icons/dagre/sketch.exp.svg new file mode 100644 index 000000000..5e302b9c2 --- /dev/null +++ b/e2etests/testdata/txtar/connection-icons/dagre/sketch.exp.svg @@ -0,0 +1,106 @@ +abc hello + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/connection-icons/elk/board.exp.json b/e2etests/testdata/txtar/connection-icons/elk/board.exp.json new file mode 100644 index 000000000..18f1b8346 --- /dev/null +++ b/e2etests/testdata/txtar/connection-icons/elk/board.exp.json @@ -0,0 +1,288 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "a", + "type": "rectangle", + "pos": { + "x": 12, + "y": 12 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "b", + "type": "rectangle", + "pos": { + "x": 238, + "y": 12 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "c", + "type": "rectangle", + "pos": { + "x": 361, + "y": 12 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "(a -> b)[0]", + "src": "a", + "srcArrow": "none", + "dst": "b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "hello", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 65, + "y": 45 + }, + { + "x": 238, + "y": 45 + } + ], + "animated": false, + "tooltip": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0 + }, + { + "id": "(b -> c)[0]", + "src": "b", + "srcArrow": "none", + "dst": "c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 291, + "y": 45 + }, + { + "x": 361, + "y": 45 + } + ], + "animated": false, + "tooltip": "", + "icon": { + "Scheme": "https", + "Opaque": "", + "User": null, + "Host": "icons.terrastruct.com", + "Path": "/essentials/213-alarm.svg", + "RawPath": "/essentials%2F213-alarm.svg", + "OmitHost": false, + "ForceQuery": false, + "RawQuery": "", + "Fragment": "", + "RawFragment": "" + }, + "iconPosition": "INSIDE_MIDDLE_CENTER", + "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": "", + "animated": false, + "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/connection-icons/elk/sketch.exp.svg b/e2etests/testdata/txtar/connection-icons/elk/sketch.exp.svg new file mode 100644 index 000000000..be28cc05d --- /dev/null +++ b/e2etests/testdata/txtar/connection-icons/elk/sketch.exp.svg @@ -0,0 +1,106 @@ +abc hello + + + + + + + \ No newline at end of file diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt index a7ee56453..fcc6a7619 100644 --- a/e2etests/txtar.txt +++ b/e2etests/txtar.txt @@ -766,3 +766,12 @@ asdf:{ shape:sQl_table zxcv } + +-- connection-icons -- +direction: right +a -> b: hello { + icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg +} +b -> c: { + icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg +} From 69f1ed946771e0dff31eb10723ac75a14fcb364a Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Mon, 17 Feb 2025 09:59:34 -0700 Subject: [PATCH 11/14] readme: add studio button --- README.md | 6 +++++- docs/assets/playground_button.png | Bin 22772 -> 7035 bytes docs/assets/studio_button.png | Bin 0 -> 6934 bytes 3 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 docs/assets/studio_button.png diff --git a/README.md b/README.md index 7650c01b9..12133f941 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ A modern diagram scripting language that turns text to diagrams. -[Docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) | [Playground](https://play.d2lang.com) +[Docs](https://d2lang.com) | [Cheat sheet](./docs/assets/cheat_sheet.pdf) | [Comparisons](https://text-to-diagram.com) | [Playground](https://play.d2lang.com) | [IDE](https://app.terrastruct.com) [![ci](https://github.com/terrastruct/d2/actions/workflows/ci.yml/badge.svg)](https://github.com/terrastruct/d2/actions/workflows/ci.yml) [![daily](https://github.com/terrastruct/d2/actions/workflows/daily.yml/badge.svg)](https://github.com/terrastruct/d2/actions/workflows/daily.yml) @@ -16,6 +16,9 @@ D2 Playground button + +D2 Studio button + https://user-images.githubusercontent.com/3120367/206125010-bd1fea8e-248a-43e7-8f85-0bbfca0c6e2a.mp4 @@ -269,6 +272,7 @@ let us know and we'll be happy to include it here! - **Comparison site**: [https://github.com/terrastruct/text-to-diagram-site](https://github.com/terrastruct/text-to-diagram-site) - **Playground**: [https://github.com/terrastruct/d2-playground](https://github.com/terrastruct/d2-playground) +- **IDE (paid)**: [https://app.terrastruct.com](https://app.terrastruct.com) - **Language docs**: [https://github.com/terrastruct/d2-docs](https://github.com/terrastruct/d2-docs) - **Hosted icons**: [https://icons.terrastruct.com](https://icons.terrastruct.com) diff --git a/docs/assets/playground_button.png b/docs/assets/playground_button.png index b08fe380dab1b04ea0d8edbb37234341d80747ca..a0cd9a21228835db724715ace8b7cf2b1d6e09d3 100644 GIT binary patch literal 7035 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 literal 22772 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

ezmpE_{A5i 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 d&KXPB+-Fg0WH Date: Tue, 18 Feb 2025 18:37:49 +0000 Subject: [PATCH 12/14] compiler: support center in d2-config --- d2compiler/compile.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 4a5e22c53..e8af01b14 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -1455,6 +1455,12 @@ func compileConfig(ir *d2ir.Map) (*d2target.Config, error) { config.LayoutEngine = go2.Pointer(f.Primary().Value.ScalarString()) } + f = configMap.GetField(d2ast.FlatUnquotedString("center")) + if f != nil { + val, _ := strconv.ParseBool(f.Primary().Value.ScalarString()) + config.Center = &val + } + f = configMap.GetField(d2ast.FlatUnquotedString("theme-overrides")) if f != nil { overrides, err := compileThemeOverrides(f.Map()) From c8c4d30810da84b6f6abdd13b0fc6c7d4ad41193 Mon Sep 17 00:00:00 2001 From: delfino Date: Tue, 18 Feb 2025 19:02:36 +0000 Subject: [PATCH 13/14] updating changelog --- ci/release/changelogs/next.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index a3776ba8d..ea7e4f2aa 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -6,4 +6,6 @@ #### Bugfixes โ›‘๏ธ -- Compiler: fixes panic when `sql_shape` shape value had mixed casing [#2349](https://github.com/terrastruct/d2/pull/2349) +- Compiler: + - fixes panic when `sql_shape` shape value had mixed casing [#2349](https://github.com/terrastruct/d2/pull/2349) + - fixes support for `center` in `d2-config` [#2360](https://github.com/terrastruct/d2/pull/2360) From 1c240c562a166e932c0fad86cd37ff0f3ed70a44 Mon Sep 17 00:00:00 2001 From: Alexander Wang Date: Wed, 19 Feb 2025 08:45:21 -0700 Subject: [PATCH 14/14] fix delete underscore linked --- d2ir/compile.go | 5 - d2oracle/edit_test.go | 22 ++ .../TestDelete/underscore_linked.exp.json | 292 ++++++++++++++++++ 3 files changed, 314 insertions(+), 5 deletions(-) create mode 100644 testdata/d2oracle/TestDelete/underscore_linked.exp.json diff --git a/d2ir/compile.go b/d2ir/compile.go index 156f7a41c..60a35cf38 100644 --- a/d2ir/compile.go +++ b/d2ir/compile.go @@ -1070,11 +1070,6 @@ func (c *compiler) compileLink(f *Field, refctx *RefContext) { return } - if linkIDA[0].ScalarString() == "root" && linkIDA[0].IsUnquoted() { - c.errorf(refctx.Key.Key, "cannot refer to root in link") - return - } - if !linkIDA[0].IsUnquoted() { return } diff --git a/d2oracle/edit_test.go b/d2oracle/edit_test.go index 543440411..0c51d26da 100644 --- a/d2oracle/edit_test.go +++ b/d2oracle/edit_test.go @@ -6115,6 +6115,28 @@ y exp: `y a -> b c -> d +`, + }, + { + name: "underscore_linked", + text: `k + +layers: { + x: { + a + b: {link: _} + } +} +`, + key: `b`, + boardPath: []string{"x"}, + exp: `k + +layers: { + x: { + a + } +} `, }, { diff --git a/testdata/d2oracle/TestDelete/underscore_linked.exp.json b/testdata/d2oracle/TestDelete/underscore_linked.exp.json new file mode 100644 index 000000000..5ff160e38 --- /dev/null +++ b/testdata/d2oracle/TestDelete/underscore_linked.exp.json @@ -0,0 +1,292 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-7:0:32", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "value": [ + { + "string": "k", + "raw_string": "k" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + }, + { + "map_key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-6:1:31", + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-2:6:9", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:0:3-2:6:9", + "value": [ + { + "string": "layers", + "raw_string": "layers" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,2:8:11-6:1:31", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-5:3:29", + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-3:3:16", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:2:15-3:3:16", + "value": [ + { + "string": "x", + "raw_string": "x" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,3:5:18-5:3:29", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + } + ] + } + } + } + } + ] + } + } + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "k", + "id_val": "k", + "references": [ + { + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-0:1:1", + "value": [ + { + "string": "k", + "raw_string": "k" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "k" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ], + "layers": [ + { + "name": "x", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,0:0:0-1:0:0", + "nodes": [ + { + "map_key": { + "range": ",0:0:0-0:0:0", + "key": { + "range": ",0:0:0-0:0:0", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "primary": {}, + "value": {} + } + } + ] + }, + "root": { + "id": "", + "id_val": "", + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + }, + "edges": null, + "objects": [ + { + "id": "a", + "id_val": "a", + "references": [ + { + "key": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2oracle/TestDelete/underscore_linked.d2,4:4:24-4:5:25", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + } + ], + "attributes": { + "label": { + "value": "a" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null + }, + "zIndex": 0 + } + ] + } + ] + }, + "err": "" +}