diff --git a/d2compiler/compile.go b/d2compiler/compile.go index 817f4169f..867b8a756 100644 --- a/d2compiler/compile.go +++ b/d2compiler/compile.go @@ -420,6 +420,45 @@ func (c *compiler) compileReserved(attrs *d2graph.Attributes, f *d2ir.Field) { attrs.GridColumns = &d2graph.Scalar{} attrs.GridColumns.Value = scalar.ScalarString() attrs.GridColumns.MapKey = f.LastPrimaryKey() + case "grid-gap": + v, err := strconv.Atoi(scalar.ScalarString()) + if err != nil { + c.errorf(scalar, "non-integer grid-gap %#v: %s", scalar.ScalarString(), err) + return + } + if v < 0 { + c.errorf(scalar, "grid-gap must be a non-negative integer: %#v", scalar.ScalarString()) + return + } + attrs.GridGap = &d2graph.Scalar{} + attrs.GridGap.Value = scalar.ScalarString() + attrs.GridGap.MapKey = f.LastPrimaryKey() + case "grid-gap-rows": + v, err := strconv.Atoi(scalar.ScalarString()) + if err != nil { + c.errorf(scalar, "non-integer grid-gap-rows %#v: %s", scalar.ScalarString(), err) + return + } + if v < 0 { + c.errorf(scalar, "grid-gap-rows must be a non-negative integer: %#v", scalar.ScalarString()) + return + } + attrs.GridGapRows = &d2graph.Scalar{} + attrs.GridGapRows.Value = scalar.ScalarString() + attrs.GridGapRows.MapKey = f.LastPrimaryKey() + case "grid-gap-columns": + v, err := strconv.Atoi(scalar.ScalarString()) + if err != nil { + c.errorf(scalar, "non-integer grid-gap-columns %#v: %s", scalar.ScalarString(), err) + return + } + if v < 0 { + c.errorf(scalar, "grid-gap-columns must be a non-negative integer: %#v", scalar.ScalarString()) + return + } + attrs.GridGapColumns = &d2graph.Scalar{} + attrs.GridGapColumns.Value = scalar.ScalarString() + attrs.GridGapColumns.MapKey = f.LastPrimaryKey() case "class": attrs.Classes = append(attrs.Classes, scalar.ScalarString()) case "classes": @@ -757,7 +796,7 @@ func (c *compiler) validateKey(obj *d2graph.Object, f *d2ir.Field) { if !in && arrowheadIn { c.errorf(f.LastPrimaryKey(), fmt.Sprintf(`invalid shape, can only set "%s" for arrowheads`, obj.Attributes.Shape.Value)) } - case "grid-rows", "grid-columns": + case "grid-rows", "grid-columns", "grid-gap", "grid-gap-rows", "grid-gap-columns": for _, child := range obj.ChildrenArray { if child.IsContainer() { c.errorf(f.LastPrimaryKey(), diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 8b5e27696..261e1af5f 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -136,8 +136,11 @@ type Attributes struct { Direction Scalar `json:"direction"` Constraint Scalar `json:"constraint"` - GridRows *Scalar `json:"gridRows,omitempty"` - GridColumns *Scalar `json:"gridColumns,omitempty"` + GridRows *Scalar `json:"gridRows,omitempty"` + GridColumns *Scalar `json:"gridColumns,omitempty"` + GridGap *Scalar `json:"gridGap,omitempty"` + GridGapRows *Scalar `json:"gridGapRows,omitempty"` + GridGapColumns *Scalar `json:"gridGapColumns,omitempty"` // These names are attached to the rendered elements in SVG // so that users can target them however they like outside of D2 @@ -1588,23 +1591,26 @@ var ReservedKeywords2 map[string]struct{} // Non Style/Holder keywords. var SimpleReservedKeywords = map[string]struct{}{ - "label": {}, - "desc": {}, - "shape": {}, - "icon": {}, - "constraint": {}, - "tooltip": {}, - "link": {}, - "near": {}, - "width": {}, - "height": {}, - "direction": {}, - "top": {}, - "left": {}, - "grid-rows": {}, - "grid-columns": {}, - "class": {}, - "classes": {}, + "label": {}, + "desc": {}, + "shape": {}, + "icon": {}, + "constraint": {}, + "tooltip": {}, + "link": {}, + "near": {}, + "width": {}, + "height": {}, + "direction": {}, + "top": {}, + "left": {}, + "grid-rows": {}, + "grid-columns": {}, + "grid-gap": {}, + "grid-gap-rows": {}, + "grid-gap-columns": {}, + "class": {}, + "classes": {}, } // ReservedKeywordHolders are reserved keywords that are meaningless on its own and exist solely to hold a set of reserved keywords diff --git a/d2oracle/edit.go b/d2oracle/edit.go index 58aaab1b6..7849aefea 100644 --- a/d2oracle/edit.go +++ b/d2oracle/edit.go @@ -324,6 +324,21 @@ func _set(g *d2graph.Graph, key string, tag, value *string) error { attrs.GridColumns.MapKey.SetScalar(mk.Value.ScalarBox()) return nil } + case "grid-gap": + if attrs.GridGap != nil && attrs.GridGap.MapKey != nil { + attrs.GridGap.MapKey.SetScalar(mk.Value.ScalarBox()) + return nil + } + case "grid-gap-rows": + if attrs.GridGapRows != nil && attrs.GridGapRows.MapKey != nil { + attrs.GridGapRows.MapKey.SetScalar(mk.Value.ScalarBox()) + return nil + } + case "grid-gap-columns": + if attrs.GridGapColumns != nil && attrs.GridGapColumns.MapKey != nil { + attrs.GridGapColumns.MapKey.SetScalar(mk.Value.ScalarBox()) + return nil + } case "source-arrowhead", "target-arrowhead": if reservedKey == "source-arrowhead" { attrs = edge.SrcArrowhead