From 3d2a4dd8fd87461c8714feaad831af7b68ca82cb Mon Sep 17 00:00:00 2001 From: Furox-Art <177975472+Furox-Art@users.noreply.github.com> Date: Mon, 14 Sep 2026 02:12:16 +0300 Subject: [PATCH 1/2] feat: add cycle shape shape: cycle arranges its children with their centers on a circle and routes edges between them along that circle as exact circular arcs (cubic Bezier chains using the canonical arc control offset), with endpoints trimmed to each child's visible border so arrowheads touch rectangles, circles, polygons alike. - children keep their internal layout: the core layout runs first and containers are moved onto the ring together with their descendants, translating internal and cross-child edge routes along - root-level edges take the shorter way around the ring; on a two-node cycle opposite edges take opposite semicircles - the container renders like a container (styles apply), in both d2svg and d2scenebuild - e2e txtar fixture cycle-diagram with dagre, elk and tala goldens --- ci/release/changelogs/next.md | 3 + d2graph/cyclediagram.go | 7 + d2graph/d2graph.go | 2 +- d2layouts/d2cycle/layout.go | 493 ++++++ d2layouts/d2cycle/layout_test.go | 281 +++ d2layouts/d2layouts.go | 10 + d2renderers/d2scenebuild/shape_effects.go | 2 +- d2renderers/d2scenebuild/sketch_shapes.go | 6 +- d2renderers/d2svg/d2svg.go | 2 +- d2target/d2target.go | 3 + .../txtar/cycle-diagram/dagre/board.exp.json | 1567 +++++++++++++++++ .../txtar/cycle-diagram/dagre/sketch.exp.svg | 40 + .../txtar/cycle-diagram/elk/board.exp.json | 1565 ++++++++++++++++ .../txtar/cycle-diagram/elk/sketch.exp.svg | 40 + .../txtar/cycle-diagram/tala/board.exp.json | 1525 ++++++++++++++++ .../txtar/cycle-diagram/tala/sketch.exp.svg | 40 + e2etests/txtar.txt | 32 + 17 files changed, 5612 insertions(+), 6 deletions(-) create mode 100644 d2graph/cyclediagram.go create mode 100644 d2layouts/d2cycle/layout.go create mode 100644 d2layouts/d2cycle/layout_test.go create mode 100644 e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json create mode 100644 e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg create mode 100644 e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json create mode 100644 e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg create mode 100644 e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json create mode 100644 e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg diff --git a/ci/release/changelogs/next.md b/ci/release/changelogs/next.md index ffe01dbc1e..25a21db7ea 100644 --- a/ci/release/changelogs/next.md +++ b/ci/release/changelogs/next.md @@ -1,5 +1,8 @@ #### Features 🚀 +- layouts: add `shape: cycle` which arranges its children on a circle and routes + their edges along it as circular arcs trimmed to the children's borders + #### Improvements 🧹 - d2svg: diff --git a/d2graph/cyclediagram.go b/d2graph/cyclediagram.go new file mode 100644 index 0000000000..65d7903935 --- /dev/null +++ b/d2graph/cyclediagram.go @@ -0,0 +1,7 @@ +package d2graph + +import "github.com/d2lang/d2/d2target" + +func (obj *Object) IsCycleDiagram() bool { + return obj != nil && obj.Shape.Value == d2target.ShapeCycleDiagram +} diff --git a/d2graph/d2graph.go b/d2graph/d2graph.go index 92f5c8948a..6c96306c5f 100644 --- a/d2graph/d2graph.go +++ b/d2graph/d2graph.go @@ -543,7 +543,7 @@ func (obj *Object) GetFill() string { return color.N7 } - if shape == "" || strings.EqualFold(shape, d2target.ShapeSquare) || strings.EqualFold(shape, d2target.ShapeCircle) || strings.EqualFold(shape, d2target.ShapeOval) || strings.EqualFold(shape, d2target.ShapeRectangle) || strings.EqualFold(shape, d2target.ShapeHierarchy) { + if shape == "" || strings.EqualFold(shape, d2target.ShapeSquare) || strings.EqualFold(shape, d2target.ShapeCircle) || strings.EqualFold(shape, d2target.ShapeOval) || strings.EqualFold(shape, d2target.ShapeRectangle) || strings.EqualFold(shape, d2target.ShapeCycleDiagram) || strings.EqualFold(shape, d2target.ShapeHierarchy) { if level == 1 { if !obj.IsContainer() { return color.B6 diff --git a/d2layouts/d2cycle/layout.go b/d2layouts/d2cycle/layout.go new file mode 100644 index 0000000000..e20507fa29 --- /dev/null +++ b/d2layouts/d2cycle/layout.go @@ -0,0 +1,493 @@ +// Package d2cycle implements the layout for objects of shape cycle. +// +// Children of a cycle object are arranged with their centers on a circle and +// edges between them are routed along that circle. Arcs are exact circular +// curves (chains of cubics using the canonical k = 4/3·tan(Δθ/4) control +// offset) whose endpoints are trimmed to each endpoint shape's visible border. +package d2cycle + +import ( + "context" + "math" + + "github.com/d2lang/d2/d2graph" + "github.com/d2lang/d2/lib/geo" + "github.com/d2lang/d2/lib/label" + "github.com/d2lang/d2/lib/shape" + "github.com/d2lang/util-go/go2" +) + +const ( + // minRadius is the smallest radius a cycle is drawn with. + minRadius = 200. + // padding is the minimum gap between adjacent children along the circle. + padding = 24. + // maxArcSweep bounds each cubic segment so the Bezier approximation of a + // circular arc stays visually exact. + maxArcSweep = math.Pi / 2 + // chordStep is the angular resolution used when intersecting the cycle + // circle with a child shape's perimeter. The sagitta of a 1° chord is + // ~R·2.4e-5, far below a pixel. + chordStep = math.Pi / 180 + epsilon = 1e-6 +) + +// Layout arranges the direct children of the graph's root on a circle, keeps +// the internal layout of every child intact, and routes root-level edges as +// circular arcs trimmed to the endpoints' borders. +func Layout(ctx context.Context, g *d2graph.Graph, coreLayout d2graph.LayoutGraph) error { + objects := g.Root.ChildrenArray + if len(objects) == 0 { + return nil + } + + // Lay out children with the core engine first so nested content inside + // each child is fully positioned and routed before the child is moved + // onto the circle. The core layout may replace object pointers, so the + // children are re-read afterwards. + if coreLayout != nil { + if err := coreLayout(ctx, g); err != nil { + return err + } + } + + for _, obj := range g.Objects { + positionLabelsIcons(obj) + } + + radius := calculateRadius(g.Root.ChildrenArray) + if radius > 0 { + deltas := positionObjects(g.Root.ChildrenArray, radius) + applyDeltas(g, deltas) + directions := arcDirections(g) + for _, edge := range g.Edges { + if isCycleEdge(g, edge) { + routeCircularArc(edge, radius, directions[edge]) + } + } + } + + normalizeGraph(g) + return nil +} + +// calculateRadius returns the circle radius that fits all children with +// padding between neighbors, or 0 when there is nothing to place on a ring. +func calculateRadius(objects []*d2graph.Object) float64 { + if len(objects) < 2 { + return 0 + } + + maxHalfDiagonal := 0. + for _, obj := range objects { + maxHalfDiagonal = math.Max(maxHalfDiagonal, math.Hypot(obj.Width/2, obj.Height/2)) + } + + minimum := (maxHalfDiagonal + padding) / math.Sin(math.Pi/float64(len(objects))) + return math.Max(minimum, minRadius) +} + +// positionObjects centers each child on the circle, the first at 12 o'clock +// and the rest clockwise, and returns the translation applied to each child. +func positionObjects(objects []*d2graph.Object, radius float64) map[*d2graph.Object]*geo.Point { + deltas := make(map[*d2graph.Object]*geo.Point, len(objects)) + for i, obj := range objects { + angle := -math.Pi/2 + 2*math.Pi*float64(i)/float64(len(objects)) + x := radius * math.Cos(angle) + y := radius * math.Sin(angle) + + var dx, dy float64 + if obj.TopLeft != nil { + dx = x - obj.Width/2 - obj.TopLeft.X + dy = y - obj.Height/2 - obj.TopLeft.Y + } + deltas[obj] = geo.NewPoint(dx, dy) + } + return deltas +} + +// applyDeltas moves every child together with its descendants and keeps all +// edge routes attached to the moved endpoints. +func applyDeltas(g *d2graph.Graph, deltas map[*d2graph.Object]*geo.Point) { + if len(deltas) == 0 { + return + } + + for obj, delta := range deltas { + obj.MoveWithDescendants(delta.X, delta.Y) + } + + for _, edge := range g.Edges { + if len(edge.Route) == 0 || edge.Src == nil || edge.Dst == nil { + continue + } + srcDelta := ancestorDelta(edge.Src, deltas) + dstDelta := ancestorDelta(edge.Dst, deltas) + if srcDelta == nil && dstDelta == nil { + continue + } + if srcDelta != nil && dstDelta != nil && sameDelta(srcDelta, dstDelta) { + // Both endpoints belong to the same child: preserve the core + // layout's route shape by translating it entirely. + edge.Move(srcDelta.X, srcDelta.Y) + continue + } + if srcDelta != nil { + edge.Route[0].X += srcDelta.X + edge.Route[0].Y += srcDelta.Y + } + if dstDelta != nil { + last := edge.Route[len(edge.Route)-1] + last.X += dstDelta.X + last.Y += dstDelta.Y + } + } +} + +// ancestorDelta returns the delta of the root child that obj descends from. +func ancestorDelta(obj *d2graph.Object, deltas map[*d2graph.Object]*geo.Point) *geo.Point { + for curr := obj; curr != nil; curr = curr.Parent { + if delta, ok := deltas[curr]; ok { + return delta + } + } + return nil +} + +func sameDelta(a, b *geo.Point) bool { + return a == b || (a.X == b.X && a.Y == b.Y) +} + +// isCycleEdge reports whether both endpoints are direct children of the cycle +// root, i.e. the edge should follow the circle. +func isCycleEdge(g *d2graph.Graph, edge *d2graph.Edge) bool { + if edge.Src == nil || edge.Dst == nil { + return false + } + return edge.Src.Parent == g.Root && edge.Dst.Parent == g.Root +} + +// arcDirections decides the travel direction of every cycle edge. With only +// two children on the ring, edges between the same pair in opposite +// directions take opposite semicircles so their arcs do not overlap. On +// larger rings the opposite arc would cross other children, so every edge +// takes the shorter way around. +func arcDirections(g *d2graph.Graph) map[*d2graph.Edge]float64 { + ringSize := len(g.Root.ChildrenArray) + type pairKey struct{ a, b string } + pairs := make(map[pairKey][]*d2graph.Edge) + order := make([]pairKey, 0, len(g.Edges)) + + for _, edge := range g.Edges { + if !isCycleEdge(g, edge) { + continue + } + a, b := edge.Src.AbsID(), edge.Dst.AbsID() + if a > b { + a, b = b, a + } + key := pairKey{a, b} + if _, ok := pairs[key]; !ok { + order = append(order, key) + } + pairs[key] = append(pairs[key], edge) + } + + directions := make(map[*d2graph.Edge]float64, len(g.Edges)) + for _, key := range order { + edges := pairs[key] + opposite := ringSize == 2 && len(edges) > 1 && + edges[0].Src.AbsID() != edges[len(edges)-1].Src.AbsID() + for i, edge := range edges { + srcAngle := math.Atan2(edge.Src.Center().Y, edge.Src.Center().X) + dstAngle := math.Atan2(edge.Dst.Center().Y, edge.Dst.Center().X) + cw := modulo2Pi(dstAngle - srcAngle) + cclw := modulo2Pi(srcAngle - dstAngle) + if edge.Src == edge.Dst { + directions[edge] = -1 + } else if opposite && i > 0 { + // Same angular travel direction from the reversed endpoints + // traces the complementary arc, so the two edges do not + // overlap. + directions[edge] = directions[edges[0]] + } else if cw <= cclw { + directions[edge] = 1 + } else { + directions[edge] = -1 + } + } + } + return directions +} + +// routeCircularArc replaces the edge's route with an exact circular arc along +// the ring, trimmed to the visible borders of both endpoints. dir is +1 for +// clockwise travel, -1 for counter-clockwise. +func routeCircularArc(edge *d2graph.Edge, radius, dir float64) { + if edge.Src == nil || edge.Dst == nil || len(edge.Route) == 0 { + return + } + + center := geo.NewPoint(0, 0) + srcAngle := math.Atan2(edge.Src.Center().Y, edge.Src.Center().X) + dstAngle := math.Atan2(edge.Dst.Center().Y, edge.Dst.Center().X) + + exit := borderCrossingFor(edge.Src, center, radius, srcAngle, dir) + entry := borderCrossingFor(edge.Dst, center, radius, dstAngle, -dir) + if exit == nil || entry == nil { + routeStraight(edge) + return + } + + // Total swept angle from exit to entry travelling in direction dir. + sweep := modulo2Pi(entry.angle - exit.angle) + if dir < 0 { + sweep -= 2 * math.Pi + } + if math.Abs(sweep) < epsilon { + routeStraight(edge) + return + } + + start := exit.point + if start == nil { + start = pointOnCircle(center, radius, exit.angle) + } + end := entry.point + if end == nil { + end = pointOnCircle(center, radius, entry.angle) + } + + edge.Route = buildArcRoute(center, radius, exit.angle, exit.angle+sweep) + edge.Route[0] = start + edge.Route[len(edge.Route)-1] = end + edge.IsCurve = true +} + +func routeStraight(edge *d2graph.Edge) { + if edge.Src == nil || edge.Dst == nil { + return + } + edge.Route = []*geo.Point{edge.Src.Center(), edge.Dst.Center()} + edge.TraceToShape(edge.Route, 0, 1) + edge.IsCurve = false +} + +type borderCrossing struct { + angle float64 + // point is set when the crossing lies on the shape's visible perimeter; + // otherwise the point on the circle at angle is used. + point *geo.Point +} + +// borderCrossingFor returns where the circle leaves obj's border when +// travelling from the object's center angle in the given direction (searchDir +// is the direction to search in: +dir for an exit, -dir for an entry). +func borderCrossingFor(obj *d2graph.Object, center *geo.Point, radius, objAngle, searchDir float64) *borderCrossing { + if p := perimeterCrossing(obj, center, radius, objAngle, searchDir); p != nil { + return p + } + + // Fall back to the box border: bisect between the object's center angle + // (inside) and a quarter turn away (outside), then project onto the + // visible border for non-rectangular shapes. + inside := objAngle + outside := objAngle + searchDir*math.Pi/2 + for i := 0; i < 48; i++ { + mid := (inside + outside) / 2 + if obj.Box.Contains(pointOnCircle(center, radius, mid)) { + inside = mid + } else { + outside = mid + } + } + if math.Abs(outside-objAngle) < epsilon { + return nil + } + p := pointOnCircle(center, radius, outside) + if s := obj.ToShape(); s != nil && !s.IsRectangular() { + p = shape.TraceToShapeBorder(s, p, pointOnCircle(center, radius, outside-searchDir*0.001)) + } + return &borderCrossing{angle: math.Atan2(p.Y-center.Y, p.X-center.X), point: p} +} + +// perimeterCrossing intersects the circle with the shape's visible perimeter +// by marching a quarter turn from the object's center angle with chords. It +// returns the crossing closest to the center angle: the border exit when +// marching forward, the border entry when marching backward. nil when the +// shape has no perimeter geometry or no crossing was found. +func perimeterCrossing(obj *d2graph.Object, center *geo.Point, radius, objAngle, searchDir float64) *borderCrossing { + s := obj.ToShape() + if s == nil || s.IsRectangular() { + return nil + } + perimeter := s.Perimeter() + if len(perimeter) == 0 { + return nil + } + + from := objAngle + to := objAngle + searchDir*math.Pi/2 + steps := int(math.Ceil(math.Abs(to-from) / chordStep)) + if steps < 8 { + steps = 8 + } + + var best *borderCrossing + bestDist := math.Inf(1) + for i := 0; i < steps; i++ { + a0 := from + (to-from)*float64(i)/float64(steps) + a1 := from + (to-from)*float64(i+1)/float64(steps) + chord := geo.Segment{ + Start: pointOnCircle(center, radius, a0), + End: pointOnCircle(center, radius, a1), + } + for _, elem := range perimeter { + for _, p := range elem.Intersections(chord) { + angle := math.Atan2(p.Y-center.Y, p.X-center.X) + dist := math.Abs(moduloSigned(angle - objAngle)) + if dist < bestDist { + bestDist = dist + best = &borderCrossing{angle: angle, point: p} + } + } + } + } + return best +} + +// buildArcRoute returns a route of cubic Bezier control points tracing the +// circle from startAngle to endAngle, split into segments of at most +// maxArcSweep. Every chain endpoint lies exactly on the circle. +func buildArcRoute(center *geo.Point, radius, startAngle, endAngle float64) []*geo.Point { + sweep := endAngle - startAngle + segments := int(math.Ceil(math.Abs(sweep) / maxArcSweep)) + if segments < 1 { + segments = 1 + } + + route := make([]*geo.Point, 1, 3*segments+1) + route[0] = pointOnCircle(center, radius, startAngle) + for i := 0; i < segments; i++ { + a0 := startAngle + sweep*float64(i)/float64(segments) + a1 := startAngle + sweep*float64(i+1)/float64(segments) + p0 := pointOnCircle(center, radius, a0) + p3 := pointOnCircle(center, radius, a1) + // Canonical circular arc to cubic Bezier control point offset. + k := 4. / 3. * math.Tan((a1-a0)/4.) + + c1 := geo.NewPoint( + p0.X-k*radius*math.Sin(a0), + p0.Y+k*radius*math.Cos(a0), + ) + c2 := geo.NewPoint( + p3.X+k*radius*math.Sin(a1), + p3.Y-k*radius*math.Cos(a1), + ) + + route = append(route, c1, c2, p3) + } + return route +} + +func pointOnCircle(center *geo.Point, radius, angle float64) *geo.Point { + return geo.NewPoint(center.X+radius*math.Cos(angle), center.Y+radius*math.Sin(angle)) +} + +// normalizeGraph shifts all objects and routes so the bounding box starts at +// (0, 0) and records the bounding box on the root. +func normalizeGraph(g *d2graph.Graph) { + tl := geo.NewPoint(math.Inf(1), math.Inf(1)) + br := geo.NewPoint(math.Inf(-1), math.Inf(-1)) + + for _, obj := range g.Objects { + if obj.TopLeft == nil { + continue + } + tl.X = math.Min(tl.X, obj.TopLeft.X) + tl.Y = math.Min(tl.Y, obj.TopLeft.Y) + br.X = math.Max(br.X, obj.TopLeft.X+obj.Width) + br.Y = math.Max(br.Y, obj.TopLeft.Y+obj.Height) + } + for _, edge := range g.Edges { + for _, point := range edge.Route { + tl.X = math.Min(tl.X, point.X) + tl.Y = math.Min(tl.Y, point.Y) + br.X = math.Max(br.X, point.X) + br.Y = math.Max(br.Y, point.Y) + } + } + + if math.IsInf(tl.X, 0) || math.IsInf(tl.Y, 0) { + return + } + + dx := -tl.X + dy := -tl.Y + if dx != 0 || dy != 0 { + for _, obj := range g.Objects { + if obj.TopLeft != nil { + obj.TopLeft.X += dx + obj.TopLeft.Y += dy + } + } + for _, edge := range g.Edges { + edge.Move(dx, dy) + } + } + g.Root.Box = geo.NewBox(geo.NewPoint(0, 0), br.X-tl.X, br.Y-tl.Y) +} + +func modulo2Pi(angle float64) float64 { + angle = math.Mod(angle, 2*math.Pi) + if angle < 0 { + angle += 2 * math.Pi + } + return angle +} + +// moduloSigned reduces an angle to [0, 2π) keeping the magnitude of signed +// offsets small enough for nearest-crossing comparisons. +func moduloSigned(angle float64) float64 { + angle = math.Mod(angle, 2*math.Pi) + if angle < 0 { + angle += 2 * math.Pi + } + return angle +} + +// positionLabelsIcons matches the default label and icon placement used by +// the core layouts. +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..140180424d --- /dev/null +++ b/d2layouts/d2cycle/layout_test.go @@ -0,0 +1,281 @@ +package d2cycle + +import ( + "context" + "math" + "strings" + "testing" + + "github.com/d2lang/d2/d2compiler" + "github.com/d2lang/d2/d2graph" + "github.com/d2lang/d2/lib/geo" +) + +func compileGraph(t *testing.T, script string) *d2graph.Graph { + t.Helper() + g, _, err := d2compiler.Compile("", strings.NewReader(script), nil) + if err != nil { + t.Fatal(err) + } + return g +} + +// fakeCoreLayout gives every object a 100x100 box, spaces root children +// horizontally and routes all edges as straight lines between centers. +func fakeCoreLayout(_ context.Context, g *d2graph.Graph) error { + x := 0. + for _, obj := range g.Root.ChildrenArray { + setBox(obj, geo.NewPoint(x, 0), 100, 100) + x += 300 + } + for _, obj := range g.Objects { + if obj.Parent == nil || obj.Parent == g.Root { + continue + } + p := obj.Parent + setBox(obj, geo.NewPoint(p.TopLeft.X+20, p.TopLeft.Y+20), 30, 30) + } + for _, edge := range g.Edges { + edge.Route = []*geo.Point{edge.Src.Center(), edge.Dst.Center()} + } + return nil +} + +func setBox(obj *d2graph.Object, tl *geo.Point, w, h float64) { + obj.Box = geo.NewBox(tl, w, h) +} + +func TestCalculateRadius(t *testing.T) { + g := compileGraph(t, `shape: cycle +a +`) + if got := calculateRadius(g.Root.ChildrenArray); got != 0 { + t.Fatalf("expected radius 0 for single child, got %v", got) + } + + g = compileGraph(t, `shape: cycle +a; b; c +`) + for _, obj := range g.Objects { + setBox(obj, geo.NewPoint(0, 0), 300, 300) + } + radius := calculateRadius(g.Root.ChildrenArray) + // Children must fit: half diagonal + padding away from each other along + // the chord between adjacent positions. + want := (150*math.Sqrt2 + padding) / math.Sin(math.Pi/3) + if math.Abs(radius-want) > 0.001 { + t.Fatalf("expected radius %v, got %v", want, radius) + } + + g = compileGraph(t, `shape: cycle +a; b +`) + for _, obj := range g.Objects { + setBox(obj, geo.NewPoint(0, 0), 40, 40) + } + if got := calculateRadius(g.Root.ChildrenArray); got != minRadius { + t.Fatalf("expected small children to use the minimum radius %v, got %v", minRadius, got) + } +} + +func TestLayoutPositionsChildrenOnCircle(t *testing.T) { + g := compileGraph(t, `shape: cycle +a -> b -> c -> d -> a +`) + if err := Layout(context.Background(), g, fakeCoreLayout); err != nil { + t.Fatal(err) + } + + children := g.Root.ChildrenArray + if len(children) != 4 { + t.Fatalf("expected 4 children, got %d", len(children)) + } + var cx, cy float64 + for _, obj := range children { + cx += obj.Center().X + cy += obj.Center().Y + } + cx /= float64(len(children)) + cy /= float64(len(children)) + + radius := -1. + for _, obj := range children { + d := math.Hypot(obj.Center().X-cx, obj.Center().Y-cy) + if radius == -1 { + radius = d + } + if math.Abs(d-radius) > 0.001 { + t.Fatalf("expected %q at radius %v from (%v,%v), got %v", obj.ID, radius, cx, cy, d) + } + } + if radius < minRadius-0.001 { + t.Fatalf("expected radius >= %v, got %v", minRadius, radius) + } + + // First child sits at 12 o'clock, the rest clockwise. + first := children[0] + if math.Abs(first.Center().X-cx) > 0.001 || first.Center().Y >= cy { + t.Fatalf("expected first child centered on top, got (%v,%v) with center (%v,%v)", first.Center().X, first.Center().Y, cx, cy) + } + second := children[1] + if second.Center().X <= cx { + t.Fatalf("expected second child on the right side, got (%v,%v)", second.Center().X, second.Center().Y) + } +} + +func TestLayoutRoutesCircularArcsTrimmedToBorders(t *testing.T) { + g := compileGraph(t, `shape: cycle +a -> b -> c -> d -> a +`) + if err := Layout(context.Background(), g, fakeCoreLayout); err != nil { + t.Fatal(err) + } + + byID := make(map[string]*d2graph.Object) + for _, obj := range g.Objects { + byID[obj.ID] = obj + } + var cx, cy, radius float64 + for i, obj := range g.Root.ChildrenArray { + cx += obj.Center().X + cy += obj.Center().Y + if i == len(g.Root.ChildrenArray)-1 { + cx /= float64(len(g.Root.ChildrenArray)) + cy /= float64(len(g.Root.ChildrenArray)) + radius = math.Hypot(byID["a"].Center().X-cx, byID["a"].Center().Y-cy) + } + } + + for _, edge := range g.Edges { + if !edge.IsCurve { + t.Fatalf("expected edge %q to be routed as a curve", edge.AbsID()) + } + route := edge.Route + // Bezier chain: 1 + 3n points. + if len(route) < 4 || (len(route)-1)%3 != 0 { + t.Fatalf("expected edge %q route to be a Bezier chain, got %d points", edge.AbsID(), len(route)) + } + for i, p := range route { + if i%3 != 0 { + continue + } + if d := math.Abs(math.Hypot(p.X-cx, p.Y-cy) - radius); d > 0.001 { + t.Fatalf("expected chain endpoint %v of edge %q on the circle (radius %v), off by %v", p, edge.AbsID(), radius, d) + } + } + // Endpoints must stop at the endpoints' borders, not their centers. + if borderGap(route[0], byID[edge.Src.ID]) > 0.001 { + t.Fatalf("expected edge %q to start on the source border, got %v", edge.AbsID(), route[0]) + } + if borderGap(route[len(route)-1], byID[edge.Dst.ID]) > 0.001 { + t.Fatalf("expected edge %q to end on the destination border, got %v", edge.AbsID(), route[len(route)-1]) + } + } +} + +func borderGap(p *geo.Point, obj *d2graph.Object) float64 { + dx := math.Max(math.Abs(p.X-obj.Center().X)-obj.Width/2, 0) + dy := math.Max(math.Abs(p.Y-obj.Center().Y)-obj.Height/2, 0) + return math.Hypot(dx, dy) +} + +func TestLayoutKeepsNestedContentAndCrossChildEdges(t *testing.T) { + g := compileGraph(t, `shape: cycle +x: { + p -> q +} +y +x.p -> y +`) + if err := Layout(context.Background(), g, fakeCoreLayout); err != nil { + t.Fatal(err) + } + + var p, q *d2graph.Object + for _, obj := range g.Objects { + switch obj.AbsID() { + case "x.p": + p = obj + case "x.q": + q = obj + } + } + if p == nil || q == nil { + t.Fatal("expected nested objects to survive the cycle layout") + } + // p and q keep their internal offset inside x after x moved onto the ring. + x := g.Root.ChildrenArray[0] + if math.Abs(p.Center().X-(x.TopLeft.X+20+15)) > 0.001 || math.Abs(p.Center().Y-(x.TopLeft.Y+20+15)) > 0.001 { + t.Fatalf("expected nested content to move with its container, got p at (%v,%v) with x at %v", p.Center().X, p.Center().Y, x.TopLeft) + } + + for _, edge := range g.Edges { + srcInX, dstInX := edge.Src.Parent == x, edge.Dst.Parent == x + if srcInX && dstInX { + if !samePoint(edge.Route[0], edge.Src.Center()) || !samePoint(edge.Route[len(edge.Route)-1], edge.Dst.Center()) { + t.Fatalf("expected internal edge of %q to move with the container", x.ID) + } + } + if !srcInX && dstInX || srcInX && !dstInX { + if !samePoint(edge.Route[0], edge.Src.Center()) || !samePoint(edge.Route[len(edge.Route)-1], edge.Dst.Center()) { + t.Fatalf("expected cross-child edge to stay attached to its endpoints") + } + } + } +} + +func TestLayoutOppositeEdgesOnTwoNodeCycleDoNotOverlap(t *testing.T) { + g := compileGraph(t, `shape: cycle +a -> b +b -> a +`) + if err := Layout(context.Background(), g, fakeCoreLayout); err != nil { + t.Fatal(err) + } + + var cx, cy float64 + for _, obj := range g.Root.ChildrenArray { + cx += obj.Center().X + cy += obj.Center().Y + } + cx /= 2 + cy /= 2 + + angles := make([][]float64, 0, 2) + for _, edge := range g.Edges { + var as, ae float64 + for i, p := range edge.Route { + if i%3 != 0 { + continue + } + a := math.Atan2(p.Y-cy, p.X-cx) + switch i { + case 0: + as = a + default: + ae = a + } + } + angles = append(angles, []float64{modulo2Pi(as), modulo2Pi(ae)}) + } + + // One arc must pass through the right side (angle 0), the other through + // the left side (angle pi). + passesZero, passesPi := false, false + for _, a := range angles { + mid := modulo2Pi(a[0] + modulo2Pi(a[1]-a[0])/2) + if math.Abs(mid) < math.Pi/4 || math.Abs(mid) > 7*math.Pi/4 { + passesZero = true + } + if math.Abs(mid-math.Pi) < math.Pi/4 { + passesPi = true + } + } + if !passesZero || !passesPi { + t.Fatalf("expected opposite edges to take opposite semicircles, got %v", angles) + } +} + +func samePoint(a, b *geo.Point) bool { + return math.Abs(a.X-b.X) < 0.001 && math.Abs(a.Y-b.Y) < 0.001 +} diff --git a/d2layouts/d2layouts.go b/d2layouts/d2layouts.go index c9c45479df..531a4b842e 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" @@ -26,6 +27,7 @@ const ( ConstantNearGraph DiagramType = "constant-near" GridDiagram DiagramType = "grid-diagram" SequenceDiagram DiagramType = "sequence-diagram" + CycleDiagram DiagramType = "cycle-diagram" ) type GraphInfo struct { @@ -289,6 +291,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 +401,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/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/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 2d5fbfc156..f49b42486f 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -1829,7 +1829,7 @@ func drawShape(writer, appendixWriter io.Writer, diagramHash string, targetShape fmt.Fprint(writer, el.Render()) // TODO should standardize "" to rectangle - case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, d2target.ShapeHierarchy, "": + case d2target.ShapeRectangle, d2target.ShapeSequenceDiagram, d2target.ShapeCycleDiagram, d2target.ShapeHierarchy, "": borderRadius := math.MaxFloat64 if targetShape.BorderRadius != 0 { borderRadius = float64(targetShape.BorderRadius) diff --git a/d2target/d2target.go b/d2target/d2target.go index 432745bece..0d274a469c 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -1266,6 +1266,7 @@ const ( ShapeSQLTable = "sql_table" ShapeImage = "image" ShapeSequenceDiagram = "sequence_diagram" + ShapeCycleDiagram = "cycle" ShapeHierarchy = "hierarchy" ) @@ -1294,6 +1295,7 @@ var Shapes = []string{ ShapeSQLTable, ShapeImage, ShapeSequenceDiagram, + ShapeCycleDiagram, ShapeHierarchy, } @@ -1364,6 +1366,7 @@ var DSL_SHAPE_TO_SHAPE_TYPE = map[string]string{ ShapeSQLTable: shape.TABLE_TYPE, ShapeImage: shape.IMAGE_TYPE, ShapeSequenceDiagram: shape.SQUARE_TYPE, + ShapeCycleDiagram: shape.SQUARE_TYPE, ShapeHierarchy: shape.SQUARE_TYPE, } diff --git a/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json new file mode 100644 index 0000000000..d97747f4c0 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json @@ -0,0 +1,1567 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "four", + "type": "cycle", + "pos": { + "x": 0, + "y": 62 + }, + "width": 453, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "four.a", + "type": "rectangle", + "pos": { + "x": 200, + "y": 62 + }, + "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": "four.b", + "type": "rectangle", + "pos": { + "x": 400, + "y": 262 + }, + "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": "four.c", + "type": "rectangle", + "pos": { + "x": 200, + "y": 462 + }, + "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": "four.d", + "type": "rectangle", + "pos": { + "x": 0, + "y": 262 + }, + "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": "nonrect", + "type": "cycle", + "pos": { + "x": 513, + "y": 76 + }, + "width": 415, + "height": 439, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "nonrect.a", + "type": "oval", + "pos": { + "x": 682, + "y": 76 + }, + "width": 77, + "height": 77, + "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": "nonrect.b", + "type": "hexagon", + "pos": { + "x": 868, + "y": 380 + }, + "width": 50, + "height": 69, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N5", + "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": "nonrect.c", + "type": "diamond", + "pos": { + "x": 524, + "y": 368 + }, + "width": 46, + "height": 92, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N4", + "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": "mixed", + "type": "cycle", + "pos": { + "x": 988, + "y": 0 + }, + "width": 468, + "height": 590, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "honeydew", + "stroke": "red", + "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": "mixed.x", + "type": "rectangle", + "pos": { + "x": 1153, + "y": 0 + }, + "width": 167, + "height": 292, + "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": "x", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 11, + "labelHeight": 31, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "mixed.x.p", + "type": "rectangle", + "pos": { + "x": 1237, + "y": 30 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "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": "p", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "mixed.x.q", + "type": "rectangle", + "pos": { + "x": 1183, + "y": 196 + }, + "width": 54, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "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": "q", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 9, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "mixed.y", + "type": "rectangle", + "pos": { + "x": 1402, + "y": 445 + }, + "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": "y", + "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": "mixed.z", + "type": "oval", + "pos": { + "x": 1006, + "y": 440 + }, + "width": 77, + "height": 77, + "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": "z", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 7, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "two", + "type": "cycle", + "pos": { + "x": 1516, + "y": 62 + }, + "width": 400, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "two.a", + "type": "rectangle", + "pos": { + "x": 1689, + "y": 62 + }, + "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": "two.b", + "type": "rectangle", + "pos": { + "x": 1689, + "y": 462 + }, + "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": "four.(a -> b)[0]", + "src": "four.a", + "srcArrow": "none", + "dst": "four.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": 96.76300048828125 + }, + { + "x": 340.5150146484375, + "y": 108.3949966430664 + }, + { + "x": 409.77301025390625, + "y": 175.41299438476562 + }, + { + "x": 424.25799560546875, + "y": 261.9989929199219 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(b -> c)[0]", + "src": "four.b", + "srcArrow": "none", + "dst": "four.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": 328 + }, + { + "x": 409.77301025390625, + "y": 414.58599853515625 + }, + { + "x": 340.5150146484375, + "y": 481.60400390625 + }, + { + "x": 253.5, + "y": 493.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(c -> d)[0]", + "src": "four.c", + "srcArrow": "none", + "dst": "four.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.49899291992188, + "y": 493.2359924316406 + }, + { + "x": 113.48400115966797, + "y": 481.60400390625 + }, + { + "x": 44.22600173950195, + "y": 414.58599853515625 + }, + { + "x": 29.740999221801758, + "y": 328 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(d -> a)[0]", + "src": "four.d", + "srcArrow": "none", + "dst": "four.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": 29.740999221801758, + "y": 261.9989929199219 + }, + { + "x": 44.22600173950195, + "y": 175.41299438476562 + }, + { + "x": 113.48400115966797, + "y": 108.3949966430664 + }, + { + "x": 200.49899291992188, + "y": 96.76300048828125 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(a -> b)[0]", + "src": "nonrect.a", + "srcArrow": "none", + "dst": "nonrect.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": 759.0819702148438, + "y": 118.20600128173828 + }, + { + "x": 816.791015625, + "y": 129.4709930419922 + }, + { + "x": 866.6190185546875, + "y": 165.56100463867188 + }, + { + "x": 895.3189697265625, + "y": 216.8800048828125 + }, + { + "x": 924.0180053710938, + "y": 268.1990051269531 + }, + { + "x": 928.6810302734375, + "y": 329.5469970703125 + }, + { + "x": 907.760986328125, + "y": 384.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(b -> c)[0]", + "src": "nonrect.b", + "srcArrow": "none", + "dst": "nonrect.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": 877.760986328125, + "y": 438.5 + }, + { + "x": 839.239013671875, + "y": 487.1719970703125 + }, + { + "x": 780.3289794921875, + "y": 515.260009765625 + }, + { + "x": 718.260986328125, + "y": 514.4840087890625 + }, + { + "x": 656.1939697265625, + "y": 513.7080078125 + }, + { + "x": 598.0040283203125, + "y": 484.1579895019531 + }, + { + "x": 560.760986328125, + "y": 434.5 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(c -> a)[0]", + "src": "nonrect.c", + "srcArrow": "none", + "dst": "nonrect.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": 535.760986328125, + "y": 390.5 + }, + { + "x": 513, + "y": 335.0880126953125 + }, + { + "x": 516.27197265625, + "y": 272.3909912109375 + }, + { + "x": 544.6820068359375, + "y": 219.6510009765625 + }, + { + "x": 573.0910034179688, + "y": 166.91099548339844 + }, + { + "x": 623.64501953125, + "y": 129.68299865722656 + }, + { + "x": 682.4390258789062, + "y": 118.20600128173828 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(b -> a)[0]", + "src": "nonrect.b", + "srcArrow": "none", + "dst": "nonrect.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": 907.760986328125, + "y": 384.5 + }, + { + "x": 928.6810302734375, + "y": 329.5469970703125 + }, + { + "x": 924.0180053710938, + "y": 268.1990051269531 + }, + { + "x": 895.3189697265625, + "y": 216.8800048828125 + }, + { + "x": 866.6190185546875, + "y": 165.56100463867188 + }, + { + "x": 816.791015625, + "y": 129.4709930419922 + }, + { + "x": 759.0819702148438, + "y": 118.20600128173828 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.x.(p -> q)[0]", + "src": "mixed.x.p", + "srcArrow": "none", + "dst": "mixed.x.q", + "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": 1242.9300537109375, + "y": 96 + }, + { + "x": 1217.3299560546875, + "y": 136 + }, + { + "x": 1210.9300537109375, + "y": 156 + }, + { + "x": 1210.9300537109375, + "y": 196 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(x.p -> y)[0]", + "src": "mixed.x.p", + "srcArrow": "none", + "dst": "mixed.y", + "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": 1277.9300537109375, + "y": 96 + }, + { + "x": 1387.4300537109375, + "y": 523.9229736328125 + }, + { + "x": 1406.030029296875, + "y": 545.9229736328125 + }, + { + "x": 1402.6209716796875, + "y": 455.8840026855469 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(y -> z)[0]", + "src": "mixed.y", + "srcArrow": "none", + "dst": "mixed.z", + "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": 1406.322998046875, + "y": 511.8840026855469 + }, + { + "x": 1363.9429931640625, + "y": 561.60400390625 + }, + { + "x": 1301.8089599609375, + "y": 590.1240234375 + }, + { + "x": 1236.47900390625, + "y": 589.843994140625 + }, + { + "x": 1171.14794921875, + "y": 589.5640258789062 + }, + { + "x": 1109.260986328125, + "y": 560.5120239257812 + }, + { + "x": 1067.31005859375, + "y": 510.42999267578125 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(z -> x)[0]", + "src": "mixed.z", + "srcArrow": "none", + "dst": "mixed.x", + "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": 1028.9549560546875, + "y": 443.99798583984375 + }, + { + "x": 988, + "y": 331.76800537109375 + }, + { + "x": 1043.239990234375, + "y": 207.25799560546875 + }, + { + "x": 1153.9300537109375, + "y": 162.3070068359375 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "two.(a -> b)[0]", + "src": "two.a", + "srcArrow": "none", + "dst": "two.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": 1742.5, + "y": 96.76300048828125 + }, + { + "x": 1841.8310546875, + "y": 110.04100036621094 + }, + { + "x": 1916, + "y": 194.78500366210938 + }, + { + "x": 1916, + "y": 295 + }, + { + "x": 1916, + "y": 395.2139892578125 + }, + { + "x": 1841.8310546875, + "y": 479.9580078125 + }, + { + "x": 1742.5, + "y": 493.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "two.(b -> a)[0]", + "src": "two.b", + "srcArrow": "none", + "dst": "two.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": 1689.4990234375, + "y": 493.2359924316406 + }, + { + "x": 1590.16796875, + "y": 479.9580078125 + }, + { + "x": 1516, + "y": 395.2139892578125 + }, + { + "x": 1516, + "y": 295 + }, + { + "x": 1516, + "y": 194.78500366210938 + }, + { + "x": 1590.16796875, + "y": 110.04100036621094 + }, + { + "x": 1689.4990234375, + "y": 96.76300048828125 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg new file mode 100644 index 0000000000..a521f90259 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg @@ -0,0 +1,40 @@ +abcdabcxyzabpq + + + \ 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..ddd043e73a --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json @@ -0,0 +1,1565 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "four", + "type": "cycle", + "pos": { + "x": 12, + "y": 99 + }, + "width": 454, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "four.a", + "type": "rectangle", + "pos": { + "x": 212, + "y": 99 + }, + "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": "four.b", + "type": "rectangle", + "pos": { + "x": 412, + "y": 299 + }, + "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": "four.c", + "type": "rectangle", + "pos": { + "x": 212, + "y": 499 + }, + "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": "four.d", + "type": "rectangle", + "pos": { + "x": 12, + "y": 299 + }, + "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": "nonrect", + "type": "cycle", + "pos": { + "x": 485, + "y": 111 + }, + "width": 421, + "height": 441, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "nonrect.a", + "type": "oval", + "pos": { + "x": 653, + "y": 111 + }, + "width": 80, + "height": 77, + "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": "nonrect.b", + "type": "hexagon", + "pos": { + "x": 826, + "y": 415 + }, + "width": 80, + "height": 69, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N5", + "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": "nonrect.c", + "type": "diamond", + "pos": { + "x": 496, + "y": 404 + }, + "width": 46, + "height": 92, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N4", + "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": "mixed", + "type": "cycle", + "pos": { + "x": 926, + "y": 12 + }, + "width": 509, + "height": 641, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "honeydew", + "stroke": "red", + "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": "mixed.x", + "type": "rectangle", + "pos": { + "x": 1097, + "y": 12 + }, + "width": 201, + "height": 312, + "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": "x", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 11, + "labelHeight": 31, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "mixed.x.p", + "type": "rectangle", + "pos": { + "x": 1167, + "y": 62 + }, + "width": 80, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "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": "p", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "mixed.x.q", + "type": "rectangle", + "pos": { + "x": 1147, + "y": 208 + }, + "width": 54, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "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": "q", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 9, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "mixed.y", + "type": "rectangle", + "pos": { + "x": 1380, + "y": 497 + }, + "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": "y", + "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": "mixed.z", + "type": "oval", + "pos": { + "x": 949, + "y": 492 + }, + "width": 77, + "height": 77, + "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": "z", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 7, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "two", + "type": "cycle", + "pos": { + "x": 1454, + "y": 99 + }, + "width": 400, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "two.a", + "type": "rectangle", + "pos": { + "x": 1628, + "y": 99 + }, + "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": "two.b", + "type": "rectangle", + "pos": { + "x": 1628, + "y": 499 + }, + "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": "four.(a -> b)[0]", + "src": "four.a", + "srcArrow": "none", + "dst": "four.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": 133.8780059814453 + }, + { + "x": 352.5150146484375, + "y": 145.50999450683594 + }, + { + "x": 421.77301025390625, + "y": 212.5279998779297 + }, + { + "x": 436.25799560546875, + "y": 299.114990234375 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(b -> c)[0]", + "src": "four.b", + "srcArrow": "none", + "dst": "four.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": 365.114990234375 + }, + { + "x": 421.77301025390625, + "y": 451.70098876953125 + }, + { + "x": 352.5150146484375, + "y": 518.718994140625 + }, + { + "x": 265.5, + "y": 530.3510131835938 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(c -> d)[0]", + "src": "four.c", + "srcArrow": "none", + "dst": "four.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.49899291992188, + "y": 530.3510131835938 + }, + { + "x": 125.48400115966797, + "y": 518.718994140625 + }, + { + "x": 56.22600173950195, + "y": 451.70098876953125 + }, + { + "x": 41.74100112915039, + "y": 365.114990234375 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(d -> a)[0]", + "src": "four.d", + "srcArrow": "none", + "dst": "four.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": 41.74100112915039, + "y": 299.114990234375 + }, + { + "x": 56.22600173950195, + "y": 212.5279998779297 + }, + { + "x": 125.48400115966797, + "y": 145.50999450683594 + }, + { + "x": 212.49899291992188, + "y": 133.8780059814453 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(a -> b)[0]", + "src": "nonrect.a", + "srcArrow": "none", + "dst": "nonrect.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": 732.9439697265625, + "y": 154.35800170898438 + }, + { + "x": 789.5540161132812, + "y": 165.84100341796875 + }, + { + "x": 838.4299926757812, + "y": 201.24400329589844 + }, + { + "x": 866.9940185546875, + "y": 251.44900512695312 + }, + { + "x": 895.5590209960938, + "y": 301.65399169921875 + }, + { + "x": 901.0230102539062, + "y": 361.7569885253906 + }, + { + "x": 882.1610107421875, + "y": 416.35400390625 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(b -> c)[0]", + "src": "nonrect.b", + "srcArrow": "none", + "dst": "nonrect.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": 844.1610107421875, + "y": 481.35400390625 + }, + { + "x": 804.6160278320312, + "y": 527.0809936523438 + }, + { + "x": 746.469970703125, + "y": 552.3759765625 + }, + { + "x": 686.0540161132812, + "y": 550.2269897460938 + }, + { + "x": 625.6370239257812, + "y": 548.0789794921875 + }, + { + "x": 569.4340209960938, + "y": 518.718017578125 + }, + { + "x": 533.1610107421875, + "y": 470.35400390625 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(c -> a)[0]", + "src": "nonrect.c", + "srcArrow": "none", + "dst": "nonrect.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": 508.1610107421875, + "y": 426.35400390625 + }, + { + "x": 485.5, + "y": 371.1830139160156 + }, + { + "x": 488.6409912109375, + "y": 308.77801513671875 + }, + { + "x": 516.72998046875, + "y": 256.1629943847656 + }, + { + "x": 544.8189697265625, + "y": 203.54800415039062 + }, + { + "x": 594.9249877929688, + "y": 166.21499633789062 + }, + { + "x": 653.3779907226562, + "y": 154.35800170898438 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(b -> a)[0]", + "src": "nonrect.b", + "srcArrow": "none", + "dst": "nonrect.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": 882.1610107421875, + "y": 416.35400390625 + }, + { + "x": 901.0230102539062, + "y": 361.7569885253906 + }, + { + "x": 895.5590209960938, + "y": 301.65399169921875 + }, + { + "x": 866.9940185546875, + "y": 251.44900512695312 + }, + { + "x": 838.4299926757812, + "y": 201.24400329589844 + }, + { + "x": 789.5540161132812, + "y": 165.84100341796875 + }, + { + "x": 732.9439697265625, + "y": 154.35800170898438 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.x.(p -> q)[0]", + "src": "mixed.x.p", + "srcArrow": "none", + "dst": "mixed.x.q", + "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": 1194.623046875, + "y": 128 + }, + { + "x": 1194.623046875, + "y": 168 + }, + { + "x": 1174.4560546875, + "y": 168 + }, + { + "x": 1174.4560546875, + "y": 208 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(x.p -> y)[0]", + "src": "mixed.x.p", + "srcArrow": "none", + "dst": "mixed.y", + "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": 1221.2889404296875, + "y": 128 + }, + { + "x": 1333.7889404296875, + "y": 617.989990234375 + }, + { + "x": 1353.9560546875, + "y": 617.989990234375 + }, + { + "x": 1407.5260009765625, + "y": 497.9859924316406 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(y -> z)[0]", + "src": "mixed.y", + "srcArrow": "none", + "dst": "mixed.z", + "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": 1384.6240234375, + "y": 563.9860229492188 + }, + { + "x": 1338.468017578125, + "y": 619.9349975585938 + }, + { + "x": 1269.6490478515625, + "y": 652.22998046875 + }, + { + "x": 1197.1190185546875, + "y": 651.97900390625 + }, + { + "x": 1124.5889892578125, + "y": 651.72900390625 + }, + { + "x": 1055.9959716796875, + "y": 618.9580078125 + }, + { + "x": 1010.2310180664062, + "y": 562.68798828125 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(z -> x)[0]", + "src": "mixed.z", + "srcArrow": "none", + "dst": "mixed.x", + "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": 971.85302734375, + "y": 496.21600341796875 + }, + { + "x": 926.3660278320312, + "y": 376.9519958496094 + }, + { + "x": 981.3410034179688, + "y": 242.86700439453125 + }, + { + "x": 1097.4560546875, + "y": 189.8560028076172 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "two.(a -> b)[0]", + "src": "two.a", + "srcArrow": "none", + "dst": "two.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": 1681.0260009765625, + "y": 133.8780059814453 + }, + { + "x": 1780.3570556640625, + "y": 147.15699768066406 + }, + { + "x": 1854.5260009765625, + "y": 231.89999389648438 + }, + { + "x": 1854.5260009765625, + "y": 332.114990234375 + }, + { + "x": 1854.5260009765625, + "y": 432.3299865722656 + }, + { + "x": 1780.3570556640625, + "y": 517.072998046875 + }, + { + "x": 1681.0260009765625, + "y": 530.3510131835938 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "two.(b -> a)[0]", + "src": "two.b", + "srcArrow": "none", + "dst": "two.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": 1628.0260009765625, + "y": 530.3510131835938 + }, + { + "x": 1528.6949462890625, + "y": 517.072998046875 + }, + { + "x": 1454.5260009765625, + "y": 432.3299865722656 + }, + { + "x": 1454.5260009765625, + "y": 332.114990234375 + }, + { + "x": 1454.5260009765625, + "y": 231.89999389648438 + }, + { + "x": 1528.6949462890625, + "y": 147.15699768066406 + }, + { + "x": 1628.0260009765625, + "y": 133.8780059814453 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg new file mode 100644 index 0000000000..0502fbe56d --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg @@ -0,0 +1,40 @@ +abcdabcxyzabpq + + + \ 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..52d9ba2a41 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json @@ -0,0 +1,1525 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "monoFontFamily": "SourceCodePro", + "shapes": [ + { + "id": "four", + "type": "cycle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 453, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "four.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": "four.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": "four.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": "four.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": "nonrect", + "type": "cycle", + "pos": { + "x": 473, + "y": 0 + }, + "width": 440, + "height": 464, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "nonrect.a", + "type": "oval", + "pos": { + "x": 619, + "y": 0 + }, + "width": 120, + "height": 120, + "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": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 12, + "labelHeight": 32, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "nonrect.b", + "type": "hexagon", + "pos": { + "x": 792, + "y": 299 + }, + "width": 121, + "height": 121, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N5", + "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": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "nonrect.c", + "type": "diamond", + "pos": { + "x": 483, + "y": 314 + }, + "width": 46, + "height": 92, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N4", + "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": "mixed", + "type": "cycle", + "pos": { + "x": 0, + "y": 486 + }, + "width": 499, + "height": 633, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "honeydew", + "stroke": "red", + "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": "mixed.x", + "type": "rectangle", + "pos": { + "x": 180, + "y": 486 + }, + "width": 174, + "height": 318, + "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": "x", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 11, + "labelHeight": 31, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "mixed.x.p", + "type": "rectangle", + "pos": { + "x": 241, + "y": 546 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "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": "p", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "mixed.x.q", + "type": "rectangle", + "pos": { + "x": 240, + "y": 678 + }, + "width": 54, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "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": "q", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 9, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "mixed.y", + "type": "rectangle", + "pos": { + "x": 445, + "y": 967 + }, + "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": "y", + "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": "mixed.z", + "type": "oval", + "pos": { + "x": 23, + "y": 961 + }, + "width": 77, + "height": 77, + "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": "z", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 7, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "two", + "type": "cycle", + "pos": { + "x": 520, + "y": 486 + }, + "width": 400, + "height": 520, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "two.a", + "type": "rectangle", + "pos": { + "x": 660, + "y": 486 + }, + "width": 120, + "height": 120, + "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": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "two.b", + "type": "rectangle", + "pos": { + "x": 660, + "y": 886 + }, + "width": 120, + "height": 120, + "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": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 37, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "four.(a -> b)[0]", + "src": "four.a", + "srcArrow": "none", + "dst": "four.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": 340.5150146484375, + "y": 46.39500045776367 + }, + { + "x": 409.77301025390625, + "y": 113.41300201416016 + }, + { + "x": 424.25799560546875, + "y": 199.99899291992188 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(b -> c)[0]", + "src": "four.b", + "srcArrow": "none", + "dst": "four.c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 424.25799560546875, + "y": 266 + }, + { + "x": 409.77301025390625, + "y": 352.58599853515625 + }, + { + "x": 340.5150146484375, + "y": 419.60400390625 + }, + { + "x": 253.5, + "y": 431.2359924316406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(c -> d)[0]", + "src": "four.c", + "srcArrow": "none", + "dst": "four.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.49899291992188, + "y": 431.2359924316406 + }, + { + "x": 113.48400115966797, + "y": 419.60400390625 + }, + { + "x": 44.22600173950195, + "y": 352.58599853515625 + }, + { + "x": 29.740999221801758, + "y": 266 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "four.(d -> a)[0]", + "src": "four.d", + "srcArrow": "none", + "dst": "four.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": 29.740999221801758, + "y": 199.99899291992188 + }, + { + "x": 44.22600173950195, + "y": 113.41300201416016 + }, + { + "x": 113.48400115966797, + "y": 46.39500045776367 + }, + { + "x": 200.49899291992188, + "y": 34.76300048828125 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(a -> b)[0]", + "src": "nonrect.a", + "srcArrow": "none", + "dst": "nonrect.b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 739.1439819335938, + "y": 69.00499725341797 + }, + { + "x": 837.260009765625, + "y": 99.4729995727539 + }, + { + "x": 896.3280029296875, + "y": 199.3280029296875 + }, + { + "x": 875.822998046875, + "y": 300 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(b -> c)[0]", + "src": "nonrect.b", + "srcArrow": "none", + "dst": "nonrect.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": 815.822998046875, + "y": 407 + }, + { + "x": 774.0809936523438, + "y": 445.260009765625 + }, + { + "x": 718.166015625, + "y": 464.29400634765625 + }, + { + "x": 661.7730102539062, + "y": 459.1830139160156 + }, + { + "x": 605.3809814453125, + "y": 454.072998046875 + }, + { + "x": 553.7969970703125, + "y": 425.2980041503906 + }, + { + "x": 519.822998046875, + "y": 380 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(c -> a)[0]", + "src": "nonrect.c", + "srcArrow": "none", + "dst": "nonrect.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": 494.822998046875, + "y": 336 + }, + { + "x": 473.5, + "y": 284.08599853515625 + }, + { + "x": 474.96600341796875, + "y": 225.6020050048828 + }, + { + "x": 498.86700439453125, + "y": 174.82400512695312 + }, + { + "x": 522.7680053710938, + "y": 124.0459976196289 + }, + { + "x": 566.905029296875, + "y": 85.64600372314453 + }, + { + "x": 620.5029907226562, + "y": 69.00499725341797 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "nonrect.(b -> a)[0]", + "src": "nonrect.b", + "srcArrow": "none", + "dst": "nonrect.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": 875.822998046875, + "y": 300 + }, + { + "x": 896.3280029296875, + "y": 199.3280029296875 + }, + { + "x": 837.260009765625, + "y": 99.4729995727539 + }, + { + "x": 739.1439819335938, + "y": 69.00499725341797 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.x.(p -> q)[0]", + "src": "mixed.x.p", + "srcArrow": "none", + "dst": "mixed.x.q", + "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": 267.4649963378906, + "y": 612 + }, + { + "x": 267.4649963378906, + "y": 678 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(x.p -> y)[0]", + "src": "mixed.x.p", + "srcArrow": "none", + "dst": "mixed.y", + "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": 294.4649963378906, + "y": 579 + }, + { + "x": 445.7099914550781, + "y": 1000.4949951171875 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(y -> z)[0]", + "src": "mixed.y", + "srcArrow": "none", + "dst": "mixed.z", + "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": 449.7170104980469, + "y": 1033.4949951171875 + }, + { + "x": 404.49700927734375, + "y": 1087.89501953125 + }, + { + "x": 337.34100341796875, + "y": 1119.25 + }, + { + "x": 266.60198974609375, + "y": 1118.9930419921875 + }, + { + "x": 195.86199951171875, + "y": 1118.7349853515625 + }, + { + "x": 128.93600463867188, + "y": 1086.8909912109375 + }, + { + "x": 84.12000274658203, + "y": 1032.1590576171875 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "mixed.(z -> x)[0]", + "src": "mixed.z", + "srcArrow": "none", + "dst": "mixed.x", + "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": 45.74800109863281, + "y": 965.697021484375 + }, + { + "x": 0, + "y": 844.5339965820312 + }, + { + "x": 59.994998931884766, + "y": 709.0880126953125 + }, + { + "x": 180.46499633789062, + "y": 661.5460205078125 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "two.(a -> b)[0]", + "src": "two.a", + "srcArrow": "none", + "dst": "two.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": 780, + "y": 555.2119750976562 + }, + { + "x": 863.3200073242188, + "y": 581.4149780273438 + }, + { + "x": 920, + "y": 658.656005859375 + }, + { + "x": 920, + "y": 746 + }, + { + "x": 920, + "y": 833.343017578125 + }, + { + "x": 863.3200073242188, + "y": 910.583984375 + }, + { + "x": 780, + "y": 936.7869873046875 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "two.(b -> a)[0]", + "src": "two.b", + "srcArrow": "none", + "dst": "two.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": 659.9990234375, + "y": 936.7869873046875 + }, + { + "x": 576.6790161132812, + "y": 910.583984375 + }, + { + "x": 520, + "y": 833.343017578125 + }, + { + "x": 520, + "y": 746 + }, + { + "x": 520, + "y": 658.656005859375 + }, + { + "x": 576.6790161132812, + "y": 581.4149780273438 + }, + { + "x": 659.9990234375, + "y": 555.2119750976562 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + } + ], + "root": { + "id": "", + "type": "", + "pos": { + "x": 0, + "y": 0 + }, + "width": 0, + "height": 0, + "opacity": 0, + "strokeDash": 0, + "strokeWidth": 0, + "borderRadius": 0, + "fill": "N7", + "stroke": "", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 0, + "fontFamily": "", + "language": "", + "color": "", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 0 + } +} diff --git a/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg new file mode 100644 index 0000000000..d4e4db7928 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg @@ -0,0 +1,40 @@ +abcdabcxyzabpq + + + \ No newline at end of file diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt index 7585d91b3e..15e8ff8657 100644 --- a/e2etests/txtar.txt +++ b/e2etests/txtar.txt @@ -1773,3 +1773,35 @@ style: {fill-pattern: dots; fill:"radial-gradient(#fbfbf8, #e3e3f0)"; stroke: "# a->b +-- cycle-diagram -- +four: "" { + shape: cycle + a -> b -> c -> d -> a +} +nonrect: "" { + shape: cycle + a: {shape: circle} + b: {shape: hexagon} + c: {shape: diamond} + a -> b -> c -> a + b -> a +} +mixed: "" { + shape: cycle + style.fill: honeydew + style.stroke: red + x: { + p + q + p -> q + } + y + z: {shape: circle} + x.p -> y + y -> z -> x +} +two: "" { + shape: cycle + a -> b + b -> a +} From f2f2863d8210156dd72110c3fd93eede826228cf Mon Sep 17 00:00:00 2001 From: Furox-Art <177975472+Furox-Art@users.noreply.github.com> Date: Thu, 24 Sep 2026 01:31:20 +0300 Subject: [PATCH 2/2] fix: inset cycle container content --- d2layouts/d2cycle/layout.go | 35 +- d2layouts/d2cycle/layout_test.go | 38 ++ .../txtar/cycle-diagram/dagre/board.exp.json | 386 +++++++++--------- .../txtar/cycle-diagram/dagre/sketch.exp.svg | 36 +- .../txtar/cycle-diagram/elk/board.exp.json | 386 +++++++++--------- .../txtar/cycle-diagram/elk/sketch.exp.svg | 36 +- .../txtar/cycle-diagram/tala/board.exp.json | 348 ++++++++-------- .../txtar/cycle-diagram/tala/sketch.exp.svg | 36 +- 8 files changed, 682 insertions(+), 619 deletions(-) diff --git a/d2layouts/d2cycle/layout.go b/d2layouts/d2cycle/layout.go index e20507fa29..ec48174369 100644 --- a/d2layouts/d2cycle/layout.go +++ b/d2layouts/d2cycle/layout.go @@ -21,7 +21,8 @@ const ( // minRadius is the smallest radius a cycle is drawn with. minRadius = 200. // padding is the minimum gap between adjacent children along the circle. - padding = 24. + padding = 24. + containerPadding = 30. // maxArcSweep bounds each cubic segment so the Bezier approximation of a // circular arc stays visually exact. maxArcSweep = math.Pi / 2 @@ -51,6 +52,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, coreLayout d2graph.LayoutGrap } } + positionLabelsIcons(g.Root) for _, obj := range g.Objects { positionLabelsIcons(obj) } @@ -395,7 +397,7 @@ func pointOnCircle(center *geo.Point, radius, angle float64) *geo.Point { } // normalizeGraph shifts all objects and routes so the bounding box starts at -// (0, 0) and records the bounding box on the root. +// (0, 0) and records the padded bounding box on the root. func normalizeGraph(g *d2graph.Graph) { tl := geo.NewPoint(math.Inf(1), math.Inf(1)) br := geo.NewPoint(math.Inf(-1), math.Inf(-1)) @@ -422,8 +424,9 @@ func normalizeGraph(g *d2graph.Graph) { return } - dx := -tl.X - dy := -tl.Y + contentPadding := containerContentPadding(g.Root) + dx := contentPadding.Left - tl.X + dy := contentPadding.Top - tl.Y if dx != 0 || dy != 0 { for _, obj := range g.Objects { if obj.TopLeft != nil { @@ -435,7 +438,29 @@ func normalizeGraph(g *d2graph.Graph) { edge.Move(dx, dy) } } - g.Root.Box = geo.NewBox(geo.NewPoint(0, 0), br.X-tl.X, br.Y-tl.Y) + g.Root.Box = geo.NewBox( + geo.NewPoint(0, 0), + br.X-tl.X+contentPadding.Left+contentPadding.Right, + br.Y-tl.Y+contentPadding.Top+contentPadding.Bottom, + ) +} + +func containerContentPadding(root *d2graph.Object) geo.Spacing { + padding := geo.Spacing{ + Top: containerPadding, + Bottom: containerPadding, + Left: containerPadding, + Right: containerPadding, + } + if root == nil { + return padding + } + margin, spacing := root.Spacing() + padding.Top = math.Max(padding.Top, math.Max(margin.Top, spacing.Top)) + padding.Bottom = math.Max(padding.Bottom, math.Max(margin.Bottom, spacing.Bottom)) + padding.Left = math.Max(padding.Left, math.Max(margin.Left, spacing.Left)) + padding.Right = math.Max(padding.Right, math.Max(margin.Right, spacing.Right)) + return padding } func modulo2Pi(angle float64) float64 { diff --git a/d2layouts/d2cycle/layout_test.go b/d2layouts/d2cycle/layout_test.go index 140180424d..9125c0e637 100644 --- a/d2layouts/d2cycle/layout_test.go +++ b/d2layouts/d2cycle/layout_test.go @@ -8,7 +8,9 @@ import ( "github.com/d2lang/d2/d2compiler" "github.com/d2lang/d2/d2graph" + "github.com/d2lang/d2/d2target" "github.com/d2lang/d2/lib/geo" + "github.com/d2lang/d2/lib/label" ) func compileGraph(t *testing.T, script string) *d2graph.Graph { @@ -123,6 +125,42 @@ a -> b -> c -> d -> a } } +func TestLayoutAddsContainerInset(t *testing.T) { + g := compileGraph(t, `shape: cycle +a -> b -> c -> d -> a +`) + g.Root.Label.Value = "Cycle" + g.Root.LabelDimensions = d2target.TextDimensions{Width: 80, Height: 40} + labelPosition := label.InsideTopCenter.String() + g.Root.LabelPosition = &labelPosition + if err := Layout(context.Background(), g, fakeCoreLayout); err != nil { + t.Fatal(err) + } + + root := g.Root.Box + if root == nil { + t.Fatal("expected cycle root box") + } + for _, child := range g.Root.ChildrenArray { + if child.TopLeft.X <= root.TopLeft.X || child.TopLeft.Y < root.TopLeft.Y+50 || + child.TopLeft.X+child.Width >= root.TopLeft.X+root.Width || + child.TopLeft.Y+child.Height >= root.TopLeft.Y+root.Height { + t.Fatalf("expected child %q to be inset from cycle container, got child %v and root %v", child.ID, child.TopLeft, root.TopLeft) + } + } + for _, edge := range g.Edges { + if !edge.IsCurve { + t.Fatalf("expected cycle edge %q to remain curved", edge.AbsID()) + } + for _, point := range edge.Route { + if point.X <= root.TopLeft.X || point.Y <= root.TopLeft.Y || + point.X >= root.TopLeft.X+root.Width || point.Y >= root.TopLeft.Y+root.Height { + t.Fatalf("expected edge %q point %v to be inset from cycle container %v", edge.AbsID(), point, root.TopLeft) + } + } + } +} + func TestLayoutRoutesCircularArcsTrimmedToBorders(t *testing.T) { g := compileGraph(t, `shape: cycle a -> b -> c -> d -> a diff --git a/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json index d97747f4c0..5648bcd0dc 100644 --- a/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json @@ -19,8 +19,8 @@ "x": 0, "y": 62 }, - "width": 453, - "height": 466, + "width": 513, + "height": 526, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -57,8 +57,8 @@ "id": "four.a", "type": "rectangle", "pos": { - "x": 200, - "y": 62 + "x": 230, + "y": 92 }, "width": 53, "height": 66, @@ -99,8 +99,8 @@ "id": "four.b", "type": "rectangle", "pos": { - "x": 400, - "y": 262 + "x": 430, + "y": 292 }, "width": 53, "height": 66, @@ -141,8 +141,8 @@ "id": "four.c", "type": "rectangle", "pos": { - "x": 200, - "y": 462 + "x": 230, + "y": 492 }, "width": 53, "height": 66, @@ -183,8 +183,8 @@ "id": "four.d", "type": "rectangle", "pos": { - "x": 0, - "y": 262 + "x": 30, + "y": 292 }, "width": 54, "height": 66, @@ -225,11 +225,11 @@ "id": "nonrect", "type": "cycle", "pos": { - "x": 513, + "x": 573, "y": 76 }, - "width": 415, - "height": 439, + "width": 475, + "height": 499, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -266,8 +266,8 @@ "id": "nonrect.a", "type": "oval", "pos": { - "x": 682, - "y": 76 + "x": 772, + "y": 106 }, "width": 77, "height": 77, @@ -308,8 +308,8 @@ "id": "nonrect.b", "type": "hexagon", "pos": { - "x": 868, - "y": 380 + "x": 958, + "y": 410 }, "width": 50, "height": 69, @@ -350,8 +350,8 @@ "id": "nonrect.c", "type": "diamond", "pos": { - "x": 524, - "y": 368 + "x": 614, + "y": 398 }, "width": 46, "height": 92, @@ -392,11 +392,11 @@ "id": "mixed", "type": "cycle", "pos": { - "x": 988, + "x": 1108, "y": 0 }, - "width": 468, - "height": 590, + "width": 528, + "height": 650, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -433,8 +433,8 @@ "id": "mixed.x", "type": "rectangle", "pos": { - "x": 1153, - "y": 0 + "x": 1303, + "y": 30 }, "width": 167, "height": 292, @@ -475,8 +475,8 @@ "id": "mixed.x.p", "type": "rectangle", "pos": { - "x": 1237, - "y": 30 + "x": 1387, + "y": 60 }, "width": 53, "height": 66, @@ -517,8 +517,8 @@ "id": "mixed.x.q", "type": "rectangle", "pos": { - "x": 1183, - "y": 196 + "x": 1333, + "y": 226 }, "width": 54, "height": 66, @@ -559,8 +559,8 @@ "id": "mixed.y", "type": "rectangle", "pos": { - "x": 1402, - "y": 445 + "x": 1552, + "y": 475 }, "width": 54, "height": 66, @@ -601,8 +601,8 @@ "id": "mixed.z", "type": "oval", "pos": { - "x": 1006, - "y": 440 + "x": 1156, + "y": 470 }, "width": 77, "height": 77, @@ -643,11 +643,11 @@ "id": "two", "type": "cycle", "pos": { - "x": 1516, + "x": 1696, "y": 62 }, - "width": 400, - "height": 466, + "width": 460, + "height": 526, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -684,8 +684,8 @@ "id": "two.a", "type": "rectangle", "pos": { - "x": 1689, - "y": 62 + "x": 1899, + "y": 92 }, "width": 53, "height": 66, @@ -726,8 +726,8 @@ "id": "two.b", "type": "rectangle", "pos": { - "x": 1689, - "y": 462 + "x": 1899, + "y": 492 }, "width": 53, "height": 66, @@ -792,20 +792,20 @@ "link": "", "route": [ { - "x": 253.5, - "y": 96.76300048828125 + "x": 283.5, + "y": 126.76300048828125 }, { - "x": 340.5150146484375, - "y": 108.3949966430664 + "x": 370.5150146484375, + "y": 138.39500427246094 }, { - "x": 409.77301025390625, - "y": 175.41299438476562 + "x": 439.77301025390625, + "y": 205.41299438476562 }, { - "x": 424.25799560546875, - "y": 261.9989929199219 + "x": 454.25799560546875, + "y": 291.9989929199219 } ], "isCurve": true, @@ -840,20 +840,20 @@ "link": "", "route": [ { - "x": 424.25799560546875, - "y": 328 + "x": 454.25799560546875, + "y": 358 }, { - "x": 409.77301025390625, - "y": 414.58599853515625 + "x": 439.77301025390625, + "y": 444.58599853515625 }, { - "x": 340.5150146484375, - "y": 481.60400390625 + "x": 370.5150146484375, + "y": 511.60400390625 }, { - "x": 253.5, - "y": 493.2359924316406 + "x": 283.5, + "y": 523.2360229492188 } ], "isCurve": true, @@ -888,20 +888,20 @@ "link": "", "route": [ { - "x": 200.49899291992188, - "y": 493.2359924316406 + "x": 230.49899291992188, + "y": 523.2360229492188 }, { - "x": 113.48400115966797, - "y": 481.60400390625 + "x": 143.48399353027344, + "y": 511.60400390625 }, { - "x": 44.22600173950195, - "y": 414.58599853515625 + "x": 74.22599792480469, + "y": 444.58599853515625 }, { - "x": 29.740999221801758, - "y": 328 + "x": 59.74100112915039, + "y": 358 } ], "isCurve": true, @@ -936,20 +936,20 @@ "link": "", "route": [ { - "x": 29.740999221801758, - "y": 261.9989929199219 + "x": 59.74100112915039, + "y": 291.9989929199219 }, { - "x": 44.22600173950195, - "y": 175.41299438476562 + "x": 74.22599792480469, + "y": 205.41299438476562 }, { - "x": 113.48400115966797, - "y": 108.3949966430664 + "x": 143.48399353027344, + "y": 138.39500427246094 }, { - "x": 200.49899291992188, - "y": 96.76300048828125 + "x": 230.49899291992188, + "y": 126.76300048828125 } ], "isCurve": true, @@ -984,32 +984,32 @@ "link": "", "route": [ { - "x": 759.0819702148438, - "y": 118.20600128173828 + "x": 849.0819702148438, + "y": 148.20599365234375 }, { - "x": 816.791015625, - "y": 129.4709930419922 + "x": 906.791015625, + "y": 159.4709930419922 }, { - "x": 866.6190185546875, - "y": 165.56100463867188 + "x": 956.6190185546875, + "y": 195.56100463867188 }, { - "x": 895.3189697265625, - "y": 216.8800048828125 + "x": 985.3189697265625, + "y": 246.8800048828125 }, { - "x": 924.0180053710938, - "y": 268.1990051269531 + "x": 1014.0180053710938, + "y": 298.1990051269531 }, { - "x": 928.6810302734375, - "y": 329.5469970703125 + "x": 1018.6810302734375, + "y": 359.5469970703125 }, { - "x": 907.760986328125, - "y": 384.5 + "x": 997.760986328125, + "y": 414.5 } ], "isCurve": true, @@ -1044,32 +1044,32 @@ "link": "", "route": [ { - "x": 877.760986328125, - "y": 438.5 + "x": 967.760986328125, + "y": 468.5 }, { - "x": 839.239013671875, - "y": 487.1719970703125 + "x": 929.239013671875, + "y": 517.1719970703125 }, { - "x": 780.3289794921875, - "y": 515.260009765625 + "x": 870.3289794921875, + "y": 545.260009765625 }, { - "x": 718.260986328125, - "y": 514.4840087890625 + "x": 808.260986328125, + "y": 544.4840087890625 }, { - "x": 656.1939697265625, - "y": 513.7080078125 + "x": 746.1939697265625, + "y": 543.7080078125 }, { - "x": 598.0040283203125, - "y": 484.1579895019531 + "x": 688.0040283203125, + "y": 514.1580200195312 }, { - "x": 560.760986328125, - "y": 434.5 + "x": 650.760986328125, + "y": 464.5 } ], "isCurve": true, @@ -1104,32 +1104,32 @@ "link": "", "route": [ { - "x": 535.760986328125, - "y": 390.5 + "x": 625.760986328125, + "y": 420.5 }, { - "x": 513, - "y": 335.0880126953125 + "x": 603, + "y": 365.0880126953125 }, { - "x": 516.27197265625, - "y": 272.3909912109375 + "x": 606.27197265625, + "y": 302.3909912109375 }, { - "x": 544.6820068359375, - "y": 219.6510009765625 + "x": 634.6820068359375, + "y": 249.6510009765625 }, { - "x": 573.0910034179688, - "y": 166.91099548339844 + "x": 663.0910034179688, + "y": 196.91099548339844 }, { - "x": 623.64501953125, - "y": 129.68299865722656 + "x": 713.64501953125, + "y": 159.68299865722656 }, { - "x": 682.4390258789062, - "y": 118.20600128173828 + "x": 772.4390258789062, + "y": 148.20599365234375 } ], "isCurve": true, @@ -1164,32 +1164,32 @@ "link": "", "route": [ { - "x": 907.760986328125, - "y": 384.5 + "x": 997.760986328125, + "y": 414.5 }, { - "x": 928.6810302734375, - "y": 329.5469970703125 + "x": 1018.6810302734375, + "y": 359.5469970703125 }, { - "x": 924.0180053710938, - "y": 268.1990051269531 + "x": 1014.0180053710938, + "y": 298.1990051269531 }, { - "x": 895.3189697265625, - "y": 216.8800048828125 + "x": 985.3189697265625, + "y": 246.8800048828125 }, { - "x": 866.6190185546875, - "y": 165.56100463867188 + "x": 956.6190185546875, + "y": 195.56100463867188 }, { - "x": 816.791015625, - "y": 129.4709930419922 + "x": 906.791015625, + "y": 159.4709930419922 }, { - "x": 759.0819702148438, - "y": 118.20600128173828 + "x": 849.0819702148438, + "y": 148.20599365234375 } ], "isCurve": true, @@ -1224,20 +1224,20 @@ "link": "", "route": [ { - "x": 1242.9300537109375, - "y": 96 + "x": 1392.9300537109375, + "y": 126 }, { - "x": 1217.3299560546875, - "y": 136 + "x": 1367.3299560546875, + "y": 166 }, { - "x": 1210.9300537109375, - "y": 156 + "x": 1360.9300537109375, + "y": 186 }, { - "x": 1210.9300537109375, - "y": 196 + "x": 1360.9300537109375, + "y": 226 } ], "isCurve": true, @@ -1272,20 +1272,20 @@ "link": "", "route": [ { - "x": 1277.9300537109375, - "y": 96 + "x": 1427.9300537109375, + "y": 126 }, { - "x": 1387.4300537109375, - "y": 523.9229736328125 + "x": 1537.4300537109375, + "y": 553.9229736328125 }, { - "x": 1406.030029296875, - "y": 545.9229736328125 + "x": 1556.030029296875, + "y": 575.9229736328125 }, { - "x": 1402.6209716796875, - "y": 455.8840026855469 + "x": 1552.6209716796875, + "y": 485.8840026855469 } ], "isCurve": true, @@ -1320,32 +1320,32 @@ "link": "", "route": [ { - "x": 1406.322998046875, - "y": 511.8840026855469 + "x": 1556.322998046875, + "y": 541.8839721679688 }, { - "x": 1363.9429931640625, - "y": 561.60400390625 + "x": 1513.9429931640625, + "y": 591.60400390625 }, { - "x": 1301.8089599609375, - "y": 590.1240234375 + "x": 1451.8089599609375, + "y": 620.1240234375 }, { - "x": 1236.47900390625, - "y": 589.843994140625 + "x": 1386.47900390625, + "y": 619.843994140625 }, { - "x": 1171.14794921875, - "y": 589.5640258789062 + "x": 1321.14794921875, + "y": 619.5640258789062 }, { - "x": 1109.260986328125, - "y": 560.5120239257812 + "x": 1259.260986328125, + "y": 590.5120239257812 }, { - "x": 1067.31005859375, - "y": 510.42999267578125 + "x": 1217.31005859375, + "y": 540.4299926757812 } ], "isCurve": true, @@ -1380,20 +1380,20 @@ "link": "", "route": [ { - "x": 1028.9549560546875, - "y": 443.99798583984375 + "x": 1178.9549560546875, + "y": 473.99798583984375 }, { - "x": 988, - "y": 331.76800537109375 + "x": 1138, + "y": 361.76800537109375 }, { - "x": 1043.239990234375, - "y": 207.25799560546875 + "x": 1193.239990234375, + "y": 237.25799560546875 }, { - "x": 1153.9300537109375, - "y": 162.3070068359375 + "x": 1303.9300537109375, + "y": 192.3070068359375 } ], "isCurve": true, @@ -1428,32 +1428,32 @@ "link": "", "route": [ { - "x": 1742.5, - "y": 96.76300048828125 + "x": 1952.5, + "y": 126.76300048828125 }, { - "x": 1841.8310546875, - "y": 110.04100036621094 + "x": 2051.8310546875, + "y": 140.04100036621094 }, { - "x": 1916, - "y": 194.78500366210938 + "x": 2126, + "y": 224.78500366210938 }, { - "x": 1916, - "y": 295 + "x": 2126, + "y": 325 }, { - "x": 1916, - "y": 395.2139892578125 + "x": 2126, + "y": 425.2139892578125 }, { - "x": 1841.8310546875, - "y": 479.9580078125 + "x": 2051.8310546875, + "y": 509.9580078125 }, { - "x": 1742.5, - "y": 493.2359924316406 + "x": 1952.5, + "y": 523.2360229492188 } ], "isCurve": true, @@ -1488,32 +1488,32 @@ "link": "", "route": [ { - "x": 1689.4990234375, - "y": 493.2359924316406 + "x": 1899.4990234375, + "y": 523.2360229492188 }, { - "x": 1590.16796875, - "y": 479.9580078125 + "x": 1800.16796875, + "y": 509.9580078125 }, { - "x": 1516, - "y": 395.2139892578125 + "x": 1726, + "y": 425.2139892578125 }, { - "x": 1516, - "y": 295 + "x": 1726, + "y": 325 }, { - "x": 1516, - "y": 194.78500366210938 + "x": 1726, + "y": 224.78500366210938 }, { - "x": 1590.16796875, - "y": 110.04100036621094 + "x": 1800.16796875, + "y": 140.04100036621094 }, { - "x": 1689.4990234375, - "y": 96.76300048828125 + "x": 1899.4990234375, + "y": 126.76300048828125 } ], "isCurve": true, diff --git a/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg index a521f90259..83da95a92f 100644 --- a/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg @@ -1,16 +1,16 @@ -abcdabcxyzabpq - + .d2-795383471 .fill-N1{fill:#0A0F25;} + .d2-795383471 .fill-N4{fill:#CFD2DD;} + .d2-795383471 .fill-N5{fill:#DEE1EB;} + .d2-795383471 .fill-N7{fill:#FFFFFF;} + .d2-795383471 .fill-B1{fill:#0D32B2;} + .d2-795383471 .fill-B4{fill:#E3E9FD;} + .d2-795383471 .fill-B5{fill:#EDF0FD;} + .d2-795383471 .fill-B6{fill:#F7F8FE;} + .d2-795383471 .stroke-B1{stroke:#0D32B2;} + .d2-795383471 .stroke-B2{stroke:#0D32B2;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.light-code{display: block}.dark-code{display: none}]]>abcdabcxyzabpq + \ 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 index ddd043e73a..bccfe47cc3 100644 --- a/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json +++ b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json @@ -19,8 +19,8 @@ "x": 12, "y": 99 }, - "width": 454, - "height": 466, + "width": 514, + "height": 526, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -57,8 +57,8 @@ "id": "four.a", "type": "rectangle", "pos": { - "x": 212, - "y": 99 + "x": 242, + "y": 129 }, "width": 53, "height": 66, @@ -99,8 +99,8 @@ "id": "four.b", "type": "rectangle", "pos": { - "x": 412, - "y": 299 + "x": 442, + "y": 329 }, "width": 53, "height": 66, @@ -141,8 +141,8 @@ "id": "four.c", "type": "rectangle", "pos": { - "x": 212, - "y": 499 + "x": 242, + "y": 529 }, "width": 53, "height": 66, @@ -183,8 +183,8 @@ "id": "four.d", "type": "rectangle", "pos": { - "x": 12, - "y": 299 + "x": 42, + "y": 329 }, "width": 54, "height": 66, @@ -225,11 +225,11 @@ "id": "nonrect", "type": "cycle", "pos": { - "x": 485, + "x": 545, "y": 111 }, - "width": 421, - "height": 441, + "width": 481, + "height": 501, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -266,8 +266,8 @@ "id": "nonrect.a", "type": "oval", "pos": { - "x": 653, - "y": 111 + "x": 743, + "y": 141 }, "width": 80, "height": 77, @@ -308,8 +308,8 @@ "id": "nonrect.b", "type": "hexagon", "pos": { - "x": 826, - "y": 415 + "x": 916, + "y": 445 }, "width": 80, "height": 69, @@ -350,8 +350,8 @@ "id": "nonrect.c", "type": "diamond", "pos": { - "x": 496, - "y": 404 + "x": 586, + "y": 434 }, "width": 46, "height": 92, @@ -392,11 +392,11 @@ "id": "mixed", "type": "cycle", "pos": { - "x": 926, + "x": 1046, "y": 12 }, - "width": 509, - "height": 641, + "width": 569, + "height": 701, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -433,8 +433,8 @@ "id": "mixed.x", "type": "rectangle", "pos": { - "x": 1097, - "y": 12 + "x": 1247, + "y": 42 }, "width": 201, "height": 312, @@ -475,8 +475,8 @@ "id": "mixed.x.p", "type": "rectangle", "pos": { - "x": 1167, - "y": 62 + "x": 1317, + "y": 92 }, "width": 80, "height": 66, @@ -517,8 +517,8 @@ "id": "mixed.x.q", "type": "rectangle", "pos": { - "x": 1147, - "y": 208 + "x": 1297, + "y": 238 }, "width": 54, "height": 66, @@ -559,8 +559,8 @@ "id": "mixed.y", "type": "rectangle", "pos": { - "x": 1380, - "y": 497 + "x": 1530, + "y": 527 }, "width": 54, "height": 66, @@ -601,8 +601,8 @@ "id": "mixed.z", "type": "oval", "pos": { - "x": 949, - "y": 492 + "x": 1099, + "y": 522 }, "width": 77, "height": 77, @@ -643,11 +643,11 @@ "id": "two", "type": "cycle", "pos": { - "x": 1454, + "x": 1634, "y": 99 }, - "width": 400, - "height": 466, + "width": 460, + "height": 526, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -684,8 +684,8 @@ "id": "two.a", "type": "rectangle", "pos": { - "x": 1628, - "y": 99 + "x": 1838, + "y": 129 }, "width": 53, "height": 66, @@ -726,8 +726,8 @@ "id": "two.b", "type": "rectangle", "pos": { - "x": 1628, - "y": 499 + "x": 1838, + "y": 529 }, "width": 53, "height": 66, @@ -792,20 +792,20 @@ "link": "", "route": [ { - "x": 265.5, - "y": 133.8780059814453 + "x": 295.5, + "y": 163.8780059814453 }, { - "x": 352.5150146484375, - "y": 145.50999450683594 + "x": 382.5150146484375, + "y": 175.50999450683594 }, { - "x": 421.77301025390625, - "y": 212.5279998779297 + "x": 451.77301025390625, + "y": 242.5279998779297 }, { - "x": 436.25799560546875, - "y": 299.114990234375 + "x": 466.25799560546875, + "y": 329.114990234375 } ], "isCurve": true, @@ -840,20 +840,20 @@ "link": "", "route": [ { - "x": 436.25799560546875, - "y": 365.114990234375 + "x": 466.25799560546875, + "y": 395.114990234375 }, { - "x": 421.77301025390625, - "y": 451.70098876953125 + "x": 451.77301025390625, + "y": 481.70098876953125 }, { - "x": 352.5150146484375, - "y": 518.718994140625 + "x": 382.5150146484375, + "y": 548.718994140625 }, { - "x": 265.5, - "y": 530.3510131835938 + "x": 295.5, + "y": 560.3510131835938 } ], "isCurve": true, @@ -888,20 +888,20 @@ "link": "", "route": [ { - "x": 212.49899291992188, - "y": 530.3510131835938 + "x": 242.49899291992188, + "y": 560.3510131835938 }, { - "x": 125.48400115966797, - "y": 518.718994140625 + "x": 155.48399353027344, + "y": 548.718994140625 }, { - "x": 56.22600173950195, - "y": 451.70098876953125 + "x": 86.22599792480469, + "y": 481.70098876953125 }, { - "x": 41.74100112915039, - "y": 365.114990234375 + "x": 71.74099731445312, + "y": 395.114990234375 } ], "isCurve": true, @@ -936,20 +936,20 @@ "link": "", "route": [ { - "x": 41.74100112915039, - "y": 299.114990234375 + "x": 71.74099731445312, + "y": 329.114990234375 }, { - "x": 56.22600173950195, - "y": 212.5279998779297 + "x": 86.22599792480469, + "y": 242.5279998779297 }, { - "x": 125.48400115966797, - "y": 145.50999450683594 + "x": 155.48399353027344, + "y": 175.50999450683594 }, { - "x": 212.49899291992188, - "y": 133.8780059814453 + "x": 242.49899291992188, + "y": 163.8780059814453 } ], "isCurve": true, @@ -984,32 +984,32 @@ "link": "", "route": [ { - "x": 732.9439697265625, - "y": 154.35800170898438 + "x": 822.9439697265625, + "y": 184.35800170898438 }, { - "x": 789.5540161132812, - "y": 165.84100341796875 + "x": 879.5540161132812, + "y": 195.84100341796875 }, { - "x": 838.4299926757812, - "y": 201.24400329589844 + "x": 928.4299926757812, + "y": 231.24400329589844 }, { - "x": 866.9940185546875, - "y": 251.44900512695312 + "x": 956.9940185546875, + "y": 281.4490051269531 }, { - "x": 895.5590209960938, - "y": 301.65399169921875 + "x": 985.5590209960938, + "y": 331.65399169921875 }, { - "x": 901.0230102539062, - "y": 361.7569885253906 + "x": 991.0230102539062, + "y": 391.7569885253906 }, { - "x": 882.1610107421875, - "y": 416.35400390625 + "x": 972.1610107421875, + "y": 446.35400390625 } ], "isCurve": true, @@ -1044,32 +1044,32 @@ "link": "", "route": [ { - "x": 844.1610107421875, - "y": 481.35400390625 + "x": 934.1610107421875, + "y": 511.35400390625 }, { - "x": 804.6160278320312, - "y": 527.0809936523438 + "x": 894.6160278320312, + "y": 557.0809936523438 }, { - "x": 746.469970703125, - "y": 552.3759765625 + "x": 836.469970703125, + "y": 582.3759765625 }, { - "x": 686.0540161132812, - "y": 550.2269897460938 + "x": 776.0540161132812, + "y": 580.2269897460938 }, { - "x": 625.6370239257812, - "y": 548.0789794921875 + "x": 715.6370239257812, + "y": 578.0789794921875 }, { - "x": 569.4340209960938, - "y": 518.718017578125 + "x": 659.4340209960938, + "y": 548.718017578125 }, { - "x": 533.1610107421875, - "y": 470.35400390625 + "x": 623.1610107421875, + "y": 500.35400390625 } ], "isCurve": true, @@ -1104,32 +1104,32 @@ "link": "", "route": [ { - "x": 508.1610107421875, - "y": 426.35400390625 + "x": 598.1610107421875, + "y": 456.35400390625 }, { - "x": 485.5, - "y": 371.1830139160156 + "x": 575.5, + "y": 401.1830139160156 }, { - "x": 488.6409912109375, - "y": 308.77801513671875 + "x": 578.6409912109375, + "y": 338.77801513671875 }, { - "x": 516.72998046875, - "y": 256.1629943847656 + "x": 606.72998046875, + "y": 286.1629943847656 }, { - "x": 544.8189697265625, - "y": 203.54800415039062 + "x": 634.8189697265625, + "y": 233.54800415039062 }, { - "x": 594.9249877929688, - "y": 166.21499633789062 + "x": 684.9249877929688, + "y": 196.21499633789062 }, { - "x": 653.3779907226562, - "y": 154.35800170898438 + "x": 743.3779907226562, + "y": 184.35800170898438 } ], "isCurve": true, @@ -1164,32 +1164,32 @@ "link": "", "route": [ { - "x": 882.1610107421875, - "y": 416.35400390625 + "x": 972.1610107421875, + "y": 446.35400390625 }, { - "x": 901.0230102539062, - "y": 361.7569885253906 + "x": 991.0230102539062, + "y": 391.7569885253906 }, { - "x": 895.5590209960938, - "y": 301.65399169921875 + "x": 985.5590209960938, + "y": 331.65399169921875 }, { - "x": 866.9940185546875, - "y": 251.44900512695312 + "x": 956.9940185546875, + "y": 281.4490051269531 }, { - "x": 838.4299926757812, - "y": 201.24400329589844 + "x": 928.4299926757812, + "y": 231.24400329589844 }, { - "x": 789.5540161132812, - "y": 165.84100341796875 + "x": 879.5540161132812, + "y": 195.84100341796875 }, { - "x": 732.9439697265625, - "y": 154.35800170898438 + "x": 822.9439697265625, + "y": 184.35800170898438 } ], "isCurve": true, @@ -1224,20 +1224,20 @@ "link": "", "route": [ { - "x": 1194.623046875, - "y": 128 + "x": 1344.623046875, + "y": 158 }, { - "x": 1194.623046875, - "y": 168 + "x": 1344.623046875, + "y": 198 }, { - "x": 1174.4560546875, - "y": 168 + "x": 1324.4560546875, + "y": 198 }, { - "x": 1174.4560546875, - "y": 208 + "x": 1324.4560546875, + "y": 238 } ], "animated": false, @@ -1271,20 +1271,20 @@ "link": "", "route": [ { - "x": 1221.2889404296875, - "y": 128 + "x": 1371.2889404296875, + "y": 158 }, { - "x": 1333.7889404296875, - "y": 617.989990234375 + "x": 1483.7889404296875, + "y": 647.989990234375 }, { - "x": 1353.9560546875, - "y": 617.989990234375 + "x": 1503.9560546875, + "y": 647.989990234375 }, { - "x": 1407.5260009765625, - "y": 497.9859924316406 + "x": 1557.5260009765625, + "y": 527.9860229492188 } ], "animated": false, @@ -1318,32 +1318,32 @@ "link": "", "route": [ { - "x": 1384.6240234375, - "y": 563.9860229492188 + "x": 1534.6240234375, + "y": 593.9860229492188 }, { - "x": 1338.468017578125, - "y": 619.9349975585938 + "x": 1488.468017578125, + "y": 649.9349975585938 }, { - "x": 1269.6490478515625, - "y": 652.22998046875 + "x": 1419.6490478515625, + "y": 682.22998046875 }, { - "x": 1197.1190185546875, - "y": 651.97900390625 + "x": 1347.1190185546875, + "y": 681.97900390625 }, { - "x": 1124.5889892578125, - "y": 651.72900390625 + "x": 1274.5889892578125, + "y": 681.72900390625 }, { - "x": 1055.9959716796875, - "y": 618.9580078125 + "x": 1205.9959716796875, + "y": 648.9580078125 }, { - "x": 1010.2310180664062, - "y": 562.68798828125 + "x": 1160.23095703125, + "y": 592.68798828125 } ], "isCurve": true, @@ -1378,20 +1378,20 @@ "link": "", "route": [ { - "x": 971.85302734375, - "y": 496.21600341796875 + "x": 1121.85302734375, + "y": 526.2160034179688 }, { - "x": 926.3660278320312, - "y": 376.9519958496094 + "x": 1076.365966796875, + "y": 406.9519958496094 }, { - "x": 981.3410034179688, - "y": 242.86700439453125 + "x": 1131.3409423828125, + "y": 272.86700439453125 }, { - "x": 1097.4560546875, - "y": 189.8560028076172 + "x": 1247.4560546875, + "y": 219.8560028076172 } ], "isCurve": true, @@ -1426,32 +1426,32 @@ "link": "", "route": [ { - "x": 1681.0260009765625, - "y": 133.8780059814453 + "x": 1891.0260009765625, + "y": 163.8780059814453 }, { - "x": 1780.3570556640625, - "y": 147.15699768066406 + "x": 1990.3570556640625, + "y": 177.15699768066406 }, { - "x": 1854.5260009765625, - "y": 231.89999389648438 + "x": 2064.52587890625, + "y": 261.8999938964844 }, { - "x": 1854.5260009765625, - "y": 332.114990234375 + "x": 2064.52587890625, + "y": 362.114990234375 }, { - "x": 1854.5260009765625, - "y": 432.3299865722656 + "x": 2064.52587890625, + "y": 462.3299865722656 }, { - "x": 1780.3570556640625, - "y": 517.072998046875 + "x": 1990.3570556640625, + "y": 547.072998046875 }, { - "x": 1681.0260009765625, - "y": 530.3510131835938 + "x": 1891.0260009765625, + "y": 560.3510131835938 } ], "isCurve": true, @@ -1486,32 +1486,32 @@ "link": "", "route": [ { - "x": 1628.0260009765625, - "y": 530.3510131835938 + "x": 1838.0260009765625, + "y": 560.3510131835938 }, { - "x": 1528.6949462890625, - "y": 517.072998046875 + "x": 1738.6949462890625, + "y": 547.072998046875 }, { - "x": 1454.5260009765625, - "y": 432.3299865722656 + "x": 1664.5260009765625, + "y": 462.3299865722656 }, { - "x": 1454.5260009765625, - "y": 332.114990234375 + "x": 1664.5260009765625, + "y": 362.114990234375 }, { - "x": 1454.5260009765625, - "y": 231.89999389648438 + "x": 1664.5260009765625, + "y": 261.8999938964844 }, { - "x": 1528.6949462890625, - "y": 147.15699768066406 + "x": 1738.6949462890625, + "y": 177.15699768066406 }, { - "x": 1628.0260009765625, - "y": 133.8780059814453 + "x": 1838.0260009765625, + "y": 163.8780059814453 } ], "isCurve": true, diff --git a/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg index 0502fbe56d..d7adc6a814 100644 --- a/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg +++ b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg @@ -1,16 +1,16 @@ -abcdabcxyzabpq - + .d2-700540364 .fill-N1{fill:#0A0F25;} + .d2-700540364 .fill-N4{fill:#CFD2DD;} + .d2-700540364 .fill-N5{fill:#DEE1EB;} + .d2-700540364 .fill-N7{fill:#FFFFFF;} + .d2-700540364 .fill-B1{fill:#0D32B2;} + .d2-700540364 .fill-B4{fill:#E3E9FD;} + .d2-700540364 .fill-B5{fill:#EDF0FD;} + .d2-700540364 .fill-B6{fill:#F7F8FE;} + .d2-700540364 .stroke-B1{stroke:#0D32B2;} + .d2-700540364 .stroke-B2{stroke:#0D32B2;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.light-code{display: block}.dark-code{display: none}]]>abcdabcxyzabpq + \ 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 index 52d9ba2a41..813ddcfcfe 100644 --- a/e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json +++ b/e2etests/testdata/txtar/cycle-diagram/tala/board.exp.json @@ -19,8 +19,8 @@ "x": 0, "y": 0 }, - "width": 453, - "height": 466, + "width": 513, + "height": 526, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -57,8 +57,8 @@ "id": "four.a", "type": "rectangle", "pos": { - "x": 200, - "y": 0 + "x": 230, + "y": 30 }, "width": 53, "height": 66, @@ -99,8 +99,8 @@ "id": "four.b", "type": "rectangle", "pos": { - "x": 400, - "y": 200 + "x": 430, + "y": 230 }, "width": 53, "height": 66, @@ -141,8 +141,8 @@ "id": "four.c", "type": "rectangle", "pos": { - "x": 200, - "y": 400 + "x": 230, + "y": 430 }, "width": 53, "height": 66, @@ -183,8 +183,8 @@ "id": "four.d", "type": "rectangle", "pos": { - "x": 0, - "y": 200 + "x": 30, + "y": 230 }, "width": 54, "height": 66, @@ -225,11 +225,11 @@ "id": "nonrect", "type": "cycle", "pos": { - "x": 473, + "x": 533, "y": 0 }, - "width": 440, - "height": 464, + "width": 500, + "height": 524, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -266,8 +266,8 @@ "id": "nonrect.a", "type": "oval", "pos": { - "x": 619, - "y": 0 + "x": 709, + "y": 30 }, "width": 120, "height": 120, @@ -308,8 +308,8 @@ "id": "nonrect.b", "type": "hexagon", "pos": { - "x": 792, - "y": 299 + "x": 882, + "y": 329 }, "width": 121, "height": 121, @@ -350,8 +350,8 @@ "id": "nonrect.c", "type": "diamond", "pos": { - "x": 483, - "y": 314 + "x": 573, + "y": 344 }, "width": 46, "height": 92, @@ -393,10 +393,10 @@ "type": "cycle", "pos": { "x": 0, - "y": 486 + "y": 546 }, - "width": 499, - "height": 633, + "width": 559, + "height": 693, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -433,8 +433,8 @@ "id": "mixed.x", "type": "rectangle", "pos": { - "x": 180, - "y": 486 + "x": 210, + "y": 576 }, "width": 174, "height": 318, @@ -475,8 +475,8 @@ "id": "mixed.x.p", "type": "rectangle", "pos": { - "x": 241, - "y": 546 + "x": 271, + "y": 636 }, "width": 53, "height": 66, @@ -517,8 +517,8 @@ "id": "mixed.x.q", "type": "rectangle", "pos": { - "x": 240, - "y": 678 + "x": 270, + "y": 768 }, "width": 54, "height": 66, @@ -559,8 +559,8 @@ "id": "mixed.y", "type": "rectangle", "pos": { - "x": 445, - "y": 967 + "x": 475, + "y": 1057 }, "width": 54, "height": 66, @@ -601,8 +601,8 @@ "id": "mixed.z", "type": "oval", "pos": { - "x": 23, - "y": 961 + "x": 53, + "y": 1051 }, "width": 77, "height": 77, @@ -643,11 +643,11 @@ "id": "two", "type": "cycle", "pos": { - "x": 520, - "y": 486 + "x": 580, + "y": 546 }, - "width": 400, - "height": 520, + "width": 460, + "height": 580, "opacity": 1, "strokeDash": 0, "strokeWidth": 2, @@ -684,8 +684,8 @@ "id": "two.a", "type": "rectangle", "pos": { - "x": 660, - "y": 486 + "x": 750, + "y": 576 }, "width": 120, "height": 120, @@ -726,8 +726,8 @@ "id": "two.b", "type": "rectangle", "pos": { - "x": 660, - "y": 886 + "x": 750, + "y": 976 }, "width": 120, "height": 120, @@ -792,20 +792,20 @@ "link": "", "route": [ { - "x": 253.5, - "y": 34.76300048828125 + "x": 283.5, + "y": 64.76300048828125 }, { - "x": 340.5150146484375, - "y": 46.39500045776367 + "x": 370.5150146484375, + "y": 76.3949966430664 }, { - "x": 409.77301025390625, - "y": 113.41300201416016 + "x": 439.77301025390625, + "y": 143.41299438476562 }, { - "x": 424.25799560546875, - "y": 199.99899291992188 + "x": 454.25799560546875, + "y": 229.99899291992188 } ], "isCurve": true, @@ -840,20 +840,20 @@ "link": "", "route": [ { - "x": 424.25799560546875, - "y": 266 + "x": 454.25799560546875, + "y": 296 }, { - "x": 409.77301025390625, - "y": 352.58599853515625 + "x": 439.77301025390625, + "y": 382.58599853515625 }, { - "x": 340.5150146484375, - "y": 419.60400390625 + "x": 370.5150146484375, + "y": 449.60400390625 }, { - "x": 253.5, - "y": 431.2359924316406 + "x": 283.5, + "y": 461.2359924316406 } ], "isCurve": true, @@ -888,20 +888,20 @@ "link": "", "route": [ { - "x": 200.49899291992188, - "y": 431.2359924316406 + "x": 230.49899291992188, + "y": 461.2359924316406 }, { - "x": 113.48400115966797, - "y": 419.60400390625 + "x": 143.48399353027344, + "y": 449.60400390625 }, { - "x": 44.22600173950195, - "y": 352.58599853515625 + "x": 74.22599792480469, + "y": 382.58599853515625 }, { - "x": 29.740999221801758, - "y": 266 + "x": 59.74100112915039, + "y": 296 } ], "isCurve": true, @@ -936,20 +936,20 @@ "link": "", "route": [ { - "x": 29.740999221801758, - "y": 199.99899291992188 + "x": 59.74100112915039, + "y": 229.99899291992188 }, { - "x": 44.22600173950195, - "y": 113.41300201416016 + "x": 74.22599792480469, + "y": 143.41299438476562 }, { - "x": 113.48400115966797, - "y": 46.39500045776367 + "x": 143.48399353027344, + "y": 76.3949966430664 }, { - "x": 200.49899291992188, - "y": 34.76300048828125 + "x": 230.49899291992188, + "y": 64.76300048828125 } ], "isCurve": true, @@ -984,20 +984,20 @@ "link": "", "route": [ { - "x": 739.1439819335938, - "y": 69.00499725341797 + "x": 829.1439819335938, + "y": 99.00499725341797 }, { - "x": 837.260009765625, - "y": 99.4729995727539 + "x": 927.260009765625, + "y": 129.47300720214844 }, { - "x": 896.3280029296875, - "y": 199.3280029296875 + "x": 986.3280029296875, + "y": 229.3280029296875 }, { - "x": 875.822998046875, - "y": 300 + "x": 965.822998046875, + "y": 330 } ], "isCurve": true, @@ -1032,32 +1032,32 @@ "link": "", "route": [ { - "x": 815.822998046875, - "y": 407 + "x": 905.822998046875, + "y": 437 }, { - "x": 774.0809936523438, - "y": 445.260009765625 + "x": 864.0809936523438, + "y": 475.260009765625 }, { - "x": 718.166015625, - "y": 464.29400634765625 + "x": 808.166015625, + "y": 494.29400634765625 }, { - "x": 661.7730102539062, - "y": 459.1830139160156 + "x": 751.7730102539062, + "y": 489.1830139160156 }, { - "x": 605.3809814453125, - "y": 454.072998046875 + "x": 695.3809814453125, + "y": 484.072998046875 }, { - "x": 553.7969970703125, - "y": 425.2980041503906 + "x": 643.7969970703125, + "y": 455.2980041503906 }, { - "x": 519.822998046875, - "y": 380 + "x": 609.822998046875, + "y": 410 } ], "isCurve": true, @@ -1092,32 +1092,32 @@ "link": "", "route": [ { - "x": 494.822998046875, - "y": 336 + "x": 584.822998046875, + "y": 366 }, { - "x": 473.5, - "y": 284.08599853515625 + "x": 563.5, + "y": 314.08599853515625 }, { - "x": 474.96600341796875, - "y": 225.6020050048828 + "x": 564.9660034179688, + "y": 255.6020050048828 }, { - "x": 498.86700439453125, - "y": 174.82400512695312 + "x": 588.8670043945312, + "y": 204.82400512695312 }, { - "x": 522.7680053710938, - "y": 124.0459976196289 + "x": 612.7680053710938, + "y": 154.04600524902344 }, { - "x": 566.905029296875, - "y": 85.64600372314453 + "x": 656.905029296875, + "y": 115.64600372314453 }, { - "x": 620.5029907226562, - "y": 69.00499725341797 + "x": 710.5029907226562, + "y": 99.00499725341797 } ], "isCurve": true, @@ -1152,20 +1152,20 @@ "link": "", "route": [ { - "x": 875.822998046875, - "y": 300 + "x": 965.822998046875, + "y": 330 }, { - "x": 896.3280029296875, - "y": 199.3280029296875 + "x": 986.3280029296875, + "y": 229.3280029296875 }, { - "x": 837.260009765625, - "y": 99.4729995727539 + "x": 927.260009765625, + "y": 129.47300720214844 }, { - "x": 739.1439819335938, - "y": 69.00499725341797 + "x": 829.1439819335938, + "y": 99.00499725341797 } ], "isCurve": true, @@ -1200,12 +1200,12 @@ "link": "", "route": [ { - "x": 267.4649963378906, - "y": 612 + "x": 297.4649963378906, + "y": 702 }, { - "x": 267.4649963378906, - "y": 678 + "x": 297.4649963378906, + "y": 768 } ], "animated": false, @@ -1239,12 +1239,12 @@ "link": "", "route": [ { - "x": 294.4649963378906, - "y": 579 + "x": 324.4649963378906, + "y": 669 }, { - "x": 445.7099914550781, - "y": 1000.4949951171875 + "x": 475.7099914550781, + "y": 1090.4949951171875 } ], "animated": false, @@ -1278,32 +1278,32 @@ "link": "", "route": [ { - "x": 449.7170104980469, - "y": 1033.4949951171875 + "x": 479.7170104980469, + "y": 1123.4949951171875 }, { - "x": 404.49700927734375, - "y": 1087.89501953125 + "x": 434.49700927734375, + "y": 1177.89501953125 }, { - "x": 337.34100341796875, - "y": 1119.25 + "x": 367.34100341796875, + "y": 1209.25 }, { - "x": 266.60198974609375, - "y": 1118.9930419921875 + "x": 296.60198974609375, + "y": 1208.9930419921875 }, { - "x": 195.86199951171875, - "y": 1118.7349853515625 + "x": 225.86199951171875, + "y": 1208.7349853515625 }, { - "x": 128.93600463867188, - "y": 1086.8909912109375 + "x": 158.93600463867188, + "y": 1176.8909912109375 }, { - "x": 84.12000274658203, - "y": 1032.1590576171875 + "x": 114.12000274658203, + "y": 1122.1590576171875 } ], "isCurve": true, @@ -1338,20 +1338,20 @@ "link": "", "route": [ { - "x": 45.74800109863281, - "y": 965.697021484375 + "x": 75.74800109863281, + "y": 1055.697021484375 }, { - "x": 0, - "y": 844.5339965820312 + "x": 30, + "y": 934.5339965820312 }, { - "x": 59.994998931884766, - "y": 709.0880126953125 + "x": 89.99500274658203, + "y": 799.0880126953125 }, { - "x": 180.46499633789062, - "y": 661.5460205078125 + "x": 210.46499633789062, + "y": 751.5460205078125 } ], "isCurve": true, @@ -1386,32 +1386,32 @@ "link": "", "route": [ { - "x": 780, - "y": 555.2119750976562 + "x": 870, + "y": 645.2119750976562 }, { - "x": 863.3200073242188, - "y": 581.4149780273438 + "x": 953.3200073242188, + "y": 671.4149780273438 }, { - "x": 920, - "y": 658.656005859375 + "x": 1010, + "y": 748.656005859375 }, { - "x": 920, - "y": 746 + "x": 1010, + "y": 836 }, { - "x": 920, - "y": 833.343017578125 + "x": 1010, + "y": 923.343017578125 }, { - "x": 863.3200073242188, - "y": 910.583984375 + "x": 953.3200073242188, + "y": 1000.583984375 }, { - "x": 780, - "y": 936.7869873046875 + "x": 870, + "y": 1026.7869873046875 } ], "isCurve": true, @@ -1446,32 +1446,32 @@ "link": "", "route": [ { - "x": 659.9990234375, - "y": 936.7869873046875 + "x": 749.9990234375, + "y": 1026.7869873046875 }, { - "x": 576.6790161132812, - "y": 910.583984375 + "x": 666.6790161132812, + "y": 1000.583984375 }, { - "x": 520, - "y": 833.343017578125 + "x": 610, + "y": 923.343017578125 }, { - "x": 520, - "y": 746 + "x": 610, + "y": 836 }, { - "x": 520, - "y": 658.656005859375 + "x": 610, + "y": 748.656005859375 }, { - "x": 576.6790161132812, - "y": 581.4149780273438 + "x": 666.6790161132812, + "y": 671.4149780273438 }, { - "x": 659.9990234375, - "y": 555.2119750976562 + "x": 749.9990234375, + "y": 645.2119750976562 } ], "isCurve": true, diff --git a/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg b/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg index d4e4db7928..adac8f79f3 100644 --- a/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg +++ b/e2etests/testdata/txtar/cycle-diagram/tala/sketch.exp.svg @@ -1,16 +1,16 @@ -abcdabcxyzabpq - + .d2-2701627539 .fill-N1{fill:#0A0F25;} + .d2-2701627539 .fill-N4{fill:#CFD2DD;} + .d2-2701627539 .fill-N5{fill:#DEE1EB;} + .d2-2701627539 .fill-N7{fill:#FFFFFF;} + .d2-2701627539 .fill-B1{fill:#0D32B2;} + .d2-2701627539 .fill-B4{fill:#E3E9FD;} + .d2-2701627539 .fill-B5{fill:#EDF0FD;} + .d2-2701627539 .fill-B6{fill:#F7F8FE;} + .d2-2701627539 .stroke-B1{stroke:#0D32B2;} + .d2-2701627539 .stroke-B2{stroke:#0D32B2;}.appendix text.text{fill:#0A0F25}.md{--color-fg-default:#0A0F25;--color-fg-muted:#676C7E;--color-fg-subtle:#9499AB;--color-canvas-default:#FFFFFF;--color-canvas-subtle:#EEF1F8;--color-border-default:#0D32B2;--color-border-muted:#0D32B2;--color-neutral-muted:#EEF1F8;--color-accent-fg:#0D32B2;--color-accent-emphasis:#0D32B2;--color-attention-subtle:#676C7E;--color-danger-fg:red;}.light-code{display: block}.dark-code{display: none}]]>abcdabcxyzabpq + \ No newline at end of file