diff --git a/d2ast/keywords.go b/d2ast/keywords.go index 65f67b775..d1454a51d 100644 --- a/d2ast/keywords.go +++ b/d2ast/keywords.go @@ -8,6 +8,7 @@ var ReservedKeywords map[string]struct{} // Non Style/Holder keywords. var SimpleReservedKeywords = map[string]struct{}{ "label": {}, + "legend-label": {}, "shape": {}, "icon": {}, "constraint": {}, diff --git a/d2compiler/compile.go b/d2compiler/compile.go index e8af01b14..28c6ba7f2 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -547,6 +547,10 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { attrs.Tooltip = &d2graph.Scalar{} attrs.Tooltip.Value = scalar.ScalarString() attrs.Tooltip.MapKey = f.LastPrimaryKey() + case "legend-label": + attrs.LegendLabel = &d2graph.Scalar{} + attrs.LegendLabel.Value = scalar.ScalarString() + attrs.LegendLabel.MapKey = f.LastPrimaryKey() case "width": _, err := strconv.Atoi(scalar.ScalarString()) if err != nil { diff --git a/d2compiler/compile_test.go b/d2compiler/compile_test.go index 31e11235f..fcd7eb454 100644 --- a/d2compiler/compile_test.go +++ b/d2compiler/compile_test.go @@ -5433,6 +5433,23 @@ b -> c assert.Equal(t, "red", g.Edges[0].Style.Stroke.Value) }, }, + { + name: "legend-label", + run: func(t *testing.T) { + g, _ := assertCompile(t, ` +a.legend-label: This is A +b: {legend-label: This is B} +a -> b: { + legend-label: "This is a->b" +} +`, ``) + assert.Equal(t, "a", g.Objects[0].ID) + assert.Equal(t, "This is A", g.Objects[0].LegendLabel.Value) + assert.Equal(t, "b", g.Objects[1].ID) + assert.Equal(t, "This is B", g.Objects[1].LegendLabel.Value) + assert.Equal(t, "This is a->b", g.Edges[0].LegendLabel.Value) + }, + }, } for _, tc := range tca { diff --git a/d2exporter/export.go b/d2exporter/export.go index 672bae188..fb820b782 100644 --- a/d2exporter/export.go +++ b/d2exporter/export.go @@ -196,6 +196,9 @@ func toShape(obj *d2graph.Object, g *d2graph.Graph) d2target.Shape { if obj.Tooltip != nil { shape.Tooltip = obj.Tooltip.Value } + if obj.LegendLabel != nil { + shape.LegendLabel = obj.LegendLabel.Value + } if obj.Style.Animated != nil { shape.Animated, _ = strconv.ParseBool(obj.Style.Animated.Value) } diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 0776849ab..52848bc1a 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -222,7 +222,8 @@ type Attributes struct { // These names are attached to the rendered elements in SVG // so that users can target them however they like outside of D2 - Classes []string `json:"classes,omitempty"` + Classes []string `json:"classes,omitempty"` + LegendLabel *Scalar `json:"legendLabel,omitempty"` } // ApplyTextTransform will alter the `Label.Value` of the current object based diff --git a/d2target/d2target.go b/d2target/d2target.go index 266e0f184..e78a264b7 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -485,6 +485,7 @@ type Shape struct { PrettyLink string `json:"prettyLink,omitempty"` Icon *url.URL `json:"icon"` IconPosition string `json:"iconPosition"` + LegendLabel string `json:"legendLabel,omitempty"` // Whether the shape should allow shapes behind it to bleed through // Currently just used for sequence diagram groups @@ -613,6 +614,7 @@ type Connection struct { Animated bool `json:"animated"` Tooltip string `json:"tooltip"` + LegendLabel string `json:"legendLabel,omitempty"` Icon *url.URL `json:"icon"` IconPosition string `json:"iconPosition,omitempty"` diff --git a/testdata/d2compiler/TestCompile2/globs/legend-label.exp.json b/testdata/d2compiler/TestCompile2/globs/legend-label.exp.json new file mode 100644 index 000000000..b2219a40b --- /dev/null +++ b/testdata/d2compiler/TestCompile2/globs/legend-label.exp.json @@ -0,0 +1,410 @@ +{ + "graph": { + "name": "", + "isFolderOnly": false, + "ast": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,0:0:0-6:0:99", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,1:0:1-1:25:26", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,1:0:1-1:14:15", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,1:0:1-1:1:2", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,1:2:3-1:14:15", + "value": [ + { + "string": "legend-label", + "raw_string": "legend-label" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,1:16:17-1:25:26", + "value": [ + { + "string": "This is A", + "raw_string": "This is A" + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:0:27-2:28:55", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:0:27-2:1:28", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:0:27-2:1:28", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:3:30-2:28:55", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:4:31-2:27:54", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:4:31-2:16:43", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:4:31-2:16:43", + "value": [ + { + "string": "legend-label", + "raw_string": "legend-label" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:18:45-2:27:54", + "value": [ + { + "string": "This is B", + "raw_string": "This is B" + } + ] + } + } + } + } + ] + } + } + } + }, + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:0:56-5:1:98", + "edges": [ + { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:0:56-3:6:62", + "src": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:0:56-3:1:57", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:0:56-3:1:57", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "src_arrow": "", + "dst": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:5:61-3:6:62", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:5:61-3:6:62", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + } + ] + }, + "dst_arrow": ">" + } + ], + "primary": {}, + "value": { + "map": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:8:64-5:1:98", + "nodes": [ + { + "map_key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,4:2:68-4:30:96", + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,4:2:68-4:14:80", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,4:2:68-4:14:80", + "value": [ + { + "string": "legend-label", + "raw_string": "legend-label" + } + ] + } + } + ] + }, + "primary": {}, + "value": { + "double_quoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,4:16:82-4:30:96", + "value": [ + { + "string": "This is a->b", + "raw_string": "This is a->b" + } + ] + } + } + } + } + ] + } + } + } + } + ] + }, + "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": [ + { + "index": 0, + "isCurve": false, + "src_arrow": false, + "dst_arrow": true, + "references": [ + { + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "" + }, + "direction": { + "value": "" + }, + "constraint": null, + "legendLabel": { + "value": "This is a->b" + } + }, + "zIndex": 0 + } + ], + "objects": [ + { + "id": "a", + "id_val": "a", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,1:0:1-1:14:15", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,1:0:1-1:1:2", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + }, + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,1:2:3-1:14:15", + "value": [ + { + "string": "legend-label", + "raw_string": "legend-label" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:0:56-3:1:57", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:0:56-3:1:57", + "value": [ + { + "string": "a", + "raw_string": "a" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "a" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null, + "legendLabel": { + "value": "This is A" + } + }, + "zIndex": 0 + }, + { + "id": "b", + "id_val": "b", + "references": [ + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:0:27-2:1:28", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,2:0:27-2:1:28", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": -1 + }, + { + "key": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:5:61-3:6:62", + "path": [ + { + "unquoted_string": { + "range": "d2/testdata/d2compiler/TestCompile2/globs/legend-label.d2,3:5:61-3:6:62", + "value": [ + { + "string": "b", + "raw_string": "b" + } + ] + } + } + ] + }, + "key_path_index": 0, + "map_key_edge_index": 0 + } + ], + "attributes": { + "label": { + "value": "b" + }, + "labelDimensions": { + "width": 0, + "height": 0 + }, + "style": {}, + "near_key": null, + "shape": { + "value": "rectangle" + }, + "direction": { + "value": "" + }, + "constraint": null, + "legendLabel": { + "value": "This is B" + } + }, + "zIndex": 0 + } + ] + }, + "err": null +}