label and icon position attributes

This commit is contained in:
Alexander Wang 2023-06-22 16:13:08 -07:00
parent d3b758610d
commit 6f9e478f59
No known key found for this signature in database
GPG key ID: D89FA31966BDBECE
9 changed files with 930 additions and 1 deletions

View file

@ -382,6 +382,47 @@ func (c *compiler) compileLabel(attrs *d2graph.Attributes, f d2ir.Node) {
attrs.Label.MapKey = f.LastPrimaryKey()
}
func (c *compiler) compilePosition(attrs *d2graph.Attributes, f *d2ir.Field) {
name := f.Name
if f.Map() != nil {
for _, f := range f.Map().Fields {
if f.Name == "near" {
if f.Primary() == nil {
c.errorf(f.LastPrimaryKey(), `invalid "near" field`)
} else {
scalar := f.Primary().Value
switch scalar := scalar.(type) {
case *d2ast.Null:
attrs.LabelPosition = nil
default:
if _, ok := d2graph.LabelPositions[scalar.ScalarString()]; !ok {
c.errorf(f.LastPrimaryKey(), `invalid "near" field`)
} else {
switch name {
case "label":
attrs.LabelPosition = &d2graph.Scalar{}
attrs.LabelPosition.Value = scalar.ScalarString()
attrs.LabelPosition.MapKey = f.LastPrimaryKey()
case "icon":
attrs.IconPosition = &d2graph.Scalar{}
attrs.IconPosition.Value = scalar.ScalarString()
attrs.IconPosition.MapKey = f.LastPrimaryKey()
}
}
}
}
} else {
if f.LastPrimaryKey() != nil {
c.errorf(f.LastPrimaryKey(), `unexpected field %s`, f.Name)
}
}
}
if len(f.Map().Edges) > 0 {
c.errorf(f.LastPrimaryKey(), "unexpected edges in map")
}
}
}
func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
if f.Primary() == nil {
if f.Composite != nil {
@ -402,6 +443,8 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
}
}
}
case "label", "icon":
c.compilePosition(attrs, f)
default:
c.errorf(f.LastPrimaryKey(), "reserved field %v does not accept composite", f.Name)
}
@ -412,6 +455,7 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
switch f.Name {
case "label":
c.compileLabel(attrs, f)
c.compilePosition(attrs, f)
case "shape":
in := d2target.IsShape(scalar.ScalarString())
_, isArrowhead := d2target.Arrowheads[scalar.ScalarString()]
@ -432,6 +476,7 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) {
return
}
attrs.Icon = iconURL
c.compilePosition(attrs, f)
case "near":
nearKey, err := d2parser.ParseKey(scalar.ScalarString())
if err != nil {

View file

@ -2371,6 +2371,77 @@ obj {
`,
expErr: `d2/testdata/d2compiler/TestCompile/near_near_const.d2:7:8: near keys cannot be set to an object with a constant near key`,
},
{
name: "label-near-parent",
text: `hey: sushi {
label.near: outside-top-left
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
tassert.Equal(t, "sushi", g.Objects[0].Attributes.Label.Value)
tassert.Equal(t, "outside-top-left", g.Objects[0].Attributes.LabelPosition.Value)
},
},
{
name: "label-near-composite-separate",
text: `hey: {
label: sushi
label.near: outside-top-left
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
tassert.Equal(t, "sushi", g.Objects[0].Attributes.Label.Value)
tassert.Equal(t, "outside-top-left", g.Objects[0].Attributes.LabelPosition.Value)
},
},
{
name: "label-near-composite-together",
text: `hey: {
label: sushi {
near: outside-top-left
}
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
tassert.Equal(t, "sushi", g.Objects[0].Attributes.Label.Value)
tassert.Equal(t, "outside-top-left", g.Objects[0].Attributes.LabelPosition.Value)
},
},
{
name: "icon-near-composite-together",
text: `hey: {
icon: https://asdf.com {
near: outside-top-left
}
}
`,
assertions: func(t *testing.T, g *d2graph.Graph) {
tassert.Equal(t, "asdf.com", g.Objects[0].Attributes.Icon.Host)
tassert.Equal(t, "outside-top-left", g.Objects[0].Attributes.IconPosition.Value)
},
},
{
name: "label-near-invalid-edge",
text: `hey: {
label: sushi {
near: outside-top-left
a -> b
}
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/label-near-invalid-edge.d2:2:3: unexpected edges in map`,
},
{
name: "label-near-invalid-field",
text: `hey: {
label: sushi {
near: outside-top-left
a
}
}
`,
expErr: `d2/testdata/d2compiler/TestCompile/label-near-invalid-field.d2:4:3: unexpected field a`,
},
{
name: "grid",
text: `hey: {

View file

@ -143,6 +143,9 @@ type Attributes struct {
VerticalGap *Scalar `json:"verticalGap,omitempty"`
HorizontalGap *Scalar `json:"horizontalGap,omitempty"`
LabelPosition *Scalar `json:"labelPosition,omitempty"`
IconPosition *Scalar `json:"iconPosition,omitempty"`
// 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"`
@ -1645,7 +1648,10 @@ var ReservedKeywordHolders = map[string]struct{}{
// CompositeReservedKeywords are reserved keywords that can hold composites
var CompositeReservedKeywords = map[string]struct{}{
"classes": {},
"classes": {},
"constraint": {},
"label": {},
"icon": {},
}
// StyleKeywords are reserved keywords which cannot exist outside of the "style" keyword
@ -1696,6 +1702,38 @@ var NearConstantsArray = []string{
}
var NearConstants map[string]struct{}
// LabelPositionsArray are the values that labels and icons can set `near` to
var LabelPositionsArray = []string{
"top-left",
"top-center",
"top-right",
"center-left",
"center-center",
"center-right",
"bottom-left",
"bottom-center",
"bottom-right",
"outside-top-left",
"outside-top-center",
"outside-top-right",
"outside-left-top",
"outside-left-center",
"outside-left-bottom",
"outside-right-top",
"outside-right-center",
"outside-right-bottom",
"outside-bottom-left",
"outside-bottom-center",
"outside-bottom-right",
}
var LabelPositions map[string]struct{}
var FillPatterns = []string{
"dots",
"lines",
@ -1734,6 +1772,11 @@ func init() {
for _, k := range NearConstantsArray {
NearConstants[k] = struct{}{}
}
LabelPositions = make(map[string]struct{}, len(LabelPositionsArray))
for _, k := range LabelPositionsArray {
LabelPositions[k] = struct{}{}
}
}
func (g *Graph) GetBoard(name string) *Graph {

View file

@ -0,0 +1,200 @@
{
"graph": {
"name": "",
"isFolderOnly": false,
"ast": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,0:0:0-5:0:64",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,0:0:0-4:1:63",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,0:0:0-0:3:3",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,0:0:0-0:3:3",
"value": [
{
"string": "hey",
"raw_string": "hey"
}
]
}
}
]
},
"primary": {},
"value": {
"map": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,0:5:5-4:1:63",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,1:1:8-3:3:61",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,1:1:8-1:5:12",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,1:1:8-1:5:12",
"value": [
{
"string": "icon",
"raw_string": "icon"
}
]
}
}
]
},
"primary": {
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,1:7:14-1:23:30",
"value": [
{
"string": "https://asdf.com",
"raw_string": "https://asdf.com"
}
]
}
},
"value": {
"map": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,1:24:31-3:3:61",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,2:2:35-2:24:57",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,2:2:35-2:6:39",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,2:2:35-2:6:39",
"value": [
{
"string": "near",
"raw_string": "near"
}
]
}
}
]
},
"primary": {},
"value": {
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,2:8:41-2:24:57",
"value": [
{
"string": "outside-top-left",
"raw_string": "outside-top-left"
}
]
}
}
}
}
]
}
}
}
}
]
}
}
}
}
]
},
"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": "hey",
"id_val": "hey",
"references": [
{
"key": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,0:0:0-0:3:3",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/icon-near-composite-together.d2,0:0:0-0:3:3",
"value": [
{
"string": "hey",
"raw_string": "hey"
}
]
}
}
]
},
"key_path_index": 0,
"map_key_edge_index": -1
}
],
"attributes": {
"label": {
"value": "hey"
},
"labelDimensions": {
"width": 0,
"height": 0
},
"style": {},
"icon": {
"Scheme": "https",
"Opaque": "",
"User": null,
"Host": "asdf.com",
"Path": "",
"RawPath": "",
"OmitHost": false,
"ForceQuery": false,
"RawQuery": "",
"Fragment": "",
"RawFragment": ""
},
"near_key": null,
"shape": {
"value": "rectangle"
},
"direction": {
"value": ""
},
"constraint": null,
"iconPosition": {
"value": "outside-top-left"
}
},
"zIndex": 0
}
]
},
"err": null
}

View file

@ -0,0 +1,192 @@
{
"graph": {
"name": "",
"isFolderOnly": false,
"ast": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,0:0:0-4:0:53",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,0:0:0-3:1:52",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,0:0:0-0:3:3",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,0:0:0-0:3:3",
"value": [
{
"string": "hey",
"raw_string": "hey"
}
]
}
}
]
},
"primary": {},
"value": {
"map": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,0:5:5-3:1:52",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,1:1:8-1:13:20",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,1:1:8-1:6:13",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,1:1:8-1:6:13",
"value": [
{
"string": "label",
"raw_string": "label"
}
]
}
}
]
},
"primary": {},
"value": {
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,1:8:15-1:13:20",
"value": [
{
"string": "sushi",
"raw_string": "sushi"
}
]
}
}
}
},
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,2:1:22-2:29:50",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,2:1:22-2:11:32",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,2:1:22-2:6:27",
"value": [
{
"string": "label",
"raw_string": "label"
}
]
}
},
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,2:7:28-2:11:32",
"value": [
{
"string": "near",
"raw_string": "near"
}
]
}
}
]
},
"primary": {},
"value": {
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,2:13:34-2:29:50",
"value": [
{
"string": "outside-top-left",
"raw_string": "outside-top-left"
}
]
}
}
}
}
]
}
}
}
}
]
},
"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": "hey",
"id_val": "hey",
"references": [
{
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,0:0:0-0:3:3",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-separate.d2,0:0:0-0:3:3",
"value": [
{
"string": "hey",
"raw_string": "hey"
}
]
}
}
]
},
"key_path_index": 0,
"map_key_edge_index": -1
}
],
"attributes": {
"label": {
"value": "sushi"
},
"labelDimensions": {
"width": 0,
"height": 0
},
"style": {},
"near_key": null,
"shape": {
"value": "rectangle"
},
"direction": {
"value": ""
},
"constraint": null,
"labelPosition": {
"value": "outside-top-left"
}
},
"zIndex": 0
}
]
},
"err": null
}

View file

@ -0,0 +1,187 @@
{
"graph": {
"name": "",
"isFolderOnly": false,
"ast": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,0:0:0-5:0:55",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,0:0:0-4:1:54",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,0:0:0-0:3:3",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,0:0:0-0:3:3",
"value": [
{
"string": "hey",
"raw_string": "hey"
}
]
}
}
]
},
"primary": {},
"value": {
"map": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,0:5:5-4:1:54",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,1:2:9-3:3:52",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,1:2:9-1:7:14",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,1:2:9-1:7:14",
"value": [
{
"string": "label",
"raw_string": "label"
}
]
}
}
]
},
"primary": {
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,1:9:16-1:14:21",
"value": [
{
"string": "sushi",
"raw_string": "sushi"
}
]
}
},
"value": {
"map": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,1:15:22-3:3:52",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,2:2:26-2:24:48",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,2:2:26-2:6:30",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,2:2:26-2:6:30",
"value": [
{
"string": "near",
"raw_string": "near"
}
]
}
}
]
},
"primary": {},
"value": {
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,2:8:32-2:24:48",
"value": [
{
"string": "outside-top-left",
"raw_string": "outside-top-left"
}
]
}
}
}
}
]
}
}
}
}
]
}
}
}
}
]
},
"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": "hey",
"id_val": "hey",
"references": [
{
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,0:0:0-0:3:3",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-composite-together.d2,0:0:0-0:3:3",
"value": [
{
"string": "hey",
"raw_string": "hey"
}
]
}
}
]
},
"key_path_index": 0,
"map_key_edge_index": -1
}
],
"attributes": {
"label": {
"value": "sushi"
},
"labelDimensions": {
"width": 0,
"height": 0
},
"style": {},
"near_key": null,
"shape": {
"value": "rectangle"
},
"direction": {
"value": ""
},
"constraint": null,
"labelPosition": {
"value": "outside-top-left"
}
},
"zIndex": 0
}
]
},
"err": null
}

View file

@ -0,0 +1,11 @@
{
"graph": null,
"err": {
"errs": [
{
"range": "d2/testdata/d2compiler/TestCompile/label-near-invalid-edge.d2,1:2:9-4:3:61",
"errmsg": "d2/testdata/d2compiler/TestCompile/label-near-invalid-edge.d2:2:3: unexpected edges in map"
}
]
}
}

View file

@ -0,0 +1,11 @@
{
"graph": null,
"err": {
"errs": [
{
"range": "d2/testdata/d2compiler/TestCompile/label-near-invalid-field.d2,3:2:51-3:3:52",
"errmsg": "d2/testdata/d2compiler/TestCompile/label-near-invalid-field.d2:4:3: unexpected field a"
}
]
}
}

View file

@ -0,0 +1,169 @@
{
"graph": {
"name": "",
"isFolderOnly": false,
"ast": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,0:0:0-3:0:45",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,0:0:0-2:1:44",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,0:0:0-0:3:3",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,0:0:0-0:3:3",
"value": [
{
"string": "hey",
"raw_string": "hey"
}
]
}
}
]
},
"primary": {
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,0:5:5-0:10:10",
"value": [
{
"string": "sushi",
"raw_string": "sushi"
}
]
}
},
"value": {
"map": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,0:11:11-2:1:44",
"nodes": [
{
"map_key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,1:1:14-1:29:42",
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,1:1:14-1:11:24",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,1:1:14-1:6:19",
"value": [
{
"string": "label",
"raw_string": "label"
}
]
}
},
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,1:7:20-1:11:24",
"value": [
{
"string": "near",
"raw_string": "near"
}
]
}
}
]
},
"primary": {},
"value": {
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,1:13:26-1:29:42",
"value": [
{
"string": "outside-top-left",
"raw_string": "outside-top-left"
}
]
}
}
}
}
]
}
}
}
}
]
},
"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": "hey",
"id_val": "hey",
"references": [
{
"key": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,0:0:0-0:3:3",
"path": [
{
"unquoted_string": {
"range": "d2/testdata/d2compiler/TestCompile/label-near-parent.d2,0:0:0-0:3:3",
"value": [
{
"string": "hey",
"raw_string": "hey"
}
]
}
}
]
},
"key_path_index": 0,
"map_key_edge_index": -1
}
],
"attributes": {
"label": {
"value": "sushi"
},
"labelDimensions": {
"width": 0,
"height": 0
},
"style": {},
"near_key": null,
"shape": {
"value": "rectangle"
},
"direction": {
"value": ""
},
"constraint": null,
"labelPosition": {
"value": "outside-top-left"
}
},
"zIndex": 0
}
]
},
"err": null
}