@nowline/layout 0.8.5 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/axis-columns.d.ts +70 -0
- package/dist/axis-columns.d.ts.map +1 -0
- package/dist/axis-columns.js +104 -0
- package/dist/axis-columns.js.map +1 -0
- package/dist/band-scale.d.ts.map +1 -1
- package/dist/calendar-resolver.d.ts +27 -0
- package/dist/calendar-resolver.d.ts.map +1 -0
- package/dist/calendar-resolver.js +132 -0
- package/dist/calendar-resolver.js.map +1 -0
- package/dist/calendar.d.ts +0 -19
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +4 -44
- package/dist/calendar.js.map +1 -1
- package/dist/canvas-width.d.ts +23 -0
- package/dist/canvas-width.d.ts.map +1 -0
- package/dist/canvas-width.js +26 -0
- package/dist/canvas-width.js.map +1 -0
- package/dist/capacity.d.ts +28 -0
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +64 -29
- package/dist/capacity.js.map +1 -1
- package/dist/chart-translation.d.ts +22 -0
- package/dist/chart-translation.d.ts.map +1 -0
- package/dist/chart-translation.js +83 -0
- package/dist/chart-translation.js.map +1 -0
- package/dist/container-header-geometry.d.ts +64 -0
- package/dist/container-header-geometry.d.ts.map +1 -0
- package/dist/container-header-geometry.js +89 -0
- package/dist/container-header-geometry.js.map +1 -0
- package/dist/dsl-utils.d.ts +3 -4
- package/dist/dsl-utils.d.ts.map +1 -1
- package/dist/dsl-utils.js +8 -25
- package/dist/dsl-utils.js.map +1 -1
- package/dist/edge-routing.d.ts +69 -78
- package/dist/edge-routing.d.ts.map +1 -1
- package/dist/edge-routing.js +271 -401
- package/dist/edge-routing.js.map +1 -1
- package/dist/frame-tab-geometry.d.ts +24 -14
- package/dist/frame-tab-geometry.d.ts.map +1 -1
- package/dist/frame-tab-geometry.js +69 -23
- package/dist/frame-tab-geometry.js.map +1 -1
- package/dist/header-card-geometry.d.ts +12 -0
- package/dist/header-card-geometry.d.ts.map +1 -1
- package/dist/header-card-geometry.js +14 -2
- package/dist/header-card-geometry.js.map +1 -1
- package/dist/header-logo.d.ts +28 -0
- package/dist/header-logo.d.ts.map +1 -0
- package/dist/header-logo.js +51 -0
- package/dist/header-logo.js.map +1 -0
- package/dist/i18n.d.ts +11 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +11 -1
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/inline-date-pin-geometry.d.ts +19 -11
- package/dist/inline-date-pin-geometry.d.ts.map +1 -1
- package/dist/inline-date-pin-geometry.js +45 -46
- package/dist/inline-date-pin-geometry.js.map +1 -1
- package/dist/item-bar-geometry.d.ts +192 -4
- package/dist/item-bar-geometry.d.ts.map +1 -1
- package/dist/item-bar-geometry.js +258 -5
- package/dist/item-bar-geometry.js.map +1 -1
- package/dist/item-port-geometry.d.ts +13 -0
- package/dist/item-port-geometry.d.ts.map +1 -0
- package/dist/item-port-geometry.js +63 -0
- package/dist/item-port-geometry.js.map +1 -0
- package/dist/layout-context.d.ts +115 -51
- package/dist/layout-context.d.ts.map +1 -1
- package/dist/layout-context.js +96 -5
- package/dist/layout-context.js.map +1 -1
- package/dist/layout-insights.d.ts.map +1 -1
- package/dist/layout-insights.js +73 -1
- package/dist/layout-insights.js.map +1 -1
- package/dist/layout.d.ts +35 -0
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +18 -1315
- package/dist/layout.js.map +1 -1
- package/dist/nodes/anchor-node.d.ts.map +1 -1
- package/dist/nodes/anchor-node.js +11 -4
- package/dist/nodes/anchor-node.js.map +1 -1
- package/dist/nodes/content-extent.d.ts +24 -0
- package/dist/nodes/content-extent.d.ts.map +1 -0
- package/dist/nodes/content-extent.js +100 -0
- package/dist/nodes/content-extent.js.map +1 -0
- package/dist/nodes/dependency-edges.d.ts +6 -0
- package/dist/nodes/dependency-edges.d.ts.map +1 -0
- package/dist/nodes/dependency-edges.js +148 -0
- package/dist/nodes/dependency-edges.js.map +1 -0
- package/dist/nodes/footnote-node.d.ts +24 -3
- package/dist/nodes/footnote-node.d.ts.map +1 -1
- package/dist/nodes/footnote-node.js +129 -25
- package/dist/nodes/footnote-node.js.map +1 -1
- package/dist/nodes/group-node.d.ts +10 -21
- package/dist/nodes/group-node.d.ts.map +1 -1
- package/dist/nodes/group-node.js +79 -154
- package/dist/nodes/group-node.js.map +1 -1
- package/dist/nodes/header-node.d.ts +56 -0
- package/dist/nodes/header-node.d.ts.map +1 -0
- package/dist/nodes/header-node.js +163 -0
- package/dist/nodes/header-node.js.map +1 -0
- package/dist/nodes/include-node.d.ts +22 -12
- package/dist/nodes/include-node.d.ts.map +1 -1
- package/dist/nodes/include-node.js +95 -62
- package/dist/nodes/include-node.js.map +1 -1
- package/dist/nodes/item-measure.d.ts +135 -0
- package/dist/nodes/item-measure.d.ts.map +1 -0
- package/dist/nodes/item-measure.js +514 -0
- package/dist/nodes/item-measure.js.map +1 -0
- package/dist/nodes/item-node.d.ts +162 -9
- package/dist/nodes/item-node.d.ts.map +1 -1
- package/dist/nodes/item-node.js +256 -69
- package/dist/nodes/item-node.js.map +1 -1
- package/dist/nodes/marker-band.d.ts +118 -0
- package/dist/nodes/marker-band.d.ts.map +1 -0
- package/dist/nodes/marker-band.js +303 -0
- package/dist/nodes/marker-band.js.map +1 -0
- package/dist/nodes/marker-geometry.js +1 -1
- package/dist/nodes/marker-geometry.js.map +1 -1
- package/dist/nodes/milestone-node.d.ts +112 -15
- package/dist/nodes/milestone-node.d.ts.map +1 -1
- package/dist/nodes/milestone-node.js +246 -95
- package/dist/nodes/milestone-node.js.map +1 -1
- package/dist/nodes/nowline-node.d.ts +8 -0
- package/dist/nodes/nowline-node.d.ts.map +1 -0
- package/dist/nodes/nowline-node.js +74 -0
- package/dist/nodes/nowline-node.js.map +1 -0
- package/dist/nodes/parallel-node.d.ts +7 -12
- package/dist/nodes/parallel-node.d.ts.map +1 -1
- package/dist/nodes/parallel-node.js +66 -32
- package/dist/nodes/parallel-node.js.map +1 -1
- package/dist/nodes/region-markers.d.ts +41 -0
- package/dist/nodes/region-markers.d.ts.map +1 -0
- package/dist/nodes/region-markers.js +130 -0
- package/dist/nodes/region-markers.js.map +1 -0
- package/dist/nodes/roadmap-node.d.ts +9 -72
- package/dist/nodes/roadmap-node.d.ts.map +1 -1
- package/dist/nodes/roadmap-node.js +38 -788
- package/dist/nodes/roadmap-node.js.map +1 -1
- package/dist/nodes/row-track.d.ts +33 -0
- package/dist/nodes/row-track.d.ts.map +1 -0
- package/dist/nodes/row-track.js +170 -0
- package/dist/nodes/row-track.js.map +1 -0
- package/dist/nodes/sequence-item.d.ts +25 -0
- package/dist/nodes/sequence-item.d.ts.map +1 -0
- package/dist/nodes/sequence-item.js +396 -0
- package/dist/nodes/sequence-item.js.map +1 -0
- package/dist/nodes/sequence-one.d.ts +5 -0
- package/dist/nodes/sequence-one.d.ts.map +1 -0
- package/dist/nodes/sequence-one.js +26 -0
- package/dist/nodes/sequence-one.js.map +1 -0
- package/dist/nodes/swimlane-node.d.ts +5 -17
- package/dist/nodes/swimlane-node.d.ts.map +1 -1
- package/dist/nodes/swimlane-node.js +62 -147
- package/dist/nodes/swimlane-node.js.map +1 -1
- package/dist/nodes/wave-node.d.ts +52 -0
- package/dist/nodes/wave-node.d.ts.map +1 -0
- package/dist/nodes/wave-node.js +483 -0
- package/dist/nodes/wave-node.js.map +1 -0
- package/dist/non-working-display.d.ts +14 -0
- package/dist/non-working-display.d.ts.map +1 -0
- package/dist/non-working-display.js +28 -0
- package/dist/non-working-display.js.map +1 -0
- package/dist/phases/date-window.d.ts +48 -0
- package/dist/phases/date-window.d.ts.map +1 -0
- package/dist/phases/date-window.js +130 -0
- package/dist/phases/date-window.js.map +1 -0
- package/dist/phases/finalize.d.ts +198 -0
- package/dist/phases/finalize.d.ts.map +1 -0
- package/dist/phases/finalize.js +1058 -0
- package/dist/phases/finalize.js.map +1 -0
- package/dist/phases/measure.d.ts +77 -0
- package/dist/phases/measure.d.ts.map +1 -0
- package/dist/phases/measure.js +222 -0
- package/dist/phases/measure.js.map +1 -0
- package/dist/phases/place.d.ts +28 -0
- package/dist/phases/place.d.ts.map +1 -0
- package/dist/phases/place.js +225 -0
- package/dist/phases/place.js.map +1 -0
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +23 -3
- package/dist/renderable.js.map +1 -1
- package/dist/resolve-today.d.ts +11 -1
- package/dist/resolve-today.d.ts.map +1 -1
- package/dist/resolve-today.js +26 -12
- package/dist/resolve-today.js.map +1 -1
- package/dist/row-packer.d.ts +42 -16
- package/dist/row-packer.d.ts.map +1 -1
- package/dist/row-packer.js +51 -78
- package/dist/row-packer.js.map +1 -1
- package/dist/schedule.d.ts +58 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +280 -106
- package/dist/schedule.js.map +1 -1
- package/dist/start-rules.d.ts +27 -0
- package/dist/start-rules.d.ts.map +1 -0
- package/dist/start-rules.js +91 -0
- package/dist/start-rules.js.map +1 -0
- package/dist/start-terms.d.ts +23 -0
- package/dist/start-terms.d.ts.map +1 -0
- package/dist/start-terms.js +27 -0
- package/dist/start-terms.js.map +1 -0
- package/dist/status-dot.d.ts +46 -0
- package/dist/status-dot.d.ts.map +1 -0
- package/dist/status-dot.js +78 -0
- package/dist/status-dot.js.map +1 -0
- package/dist/style-resolution.d.ts.map +1 -1
- package/dist/style-resolution.js +23 -43
- package/dist/style-resolution.js.map +1 -1
- package/dist/text-measure.d.ts +28 -0
- package/dist/text-measure.d.ts.map +1 -0
- package/dist/text-measure.js +70 -0
- package/dist/text-measure.js.map +1 -0
- package/dist/themes/contrast.d.ts +4 -0
- package/dist/themes/contrast.d.ts.map +1 -0
- package/dist/themes/contrast.js +32 -0
- package/dist/themes/contrast.js.map +1 -0
- package/dist/themes/dark.d.ts.map +1 -1
- package/dist/themes/dark.js +28 -1
- package/dist/themes/dark.js.map +1 -1
- package/dist/themes/grayscale.d.ts.map +1 -1
- package/dist/themes/grayscale.js +28 -1
- package/dist/themes/grayscale.js.map +1 -1
- package/dist/themes/index.d.ts +4 -11
- package/dist/themes/index.d.ts.map +1 -1
- package/dist/themes/index.js +6 -4
- package/dist/themes/index.js.map +1 -1
- package/dist/themes/light.d.ts.map +1 -1
- package/dist/themes/light.js +28 -1
- package/dist/themes/light.js.map +1 -1
- package/dist/themes/shape.d.ts +31 -0
- package/dist/themes/shape.d.ts.map +1 -1
- package/dist/themes/shared.d.ts +44 -50
- package/dist/themes/shared.d.ts.map +1 -1
- package/dist/themes/shared.js +79 -83
- package/dist/themes/shared.js.map +1 -1
- package/dist/time-scale.d.ts +42 -6
- package/dist/time-scale.d.ts.map +1 -1
- package/dist/time-scale.js +109 -17
- package/dist/time-scale.js.map +1 -1
- package/dist/types.d.ts +528 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/view-preset.d.ts +61 -6
- package/dist/view-preset.d.ts.map +1 -1
- package/dist/view-preset.js +439 -24
- package/dist/view-preset.js.map +1 -1
- package/dist/wave-barrier.d.ts +107 -0
- package/dist/wave-barrier.d.ts.map +1 -0
- package/dist/wave-barrier.js +174 -0
- package/dist/wave-barrier.js.map +1 -0
- package/dist/wave-hatch.d.ts +4 -0
- package/dist/wave-hatch.d.ts.map +1 -0
- package/dist/wave-hatch.js +21 -0
- package/dist/wave-hatch.js.map +1 -0
- package/dist/wave-layout.d.ts +99 -0
- package/dist/wave-layout.d.ts.map +1 -0
- package/dist/wave-layout.js +130 -0
- package/dist/wave-layout.js.map +1 -0
- package/dist/working-calendar.d.ts +128 -4
- package/dist/working-calendar.d.ts.map +1 -1
- package/dist/working-calendar.js +725 -51
- package/dist/working-calendar.js.map +1 -1
- package/dist/working-span.d.ts +11 -0
- package/dist/working-span.d.ts.map +1 -0
- package/dist/working-span.js +18 -0
- package/dist/working-span.js.map +1 -0
- package/package.json +7 -7
- package/src/axis-columns.ts +140 -0
- package/src/calendar-resolver.ts +161 -0
- package/src/calendar.ts +4 -45
- package/src/canvas-width.ts +36 -0
- package/src/capacity.ts +71 -30
- package/src/chart-translation.ts +88 -0
- package/src/container-header-geometry.ts +116 -0
- package/src/dsl-utils.ts +8 -26
- package/src/edge-routing.ts +347 -491
- package/src/frame-tab-geometry.ts +78 -25
- package/src/header-card-geometry.ts +17 -2
- package/src/header-logo.ts +67 -0
- package/src/i18n.ts +22 -1
- package/src/index.ts +97 -12
- package/src/inline-date-pin-geometry.ts +55 -58
- package/src/item-bar-geometry.ts +341 -5
- package/src/item-port-geometry.ts +69 -0
- package/src/layout-context.ts +210 -72
- package/src/layout-insights.ts +109 -2
- package/src/layout.ts +57 -1530
- package/src/nodes/anchor-node.ts +11 -4
- package/src/nodes/content-extent.ts +107 -0
- package/src/nodes/dependency-edges.ts +171 -0
- package/src/nodes/footnote-node.ts +171 -31
- package/src/nodes/group-node.ts +92 -215
- package/src/nodes/header-node.ts +255 -0
- package/src/nodes/include-node.ts +124 -110
- package/src/nodes/item-measure.ts +723 -0
- package/src/nodes/item-node.ts +358 -80
- package/src/nodes/marker-band.ts +406 -0
- package/src/nodes/marker-geometry.ts +1 -1
- package/src/nodes/milestone-node.ts +321 -136
- package/src/nodes/nowline-node.ts +87 -0
- package/src/nodes/parallel-node.ts +69 -39
- package/src/nodes/region-markers.ts +194 -0
- package/src/nodes/roadmap-node.ts +46 -950
- package/src/nodes/row-track.ts +217 -0
- package/src/nodes/sequence-item.ts +433 -0
- package/src/nodes/sequence-one.ts +40 -0
- package/src/nodes/swimlane-node.ts +66 -206
- package/src/nodes/wave-node.ts +609 -0
- package/src/non-working-display.ts +32 -0
- package/src/phases/date-window.ts +191 -0
- package/src/phases/finalize.ts +1303 -0
- package/src/phases/measure.ts +329 -0
- package/src/phases/place.ts +318 -0
- package/src/renderable.ts +23 -3
- package/src/resolve-today.ts +28 -25
- package/src/row-packer.ts +85 -84
- package/src/schedule.ts +362 -114
- package/src/start-rules.ts +113 -0
- package/src/start-terms.ts +38 -0
- package/src/status-dot.ts +81 -0
- package/src/style-resolution.ts +27 -57
- package/src/text-measure.ts +71 -0
- package/src/themes/contrast.ts +34 -0
- package/src/themes/dark.ts +28 -1
- package/src/themes/grayscale.ts +28 -1
- package/src/themes/index.ts +10 -7
- package/src/themes/light.ts +28 -1
- package/src/themes/shape.ts +38 -0
- package/src/themes/shared.ts +85 -93
- package/src/time-scale.ts +117 -19
- package/src/types.ts +529 -20
- package/src/view-preset.ts +520 -27
- package/src/wave-barrier.ts +242 -0
- package/src/wave-hatch.ts +25 -0
- package/src/wave-layout.ts +203 -0
- package/src/working-calendar.ts +875 -57
- package/src/working-span.ts +19 -0
package/dist/edge-routing.js
CHANGED
|
@@ -1,435 +1,305 @@
|
|
|
1
|
-
//
|
|
2
|
-
// `routeEdge` shipped in m2g with one that:
|
|
1
|
+
// Date-port dependency router (specs/true-width-layout.md §3.4).
|
|
3
2
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
// thinner stroke) when no clean channel exists in the source-target
|
|
13
|
-
// gap.
|
|
3
|
+
// An arrow runs on the dates. It leaves the middle of its source's last
|
|
4
|
+
// day through the source's bottom edge (top edge when the target is
|
|
5
|
+
// above), drops into a gap between rows, runs horizontally in that gap to
|
|
6
|
+
// the middle of the target's first day, and turns into the target's top
|
|
7
|
+
// edge (bottom edge when the source is below), arrowhead first. Day
|
|
8
|
+
// centres, never date lines: in a chain the successor starts exactly on
|
|
9
|
+
// its predecessor's end date, so a leg on the date line would run down the
|
|
10
|
+
// successor's edge.
|
|
14
11
|
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// the
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
export const
|
|
38
|
-
/**
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
/**
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
* elbow corner; the renderer's `roundedOrthogonalPath` rounds the
|
|
48
|
-
* corners. Left-to-right edges use the per-side `MIN_*_STUB_PX`
|
|
49
|
-
* constants instead — they provide tighter, asymmetric control over
|
|
50
|
-
* the source vs target sides. */
|
|
51
|
-
const STUB_OUT_PX = 10;
|
|
52
|
-
export class ChannelGrid {
|
|
53
|
-
items;
|
|
54
|
-
brackets;
|
|
55
|
-
constructor(input) {
|
|
56
|
-
this.items = input.itemBars;
|
|
57
|
-
this.brackets = input.brackets;
|
|
58
|
-
}
|
|
59
|
-
/** True when a vertical line at `x` intersects any item bar within
|
|
60
|
-
* the Y span `[yMin, yMax]`. */
|
|
61
|
-
hasObstacle(x, yMin, yMax) {
|
|
62
|
-
const lo = Math.min(yMin, yMax);
|
|
63
|
-
const hi = Math.max(yMin, yMax);
|
|
64
|
-
for (const bar of this.items) {
|
|
65
|
-
if (x <= bar.x || x >= bar.x + bar.width)
|
|
66
|
-
continue;
|
|
67
|
-
if (bar.y + bar.height <= lo || bar.y >= hi)
|
|
68
|
-
continue;
|
|
69
|
-
return true;
|
|
70
|
-
}
|
|
71
|
-
return false;
|
|
72
|
-
}
|
|
73
|
-
/** Visible brackets within `radius` px of `x` whose Y span overlaps
|
|
74
|
-
* `[yMin, yMax]`. The clearance nudge uses these to shift the
|
|
75
|
-
* elbow X away from the bracket. */
|
|
76
|
-
bracketsNear(x, yMin, yMax, radius) {
|
|
77
|
-
const lo = Math.min(yMin, yMax);
|
|
78
|
-
const hi = Math.max(yMin, yMax);
|
|
79
|
-
const out = [];
|
|
80
|
-
for (const b of this.brackets) {
|
|
81
|
-
if (Math.abs(b.x - x) > radius)
|
|
82
|
-
continue;
|
|
83
|
-
if (b.yBottom <= lo || b.yTop >= hi)
|
|
84
|
-
continue;
|
|
85
|
-
out.push(b);
|
|
86
|
-
}
|
|
87
|
-
return out;
|
|
88
|
-
}
|
|
12
|
+
// Legs pass behind bars and cross them at right angles. The horizontal leg
|
|
13
|
+
// sits in a gap no bar, spilled caption or container header reaches into
|
|
14
|
+
// (between the source and the target, over the leg's x-range); among the
|
|
15
|
+
// gaps, the router takes the one whose vertical legs keep clear of every
|
|
16
|
+
// bar edge, then the one whose vertical legs cross the fewest bars, then
|
|
17
|
+
// the one nearest the source. Arrows sharing a gap take offset lanes
|
|
18
|
+
// `CHANNEL_LANE_PX` apart; arrows landing in the same day column share one
|
|
19
|
+
// lane, so they merge into one trunk over their target (§8 O1).
|
|
20
|
+
//
|
|
21
|
+
// Two shapes need no gap: a target on its source's row (a dotted leg at
|
|
22
|
+
// the row's midline, or no arrow when it starts where the source ends),
|
|
23
|
+
// and a marker source (`dependency-edges.ts`). Everything here is pure
|
|
24
|
+
// geometry on numbers the caller reads off the final model.
|
|
25
|
+
/** Spacing between the lanes of one gap (§3.4 "Channels"). */
|
|
26
|
+
export const CHANNEL_LANE_PX = 2.5;
|
|
27
|
+
/** A vertical leg keeps more than this from a bar's left or right edge over its height (§6). */
|
|
28
|
+
export const EDGE_CLEARANCE_PX = 1;
|
|
29
|
+
/** Full arrowhead width (and the length along the leg is `ARROW_HEAD_LENGTH_PX`). */
|
|
30
|
+
export const ARROW_HEAD_WIDTH_PX = 7;
|
|
31
|
+
/** Length of a full arrowhead along its leg. */
|
|
32
|
+
export const ARROW_HEAD_LENGTH_PX = 5;
|
|
33
|
+
/** The narrowest arrowhead; a target narrower than this gets a dot of this diameter (§8 O6). */
|
|
34
|
+
export const ARROW_HEAD_MIN_PX = 3;
|
|
35
|
+
/** Two x's (or y's) closer than this are the same date line. */
|
|
36
|
+
const SAME_PX = 0.01;
|
|
37
|
+
/** A gap must be at least this tall to carry a leg. */
|
|
38
|
+
const MIN_GAP_PX = 2;
|
|
39
|
+
/** Clearance kept between a horizontal leg and anything it must not touch. */
|
|
40
|
+
const BLOCK_MARGIN_PX = 1;
|
|
41
|
+
/** Where an arrow leaves `end`: the middle of its last day, or its middle when shorter than a day. */
|
|
42
|
+
export function sourcePortX(end, pixelsPerDay) {
|
|
43
|
+
return end.endX - Math.min(pixelsPerDay / 2, (end.endX - end.startX) / 2);
|
|
89
44
|
}
|
|
90
|
-
/**
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
export function collectRoutingObstacles(swimlanes, includes) {
|
|
100
|
-
const itemBars = [];
|
|
101
|
-
const brackets = [];
|
|
102
|
-
const visitChild = (child) => {
|
|
103
|
-
if (child.kind === 'item') {
|
|
104
|
-
itemBars.push(child.box);
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
if (child.kind === 'parallel') {
|
|
108
|
-
// Parallel `bracket:solid|dashed` paints `[ ]` strokes at
|
|
109
|
-
// the box's left/right edges with 12 px vertical padding
|
|
110
|
-
// (matches `renderParallel`). The bracket has THREE
|
|
111
|
-
// strokes per side — a vertical bar at the box edge, plus
|
|
112
|
-
// top/bottom horizontal "feet" extending 4 px inward
|
|
113
|
-
// toward the parallel's center. We model the verticals as
|
|
114
|
-
// full-height bracket lines AND emit thin-Y-band entries
|
|
115
|
-
// at the foot tips so the clearance nudge sees the foot
|
|
116
|
-
// extent (otherwise an elbow nudged just past the
|
|
117
|
-
// vertical at +4 px would land squarely on the inward
|
|
118
|
-
// foot tip — see issue #2 in the channel-routing handoff).
|
|
119
|
-
if (child.style.bracket === 'solid' || child.style.bracket === 'dashed') {
|
|
120
|
-
const padding = 12;
|
|
121
|
-
const stub = 4;
|
|
122
|
-
const yTop = child.box.y - padding;
|
|
123
|
-
const yBottom = child.box.y + child.box.height + padding;
|
|
124
|
-
const lx = child.box.x;
|
|
125
|
-
const rx = child.box.x + child.box.width;
|
|
126
|
-
brackets.push({ x: lx, yTop, yBottom });
|
|
127
|
-
brackets.push({ x: rx, yTop, yBottom });
|
|
128
|
-
// Foot tips: tiny Y bands centered on each foot row
|
|
129
|
-
// so the entries only fire when the elbow's Y span
|
|
130
|
-
// actually crosses the foot line.
|
|
131
|
-
brackets.push({ x: lx + stub, yTop: yTop - 1, yBottom: yTop + 1 });
|
|
132
|
-
brackets.push({ x: lx + stub, yTop: yBottom - 1, yBottom: yBottom + 1 });
|
|
133
|
-
brackets.push({ x: rx - stub, yTop: yTop - 1, yBottom: yTop + 1 });
|
|
134
|
-
brackets.push({ x: rx - stub, yTop: yBottom - 1, yBottom: yBottom + 1 });
|
|
135
|
-
}
|
|
136
|
-
for (const sub of child.children)
|
|
137
|
-
visitChild(sub);
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
if (child.kind === 'group') {
|
|
141
|
-
// Bracket-style groups (no fill) paint a left-side `[`
|
|
142
|
-
// glyph along `box.x`. Filled-style groups have no bracket
|
|
143
|
-
// — the chiclet is the visual instead.
|
|
144
|
-
const isFilled = child.style.bg !== 'none' && child.style.bg !== '#ffffff';
|
|
145
|
-
if (!isFilled && child.style.bracket && child.style.bracket !== 'none') {
|
|
146
|
-
brackets.push({
|
|
147
|
-
x: child.box.x,
|
|
148
|
-
yTop: child.box.y,
|
|
149
|
-
yBottom: child.box.y + child.box.height,
|
|
150
|
-
});
|
|
151
|
-
}
|
|
152
|
-
for (const sub of child.children)
|
|
153
|
-
visitChild(sub);
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
};
|
|
157
|
-
const visitSwimlane = (lane) => {
|
|
158
|
-
for (const child of lane.children)
|
|
159
|
-
visitChild(child);
|
|
160
|
-
for (const nested of lane.nested)
|
|
161
|
-
visitSwimlane(nested);
|
|
162
|
-
};
|
|
163
|
-
for (const lane of swimlanes)
|
|
164
|
-
visitSwimlane(lane);
|
|
165
|
-
for (const region of includes) {
|
|
166
|
-
for (const lane of region.nestedSwimlanes)
|
|
167
|
-
visitSwimlane(lane);
|
|
168
|
-
}
|
|
169
|
-
return { itemBars, brackets };
|
|
45
|
+
/** Where an arrow lands on `end`: the middle of its first day, or its middle when shorter than a day. */
|
|
46
|
+
export function targetPortX(end, pixelsPerDay) {
|
|
47
|
+
return end.startX + Math.min(pixelsPerDay / 2, (end.endX - end.startX) / 2);
|
|
48
|
+
}
|
|
49
|
+
/** Arrowhead for a leg landing across a target `width` wide (§8 O6). */
|
|
50
|
+
export function arrowHead(width) {
|
|
51
|
+
if (width < ARROW_HEAD_MIN_PX)
|
|
52
|
+
return { headWidth: ARROW_HEAD_MIN_PX, head: 'dot' };
|
|
53
|
+
return { headWidth: Math.min(ARROW_HEAD_WIDTH_PX, width), head: 'arrow' };
|
|
170
54
|
}
|
|
55
|
+
const within = (y, lo, hi) => y >= lo - SAME_PX && y <= hi + SAME_PX;
|
|
171
56
|
/**
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
* (`from.y ≈ to.y`) get a degenerate "channel" at midX so the
|
|
176
|
-
* renderer emits a straight line.
|
|
57
|
+
* True when the target sits on its source's row: each one's row midline
|
|
58
|
+
* crosses the other (a container source, which has no row, only needs the
|
|
59
|
+
* target's midline to cross it).
|
|
177
60
|
*/
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
61
|
+
function onSameRow(s, t) {
|
|
62
|
+
if (t.rowY === undefined || !within(t.rowY, s.top, s.bottom))
|
|
63
|
+
return false;
|
|
64
|
+
return s.rowY === undefined || within(s.rowY, t.top, t.bottom);
|
|
65
|
+
}
|
|
66
|
+
/** The free stretches of `(lo, hi)` no block over `[xLo, xHi]` reaches into. */
|
|
67
|
+
function freeGaps(blocks, xLo, xHi, lo, hi) {
|
|
68
|
+
const covered = [];
|
|
69
|
+
for (const b of blocks) {
|
|
70
|
+
if (b.x >= xHi + BLOCK_MARGIN_PX || b.x + b.width <= xLo - BLOCK_MARGIN_PX)
|
|
71
|
+
continue;
|
|
72
|
+
const top = b.y - BLOCK_MARGIN_PX;
|
|
73
|
+
const bottom = b.y + b.height + BLOCK_MARGIN_PX;
|
|
74
|
+
if (bottom <= lo || top >= hi)
|
|
75
|
+
continue;
|
|
76
|
+
covered.push([Math.max(lo, top), Math.min(hi, bottom)]);
|
|
182
77
|
}
|
|
183
|
-
|
|
184
|
-
const
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
// arrow drops to under-bar at the target-stub line.
|
|
191
|
-
const minX = from.x + MIN_SOURCE_STUB_PX;
|
|
192
|
-
const maxX = to.x - MIN_TARGET_STUB_PX;
|
|
193
|
-
if (minX > maxX) {
|
|
194
|
-
// Gutter narrower than the combined min stubs — give up
|
|
195
|
-
// and honor the target-stub line so the arrowhead still
|
|
196
|
-
// has its visible lead-in. Source stub absorbs the loss.
|
|
197
|
-
return {
|
|
198
|
-
x: to.x - MIN_TARGET_STUB_PX,
|
|
199
|
-
underBar: true,
|
|
200
|
-
range: null,
|
|
201
|
-
};
|
|
202
|
-
}
|
|
203
|
-
const range = { minX, maxX };
|
|
204
|
-
// Try the natural gutter midpoint, clamped into the range.
|
|
205
|
-
const naturalMid = (from.x + to.x) / 2;
|
|
206
|
-
const mid = Math.max(minX, Math.min(maxX, naturalMid));
|
|
207
|
-
if (!grid.hasObstacle(mid, yMin, yMax)) {
|
|
208
|
-
return { x: mid, underBar: false, range };
|
|
209
|
-
}
|
|
210
|
-
// Walk inside the satisfiable range looking for a clear strip.
|
|
211
|
-
const span = maxX - minX;
|
|
212
|
-
for (let step = 1; step <= span; step++) {
|
|
213
|
-
const left = mid - step;
|
|
214
|
-
if (left >= minX && !grid.hasObstacle(left, yMin, yMax)) {
|
|
215
|
-
return { x: left, underBar: false, range };
|
|
216
|
-
}
|
|
217
|
-
const right = mid + step;
|
|
218
|
-
if (right <= maxX && !grid.hasObstacle(right, yMin, yMax)) {
|
|
219
|
-
return { x: right, underBar: false, range };
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
return { x: mid, underBar: true, range };
|
|
78
|
+
covered.sort((a, b) => a[0] - b[0]);
|
|
79
|
+
const gaps = [];
|
|
80
|
+
let y = lo;
|
|
81
|
+
for (const [top, bottom] of covered) {
|
|
82
|
+
if (top - y >= MIN_GAP_PX)
|
|
83
|
+
gaps.push([y, top]);
|
|
84
|
+
y = Math.max(y, bottom);
|
|
223
85
|
}
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
86
|
+
if (hi - y >= MIN_GAP_PX)
|
|
87
|
+
gaps.push([y, hi]);
|
|
88
|
+
return gaps;
|
|
89
|
+
}
|
|
90
|
+
/** Bar or container edges a vertical leg at `x` over `(y0, y1)` runs within the clearance of. */
|
|
91
|
+
function grazes(edges, x, y0, y1) {
|
|
92
|
+
const lo = Math.min(y0, y1);
|
|
93
|
+
const hi = Math.max(y0, y1);
|
|
94
|
+
let n = 0;
|
|
95
|
+
for (const e of edges) {
|
|
96
|
+
if (Math.abs(e.x - x) > EDGE_CLEARANCE_PX)
|
|
97
|
+
continue;
|
|
98
|
+
if (e.bottom <= lo || e.top >= hi)
|
|
99
|
+
continue;
|
|
100
|
+
n++;
|
|
101
|
+
}
|
|
102
|
+
return n;
|
|
103
|
+
}
|
|
104
|
+
/** Blocks a vertical leg at `x` over `(y0, y1)` passes behind. */
|
|
105
|
+
function crossings(blocks, x, y0, y1) {
|
|
106
|
+
const lo = Math.min(y0, y1);
|
|
107
|
+
const hi = Math.max(y0, y1);
|
|
108
|
+
let n = 0;
|
|
109
|
+
for (const b of blocks) {
|
|
110
|
+
if (b.height <= 0 || x <= b.x || x >= b.x + b.width)
|
|
111
|
+
continue;
|
|
112
|
+
if (b.y + b.height <= lo || b.y >= hi)
|
|
113
|
+
continue;
|
|
114
|
+
n++;
|
|
232
115
|
}
|
|
233
|
-
|
|
234
|
-
return { x: from.x + STUB_OUT_PX, underBar: true, range: null };
|
|
116
|
+
return n;
|
|
235
117
|
}
|
|
236
118
|
/**
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
* still within nudge distance of another bracket — returns the input
|
|
242
|
-
* X clamped to the range and signals `forceUnderBar` so the leg paints
|
|
243
|
-
* BEHIND the bars and bracket strokes (z-order: bracket renders after
|
|
244
|
-
* under-bar edges, so the bracket cleanly covers the colliding
|
|
245
|
-
* portion).
|
|
119
|
+
* Pick the gap for an arrow from `from` (on the source's edge) to `to` (on
|
|
120
|
+
* the target's edge): clear of every bar edge first, then fewest bars
|
|
121
|
+
* crossed, then nearest the source. With no free gap at all, the leg runs
|
|
122
|
+
* just past the source and passes behind whatever is there.
|
|
246
123
|
*/
|
|
247
|
-
function
|
|
248
|
-
const
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
124
|
+
function pickGap(from, to, dir, obstacles) {
|
|
125
|
+
const xLo = Math.min(from.x, to.x);
|
|
126
|
+
const xHi = Math.max(from.x, to.x);
|
|
127
|
+
const lo = Math.min(from.y, to.y);
|
|
128
|
+
const hi = Math.max(from.y, to.y);
|
|
129
|
+
let best = null;
|
|
130
|
+
for (const gap of freeGaps(obstacles.blocks, xLo, xHi, lo, hi)) {
|
|
131
|
+
const centre = (gap[0] + gap[1]) / 2;
|
|
132
|
+
const key = [
|
|
133
|
+
grazes(obstacles.edges, from.x, from.y, centre) +
|
|
134
|
+
grazes(obstacles.edges, to.x, centre, to.y),
|
|
135
|
+
crossings(obstacles.blocks, from.x, from.y, centre) +
|
|
136
|
+
crossings(obstacles.blocks, to.x, centre, to.y),
|
|
137
|
+
Math.abs(centre - from.y),
|
|
138
|
+
];
|
|
139
|
+
if (!best || lessThan(key, best.key))
|
|
140
|
+
best = { gap, centre, key };
|
|
262
141
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
if (stillNear.length === 0) {
|
|
282
|
-
return { x: clamped, forceUnderBar: false };
|
|
283
|
-
}
|
|
142
|
+
if (best)
|
|
143
|
+
return { gap: best.gap, centre: best.centre };
|
|
144
|
+
const reach = Math.min(4, (hi - lo) / 2);
|
|
145
|
+
const centre = from.y + dir * reach;
|
|
146
|
+
return { gap: [lo, hi], centre };
|
|
147
|
+
}
|
|
148
|
+
function lessThan(a, b) {
|
|
149
|
+
for (let i = 0; i < a.length; i++) {
|
|
150
|
+
if (a[i] !== b[i])
|
|
151
|
+
return a[i] < b[i];
|
|
152
|
+
}
|
|
153
|
+
return false;
|
|
154
|
+
}
|
|
155
|
+
/** Lane offsets to try, the first one toward the source (so the arrowhead keeps its leg). */
|
|
156
|
+
function laneOffsets(dir, halfHeight) {
|
|
157
|
+
const out = [0];
|
|
158
|
+
for (let k = 1; k * CHANNEL_LANE_PX <= halfHeight + SAME_PX; k++) {
|
|
159
|
+
out.push(-dir * k * CHANNEL_LANE_PX, dir * k * CHANNEL_LANE_PX);
|
|
284
160
|
}
|
|
285
|
-
|
|
286
|
-
// the range) and force under-bar so item bars + bracket strokes
|
|
287
|
-
// mask the colliding portion of the leg.
|
|
288
|
-
const fallback = range ? Math.min(Math.max(x, range.minX), range.maxX) : x;
|
|
289
|
-
return { x: fallback, forceUnderBar: true };
|
|
161
|
+
return out;
|
|
290
162
|
}
|
|
291
163
|
/**
|
|
292
|
-
*
|
|
293
|
-
*
|
|
294
|
-
*
|
|
295
|
-
*
|
|
296
|
-
* collapse back to the centerline (rare; visual stacking accepted).
|
|
164
|
+
* Give each gap-routed arrow its lane: an arrow whose day column already
|
|
165
|
+
* has a lane in this gap joins it; otherwise the first offset no other
|
|
166
|
+
* horizontal leg over the same x's sits on, or, when the gap is full, the
|
|
167
|
+
* least crowded one.
|
|
297
168
|
*/
|
|
298
|
-
function
|
|
299
|
-
|
|
300
|
-
const
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
let groupStart = 0;
|
|
309
|
-
while (groupStart < byX.length) {
|
|
310
|
-
let groupEnd = groupStart + 1;
|
|
311
|
-
while (groupEnd < byX.length &&
|
|
312
|
-
Math.abs(byX[groupEnd].channelX - byX[groupStart].channelX) < SLOT_GROUP_TOLERANCE_PX) {
|
|
313
|
-
groupEnd++;
|
|
169
|
+
function assignLanes(pending) {
|
|
170
|
+
const placed = [];
|
|
171
|
+
const out = new Map();
|
|
172
|
+
const order = [...pending].sort((a, b) => a.centre - b.centre || a.xLo - b.xLo || a.index - b.index);
|
|
173
|
+
for (const p of order) {
|
|
174
|
+
const [lo, hi] = p.gap;
|
|
175
|
+
const mate = placed.find((q) => q.bundle === p.bundle && q.y >= lo - SAME_PX && q.y <= hi + SAME_PX);
|
|
176
|
+
let y = p.centre;
|
|
177
|
+
if (mate) {
|
|
178
|
+
y = mate.y;
|
|
314
179
|
}
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
const eMin = Math.min(entry.req.from.y, entry.req.to.y);
|
|
329
|
-
const eMax = Math.max(entry.req.from.y, entry.req.to.y);
|
|
330
|
-
let assigned = -1;
|
|
331
|
-
for (let s = 0; s < slotMaxY.length; s++) {
|
|
332
|
-
if (slotMaxY[s] <= eMin) {
|
|
333
|
-
assigned = s;
|
|
334
|
-
slotMaxY[s] = eMax;
|
|
335
|
-
break;
|
|
180
|
+
else {
|
|
181
|
+
// The gap already keeps `BLOCK_MARGIN_PX` from its neighbours.
|
|
182
|
+
const offsets = laneOffsets(p.dir, (hi - lo) / 2);
|
|
183
|
+
let bestConflicts = Number.POSITIVE_INFINITY;
|
|
184
|
+
for (const off of offsets) {
|
|
185
|
+
const cy = p.centre + off;
|
|
186
|
+
const conflicts = placed.filter((q) => q.bundle !== p.bundle &&
|
|
187
|
+
Math.abs(q.y - cy) < CHANNEL_LANE_PX - SAME_PX &&
|
|
188
|
+
q.xLo < p.xHi + BLOCK_MARGIN_PX &&
|
|
189
|
+
q.xHi > p.xLo - BLOCK_MARGIN_PX).length;
|
|
190
|
+
if (conflicts < bestConflicts) {
|
|
191
|
+
bestConflicts = conflicts;
|
|
192
|
+
y = cy;
|
|
336
193
|
}
|
|
194
|
+
if (conflicts === 0)
|
|
195
|
+
break;
|
|
337
196
|
}
|
|
338
|
-
if (assigned === -1) {
|
|
339
|
-
assigned = slotMaxY.length;
|
|
340
|
-
slotMaxY.push(eMax);
|
|
341
|
-
}
|
|
342
|
-
slotsByOrderIndex.set(entry.orderIndex, assigned);
|
|
343
197
|
}
|
|
344
|
-
|
|
198
|
+
placed.push({ y, xLo: p.xLo, xHi: p.xHi, bundle: p.bundle });
|
|
199
|
+
out.set(p.index, y);
|
|
345
200
|
}
|
|
346
|
-
return
|
|
347
|
-
}
|
|
348
|
-
/** Convert a slot index to its signed offset around the channel
|
|
349
|
-
* centerline. Slots 0/1/2/... become 0, +SLOT, -SLOT, +2*SLOT,
|
|
350
|
-
* -2*SLOT... so the FIRST edge sits on the centerline (no shift)
|
|
351
|
-
* and additional edges fan out alternately. Past 2 ± slots, slots
|
|
352
|
-
* collapse back to the centerline (visual stacking accepted). */
|
|
353
|
-
function slotOffset(slot) {
|
|
354
|
-
if (slot === 0)
|
|
355
|
-
return 0;
|
|
356
|
-
if (slot > 4)
|
|
357
|
-
return 0; // collapse beyond ±2 slots
|
|
358
|
-
const half = Math.ceil(slot / 2);
|
|
359
|
-
const sign = slot % 2 === 1 ? 1 : -1;
|
|
360
|
-
return sign * half * SLOT_SPACING_PX;
|
|
201
|
+
return out;
|
|
361
202
|
}
|
|
362
203
|
/**
|
|
363
|
-
*
|
|
364
|
-
*
|
|
365
|
-
*
|
|
366
|
-
*
|
|
204
|
+
* Route every item dependency of one chart (the host, or one isolated
|
|
205
|
+
* region) in a batch, so arrows sharing a gap can take lanes. Returns one
|
|
206
|
+
* entry per request; `null` where position already says it (a target on
|
|
207
|
+
* its source's row that starts where the source ends).
|
|
367
208
|
*/
|
|
368
|
-
function
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
209
|
+
export function routeDependencies(requests, obstacles, pixelsPerDay) {
|
|
210
|
+
const out = new Array(requests.length).fill(null);
|
|
211
|
+
const pending = [];
|
|
212
|
+
requests.forEach((req, index) => {
|
|
213
|
+
const { source: s, target: t } = req;
|
|
214
|
+
const slack = t.startX - s.endX > SAME_PX;
|
|
215
|
+
if (onSameRow(s, t) && t.startX >= s.endX - SAME_PX) {
|
|
216
|
+
if (!slack)
|
|
217
|
+
return;
|
|
218
|
+
// A dotted leg along the row, from the source's end date to the
|
|
219
|
+
// target's painted start.
|
|
220
|
+
const y = s.exitY !== undefined && within(s.exitY, t.top, t.bottom) ? s.exitY : (t.rowY ?? 0);
|
|
221
|
+
out[index] = {
|
|
222
|
+
fromId: req.fromId,
|
|
223
|
+
toId: req.toId,
|
|
224
|
+
waypoints: [
|
|
225
|
+
{ x: s.endX, y },
|
|
226
|
+
{ x: t.paintedX, y },
|
|
227
|
+
],
|
|
228
|
+
slack: true,
|
|
229
|
+
...arrowHead(t.bottom - t.top),
|
|
230
|
+
};
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
const px = sourcePortX(s, pixelsPerDay);
|
|
234
|
+
const tx = targetPortX(t, pixelsPerDay);
|
|
235
|
+
let from;
|
|
236
|
+
let to;
|
|
237
|
+
let dir;
|
|
238
|
+
if (t.top >= s.bottom - SAME_PX) {
|
|
239
|
+
dir = 1;
|
|
240
|
+
from = { x: px, y: s.bottom };
|
|
241
|
+
to = { x: tx, y: t.top };
|
|
394
242
|
}
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
243
|
+
else if (t.bottom <= s.top + SAME_PX) {
|
|
244
|
+
dir = -1;
|
|
245
|
+
from = { x: px, y: s.top };
|
|
246
|
+
to = { x: tx, y: t.bottom };
|
|
247
|
+
}
|
|
248
|
+
else {
|
|
249
|
+
// Overlapping but not on one row (a target that starts before
|
|
250
|
+
// its source ends): leave the source's bottom, run below both,
|
|
251
|
+
// come up into the target's bottom.
|
|
252
|
+
const below = Math.max(s.bottom, t.bottom) + 4;
|
|
253
|
+
out[index] = {
|
|
399
254
|
fromId: req.fromId,
|
|
400
255
|
toId: req.toId,
|
|
401
|
-
waypoints: [
|
|
402
|
-
|
|
256
|
+
waypoints: [
|
|
257
|
+
{ x: px, y: s.bottom },
|
|
258
|
+
{ x: px, y: below },
|
|
259
|
+
{ x: tx, y: below },
|
|
260
|
+
{ x: tx, y: t.bottom },
|
|
261
|
+
],
|
|
262
|
+
slack,
|
|
263
|
+
...arrowHead(t.paintedWidth),
|
|
403
264
|
};
|
|
404
|
-
|
|
265
|
+
return;
|
|
405
266
|
}
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
267
|
+
if (Math.abs(px - tx) < SAME_PX) {
|
|
268
|
+
out[index] = {
|
|
269
|
+
fromId: req.fromId,
|
|
270
|
+
toId: req.toId,
|
|
271
|
+
waypoints: [from, to],
|
|
272
|
+
slack,
|
|
273
|
+
...arrowHead(t.paintedWidth),
|
|
274
|
+
};
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
const { gap, centre } = pickGap(from, to, dir, obstacles);
|
|
278
|
+
pending.push({
|
|
279
|
+
index,
|
|
280
|
+
req,
|
|
281
|
+
from,
|
|
282
|
+
to,
|
|
283
|
+
dir,
|
|
284
|
+
gap,
|
|
285
|
+
centre,
|
|
286
|
+
xLo: Math.min(px, tx),
|
|
287
|
+
xHi: Math.max(px, tx),
|
|
288
|
+
bundle: `${Math.round(tx * 100)}:${dir}`,
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
const lanes = assignLanes(pending);
|
|
292
|
+
for (const p of pending) {
|
|
293
|
+
const y = lanes.get(p.index) ?? p.centre;
|
|
294
|
+
const slack = p.req.target.startX - p.req.source.endX > SAME_PX;
|
|
295
|
+
out[p.index] = {
|
|
296
|
+
fromId: p.req.fromId,
|
|
297
|
+
toId: p.req.toId,
|
|
298
|
+
waypoints: [p.from, { x: p.from.x, y }, { x: p.to.x, y }, p.to],
|
|
299
|
+
slack,
|
|
300
|
+
...arrowHead(p.req.target.paintedWidth),
|
|
420
301
|
};
|
|
421
302
|
}
|
|
422
|
-
return
|
|
423
|
-
}
|
|
424
|
-
/**
|
|
425
|
-
* Build the orthogonal polyline through a chosen vertical channel.
|
|
426
|
-
* Source-out-stub → vertical leg → target-in-stub. Same-row edges
|
|
427
|
-
* collapse to two points (handled by the caller's same-row skip).
|
|
428
|
-
*/
|
|
429
|
-
function buildOrthogonalPath(from, to, slotX) {
|
|
430
|
-
if (Math.abs(from.y - to.y) < 0.5) {
|
|
431
|
-
return [from, to];
|
|
432
|
-
}
|
|
433
|
-
return [from, { x: slotX, y: from.y }, { x: slotX, y: to.y }, to];
|
|
303
|
+
return out;
|
|
434
304
|
}
|
|
435
305
|
//# sourceMappingURL=edge-routing.js.map
|