@nowline/layout 0.8.6 → 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/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 -152
- 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 -30
- 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 -145
- 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 +4 -4
- 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 -214
- 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 -38
- 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 -205
- 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
|
@@ -1,31 +1,29 @@
|
|
|
1
1
|
// ParallelNode — Renderable for a `parallel { ... }` block. Stacks
|
|
2
|
-
// children top-to-bottom (each child owns a fresh
|
|
3
|
-
//
|
|
4
|
-
// `
|
|
5
|
-
//
|
|
6
|
-
//
|
|
2
|
+
// children top-to-bottom inside its body (each child owns a fresh
|
|
3
|
+
// sub-track) under a header band, the way a group is drawn
|
|
4
|
+
// (specs/true-width-layout.md §3.3; `container-header-geometry.ts`). Each
|
|
5
|
+
// child is sequenced via the injected `helpers.sequenceOne` callback
|
|
6
|
+
// (`sequence-one.ts`), which dispatches to `sequenceItem`, `GroupNode` or a
|
|
7
|
+
// nested `ParallelNode`.
|
|
7
8
|
|
|
8
9
|
import type { GroupBlock, ItemDeclaration, ParallelBlock } from '@nowline/core';
|
|
10
|
+
import { containerChrome } from '../container-header-geometry.js';
|
|
9
11
|
import { propValues } from '../dsl-utils.js';
|
|
10
12
|
import { computeContainerInlineDatePins, pickInlineDate } from '../inline-date-pin-geometry.js';
|
|
11
|
-
import
|
|
13
|
+
import {
|
|
14
|
+
type LayoutContext,
|
|
15
|
+
type LayoutHelpers,
|
|
16
|
+
newCursor,
|
|
17
|
+
type TrackCursor,
|
|
18
|
+
} from '../layout-context.js';
|
|
12
19
|
import { resolveStyle } from '../style-resolution.js';
|
|
13
|
-
import { TRACK_BLOCK_TAIL_GUTTER_PX } from '../themes/shared.js';
|
|
14
20
|
import type { BoundingBox, PositionedParallel, PositionedTrackChild } from '../types.js';
|
|
15
|
-
|
|
16
|
-
export interface ParallelNodeDeps {
|
|
17
|
-
sequenceOne: (
|
|
18
|
-
child: ItemDeclaration | GroupBlock | ParallelBlock,
|
|
19
|
-
cursor: TrackCursor,
|
|
20
|
-
ctx: LayoutContext,
|
|
21
|
-
) => PositionedTrackChild;
|
|
22
|
-
newCursor: (x: number, y: number) => TrackCursor;
|
|
23
|
-
}
|
|
21
|
+
import { waveFloorX } from '../wave-layout.js';
|
|
24
22
|
|
|
25
23
|
export class ParallelNode {
|
|
26
24
|
constructor(
|
|
27
25
|
public readonly node: ParallelBlock,
|
|
28
|
-
private readonly
|
|
26
|
+
private readonly helpers: LayoutHelpers,
|
|
29
27
|
) {}
|
|
30
28
|
|
|
31
29
|
get id(): string {
|
|
@@ -34,18 +32,33 @@ export class ParallelNode {
|
|
|
34
32
|
|
|
35
33
|
/**
|
|
36
34
|
* Sequence children into stacked sub-tracks, advance the parent
|
|
37
|
-
* `cursor`
|
|
38
|
-
*
|
|
39
|
-
* `PositionedParallel`.
|
|
35
|
+
* `cursor` to the parallel's end in time (the latest track end), and
|
|
36
|
+
* return a `PositionedParallel`.
|
|
40
37
|
*/
|
|
41
38
|
place(cursor: TrackCursor, ctx: LayoutContext): PositionedParallel {
|
|
42
39
|
const { node } = this;
|
|
43
|
-
const {
|
|
40
|
+
const { helpers } = this;
|
|
44
41
|
const style = resolveStyle('parallel', node.properties, ctx.styleCtx);
|
|
45
|
-
|
|
46
|
-
|
|
42
|
+
// The block's own wave and its lead wave floor the shared start
|
|
43
|
+
// (specs/waves.md §5.1); each track is floored again by its own
|
|
44
|
+
// wave where it is placed. A no-op without waves.
|
|
45
|
+
const startX = waveFloorX(node, cursor.x, ctx);
|
|
46
|
+
const afterDate = pickInlineDate(propValues(node.properties, 'after'));
|
|
47
|
+
const beforeDate = pickInlineDate(propValues(node.properties, 'before'));
|
|
48
|
+
const title = node.title ?? node.name;
|
|
49
|
+
const chrome = containerChrome({
|
|
50
|
+
drawn: true,
|
|
51
|
+
hasTitle: Boolean(title),
|
|
52
|
+
hasPins: Boolean(afterDate || beforeDate),
|
|
53
|
+
hasBracket: style.bracket !== 'none',
|
|
54
|
+
});
|
|
55
|
+
const firstTrackY = cursor.y + chrome.header + chrome.pad;
|
|
47
56
|
const children: PositionedTrackChild[] = [];
|
|
48
|
-
|
|
57
|
+
// The tracks' span in time: the bracket and body open on the
|
|
58
|
+
// earliest track start and close on the latest track end, the join
|
|
59
|
+
// (bars span exactly their dates, specs/true-width-layout.md §3.1).
|
|
60
|
+
let minTimeLeft = Number.POSITIVE_INFINITY;
|
|
61
|
+
let maxTimeRight = Number.NEGATIVE_INFINITY;
|
|
49
62
|
let accumulatedHeight = 0;
|
|
50
63
|
|
|
51
64
|
// Each child of a parallel block lives on its own sub-track,
|
|
@@ -61,47 +74,65 @@ export class ParallelNode {
|
|
|
61
74
|
for (const child of node.content) {
|
|
62
75
|
if (child.$type === 'DescriptionDirective') continue;
|
|
63
76
|
ctx.currentFlowKey = `${previousFlowKey}/par:${parId}#${childIndex}`;
|
|
64
|
-
const subCursor =
|
|
65
|
-
const positioned =
|
|
77
|
+
const subCursor = newCursor(startX, firstTrackY + accumulatedHeight);
|
|
78
|
+
const positioned = helpers.sequenceOne(
|
|
66
79
|
child as ItemDeclaration | GroupBlock,
|
|
67
80
|
subCursor,
|
|
68
81
|
ctx,
|
|
69
82
|
);
|
|
70
83
|
children.push(positioned);
|
|
71
84
|
accumulatedHeight += Math.max(ctx.bandScale.step(), subCursor.height);
|
|
72
|
-
|
|
85
|
+
minTimeLeft = Math.min(minTimeLeft, subCursor.startX);
|
|
86
|
+
maxTimeRight = Math.max(maxTimeRight, subCursor.x);
|
|
73
87
|
childIndex++;
|
|
74
88
|
}
|
|
75
89
|
ctx.currentFlowKey = previousFlowKey;
|
|
90
|
+
if (children.length === 0) {
|
|
91
|
+
// Under `show` an empty box still opens on a working day.
|
|
92
|
+
minTimeLeft = ctx.scale.showsNonWorking ? ctx.scale.startX(startX) : startX;
|
|
93
|
+
maxTimeRight = minTimeLeft;
|
|
94
|
+
}
|
|
76
95
|
|
|
96
|
+
// Each track's height carries the inter-row gap below it; the body
|
|
97
|
+
// ends `pad` below the last track's painted bottom, and the cursor
|
|
98
|
+
// adds the gap back after the body.
|
|
99
|
+
const step = ctx.bandScale.step();
|
|
100
|
+
const interRowGap = step - ctx.bandScale.bandwidth();
|
|
101
|
+
const contentHeight = Math.max(step, accumulatedHeight) - interRowGap;
|
|
77
102
|
const box: BoundingBox = {
|
|
78
|
-
x:
|
|
79
|
-
y:
|
|
80
|
-
width:
|
|
81
|
-
height:
|
|
103
|
+
x: minTimeLeft,
|
|
104
|
+
y: cursor.y,
|
|
105
|
+
width: maxTimeRight - minTimeLeft,
|
|
106
|
+
height: chrome.header + chrome.pad + contentHeight + chrome.pad,
|
|
82
107
|
};
|
|
83
108
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
109
|
+
// No tail gutter: the parallel joins on its latest track end in
|
|
110
|
+
// time, as engine C does (specs/scheduler-unification.md §8
|
|
111
|
+
// decisions 6, 8).
|
|
112
|
+
cursor.startX = box.x;
|
|
113
|
+
cursor.x = maxTimeRight;
|
|
114
|
+
cursor.height = Math.max(cursor.height, box.height + interRowGap);
|
|
87
115
|
|
|
88
116
|
const id = node.name;
|
|
117
|
+
// `after:` the parallel is a time path: its latest track end.
|
|
89
118
|
if (id) {
|
|
90
119
|
ctx.entityLeftEdges.set(id, box.x);
|
|
91
|
-
ctx.entityRightEdges.set(id,
|
|
120
|
+
ctx.entityRightEdges.set(id, maxTimeRight);
|
|
92
121
|
}
|
|
93
122
|
|
|
94
123
|
const inlineDatePins = computeContainerInlineDatePins({
|
|
95
124
|
box,
|
|
96
|
-
afterDate
|
|
97
|
-
beforeDate
|
|
125
|
+
afterDate,
|
|
126
|
+
beforeDate,
|
|
127
|
+
title,
|
|
98
128
|
});
|
|
99
129
|
|
|
100
130
|
return {
|
|
101
131
|
kind: 'parallel',
|
|
102
132
|
id,
|
|
103
|
-
title
|
|
133
|
+
title,
|
|
104
134
|
box,
|
|
135
|
+
headerHeight: chrome.header,
|
|
105
136
|
children,
|
|
106
137
|
style,
|
|
107
138
|
inlineDatePins: inlineDatePins.length > 0 ? inlineDatePins : undefined,
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
// Region markers — an isolated region's own anchors and milestones, drawn
|
|
2
|
+
// in a marker row of the region's own, under its label tab
|
|
3
|
+
// (specs/rendering.md § Included Roadmap Region). The row packs with the
|
|
4
|
+
// host's packer and label rules, bounded by the region's dashed border
|
|
5
|
+
// instead of the chart; the region's lanes move down by its height, and
|
|
6
|
+
// each cut line stops at the region's last lane.
|
|
7
|
+
|
|
8
|
+
import type { AnchorDeclaration, IsolatedRegion } from '@nowline/core';
|
|
9
|
+
import type { LayoutContext } from '../layout-context.js';
|
|
10
|
+
import { estimateTextWidth } from '../text-measure.js';
|
|
11
|
+
import type { PositionedAnchor, PositionedIncludeRegion, PositionedMilestone } from '../types.js';
|
|
12
|
+
import { buildAnchors } from './anchor-node.js';
|
|
13
|
+
import {
|
|
14
|
+
datePinnedMarkerEntries,
|
|
15
|
+
type MarkerEntity,
|
|
16
|
+
markerArrows,
|
|
17
|
+
markerLabelsRightX,
|
|
18
|
+
markerRowPlacements,
|
|
19
|
+
packMarkerRow,
|
|
20
|
+
} from './marker-band.js';
|
|
21
|
+
import { MARKER_ROW_PITCH_PX } from './marker-geometry.js';
|
|
22
|
+
import { buildMilestones, type MilestonePlacement, positionMilestones } from './milestone-node.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* How far a region's marker labels stay inside the region box on either
|
|
26
|
+
* side: the renderer's 8 px border inset plus 8 px of air.
|
|
27
|
+
*/
|
|
28
|
+
export const REGION_MARKER_INSET_X_PX = 16;
|
|
29
|
+
|
|
30
|
+
/** The region marker band's top, below the region's top border. */
|
|
31
|
+
const REGION_MARKER_BAND_TOP_PX = 8;
|
|
32
|
+
|
|
33
|
+
/** Air between the region marker band and the region's first lane. */
|
|
34
|
+
const REGION_MARKER_BAND_GAP_PX = 6;
|
|
35
|
+
|
|
36
|
+
/** A region's placed markers and how far its lanes move down for them. */
|
|
37
|
+
export interface RegionMarkers {
|
|
38
|
+
anchors: PositionedAnchor[];
|
|
39
|
+
milestones: PositionedMilestone[];
|
|
40
|
+
/** The lanes' shift: 0 when the region has no marker to draw. */
|
|
41
|
+
laneShift: number;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Place `region`'s anchors and milestones once its lanes are laid out
|
|
46
|
+
* from `lanesTopY` down to `lanesBottomY`, inside a region whose top
|
|
47
|
+
* border is at `regionTopY`. The pack is bounded by the region's left
|
|
48
|
+
* inset and by `chartRightX` (the canvas before finalize grows it) less
|
|
49
|
+
* the right inset; a label that fits on neither side packs to the right
|
|
50
|
+
* and finalize f3 grows the canvas for it (`regionMarkerCanvasRightX`).
|
|
51
|
+
* The caller shifts the lanes by `laneShift`.
|
|
52
|
+
*/
|
|
53
|
+
export function placeRegionMarkers(
|
|
54
|
+
region: IsolatedRegion,
|
|
55
|
+
ctx: LayoutContext,
|
|
56
|
+
regionTopY: number,
|
|
57
|
+
lanesTopY: number,
|
|
58
|
+
lanesBottomY: number,
|
|
59
|
+
chartRightX: number,
|
|
60
|
+
): RegionMarkers {
|
|
61
|
+
// A milestone `after:` an anchor reads the anchor's edge. Without waves
|
|
62
|
+
// the edges are already seeded for the lanes; with waves they are
|
|
63
|
+
// seeded here, after the lanes, so the lanes still see only the wave
|
|
64
|
+
// edges.
|
|
65
|
+
seedRegionAnchorEdges(region.content.anchors, ctx);
|
|
66
|
+
const placements = buildMilestones(region.content.milestones, ctx);
|
|
67
|
+
const entries = regionMarkerEntries(region.content.anchors, placements, ctx);
|
|
68
|
+
if (entries.length === 0) return { anchors: [], milestones: [], laneShift: 0 };
|
|
69
|
+
|
|
70
|
+
const leftX = REGION_MARKER_INSET_X_PX;
|
|
71
|
+
const packed = packMarkerRow(
|
|
72
|
+
entries,
|
|
73
|
+
leftX,
|
|
74
|
+
markerLabelsRightX(
|
|
75
|
+
entries,
|
|
76
|
+
leftX,
|
|
77
|
+
chartRightX - REGION_MARKER_INSET_X_PX,
|
|
78
|
+
estimateTextWidth,
|
|
79
|
+
),
|
|
80
|
+
estimateTextWidth,
|
|
81
|
+
markerArrows(placements),
|
|
82
|
+
);
|
|
83
|
+
const bandTopY = regionTopY + REGION_MARKER_BAND_TOP_PX;
|
|
84
|
+
const laneShift =
|
|
85
|
+
bandTopY + packed.rowCount * MARKER_ROW_PITCH_PX + REGION_MARKER_BAND_GAP_PX - lanesTopY;
|
|
86
|
+
const cutBottomY = lanesBottomY + laneShift;
|
|
87
|
+
|
|
88
|
+
const rows = markerRowPlacements(packed.placements, bandTopY);
|
|
89
|
+
ctx.markerRowPlacements = rows;
|
|
90
|
+
ctx.chartTopY = lanesTopY + laneShift;
|
|
91
|
+
ctx.swimlaneBottomY = cutBottomY;
|
|
92
|
+
const anchors = buildAnchors(region.content.anchors, ctx, new Set());
|
|
93
|
+
// Placement read each item predecessor's y before the lanes moved; an
|
|
94
|
+
// arrow from a region marker starts on its row in `rows`.
|
|
95
|
+
const milestones = positionMilestones(
|
|
96
|
+
placements.map((p) => ({
|
|
97
|
+
...p,
|
|
98
|
+
cutTopY: ctx.chartTopY,
|
|
99
|
+
cutBottomY,
|
|
100
|
+
slackArrows: p.slackArrows?.map((a) => ('y' in a ? { x: a.x, y: a.y + laneShift } : a)),
|
|
101
|
+
})),
|
|
102
|
+
rows,
|
|
103
|
+
);
|
|
104
|
+
return { anchors, milestones, laneShift };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* The canvas right edge `region`'s marker labels need: a label that fits
|
|
109
|
+
* on neither side of its marker inside the region's insets grows the
|
|
110
|
+
* canvas until it fits on the right, the host's marker-row rule. Returns
|
|
111
|
+
* `chartRightX` when every label fits.
|
|
112
|
+
*/
|
|
113
|
+
export function regionMarkerCanvasRightX(
|
|
114
|
+
region: PositionedIncludeRegion,
|
|
115
|
+
chartRightX: number,
|
|
116
|
+
): number {
|
|
117
|
+
const entries: MarkerEntity[] = [
|
|
118
|
+
...(region.anchors ?? []).map((a) => positionedEntry(a, false)),
|
|
119
|
+
...(region.milestones ?? []).map((m) => positionedEntry(m, true)),
|
|
120
|
+
];
|
|
121
|
+
if (entries.length === 0) return chartRightX;
|
|
122
|
+
const rightX = markerLabelsRightX(
|
|
123
|
+
entries,
|
|
124
|
+
region.box.x + REGION_MARKER_INSET_X_PX,
|
|
125
|
+
chartRightX - REGION_MARKER_INSET_X_PX,
|
|
126
|
+
estimateTextWidth,
|
|
127
|
+
);
|
|
128
|
+
return rightX + REGION_MARKER_INSET_X_PX;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/** The right end of `region`'s rightmost marker label, or 0 with none. */
|
|
132
|
+
export function regionMarkerLabelsRightX(region: PositionedIncludeRegion): number {
|
|
133
|
+
let rightX = 0;
|
|
134
|
+
for (const m of [...(region.anchors ?? []), ...(region.milestones ?? [])]) {
|
|
135
|
+
rightX = Math.max(rightX, m.labelBox.x + m.labelBox.width);
|
|
136
|
+
}
|
|
137
|
+
return rightX;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* The host's marker order: the date-pinned anchors, the dated milestones,
|
|
142
|
+
* then the after-only ones, so the stable sort in `packMarkerRow` breaks
|
|
143
|
+
* a tie on x the same way the host's band does.
|
|
144
|
+
*/
|
|
145
|
+
function regionMarkerEntries(
|
|
146
|
+
anchors: ReadonlyMap<string, AnchorDeclaration>,
|
|
147
|
+
milestones: readonly MilestonePlacement[],
|
|
148
|
+
ctx: LayoutContext,
|
|
149
|
+
): MarkerEntity[] {
|
|
150
|
+
const entries: MarkerEntity[] = datePinnedMarkerEntries(anchors, new Map(), ctx.scale);
|
|
151
|
+
const milestoneEntry = (m: MilestonePlacement): MarkerEntity => ({
|
|
152
|
+
id: m.id,
|
|
153
|
+
centerX: m.slipX ?? m.x,
|
|
154
|
+
radius: m.radius,
|
|
155
|
+
title: m.title,
|
|
156
|
+
fontSize: 10,
|
|
157
|
+
bold: true,
|
|
158
|
+
...(m.slipX !== undefined ? { slipFromX: m.x } : {}),
|
|
159
|
+
});
|
|
160
|
+
entries.push(...milestones.filter((m) => m.fixed).map(milestoneEntry));
|
|
161
|
+
entries.push(...milestones.filter((m) => !m.fixed).map(milestoneEntry));
|
|
162
|
+
return entries;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
function positionedEntry(m: PositionedAnchor | PositionedMilestone, bold: boolean): MarkerEntity {
|
|
166
|
+
// A slipped milestone's label sits at its slipped diamond.
|
|
167
|
+
const slipX = 'slip' in m ? m.slip?.x : undefined;
|
|
168
|
+
return {
|
|
169
|
+
id: m.id ?? '',
|
|
170
|
+
centerX: slipX ?? m.center.x,
|
|
171
|
+
radius: m.radius,
|
|
172
|
+
title: m.title,
|
|
173
|
+
fontSize: 10,
|
|
174
|
+
bold,
|
|
175
|
+
// The label is forced right of the slipped diamond, as in the pack
|
|
176
|
+
// (`regionMarkerEntries`) and f3's host entries.
|
|
177
|
+
...(slipX !== undefined ? { slipFromX: m.center.x } : {}),
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Seed a region's dated anchors into its context's edge maps at their x on
|
|
183
|
+
* the host's scale, the way the host seeds its own (`phases/place.ts`).
|
|
184
|
+
* An anchor outside the date window is skipped, as on the host.
|
|
185
|
+
*/
|
|
186
|
+
export function seedRegionAnchorEdges(
|
|
187
|
+
anchors: ReadonlyMap<string, AnchorDeclaration>,
|
|
188
|
+
ctx: LayoutContext,
|
|
189
|
+
): void {
|
|
190
|
+
for (const e of datePinnedMarkerEntries(anchors, new Map(), ctx.scale)) {
|
|
191
|
+
ctx.entityLeftEdges.set(e.id, e.centerX);
|
|
192
|
+
ctx.entityRightEdges.set(e.id, e.centerX);
|
|
193
|
+
}
|
|
194
|
+
}
|