diff --git a/d2layouts/d2elklayout/layout.go b/d2layouts/d2elklayout/layout.go index 7cf396ffe..f2e72657e 100644 --- a/d2layouts/d2elklayout/layout.go +++ b/d2layouts/d2elklayout/layout.go @@ -96,6 +96,7 @@ var DefaultOpts = ConfigurableOpts{ } var port_spacing = 40. +var edge_node_spacing = 40 type elkOpts struct { EdgeNode int `json:"elk.spacing.edgeNode,omitempty"` @@ -143,7 +144,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err LayoutOptions: &elkOpts{ Thoroughness: 8, EdgeEdgeBetweenLayersSpacing: 50, - EdgeNode: 40, + EdgeNode: edge_node_spacing, HierarchyHandling: "INCLUDE_CHILDREN", FixedAlignment: "BALANCED", ConsiderModelOrder: "NODES_AND_EDGES", @@ -224,7 +225,7 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err EdgeEdgeBetweenLayersSpacing: 50, HierarchyHandling: "INCLUDE_CHILDREN", FixedAlignment: "BALANCED", - EdgeNode: 40, + EdgeNode: edge_node_spacing, ConsiderModelOrder: "NODES_AND_EDGES", // Why is it (height, width)? I have no clue, but it works. NodeSizeMinimum: fmt.Sprintf("(%d, %d)", int(math.Ceil(height)), int(math.Ceil(width))), @@ -442,5 +443,131 @@ func Layout(ctx context.Context, g *d2graph.Graph, opts *ConfigurableOpts) (err edge.Route = points } + deleteBends(g) + return nil } + +// deleteBends is a shim for ELK to delete unnecessary bends +// see https://github.com/terrastruct/d2/issues/1030 +func deleteBends(g *d2graph.Graph) { + // Get rid of S-shapes at the source and the target + for _, isSource := range []bool{true, false} { + for ei, e := range g.Edges { + if len(e.Route) < 4 { + continue + } + var endpoint *d2graph.Object + var start *geo.Point + var corner *geo.Point + var end *geo.Point + + if isSource { + start = e.Route[0] + corner = e.Route[1] + end = e.Route[2] + endpoint = e.Src + } else { + start = e.Route[len(e.Route)-1] + corner = e.Route[len(e.Route)-2] + end = e.Route[len(e.Route)-3] + endpoint = e.Dst + } + + isHorizontal := start.Y == corner.Y + + // Make sure it's still attached + if isHorizontal { + if end.Y <= endpoint.TopLeft.Y+10 { + continue + } + if end.Y >= endpoint.TopLeft.Y+endpoint.Height-10 { + continue + } + } else { + if end.X <= endpoint.TopLeft.X+10 { + continue + } + if end.X >= endpoint.TopLeft.X+endpoint.Width-10 { + continue + } + } + + var newStart *geo.Point + if isHorizontal { + newStart = geo.NewPoint(start.X, end.Y) + } else { + newStart = geo.NewPoint(end.X, start.Y) + } + + // Check that the new segment doesn't collide with anything new + + oldSegment := geo.NewSegment(start, corner) + newSegment := geo.NewSegment(newStart, end) + + oldIntersects := countObjectIntersects(g, *oldSegment) + newIntersects := countObjectIntersects(g, *newSegment) + + if newIntersects > oldIntersects { + continue + } + + oldIntersects = countEdgeIntersects(g, g.Edges[ei], *oldSegment) + newIntersects = countEdgeIntersects(g, g.Edges[ei], *newSegment) + + if newIntersects > oldIntersects { + continue + } + + // commit + if isSource { + g.Edges[ei].Route = append( + []*geo.Point{newStart}, + e.Route[3:]..., + ) + } else { + g.Edges[ei].Route = append( + e.Route[:len(e.Route)-3], + newStart, + ) + } + } + } +} + +func countObjectIntersects(g *d2graph.Graph, s geo.Segment) int { + count := 0 + for _, o := range g.Objects { + if o.Intersects(s, float64(edge_node_spacing)) { + count++ + } + } + return count +} + +// countEdgeIntersects counts both crossings AND getting too close to a parallel segment +func countEdgeIntersects(g *d2graph.Graph, sEdge *d2graph.Edge, s geo.Segment) int { + isHorizontal := s.Start.Y == s.End.Y + count := 0 + for _, e := range g.Edges { + if e == sEdge { + continue + } + + for i := 0; i < len(e.Route)-1; i++ { + otherS := geo.NewSegment(e.Route[i], e.Route[i+1]) + otherIsHorizontal := otherS.Start.Y == otherS.End.Y + if isHorizontal == otherIsHorizontal { + if s.Overlaps(*otherS, isHorizontal, float64(edge_node_spacing)) { + count++ + } + } else { + if s.Intersects(*otherS) { + count++ + } + } + } + + } + return count +} diff --git a/e2etests/stable_test.go b/e2etests/stable_test.go index 706225b51..12453afe5 100644 --- a/e2etests/stable_test.go +++ b/e2etests/stable_test.go @@ -81,6 +81,59 @@ func testStable(t *testing.T) { } `, }, + { + name: "elk_shim", + script: `network: { + cell tower: { + satellites: { + shape: stored_data + style.multiple: true + width: 140 + } + + transmitter: { + width: 140 + } + + satellites -> transmitter: send { + } + satellites -> transmitter: send { + } + satellites -> transmitter: send { + } + } + + online portal: { + ui: { shape: hexagon } + } + + data processor: { + storage: { + shape: cylinder + style.multiple: true + } + } + + cell tower.transmitter -> data processor.storage: phone logs +} + +user: { + shape: person + width: 130 +} + +user -> network.cell tower: make call +user -> network.online portal.ui: access { + style.stroke-dash: 3 +} + +api server -> network.online portal.ui: display +api server -> logs: persist +logs: { shape: page; style.multiple: true } + +network.data processor -> api server + `, + }, { name: "mono-font", script: `satellites: SATELLITES { diff --git a/e2etests/testdata/stable/elk_shim/dagre/board.exp.json b/e2etests/testdata/stable/elk_shim/dagre/board.exp.json new file mode 100644 index 000000000..0e917cd3c --- /dev/null +++ b/e2etests/testdata/stable/elk_shim/dagre/board.exp.json @@ -0,0 +1,1109 @@ +{ + "name": "", + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "network", + "type": "rectangle", + "pos": { + "x": 0, + "y": 275 + }, + "width": 387, + "height": 1215, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "network", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 96, + "labelHeight": 36, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "network.cell tower", + "type": "rectangle", + "pos": { + "x": 95, + "y": 340 + }, + "width": 273, + "height": 307, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "cell tower", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 99, + "labelHeight": 31, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "network.cell tower.satellites", + "type": "stored_data", + "pos": { + "x": 161, + "y": 372 + }, + "width": 140, + "height": 61, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "AA5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": true, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "satellites", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 65, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "network.cell tower.transmitter", + "type": "rectangle", + "pos": { + "x": 161, + "y": 554 + }, + "width": 140, + "height": 61, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "transmitter", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 83, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "network.online portal", + "type": "rectangle", + "pos": { + "x": 20, + "y": 1309 + }, + "width": 146, + "height": 151, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "online portal", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 128, + "labelHeight": 31, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "network.online portal.ui", + "type": "hexagon", + "pos": { + "x": 67, + "y": 1350 + }, + "width": 59, + "height": 69, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "ui", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "network.data processor", + "type": "rectangle", + "pos": { + "x": 142, + "y": 804 + }, + "width": 179, + "height": 182, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "data processor", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 150, + "labelHeight": 31, + "labelPosition": "OUTSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "network.data processor.storage", + "type": "cylinder", + "pos": { + "x": 182, + "y": 836 + }, + "width": 99, + "height": 118, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "AA5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": true, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "storage", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 54, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "user", + "type": "person", + "pos": { + "x": 75, + "y": 0 + }, + "width": 130, + "height": 87, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B3", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "user", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 32, + "labelHeight": 21, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "api server", + "type": "rectangle", + "pos": { + "x": 428, + "y": 1066 + }, + "width": 116, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "api server", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 71, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "logs", + "type": "page", + "pos": { + "x": 449, + "y": 1303 + }, + "width": 73, + "height": 87, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "AB4", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": true, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "logs", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 28, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "network.cell tower.(satellites -> transmitter)[0]", + "src": "network.cell tower.satellites", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.cell tower.transmitter", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "send", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 210, + "y": 434 + }, + { + "x": 176.4, + "y": 482 + }, + { + "x": 176.4, + "y": 506.2 + }, + { + "x": 210, + "y": 555 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "network.cell tower.(satellites -> transmitter)[1]", + "src": "network.cell tower.satellites", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.cell tower.transmitter", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "send", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 231, + "y": 434 + }, + { + "x": 231.2, + "y": 482 + }, + { + "x": 231.25, + "y": 506.2 + }, + { + "x": 231.25, + "y": 555 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "network.cell tower.(satellites -> transmitter)[2]", + "src": "network.cell tower.satellites", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.cell tower.transmitter", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "send", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 253, + "y": 434 + }, + { + "x": 286.2, + "y": 482 + }, + { + "x": 286.1, + "y": 506.2 + }, + { + "x": 252.5, + "y": 555 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "network.(cell tower.transmitter -> data processor.storage)[0]", + "src": "network.cell tower.transmitter", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.data processor.storage", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "phone logs", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 74, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 231.25, + "y": 615.5 + }, + { + "x": 231.25, + "y": 641.1 + }, + { + "x": 231.25, + "y": 659.6 + }, + { + "x": 231.25, + "y": 677.75 + }, + { + "x": 231.25, + "y": 695.9 + }, + { + "x": 231.2, + "y": 782.2 + }, + { + "x": 231, + "y": 837 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(user -> network.cell tower)[0]", + "src": "user", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.cell tower", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "make call", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 64, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 164, + "y": 87 + }, + { + "x": 217.8, + "y": 156.2 + }, + { + "x": 231.25, + "y": 248.2 + }, + { + "x": 231.25, + "y": 305 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(user -> network.online portal.ui)[0]", + "src": "user", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.online portal.ui", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 3, + "strokeWidth": 2, + "stroke": "B2", + "borderRadius": 10, + "label": "access", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 44, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 123, + "y": 87 + }, + { + "x": 84.4, + "y": 156.2 + }, + { + "x": 74.75, + "y": 185.6 + }, + { + "x": 74.75, + "y": 203.75 + }, + { + "x": 74.75, + "y": 221.9 + }, + { + "x": 74.75, + "y": 244 + }, + { + "x": 74.75, + "y": 259 + }, + { + "x": 74.75, + "y": 274 + }, + { + "x": 74.75, + "y": 300.1 + }, + { + "x": 74.75, + "y": 324.25 + }, + { + "x": 74.75, + "y": 348.4 + }, + { + "x": 74.75, + "y": 382.7 + }, + { + "x": 74.75, + "y": 410 + }, + { + "x": 74.75, + "y": 437.3 + }, + { + "x": 74.75, + "y": 473.7 + }, + { + "x": 74.75, + "y": 501 + }, + { + "x": 74.75, + "y": 528.3 + }, + { + "x": 74.75, + "y": 562.6 + }, + { + "x": 74.75, + "y": 586.75 + }, + { + "x": 74.75, + "y": 610.9 + }, + { + "x": 74.75, + "y": 639.1 + }, + { + "x": 74.75, + "y": 657.25 + }, + { + "x": 74.75, + "y": 675.4 + }, + { + "x": 74.75, + "y": 699.6 + }, + { + "x": 74.75, + "y": 717.75 + }, + { + "x": 74.75, + "y": 735.9 + }, + { + "x": 74.75, + "y": 769.8 + }, + { + "x": 74.75, + "y": 802.5 + }, + { + "x": 74.75, + "y": 835.2 + }, + { + "x": 74.75, + "y": 878.8 + }, + { + "x": 74.75, + "y": 911.5 + }, + { + "x": 74.75, + "y": 944.2 + }, + { + "x": 74.75, + "y": 976 + }, + { + "x": 74.75, + "y": 991 + }, + { + "x": 74.75, + "y": 1006 + }, + { + "x": 74.75, + "y": 1032.6 + }, + { + "x": 74.75, + "y": 1057.5 + }, + { + "x": 74.75, + "y": 1082.4 + }, + { + "x": 74.75, + "y": 1117.7 + }, + { + "x": 74.75, + "y": 1145.75 + }, + { + "x": 74.75, + "y": 1173.8 + }, + { + "x": 77.6, + "y": 1272.6 + }, + { + "x": 89, + "y": 1351 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(api server -> network.online portal.ui)[0]", + "src": "api server", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.online portal.ui", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "display", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 427.75, + "y": 1113.8881262868908 + }, + { + "x": 182.75, + "y": 1176.777625257378 + }, + { + "x": 118.2, + "y": 1272.6 + }, + { + "x": 105, + "y": 1351 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(api server -> logs)[0]", + "src": "api server", + "srcArrow": "none", + "srcLabel": "", + "dst": "logs", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "persist", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 46, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 485.75, + "y": 1132 + }, + { + "x": 485.75, + "y": 1180.4 + }, + { + "x": 485.8, + "y": 1263 + }, + { + "x": 486, + "y": 1303 + } + ], + "isCurve": true, + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(network.data processor -> api server)[0]", + "src": "network.data processor", + "srcArrow": "none", + "srcLabel": "", + "dst": "api server", + "dstArrow": "triangle", + "dstLabel": "", + "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, + "route": [ + { + "x": 231.25, + "y": 986.5 + }, + { + "x": 231.25, + "y": 1010.1 + }, + { + "x": 270.55, + "y": 1028.8168958742633 + }, + { + "x": 427.75, + "y": 1080.0844793713163 + } + ], + "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": "", + "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/stable/elk_shim/dagre/sketch.exp.svg b/e2etests/testdata/stable/elk_shim/dagre/sketch.exp.svg new file mode 100644 index 000000000..1b47e10f3 --- /dev/null +++ b/e2etests/testdata/stable/elk_shim/dagre/sketch.exp.svg @@ -0,0 +1,116 @@ +networkuserapi serverlogscell toweronline portaldata processorsatellitestransmitteruistorage sendsendsendphone logsmake call accessdisplaypersist + + + + + + + + + + \ No newline at end of file diff --git a/e2etests/testdata/stable/elk_shim/elk/board.exp.json b/e2etests/testdata/stable/elk_shim/elk/board.exp.json new file mode 100644 index 000000000..48c17c04d --- /dev/null +++ b/e2etests/testdata/stable/elk_shim/elk/board.exp.json @@ -0,0 +1,908 @@ +{ + "name": "", + "isFolderOnly": false, + "fontFamily": "SourceSansPro", + "shapes": [ + { + "id": "network", + "type": "rectangle", + "pos": { + "x": 12, + "y": 311 + }, + "width": 540, + "height": 892, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B4", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "network", + "fontSize": 28, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 96, + "labelHeight": 36, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "network.cell tower", + "type": "rectangle", + "pos": { + "x": 62, + "y": 361 + }, + "width": 240, + "height": 403, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "cell tower", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 99, + "labelHeight": 31, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "network.cell tower.satellites", + "type": "stored_data", + "pos": { + "x": 112, + "y": 411 + }, + "width": 140, + "height": 61, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "AA5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": true, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "satellites", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 65, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "network.cell tower.transmitter", + "type": "rectangle", + "pos": { + "x": 112, + "y": 653 + }, + "width": 140, + "height": 61, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "transmitter", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 83, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "network.online portal", + "type": "rectangle", + "pos": { + "x": 322, + "y": 366 + }, + "width": 180, + "height": 169, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "online portal", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 128, + "labelHeight": 31, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "network.online portal.ui", + "type": "hexagon", + "pos": { + "x": 372, + "y": 416 + }, + "width": 80, + "height": 69, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "N5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "ui", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 14, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "network.data processor", + "type": "rectangle", + "pos": { + "x": 82, + "y": 935 + }, + "width": 199, + "height": 218, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "data processor", + "fontSize": 24, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": false, + "underline": false, + "labelWidth": 150, + "labelHeight": 31, + "labelPosition": "INSIDE_TOP_CENTER", + "zIndex": 0, + "level": 2 + }, + { + "id": "network.data processor.storage", + "type": "cylinder", + "pos": { + "x": 132, + "y": 985 + }, + "width": 99, + "height": 118, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "AA5", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": true, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "storage", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 54, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 3 + }, + { + "id": "user", + "type": "person", + "pos": { + "x": 286, + "y": 12 + }, + "width": 130, + "height": 87, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B3", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "user", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 32, + "labelHeight": 21, + "labelPosition": "OUTSIDE_BOTTOM_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "api server", + "type": "rectangle", + "pos": { + "x": 534, + "y": 59 + }, + "width": 116, + "height": 66, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "B6", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": false, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "api server", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 71, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + }, + { + "id": "logs", + "type": "page", + "pos": { + "x": 632, + "y": 311 + }, + "width": 73, + "height": 87, + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "borderRadius": 0, + "fill": "AB4", + "stroke": "B1", + "shadow": false, + "3d": false, + "multiple": true, + "double-border": false, + "tooltip": "", + "link": "", + "icon": null, + "iconPosition": "", + "blend": false, + "fields": null, + "methods": null, + "columns": null, + "label": "logs", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N1", + "italic": false, + "bold": true, + "underline": false, + "labelWidth": 28, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "zIndex": 0, + "level": 1 + } + ], + "connections": [ + { + "id": "network.cell tower.(satellites -> transmitter)[0]", + "src": "network.cell tower.satellites", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.cell tower.transmitter", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "send", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 138, + "y": 472 + }, + { + "x": 138, + "y": 653 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "network.cell tower.(satellites -> transmitter)[1]", + "src": "network.cell tower.satellites", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.cell tower.transmitter", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "send", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 182, + "y": 472 + }, + { + "x": 182, + "y": 653 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "network.cell tower.(satellites -> transmitter)[2]", + "src": "network.cell tower.satellites", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.cell tower.transmitter", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "send", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 33, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 226, + "y": 472 + }, + { + "x": 226, + "y": 653 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "network.(cell tower.transmitter -> data processor.storage)[0]", + "src": "network.cell tower.transmitter", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.data processor.storage", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "phone logs", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 74, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 182, + "y": 714 + }, + { + "x": 182, + "y": 985 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(user -> network.cell tower)[0]", + "src": "user", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.cell tower", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "make call", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 64, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 329, + "y": 99 + }, + { + "x": 329.33333333333337, + "y": 165 + }, + { + "x": 222.83333333333334, + "y": 165 + }, + { + "x": 222.83333333333334, + "y": 361 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(user -> network.online portal.ui)[0]", + "src": "user", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.online portal.ui", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 3, + "strokeWidth": 2, + "stroke": "B2", + "borderRadius": 10, + "label": "access", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 44, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 373, + "y": 99 + }, + { + "x": 372.6666666666667, + "y": 165 + }, + { + "x": 398.6666666666667, + "y": 165 + }, + { + "x": 399, + "y": 416 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(api server -> network.online portal.ui)[0]", + "src": "api server", + "srcArrow": "none", + "srcLabel": "", + "dst": "network.online portal.ui", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "display", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 48, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 563, + "y": 125 + }, + { + "x": 563, + "y": 165 + }, + { + "x": 462.5, + "y": 165 + }, + { + "x": 462.5, + "y": 266 + }, + { + "x": 425.33333333333337, + "y": 266 + }, + { + "x": 425, + "y": 416 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(api server -> logs)[0]", + "src": "api server", + "srcArrow": "none", + "srcLabel": "", + "dst": "logs", + "dstArrow": "triangle", + "dstLabel": "", + "opacity": 1, + "strokeDash": 0, + "strokeWidth": 2, + "stroke": "B1", + "borderRadius": 10, + "label": "persist", + "fontSize": 16, + "fontFamily": "DEFAULT", + "language": "", + "color": "N2", + "italic": true, + "bold": false, + "underline": false, + "labelWidth": 46, + "labelHeight": 21, + "labelPosition": "INSIDE_MIDDLE_CENTER", + "labelPercentage": 0, + "route": [ + { + "x": 621, + "y": 125 + }, + { + "x": 621, + "y": 165 + }, + { + "x": 668.5, + "y": 165 + }, + { + "x": 669, + "y": 311 + } + ], + "animated": false, + "tooltip": "", + "icon": null, + "zIndex": 0 + }, + { + "id": "(network.data processor -> api server)[0]", + "src": "network.data processor", + "srcArrow": "none", + "srcLabel": "", + "dst": "api server", + "dstArrow": "triangle", + "dstLabel": "", + "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, + "route": [ + { + "x": 132.5, + "y": 1153 + }, + { + "x": 132.5, + "y": 1248 + }, + { + "x": 592, + "y": 1248 + }, + { + "x": 592, + "y": 125 + } + ], + "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": "", + "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/stable/elk_shim/elk/sketch.exp.svg b/e2etests/testdata/stable/elk_shim/elk/sketch.exp.svg new file mode 100644 index 000000000..b519dbbbf --- /dev/null +++ b/e2etests/testdata/stable/elk_shim/elk/sketch.exp.svg @@ -0,0 +1,116 @@ +networkuserapi serverlogscell toweronline portaldata processorsatellitestransmitteruistorage sendsendsendphone logsmake call accessdisplaypersist + + + + + + + + + + \ No newline at end of file diff --git a/lib/geo/box.go b/lib/geo/box.go index 24ac71c87..3fe0eb7c2 100644 --- a/lib/geo/box.go +++ b/lib/geo/box.go @@ -27,6 +27,28 @@ func (b *Box) Center() *Point { return NewPoint(b.TopLeft.X+b.Width/2, b.TopLeft.Y+b.Height/2) } +// Intersects returns true if the segment comes within buffer of the box +func (b *Box) Intersects(s Segment, buffer float64) bool { + tl := NewPoint(b.TopLeft.X-buffer, b.TopLeft.Y-buffer) + tr := NewPoint(tl.X+b.Width+buffer*2, tl.Y) + br := NewPoint(tr.X, tr.Y+b.Height+buffer*2) + bl := NewPoint(tl.X, br.Y) + + if p := IntersectionPoint(s.Start, s.End, tl, tr); p != nil { + return true + } + if p := IntersectionPoint(s.Start, s.End, tr, br); p != nil { + return true + } + if p := IntersectionPoint(s.Start, s.End, br, bl); p != nil { + return true + } + if p := IntersectionPoint(s.Start, s.End, bl, tl); p != nil { + return true + } + return false +} + func (b *Box) Intersections(s Segment) []*Point { pts := []*Point{} diff --git a/lib/geo/segment.go b/lib/geo/segment.go index df6f64a5c..bd88ac2cf 100644 --- a/lib/geo/segment.go +++ b/lib/geo/segment.go @@ -38,6 +38,10 @@ func (s Segment) Overlaps(otherS Segment, isHorizontal bool, buffer float64) boo } } +func (segment Segment) Intersects(otherSegment Segment) bool { + return IntersectionPoint(segment.Start, segment.End, otherSegment.Start, otherSegment.End) != nil +} + //nolint:unused func (s Segment) ToString() string { return fmt.Sprintf("%v -> %v", s.Start.ToString(), s.End.ToString())