From 8893981749817b64745ed0a13471376c1182416c Mon Sep 17 00:00:00 2001 From: Mayank77maruti Date: Fri, 21 Feb 2025 16:34:23 +0000 Subject: [PATCH] initial push --- d2graph/cyclediagram.go | 7 + d2layouts/d2cycle/layout.go | 242 +++ d2layouts/d2layouts.go | 10 + d2renderers/d2svg/d2svg.go | 90 +- d2target/d2target.go | 3 + .../txtar/cycle-diagram/dagre/board.exp.json | 1495 +++++++++++++++++ .../txtar/cycle-diagram/dagre/sketch.exp.svg | 103 ++ .../txtar/cycle-diagram/elk/board.exp.json | 1495 +++++++++++++++++ .../txtar/cycle-diagram/elk/sketch.exp.svg | 103 ++ e2etests/txtar.txt | 14 + lib/geo/point.go | 8 + 11 files changed, 3550 insertions(+), 20 deletions(-) create mode 100644 d2graph/cyclediagram.go create mode 100644 d2layouts/d2cycle/layout.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 diff --git a/d2graph/cyclediagram.go b/d2graph/cyclediagram.go new file mode 100644 index 000000000..1014dd1e8 --- /dev/null +++ b/d2graph/cyclediagram.go @@ -0,0 +1,7 @@ +package d2graph + +import "oss.terrastruct.com/d2/d2target" + +func (obj *Object) IsCycleDiagram() bool { + return obj != nil && obj.Shape.Value == d2target.ShapeCycleDiagram +} \ No newline at end of file diff --git a/d2layouts/d2cycle/layout.go b/d2layouts/d2cycle/layout.go new file mode 100644 index 000000000..db489bf95 --- /dev/null +++ b/d2layouts/d2cycle/layout.go @@ -0,0 +1,242 @@ +package d2cycle + +import ( + "context" + "math" + + "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/lib/geo" + "oss.terrastruct.com/d2/lib/label" + "oss.terrastruct.com/util-go/go2" +) + +const ( + MIN_RADIUS = 200 + PADDING = 20 + MIN_SEGMENT_LEN = 10 + ARC_STEPS = 30 // high resolution for smooth arcs +) + +// Layout arranges nodes in a circle and routes edges with properly clipped arcs +func Layout(ctx context.Context, g *d2graph.Graph, layout d2graph.LayoutGraph) error { + objects := g.Root.ChildrenArray + if len(objects) == 0 { + return nil + } + + // Position labels and icons first + for _, obj := range g.Objects { + positionLabelsIcons(obj) + } + + // Calculate layout parameters + nodeCircleRadius := calculateRadius(objects) + maxNodeSize := 0.0 + for _, obj := range objects { + size := math.Max(obj.Width, obj.Height) + maxNodeSize = math.Max(maxNodeSize, size) + } + + // Position nodes in circle + positionObjects(objects, nodeCircleRadius) + + // Create properly clipped edge arcs + for _, edge := range g.Edges { + createCircularArc(edge, nodeCircleRadius, maxNodeSize) + } + + return nil +} + +func calculateRadius(objects []*d2graph.Object) float64 { + numObjects := float64(len(objects)) + maxSize := 0.0 + for _, obj := range objects { + size := math.Max(obj.Width, obj.Height) + maxSize = math.Max(maxSize, size) + } + minRadius := (maxSize/2 + PADDING) / math.Sin(math.Pi/numObjects) + return math.Max(minRadius, MIN_RADIUS) +} + +func positionObjects(objects []*d2graph.Object, radius float64) { + numObjects := float64(len(objects)) + angleOffset := -math.Pi / 2 // Start at top + + for i, obj := range objects { + angle := angleOffset + (2*math.Pi*float64(i))/numObjects + x := radius * math.Cos(angle) + y := radius * math.Sin(angle) + + // Center object at calculated position + obj.TopLeft = geo.NewPoint( + x-obj.Width/2, + y-obj.Height/2, + ) + } +} + +func createCircularArc(edge *d2graph.Edge, nodeCircleRadius, maxNodeSize float64) { + if edge.Src == nil || edge.Dst == nil { + return + } + + srcCenter := edge.Src.Center() + dstCenter := edge.Dst.Center() + + // Calculate arc radius outside node circle + arcRadius := nodeCircleRadius + maxNodeSize/2 + PADDING + + // Calculate angles for arc endpoints + srcAngle := math.Atan2(srcCenter.Y, srcCenter.X) + dstAngle := math.Atan2(dstCenter.Y, dstCenter.X) + if dstAngle < srcAngle { + dstAngle += 2 * math.Pi + } + + // Generate arc path points + path := make([]*geo.Point, 0, ARC_STEPS+1) + for i := 0; i <= ARC_STEPS; i++ { + t := float64(i) / ARC_STEPS + angle := srcAngle + t*(dstAngle-srcAngle) + x := arcRadius * math.Cos(angle) + y := arcRadius * math.Sin(angle) + path = append(path, geo.NewPoint(x, y)) + } + + // Set exact endpoints (will be clipped later) + path[0] = srcCenter + path[len(path)-1] = dstCenter + + // Clip path to node borders + edge.Route = path + startIndex, endIndex := edge.TraceToShape(edge.Route, 0, len(edge.Route)-1) + if startIndex < endIndex { + edge.Route = edge.Route[startIndex : endIndex+1] + } + edge.IsCurve = true +} + +// clampPointOutsideBox walks forward from 'startIdx' until the path segment +// leaves the bounding box. Then it sets path[startIdx] to the intersection. +// If we never find it, we return (startIdx, path[startIdx]) meaning we can't clamp. +func clampPointOutsideBox(box *geo.Box, path []*geo.Point, startIdx int) (int, *geo.Point) { + if startIdx >= len(path)-1 { + return startIdx, path[startIdx] + } + // If path[startIdx] is outside, no clamp needed + if !boxContains(box, path[startIdx]) { + return startIdx, path[startIdx] + } + + // Walk forward looking for outside + for i := startIdx + 1; i < len(path); i++ { + insideNext := boxContains(box, path[i]) + if insideNext { + // still inside -> keep going + continue + } + // crossing from inside to outside between path[i-1], path[i] + seg := geo.NewSegment(path[i-1], path[i]) + inters := boxIntersections(box, *seg) + if len(inters) > 0 { + // use first intersection + return i, inters[0] + } + // fallback => no intersection found + return i, path[i] + } + // entire remainder is inside, so we can't clamp + // Just return the end + last := len(path) - 1 + return last, path[last] +} + +// clampPointOutsideBoxReverse scans backward from endIdx while path[j] is in the box. +// Once we find crossing (outside→inside), we return (j, intersection). +func clampPointOutsideBoxReverse(box *geo.Box, path []*geo.Point, endIdx int) (int, *geo.Point) { + if endIdx <= 0 { + return endIdx, path[endIdx] + } + if !boxContains(box, path[endIdx]) { + // already outside + return endIdx, path[endIdx] + } + + for j := endIdx - 1; j >= 0; j-- { + if boxContains(box, path[j]) { + continue + } + // crossing from outside -> inside between path[j], path[j+1] + seg := geo.NewSegment(path[j], path[j+1]) + inters := boxIntersections(box, *seg) + if len(inters) > 0 { + return j, inters[0] + } + return j, path[j] + } + + // entire path inside + return 0, path[0] +} + +// Helper if your geo.Box doesn’t implement Contains() +func boxContains(b *geo.Box, p *geo.Point) bool { + // typical bounding-box check + return p.X >= b.TopLeft.X && + p.X <= b.TopLeft.X+b.Width && + p.Y >= b.TopLeft.Y && + p.Y <= b.TopLeft.Y+b.Height +} + +// Helper if your geo.Box doesn’t implement Intersections(geo.Segment) yet +func boxIntersections(b *geo.Box, seg geo.Segment) []*geo.Point { + // We'll assume d2's standard geo.Box has a built-in Intersections(*Segment) method. + // If not, implement manually. For example, checking each of the 4 edges: + // left, right, top, bottom + // For simplicity, if you do have b.Intersections(...) you can just do: + // return b.Intersections(seg) + return b.Intersections(seg) + // If you don't have that, you'd code the line-rect intersection yourself. +} + +// positionLabelsIcons is basically your logic that sets default label/icon positions if needed +func positionLabelsIcons(obj *d2graph.Object) { + // If there's an icon but no icon position, give it a default + 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 there's a label but no label position, give it a default + 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 the label is bigger than the shape, fallback to outside positions + 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()) + } + } + } +} \ No newline at end of file diff --git a/d2layouts/d2layouts.go b/d2layouts/d2layouts.go index 87874a6b4..d4dc1ad81 100644 --- a/d2layouts/d2layouts.go +++ b/d2layouts/d2layouts.go @@ -9,6 +9,7 @@ import ( "strings" "oss.terrastruct.com/d2/d2graph" + "oss.terrastruct.com/d2/d2layouts/d2cycle" "oss.terrastruct.com/d2/d2layouts/d2grid" "oss.terrastruct.com/d2/d2layouts/d2near" "oss.terrastruct.com/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 { @@ -260,6 +262,12 @@ func LayoutNested(ctx context.Context, g *d2graph.Graph, graphInfo GraphInfo, co if err != nil { return err } + case CycleDiagram: + log.Debug(ctx, "layout sequence", 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) @@ -360,6 +368,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/d2svg/d2svg.go b/d2renderers/d2svg/d2svg.go index 153b7b345..a42ee3681 100644 --- a/d2renderers/d2svg/d2svg.go +++ b/d2renderers/d2svg/d2svg.go @@ -452,37 +452,78 @@ func getArrowheadAdjustments(connection d2target.Connection, idToShape map[strin func pathData(connection d2target.Connection, srcAdj, dstAdj *geo.Point) string { var path []string route := connection.Route + if len(route) == 0 { + return "" + } + // Move command to start path = append(path, fmt.Sprintf("M %f %f", route[0].X+srcAdj.X, route[0].Y+srcAdj.Y, )) if connection.IsCurve { + // If we don't have enough points to do triple-step, handle small fallback + if len(route) < 3 { + // If only 1 or 2 points in route, just draw lines + for _, p := range route[1:] { + path = append(path, fmt.Sprintf("L %f %f", + p.X+dstAdj.X, p.Y+dstAdj.Y, + )) + } + return strings.Join(path, " ") + } + i := 1 - for ; i < len(route)-3; i += 3 { + // Process triple curves in steps of 3 + for ; i+2 < len(route)-1; i += 3 { path = append(path, fmt.Sprintf("C %f %f %f %f %f %f", route[i].X, route[i].Y, route[i+1].X, route[i+1].Y, route[i+2].X, route[i+2].Y, )) } - // final curve target adjustment - path = append(path, fmt.Sprintf("C %f %f %f %f %f %f", - route[i].X, route[i].Y, - route[i+1].X, route[i+1].Y, - route[i+2].X+dstAdj.X, - route[i+2].Y+dstAdj.Y, - )) + + // Now handle the “final” curve to last point + // Make sure i+2 is still within range + if i+2 < len(route) { + // last triple + path = append(path, fmt.Sprintf("C %f %f %f %f %f %f", + route[i].X, route[i].Y, + route[i+1].X, route[i+1].Y, + route[i+2].X+dstAdj.X, // final point plus dst adjustment + route[i+2].Y+dstAdj.Y, + )) + } else if i+1 < len(route) { + // We have i+1 but not i+2 => do a simpler final curve or line + path = append(path, fmt.Sprintf("C %f %f %f %f %f %f", + route[i].X, route[i].Y, + route[i].X, route[i].Y, // repeated for control + route[i+1].X+dstAdj.X, + route[i+1].Y+dstAdj.Y, + )) + } else { + // We have no final triple => do nothing or fallback line + } } else { + // Not a curve => the "rounded corner" logic for i := 1; i < len(route)-1; i++ { prevSource := route[i-1] prevTarget := route[i] currTarget := route[i+1] + + // Make sure i+1 is valid + if i+1 >= len(route) { + break + } + prevVector := prevSource.VectorTo(prevTarget) currVector := prevTarget.VectorTo(currTarget) - dist := geo.EuclideanDistance(prevTarget.X, prevTarget.Y, currTarget.X, currTarget.Y) + dist := geo.EuclideanDistance( + prevTarget.X, prevTarget.Y, + currTarget.X, currTarget.Y, + ) connectionBorderRadius := connection.BorderRadius units := math.Min(connectionBorderRadius, dist/2) @@ -490,20 +531,26 @@ func pathData(connection d2target.Connection, srcAdj, dstAdj *geo.Point) string prevTranslations := prevVector.Unit().Multiply(units).ToPoint() currTranslations := currVector.Unit().Multiply(units).ToPoint() + // Move to corner with "L" path = append(path, fmt.Sprintf("L %f %f", prevTarget.X-prevTranslations.X, prevTarget.Y-prevTranslations.Y, )) - // If the segment length is too small, instead of drawing 2 arcs, just skip this segment and bezier curve to the next one if units < connectionBorderRadius && i < len(route)-2 { + // Next checks i+2 => ensure it’s in range + if i+2 >= len(route) { + // can't do nextTarget => break or do fallback + continue + } nextTarget := route[i+2] - nextVector := geo.NewVector(nextTarget.X-currTarget.X, nextTarget.Y-currTarget.Y) - i++ + nextVector := geo.NewVector( + nextTarget.X-currTarget.X, + nextTarget.Y-currTarget.Y, + ) + i++ // skip next point nextTranslations := nextVector.Unit().Multiply(units).ToPoint() - // These 2 bezier control points aren't just at the corner -- they are reflected at the corner, which causes the curve to be ~tangent to the corner, - // which matches how the two arcs look path = append(path, fmt.Sprintf("C %f %f %f %f %f %f", // Control point prevTarget.X+prevTranslations.X, @@ -511,7 +558,7 @@ func pathData(connection d2target.Connection, srcAdj, dstAdj *geo.Point) string // Control point currTarget.X-nextTranslations.X, currTarget.Y-nextTranslations.Y, - // Where curve ends + // End currTarget.X+nextTranslations.X, currTarget.Y+nextTranslations.Y, )) @@ -525,11 +572,14 @@ func pathData(connection d2target.Connection, srcAdj, dstAdj *geo.Point) string } } - lastPoint := route[len(route)-1] - path = append(path, fmt.Sprintf("L %f %f", - lastPoint.X+dstAdj.X, - lastPoint.Y+dstAdj.Y, - )) + // Finally, draw a line to the last route point + dst offset + if len(route) > 1 { + lastPoint := route[len(route)-1] + path = append(path, fmt.Sprintf("L %f %f", + lastPoint.X+dstAdj.X, + lastPoint.Y+dstAdj.Y, + )) + } } return strings.Join(path, " ") diff --git a/d2target/d2target.go b/d2target/d2target.go index 266e0f184..68e7eb331 100644 --- a/d2target/d2target.go +++ b/d2target/d2target.go @@ -942,6 +942,7 @@ const ( ShapeSQLTable = "sql_table" ShapeImage = "image" ShapeSequenceDiagram = "sequence_diagram" + ShapeCycleDiagram = "cycle" ShapeHierarchy = "hierarchy" ) @@ -969,6 +970,7 @@ var Shapes = []string{ ShapeSQLTable, ShapeImage, ShapeSequenceDiagram, + ShapeCycleDiagram, ShapeHierarchy, } @@ -1037,6 +1039,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 000000000..575de3712 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/board.exp.json @@ -0,0 +1,1495 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "1", + "type": "cycle", + "pos": { + "x": 0, + "y": 0 + }, + "width": 453, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "1.a", + "type": "rectangle", + "pos": { + "x": -26, + "y": -233 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "1.b", + "type": "rectangle", + "pos": { + "x": 173, + "y": -33 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "1.c", + "type": "rectangle", + "pos": { + "x": -26, + "y": 167 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "1.d", + "type": "rectangle", + "pos": { + "x": -227, + "y": -32 + }, + "width": 54, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "d", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 9, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2", + "type": "cycle", + "pos": { + "x": 513, + "y": 50 + }, + "width": 399, + "height": 366, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "2.a", + "type": "rectangle", + "pos": { + "x": 486, + "y": -183 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2.b", + "type": "rectangle", + "pos": { + "x": 659, + "y": 116 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2.c", + "type": "rectangle", + "pos": { + "x": 313, + "y": 117 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "3", + "type": "cycle", + "pos": { + "x": 972, + "y": 0 + }, + "width": 53, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "3.a", + "type": "rectangle", + "pos": { + "x": 945, + "y": -233 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "3.b", + "type": "rectangle", + "pos": { + "x": 945, + "y": 167 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "1.(a -> b)[0]", + "src": "1.a", + "srcArrow": "none", + "dst": "1.b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 8.239999771118164, + "y": -232.6529998779297 + }, + { + "x": 13.239999771118164, + "y": -252.6529998779297 + }, + { + "x": 26.44499969482422, + "y": -251.61399841308594 + }, + { + "x": 39.57699966430664, + "y": -249.88499450683594 + }, + { + "x": 52.60100173950195, + "y": -247.4709930419922 + }, + { + "x": 65.48100280761719, + "y": -244.37899780273438 + }, + { + "x": 78.18099975585938, + "y": -240.61700439453125 + }, + { + "x": 90.66699981689453, + "y": -236.19500732421875 + }, + { + "x": 102.90399932861328, + "y": -231.1269989013672 + }, + { + "x": 114.85900115966797, + "y": -225.4239959716797 + }, + { + "x": 126.4990005493164, + "y": -219.10400390625 + }, + { + "x": 137.79299926757812, + "y": -212.18299865722656 + }, + { + "x": 148.70899963378906, + "y": -204.68099975585938 + }, + { + "x": 159.21800231933594, + "y": -196.61700439453125 + }, + { + "x": 169.2899932861328, + "y": -188.01499938964844 + }, + { + "x": 178.8979949951172, + "y": -178.8979949951172 + }, + { + "x": 188.01499938964844, + "y": -169.2899932861328 + }, + { + "x": 196.61700439453125, + "y": -159.21800231933594 + }, + { + "x": 204.68099975585938, + "y": -148.70899963378906 + }, + { + "x": 212.18299865722656, + "y": -137.79299926757812 + }, + { + "x": 219.10400390625, + "y": -126.5 + }, + { + "x": 225.4239959716797, + "y": -114.85900115966797 + }, + { + "x": 231.1269989013672, + "y": -102.90399932861328 + }, + { + "x": 236.19500732421875, + "y": -90.66699981689453 + }, + { + "x": 240.61700439453125, + "y": -78.18099975585938 + }, + { + "x": 244.37899780273438, + "y": -65.48100280761719 + }, + { + "x": 247.4709930419922, + "y": -52.60100173950195 + }, + { + "x": 249.88499450683594, + "y": -39.57699966430664 + }, + { + "x": 251.61399841308594, + "y": -26.44499969482422 + }, + { + "x": 252.6529998779297, + "y": -13.239999771118164 + }, + { + "x": 226.6529998779297, + "y": -6.239999771118164 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(b -> c)[0]", + "src": "1.b", + "srcArrow": "none", + "dst": "1.c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 226.6529998779297, + "y": 6.239999771118164 + }, + { + "x": 252.6529998779297, + "y": 13.239999771118164 + }, + { + "x": 251.61399841308594, + "y": 26.44499969482422 + }, + { + "x": 249.88499450683594, + "y": 39.57699966430664 + }, + { + "x": 247.4709930419922, + "y": 52.60100173950195 + }, + { + "x": 244.37899780273438, + "y": 65.48100280761719 + }, + { + "x": 240.61700439453125, + "y": 78.18099975585938 + }, + { + "x": 236.19500732421875, + "y": 90.66699981689453 + }, + { + "x": 231.1269989013672, + "y": 102.90399932861328 + }, + { + "x": 225.4239959716797, + "y": 114.85900115966797 + }, + { + "x": 219.10400390625, + "y": 126.4990005493164 + }, + { + "x": 212.18299865722656, + "y": 137.79299926757812 + }, + { + "x": 204.68099975585938, + "y": 148.70899963378906 + }, + { + "x": 196.61700439453125, + "y": 159.21800231933594 + }, + { + "x": 188.01499938964844, + "y": 169.2899932861328 + }, + { + "x": 178.8979949951172, + "y": 178.8979949951172 + }, + { + "x": 169.2899932861328, + "y": 188.01499938964844 + }, + { + "x": 159.21800231933594, + "y": 196.61700439453125 + }, + { + "x": 148.70899963378906, + "y": 204.68099975585938 + }, + { + "x": 137.79299926757812, + "y": 212.18299865722656 + }, + { + "x": 126.5, + "y": 219.10400390625 + }, + { + "x": 114.85900115966797, + "y": 225.4239959716797 + }, + { + "x": 102.90399932861328, + "y": 231.1269989013672 + }, + { + "x": 90.66699981689453, + "y": 236.19500732421875 + }, + { + "x": 78.18099975585938, + "y": 240.61700439453125 + }, + { + "x": 65.48100280761719, + "y": 244.37899780273438 + }, + { + "x": 52.60100173950195, + "y": 247.4709930419922 + }, + { + "x": 39.57699966430664, + "y": 249.88499450683594 + }, + { + "x": 26.44499969482422, + "y": 251.61399841308594 + }, + { + "x": 13.239999771118164, + "y": 252.6529998779297 + }, + { + "x": 8.239999771118164, + "y": 232.6529998779297 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(c -> d)[0]", + "src": "1.c", + "srcArrow": "none", + "dst": "1.d", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": -8.239999771118164, + "y": 232.6529998779297 + }, + { + "x": -13.239999771118164, + "y": 252.6529998779297 + }, + { + "x": -26.44499969482422, + "y": 251.61399841308594 + }, + { + "x": -39.57699966430664, + "y": 249.88499450683594 + }, + { + "x": -52.60100173950195, + "y": 247.4709930419922 + }, + { + "x": -65.48100280761719, + "y": 244.37899780273438 + }, + { + "x": -78.18099975585938, + "y": 240.61700439453125 + }, + { + "x": -90.66699981689453, + "y": 236.19500732421875 + }, + { + "x": -102.90399932861328, + "y": 231.1269989013672 + }, + { + "x": -114.85900115966797, + "y": 225.4239959716797 + }, + { + "x": -126.4990005493164, + "y": 219.10400390625 + }, + { + "x": -137.79299926757812, + "y": 212.18299865722656 + }, + { + "x": -148.70899963378906, + "y": 204.68099975585938 + }, + { + "x": -159.21800231933594, + "y": 196.61700439453125 + }, + { + "x": -169.2899932861328, + "y": 188.01499938964844 + }, + { + "x": -178.8979949951172, + "y": 178.8979949951172 + }, + { + "x": -188.01499938964844, + "y": 169.2899932861328 + }, + { + "x": -196.61700439453125, + "y": 159.21800231933594 + }, + { + "x": -204.68099975585938, + "y": 148.70899963378906 + }, + { + "x": -212.18299865722656, + "y": 137.79299926757812 + }, + { + "x": -219.10400390625, + "y": 126.5 + }, + { + "x": -225.4239959716797, + "y": 114.85900115966797 + }, + { + "x": -231.1269989013672, + "y": 102.90399932861328 + }, + { + "x": -236.19500732421875, + "y": 90.66699981689453 + }, + { + "x": -240.61700439453125, + "y": 78.18099975585938 + }, + { + "x": -244.37899780273438, + "y": 65.48100280761719 + }, + { + "x": -247.4709930419922, + "y": 52.60100173950195 + }, + { + "x": -249.88499450683594, + "y": 39.57699966430664 + }, + { + "x": -251.61399841308594, + "y": 26.44499969482422 + }, + { + "x": -252.6529998779297, + "y": 13.239999771118164 + }, + { + "x": -226.6529998779297, + "y": 7.239999771118164 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(a -> b)[0]", + "src": "2.a", + "srcArrow": "none", + "dst": "2.b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 523.6480102539062, + "y": -183.38299560546875 + }, + { + "x": 530.6480102539062, + "y": -202.38299560546875 + }, + { + "x": 548.2100219726562, + "y": -200.53700256347656 + }, + { + "x": 565.6010131835938, + "y": -197.4709930419922 + }, + { + "x": 582.7360229492188, + "y": -193.19900512695312 + }, + { + "x": 599.531005859375, + "y": -187.74200439453125 + }, + { + "x": 615.9039916992188, + "y": -181.1269989013672 + }, + { + "x": 631.7760009765625, + "y": -173.38499450683594 + }, + { + "x": 647.0689697265625, + "y": -164.55599975585938 + }, + { + "x": 661.708984375, + "y": -154.68099975585938 + }, + { + "x": 675.625, + "y": -143.8090057373047 + }, + { + "x": 688.7479858398438, + "y": -131.99200439453125 + }, + { + "x": 701.0150146484375, + "y": -119.29000091552734 + }, + { + "x": 712.3660278320312, + "y": -105.76200103759766 + }, + { + "x": 722.7459716796875, + "y": -91.4749984741211 + }, + { + "x": 732.10400390625, + "y": -76.5 + }, + { + "x": 740.3939819335938, + "y": -60.90700149536133 + }, + { + "x": 747.5770263671875, + "y": -44.775001525878906 + }, + { + "x": 753.6170043945312, + "y": -28.180999755859375 + }, + { + "x": 758.4840087890625, + "y": -11.206000328063965 + }, + { + "x": 762.156005859375, + "y": 6.066999912261963 + }, + { + "x": 764.614013671875, + "y": 23.554000854492188 + }, + { + "x": 765.844970703125, + "y": 41.16999816894531 + }, + { + "x": 765.844970703125, + "y": 58.82899856567383 + }, + { + "x": 764.614013671875, + "y": 76.44499969482422 + }, + { + "x": 762.156005859375, + "y": 93.93199920654297 + }, + { + "x": 758.4840087890625, + "y": 111.20600128173828 + }, + { + "x": 753.6170043945312, + "y": 128.18099975585938 + }, + { + "x": 747.5770263671875, + "y": 144.77499389648438 + }, + { + "x": 740.3939819335938, + "y": 160.90699768066406 + }, + { + "x": 712.3939819335938, + "y": 154.90699768066406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(b -> c)[0]", + "src": "2.b", + "srcArrow": "none", + "dst": "2.c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 712.7459716796875, + "y": 180.47500610351562 + }, + { + "x": 722.7459716796875, + "y": 191.47500610351562 + }, + { + "x": 712.3660278320312, + "y": 205.76199340820312 + }, + { + "x": 701.0150146484375, + "y": 219.2899932861328 + }, + { + "x": 688.7479858398438, + "y": 231.99200439453125 + }, + { + "x": 675.625, + "y": 243.8090057373047 + }, + { + "x": 661.708984375, + "y": 254.68099975585938 + }, + { + "x": 647.0689697265625, + "y": 264.5559997558594 + }, + { + "x": 631.7760009765625, + "y": 273.385009765625 + }, + { + "x": 615.9039916992188, + "y": 281.12701416015625 + }, + { + "x": 599.531005859375, + "y": 287.74200439453125 + }, + { + "x": 582.7360229492188, + "y": 293.1990051269531 + }, + { + "x": 565.6010131835938, + "y": 297.47100830078125 + }, + { + "x": 548.2100219726562, + "y": 300.5369873046875 + }, + { + "x": 530.6480102539062, + "y": 302.38299560546875 + }, + { + "x": 513, + "y": 303 + }, + { + "x": 495.35101318359375, + "y": 302.38299560546875 + }, + { + "x": 477.78900146484375, + "y": 300.5369873046875 + }, + { + "x": 460.39801025390625, + "y": 297.47100830078125 + }, + { + "x": 443.26300048828125, + "y": 293.1990051269531 + }, + { + "x": 426.4679870605469, + "y": 287.74200439453125 + }, + { + "x": 410.0950012207031, + "y": 281.12701416015625 + }, + { + "x": 394.2229919433594, + "y": 273.385009765625 + }, + { + "x": 378.92999267578125, + "y": 264.5559997558594 + }, + { + "x": 364.2900085449219, + "y": 254.68099975585938 + }, + { + "x": 350.3739929199219, + "y": 243.8090057373047 + }, + { + "x": 337.2510070800781, + "y": 231.99200439453125 + }, + { + "x": 324.9840087890625, + "y": 219.2899932861328 + }, + { + "x": 313.63299560546875, + "y": 205.76199340820312 + }, + { + "x": 303.25299072265625, + "y": 191.47500610351562 + }, + { + "x": 313.25299072265625, + "y": 180.47500610351562 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "3.(a -> b)[0]", + "src": "3.a", + "srcArrow": "none", + "dst": "3.b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 988.4450073242188, + "y": -232.61399841308594 + }, + { + "x": 998.4450073242188, + "y": -251.61399841308594 + }, + { + "x": 1024.6009521484375, + "y": -247.4709930419922 + }, + { + "x": 1050.1810302734375, + "y": -240.61700439453125 + }, + { + "x": 1074.904052734375, + "y": -231.1269989013672 + }, + { + "x": 1098.5, + "y": -219.10400390625 + }, + { + "x": 1120.708984375, + "y": -204.68099975585938 + }, + { + "x": 1141.2900390625, + "y": -188.01499938964844 + }, + { + "x": 1160.0150146484375, + "y": -169.2899932861328 + }, + { + "x": 1176.6810302734375, + "y": -148.70899963378906 + }, + { + "x": 1191.10400390625, + "y": -126.5 + }, + { + "x": 1203.126953125, + "y": -102.90399932861328 + }, + { + "x": 1212.616943359375, + "y": -78.18099975585938 + }, + { + "x": 1219.470947265625, + "y": -52.60100173950195 + }, + { + "x": 1223.614013671875, + "y": -26.44499969482422 + }, + { + "x": 1225, + "y": 0 + }, + { + "x": 1223.614013671875, + "y": 26.44499969482422 + }, + { + "x": 1219.470947265625, + "y": 52.60100173950195 + }, + { + "x": 1212.616943359375, + "y": 78.18099975585938 + }, + { + "x": 1203.126953125, + "y": 102.90399932861328 + }, + { + "x": 1191.10400390625, + "y": 126.4990005493164 + }, + { + "x": 1176.6810302734375, + "y": 148.70899963378906 + }, + { + "x": 1160.0150146484375, + "y": 169.2899932861328 + }, + { + "x": 1141.2900390625, + "y": 188.01499938964844 + }, + { + "x": 1120.708984375, + "y": 204.68099975585938 + }, + { + "x": 1098.5, + "y": 219.10400390625 + }, + { + "x": 1074.904052734375, + "y": 231.1269989013672 + }, + { + "x": 1050.1810302734375, + "y": 240.61700439453125 + }, + { + "x": 1024.6009521484375, + "y": 247.4709930419922 + }, + { + "x": 998.4450073242188, + "y": 251.61399841308594 + }, + { + "x": 988.4450073242188, + "y": 232.61399841308594 + } + ], + "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 000000000..f61974313 --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/dagre/sketch.exp.svg @@ -0,0 +1,103 @@ +abcdabcab + + + + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json new file mode 100644 index 000000000..6ff0b5c2d --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/board.exp.json @@ -0,0 +1,1495 @@ +{ + "name": "", + "config": { + "sketch": false, + "themeID": 0, + "darkThemeID": null, + "pad": null, + "center": null, + "layoutEngine": null + }, + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "1", + "type": "cycle", + "pos": { + "x": 12, + "y": 12 + }, + "width": 454, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "1.a", + "type": "rectangle", + "pos": { + "x": -14, + "y": -221 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "1.b", + "type": "rectangle", + "pos": { + "x": 185, + "y": -21 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "1.c", + "type": "rectangle", + "pos": { + "x": -14, + "y": 179 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "1.d", + "type": "rectangle", + "pos": { + "x": -215, + "y": -20 + }, + "width": 54, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "d", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 9, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2", + "type": "cycle", + "pos": { + "x": 485, + "y": 61 + }, + "width": 400, + "height": 367, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "2.a", + "type": "rectangle", + "pos": { + "x": 459, + "y": -171 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2.b", + "type": "rectangle", + "pos": { + "x": 632, + "y": 128 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "2.c", + "type": "rectangle", + "pos": { + "x": 285, + "y": 129 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "c", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "3", + "type": "cycle", + "pos": { + "x": 904, + "y": 12 + }, + "width": 53, + "height": 466, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N7", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "zIndex": 0, + "level": 1 + }, + { + "id": "3.a", + "type": "rectangle", + "pos": { + "x": 878, + "y": -221 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "a", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "3.b", + "type": "rectangle", + "pos": { + "x": 878, + "y": 179 + }, + "width": 53, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "animated": false, + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "b", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 8, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 2 + } + ], + "connections": [ + { + "id": "1.(a -> b)[0]", + "src": "1.a", + "srcArrow": "none", + "dst": "1.b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 20.239999771118164, + "y": -220.6529998779297 + }, + { + "x": 25.239999771118164, + "y": -240.6529998779297 + }, + { + "x": 38.44499969482422, + "y": -239.61399841308594 + }, + { + "x": 51.57699966430664, + "y": -237.88499450683594 + }, + { + "x": 64.60099792480469, + "y": -235.4709930419922 + }, + { + "x": 77.48100280761719, + "y": -232.37899780273438 + }, + { + "x": 90.18099975585938, + "y": -228.61700439453125 + }, + { + "x": 102.66699981689453, + "y": -224.19500732421875 + }, + { + "x": 114.90399932861328, + "y": -219.1269989013672 + }, + { + "x": 126.85900115966797, + "y": -213.4239959716797 + }, + { + "x": 138.5, + "y": -207.10400390625 + }, + { + "x": 149.79299926757812, + "y": -200.18299865722656 + }, + { + "x": 160.70899963378906, + "y": -192.68099975585938 + }, + { + "x": 171.21800231933594, + "y": -184.61700439453125 + }, + { + "x": 181.2899932861328, + "y": -176.01499938964844 + }, + { + "x": 190.8979949951172, + "y": -166.8979949951172 + }, + { + "x": 200.01499938964844, + "y": -157.2899932861328 + }, + { + "x": 208.61700439453125, + "y": -147.21800231933594 + }, + { + "x": 216.68099975585938, + "y": -136.70899963378906 + }, + { + "x": 224.18299865722656, + "y": -125.79299926757812 + }, + { + "x": 231.10400390625, + "y": -114.5 + }, + { + "x": 237.4239959716797, + "y": -102.85900115966797 + }, + { + "x": 243.1269989013672, + "y": -90.90399932861328 + }, + { + "x": 248.19500732421875, + "y": -78.66699981689453 + }, + { + "x": 252.61700439453125, + "y": -66.18099975585938 + }, + { + "x": 256.3789978027344, + "y": -53.48099899291992 + }, + { + "x": 259.47100830078125, + "y": -40.60100173950195 + }, + { + "x": 261.885009765625, + "y": -27.57699966430664 + }, + { + "x": 263.614013671875, + "y": -14.444999694824219 + }, + { + "x": 264.65301513671875, + "y": -1.2400000095367432 + }, + { + "x": 238.6529998779297, + "y": 5.758999824523926 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(b -> c)[0]", + "src": "1.b", + "srcArrow": "none", + "dst": "1.c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 238.6529998779297, + "y": 18.239999771118164 + }, + { + "x": 264.65301513671875, + "y": 25.239999771118164 + }, + { + "x": 263.614013671875, + "y": 38.44499969482422 + }, + { + "x": 261.885009765625, + "y": 51.57699966430664 + }, + { + "x": 259.47100830078125, + "y": 64.60099792480469 + }, + { + "x": 256.3789978027344, + "y": 77.48100280761719 + }, + { + "x": 252.61700439453125, + "y": 90.18099975585938 + }, + { + "x": 248.19500732421875, + "y": 102.66699981689453 + }, + { + "x": 243.1269989013672, + "y": 114.90399932861328 + }, + { + "x": 237.4239959716797, + "y": 126.85900115966797 + }, + { + "x": 231.10400390625, + "y": 138.5 + }, + { + "x": 224.18299865722656, + "y": 149.79299926757812 + }, + { + "x": 216.68099975585938, + "y": 160.70899963378906 + }, + { + "x": 208.61700439453125, + "y": 171.21800231933594 + }, + { + "x": 200.01499938964844, + "y": 181.2899932861328 + }, + { + "x": 190.8979949951172, + "y": 190.8979949951172 + }, + { + "x": 181.2899932861328, + "y": 200.01499938964844 + }, + { + "x": 171.21800231933594, + "y": 208.61700439453125 + }, + { + "x": 160.70899963378906, + "y": 216.68099975585938 + }, + { + "x": 149.79299926757812, + "y": 224.18299865722656 + }, + { + "x": 138.5, + "y": 231.10400390625 + }, + { + "x": 126.85900115966797, + "y": 237.4239959716797 + }, + { + "x": 114.90399932861328, + "y": 243.1269989013672 + }, + { + "x": 102.66699981689453, + "y": 248.19500732421875 + }, + { + "x": 90.18099975585938, + "y": 252.61700439453125 + }, + { + "x": 77.48100280761719, + "y": 256.3789978027344 + }, + { + "x": 64.60099792480469, + "y": 259.47100830078125 + }, + { + "x": 51.57699966430664, + "y": 261.885009765625 + }, + { + "x": 38.44499969482422, + "y": 263.614013671875 + }, + { + "x": 25.239999771118164, + "y": 264.65301513671875 + }, + { + "x": 20.239999771118164, + "y": 244.6529998779297 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "1.(c -> d)[0]", + "src": "1.c", + "srcArrow": "none", + "dst": "1.d", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 3.759000062942505, + "y": 244.6529998779297 + }, + { + "x": -1.2400000095367432, + "y": 264.65301513671875 + }, + { + "x": -14.444999694824219, + "y": 263.614013671875 + }, + { + "x": -27.57699966430664, + "y": 261.885009765625 + }, + { + "x": -40.60100173950195, + "y": 259.47100830078125 + }, + { + "x": -53.48099899291992, + "y": 256.3789978027344 + }, + { + "x": -66.18099975585938, + "y": 252.61700439453125 + }, + { + "x": -78.66699981689453, + "y": 248.19500732421875 + }, + { + "x": -90.90399932861328, + "y": 243.1269989013672 + }, + { + "x": -102.85900115966797, + "y": 237.4239959716797 + }, + { + "x": -114.4990005493164, + "y": 231.10400390625 + }, + { + "x": -125.79299926757812, + "y": 224.18299865722656 + }, + { + "x": -136.70899963378906, + "y": 216.68099975585938 + }, + { + "x": -147.21800231933594, + "y": 208.61700439453125 + }, + { + "x": -157.2899932861328, + "y": 200.01499938964844 + }, + { + "x": -166.8979949951172, + "y": 190.8979949951172 + }, + { + "x": -176.01499938964844, + "y": 181.2899932861328 + }, + { + "x": -184.61700439453125, + "y": 171.21800231933594 + }, + { + "x": -192.68099975585938, + "y": 160.70899963378906 + }, + { + "x": -200.18299865722656, + "y": 149.79299926757812 + }, + { + "x": -207.10400390625, + "y": 138.5 + }, + { + "x": -213.4239959716797, + "y": 126.85900115966797 + }, + { + "x": -219.1269989013672, + "y": 114.90399932861328 + }, + { + "x": -224.19500732421875, + "y": 102.66699981689453 + }, + { + "x": -228.61700439453125, + "y": 90.18099975585938 + }, + { + "x": -232.37899780273438, + "y": 77.48100280761719 + }, + { + "x": -235.4709930419922, + "y": 64.60099792480469 + }, + { + "x": -237.88499450683594, + "y": 51.57699966430664 + }, + { + "x": -239.61399841308594, + "y": 38.44499969482422 + }, + { + "x": -240.6529998779297, + "y": 25.239999771118164 + }, + { + "x": -214.6529998779297, + "y": 19.239999771118164 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(a -> b)[0]", + "src": "2.a", + "srcArrow": "none", + "dst": "2.b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 496.14801025390625, + "y": -171.38299560546875 + }, + { + "x": 503.14801025390625, + "y": -190.38299560546875 + }, + { + "x": 520.7100219726562, + "y": -188.53700256347656 + }, + { + "x": 538.1010131835938, + "y": -185.4709930419922 + }, + { + "x": 555.2360229492188, + "y": -181.19900512695312 + }, + { + "x": 572.031005859375, + "y": -175.74200439453125 + }, + { + "x": 588.4039916992188, + "y": -169.1269989013672 + }, + { + "x": 604.2760009765625, + "y": -161.38499450683594 + }, + { + "x": 619.5689697265625, + "y": -152.55599975585938 + }, + { + "x": 634.208984375, + "y": -142.68099975585938 + }, + { + "x": 648.125, + "y": -131.8090057373047 + }, + { + "x": 661.2479858398438, + "y": -119.99199676513672 + }, + { + "x": 673.5150146484375, + "y": -107.29000091552734 + }, + { + "x": 684.8660278320312, + "y": -93.76200103759766 + }, + { + "x": 695.2459716796875, + "y": -79.4749984741211 + }, + { + "x": 704.60400390625, + "y": -64.5 + }, + { + "x": 712.8939819335938, + "y": -48.90700149536133 + }, + { + "x": 720.0770263671875, + "y": -32.775001525878906 + }, + { + "x": 726.1170043945312, + "y": -16.180999755859375 + }, + { + "x": 730.9840087890625, + "y": 0.7929999828338623 + }, + { + "x": 734.656005859375, + "y": 18.066999435424805 + }, + { + "x": 737.114013671875, + "y": 35.55400085449219 + }, + { + "x": 738.344970703125, + "y": 53.16999816894531 + }, + { + "x": 738.344970703125, + "y": 70.8290023803711 + }, + { + "x": 737.114013671875, + "y": 88.44499969482422 + }, + { + "x": 734.656005859375, + "y": 105.93199920654297 + }, + { + "x": 730.9840087890625, + "y": 123.20600128173828 + }, + { + "x": 726.1170043945312, + "y": 140.18099975585938 + }, + { + "x": 720.0770263671875, + "y": 156.77499389648438 + }, + { + "x": 712.8939819335938, + "y": 172.90699768066406 + }, + { + "x": 684.8939819335938, + "y": 166.90699768066406 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "2.(b -> c)[0]", + "src": "2.b", + "srcArrow": "none", + "dst": "2.c", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 685.2459716796875, + "y": 192.47500610351562 + }, + { + "x": 695.2459716796875, + "y": 203.47500610351562 + }, + { + "x": 684.8660278320312, + "y": 217.76199340820312 + }, + { + "x": 673.5150146484375, + "y": 231.2899932861328 + }, + { + "x": 661.2479858398438, + "y": 243.99200439453125 + }, + { + "x": 648.125, + "y": 255.8090057373047 + }, + { + "x": 634.208984375, + "y": 266.6809997558594 + }, + { + "x": 619.5689697265625, + "y": 276.5559997558594 + }, + { + "x": 604.2760009765625, + "y": 285.385009765625 + }, + { + "x": 588.4039916992188, + "y": 293.12701416015625 + }, + { + "x": 572.031005859375, + "y": 299.74200439453125 + }, + { + "x": 555.2360229492188, + "y": 305.1990051269531 + }, + { + "x": 538.1010131835938, + "y": 309.47100830078125 + }, + { + "x": 520.7100219726562, + "y": 312.5369873046875 + }, + { + "x": 503.14801025390625, + "y": 314.38299560546875 + }, + { + "x": 485.5, + "y": 315 + }, + { + "x": 467.85101318359375, + "y": 314.38299560546875 + }, + { + "x": 450.28900146484375, + "y": 312.5369873046875 + }, + { + "x": 432.89801025390625, + "y": 309.47100830078125 + }, + { + "x": 415.76300048828125, + "y": 305.1990051269531 + }, + { + "x": 398.9679870605469, + "y": 299.74200439453125 + }, + { + "x": 382.5950012207031, + "y": 293.12701416015625 + }, + { + "x": 366.7229919433594, + "y": 285.385009765625 + }, + { + "x": 351.42999267578125, + "y": 276.5559997558594 + }, + { + "x": 336.7900085449219, + "y": 266.6809997558594 + }, + { + "x": 322.8739929199219, + "y": 255.8090057373047 + }, + { + "x": 309.7510070800781, + "y": 243.99200439453125 + }, + { + "x": 297.4840087890625, + "y": 231.2899932861328 + }, + { + "x": 286.13299560546875, + "y": 217.76199340820312 + }, + { + "x": 275.75299072265625, + "y": 203.47500610351562 + }, + { + "x": 285.75299072265625, + "y": 192.47500610351562 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "3.(a -> b)[0]", + "src": "3.a", + "srcArrow": "none", + "dst": "3.b", + "dstArrow": "triangle", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 0, + "labelHeight": 0, + "labelPosition": "", + "labelPercentage": 0, + "link": "", + "route": [ + { + "x": 921.35498046875, + "y": -220.61399841308594 + }, + { + "x": 931.35498046875, + "y": -239.61399841308594 + }, + { + "x": 957.510986328125, + "y": -235.4709930419922 + }, + { + "x": 983.0910034179688, + "y": -228.61700439453125 + }, + { + "x": 1007.8140258789062, + "y": -219.1269989013672 + }, + { + "x": 1031.4100341796875, + "y": -207.10400390625 + }, + { + "x": 1053.6190185546875, + "y": -192.68099975585938 + }, + { + "x": 1074.199951171875, + "y": -176.01499938964844 + }, + { + "x": 1092.925048828125, + "y": -157.2899932861328 + }, + { + "x": 1109.5909423828125, + "y": -136.70899963378906 + }, + { + "x": 1124.0140380859375, + "y": -114.5 + }, + { + "x": 1136.0369873046875, + "y": -90.90399932861328 + }, + { + "x": 1145.5269775390625, + "y": -66.18099975585938 + }, + { + "x": 1152.3809814453125, + "y": -40.60100173950195 + }, + { + "x": 1156.5240478515625, + "y": -14.444999694824219 + }, + { + "x": 1157.9100341796875, + "y": 12 + }, + { + "x": 1156.5240478515625, + "y": 38.44499969482422 + }, + { + "x": 1152.3809814453125, + "y": 64.60099792480469 + }, + { + "x": 1145.5269775390625, + "y": 90.18099975585938 + }, + { + "x": 1136.0369873046875, + "y": 114.90399932861328 + }, + { + "x": 1124.0140380859375, + "y": 138.49899291992188 + }, + { + "x": 1109.5909423828125, + "y": 160.70899963378906 + }, + { + "x": 1092.925048828125, + "y": 181.2899932861328 + }, + { + "x": 1074.199951171875, + "y": 200.01499938964844 + }, + { + "x": 1053.6190185546875, + "y": 216.68099975585938 + }, + { + "x": 1031.4100341796875, + "y": 231.10400390625 + }, + { + "x": 1007.8140258789062, + "y": 243.1269989013672 + }, + { + "x": 983.0910034179688, + "y": 252.61700439453125 + }, + { + "x": 957.510986328125, + "y": 259.47100830078125 + }, + { + "x": 931.35498046875, + "y": 263.614013671875 + }, + { + "x": 921.35498046875, + "y": 244.61399841308594 + } + ], + "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 000000000..ac3081c7b --- /dev/null +++ b/e2etests/testdata/txtar/cycle-diagram/elk/sketch.exp.svg @@ -0,0 +1,103 @@ +abcdabcab + + + + + + + + + + + \ No newline at end of file diff --git a/e2etests/txtar.txt b/e2etests/txtar.txt index fcc6a7619..61edb0372 100644 --- a/e2etests/txtar.txt +++ b/e2etests/txtar.txt @@ -775,3 +775,17 @@ a -> b: hello { b -> c: { icon: https://icons.terrastruct.com/essentials%2F213-alarm.svg } + +-- cycle-diagram -- +1: "" { + shape: cycle + a -> b -> c -> d +} +2: "" { + shape: cycle + a -> b -> c +} +3: "" { + shape: cycle + a -> b +} \ No newline at end of file diff --git a/lib/geo/point.go b/lib/geo/point.go index ab8e034a0..47882bb18 100644 --- a/lib/geo/point.go +++ b/lib/geo/point.go @@ -324,3 +324,11 @@ func RemovePoints(points Points, toRemove []bool) Points { } return without } +func (v Vector) Normalize() Vector { + length := v.Length() + if length == 0 { + // avoid dividing by 0 + return Vector{0, 0} + } + return Vector{v[0] / length, v[1] / length} +} \ No newline at end of file