From 9bb791823e40bacc0424a3dc253c664f7894d0b7 Mon Sep 17 00:00:00 2001 From: Visio Vanitas Date: Sun, 13 Sep 2026 18:53:38 +0800 Subject: [PATCH 1/2] feat(layouts): add shape: cycle with circular border-trimmed arc routing --- d2graph/cyclediagram.go | 7 + d2graph/d2graph.go | 2 +- d2layouts/d2cycle/layout.go | 382 ++++++++ d2layouts/d2cycle/layout_test.go | 221 +++++ d2layouts/d2layouts.go | 11 +- .../d2raster/target_pixel_coverage_test.go | 1 + .../d2raster/testdata/target-shapes.exp.png | Bin 90965 -> 94619 bytes d2renderers/d2scenebuild/shape_effects.go | 2 +- d2renderers/d2scenebuild/sketch_shapes.go | 6 +- .../d2scenebuild/sketch_shapes_test.go | 2 +- d2renderers/d2svg/d2svg.go | 2 +- d2target/d2target.go | 3 + .../txtar/cycle-diagram/dagre/board.exp.json | 884 ++++++++++++++++++ .../txtar/cycle-diagram/dagre/sketch.exp.svg | 30 + .../txtar/cycle-diagram/elk/board.exp.json | 884 ++++++++++++++++++ .../txtar/cycle-diagram/elk/sketch.exp.svg | 30 + .../txtar/cycle-diagram/tala/board.exp.json | 884 ++++++++++++++++++ .../txtar/cycle-diagram/tala/sketch.exp.svg | 30 + e2etests/txtar.txt | 14 + 19 files changed, 3387 insertions(+), 8 deletions(-) create mode 100644 d2graph/cyclediagram.go create mode 100644 d2layouts/d2cycle/layout.go create mode 100644 d2layouts/d2cycle/layout_test.go create mode 100644 e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json create mode 100644 e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg create mode 100644 e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json create mode 100644 e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg create mode 100644 e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json create mode 100644 e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg diff --git a/d2graph/cyclediagram.go b/d2graph/cyclediagram.go new file mode 100644 index 0000000000..65d7903935 --- /dev/null +++ b/d2graph/cyclediagram.go @@ -0,0 +1,7 @@ +package d2graph + +import "github.com/d2lang/d2/d2target" + +func (obj *Object) IsCycleDiagram() bool { + return obj != nil && obj.Shape.Value == d2target.ShapeCycleDiagram +} diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 92f5c8948a..6c96306c5f 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -543,7 +543,7 @@ func (obj *Object) GetFill() string { return color.N7 } - if shape == "" || strings.EqualFold(shape, d2target.ShapeSquare) || strings.EqualFold(shape, d2target.ShapeCircle) || strings.EqualFold(shape, d2target.ShapeOval) || strings.EqualFold(shape, d2target.ShapeRectangle) || strings.EqualFold(shape, d2target.ShapeHierarchy) { + if shape == "" || strings.EqualFold(shape, d2target.ShapeSquare) || strings.EqualFold(shape, d2target.ShapeCircle) || strings.EqualFold(shape, d2target.ShapeOval) || strings.EqualFold(shape, d2target.ShapeRectangle) || strings.EqualFold(shape, d2target.ShapeCycleDiagram) || strings.EqualFold(shape, d2target.ShapeHierarchy) { if level == 1 { if !obj.IsContainer() { return color.B6 diff --git a/d2layouts/d2cycle/layout.go b/d2layouts/d2cycle/layout.go new file mode 100644 index 0000000000..07c351e999 --- /dev/null +++ b/d2layouts/d2cycle/layout.go @@ -0,0 +1,382 @@ +package d2cycle + +import ( + "context" + "math" + + "github.com/d2lang/d2/d2graph" + "github.com/d2lang/d2/lib/geo" + "github.com/d2lang/d2/lib/label" + "github.com/d2lang/d2/lib/shape" + "github.com/d2lang/util-go/go2" +) + +const ( + minRadius = 200. + padding = 24. + maxArcSweep = math.Pi / 2 + epsilon = 1e-6 +) + +type anglePoint struct { + angle float64 + point *geo.Point +} + +// Layout arranges the root children around a circle and routes their root-level +// edges as cubic Bezier arcs trimmed to each shape's visible perimeter. +func Layout(ctx context.Context, g *d2graph.Graph, coreLayout d2graph.LayoutGraph) error { + objects := g.Root.ChildrenArray + if len(objects) == 0 { + return nil + } + + if coreLayout != nil { + if err := coreLayout(ctx, g); err != nil { + return err + } + } + + for _, obj := range g.Objects { + positionLabelsIcons(obj) + } + + radius := calculateRadius(objects) + positionObjects(objects, radius) + + center := geo.NewPoint(0, 0) + for _, edge := range g.Edges { + if isCycleEdge(g, edge) { + routeCircularArc(edge, center) + } + } + normalizeGraph(g) + return nil +} + +func calculateRadius(objects []*d2graph.Object) float64 { + if len(objects) < 2 { + return 0 + } + + maxHalfDiagonal := 0. + for _, obj := range objects { + maxHalfDiagonal = math.Max(maxHalfDiagonal, math.Hypot(obj.Width/2, obj.Height/2)) + } + + minimum := (maxHalfDiagonal + padding) / math.Sin(math.Pi/float64(len(objects))) + return math.Max(minimum, minRadius) +} + +func positionObjects(objects []*d2graph.Object, radius float64) { + for i, obj := range objects { + angle := -math.Pi/2 + 2*math.Pi*float64(i)/float64(len(objects)) + center := pointOnCircle(geo.NewPoint(0, 0), radius, angle) + moveObjectAndInternalEdges(obj, center.X-obj.Width/2, center.Y-obj.Height/2) + } +} + +func moveObjectAndInternalEdges(obj *d2graph.Object, x, y float64) { + if obj.TopLeft == nil { + obj.TopLeft = geo.NewPoint(0, 0) + } + + dx := x - obj.TopLeft.X + dy := y - obj.TopLeft.Y + obj.MoveWithDescendants(dx, dy) + + for _, edge := range obj.Graph.Edges { + if edge.Src == nil || edge.Dst == nil || len(edge.Route) == 0 { + continue + } + + srcMoved := edge.Src.IsDescendantOf(obj) + dstMoved := edge.Dst.IsDescendantOf(obj) + switch { + case srcMoved && dstMoved: + edge.Move(dx, dy) + case srcMoved: + edge.Route[0].X += dx + edge.Route[0].Y += dy + case dstMoved: + i := len(edge.Route) - 1 + edge.Route[i].X += dx + edge.Route[i].Y += dy + } + } +} + +func isCycleEdge(g *d2graph.Graph, edge *d2graph.Edge) bool { + if edge.Src == nil || edge.Dst == nil { + return false + } + return edge.Src.Parent == g.Root && edge.Dst.Parent == g.Root +} + +func normalizeGraph(g *d2graph.Graph) { + tl := geo.NewPoint(math.Inf(1), math.Inf(1)) + br := geo.NewPoint(math.Inf(-1), math.Inf(-1)) + + for _, obj := range g.Objects { + if obj.TopLeft == nil { + continue + } + tl.X = math.Min(tl.X, obj.TopLeft.X) + tl.Y = math.Min(tl.Y, obj.TopLeft.Y) + br.X = math.Max(br.X, obj.TopLeft.X+obj.Width) + br.Y = math.Max(br.Y, obj.TopLeft.Y+obj.Height) + } + for _, edge := range g.Edges { + for _, point := range edge.Route { + tl.X = math.Min(tl.X, point.X) + tl.Y = math.Min(tl.Y, point.Y) + br.X = math.Max(br.X, point.X) + br.Y = math.Max(br.Y, point.Y) + } + } + + if math.IsInf(tl.X, 0) || math.IsInf(tl.Y, 0) { + return + } + + dx := -tl.X + dy := -tl.Y + for _, obj := range g.Objects { + if obj.TopLeft != nil { + obj.TopLeft.X += dx + obj.TopLeft.Y += dy + } + } + for _, edge := range g.Edges { + edge.Move(dx, dy) + } + g.Root.Box = geo.NewBox(geo.NewPoint(0, 0), br.X-tl.X, br.Y-tl.Y) +} + +func routeCircularArc(edge *d2graph.Edge, center *geo.Point) { + if edge.Src == nil || edge.Dst == nil { + return + } + + srcCenter := edge.Src.Center() + dstCenter := edge.Dst.Center() + radius := (distance(center, srcCenter) + distance(center, dstCenter)) / 2 + if radius == 0 { + return + } + + startAngle := angleOf(center, srcCenter) + endAngle := advanceAngle(startAngle, angleOf(center, dstCenter)) + if edge.Src == edge.Dst || endAngle-startAngle < epsilon { + endAngle = startAngle + 2*math.Pi + } + + start := findBoundaryAngle(edge.Src, center, radius, startAngle, endAngle, true) + end := findBoundaryAngle(edge.Dst, center, radius, startAngle, endAngle, false) + if start == nil || end == nil || end.angle-start.angle < epsilon { + routeStraight(edge) + return + } + + edge.Route = buildArcRoute(center, radius, start.angle, end.angle) + if len(edge.Route) >= 4 { + edge.Route[0] = start.point + edge.Route[len(edge.Route)-1] = end.point + edge.IsCurve = true + return + } + + routeStraight(edge) +} + +func routeStraight(edge *d2graph.Edge) { + edge.Route = []*geo.Point{edge.Src.Center(), edge.Dst.Center()} + edge.TraceToShape(edge.Route, 0, 1) + edge.IsCurve = false +} + +func findBoundaryAngle(obj *d2graph.Object, center *geo.Point, radius, startAngle, endAngle float64, fromStart bool) *anglePoint { + var selected *anglePoint + for _, point := range circleShapeIntersections(obj.ToShape(), center, radius, startAngle, endAngle) { + angle := advanceAngle(startAngle, angleOf(center, point)) + if angle <= startAngle+epsilon || angle >= endAngle-epsilon { + continue + } + + candidate := &anglePoint{ + angle: angle, + point: truncatePoint(point), + } + if selected == nil || + (fromStart && candidate.angle < selected.angle) || + (!fromStart && candidate.angle > selected.angle) { + selected = candidate + } + } + + if selected != nil { + return selected + } + return fallbackBoundaryAngle(obj, center, radius, startAngle, endAngle, fromStart) +} + +func fallbackBoundaryAngle(obj *d2graph.Object, center *geo.Point, radius, startAngle, endAngle float64, fromStart bool) *anglePoint { + box := obj.Box + if box == nil { + return nil + } + + low := startAngle + high := endAngle + if fromStart { + for i := 0; i < 48; i++ { + mid := (low + high) / 2 + if box.Contains(pointOnCircle(center, radius, mid)) { + low = mid + } else { + high = mid + } + } + angle := high + point := shape.TraceToShapeBorder(obj.ToShape(), pointOnCircle(center, radius, angle), pointOnCircle(center, radius, angle+0.001)) + return &anglePoint{angle: angle, point: truncatePoint(point)} + } + + for i := 0; i < 48; i++ { + mid := (low + high) / 2 + if box.Contains(pointOnCircle(center, radius, mid)) { + high = mid + } else { + low = mid + } + } + angle := low + point := shape.TraceToShapeBorder(obj.ToShape(), pointOnCircle(center, radius, angle), pointOnCircle(center, radius, angle-0.001)) + return &anglePoint{angle: angle, point: truncatePoint(point)} +} + +func buildArcRoute(center *geo.Point, radius, startAngle, endAngle float64) []*geo.Point { + sweep := endAngle - startAngle + segments := int(math.Ceil(sweep / maxArcSweep)) + if segments < 1 { + segments = 1 + } + + route := []*geo.Point{truncatePoint(pointOnCircle(center, radius, startAngle))} + for i := 0; i < segments; i++ { + a0 := startAngle + sweep*float64(i)/float64(segments) + a1 := startAngle + sweep*float64(i+1)/float64(segments) + p0 := pointOnCircle(center, radius, a0) + p3 := pointOnCircle(center, radius, a1) + k := 4. / 3. * math.Tan((a1-a0)/4.) + + c1 := geo.NewPoint( + p0.X+k*radius*-math.Sin(a0), + p0.Y+k*radius*math.Cos(a0), + ) + c2 := geo.NewPoint( + p3.X-k*radius*-math.Sin(a1), + p3.Y-k*radius*math.Cos(a1), + ) + + route = append(route, truncatePoint(c1), truncatePoint(c2), truncatePoint(p3)) + } + return route +} + +func circleShapeIntersections(s shape.Shape, center *geo.Point, radius, startAngle, endAngle float64) []*geo.Point { + sweep := endAngle - startAngle + steps := int(math.Ceil(sweep / (math.Pi / 180))) + if steps < 24 { + steps = 24 + } + + points := make([]*geo.Point, 0) + for i := 0; i < steps; i++ { + a0 := startAngle + sweep*float64(i)/float64(steps) + a1 := startAngle + sweep*float64(i+1)/float64(steps) + circleSegment := geo.Segment{ + Start: pointOnCircle(center, radius, a0), + End: pointOnCircle(center, radius, a1), + } + for _, perimeter := range s.Perimeter() { + for _, point := range perimeter.Intersections(circleSegment) { + if !hasNearbyPoint(points, point) { + points = append(points, point) + } + } + } + } + return points +} + +func hasNearbyPoint(points []*geo.Point, point *geo.Point) bool { + for _, existing := range points { + if distance(existing, point) < 0.001 { + return true + } + } + return false +} + +func pointOnCircle(center *geo.Point, radius, angle float64) *geo.Point { + return geo.NewPoint(center.X+radius*math.Cos(angle), center.Y+radius*math.Sin(angle)) +} + +func angleOf(center, point *geo.Point) float64 { + return math.Atan2(point.Y-center.Y, point.X-center.X) +} + +func advanceAngle(reference, angle float64) float64 { + for angle <= reference { + angle += 2 * math.Pi + } + return angle +} + +func distance(a, b *geo.Point) float64 { + return math.Hypot(a.X-b.X, a.Y-b.Y) +} + +func truncatePoint(point *geo.Point) *geo.Point { + point = point.Copy() + point.TruncateDecimals() + return point +} + +func positionLabelsIcons(obj *d2graph.Object) { + if obj.Icon != nil && obj.IconPosition == nil { + if len(obj.ChildrenArray) > 0 { + obj.IconPosition = go2.Pointer(label.OutsideTopLeft.String()) + if obj.LabelPosition == nil { + obj.LabelPosition = go2.Pointer(label.OutsideTopRight.String()) + return + } + } else if obj.SQLTable != nil || obj.Class != nil || obj.Language != "" { + obj.IconPosition = go2.Pointer(label.OutsideTopLeft.String()) + } else { + obj.IconPosition = go2.Pointer(label.InsideMiddleCenter.String()) + } + } + + if obj.HasLabel() && obj.LabelPosition == nil { + if len(obj.ChildrenArray) > 0 { + obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String()) + } else if obj.HasOutsideBottomLabel() { + obj.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String()) + } else if obj.Icon != nil { + obj.LabelPosition = go2.Pointer(label.InsideTopCenter.String()) + } else { + obj.LabelPosition = go2.Pointer(label.InsideMiddleCenter.String()) + } + + if float64(obj.LabelDimensions.Width) > obj.Width || + float64(obj.LabelDimensions.Height) > obj.Height { + if len(obj.ChildrenArray) > 0 { + obj.LabelPosition = go2.Pointer(label.OutsideTopCenter.String()) + } else { + obj.LabelPosition = go2.Pointer(label.OutsideBottomCenter.String()) + } + } + } +} diff --git a/d2layouts/d2cycle/layout_test.go b/d2layouts/d2cycle/layout_test.go new file mode 100644 index 0000000000..cdc78d5198 --- /dev/null +++ b/d2layouts/d2cycle/layout_test.go @@ -0,0 +1,221 @@ +package d2cycle + +import ( + "context" + "math" + "strings" + "testing" + + "github.com/d2lang/d2/d2compiler" + "github.com/d2lang/d2/d2graph" + "github.com/d2lang/d2/lib/geo" +) + +func TestCalculateRadiusKeepsSingleObjectAtCenter(t *testing.T) { + g, _, err := d2compiler.Compile("", strings.NewReader(` +shape: cycle +a +`), nil) + if err != nil { + t.Fatal(err) + } + setObjectBoxes(g, 100, 100) + + if got := calculateRadius(g.Root.ChildrenArray); got != 0 { + t.Fatalf("expected single-object cycle radius 0, got %.2f", got) + } +} + +func TestCycleLayoutPreservesNestedCoreLayout(t *testing.T) { + g, _, err := d2compiler.Compile("", strings.NewReader(` +shape: cycle +a: { + x + y + x -> y +} +b +a -> b +`), nil) + if err != nil { + t.Fatal(err) + } + setObjectBoxes(g, 100, 100) + + layoutCalled := false + coreLayout := func(_ context.Context, g *d2graph.Graph) error { + layoutCalled = true + for _, obj := range g.Objects { + switch obj.ID { + case "a": + obj.TopLeft = geo.NewPoint(0, 0) + case "b": + obj.TopLeft = geo.NewPoint(300, 0) + case "x": + obj.TopLeft = geo.NewPoint(20, 30) + case "y": + obj.TopLeft = geo.NewPoint(160, 30) + } + } + for _, edge := range g.Edges { + edge.Route = []*geo.Point{edge.Src.Center(), edge.Dst.Center()} + } + return nil + } + + if err := Layout(context.Background(), g, coreLayout); err != nil { + t.Fatal(err) + } + if !layoutCalled { + t.Fatal("expected cycle layout to call the provided core layout") + } + + var x, y *d2graph.Object + var internalEdge, cycleEdge *d2graph.Edge + for _, obj := range g.Objects { + switch obj.ID { + case "x": + x = obj + case "y": + y = obj + } + } + for _, edge := range g.Edges { + switch { + case edge.Src == x && edge.Dst == y: + internalEdge = edge + case edge.Src.Parent == g.Root && edge.Dst.Parent == g.Root: + cycleEdge = edge + } + } + + if x == nil || y == nil || internalEdge == nil || cycleEdge == nil { + t.Fatal("expected nested objects, nested edge, and root-level cycle edge") + } + if internalEdge.IsCurve { + t.Fatal("expected nested internal edge to keep the core layout route") + } + if got := len(internalEdge.Route); got != 2 { + t.Fatalf("expected nested internal edge route to keep 2 points, got %d", got) + } + if !samePoint(internalEdge.Route[0], x.Center()) { + t.Fatalf("expected nested edge start to move with nested source, got %v want %v", internalEdge.Route[0], x.Center()) + } + if !samePoint(internalEdge.Route[1], y.Center()) { + t.Fatalf("expected nested edge end to move with nested destination, got %v want %v", internalEdge.Route[1], y.Center()) + } + if !cycleEdge.IsCurve { + t.Fatal("expected root-level cycle edge to be routed as a curve") + } +} + +func TestCycleLayoutMovesCrossChildDescendantEdges(t *testing.T) { + g, _, err := d2compiler.Compile("", strings.NewReader(` +shape: cycle +a: { x } +b: { y } +a.x -> b.y +`), nil) + if err != nil { + t.Fatal(err) + } + setObjectBoxes(g, 100, 100) + + coreLayout := func(_ context.Context, g *d2graph.Graph) error { + for _, obj := range g.Objects { + switch obj.ID { + case "a": + obj.TopLeft = geo.NewPoint(0, 0) + case "b": + obj.TopLeft = geo.NewPoint(300, 0) + case "x", "y": + obj.TopLeft = geo.NewPoint(20, 30) + } + } + for _, edge := range g.Edges { + edge.Route = []*geo.Point{edge.Src.Center(), edge.Dst.Center()} + } + return nil + } + + if err := Layout(context.Background(), g, coreLayout); err != nil { + t.Fatal(err) + } + + var x, y *d2graph.Object + for _, obj := range g.Objects { + switch obj.ID { + case "x": + x = obj + case "y": + y = obj + } + } + var edge *d2graph.Edge + for _, e := range g.Edges { + if e.Src == x && e.Dst == y { + edge = e + } + } + + if edge == nil { + t.Fatal("expected cross-child descendant edge") + } + if len(edge.Route) != 2 { + t.Fatalf("expected 2-point route, got %d", len(edge.Route)) + } + if !samePoint(edge.Route[0], x.Center()) { + t.Fatalf("expected edge start at %v, got %v", x.Center(), edge.Route[0]) + } + if !samePoint(edge.Route[1], y.Center()) { + t.Fatalf("expected edge end at %v, got %v", y.Center(), edge.Route[1]) + } +} + +func setObjectBoxes(g *d2graph.Graph, width, height float64) { + for _, obj := range g.Objects { + obj.Box = geo.NewBox(geo.NewPoint(0, 0), width, height) + } +} + +func samePoint(a, b *geo.Point) bool { + return math.Abs(a.X-b.X) < 0.001 && math.Abs(a.Y-b.Y) < 0.001 +} + +func TestCycleLayoutSupportsVariousShapes(t *testing.T) { + g, _, err := d2compiler.Compile("", strings.NewReader(` +shape: cycle +a: { shape: circle } +b: { shape: rectangle } +c: { shape: cylinder } +d: { shape: hexagon } +a -> b -> c -> d -> a +`), nil) + if err != nil { + t.Fatal(err) + } + setObjectBoxes(g, 100, 100) + + coreLayout := func(_ context.Context, g *d2graph.Graph) error { + for _, obj := range g.Objects { + obj.TopLeft = geo.NewPoint(0, 0) + } + return nil + } + + if err := Layout(context.Background(), g, coreLayout); err != nil { + t.Fatal(err) + } + + for _, edge := range g.Edges { + if !edge.IsCurve { + t.Fatalf("expected edge %v -> %v to be curve", edge.Src.ID, edge.Dst.ID) + } + if len(edge.Route) < 4 { + t.Fatalf("expected edge %v -> %v route to have at least 4 points, got %d", edge.Src.ID, edge.Dst.ID, len(edge.Route)) + } + if (len(edge.Route)-1)%3 != 0 { + t.Fatalf("expected edge %v -> %v route length 1+3n, got %d", edge.Src.ID, edge.Dst.ID, len(edge.Route)) + } + } +} diff --git a/d2layouts/d2layouts.go b/d2layouts/d2layouts.go index c9c45479df..30f5ca0f84 100644 --- a/d2layouts/d2layouts.go +++ b/d2layouts/d2layouts.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/d2lang/d2/d2graph" + "github.com/d2lang/d2/d2layouts/d2cycle" "github.com/d2lang/d2/d2layouts/d2grid" "github.com/d2lang/d2/d2layouts/d2near" "github.com/d2lang/d2/d2layouts/d2sequence" @@ -20,12 +21,12 @@ import ( type DiagramType string -// a grid diagram at a constant near is const ( DefaultGraphType DiagramType = "" ConstantNearGraph DiagramType = "constant-near" GridDiagram DiagramType = "grid-diagram" SequenceDiagram DiagramType = "sequence-diagram" + CycleDiagram DiagramType = "cycle-diagram" ) type GraphInfo struct { @@ -289,6 +290,12 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co if err != nil { return err } + case CycleDiagram: + log.Debug(ctx, "layout cycle", slog.Any("rootlevel", g.RootLevel), slog.Any("shapes", g.PrintString())) + err = d2cycle.Layout(ctx, g, coreLayout) + if err != nil { + return err + } default: log.Debug(ctx, "default layout", slog.Any("rootlevel", g.RootLevel), slog.Any("shapes", g.PrintString())) err := coreLayout(ctx, g) @@ -393,6 +400,8 @@ func NestedGraphInfo(obj *d2graph.Object) (gi GraphInfo) { gi.DiagramType = SequenceDiagram } else if obj.IsGridDiagram() { gi.DiagramType = GridDiagram + } else if obj.IsCycleDiagram() { + gi.DiagramType = CycleDiagram } return gi } diff --git a/d2renderers/d2raster/target_pixel_coverage_test.go b/d2renderers/d2raster/target_pixel_coverage_test.go index 5146d4ad3c..efc2b01751 100644 --- a/d2renderers/d2raster/target_pixel_coverage_test.go +++ b/d2renderers/d2raster/target_pixel_coverage_test.go @@ -56,6 +56,7 @@ func TestTargetShapePixels(t *testing.T) { d2target.ShapeSQLTable, d2target.ShapeImage, d2target.ShapeSequenceDiagram, + d2target.ShapeCycleDiagram, d2target.ShapeHierarchy, } if !reflect.DeepEqual(shapeValues, d2target.Shapes) { diff --git a/d2renderers/d2raster/testdata/target-shapes.exp.png b/d2renderers/d2raster/testdata/target-shapes.exp.png index c7126219f90bd73ddced5a4e708b75d7845e35d7..85f894820e50229ac485b7c96b3fe8b706bb1d93 100644 GIT binary patch delta 12936 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 diff --git a/d2renderers/d2scenebuild/shape_effects.go b/d2renderers/d2scenebuild/shape_effects.go index 8d241ec1a1..ec30c3a7d8 100644 --- a/d2renderers/d2scenebuild/shape_effects.go +++ b/d2renderers/d2scenebuild/shape_effects.go @@ -295,7 +295,7 @@ func (b *builder) buildOrdinaryShapeOutline(targetShape d2target.Shape, fill d2s Fill: fill, Stroke: stroke, })) - case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "": + case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeCycleDiagram, d2target.ShapeHierarchy, "": radius := clampBorderRadius(float64(targetShape.BorderRadius), box.Width, box.Height) nodes = append(nodes, d2scene.NewNode(d2scene.Rect{ Box: box, diff --git a/d2renderers/d2scenebuild/sketch_shapes.go b/d2renderers/d2scenebuild/sketch_shapes.go index 9f1b1f15fe..56accc2ff1 100644 --- a/d2renderers/d2scenebuild/sketch_shapes.go +++ b/d2renderers/d2scenebuild/sketch_shapes.go @@ -127,7 +127,7 @@ func (b *builder) buildSketchOrdinaryShape(targetShape d2target.Shape, idPrefix object, idPrefix, fillRaw, strokeRaw, strokeWidth, dashes, dashOffset, drawable, d2scene.Translate(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)), targetShape, ) - case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "": + case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeCycleDiagram, d2target.ShapeHierarchy, "": drawable := newSketchGenerator().Rectangle(0, 0, float64(targetShape.Width), float64(targetShape.Height), options) nodes, err = b.compileSketchShapeDrawable( object, idPrefix, fillRaw, strokeRaw, strokeWidth, dashes, dashOffset, @@ -156,7 +156,7 @@ func (b *builder) buildSketchOrdinaryShape(targetShape d2target.Shape, idPrefix RadiusX: float64(targetShape.Width) / 2, RadiusY: float64(targetShape.Height) / 2, }) - case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "": + case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeCycleDiagram, d2target.ShapeHierarchy, "": overlayPrimitives = append(overlayPrimitives, d2scene.Rect{Box: sketchShapeBox(targetShape)}) default: for _, node := range roughNodes { @@ -578,7 +578,7 @@ func (b *builder) buildSketchOutlineOnly(targetShape d2target.Shape, idPrefix st ), d2scene.Translate(float64(targetShape.Pos.X), float64(targetShape.Pos.Y)), targetShape, ) - case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "": + case d2target.ShapeRectangle, d2target.ShapeSquare, d2target.ShapeSequenceDiagram, d2target.ShapeCycleDiagram, d2target.ShapeHierarchy, "": return b.compileSketchShapeDrawable( object, idPrefix, fillRaw, strokeRaw, strokeWidth, dashes, dashOffset, newSketchGenerator().Rectangle(0, 0, float64(targetShape.Width), float64(targetShape.Height), options), diff --git a/d2renderers/d2scenebuild/sketch_shapes_test.go b/d2renderers/d2scenebuild/sketch_shapes_test.go index 18040f1263..8792075369 100644 --- a/d2renderers/d2scenebuild/sketch_shapes_test.go +++ b/d2renderers/d2scenebuild/sketch_shapes_test.go @@ -28,7 +28,7 @@ func TestSketchOrdinaryShapeFamiliesUseTypedRoughPathsDeterministically(t *testi d2target.ShapeStep, d2target.ShapeCallout, d2target.ShapeStoredData, d2target.ShapePerson, d2target.ShapeC4Person, d2target.ShapeDiamond, d2target.ShapeHexagon, d2target.ShapeCloud, - d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, + d2target.ShapeSequenceDiagram, d2target.ShapeCycleDiagram, d2target.ShapeHierarchy, } for _, shapeType := range shapeTypes { name := shapeType diff --git a/d2renderers/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 2d5fbfc156..f49b42486f 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -1829,7 +1829,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape fmt.Fprint(writer, el.Render()) // TODO should standardize "" to rectangle - case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "": + case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, d2target.ShapeCycleDiagram, d2target.ShapeHierarchy, "": borderRadius := math.MaxFloat64 if targetShape.BorderRadius != 0 { borderRadius = float64(targetShape.BorderRadius) diff --git a/d2target/d2target.go b/d2target/d2target.go index 432745bece..0d274a469c 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -1266,6 +1266,7 @@ const ( ShapeSQLTable = "sql_table" ShapeImage = "image" ShapeSequenceDiagram = "sequence_diagram" + ShapeCycleDiagram = "cycle" ShapeHierarchy = "hierarchy" ) @@ -1294,6 +1295,7 @@ var Shapes = []string{ ShapeSQLTable, ShapeImage, ShapeSequenceDiagram, + ShapeCycleDiagram, ShapeHierarchy, } @@ -1364,6 +1366,7 @@ var DSL_SHAPE_TO_SHAPE_TYPE = map[string]string{ ShapeSQLTable: shape.TABLE_TYPE, ShapeImage: shape.IMAGE_TYPE, ShapeSequenceDiagram: shape.SQUARE_TYPE, + ShapeCycleDiagram: shape.SQUARE_TYPE, ShapeHierarchy: shape.SQUARE_TYPE, } diff --git a/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json new file mode 100644 index 0000000000..00791c9689 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json @@ -0,0 +1,884 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "1", + "type": "cycle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 453, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "1.a", + "type": "rectangle", + "pos": { + "x": 200, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.b", + "type": "rectangle", + "pos": { + "x": 400, + "y": 200 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.c", + "type": "rectangle", + "pos": { + "x": 200, + "y": 400 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.d", + "type": "rectangle", + "pos": { + "x": 0, + "y": 200 + }, + "width": 54, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": "d", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 9, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2", + "type": "cycle", + "pos": { + "x": 513, + "y": 17 + }, + "width": 408, + "height": 433, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "2.a", + "type": "rectangle", + "pos": { + "x": 686, + "y": 17 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "2.b", + "type": "rectangle", + "pos": { + "x": 859, + "y": 317 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "2.c", + "type": "rectangle", + "pos": { + "x": 513, + "y": 317 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "3", + "type": "cycle", + "pos": { + "x": 981, + "y": 0 + }, + "width": 226, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "3.a", + "type": "rectangle", + "pos": { + "x": 981, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "3.b", + "type": "rectangle", + "pos": { + "x": 981, + "y": 400 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + } + ], + "connections": [ + { + "id": "1.(a -> b)[0]", + "src": "1.a", + "srcArrow": "none", + "dst": "1.b", + "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": 253.5, + "y": 34.763999938964844 + }, + { + "x": 340.5150146484375, + "y": 46.39500045776367 + }, + { + "x": 409.77301025390625, + "y": 113.41400146484375 + }, + { + "x": 424.25799560546875, + "y": 200 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(b -> c)[0]", + "src": "1.b", + "srcArrow": "none", + "dst": "1.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": 424.25799560546875, + "y": 266 + }, + { + "x": 409.77301025390625, + "y": 352.58599853515625 + }, + { + "x": 340.5150146484375, + "y": 419.60400390625 + }, + { + "x": 253.5, + "y": 431.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(c -> d)[0]", + "src": "1.c", + "srcArrow": "none", + "dst": "1.d", + "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": 200.5, + "y": 431.2359924316406 + }, + { + "x": 113.48500061035156, + "y": 419.60400390625 + }, + { + "x": 44.22700119018555, + "y": 352.58599853515625 + }, + { + "x": 29.740999221801758, + "y": 266 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(a -> b)[0]", + "src": "2.a", + "srcArrow": "none", + "dst": "2.b", + "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": 739.2050170898438, + "y": 51.763999938964844 + }, + { + "x": 798.9879760742188, + "y": 59.75600051879883 + }, + { + "x": 851.9719848632812, + "y": 94.30000305175781 + }, + { + "x": 883.4019775390625, + "y": 145.7790069580078 + }, + { + "x": 914.8330078125, + "y": 197.2570037841797 + }, + { + "x": 921.35400390625, + "y": 260.16900634765625 + }, + { + "x": 901.1480102539062, + "y": 316.9989929199219 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(b -> c)[0]", + "src": "2.b", + "srcArrow": "none", + "dst": "2.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": 862.072998046875, + "y": 383 + }, + { + "x": 824.1229858398438, + "y": 425.6210021972656 + }, + { + "x": 769.7730102539062, + "y": 450 + }, + { + "x": 712.7050170898438, + "y": 450 + }, + { + "x": 655.6370239257812, + "y": 450 + }, + { + "x": 601.2869873046875, + "y": 425.6210021972656 + }, + { + "x": 563.3369750976562, + "y": 383 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "3.(a -> b)[0]", + "src": "3.a", + "srcArrow": "none", + "dst": "3.b", + "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": 1034, + "y": 34.763999938964844 + }, + { + "x": 1133.3310546875, + "y": 48.04199981689453 + }, + { + "x": 1207.5, + "y": 132.78599548339844 + }, + { + "x": 1207.5, + "y": 233 + }, + { + "x": 1207.5, + "y": 333.2139892578125 + }, + { + "x": 1133.3310546875, + "y": 417.9570007324219 + }, + { + "x": 1034, + "y": 431.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "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/cycle-diagram/dagre/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg new file mode 100644 index 0000000000..1b29c5db5d --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg @@ -0,0 +1,30 @@ +abcdabcab + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json new file mode 100644 index 0000000000..850c67df62 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json @@ -0,0 +1,884 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "1", + "type": "cycle", + "pos": { + "x": 12, + "y": 12 + }, + "width": 454, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "1.a", + "type": "rectangle", + "pos": { + "x": 212, + "y": 12 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.b", + "type": "rectangle", + "pos": { + "x": 412, + "y": 212 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.c", + "type": "rectangle", + "pos": { + "x": 212, + "y": 412 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.d", + "type": "rectangle", + "pos": { + "x": 12, + "y": 212 + }, + "width": 54, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": "d", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 9, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2", + "type": "cycle", + "pos": { + "x": 485, + "y": 28 + }, + "width": 409, + "height": 433, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "2.a", + "type": "rectangle", + "pos": { + "x": 658, + "y": 28 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "2.b", + "type": "rectangle", + "pos": { + "x": 831, + "y": 328 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "2.c", + "type": "rectangle", + "pos": { + "x": 485, + "y": 328 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "3", + "type": "cycle", + "pos": { + "x": 913, + "y": 12 + }, + "width": 227, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "3.a", + "type": "rectangle", + "pos": { + "x": 913, + "y": 12 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "3.b", + "type": "rectangle", + "pos": { + "x": 913, + "y": 412 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + } + ], + "connections": [ + { + "id": "1.(a -> b)[0]", + "src": "1.a", + "srcArrow": "none", + "dst": "1.b", + "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": 265.5, + "y": 46.763999938964844 + }, + { + "x": 352.5150146484375, + "y": 58.39500045776367 + }, + { + "x": 421.77301025390625, + "y": 125.41400146484375 + }, + { + "x": 436.25799560546875, + "y": 212 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(b -> c)[0]", + "src": "1.b", + "srcArrow": "none", + "dst": "1.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": 436.25799560546875, + "y": 278 + }, + { + "x": 421.77301025390625, + "y": 364.58599853515625 + }, + { + "x": 352.5150146484375, + "y": 431.60400390625 + }, + { + "x": 265.5, + "y": 443.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(c -> d)[0]", + "src": "1.c", + "srcArrow": "none", + "dst": "1.d", + "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": 212.5, + "y": 443.2359924316406 + }, + { + "x": 125.48500061035156, + "y": 431.60400390625 + }, + { + "x": 56.22700119018555, + "y": 364.58599853515625 + }, + { + "x": 41.74100112915039, + "y": 278 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(a -> b)[0]", + "src": "2.a", + "srcArrow": "none", + "dst": "2.b", + "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": 711.7050170898438, + "y": 63.263999938964844 + }, + { + "x": 771.4879760742188, + "y": 71.25599670410156 + }, + { + "x": 824.4719848632812, + "y": 105.80000305175781 + }, + { + "x": 855.9019775390625, + "y": 157.2790069580078 + }, + { + "x": 887.3330078125, + "y": 208.7570037841797 + }, + { + "x": 893.85400390625, + "y": 271.66900634765625 + }, + { + "x": 873.6480102539062, + "y": 328.4989929199219 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(b -> c)[0]", + "src": "2.b", + "srcArrow": "none", + "dst": "2.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": 834.572998046875, + "y": 394.5 + }, + { + "x": 796.6229858398438, + "y": 437.1210021972656 + }, + { + "x": 742.2730102539062, + "y": 461.5 + }, + { + "x": 685.2050170898438, + "y": 461.5 + }, + { + "x": 628.1370239257812, + "y": 461.5 + }, + { + "x": 573.7869873046875, + "y": 437.1210021972656 + }, + { + "x": 535.8369750976562, + "y": 394.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "3.(a -> b)[0]", + "src": "3.a", + "srcArrow": "none", + "dst": "3.b", + "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": 966.85400390625, + "y": 46.763999938964844 + }, + { + "x": 1066.18505859375, + "y": 60.04199981689453 + }, + { + "x": 1140.35400390625, + "y": 144.78599548339844 + }, + { + "x": 1140.35400390625, + "y": 245 + }, + { + "x": 1140.35400390625, + "y": 345.2139892578125 + }, + { + "x": 1066.18505859375, + "y": 429.9570007324219 + }, + { + "x": 966.85400390625, + "y": 443.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "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/cycle-diagram/elk/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg new file mode 100644 index 0000000000..9dcf3d7aaa --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg @@ -0,0 +1,30 @@ +abcdabcab + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json b/e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json new file mode 100644 index 0000000000..70207139e5 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json @@ -0,0 +1,884 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "1", + "type": "cycle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 453, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "1.a", + "type": "rectangle", + "pos": { + "x": 200, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.b", + "type": "rectangle", + "pos": { + "x": 400, + "y": 200 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.c", + "type": "rectangle", + "pos": { + "x": 200, + "y": 400 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "1.d", + "type": "rectangle", + "pos": { + "x": 0, + "y": 200 + }, + "width": 54, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": "d", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 9, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2", + "type": "cycle", + "pos": { + "x": 473, + "y": 0 + }, + "width": 408, + "height": 433, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "2.a", + "type": "rectangle", + "pos": { + "x": 646, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "2.b", + "type": "rectangle", + "pos": { + "x": 819, + "y": 300 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "2.c", + "type": "rectangle", + "pos": { + "x": 473, + "y": 300 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "3", + "type": "cycle", + "pos": { + "x": 901, + "y": 0 + }, + "width": 226, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "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": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "3.a", + "type": "rectangle", + "pos": { + "x": 901, + "y": 0 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + }, + { + "id": "3.b", + "type": "rectangle", + "pos": { + "x": 901, + "y": 400 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "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": 2 + } + ], + "connections": [ + { + "id": "1.(a -> b)[0]", + "src": "1.a", + "srcArrow": "none", + "dst": "1.b", + "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": 253.5, + "y": 34.763999938964844 + }, + { + "x": 340.5150146484375, + "y": 46.39500045776367 + }, + { + "x": 409.77301025390625, + "y": 113.41400146484375 + }, + { + "x": 424.25799560546875, + "y": 200 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(b -> c)[0]", + "src": "1.b", + "srcArrow": "none", + "dst": "1.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": 424.25799560546875, + "y": 266 + }, + { + "x": 409.77301025390625, + "y": 352.58599853515625 + }, + { + "x": 340.5150146484375, + "y": 419.60400390625 + }, + { + "x": 253.5, + "y": 431.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(c -> d)[0]", + "src": "1.c", + "srcArrow": "none", + "dst": "1.d", + "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": 200.5, + "y": 431.2359924316406 + }, + { + "x": 113.48500061035156, + "y": 419.60400390625 + }, + { + "x": 44.22700119018555, + "y": 352.58599853515625 + }, + { + "x": 29.740999221801758, + "y": 266 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(a -> b)[0]", + "src": "2.a", + "srcArrow": "none", + "dst": "2.b", + "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": 699.7050170898438, + "y": 34.763999938964844 + }, + { + "x": 759.4879760742188, + "y": 42.75600051879883 + }, + { + "x": 812.4719848632812, + "y": 77.30000305175781 + }, + { + "x": 843.9019775390625, + "y": 128.7790069580078 + }, + { + "x": 875.3330078125, + "y": 180.2570037841797 + }, + { + "x": 881.85400390625, + "y": 243.1699981689453 + }, + { + "x": 861.6480102539062, + "y": 299.9989929199219 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(b -> c)[0]", + "src": "2.b", + "srcArrow": "none", + "dst": "2.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": 822.572998046875, + "y": 366 + }, + { + "x": 784.6229858398438, + "y": 408.6210021972656 + }, + { + "x": 730.2730102539062, + "y": 433 + }, + { + "x": 673.2050170898438, + "y": 433 + }, + { + "x": 616.1370239257812, + "y": 433 + }, + { + "x": 561.7869873046875, + "y": 408.6210021972656 + }, + { + "x": 523.8369750976562, + "y": 366 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "3.(a -> b)[0]", + "src": "3.a", + "srcArrow": "none", + "dst": "3.b", + "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": 954.85400390625, + "y": 34.763999938964844 + }, + { + "x": 1054.18505859375, + "y": 48.04199981689453 + }, + { + "x": 1128.35400390625, + "y": 132.78599548339844 + }, + { + "x": 1128.35400390625, + "y": 233 + }, + { + "x": 1128.35400390625, + "y": 333.2139892578125 + }, + { + "x": 1054.18505859375, + "y": 417.9570007324219 + }, + { + "x": 954.85400390625, + "y": 431.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "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/cycle-diagram/tala/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg new file mode 100644 index 0000000000..96b9c9a22a --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg @@ -0,0 +1,30 @@ +abcdabcab + + + \ No newline at end of file diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt index 7585d91b3e..be247de7f7 100644 --- a/e2etests/txtar.txt +++ b/e2etests/txtar.txt @@ -1773,3 +1773,17 @@ style: {fill-pattern: dots; fill:"radial-gradient(#fbfbf8, #e3e3f0)"; stroke: "# a->b + +-- cycle-diagram -- +1: "" { + shape: cycle + a -> b -> c -> d +} +2: "" { + shape: cycle + a -> b -> c +} +3: "" { + shape: cycle + a -> b +} From 28f915400df83e5220c448a62479fad9a27fe584 Mon Sep 17 00:00:00 2001 From: Visio Vanitas Date: Sun, 13 Sep 2026 19:46:24 +0800 Subject: [PATCH 2/2] docs(changelog): add shape: cycle release notes --- 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 ffe01dbc1e..d5d5da268d 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,5 +1,7 @@ #### Features ๐Ÿš€ +- d2layouts: add `shape: cycle` for circular diagram layouts with border-trimmed circular arc routing. [#1578](https://github.com/terrastruct/d2/issues/1578) + #### Improvements ๐Ÿงน - d2svg: