diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index 37369784f..e4ceb4151 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -23,3 +23,4 @@ - Icon URLs that needed escaping (e.g. with ampersands) are handled correctly by CLI. [#666](https://github.com/terrastruct/d2/pull/666) - Fixes markdown shapes being slightly too short for their text in some cases. [#665](https://github.com/terrastruct/d2/pull/665) - Fixes self-connections inside layouts when using ELK. [#676](https://github.com/terrastruct/d2/pull/676) +- Fixes panic when the only diagram object has `near` set to a constant. [#687](https://github.com/terrastruct/d2/pull/687) diff --git a/d2layouts/d2near/layout.go b/d2layouts/d2near/layout.go index e05709169..057ca405a 100644 --- a/d2layouts/d2near/layout.go +++ b/d2layouts/d2near/layout.go @@ -27,12 +27,12 @@ func Layout(ctx context.Context, g *d2graph.Graph, constantNears []*d2graph.Obje // So place the center ones first, then the later ones will consider them for bounding box for _, processCenters := range []bool{true, false} { for _, obj := range constantNears { - if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "center") { + if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "-center") { obj.TopLeft = geo.NewPoint(place(obj)) } } for _, obj := range constantNears { - if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "center") { + if processCenters == strings.Contains(d2graph.Key(obj.Attributes.NearKey)[0], "-center") { // The z-index for constant nears does not matter, as it will not collide g.Objects = append(g.Objects, obj) obj.Parent.Children[obj.ID] = obj @@ -152,5 +152,14 @@ func boundingBox(g *d2graph.Graph) (tl, br *geo.Point) { } } + if math.IsInf(x1, 1) && math.IsInf(x2, -1) { + x1 = 0 + x2 = 0 + } + if math.IsInf(y1, 1) && math.IsInf(y2, -1) { + y1 = 0 + y2 = 0 + } + return geo.NewPoint(x1, y1), geo.NewPoint(x2, y2) } diff --git a/d2layouts/d2sequence/layout.go b/d2layouts/d2sequence/layout.go index 49ae00b43..14e4c6f80 100644 --- a/d2layouts/d2sequence/layout.go +++ b/d2layouts/d2sequence/layout.go @@ -17,43 +17,45 @@ func WithoutSequenceDiagrams(ctx context.Context, g *d2graph.Graph) (map[string] edgesToRemove := make(map[*d2graph.Edge]struct{}) sequenceDiagrams := make(map[string]*sequenceDiagram) - queue := make([]*d2graph.Object, 1, len(g.Objects)) - queue[0] = g.Root - for len(queue) > 0 { - obj := queue[0] - queue = queue[1:] - if len(obj.ChildrenArray) == 0 { - continue - } - if obj.Attributes.Shape.Value != d2target.ShapeSequenceDiagram { - queue = append(queue, obj.ChildrenArray...) - continue - } + if len(g.Objects) > 0 { + queue := make([]*d2graph.Object, 1, len(g.Objects)) + queue[0] = g.Root + for len(queue) > 0 { + obj := queue[0] + queue = queue[1:] + if len(obj.ChildrenArray) == 0 { + continue + } + if obj.Attributes.Shape.Value != d2target.ShapeSequenceDiagram { + queue = append(queue, obj.ChildrenArray...) + continue + } - sd, err := layoutSequenceDiagram(g, obj) - if err != nil { - return nil, nil, nil, err - } - obj.Children = make(map[string]*d2graph.Object) - obj.ChildrenArray = nil - obj.Box = geo.NewBox(nil, sd.getWidth()+GROUP_CONTAINER_PADDING*2, sd.getHeight()+GROUP_CONTAINER_PADDING*2) - obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) - sequenceDiagrams[obj.AbsID()] = sd + sd, err := layoutSequenceDiagram(g, obj) + if err != nil { + return nil, nil, nil, err + } + obj.Children = make(map[string]*d2graph.Object) + obj.ChildrenArray = nil + obj.Box = geo.NewBox(nil, sd.getWidth()+GROUP_CONTAINER_PADDING*2, sd.getHeight()+GROUP_CONTAINER_PADDING*2) + obj.LabelPosition = go2.Pointer(string(label.InsideTopCenter)) + sequenceDiagrams[obj.AbsID()] = sd - for _, edge := range sd.messages { - edgesToRemove[edge] = struct{}{} - } - for _, obj := range sd.actors { - objectsToRemove[obj] = struct{}{} - } - for _, obj := range sd.notes { - objectsToRemove[obj] = struct{}{} - } - for _, obj := range sd.groups { - objectsToRemove[obj] = struct{}{} - } - for _, obj := range sd.spans { - objectsToRemove[obj] = struct{}{} + for _, edge := range sd.messages { + edgesToRemove[edge] = struct{}{} + } + for _, obj := range sd.actors { + objectsToRemove[obj] = struct{}{} + } + for _, obj := range sd.notes { + objectsToRemove[obj] = struct{}{} + } + for _, obj := range sd.groups { + objectsToRemove[obj] = struct{}{} + } + for _, obj := range sd.spans { + objectsToRemove[obj] = struct{}{} + } } } diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go index fb9f7399a..7ec142f3a 100644 --- a/e2etests/stable_test.go +++ b/e2etests/stable_test.go @@ -1815,6 +1815,20 @@ x.y -> a.b: { style.animated: true target-arrowhead.shape: cf-many } +`, + }, + { + name: "near-alone", + script: ` +x: { + near: top-center +} +y: { + near: bottom-center +} +z: { + near: center-left +} `, }, } diff --git a/e2etests/testdata/stable/constant_near_stress/dagre/board.exp.json b/e2etests/testdata/stable/constant_near_stress/dagre/board.exp.json index e081c8884..8afeff0d2 100644 --- a/e2etests/testdata/stable/constant_near_stress/dagre/board.exp.json +++ b/e2etests/testdata/stable/constant_near_stress/dagre/board.exp.json @@ -122,11 +122,51 @@ "zIndex": 0, "level": 1 }, + { + "id": "bottom", + "type": "text", + "pos": { + "x": -414, + "y": 372 + }, + "width": 943, + "height": 131, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "transparent", + "stroke": "#0A0F25", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "# Cats, no less liquid than their shadows, offer no angles to the wind.\n\nIf we can't fix it, it ain't broke.\n\nDieters live life in the fasting lane.", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "markdown", + "color": "#0A0F25", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 943, + "labelHeight": 131, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, { "id": "Joe", "type": "person", "pos": { - "x": -151, + "x": -565, "y": 113 }, "width": 131, @@ -166,7 +206,7 @@ "id": "Donald", "type": "person", "pos": { - "x": 134, + "x": 548, "y": 113 }, "width": 155, @@ -202,46 +242,6 @@ "zIndex": 0, "level": 1 }, - { - "id": "bottom", - "type": "text", - "pos": { - "x": -414, - "y": 372 - }, - "width": 943, - "height": 131, - "opacity": 1, - "strokeDash": 0, - "strokeWidth": 2, - "borderRadius": 0, - "fill": "transparent", - "stroke": "#0A0F25", - "shadow": false, - "3d": false, - "multiple": false, - "tooltip": "", - "link": "", - "icon": null, - "iconPosition": "", - "blend": false, - "fields": null, - "methods": null, - "columns": null, - "label": "# Cats, no less liquid than their shadows, offer no angles to the wind.\n\nIf we can't fix it, it ain't broke.\n\nDieters live life in the fasting lane.", - "fontSize": 16, - "fontFamily": "DEFAULT", - "language": "markdown", - "color": "#0A0F25", - "italic": false, - "bold": false, - "underline": false, - "labelWidth": 943, - "labelHeight": 131, - "labelPosition": "INSIDE_MIDDLE_CENTER", - "zIndex": 0, - "level": 1 - }, { "id": "i am top left", "type": "text", diff --git a/e2etests/testdata/stable/constant_near_stress/dagre/sketch.exp.svg b/e2etests/testdata/stable/constant_near_stress/dagre/sketch.exp.svg index 7c4a6bc7c..59a33a536 100644 --- a/e2etests/testdata/stable/constant_near_stress/dagre/sketch.exp.svg +++ b/e2etests/testdata/stable/constant_near_stress/dagre/sketch.exp.svg @@ -3,7 +3,7 @@ id="d2-svg" style="background: white;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" -width="1410" height="748" viewBox="-643 -143 1410 748">xyThe top of the mountainJoeDonaldCats, no less liquid than their shadows, offer no angles to the wind. +xyThe top of the mountainCats, no less liquid than their shadows, offer no angles to the wind. If we can't fix it, it ain't broke. Dieters live life in the fasting lane. -i am top lefti am top righti am bottom lefti am bottom right - +JoeDonaldi am top lefti am top righti am bottom lefti am bottom right + xyThe top of the mountainJoeDonaldCats, no less liquid than their shadows, offer no angles to the wind. +xyThe top of the mountainCats, no less liquid than their shadows, offer no angles to the wind. If we can't fix it, it ain't broke. Dieters live life in the fasting lane. -i am top lefti am top righti am bottom lefti am bottom right - +JoeDonaldi am top lefti am top righti am bottom lefti am bottom right + xyz + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/near-alone/elk/board.exp.json b/e2etests/testdata/stable/near-alone/elk/board.exp.json new file mode 100644 index 000000000..1ab5233e0 --- /dev/null +++ b/e2etests/testdata/stable/near-alone/elk/board.exp.json @@ -0,0 +1,127 @@ +{ + "name": "", + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "x", + "type": "", + "pos": { + "x": -56, + "y": -146 + }, + "width": 113, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "x", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 13, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "y", + "type": "", + "pos": { + "x": -57, + "y": 20 + }, + "width": 114, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "y", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "z", + "type": "", + "pos": { + "x": -189, + "y": -63 + }, + "width": 112, + "height": 126, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "#F7F8FE", + "stroke": "#0D32B2", + "shadow": false, + "3d": false, + "multiple": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "z", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "#0A0F25", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 12, + "labelHeight": 26, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [] +} diff --git a/e2etests/testdata/stable/near-alone/elk/sketch.exp.svg b/e2etests/testdata/stable/near-alone/elk/sketch.exp.svg new file mode 100644 index 000000000..fbd9d0187 --- /dev/null +++ b/e2etests/testdata/stable/near-alone/elk/sketch.exp.svg @@ -0,0 +1,52 @@ + +xyz + + + \ No newline at end of file
If we can't fix it, it ain't broke.
Dieters live life in the fasting lane.