diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index d291ced933..4405f3ef6b 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -2,6 +2,7 @@ - elk: configure the spacing between edges routed between the same pair of layers with `--elk-edgeEdgeBetweenLayers`. It was previously fixed at 50, which forced large gaps between layers on graphs with many links between adjacent layers. - renders: add native isometric SVG, PNG, GIF, PDF, and PPTX exports with `--isometric`, source configuration, and Go/JavaScript options +- shape: `cycle` lays a container's children out on a circle and routes their connections as arcs that start and stop on the shape borders #### Improvements 🧹 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/grid_diagram.go b/d2graph/griddiagram.go similarity index 100% rename from d2graph/grid_diagram.go rename to d2graph/griddiagram.go diff --git a/d2graph/seqdiagram.go b/d2graph/sequencediagram.go similarity index 100% rename from d2graph/seqdiagram.go rename to d2graph/sequencediagram.go diff --git a/d2layouts/d2cycle/layout.go b/d2layouts/d2cycle/layout.go new file mode 100644 index 0000000000..5b687d96c0 --- /dev/null +++ b/d2layouts/d2cycle/layout.go @@ -0,0 +1,367 @@ +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 ( + MIN_RADIUS = 200 + PADDING = 20 + ARC_STEPS = 30 + // SELF_LOOP_MIN_RADIUS keeps the loop of a self edge visible even when the + // shape it hangs off is tiny. + SELF_LOOP_MIN_RADIUS = 20 +) + +// Layout arranges nodes in a circle and routes each edge as a circular arc +// that starts and ends on the borders of its source and destination shapes. +func Layout(ctx context.Context, g *d2graph.Graph, layout d2graph.LayoutGraph) error { + objects := g.Root.ChildrenArray + if len(objects) == 0 { + return nil + } + + for _, obj := range g.Objects { + positionLabelsIcons(obj) + } + + radius := calculateRadius(objects) + positionObjects(objects, radius) + + for _, edge := range g.Edges { + createCircularArc(edge, radius) + } + + normalizeToOrigin(g) + + return nil +} + +// normalizeToOrigin shifts the laid out board so that everything it draws -- +// the shapes and the arcs between them -- lies at or below the origin corner, +// and records the resulting size on the root. positionObjects centers the ring +// on (0, 0), which leaves the upper left half of the board in negative +// coordinates, but d2layouts fits the container to the nested board and then +// places it at (0, 0), and d2layouts.PositionNested offsets the contents by the +// container position alone -- both assume the nested board already starts at +// the origin. Without this the container is drawn offset from the very children +// it wraps. The arcs are included in the bounds so the container encloses them +// rather than being clipped to the ring of shapes. +func normalizeToOrigin(g *d2graph.Graph) { + minX, minY := math.Inf(1), math.Inf(1) + maxX, maxY := math.Inf(-1), math.Inf(-1) + track := func(x, y float64) { + minX, minY = math.Min(minX, x), math.Min(minY, y) + maxX, maxY = math.Max(maxX, x), math.Max(maxY, y) + } + for _, obj := range g.Objects { + if obj.TopLeft == nil { + continue + } + track(obj.TopLeft.X, obj.TopLeft.Y) + track(obj.TopLeft.X+obj.Width, obj.TopLeft.Y+obj.Height) + } + for _, edge := range g.Edges { + for _, p := range edge.Route { + track(p.X, p.Y) + } + } + if math.IsInf(minX, 1) || math.IsInf(minY, 1) { + return + } + + dx, dy := -minX, -minY + if dx != 0 || dy != 0 { + for _, obj := range g.Root.ChildrenArray { + obj.MoveWithDescendants(dx, dy) + } + for _, edge := range g.Edges { + edge.Move(dx, dy) + } + } + // The exporter truncates positions and sizes to whole pixels, and the + // circle leaves shapes on half pixels, so a bare width can round down + // past a child and draw the container border through it. Round up. + g.Root.Width = math.Ceil(maxX - minX) + g.Root.Height = math.Ceil(maxY - minY) +} + +func calculateRadius(objects []*d2graph.Object) float64 { + numObjects := float64(len(objects)) + maxSize := 0.0 + for _, obj := range objects { + size := math.Max(obj.Box.Width, obj.Box.Height) + maxSize = math.Max(maxSize, size) + } + if numObjects < 2 { + // sin(pi/1) is zero to within rounding, so the expression below + // returns about 1e17. Every coordinate on the ring is then spaced + // further apart than the shapes themselves, and the arcs computed + // from them collapse. One shape needs no room for neighbours. + return MIN_RADIUS + } + minRadius := (maxSize/2.0 + PADDING) / math.Sin(math.Pi/numObjects) + return math.Max(minRadius, MIN_RADIUS) +} + +func positionObjects(objects []*d2graph.Object, radius float64) { + numObjects := float64(len(objects)) + angleOffset := -math.Pi / 2 + + for i, obj := range objects { + angle := angleOffset + (2 * math.Pi * float64(i) / numObjects) + x := radius * math.Cos(angle) + y := radius * math.Sin(angle) + + obj.TopLeft = geo.NewPoint( + x-obj.Box.Width/2, + y-obj.Box.Height/2, + ) + } +} + +// createCircularArc routes a single edge as a circular arc whose endpoints +// lie exactly on the borders of the source and destination shapes. The arc +// belongs to the layout circle, centered at the origin with the given radius; +// the source and destination shape centers both lie on that circle. +func createCircularArc(edge *d2graph.Edge, radius float64) { + if edge.Src == nil || edge.Dst == nil { + return + } + + if edge.Src == edge.Dst { + selfLoopRoute(edge) + return + } + + srcCenter := edge.Src.Center() + dstCenter := edge.Dst.Center() + origin := geo.NewPoint(0, 0) + + srcAngle := math.Atan2(srcCenter.Y, srcCenter.X) + dstAngle := math.Atan2(dstCenter.Y, dstCenter.X) + if dstAngle < srcAngle { + dstAngle += 2 * math.Pi + } + sweep := dstAngle - srcAngle + srcShape := edge.Src.ToShape() + dstShape := edge.Dst.ToShape() + if sweep <= 0 { + fallbackStraightRoute(edge, srcShape, dstShape, srcCenter, dstCenter) + return + } + + startAngle, hasStart := nextBoundaryAngle(edge.Src.Box, origin, radius, srcAngle, sweep, true) + endAngle, hasEnd := nextBoundaryAngle(edge.Dst.Box, origin, radius, srcAngle, sweep, false) + if !hasStart || !hasEnd || endAngle <= startAngle { + fallbackStraightRoute(edge, srcShape, dstShape, srcCenter, dstCenter) + return + } + + path := make([]*geo.Point, 0, ARC_STEPS+1) + for i := 0; i <= ARC_STEPS; i++ { + t := float64(i) / float64(ARC_STEPS) + angle := startAngle + t*(endAngle-startAngle) + path = append(path, geo.NewPoint(radius*math.Cos(angle), radius*math.Sin(angle))) + } + + // path[0] / path[len-1] sit on the bounding-box border of the source / + // destination shape. For non-rectangular shapes (circle, oval, hexagon, + // cloud, ...) the bounding box border is not the shape border, so trace + // each endpoint inward from the shape center to the actual shape outline. + // TraceToShapeBorder is a no-op for rectangular shapes. + path[0] = shape.TraceToShapeBorder(srcShape, path[0], srcCenter) + path[len(path)-1] = shape.TraceToShapeBorder(dstShape, path[len(path)-1], dstCenter) + + edge.Route = path + edge.IsCurve = true +} + +// selfLoopRoute routes an edge whose source and destination are the same shape +// as a small circle sitting just outside that shape, on the far side from the +// centre of the layout ring. The circular arc code cannot express a self loop: +// the two centres coincide, so the sweep is zero and every clipped endpoint +// collapses onto the same point. That leaves a zero length route, which the +// renderer turns into NaN coordinates and the render target validator rejects. +func selfLoopRoute(edge *d2graph.Edge) { + box := edge.Src.Box + center := edge.Src.Center() + + // The ring is centred on the origin, so the shape centre already points + // away from it. A shape sitting exactly on the origin has no outward + // direction of its own; send its loop straight up. + outX, outY := center.X, center.Y + if norm := math.Hypot(outX, outY); norm > 0 { + outX, outY = outX/norm, outY/norm + } else { + outX, outY = 0, -1 + } + + reach := box.Width + box.Height + far := geo.NewPoint(center.X+outX*reach, center.Y+outY*reach) + anchor := clipToShapeBorder(edge.Src.ToShape(), box, center, far) + + loopRadius := math.Max(SELF_LOOP_MIN_RADIUS, math.Min(box.Width, box.Height)/3) + loopCenter := geo.NewPoint(anchor.X+outX*loopRadius, anchor.Y+outY*loopRadius) + // anchor lies on the loop, so start and finish the sweep there. + startAngle := math.Atan2(anchor.Y-loopCenter.Y, anchor.X-loopCenter.X) + + path := make([]*geo.Point, 0, ARC_STEPS+1) + for i := 0; i <= ARC_STEPS; i++ { + angle := startAngle + 2*math.Pi*float64(i)/float64(ARC_STEPS) + path = append(path, geo.NewPoint( + loopCenter.X+loopRadius*math.Cos(angle), + loopCenter.Y+loopRadius*math.Sin(angle), + )) + } + + edge.Route = path + edge.IsCurve = true +} + +// fallbackStraightRoute renders a straight connection whose endpoints are +// clipped to the source and destination shape borders along the line between +// the two centers, used when the analytic arc geometry degenerates (zero +// sweep, no boundary crossing in the arc range, etc.). +func fallbackStraightRoute(edge *d2graph.Edge, srcShape, dstShape shape.Shape, srcCenter, dstCenter *geo.Point) { + srcBorder := clipToShapeBorder(srcShape, edge.Src.Box, srcCenter, dstCenter) + dstBorder := clipToShapeBorder(dstShape, edge.Dst.Box, dstCenter, srcCenter) + edge.Route = []*geo.Point{srcBorder, dstBorder} + edge.IsCurve = false +} + +// clipToShapeBorder returns the point where a ray from `from` (assumed inside +// `box`) toward `toward` first exits the actual shape outline. The bounding +// box is consulted first to obtain a rectangular border point, then the shape +// helper refines it for non-rectangular shapes. +func clipToShapeBorder(shp shape.Shape, box *geo.Box, from, toward *geo.Point) *geo.Point { + dx := toward.X - from.X + dy := toward.Y - from.Y + dist := math.Hypot(dx, dy) + if dist == 0 { + return from + } + // Extend the ray well past `toward` so the segment definitely exits the + // box even when `toward` itself sits inside the box. + diag := math.Hypot(box.Width, box.Height) + scale := (dist + 2*diag) / dist + extended := geo.NewPoint(from.X+dx*scale, from.Y+dy*scale) + + rectBorder := extended + if pts := box.Intersections(geo.Segment{Start: from, End: extended}); len(pts) > 0 { + rectBorder = pts[0] + } + return shape.TraceToShapeBorder(shp, rectBorder, from) +} + +// nextBoundaryAngle scans the angles where the layout circle crosses an edge +// of the box. When forSrc is true it returns the smallest such angle strictly +// greater than srcAngle (the point where the arc exits the source box). When +// forSrc is false it returns the largest such angle strictly less than +// srcAngle+sweep (the point where the arc enters the destination box). The +// boolean is false when no crossing exists in the (srcAngle, srcAngle+sweep) +// range, in which case the caller falls back to the shape center. +func nextBoundaryAngle(box *geo.Box, origin *geo.Point, radius, srcAngle, sweep float64, forSrc bool) (float64, bool) { + candidates := boxCircleIntersectionAngles(box, origin, radius) + endAngle := srcAngle + sweep + + var best float64 + found := false + for _, raw := range candidates { + a := raw + for a <= srcAngle { + a += 2 * math.Pi + } + for a > srcAngle+2*math.Pi { + a -= 2 * math.Pi + } + if a >= endAngle { + continue + } + if !found { + best = a + found = true + continue + } + if forSrc { + if a < best { + best = a + } + } else { + if a > best { + best = a + } + } + } + return best, found +} + +func boxCircleIntersectionAngles(box *geo.Box, origin *geo.Point, radius float64) []float64 { + edges := boxEdges(box) + var angles []float64 + for _, e := range edges { + for _, p := range e.IntersectCircle(origin, radius) { + angles = append(angles, math.Atan2(p.Y-origin.Y, p.X-origin.X)) + } + } + return angles +} + +func boxEdges(box *geo.Box) []geo.Segment { + tl := box.TopLeft + tr := geo.NewPoint(tl.X+box.Width, tl.Y) + bl := geo.NewPoint(tl.X, tl.Y+box.Height) + br := geo.NewPoint(tl.X+box.Width, tl.Y+box.Height) + return []geo.Segment{ + {Start: tl, End: tr}, + {Start: tr, End: br}, + {Start: br, End: bl}, + {Start: bl, End: tl}, + } +} + +// positionLabelsIcons applies a sensible default label/icon position when one +// has not been explicitly specified. +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..6282775bd4 --- /dev/null +++ b/d2layouts/d2cycle/layout_test.go @@ -0,0 +1,101 @@ +package d2cycle + +import ( + "math" + "testing" + + "github.com/d2lang/d2/d2graph" + "github.com/d2lang/d2/lib/geo" +) + +// A curved connection is rendered as a chain of cubic segments, so the render +// target validator requires 1+3n points on its route. Every route this package +// emits samples ARC_STEPS+1 points, which ties the constant to that rule: the +// current value of 30 satisfies it, but nothing in the sampling loops would +// stop a later edit from picking a value that rejects every arc. +func TestARCStepsSamplesACubicPointCount(t *testing.T) { + points := ARC_STEPS + 1 + if (points-1)%3 != 0 { + t.Fatalf("ARC_STEPS = %d samples %d points, but a curved route needs 1+3n; pick a multiple of 3", ARC_STEPS, points) + } +} + +// A cycle holding a single shape has no neighbours to make room for, and the +// spacing formula is undefined there: sin(pi/1) rounds to 1.2e-16, which sends +// the radius to about 1e17. Coordinates that large are spaced further apart +// than the shapes are wide, so anything computed on the ring degenerates. +func TestCalculateRadiusStaysFiniteForOneObject(t *testing.T) { + obj := boxedObject(0, 0, 100, 60) + + radius := calculateRadius([]*d2graph.Object{obj}) + + if radius != MIN_RADIUS { + t.Fatalf("radius for a single object = %v, want %v", radius, MIN_RADIUS) + } +} + +// A self loop shares one centre between source and destination, so the arc +// geometry degenerates: the sweep is zero and both clipped endpoints land on +// the same point. Routing it as a straight line therefore yields a zero length +// route, which renders as NaN and is rejected by the render target validator. +func TestSelfLoopRouteIsRenderable(t *testing.T) { + for _, tc := range []struct { + name string + cx, cy, w, h float64 + }{ + {"on the ring", MIN_RADIUS, 0, 100, 60}, + {"above the ring centre", 0, -MIN_RADIUS, 100, 60}, + {"off axis", 141, -141, 100, 60}, + {"tiny shape", MIN_RADIUS, 0, 6, 6}, + {"at the ring centre", 0, 0, 100, 60}, + } { + t.Run(tc.name, func(t *testing.T) { + obj := boxedObject(tc.cx, tc.cy, tc.w, tc.h) + edge := &d2graph.Edge{Src: obj, Dst: obj} + + createCircularArc(edge, MIN_RADIUS) + + if !edge.IsCurve { + t.Error("a self loop must be marked as a curve") + } + if got, want := len(edge.Route), ARC_STEPS+1; got != want { + t.Fatalf("route has %d points, want %d", got, want) + } + for i, point := range edge.Route { + if math.IsNaN(point.X) || math.IsNaN(point.Y) || math.IsInf(point.X, 0) || math.IsInf(point.Y, 0) { + t.Fatalf("route[%d] = (%v, %v), want a finite point", i, point.X, point.Y) + } + if i == 0 { + continue + } + if previous := edge.Route[i-1]; previous.X == point.X && previous.Y == point.Y { + t.Fatalf("route[%d:%d] is a zero length segment at (%v, %v)", i-1, i, point.X, point.Y) + } + } + + // A loop squashed onto a line is not a loop. Both sides of its + // bounding box have to span the loop, which is at least + // SELF_LOOP_MIN_RADIUS across. + width, height := routeBounds(edge.Route) + if width < SELF_LOOP_MIN_RADIUS || height < SELF_LOOP_MIN_RADIUS { + t.Errorf("loop spans %v x %v, want at least %v on both sides", width, height, SELF_LOOP_MIN_RADIUS) + } + }) + } +} + +func boxedObject(centerX, centerY, width, height float64) *d2graph.Object { + return &d2graph.Object{ + Box: geo.NewBox(geo.NewPoint(centerX-width/2, centerY-height/2), width, height), + } +} + +func routeBounds(route []*geo.Point) (width, height float64) { + minX, minY := math.Inf(1), math.Inf(1) + maxX, maxY := math.Inf(-1), math.Inf(-1) + for _, point := range route { + minX, maxX = math.Min(minX, point.X), math.Max(maxX, point.X) + minY, maxY = math.Min(minY, point.Y), math.Max(maxY, point.Y) + } + return maxX - minX, maxY - minY +} 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 c7126219f9..85f894820e 100644 Binary files a/d2renderers/d2raster/testdata/target-shapes.exp.png and b/d2renderers/d2raster/testdata/target-shapes.exp.png differ 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 412e9abb2f..15f3fe1c5b 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -1831,7 +1831,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 ef9ee67bd3..b8a70edd04 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -1267,6 +1267,7 @@ const ( ShapeSQLTable = "sql_table" ShapeImage = "image" ShapeSequenceDiagram = "sequence_diagram" + ShapeCycleDiagram = "cycle" ShapeHierarchy = "hierarchy" ) @@ -1295,6 +1296,7 @@ var Shapes = []string{ ShapeSQLTable, ShapeImage, ShapeSequenceDiagram, + ShapeCycleDiagram, ShapeHierarchy, } @@ -1365,6 +1367,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..ba65555f70 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json @@ -0,0 +1,1496 @@ +{ + "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": 454, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": 514, + "y": 17 + }, + "width": 400, + "height": 433, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": 687, + "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": 860, + "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": 514, + "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": 974, + "y": 0 + }, + "width": 227, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": 974, + "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": 974, + "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.76300048828125 + }, + { + "x": 261.8789978027344, + "y": 36.06399917602539 + }, + { + "x": 270.1969909667969, + "y": 37.720001220703125 + }, + { + "x": 278.4360046386719, + "y": 39.72700119018555 + }, + { + "x": 286.5830078125, + "y": 42.08100128173828 + }, + { + "x": 294.62298583984375, + "y": 44.77899932861328 + }, + { + "x": 302.5409851074219, + "y": 47.814998626708984 + }, + { + "x": 310.3240051269531, + "y": 51.18299865722656 + }, + { + "x": 317.95599365234375, + "y": 54.87900161743164 + }, + { + "x": 325.4259948730469, + "y": 58.89500045776367 + }, + { + "x": 332.7179870605469, + "y": 63.2239990234375 + }, + { + "x": 339.82000732421875, + "y": 67.85800170898438 + }, + { + "x": 346.718994140625, + "y": 72.79000091552734 + }, + { + "x": 353.40301513671875, + "y": 78.00900268554688 + }, + { + "x": 359.8599853515625, + "y": 83.50700378417969 + }, + { + "x": 366.0780029296875, + "y": 89.27300262451172 + }, + { + "x": 372.0459899902344, + "y": 95.2979965209961 + }, + { + "x": 377.75299072265625, + "y": 101.57099914550781 + }, + { + "x": 383.1889953613281, + "y": 108.08000183105469 + }, + { + "x": 388.343994140625, + "y": 114.81300354003906 + }, + { + "x": 393.2090148925781, + "y": 121.75900268554688 + }, + { + "x": 397.7749938964844, + "y": 128.90499877929688 + }, + { + "x": 402.0350036621094, + "y": 136.23800659179688 + }, + { + "x": 405.97900390625, + "y": 143.74600219726562 + }, + { + "x": 409.60198974609375, + "y": 151.41299438476562 + }, + { + "x": 412.89599609375, + "y": 159.2270050048828 + }, + { + "x": 415.85699462890625, + "y": 167.1739959716797 + }, + { + "x": 418.47698974609375, + "y": 175.24000549316406 + }, + { + "x": 420.7539978027344, + "y": 183.40899658203125 + }, + { + "x": 422.6820068359375, + "y": 191.66700744628906 + }, + { + "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": 422.6820068359375, + "y": 274.3320007324219 + }, + { + "x": 420.7539978027344, + "y": 282.5899963378906 + }, + { + "x": 418.47698974609375, + "y": 290.7590026855469 + }, + { + "x": 415.85699462890625, + "y": 298.82501220703125 + }, + { + "x": 412.89599609375, + "y": 306.7720031738281 + }, + { + "x": 409.60198974609375, + "y": 314.58599853515625 + }, + { + "x": 405.97900390625, + "y": 322.25299072265625 + }, + { + "x": 402.0350036621094, + "y": 329.760986328125 + }, + { + "x": 397.7749938964844, + "y": 337.093994140625 + }, + { + "x": 393.2090148925781, + "y": 344.239990234375 + }, + { + "x": 388.343994140625, + "y": 351.1860046386719 + }, + { + "x": 383.1889953613281, + "y": 357.91900634765625 + }, + { + "x": 377.75299072265625, + "y": 364.4280090332031 + }, + { + "x": 372.0459899902344, + "y": 370.70098876953125 + }, + { + "x": 366.0780029296875, + "y": 376.72601318359375 + }, + { + "x": 359.8599853515625, + "y": 382.49200439453125 + }, + { + "x": 353.40301513671875, + "y": 387.989990234375 + }, + { + "x": 346.718994140625, + "y": 393.2090148925781 + }, + { + "x": 339.82000732421875, + "y": 398.1409912109375 + }, + { + "x": 332.7179870605469, + "y": 402.7749938964844 + }, + { + "x": 325.4259948730469, + "y": 407.10400390625 + }, + { + "x": 317.95599365234375, + "y": 411.1199951171875 + }, + { + "x": 310.3240051269531, + "y": 414.8160095214844 + }, + { + "x": 302.5409851074219, + "y": 418.1839904785156 + }, + { + "x": 294.62298583984375, + "y": 421.2200012207031 + }, + { + "x": 286.5830078125, + "y": 423.9179992675781 + }, + { + "x": 278.4360046386719, + "y": 426.2720031738281 + }, + { + "x": 270.1969909667969, + "y": 428.27899169921875 + }, + { + "x": 261.8789978027344, + "y": 429.93499755859375 + }, + { + "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": 192.1199951171875, + "y": 429.93499755859375 + }, + { + "x": 183.802001953125, + "y": 428.27899169921875 + }, + { + "x": 175.56300354003906, + "y": 426.2720031738281 + }, + { + "x": 167.41600036621094, + "y": 423.9179992675781 + }, + { + "x": 159.37600708007812, + "y": 421.2200012207031 + }, + { + "x": 151.45799255371094, + "y": 418.1839904785156 + }, + { + "x": 143.6750030517578, + "y": 414.8160095214844 + }, + { + "x": 136.04299926757812, + "y": 411.1199951171875 + }, + { + "x": 128.572998046875, + "y": 407.10400390625 + }, + { + "x": 121.28099822998047, + "y": 402.7749938964844 + }, + { + "x": 114.17900085449219, + "y": 398.1409912109375 + }, + { + "x": 107.27999877929688, + "y": 393.2090148925781 + }, + { + "x": 100.59600067138672, + "y": 387.989990234375 + }, + { + "x": 94.13899993896484, + "y": 382.49200439453125 + }, + { + "x": 87.9209976196289, + "y": 376.72601318359375 + }, + { + "x": 81.9530029296875, + "y": 370.70098876953125 + }, + { + "x": 76.24600219726562, + "y": 364.4280090332031 + }, + { + "x": 70.80999755859375, + "y": 357.91900634765625 + }, + { + "x": 65.65499877929688, + "y": 351.1860046386719 + }, + { + "x": 60.790000915527344, + "y": 344.239990234375 + }, + { + "x": 56.2239990234375, + "y": 337.093994140625 + }, + { + "x": 51.9640007019043, + "y": 329.760986328125 + }, + { + "x": 48.02000045776367, + "y": 322.25299072265625 + }, + { + "x": 44.39699935913086, + "y": 314.58599853515625 + }, + { + "x": 41.10300064086914, + "y": 306.7720031738281 + }, + { + "x": 38.141998291015625, + "y": 298.82501220703125 + }, + { + "x": 35.52199935913086, + "y": 290.7590026855469 + }, + { + "x": 33.244998931884766, + "y": 282.5899963378906 + }, + { + "x": 31.316999435424805, + "y": 274.3320007324219 + }, + { + "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": 740.2050170898438, + "y": 51.76300048828125 + }, + { + "x": 751.9099731445312, + "y": 53.68299865722656 + }, + { + "x": 763.4810180664062, + "y": 56.292999267578125 + }, + { + "x": 774.8770141601562, + "y": 59.58399963378906 + }, + { + "x": 786.0570068359375, + "y": 63.54600143432617 + }, + { + "x": 796.9829711914062, + "y": 68.16300201416016 + }, + { + "x": 807.6170043945312, + "y": 73.41899871826172 + }, + { + "x": 817.9199829101562, + "y": 79.2969970703125 + }, + { + "x": 827.8560180664062, + "y": 85.7760009765625 + }, + { + "x": 837.3909912109375, + "y": 92.83200073242188 + }, + { + "x": 846.489990234375, + "y": 100.44000244140625 + }, + { + "x": 855.1229858398438, + "y": 108.57499694824219 + }, + { + "x": 863.2579956054688, + "y": 117.20800018310547 + }, + { + "x": 870.8670043945312, + "y": 126.30699920654297 + }, + { + "x": 877.9240112304688, + "y": 135.8419952392578 + }, + { + "x": 884.4030151367188, + "y": 145.7779998779297 + }, + { + "x": 890.281005859375, + "y": 156.0800018310547 + }, + { + "x": 895.5380249023438, + "y": 166.71299743652344 + }, + { + "x": 900.156005859375, + "y": 177.63900756835938 + }, + { + "x": 904.1170043945312, + "y": 188.82000732421875 + }, + { + "x": 907.4089965820312, + "y": 200.21499633789062 + }, + { + "x": 910.02001953125, + "y": 211.78599548339844 + }, + { + "x": 911.9400024414062, + "y": 223.49200439453125 + }, + { + "x": 913.1630249023438, + "y": 235.2899932861328 + }, + { + "x": 913.6840209960938, + "y": 247.13999938964844 + }, + { + "x": 913.5020141601562, + "y": 259 + }, + { + "x": 912.6170043945312, + "y": 270.8290100097656 + }, + { + "x": 911.031982421875, + "y": 282.5840148925781 + }, + { + "x": 908.7540283203125, + "y": 294.2250061035156 + }, + { + "x": 905.7890014648438, + "y": 305.7099914550781 + }, + { + "x": 902.1480102539062, + "y": 317 + } + ], + "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": 863.072998046875, + "y": 383 + }, + { + "x": 855.364013671875, + "y": 391.1820068359375 + }, + { + "x": 847.2069702148438, + "y": 398.91900634765625 + }, + { + "x": 838.6279907226562, + "y": 406.1860046386719 + }, + { + "x": 829.655029296875, + "y": 412.9580078125 + }, + { + "x": 820.3150024414062, + "y": 419.21600341796875 + }, + { + "x": 810.6380004882812, + "y": 424.9389953613281 + }, + { + "x": 800.655029296875, + "y": 430.1099853515625 + }, + { + "x": 790.39697265625, + "y": 434.71099853515625 + }, + { + "x": 779.89697265625, + "y": 438.7279968261719 + }, + { + "x": 769.18701171875, + "y": 442.1499938964844 + }, + { + "x": 758.302978515625, + "y": 444.9639892578125 + }, + { + "x": 747.2769775390625, + "y": 447.1619873046875 + }, + { + "x": 736.14501953125, + "y": 448.73699951171875 + }, + { + "x": 724.9429931640625, + "y": 449.6839904785156 + }, + { + "x": 713.7050170898438, + "y": 450 + }, + { + "x": 702.4660034179688, + "y": 449.6839904785156 + }, + { + "x": 691.2639770507812, + "y": 448.73699951171875 + }, + { + "x": 680.1320190429688, + "y": 447.1619873046875 + }, + { + "x": 669.1069946289062, + "y": 444.9639892578125 + }, + { + "x": 658.2219848632812, + "y": 442.1499938964844 + }, + { + "x": 647.5130004882812, + "y": 438.7279968261719 + }, + { + "x": 637.0120239257812, + "y": 434.71099853515625 + }, + { + "x": 626.7540283203125, + "y": 430.1099853515625 + }, + { + "x": 616.77099609375, + "y": 424.9389953613281 + }, + { + "x": 607.094970703125, + "y": 419.21600341796875 + }, + { + "x": 597.7550048828125, + "y": 412.9580078125 + }, + { + "x": 588.781005859375, + "y": 406.1860046386719 + }, + { + "x": 580.2020263671875, + "y": 398.91900634765625 + }, + { + "x": 572.0449829101562, + "y": 391.1820068359375 + }, + { + "x": 564.3359985351562, + "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": 1027, + "y": 34.76300048828125 + }, + { + "x": 1045.85205078125, + "y": 38.20899963378906 + }, + { + "x": 1064.2879638671875, + "y": 43.44499969482422 + }, + { + "x": 1082.137939453125, + "y": 50.41999816894531 + }, + { + "x": 1099.238037109375, + "y": 59.071998596191406 + }, + { + "x": 1115.4320068359375, + "y": 69.3219985961914 + }, + { + "x": 1130.571044921875, + "y": 81.0739974975586 + }, + { + "x": 1144.5150146484375, + "y": 94.22100067138672 + }, + { + "x": 1157.136962890625, + "y": 108.64199829101562 + }, + { + "x": 1168.3199462890625, + "y": 124.20500183105469 + }, + { + "x": 1177.9630126953125, + "y": 140.76800537109375 + }, + { + "x": 1185.9759521484375, + "y": 158.177001953125 + }, + { + "x": 1192.2860107421875, + "y": 176.2729949951172 + }, + { + "x": 1196.8349609375, + "y": 194.88999938964844 + }, + { + "x": 1199.5810546875, + "y": 213.85699462890625 + }, + { + "x": 1200.5, + "y": 233 + }, + { + "x": 1199.5810546875, + "y": 252.14199829101562 + }, + { + "x": 1196.8349609375, + "y": 271.1090087890625 + }, + { + "x": 1192.2860107421875, + "y": 289.72601318359375 + }, + { + "x": 1185.9759521484375, + "y": 307.8219909667969 + }, + { + "x": 1177.9630126953125, + "y": 325.2309875488281 + }, + { + "x": 1168.3199462890625, + "y": 341.79400634765625 + }, + { + "x": 1157.136962890625, + "y": 357.35699462890625 + }, + { + "x": 1144.5150146484375, + "y": 371.77801513671875 + }, + { + "x": 1130.571044921875, + "y": 384.92498779296875 + }, + { + "x": 1115.4320068359375, + "y": 396.677001953125 + }, + { + "x": 1099.238037109375, + "y": 406.927001953125 + }, + { + "x": 1082.137939453125, + "y": 415.5790100097656 + }, + { + "x": 1064.2879638671875, + "y": 422.5539855957031 + }, + { + "x": 1045.85205078125, + "y": 427.7900085449219 + }, + { + "x": 1027, + "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/isometric.exp.svg b/e2etests/testdata/txtar/cycle-diagram/dagre/isometric.exp.svg new file mode 100644 index 0000000000..1ac53d5781 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/isometric.exp.svg @@ -0,0 +1 @@ +D2 isometric diagram \ No newline at end of file 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..40257d2d40 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg @@ -0,0 +1,29 @@ +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..720b49c4ec --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json @@ -0,0 +1,1496 @@ +{ + "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": "N7", + "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": 486, + "y": 28 + }, + "width": 400, + "height": 433, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": 659, + "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": 832, + "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": 486, + "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": 906, + "y": 12 + }, + "width": 227, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": 906, + "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": 906, + "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.76300048828125 + }, + { + "x": 273.8789978027344, + "y": 48.06399917602539 + }, + { + "x": 282.1969909667969, + "y": 49.720001220703125 + }, + { + "x": 290.4360046386719, + "y": 51.72700119018555 + }, + { + "x": 298.5830078125, + "y": 54.08100128173828 + }, + { + "x": 306.62298583984375, + "y": 56.77899932861328 + }, + { + "x": 314.5409851074219, + "y": 59.814998626708984 + }, + { + "x": 322.3240051269531, + "y": 63.18299865722656 + }, + { + "x": 329.95599365234375, + "y": 66.87899780273438 + }, + { + "x": 337.4259948730469, + "y": 70.8949966430664 + }, + { + "x": 344.7179870605469, + "y": 75.2239990234375 + }, + { + "x": 351.82000732421875, + "y": 79.85800170898438 + }, + { + "x": 358.718994140625, + "y": 84.79000091552734 + }, + { + "x": 365.40301513671875, + "y": 90.00900268554688 + }, + { + "x": 371.8599853515625, + "y": 95.50700378417969 + }, + { + "x": 378.0780029296875, + "y": 101.27300262451172 + }, + { + "x": 384.0459899902344, + "y": 107.2979965209961 + }, + { + "x": 389.75299072265625, + "y": 113.57099914550781 + }, + { + "x": 395.1889953613281, + "y": 120.08000183105469 + }, + { + "x": 400.343994140625, + "y": 126.81300354003906 + }, + { + "x": 405.2090148925781, + "y": 133.75900268554688 + }, + { + "x": 409.7749938964844, + "y": 140.90499877929688 + }, + { + "x": 414.0350036621094, + "y": 148.23800659179688 + }, + { + "x": 417.97900390625, + "y": 155.74600219726562 + }, + { + "x": 421.60198974609375, + "y": 163.41299438476562 + }, + { + "x": 424.89599609375, + "y": 171.2270050048828 + }, + { + "x": 427.85699462890625, + "y": 179.1739959716797 + }, + { + "x": 430.47698974609375, + "y": 187.24000549316406 + }, + { + "x": 432.7539978027344, + "y": 195.40899658203125 + }, + { + "x": 434.6820068359375, + "y": 203.66700744628906 + }, + { + "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": 434.6820068359375, + "y": 286.3320007324219 + }, + { + "x": 432.7539978027344, + "y": 294.5899963378906 + }, + { + "x": 430.47698974609375, + "y": 302.7590026855469 + }, + { + "x": 427.85699462890625, + "y": 310.82501220703125 + }, + { + "x": 424.89599609375, + "y": 318.7720031738281 + }, + { + "x": 421.60198974609375, + "y": 326.58599853515625 + }, + { + "x": 417.97900390625, + "y": 334.25299072265625 + }, + { + "x": 414.0350036621094, + "y": 341.760986328125 + }, + { + "x": 409.7749938964844, + "y": 349.093994140625 + }, + { + "x": 405.2090148925781, + "y": 356.239990234375 + }, + { + "x": 400.343994140625, + "y": 363.1860046386719 + }, + { + "x": 395.1889953613281, + "y": 369.91900634765625 + }, + { + "x": 389.75299072265625, + "y": 376.4280090332031 + }, + { + "x": 384.0459899902344, + "y": 382.70098876953125 + }, + { + "x": 378.0780029296875, + "y": 388.72601318359375 + }, + { + "x": 371.8599853515625, + "y": 394.49200439453125 + }, + { + "x": 365.40301513671875, + "y": 399.989990234375 + }, + { + "x": 358.718994140625, + "y": 405.2090148925781 + }, + { + "x": 351.82000732421875, + "y": 410.1409912109375 + }, + { + "x": 344.7179870605469, + "y": 414.7749938964844 + }, + { + "x": 337.4259948730469, + "y": 419.10400390625 + }, + { + "x": 329.95599365234375, + "y": 423.1199951171875 + }, + { + "x": 322.3240051269531, + "y": 426.8160095214844 + }, + { + "x": 314.5409851074219, + "y": 430.1839904785156 + }, + { + "x": 306.62298583984375, + "y": 433.2200012207031 + }, + { + "x": 298.5830078125, + "y": 435.9179992675781 + }, + { + "x": 290.4360046386719, + "y": 438.2720031738281 + }, + { + "x": 282.1969909667969, + "y": 440.27899169921875 + }, + { + "x": 273.8789978027344, + "y": 441.93499755859375 + }, + { + "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": 204.1199951171875, + "y": 441.93499755859375 + }, + { + "x": 195.802001953125, + "y": 440.27899169921875 + }, + { + "x": 187.56300354003906, + "y": 438.2720031738281 + }, + { + "x": 179.41600036621094, + "y": 435.9179992675781 + }, + { + "x": 171.37600708007812, + "y": 433.2200012207031 + }, + { + "x": 163.45799255371094, + "y": 430.1839904785156 + }, + { + "x": 155.6750030517578, + "y": 426.8160095214844 + }, + { + "x": 148.04299926757812, + "y": 423.1199951171875 + }, + { + "x": 140.572998046875, + "y": 419.10400390625 + }, + { + "x": 133.281005859375, + "y": 414.7749938964844 + }, + { + "x": 126.17900085449219, + "y": 410.1409912109375 + }, + { + "x": 119.27999877929688, + "y": 405.2090148925781 + }, + { + "x": 112.59600067138672, + "y": 399.989990234375 + }, + { + "x": 106.13899993896484, + "y": 394.49200439453125 + }, + { + "x": 99.9209976196289, + "y": 388.72601318359375 + }, + { + "x": 93.9530029296875, + "y": 382.70098876953125 + }, + { + "x": 88.24600219726562, + "y": 376.4280090332031 + }, + { + "x": 82.80999755859375, + "y": 369.91900634765625 + }, + { + "x": 77.65499877929688, + "y": 363.1860046386719 + }, + { + "x": 72.79000091552734, + "y": 356.239990234375 + }, + { + "x": 68.2239990234375, + "y": 349.093994140625 + }, + { + "x": 63.9640007019043, + "y": 341.760986328125 + }, + { + "x": 60.02000045776367, + "y": 334.25299072265625 + }, + { + "x": 56.39699935913086, + "y": 326.58599853515625 + }, + { + "x": 53.10300064086914, + "y": 318.7720031738281 + }, + { + "x": 50.141998291015625, + "y": 310.82501220703125 + }, + { + "x": 47.52199935913086, + "y": 302.7590026855469 + }, + { + "x": 45.244998931884766, + "y": 294.5899963378906 + }, + { + "x": 43.31700134277344, + "y": 286.3320007324219 + }, + { + "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": 712.2050170898438, + "y": 63.26300048828125 + }, + { + "x": 723.9099731445312, + "y": 65.18299865722656 + }, + { + "x": 735.4810180664062, + "y": 67.79299926757812 + }, + { + "x": 746.8770141601562, + "y": 71.08399963378906 + }, + { + "x": 758.0570068359375, + "y": 75.0459976196289 + }, + { + "x": 768.9829711914062, + "y": 79.66300201416016 + }, + { + "x": 779.6170043945312, + "y": 84.91899871826172 + }, + { + "x": 789.9199829101562, + "y": 90.7969970703125 + }, + { + "x": 799.8560180664062, + "y": 97.2760009765625 + }, + { + "x": 809.3909912109375, + "y": 104.33200073242188 + }, + { + "x": 818.489990234375, + "y": 111.94000244140625 + }, + { + "x": 827.1229858398438, + "y": 120.07499694824219 + }, + { + "x": 835.2579956054688, + "y": 128.70799255371094 + }, + { + "x": 842.8670043945312, + "y": 137.8070068359375 + }, + { + "x": 849.9240112304688, + "y": 147.3419952392578 + }, + { + "x": 856.4030151367188, + "y": 157.2779998779297 + }, + { + "x": 862.281005859375, + "y": 167.5800018310547 + }, + { + "x": 867.5380249023438, + "y": 178.21299743652344 + }, + { + "x": 872.156005859375, + "y": 189.13900756835938 + }, + { + "x": 876.1170043945312, + "y": 200.32000732421875 + }, + { + "x": 879.4089965820312, + "y": 211.71499633789062 + }, + { + "x": 882.02001953125, + "y": 223.28599548339844 + }, + { + "x": 883.9400024414062, + "y": 234.99200439453125 + }, + { + "x": 885.1630249023438, + "y": 246.7899932861328 + }, + { + "x": 885.6840209960938, + "y": 258.6400146484375 + }, + { + "x": 885.5020141601562, + "y": 270.5 + }, + { + "x": 884.6170043945312, + "y": 282.3290100097656 + }, + { + "x": 883.031982421875, + "y": 294.0840148925781 + }, + { + "x": 880.7540283203125, + "y": 305.7250061035156 + }, + { + "x": 877.7890014648438, + "y": 317.2099914550781 + }, + { + "x": 874.1480102539062, + "y": 328.5 + } + ], + "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": 835.072998046875, + "y": 394.5 + }, + { + "x": 827.364013671875, + "y": 402.6820068359375 + }, + { + "x": 819.2069702148438, + "y": 410.41900634765625 + }, + { + "x": 810.6279907226562, + "y": 417.6860046386719 + }, + { + "x": 801.655029296875, + "y": 424.4580078125 + }, + { + "x": 792.3150024414062, + "y": 430.71600341796875 + }, + { + "x": 782.6380004882812, + "y": 436.4389953613281 + }, + { + "x": 772.655029296875, + "y": 441.6099853515625 + }, + { + "x": 762.39697265625, + "y": 446.21099853515625 + }, + { + "x": 751.89697265625, + "y": 450.2279968261719 + }, + { + "x": 741.18701171875, + "y": 453.6499938964844 + }, + { + "x": 730.302978515625, + "y": 456.4639892578125 + }, + { + "x": 719.2769775390625, + "y": 458.6619873046875 + }, + { + "x": 708.14501953125, + "y": 460.23699951171875 + }, + { + "x": 696.9429931640625, + "y": 461.1839904785156 + }, + { + "x": 685.7050170898438, + "y": 461.5 + }, + { + "x": 674.4660034179688, + "y": 461.1839904785156 + }, + { + "x": 663.2639770507812, + "y": 460.23699951171875 + }, + { + "x": 652.1320190429688, + "y": 458.6619873046875 + }, + { + "x": 641.1069946289062, + "y": 456.4639892578125 + }, + { + "x": 630.2219848632812, + "y": 453.6499938964844 + }, + { + "x": 619.5130004882812, + "y": 450.2279968261719 + }, + { + "x": 609.0120239257812, + "y": 446.21099853515625 + }, + { + "x": 598.7540283203125, + "y": 441.6099853515625 + }, + { + "x": 588.77099609375, + "y": 436.4389953613281 + }, + { + "x": 579.094970703125, + "y": 430.71600341796875 + }, + { + "x": 569.7550048828125, + "y": 424.4580078125 + }, + { + "x": 560.781005859375, + "y": 417.6860046386719 + }, + { + "x": 552.2020263671875, + "y": 410.41900634765625 + }, + { + "x": 544.0449829101562, + "y": 402.6820068359375 + }, + { + "x": 536.3359985351562, + "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": 959, + "y": 46.76300048828125 + }, + { + "x": 977.8519897460938, + "y": 50.20899963378906 + }, + { + "x": 996.2880249023438, + "y": 55.44499969482422 + }, + { + "x": 1014.1380004882812, + "y": 62.41999816894531 + }, + { + "x": 1031.238037109375, + "y": 71.0719985961914 + }, + { + "x": 1047.4320068359375, + "y": 81.3219985961914 + }, + { + "x": 1062.571044921875, + "y": 93.0739974975586 + }, + { + "x": 1076.5150146484375, + "y": 106.22100067138672 + }, + { + "x": 1089.136962890625, + "y": 120.64199829101562 + }, + { + "x": 1100.3199462890625, + "y": 136.2050018310547 + }, + { + "x": 1109.9630126953125, + "y": 152.76800537109375 + }, + { + "x": 1117.9759521484375, + "y": 170.177001953125 + }, + { + "x": 1124.2860107421875, + "y": 188.2729949951172 + }, + { + "x": 1128.8349609375, + "y": 206.88999938964844 + }, + { + "x": 1131.5810546875, + "y": 225.85699462890625 + }, + { + "x": 1132.5, + "y": 245 + }, + { + "x": 1131.5810546875, + "y": 264.1419982910156 + }, + { + "x": 1128.8349609375, + "y": 283.1090087890625 + }, + { + "x": 1124.2860107421875, + "y": 301.72601318359375 + }, + { + "x": 1117.9759521484375, + "y": 319.8219909667969 + }, + { + "x": 1109.9630126953125, + "y": 337.2309875488281 + }, + { + "x": 1100.3199462890625, + "y": 353.79400634765625 + }, + { + "x": 1089.136962890625, + "y": 369.35699462890625 + }, + { + "x": 1076.5150146484375, + "y": 383.77801513671875 + }, + { + "x": 1062.571044921875, + "y": 396.92498779296875 + }, + { + "x": 1047.4320068359375, + "y": 408.677001953125 + }, + { + "x": 1031.238037109375, + "y": 418.927001953125 + }, + { + "x": 1014.1380004882812, + "y": 427.5790100097656 + }, + { + "x": 996.2880249023438, + "y": 434.5539855957031 + }, + { + "x": 977.8519897460938, + "y": 439.7900085449219 + }, + { + "x": 959, + "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/isometric.exp.svg b/e2etests/testdata/txtar/cycle-diagram/elk/isometric.exp.svg new file mode 100644 index 0000000000..96aef41957 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/isometric.exp.svg @@ -0,0 +1 @@ +D2 isometric diagram \ No newline at end of file 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..a25815cecc --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg @@ -0,0 +1,29 @@ +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..befb9fd6d0 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json @@ -0,0 +1,1496 @@ +{ + "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": 454, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": 474, + "y": 0 + }, + "width": 400, + "height": 433, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": 647, + "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": 820, + "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": 474, + "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": 894, + "y": 0 + }, + "width": 227, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": 894, + "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": 894, + "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.76300048828125 + }, + { + "x": 261.8789978027344, + "y": 36.06399917602539 + }, + { + "x": 270.1969909667969, + "y": 37.720001220703125 + }, + { + "x": 278.4360046386719, + "y": 39.72700119018555 + }, + { + "x": 286.5830078125, + "y": 42.08100128173828 + }, + { + "x": 294.62298583984375, + "y": 44.77899932861328 + }, + { + "x": 302.5409851074219, + "y": 47.814998626708984 + }, + { + "x": 310.3240051269531, + "y": 51.18299865722656 + }, + { + "x": 317.95599365234375, + "y": 54.87900161743164 + }, + { + "x": 325.4259948730469, + "y": 58.89500045776367 + }, + { + "x": 332.7179870605469, + "y": 63.2239990234375 + }, + { + "x": 339.82000732421875, + "y": 67.85800170898438 + }, + { + "x": 346.718994140625, + "y": 72.79000091552734 + }, + { + "x": 353.40301513671875, + "y": 78.00900268554688 + }, + { + "x": 359.8599853515625, + "y": 83.50700378417969 + }, + { + "x": 366.0780029296875, + "y": 89.27300262451172 + }, + { + "x": 372.0459899902344, + "y": 95.2979965209961 + }, + { + "x": 377.75299072265625, + "y": 101.57099914550781 + }, + { + "x": 383.1889953613281, + "y": 108.08000183105469 + }, + { + "x": 388.343994140625, + "y": 114.81300354003906 + }, + { + "x": 393.2090148925781, + "y": 121.75900268554688 + }, + { + "x": 397.7749938964844, + "y": 128.90499877929688 + }, + { + "x": 402.0350036621094, + "y": 136.23800659179688 + }, + { + "x": 405.97900390625, + "y": 143.74600219726562 + }, + { + "x": 409.60198974609375, + "y": 151.41299438476562 + }, + { + "x": 412.89599609375, + "y": 159.2270050048828 + }, + { + "x": 415.85699462890625, + "y": 167.1739959716797 + }, + { + "x": 418.47698974609375, + "y": 175.24000549316406 + }, + { + "x": 420.7539978027344, + "y": 183.40899658203125 + }, + { + "x": 422.6820068359375, + "y": 191.66700744628906 + }, + { + "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": 422.6820068359375, + "y": 274.3320007324219 + }, + { + "x": 420.7539978027344, + "y": 282.5899963378906 + }, + { + "x": 418.47698974609375, + "y": 290.7590026855469 + }, + { + "x": 415.85699462890625, + "y": 298.82501220703125 + }, + { + "x": 412.89599609375, + "y": 306.7720031738281 + }, + { + "x": 409.60198974609375, + "y": 314.58599853515625 + }, + { + "x": 405.97900390625, + "y": 322.25299072265625 + }, + { + "x": 402.0350036621094, + "y": 329.760986328125 + }, + { + "x": 397.7749938964844, + "y": 337.093994140625 + }, + { + "x": 393.2090148925781, + "y": 344.239990234375 + }, + { + "x": 388.343994140625, + "y": 351.1860046386719 + }, + { + "x": 383.1889953613281, + "y": 357.91900634765625 + }, + { + "x": 377.75299072265625, + "y": 364.4280090332031 + }, + { + "x": 372.0459899902344, + "y": 370.70098876953125 + }, + { + "x": 366.0780029296875, + "y": 376.72601318359375 + }, + { + "x": 359.8599853515625, + "y": 382.49200439453125 + }, + { + "x": 353.40301513671875, + "y": 387.989990234375 + }, + { + "x": 346.718994140625, + "y": 393.2090148925781 + }, + { + "x": 339.82000732421875, + "y": 398.1409912109375 + }, + { + "x": 332.7179870605469, + "y": 402.7749938964844 + }, + { + "x": 325.4259948730469, + "y": 407.10400390625 + }, + { + "x": 317.95599365234375, + "y": 411.1199951171875 + }, + { + "x": 310.3240051269531, + "y": 414.8160095214844 + }, + { + "x": 302.5409851074219, + "y": 418.1839904785156 + }, + { + "x": 294.62298583984375, + "y": 421.2200012207031 + }, + { + "x": 286.5830078125, + "y": 423.9179992675781 + }, + { + "x": 278.4360046386719, + "y": 426.2720031738281 + }, + { + "x": 270.1969909667969, + "y": 428.27899169921875 + }, + { + "x": 261.8789978027344, + "y": 429.93499755859375 + }, + { + "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": 192.1199951171875, + "y": 429.93499755859375 + }, + { + "x": 183.802001953125, + "y": 428.27899169921875 + }, + { + "x": 175.56300354003906, + "y": 426.2720031738281 + }, + { + "x": 167.41600036621094, + "y": 423.9179992675781 + }, + { + "x": 159.37600708007812, + "y": 421.2200012207031 + }, + { + "x": 151.45799255371094, + "y": 418.1839904785156 + }, + { + "x": 143.6750030517578, + "y": 414.8160095214844 + }, + { + "x": 136.04299926757812, + "y": 411.1199951171875 + }, + { + "x": 128.572998046875, + "y": 407.10400390625 + }, + { + "x": 121.28099822998047, + "y": 402.7749938964844 + }, + { + "x": 114.17900085449219, + "y": 398.1409912109375 + }, + { + "x": 107.27999877929688, + "y": 393.2090148925781 + }, + { + "x": 100.59600067138672, + "y": 387.989990234375 + }, + { + "x": 94.13899993896484, + "y": 382.49200439453125 + }, + { + "x": 87.9209976196289, + "y": 376.72601318359375 + }, + { + "x": 81.9530029296875, + "y": 370.70098876953125 + }, + { + "x": 76.24600219726562, + "y": 364.4280090332031 + }, + { + "x": 70.80999755859375, + "y": 357.91900634765625 + }, + { + "x": 65.65499877929688, + "y": 351.1860046386719 + }, + { + "x": 60.790000915527344, + "y": 344.239990234375 + }, + { + "x": 56.2239990234375, + "y": 337.093994140625 + }, + { + "x": 51.9640007019043, + "y": 329.760986328125 + }, + { + "x": 48.02000045776367, + "y": 322.25299072265625 + }, + { + "x": 44.39699935913086, + "y": 314.58599853515625 + }, + { + "x": 41.10300064086914, + "y": 306.7720031738281 + }, + { + "x": 38.141998291015625, + "y": 298.82501220703125 + }, + { + "x": 35.52199935913086, + "y": 290.7590026855469 + }, + { + "x": 33.244998931884766, + "y": 282.5899963378906 + }, + { + "x": 31.316999435424805, + "y": 274.3320007324219 + }, + { + "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": 700.2050170898438, + "y": 34.76300048828125 + }, + { + "x": 711.9099731445312, + "y": 36.68299865722656 + }, + { + "x": 723.4810180664062, + "y": 39.292999267578125 + }, + { + "x": 734.8770141601562, + "y": 42.58399963378906 + }, + { + "x": 746.0570068359375, + "y": 46.54600143432617 + }, + { + "x": 756.9829711914062, + "y": 51.16299819946289 + }, + { + "x": 767.6170043945312, + "y": 56.41899871826172 + }, + { + "x": 777.9199829101562, + "y": 62.297000885009766 + }, + { + "x": 787.8560180664062, + "y": 68.7760009765625 + }, + { + "x": 797.3909912109375, + "y": 75.83200073242188 + }, + { + "x": 806.489990234375, + "y": 83.44000244140625 + }, + { + "x": 815.1229858398438, + "y": 91.57499694824219 + }, + { + "x": 823.2579956054688, + "y": 100.20800018310547 + }, + { + "x": 830.8670043945312, + "y": 109.30699920654297 + }, + { + "x": 837.9240112304688, + "y": 118.84200286865234 + }, + { + "x": 844.4030151367188, + "y": 128.7779998779297 + }, + { + "x": 850.281005859375, + "y": 139.0800018310547 + }, + { + "x": 855.5380249023438, + "y": 149.71299743652344 + }, + { + "x": 860.156005859375, + "y": 160.63900756835938 + }, + { + "x": 864.1170043945312, + "y": 171.82000732421875 + }, + { + "x": 867.4089965820312, + "y": 183.21499633789062 + }, + { + "x": 870.02001953125, + "y": 194.78599548339844 + }, + { + "x": 871.9400024414062, + "y": 206.49200439453125 + }, + { + "x": 873.1630249023438, + "y": 218.2899932861328 + }, + { + "x": 873.6840209960938, + "y": 230.13999938964844 + }, + { + "x": 873.5020141601562, + "y": 242 + }, + { + "x": 872.6170043945312, + "y": 253.82899475097656 + }, + { + "x": 871.031982421875, + "y": 265.5840148925781 + }, + { + "x": 868.7540283203125, + "y": 277.2250061035156 + }, + { + "x": 865.7890014648438, + "y": 288.7099914550781 + }, + { + "x": 862.1480102539062, + "y": 300 + } + ], + "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": 823.072998046875, + "y": 366 + }, + { + "x": 815.364013671875, + "y": 374.1820068359375 + }, + { + "x": 807.2069702148438, + "y": 381.91900634765625 + }, + { + "x": 798.6279907226562, + "y": 389.1860046386719 + }, + { + "x": 789.655029296875, + "y": 395.9580078125 + }, + { + "x": 780.3150024414062, + "y": 402.21600341796875 + }, + { + "x": 770.6380004882812, + "y": 407.9389953613281 + }, + { + "x": 760.655029296875, + "y": 413.1099853515625 + }, + { + "x": 750.39697265625, + "y": 417.71099853515625 + }, + { + "x": 739.89697265625, + "y": 421.7279968261719 + }, + { + "x": 729.18701171875, + "y": 425.1499938964844 + }, + { + "x": 718.302978515625, + "y": 427.9639892578125 + }, + { + "x": 707.2769775390625, + "y": 430.1619873046875 + }, + { + "x": 696.14501953125, + "y": 431.73699951171875 + }, + { + "x": 684.9429931640625, + "y": 432.6839904785156 + }, + { + "x": 673.7050170898438, + "y": 433 + }, + { + "x": 662.4660034179688, + "y": 432.6839904785156 + }, + { + "x": 651.2639770507812, + "y": 431.73699951171875 + }, + { + "x": 640.1320190429688, + "y": 430.1619873046875 + }, + { + "x": 629.1069946289062, + "y": 427.9639892578125 + }, + { + "x": 618.2219848632812, + "y": 425.1499938964844 + }, + { + "x": 607.5130004882812, + "y": 421.7279968261719 + }, + { + "x": 597.0120239257812, + "y": 417.71099853515625 + }, + { + "x": 586.7540283203125, + "y": 413.1099853515625 + }, + { + "x": 576.77099609375, + "y": 407.9389953613281 + }, + { + "x": 567.094970703125, + "y": 402.21600341796875 + }, + { + "x": 557.7550048828125, + "y": 395.9580078125 + }, + { + "x": 548.781005859375, + "y": 389.1860046386719 + }, + { + "x": 540.2020263671875, + "y": 381.91900634765625 + }, + { + "x": 532.0449829101562, + "y": 374.1820068359375 + }, + { + "x": 524.3359985351562, + "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": 947, + "y": 34.76300048828125 + }, + { + "x": 965.8519897460938, + "y": 38.20899963378906 + }, + { + "x": 984.2880249023438, + "y": 43.44499969482422 + }, + { + "x": 1002.1380004882812, + "y": 50.41999816894531 + }, + { + "x": 1019.2379760742188, + "y": 59.071998596191406 + }, + { + "x": 1035.4320068359375, + "y": 69.3219985961914 + }, + { + "x": 1050.571044921875, + "y": 81.0739974975586 + }, + { + "x": 1064.5150146484375, + "y": 94.22100067138672 + }, + { + "x": 1077.136962890625, + "y": 108.64199829101562 + }, + { + "x": 1088.3199462890625, + "y": 124.20500183105469 + }, + { + "x": 1097.9630126953125, + "y": 140.76800537109375 + }, + { + "x": 1105.9759521484375, + "y": 158.177001953125 + }, + { + "x": 1112.2860107421875, + "y": 176.2729949951172 + }, + { + "x": 1116.8349609375, + "y": 194.88999938964844 + }, + { + "x": 1119.5810546875, + "y": 213.85699462890625 + }, + { + "x": 1120.5, + "y": 233 + }, + { + "x": 1119.5810546875, + "y": 252.14199829101562 + }, + { + "x": 1116.8349609375, + "y": 271.1090087890625 + }, + { + "x": 1112.2860107421875, + "y": 289.72601318359375 + }, + { + "x": 1105.9759521484375, + "y": 307.8219909667969 + }, + { + "x": 1097.9630126953125, + "y": 325.2309875488281 + }, + { + "x": 1088.3199462890625, + "y": 341.79400634765625 + }, + { + "x": 1077.136962890625, + "y": 357.35699462890625 + }, + { + "x": 1064.5150146484375, + "y": 371.77801513671875 + }, + { + "x": 1050.571044921875, + "y": 384.92498779296875 + }, + { + "x": 1035.4320068359375, + "y": 396.677001953125 + }, + { + "x": 1019.2379760742188, + "y": 406.927001953125 + }, + { + "x": 1002.1380004882812, + "y": 415.5790100097656 + }, + { + "x": 984.2880249023438, + "y": 422.5539855957031 + }, + { + "x": 965.8519897460938, + "y": 427.7900085449219 + }, + { + "x": 947, + "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/isometric.exp.svg b/e2etests/testdata/txtar/cycle-diagram/tala/isometric.exp.svg new file mode 100644 index 0000000000..3898c33b80 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/tala/isometric.exp.svg @@ -0,0 +1 @@ +D2 isometric diagram \ No newline at end of file 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..acf45b9da1 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg @@ -0,0 +1,29 @@ +abcdabcab + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-self-loop/dagre/board.exp.json b/e2etests/testdata/txtar/cycle-self-loop/dagre/board.exp.json new file mode 100644 index 0000000000..dceeba481b --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/dagre/board.exp.json @@ -0,0 +1,735 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "x", + "type": "cycle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 227, + "height": 506, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": "x.a", + "type": "rectangle", + "pos": { + "x": 0, + "y": 40 + }, + "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": "x.b", + "type": "rectangle", + "pos": { + "x": 0, + "y": 440 + }, + "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": "y", + "type": "cycle", + "pos": { + "x": 287, + "y": 200 + }, + "width": 53, + "height": 106, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": "y.a", + "type": "rectangle", + "pos": { + "x": 287, + "y": 240 + }, + "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 + } + ], + "connections": [ + { + "id": "x.(a -> a)[0]", + "src": "x.a", + "srcArrow": "none", + "dst": "x.a", + "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": 26.5, + "y": 40 + }, + { + "x": 22.340999603271484, + "y": 39.5620002746582 + }, + { + "x": 18.364999771118164, + "y": 38.27000045776367 + }, + { + "x": 14.744000434875488, + "y": 36.18000030517578 + }, + { + "x": 11.63700008392334, + "y": 33.38199996948242 + }, + { + "x": 9.178999900817871, + "y": 30 + }, + { + "x": 7.478000164031982, + "y": 26.18000030517578 + }, + { + "x": 6.609000205993652, + "y": 22.09000015258789 + }, + { + "x": 6.609000205993652, + "y": 17.909000396728516 + }, + { + "x": 7.478000164031982, + "y": 13.819000244140625 + }, + { + "x": 9.178999900817871, + "y": 10 + }, + { + "x": 11.63700008392334, + "y": 6.617000102996826 + }, + { + "x": 14.744000434875488, + "y": 3.819000005722046 + }, + { + "x": 18.364999771118164, + "y": 1.7289999723434448 + }, + { + "x": 22.340999603271484, + "y": 0.43700000643730164 + }, + { + "x": 26.499000549316406, + "y": 0 + }, + { + "x": 30.658000946044922, + "y": 0.43700000643730164 + }, + { + "x": 34.63399887084961, + "y": 1.7289999723434448 + }, + { + "x": 38.255001068115234, + "y": 3.819000005722046 + }, + { + "x": 41.36199951171875, + "y": 6.617000102996826 + }, + { + "x": 43.81999969482422, + "y": 10 + }, + { + "x": 45.520999908447266, + "y": 13.819000244140625 + }, + { + "x": 46.38999938964844, + "y": 17.909000396728516 + }, + { + "x": 46.38999938964844, + "y": 22.09000015258789 + }, + { + "x": 45.520999908447266, + "y": 26.18000030517578 + }, + { + "x": 43.81999969482422, + "y": 30 + }, + { + "x": 41.36199951171875, + "y": 33.38199996948242 + }, + { + "x": 38.255001068115234, + "y": 36.18000030517578 + }, + { + "x": 34.63399887084961, + "y": 38.27000045776367 + }, + { + "x": 30.658000946044922, + "y": 39.5620002746582 + }, + { + "x": 26.5, + "y": 40 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "x.(a -> b)[0]", + "src": "x.a", + "srcArrow": "none", + "dst": "x.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": 53, + "y": 74.76300048828125 + }, + { + "x": 71.85199737548828, + "y": 78.20899963378906 + }, + { + "x": 90.28800201416016, + "y": 83.44499969482422 + }, + { + "x": 108.13800048828125, + "y": 90.41999816894531 + }, + { + "x": 125.23799896240234, + "y": 99.0719985961914 + }, + { + "x": 141.4320068359375, + "y": 109.3219985961914 + }, + { + "x": 156.5709991455078, + "y": 121.0739974975586 + }, + { + "x": 170.51499938964844, + "y": 134.2209930419922 + }, + { + "x": 183.13699340820312, + "y": 148.64199829101562 + }, + { + "x": 194.32000732421875, + "y": 164.2050018310547 + }, + { + "x": 203.96299743652344, + "y": 180.76800537109375 + }, + { + "x": 211.9759979248047, + "y": 198.177001953125 + }, + { + "x": 218.28599548339844, + "y": 216.2729949951172 + }, + { + "x": 222.8350067138672, + "y": 234.88999938964844 + }, + { + "x": 225.58099365234375, + "y": 253.85699462890625 + }, + { + "x": 226.5, + "y": 273 + }, + { + "x": 225.58099365234375, + "y": 292.1419982910156 + }, + { + "x": 222.8350067138672, + "y": 311.1090087890625 + }, + { + "x": 218.28599548339844, + "y": 329.72601318359375 + }, + { + "x": 211.9759979248047, + "y": 347.8219909667969 + }, + { + "x": 203.96299743652344, + "y": 365.2309875488281 + }, + { + "x": 194.32000732421875, + "y": 381.79400634765625 + }, + { + "x": 183.13699340820312, + "y": 397.35699462890625 + }, + { + "x": 170.51499938964844, + "y": 411.77801513671875 + }, + { + "x": 156.5709991455078, + "y": 424.92498779296875 + }, + { + "x": 141.4320068359375, + "y": 436.677001953125 + }, + { + "x": 125.23799896240234, + "y": 446.927001953125 + }, + { + "x": 108.13800048828125, + "y": 455.5790100097656 + }, + { + "x": 90.28800201416016, + "y": 462.5539855957031 + }, + { + "x": 71.85199737548828, + "y": 467.7900085449219 + }, + { + "x": 53, + "y": 471.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "y.(a -> a)[0]", + "src": "y.a", + "srcArrow": "none", + "dst": "y.a", + "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": 313.5, + "y": 240 + }, + { + "x": 309.34100341796875, + "y": 239.56199645996094 + }, + { + "x": 305.364990234375, + "y": 238.27000427246094 + }, + { + "x": 301.7439880371094, + "y": 236.17999267578125 + }, + { + "x": 298.6369934082031, + "y": 233.3820037841797 + }, + { + "x": 296.1789855957031, + "y": 230 + }, + { + "x": 294.4779968261719, + "y": 226.17999267578125 + }, + { + "x": 293.6090087890625, + "y": 222.08999633789062 + }, + { + "x": 293.6090087890625, + "y": 217.90899658203125 + }, + { + "x": 294.4779968261719, + "y": 213.81900024414062 + }, + { + "x": 296.1789855957031, + "y": 210 + }, + { + "x": 298.6369934082031, + "y": 206.61700439453125 + }, + { + "x": 301.7439880371094, + "y": 203.81900024414062 + }, + { + "x": 305.364990234375, + "y": 201.72900390625 + }, + { + "x": 309.34100341796875, + "y": 200.43699645996094 + }, + { + "x": 313.5, + "y": 200 + }, + { + "x": 317.6579895019531, + "y": 200.43699645996094 + }, + { + "x": 321.6340026855469, + "y": 201.72900390625 + }, + { + "x": 325.2550048828125, + "y": 203.81900024414062 + }, + { + "x": 328.36199951171875, + "y": 206.61700439453125 + }, + { + "x": 330.82000732421875, + "y": 210 + }, + { + "x": 332.52099609375, + "y": 213.81900024414062 + }, + { + "x": 333.3900146484375, + "y": 217.90899658203125 + }, + { + "x": 333.3900146484375, + "y": 222.08999633789062 + }, + { + "x": 332.52099609375, + "y": 226.17999267578125 + }, + { + "x": 330.82000732421875, + "y": 230 + }, + { + "x": 328.36199951171875, + "y": 233.3820037841797 + }, + { + "x": 325.2550048828125, + "y": 236.17999267578125 + }, + { + "x": 321.6340026855469, + "y": 238.27000427246094 + }, + { + "x": 317.6579895019531, + "y": 239.56199645996094 + }, + { + "x": 313.5, + "y": 240 + } + ], + "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-self-loop/dagre/isometric.exp.svg b/e2etests/testdata/txtar/cycle-self-loop/dagre/isometric.exp.svg new file mode 100644 index 0000000000..823eae6d5b --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/dagre/isometric.exp.svg @@ -0,0 +1 @@ +D2 isometric diagram \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-self-loop/dagre/sketch.exp.svg b/e2etests/testdata/txtar/cycle-self-loop/dagre/sketch.exp.svg new file mode 100644 index 0000000000..363bcaadc7 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/dagre/sketch.exp.svg @@ -0,0 +1,29 @@ +aba + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-self-loop/elk/board.exp.json b/e2etests/testdata/txtar/cycle-self-loop/elk/board.exp.json new file mode 100644 index 0000000000..4c52a7c163 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/elk/board.exp.json @@ -0,0 +1,735 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "x", + "type": "cycle", + "pos": { + "x": 12, + "y": 12 + }, + "width": 227, + "height": 506, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": "x.a", + "type": "rectangle", + "pos": { + "x": 12, + "y": 52 + }, + "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": "x.b", + "type": "rectangle", + "pos": { + "x": 12, + "y": 452 + }, + "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": "y", + "type": "cycle", + "pos": { + "x": 259, + "y": 212 + }, + "width": 53, + "height": 106, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": "y.a", + "type": "rectangle", + "pos": { + "x": 259, + "y": 252 + }, + "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 + } + ], + "connections": [ + { + "id": "x.(a -> a)[0]", + "src": "x.a", + "srcArrow": "none", + "dst": "x.a", + "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": 38.5, + "y": 52 + }, + { + "x": 34.340999603271484, + "y": 51.5620002746582 + }, + { + "x": 30.364999771118164, + "y": 50.27000045776367 + }, + { + "x": 26.743999481201172, + "y": 48.18000030517578 + }, + { + "x": 23.636999130249023, + "y": 45.38199996948242 + }, + { + "x": 21.179000854492188, + "y": 42 + }, + { + "x": 19.47800064086914, + "y": 38.18000030517578 + }, + { + "x": 18.608999252319336, + "y": 34.09000015258789 + }, + { + "x": 18.608999252319336, + "y": 29.909000396728516 + }, + { + "x": 19.47800064086914, + "y": 25.819000244140625 + }, + { + "x": 21.179000854492188, + "y": 22 + }, + { + "x": 23.636999130249023, + "y": 18.617000579833984 + }, + { + "x": 26.743999481201172, + "y": 15.819000244140625 + }, + { + "x": 30.364999771118164, + "y": 13.729000091552734 + }, + { + "x": 34.340999603271484, + "y": 12.437000274658203 + }, + { + "x": 38.5, + "y": 12 + }, + { + "x": 42.65800094604492, + "y": 12.437000274658203 + }, + { + "x": 46.63399887084961, + "y": 13.729000091552734 + }, + { + "x": 50.255001068115234, + "y": 15.819000244140625 + }, + { + "x": 53.36199951171875, + "y": 18.617000579833984 + }, + { + "x": 55.81999969482422, + "y": 22 + }, + { + "x": 57.520999908447266, + "y": 25.819000244140625 + }, + { + "x": 58.38999938964844, + "y": 29.909000396728516 + }, + { + "x": 58.38999938964844, + "y": 34.09000015258789 + }, + { + "x": 57.520999908447266, + "y": 38.18000030517578 + }, + { + "x": 55.81999969482422, + "y": 42 + }, + { + "x": 53.36199951171875, + "y": 45.38199996948242 + }, + { + "x": 50.255001068115234, + "y": 48.18000030517578 + }, + { + "x": 46.63399887084961, + "y": 50.27000045776367 + }, + { + "x": 42.65800094604492, + "y": 51.5620002746582 + }, + { + "x": 38.5, + "y": 52 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "x.(a -> b)[0]", + "src": "x.a", + "srcArrow": "none", + "dst": "x.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": 65, + "y": 86.76300048828125 + }, + { + "x": 83.85199737548828, + "y": 90.20899963378906 + }, + { + "x": 102.28800201416016, + "y": 95.44499969482422 + }, + { + "x": 120.13800048828125, + "y": 102.41999816894531 + }, + { + "x": 137.23800659179688, + "y": 111.0719985961914 + }, + { + "x": 153.4320068359375, + "y": 121.3219985961914 + }, + { + "x": 168.5709991455078, + "y": 133.07400512695312 + }, + { + "x": 182.51499938964844, + "y": 146.2209930419922 + }, + { + "x": 195.13699340820312, + "y": 160.64199829101562 + }, + { + "x": 206.32000732421875, + "y": 176.2050018310547 + }, + { + "x": 215.96299743652344, + "y": 192.76800537109375 + }, + { + "x": 223.9759979248047, + "y": 210.177001953125 + }, + { + "x": 230.28599548339844, + "y": 228.2729949951172 + }, + { + "x": 234.8350067138672, + "y": 246.88999938964844 + }, + { + "x": 237.58099365234375, + "y": 265.85699462890625 + }, + { + "x": 238.5, + "y": 285 + }, + { + "x": 237.58099365234375, + "y": 304.1419982910156 + }, + { + "x": 234.8350067138672, + "y": 323.1090087890625 + }, + { + "x": 230.28599548339844, + "y": 341.72601318359375 + }, + { + "x": 223.9759979248047, + "y": 359.8219909667969 + }, + { + "x": 215.96299743652344, + "y": 377.2309875488281 + }, + { + "x": 206.32000732421875, + "y": 393.79400634765625 + }, + { + "x": 195.13699340820312, + "y": 409.35699462890625 + }, + { + "x": 182.51499938964844, + "y": 423.77801513671875 + }, + { + "x": 168.5709991455078, + "y": 436.92498779296875 + }, + { + "x": 153.4320068359375, + "y": 448.677001953125 + }, + { + "x": 137.23800659179688, + "y": 458.927001953125 + }, + { + "x": 120.13800048828125, + "y": 467.5790100097656 + }, + { + "x": 102.28800201416016, + "y": 474.5539855957031 + }, + { + "x": 83.85199737548828, + "y": 479.7900085449219 + }, + { + "x": 65, + "y": 483.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "y.(a -> a)[0]", + "src": "y.a", + "srcArrow": "none", + "dst": "y.a", + "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": 285.5, + "y": 252 + }, + { + "x": 281.34100341796875, + "y": 251.56199645996094 + }, + { + "x": 277.364990234375, + "y": 250.27000427246094 + }, + { + "x": 273.7439880371094, + "y": 248.17999267578125 + }, + { + "x": 270.6369934082031, + "y": 245.3820037841797 + }, + { + "x": 268.1789855957031, + "y": 242 + }, + { + "x": 266.4779968261719, + "y": 238.17999267578125 + }, + { + "x": 265.6090087890625, + "y": 234.08999633789062 + }, + { + "x": 265.6090087890625, + "y": 229.90899658203125 + }, + { + "x": 266.4779968261719, + "y": 225.81900024414062 + }, + { + "x": 268.1789855957031, + "y": 222 + }, + { + "x": 270.6369934082031, + "y": 218.61700439453125 + }, + { + "x": 273.7439880371094, + "y": 215.81900024414062 + }, + { + "x": 277.364990234375, + "y": 213.72900390625 + }, + { + "x": 281.34100341796875, + "y": 212.43699645996094 + }, + { + "x": 285.5, + "y": 212 + }, + { + "x": 289.6579895019531, + "y": 212.43699645996094 + }, + { + "x": 293.6340026855469, + "y": 213.72900390625 + }, + { + "x": 297.2550048828125, + "y": 215.81900024414062 + }, + { + "x": 300.36199951171875, + "y": 218.61700439453125 + }, + { + "x": 302.82000732421875, + "y": 222 + }, + { + "x": 304.52099609375, + "y": 225.81900024414062 + }, + { + "x": 305.3900146484375, + "y": 229.90899658203125 + }, + { + "x": 305.3900146484375, + "y": 234.08999633789062 + }, + { + "x": 304.52099609375, + "y": 238.17999267578125 + }, + { + "x": 302.82000732421875, + "y": 242 + }, + { + "x": 300.36199951171875, + "y": 245.3820037841797 + }, + { + "x": 297.2550048828125, + "y": 248.17999267578125 + }, + { + "x": 293.6340026855469, + "y": 250.27000427246094 + }, + { + "x": 289.6579895019531, + "y": 251.56199645996094 + }, + { + "x": 285.5, + "y": 252 + } + ], + "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-self-loop/elk/isometric.exp.svg b/e2etests/testdata/txtar/cycle-self-loop/elk/isometric.exp.svg new file mode 100644 index 0000000000..7fb5db723c --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/elk/isometric.exp.svg @@ -0,0 +1 @@ +D2 isometric diagram \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-self-loop/elk/sketch.exp.svg b/e2etests/testdata/txtar/cycle-self-loop/elk/sketch.exp.svg new file mode 100644 index 0000000000..86dc32353c --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/elk/sketch.exp.svg @@ -0,0 +1,29 @@ +aba + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-self-loop/tala/board.exp.json b/e2etests/testdata/txtar/cycle-self-loop/tala/board.exp.json new file mode 100644 index 0000000000..7455468e4d --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/tala/board.exp.json @@ -0,0 +1,735 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "x", + "type": "cycle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 227, + "height": 506, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": "x.a", + "type": "rectangle", + "pos": { + "x": 0, + "y": 40 + }, + "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": "x.b", + "type": "rectangle", + "pos": { + "x": 0, + "y": 440 + }, + "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": "y", + "type": "cycle", + "pos": { + "x": 247, + "y": 0 + }, + "width": 53, + "height": 106, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "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": "y.a", + "type": "rectangle", + "pos": { + "x": 247, + "y": 40 + }, + "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 + } + ], + "connections": [ + { + "id": "x.(a -> a)[0]", + "src": "x.a", + "srcArrow": "none", + "dst": "x.a", + "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": 26.5, + "y": 40 + }, + { + "x": 22.340999603271484, + "y": 39.5620002746582 + }, + { + "x": 18.364999771118164, + "y": 38.27000045776367 + }, + { + "x": 14.744000434875488, + "y": 36.18000030517578 + }, + { + "x": 11.63700008392334, + "y": 33.38199996948242 + }, + { + "x": 9.178999900817871, + "y": 30 + }, + { + "x": 7.478000164031982, + "y": 26.18000030517578 + }, + { + "x": 6.609000205993652, + "y": 22.09000015258789 + }, + { + "x": 6.609000205993652, + "y": 17.909000396728516 + }, + { + "x": 7.478000164031982, + "y": 13.819000244140625 + }, + { + "x": 9.178999900817871, + "y": 10 + }, + { + "x": 11.63700008392334, + "y": 6.617000102996826 + }, + { + "x": 14.744000434875488, + "y": 3.819000005722046 + }, + { + "x": 18.364999771118164, + "y": 1.7289999723434448 + }, + { + "x": 22.340999603271484, + "y": 0.43700000643730164 + }, + { + "x": 26.499000549316406, + "y": 0 + }, + { + "x": 30.658000946044922, + "y": 0.43700000643730164 + }, + { + "x": 34.63399887084961, + "y": 1.7289999723434448 + }, + { + "x": 38.255001068115234, + "y": 3.819000005722046 + }, + { + "x": 41.36199951171875, + "y": 6.617000102996826 + }, + { + "x": 43.81999969482422, + "y": 10 + }, + { + "x": 45.520999908447266, + "y": 13.819000244140625 + }, + { + "x": 46.38999938964844, + "y": 17.909000396728516 + }, + { + "x": 46.38999938964844, + "y": 22.09000015258789 + }, + { + "x": 45.520999908447266, + "y": 26.18000030517578 + }, + { + "x": 43.81999969482422, + "y": 30 + }, + { + "x": 41.36199951171875, + "y": 33.38199996948242 + }, + { + "x": 38.255001068115234, + "y": 36.18000030517578 + }, + { + "x": 34.63399887084961, + "y": 38.27000045776367 + }, + { + "x": 30.658000946044922, + "y": 39.5620002746582 + }, + { + "x": 26.5, + "y": 40 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "x.(a -> b)[0]", + "src": "x.a", + "srcArrow": "none", + "dst": "x.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": 53, + "y": 74.76300048828125 + }, + { + "x": 71.85199737548828, + "y": 78.20899963378906 + }, + { + "x": 90.28800201416016, + "y": 83.44499969482422 + }, + { + "x": 108.13800048828125, + "y": 90.41999816894531 + }, + { + "x": 125.23799896240234, + "y": 99.0719985961914 + }, + { + "x": 141.4320068359375, + "y": 109.3219985961914 + }, + { + "x": 156.5709991455078, + "y": 121.0739974975586 + }, + { + "x": 170.51499938964844, + "y": 134.2209930419922 + }, + { + "x": 183.13699340820312, + "y": 148.64199829101562 + }, + { + "x": 194.32000732421875, + "y": 164.2050018310547 + }, + { + "x": 203.96299743652344, + "y": 180.76800537109375 + }, + { + "x": 211.9759979248047, + "y": 198.177001953125 + }, + { + "x": 218.28599548339844, + "y": 216.2729949951172 + }, + { + "x": 222.8350067138672, + "y": 234.88999938964844 + }, + { + "x": 225.58099365234375, + "y": 253.85699462890625 + }, + { + "x": 226.5, + "y": 273 + }, + { + "x": 225.58099365234375, + "y": 292.1419982910156 + }, + { + "x": 222.8350067138672, + "y": 311.1090087890625 + }, + { + "x": 218.28599548339844, + "y": 329.72601318359375 + }, + { + "x": 211.9759979248047, + "y": 347.8219909667969 + }, + { + "x": 203.96299743652344, + "y": 365.2309875488281 + }, + { + "x": 194.32000732421875, + "y": 381.79400634765625 + }, + { + "x": 183.13699340820312, + "y": 397.35699462890625 + }, + { + "x": 170.51499938964844, + "y": 411.77801513671875 + }, + { + "x": 156.5709991455078, + "y": 424.92498779296875 + }, + { + "x": 141.4320068359375, + "y": 436.677001953125 + }, + { + "x": 125.23799896240234, + "y": 446.927001953125 + }, + { + "x": 108.13800048828125, + "y": 455.5790100097656 + }, + { + "x": 90.28800201416016, + "y": 462.5539855957031 + }, + { + "x": 71.85199737548828, + "y": 467.7900085449219 + }, + { + "x": 53, + "y": 471.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "y.(a -> a)[0]", + "src": "y.a", + "srcArrow": "none", + "dst": "y.a", + "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": 273.5, + "y": 40 + }, + { + "x": 269.34100341796875, + "y": 39.5620002746582 + }, + { + "x": 265.364990234375, + "y": 38.27000045776367 + }, + { + "x": 261.7439880371094, + "y": 36.18000030517578 + }, + { + "x": 258.6369934082031, + "y": 33.38199996948242 + }, + { + "x": 256.1789855957031, + "y": 30 + }, + { + "x": 254.47799682617188, + "y": 26.18000030517578 + }, + { + "x": 253.60899353027344, + "y": 22.09000015258789 + }, + { + "x": 253.60899353027344, + "y": 17.909000396728516 + }, + { + "x": 254.47799682617188, + "y": 13.819000244140625 + }, + { + "x": 256.1789855957031, + "y": 10 + }, + { + "x": 258.6369934082031, + "y": 6.617000102996826 + }, + { + "x": 261.7439880371094, + "y": 3.819000005722046 + }, + { + "x": 265.364990234375, + "y": 1.7289999723434448 + }, + { + "x": 269.34100341796875, + "y": 0.43700000643730164 + }, + { + "x": 273.5, + "y": 0 + }, + { + "x": 277.6579895019531, + "y": 0.43700000643730164 + }, + { + "x": 281.6340026855469, + "y": 1.7289999723434448 + }, + { + "x": 285.2550048828125, + "y": 3.819000005722046 + }, + { + "x": 288.36199951171875, + "y": 6.617000102996826 + }, + { + "x": 290.82000732421875, + "y": 10 + }, + { + "x": 292.52099609375, + "y": 13.819000244140625 + }, + { + "x": 293.3900146484375, + "y": 17.909000396728516 + }, + { + "x": 293.3900146484375, + "y": 22.09000015258789 + }, + { + "x": 292.52099609375, + "y": 26.18000030517578 + }, + { + "x": 290.82000732421875, + "y": 30 + }, + { + "x": 288.36199951171875, + "y": 33.38199996948242 + }, + { + "x": 285.2550048828125, + "y": 36.18000030517578 + }, + { + "x": 281.6340026855469, + "y": 38.27000045776367 + }, + { + "x": 277.6579895019531, + "y": 39.5620002746582 + }, + { + "x": 273.5, + "y": 40 + } + ], + "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-self-loop/tala/isometric.exp.svg b/e2etests/testdata/txtar/cycle-self-loop/tala/isometric.exp.svg new file mode 100644 index 0000000000..1f96c315cc --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/tala/isometric.exp.svg @@ -0,0 +1 @@ +D2 isometric diagram \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-self-loop/tala/sketch.exp.svg b/e2etests/testdata/txtar/cycle-self-loop/tala/sketch.exp.svg new file mode 100644 index 0000000000..a31052ca40 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-self-loop/tala/sketch.exp.svg @@ -0,0 +1,29 @@ +aba + + + \ No newline at end of file diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt index 7585d91b3e..0083b67cfa 100644 --- a/e2etests/txtar.txt +++ b/e2etests/txtar.txt @@ -1773,3 +1773,27 @@ style: {fill-pattern: dots; fill:"radial-gradient(#fbfbf8, #e3e3f0)"; stroke: "# a->b +-- cycle-self-loop -- +x: "" { + shape: cycle + a -> a + a -> b +} +y: "" { + shape: cycle + a -> a +} + +-- cycle-diagram -- +1: "" { + shape: cycle + a -> b -> c -> d +} +2: "" { + shape: cycle + a -> b -> c +} +3: "" { + shape: cycle + a -> b +} diff --git a/lib/geo/point.go b/lib/geo/point.go index ab8e034a02..4fb1082396 100644 --- a/lib/geo/point.go +++ b/lib/geo/point.go @@ -324,3 +324,12 @@ func RemovePoints(points Points, toRemove []bool) Points { } return without } + +func (v Vector) Normalize() Vector { + length := v.Length() + if length == 0 { + // avoid dividing by 0 + return Vector{0, 0} + } + return Vector{v[0] / length, v[1] / length} +} diff --git a/lib/geo/segment.go b/lib/geo/segment.go index ea29df98c9..c902ea890c 100644 --- a/lib/geo/segment.go +++ b/lib/geo/segment.go @@ -126,3 +126,41 @@ func (segment Segment) Length() float64 { func (segment Segment) ToVector() Vector { return NewVector(segment.End.X-segment.Start.X, segment.End.Y-segment.Start.Y) } + +// IntersectCircle returns intersection points of this segment with a circle +// centered at the given point with the given radius. Returned points lie on +// the segment (parameter t in [0,1]). A tangent contact yields a single +// point; the list is empty when the segment misses the circle, or only +// touches it outside its endpoints. +func (s Segment) IntersectCircle(center *Point, radius float64) []*Point { + dx := s.End.X - s.Start.X + dy := s.End.Y - s.Start.Y + fx := s.Start.X - center.X + fy := s.Start.Y - center.Y + a := dx*dx + dy*dy + if a == 0 { + return nil + } + b := 2 * (fx*dx + fy*dy) + c := fx*fx + fy*fy - radius*radius + disc := b*b - 4*a*c + if disc < 0 { + return nil + } + sqrtDisc := math.Sqrt(disc) + roots := []float64{(-b - sqrtDisc) / (2 * a)} + if disc > 0 { + roots = append(roots, (-b+sqrtDisc)/(2*a)) + } + var pts []*Point + for _, t := range roots { + if t < 0 { + continue + } + if t > 1 { + continue + } + pts = append(pts, NewPoint(s.Start.X+t*dx, s.Start.Y+t*dy)) + } + return pts +} diff --git a/lib/geo/segment_test.go b/lib/geo/segment_test.go index ce2836a7e5..afd8399a5a 100644 --- a/lib/geo/segment_test.go +++ b/lib/geo/segment_test.go @@ -31,3 +31,73 @@ func TestSegmentIntersections(t *testing.T) { intersections = s1.Intersections(*s5) assert.Equal(t, len(intersections), 0) } + +func TestSegmentIntersectCircle(t *testing.T) { + origin := NewPoint(0, 0) + + // segment passing through the origin → 2 intersections at (-r, 0) and (r, 0) + s := NewSegment(NewPoint(-10, 0), NewPoint(10, 0)) + pts := s.IntersectCircle(origin, 5) + assert.Equal(t, 2, len(pts)) + assert.InDelta(t, -5, pts[0].X, 1e-9) + assert.InDelta(t, 0, pts[0].Y, 1e-9) + assert.InDelta(t, 5, pts[1].X, 1e-9) + assert.InDelta(t, 0, pts[1].Y, 1e-9) + + // segment crossing the circle once with one endpoint inside + s = NewSegment(NewPoint(0, 0), NewPoint(10, 0)) + pts = s.IntersectCircle(origin, 5) + assert.Equal(t, 1, len(pts)) + assert.InDelta(t, 5, pts[0].X, 1e-9) + assert.InDelta(t, 0, pts[0].Y, 1e-9) + + // segment entirely outside the circle, no crossing + s = NewSegment(NewPoint(10, 10), NewPoint(20, 20)) + pts = s.IntersectCircle(origin, 5) + assert.Equal(t, 0, len(pts)) + + // segment entirely inside the circle, no crossing + s = NewSegment(NewPoint(-1, 0), NewPoint(1, 0)) + pts = s.IntersectCircle(origin, 5) + assert.Equal(t, 0, len(pts)) + + // segment endpoint on the circle + s = NewSegment(NewPoint(0, 0), NewPoint(5, 0)) + pts = s.IntersectCircle(origin, 5) + assert.Equal(t, 1, len(pts)) + assert.InDelta(t, 5, pts[0].X, 1e-9) + assert.InDelta(t, 0, pts[0].Y, 1e-9) + + // vertical segment chord intersecting the circle at two points + s = NewSegment(NewPoint(3, -10), NewPoint(3, 10)) + pts = s.IntersectCircle(origin, 5) + assert.Equal(t, 2, len(pts)) + assert.InDelta(t, 3, pts[0].X, 1e-9) + assert.InDelta(t, -4, pts[0].Y, 1e-9) + assert.InDelta(t, 3, pts[1].X, 1e-9) + assert.InDelta(t, 4, pts[1].Y, 1e-9) + + // circle centered off origin + c := NewPoint(10, 10) + s = NewSegment(NewPoint(0, 10), NewPoint(20, 10)) + pts = s.IntersectCircle(c, 5) + assert.Equal(t, 2, len(pts)) + assert.InDelta(t, 5, pts[0].X, 1e-9) + assert.InDelta(t, 10, pts[0].Y, 1e-9) + assert.InDelta(t, 15, pts[1].X, 1e-9) + assert.InDelta(t, 10, pts[1].Y, 1e-9) + + // tangent contact: the segment grazes the circle at a single point and + // must not be reported twice (regression test for the duplicated-root + // case when the discriminant is zero). + s = NewSegment(NewPoint(-10, 5), NewPoint(10, 5)) + pts = s.IntersectCircle(origin, 5) + assert.Equal(t, 1, len(pts)) + assert.InDelta(t, 0, pts[0].X, 1e-9) + assert.InDelta(t, 5, pts[0].Y, 1e-9) + + // degenerate zero-length segment returns nil + s = NewSegment(NewPoint(7, 7), NewPoint(7, 7)) + pts = s.IntersectCircle(origin, 5) + assert.Nil(t, pts) +}