@weasel-js/diagram 1.10.1 → 1.11.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.
@@ -1,15 +1,16 @@
1
1
  import * as _weasel_js_geom from '@weasel-js/geom';
2
- import { Path, PoseDescriptor, Vec2 } from '@weasel-js/core/math';
3
- import { SimulationNode, createSimulation } from '@weasel-js/simulation';
2
+ import { Path, Vec2, PoseDescriptor } from '@weasel-js/core/math';
4
3
 
5
4
  /**
6
5
  * The shapes a built body can wear, as paths in the node's bounds.
7
6
  *
8
7
  * These are the flowchart vocabulary — a decision is a diamond, an I/O step a
9
- * parallelogram, a terminator a stadium — plus `'rect'` for everything else and
8
+ * parallelogram, a terminator a stadium, a demultiplexer a trapezoid, a preparation step a hexagon —
9
+ * plus `'rect'` for everything else and
10
10
  * a `Path` escape hatch for a shape the vocabulary does not have. The path is
11
11
  * built in world coordinates from the bounds it is given, the same way the
12
- * kit's own `clipFromPose` factories are.
12
+ * kit's own `clipFromPose` factories are; a custom `Path` is drawn in a unit
13
+ * square and stretched over them, as a mark is.
13
14
  */
14
15
 
15
16
  /** An axis-aligned box. This is `PoseDescriptor.getBounds`'s return shape
@@ -22,8 +23,10 @@ interface Bounds {
22
23
  width: number;
23
24
  height: number;
24
25
  }
25
- /** A named flowchart shape, or a path a consumer built themselves. */
26
- type Outline = 'rect' | 'diamond' | 'stadium' | 'parallelogram' | Path;
26
+ /** A named flowchart shape, or a path a consumer built themselves in a unit
27
+ * square — `(0, 0)` the box's top-left, `(1, 1)` its bottom-right — so it
28
+ * follows its node wherever layout or a drag puts it. */
29
+ type Outline = 'rect' | 'diamond' | 'stadium' | 'parallelogram' | 'trapezoid' | 'hexagon' | Path;
27
30
  /** The path `outline` describes inside `bounds`. A zero-area box gives a
28
31
  * zero-area rect rather than a degenerate polygon. */
29
32
  declare function outlinePath(outline: Outline, bounds: Bounds): Path;
@@ -54,6 +57,96 @@ declare function boxForContent(outline: Outline, content: {
54
57
  height: number;
55
58
  };
56
59
 
60
+ /**
61
+ * Where a port sits on its node, in **normalized bounds coordinates**:
62
+ * `(0, 0)` is the top-left corner of the node's AABB and `(1, 1)` the
63
+ * bottom-right. `COMPASS` names the eight positions almost everything wants.
64
+ *
65
+ * Normalized rather than absolute because a node's size is authored data that
66
+ * resize, align and distribute all write — a port fixed at an offset would
67
+ * drift off the shape the first time one of them ran.
68
+ */
69
+ interface PortAnchor {
70
+ u: number;
71
+ v: number;
72
+ }
73
+ /** One more place a port may sit, and optionally the direction it faces there. */
74
+ interface PortSite extends PortAnchor {
75
+ normal?: Vec2 | null;
76
+ }
77
+ /** One attachment point declared on a node. */
78
+ interface PortSpec {
79
+ /** Unique within the node. Edges name this, so it outlives a re-layout. */
80
+ id: string;
81
+ at: PortAnchor;
82
+ /** Other places the port may sit. An edge meeting the port takes whichever
83
+ * of `at` and these faces the edge's next point most directly, so a
84
+ * diamond's second exit can leave by its top or its bottom vertex, toward
85
+ * wherever its wire goes. Everything else that shows a port where it is —
86
+ * the connect affordance, `portsOf` — reads `at`. A site faces away from
87
+ * the node's center unless it gives a `normal` of its own. */
88
+ sites?: readonly PortSite[];
89
+ /** Consulted by a connect gesture's validity predicate. Uninterpreted here. */
90
+ type?: string;
91
+ /** What the port is for, drawn beside it outside the node: beside the
92
+ * wire rather than across it. A built body draws it and keeps it in its
93
+ * `margin`; any other participant gets it on its resolved `Port`. */
94
+ label?: string;
95
+ /** The direction an edge leaves along, overriding the one the anchor
96
+ * implies. `null` means "no preferred direction" — a router goes straight
97
+ * at it. Applies at `at`; each of `sites` says its own. */
98
+ normal?: Vec2 | null;
99
+ }
100
+ /**
101
+ * The trait that makes an existing scene node a diagram participant.
102
+ *
103
+ * Attached to a node's `data`, not a node kind the plugin mints: a text block,
104
+ * an image, a path, a group or a plain rect all take part by carrying this,
105
+ * and nothing has to be authored through this package to be connectable.
106
+ */
107
+ interface DiagramNode {
108
+ /** Defaults to {@link DEFAULT_PORTS} — the four edge midpoints of the
109
+ * node's own bounds, which every node has. */
110
+ ports?: readonly PortSpec[];
111
+ /** Layout must not move this node. */
112
+ pinned?: boolean;
113
+ /** What the node is *drawn* as. Present on a built body; absent on a
114
+ * participant that already had a look of its own — a text block, an image,
115
+ * a path — which keeps whatever painter it already matched. */
116
+ outline?: Outline;
117
+ /** Present on a group's box: a node drawn around the participants it
118
+ * depends on, which layout keeps together and leaves room for. The box
119
+ * itself is never laid out — it derives its pose from its members — but an
120
+ * edge can attach to it like any participant. */
121
+ group?: DiagramGroup;
122
+ /** How far past its bounds something drawn with it reaches, on each side —
123
+ * a built body's port labels. Layout keeps neighbors that far off too. */
124
+ margin?: GroupInset;
125
+ }
126
+ /** The group half of a {@link DiagramNode}. */
127
+ interface DiagramGroup {
128
+ /** Room the box keeps around its members, in world units: one number for
129
+ * every side, or per side. Default 12 on each. */
130
+ inset?: number | Partial<GroupInset>;
131
+ /** The narrowest the box may be, in world units, as room for its label.
132
+ * A box its members leave narrower grows evenly on both sides. */
133
+ minWidth?: number;
134
+ }
135
+ /** A port resolved against a node's current pose: where it is in world
136
+ * coordinates, and which way an edge leaves it. */
137
+ interface Port {
138
+ id: string;
139
+ /** The node this port belongs to. */
140
+ nodeId: string;
141
+ /** World point an edge attaches to. */
142
+ point: Vec2;
143
+ /** Unit vector pointing away from the node, or `null` when the port
144
+ * declared no direction. Rotated with the node. */
145
+ normal: Vec2 | null;
146
+ type?: string;
147
+ label?: string;
148
+ }
149
+
57
150
  /**
58
151
  * Reading the `DiagramNode` trait off a scene node.
59
152
  *
@@ -140,6 +233,15 @@ interface GraphNode {
140
233
  pinned: boolean;
141
234
  /** The innermost group it belongs to, when it belongs to one. */
142
235
  group?: string;
236
+ /** Where its ports sit. Absent, the four compass midpoints
237
+ * (`DEFAULT_PORTS`). */
238
+ ports?: readonly PortSpec[];
239
+ /** The shape its ports are cast onto, as `portsOf` casts them. Absent,
240
+ * its bounds. */
241
+ outline?: Outline;
242
+ /** How far past `bounds` it reaches (`DiagramNode.margin`). `planOf` lays
243
+ * it out as that much bigger. */
244
+ margin?: GroupInset;
143
245
  }
144
246
  /** A box drawn around some participants, which a layout keeps together and
145
247
  * leaves room for. */
@@ -162,6 +264,37 @@ interface GraphEdge {
162
264
  id: string;
163
265
  from: string;
164
266
  to: string;
267
+ /** The port it leaves `from` by, when it names one. */
268
+ fromPort?: string;
269
+ /** The port it arrives at `to` by, when it names one. */
270
+ toPort?: string;
271
+ /** Room its label needs: the label's box, and how far its near side sits
272
+ * from the wire (`DiagramLabel.offset`). `layered` sets the label beside
273
+ * its wire in a rank of its own. */
274
+ label?: EdgeLabelRoom;
275
+ /** Where its route runs now, from `from` to `to`. `layered` starts each
276
+ * placeholder where the route already crosses that rank, so a re-run keeps
277
+ * the order the last one settled on. */
278
+ waypoints?: readonly Vec2[];
279
+ /** Its route is someone's own: layout leaves it, and keeps no room for it
280
+ * in the ranks it crosses. */
281
+ authored?: boolean;
282
+ /** The edge as drawn, when this one stands in for an edge to or from a
283
+ * group (see `expandGroupEdges`). */
284
+ origin?: {
285
+ id: string;
286
+ from: string;
287
+ to: string;
288
+ };
289
+ }
290
+ /** The room a {@link GraphEdge}'s label takes, in world units. */
291
+ interface EdgeLabelRoom {
292
+ width: number;
293
+ height: number;
294
+ /** Between the wire and the label's near side, to the left of the
295
+ * direction of travel; negative for the right. Default 0, which centers
296
+ * the label on the wire. */
297
+ offset?: number;
165
298
  }
166
299
  /**
167
300
  * Nodes, edges, and the two adjacency reads a layout needs.
@@ -229,7 +362,8 @@ interface LayoutOptions {
229
362
  /** Between one rank and the next. Default 96. */
230
363
  rankGap?: number;
231
364
  /** How `layered` orders nodes within a rank. `'seeded'` (default) keeps
232
- * the order they already sit in; `'barycenter'` reorders to reduce edge
365
+ * the order they already sit in, an edge's place included: where its
366
+ * route crosses the rank now. `'barycenter'` reorders to reduce edge
233
367
  * crossings, for a generated diagram with no arranged order to keep.
234
368
  * Other layouts ignore it. */
235
369
  order?: 'seeded' | 'barycenter';
@@ -239,12 +373,53 @@ interface LayoutOptions {
239
373
  * `1e-6` — a float-noise floor, not a snapping quantum. Raise it to let a
240
374
  * `force` pass that has stopped doing anything useful commit nothing. */
241
375
  tolerance?: number;
376
+ /** `chain` only: the ids that stand on the line, in order. Unset, the
377
+ * longest path through the graph. */
378
+ chain?: readonly string[];
379
+ /** `chain` only: which side of the line everything else hangs on, along
380
+ * the cross axis. Default `'after'`. */
381
+ chainSide?: 'after' | 'before';
242
382
  }
243
383
  /** The new top-left for each node that moves. A node that is already there is
244
384
  * absent, so an empty result means "nothing to do". */
245
385
  type LayoutResult = ReadonlyMap<string, Vec2>;
246
- /** Every layout in this package has this shape, and so does a consumer's. */
247
- type LayoutFn = (graph: Graph, opts?: LayoutOptions) => LayoutResult;
386
+ /** A layout's answer with the edge routes it laid out, keyed by edge id. */
387
+ interface LayoutPlan {
388
+ moves: LayoutResult;
389
+ /** For each edge layout routed: the points it runs through, from its
390
+ * `from` end to its `to` end. An edge absent runs straight between its
391
+ * ends. Absent altogether, the layout routes nothing. */
392
+ waypoints?: ReadonlyMap<string, readonly Vec2[]>;
393
+ /** For each edge layout left label room beside: the point on its route
394
+ * the room is beside, as a `DiagramLabel`'s `{ near }`. */
395
+ labels?: ReadonlyMap<string, Vec2>;
396
+ /** For each edge layout named ports for: the port it leaves `from` by and
397
+ * the one it arrives at `to` by, either absent where layout names none.
398
+ * Absent altogether, the layout names no ports. */
399
+ ports?: ReadonlyMap<string, LayoutPorts>;
400
+ }
401
+ /** The ports a layout names for one edge. */
402
+ interface LayoutPorts {
403
+ from?: string;
404
+ to?: string;
405
+ }
406
+ /** Every layout in this package has this shape, and so does a consumer's.
407
+ * One that routes edges as well says so with `plan`. */
408
+ interface LayoutFn {
409
+ (graph: Graph, opts?: LayoutOptions): LayoutResult;
410
+ readonly plan?: (graph: Graph, opts?: LayoutOptions) => LayoutPlan;
411
+ }
412
+ /** A layout that answers with a whole plan, now or later: ELK's, say, or one
413
+ * computed on a server. A live run asks it once, with the graph it will ease
414
+ * from, and eases to the answer the way it eases to `layered`'s. */
415
+ interface LayoutPlanner {
416
+ plan(graph: Graph, opts?: LayoutOptions): LayoutPlan | Promise<LayoutPlan>;
417
+ }
418
+ /** A layout's plan: its own `plan`, or its moves alone. A node with a
419
+ * `margin` is laid out as its footprint, so neighbors keep clear of it. */
420
+ declare function planOf(layout: LayoutFn, graph: Graph, opts?: LayoutOptions): LayoutPlan;
421
+ /** `planner`'s plan for `graph`, laid out by footprint the way {@link planOf}'s is. */
422
+ declare function plannerPlan(planner: LayoutPlanner, graph: Graph, opts?: LayoutOptions): LayoutPlan | Promise<LayoutPlan>;
248
423
  /** `LayoutOptions.nodeGap`'s default, in world units. */
249
424
  declare const DEFAULT_NODE_GAP = 48;
250
425
  /** `LayoutOptions.rankGap`'s default, in world units. */
@@ -295,6 +470,9 @@ declare function untilSettled(once: LayoutFn, graph: Graph, opts?: LayoutOptions
295
470
  * coordinates.
296
471
  */
297
472
  declare function translated(graph: Graph, placed: ReadonlyMap<string, Vec2>, pinned: ReadonlySet<string>, tolerance?: number): LayoutResult;
473
+ /** How far {@link translated} moves a placement: onto the first pinned node,
474
+ * or else so its low corner lands on the graph's current one. */
475
+ declare function anchorOffset(graph: Graph, placed: ReadonlyMap<string, Vec2>, pinned: ReadonlySet<string>): Vec2;
298
476
  /**
299
477
  * Lay a list of nodes out along the cross axis, in the order given, and hand
300
478
  * back each one's low corner plus the total span.
@@ -362,88 +540,74 @@ declare function packClusters(graph: Graph, rows: readonly (readonly GraphNode[]
362
540
  declare function expandGroupEdges(edges: readonly GraphEdge[], groups: readonly GraphGroup[]): GraphEdge[];
363
541
 
364
542
  /**
365
- * Where a port sits on its node, in **normalized bounds coordinates**:
366
- * `(0, 0)` is the top-left corner of the node's AABB and `(1, 1)` the
367
- * bottom-right. `COMPASS` names the eight positions almost everything wants.
543
+ * Perimeter port labels: where the text naming a port is drawn, and how far
544
+ * past its node that reaches.
368
545
  *
369
- * Normalized rather than absolute because a node's size is authored data that
370
- * resize, align and distribute all write — a port fixed at an offset would
371
- * drift off the shape the first time one of them ran.
546
+ * A label sits outside the node, on the port's own face, and to one side of
547
+ * the line the port's wire leaves along, so the wire does not run through it.
548
+ * A port inside a row draws its label inside the box instead; that is a
549
+ * `ports` row's job, not this module's.
372
550
  */
373
- interface PortAnchor {
374
- u: number;
375
- v: number;
376
- }
377
- /** One attachment point declared on a node. */
378
- interface PortSpec {
379
- /** Unique within the node. Edges name this, so it outlives a re-layout. */
380
- id: string;
381
- at: PortAnchor;
382
- /** Consulted by a connect gesture's validity predicate. Uninterpreted here. */
383
- type?: string;
384
- /** The direction an edge leaves along, overriding the one the anchor
385
- * implies. `null` means "no preferred direction" — a router goes straight
386
- * at it. */
387
- normal?: Vec2 | null;
388
- }
551
+
552
+ /** Between a port and the near sides of its label, in world units. */
553
+ declare const PORT_LABEL_GAP = 4;
389
554
  /**
390
- * The trait that makes an existing scene node a diagram participant.
555
+ * Where a label `size` big goes for a port at `point` facing `normal`.
391
556
  *
392
- * Attached to a node's `data`, not a node kind the plugin mints: a text block,
393
- * an image, a path, a group or a plain rect all take part by carrying this,
394
- * and nothing has to be authored through this package to be connectable.
557
+ * A port on a left or right face gets its label outside that face, above its
558
+ * wire; one on a top or bottom face, outside that face, right of its wire; a
559
+ * corner port, off the corner. A port with no direction gets it centered
560
+ * above.
395
561
  */
396
- interface DiagramNode {
397
- /** Defaults to {@link DEFAULT_PORTS} — the four edge midpoints of the
398
- * node's own bounds, which every node has. */
399
- ports?: readonly PortSpec[];
400
- /** Layout must not move this node. */
401
- pinned?: boolean;
402
- /** What the node is *drawn* as. Present on a built body; absent on a
403
- * participant that already had a look of its own — a text block, an image,
404
- * a path — which keeps whatever painter it already matched. */
405
- outline?: Outline;
406
- /** Present on a group's box: a node drawn around the participants it
407
- * depends on, which layout keeps together and leaves room for. The box
408
- * itself is never laid out — it derives its pose from its members — but an
409
- * edge can attach to it like any participant. */
410
- group?: DiagramGroup;
411
- }
412
- /** The group half of a {@link DiagramNode}. */
413
- interface DiagramGroup {
414
- /** Room the box keeps around its members, in world units: one number for
415
- * every side, or per side. Default 12 on each. */
416
- inset?: number | Partial<GroupInset>;
417
- /** The narrowest the box may be, in world units, as room for its label.
418
- * A box its members leave narrower grows evenly on both sides. */
419
- minWidth?: number;
420
- }
421
- /** A port resolved against a node's current pose: where it is in world
422
- * coordinates, and which way an edge leaves it. */
423
- interface Port {
424
- id: string;
425
- /** The node this port belongs to. */
426
- nodeId: string;
427
- /** World point an edge attaches to. */
562
+ declare function portLabelBox(port: {
428
563
  point: Vec2;
429
- /** Unit vector pointing away from the node, or `null` when the port
430
- * declared no direction. Rotated with the node. */
431
564
  normal: Vec2 | null;
432
- type?: string;
565
+ }, size: {
566
+ width: number;
567
+ height: number;
568
+ }, gap?: number): Bounds;
569
+ /** Where a built body's perimeter port label sits, as `buildBody`'s
570
+ * `portLabel` is handed it. */
571
+ interface PerimeterPortLabelBox extends Bounds {
572
+ port: PortSpec;
573
+ side: 'outside';
574
+ }
575
+ /** A labeled port, resolved, and the box its label is drawn in. */
576
+ interface PortLabelPlacement {
577
+ port: Port & {
578
+ label: string;
579
+ };
580
+ box: Bounds;
433
581
  }
582
+ /** Every labeled port of `trait` resolved against `bounds`, with its label's
583
+ * box. `size` measures one label. */
584
+ declare function portLabelsOf(trait: DiagramNode, bounds: Bounds, size: (text: string) => {
585
+ width: number;
586
+ height: number;
587
+ }): PortLabelPlacement[];
588
+ /** How far `boxes` reach past `bounds` on each side; `undefined` for none. */
589
+ declare function marginOf(bounds: Bounds, boxes: readonly Bounds[]): GroupInset | undefined;
434
590
 
435
591
  /** One line of body content. */
436
592
  type Row =
437
- /** Text. `label` is the node's name; `field` is a named value. */
593
+ /** Text. `label` is the node's name; `field` is a named value. A row with
594
+ * an `id` gets a scene node of that name under the body's, so it can be
595
+ * found again; one without is named by its place. Not a bare number. */
438
596
  {
439
597
  kind: 'label';
440
598
  text: string;
441
599
  style?: RowTextStyle;
442
- } | {
600
+ id?: string;
601
+ }
602
+ /** `room` is a value the row is measured as at least as wide as, so a
603
+ * value written in later fits the box it already has. */
604
+ | {
443
605
  kind: 'field';
444
606
  label: string;
445
607
  value: string;
446
608
  style?: RowTextStyle;
609
+ id?: string;
610
+ room?: string;
447
611
  }
448
612
  /** A row that carries ports on its own left and right edges. */
449
613
  | {
@@ -452,10 +616,12 @@ type Row =
452
616
  right?: readonly RowPort[];
453
617
  height?: number;
454
618
  }
455
- /** Reserved space a consumer paints into. */
619
+ /** Reserved space a consumer paints into. With a `width`, the body is at
620
+ * least that wide and the slot's box is that wide, centered in its row. */
456
621
  | {
457
622
  kind: 'slot';
458
623
  height: number;
624
+ width?: number;
459
625
  id?: string;
460
626
  };
461
627
  /** A port declared on a row rather than on the node's perimeter. */
@@ -490,6 +656,8 @@ interface BodySpec {
490
656
  /** The perimeter ports, replacing the four compass midpoints. A `ports`
491
657
  * row's own ports are added either way. */
492
658
  ports?: readonly PortSpec[];
659
+ /** Perimeter port labels by id, drawn outside the box; over a port's own `label`. */
660
+ portLabels?: Readonly<Record<string, string>>;
493
661
  }
494
662
  /** What {@link buildBody} emits: the `AddNodeSpec` fields it fills in. Named
495
663
  * structurally rather than imported so a consumer whose scene is typed
@@ -597,9 +765,9 @@ interface BuildBodyOptions<TData, TLayer extends string, TPose extends Bounds> {
597
765
  body: (trait: DiagramNode, pose: TPose) => TData;
598
766
  /** One row's data, or `null` to leave that row undrawn. */
599
767
  row: RowNodeData<TData>;
600
- /** One row port's label, or `null` to leave it undrawn. Ports with no
601
- * `label` are never offered. Omit to draw none. */
602
- portLabel?: (text: string, box: RowPortBox) => TData | null;
768
+ /** One port's label — a row port's or a perimeter port's — or `null` to
769
+ * leave it undrawn. Ports with no label are never offered. Omit to draw none. */
770
+ portLabel?: (text: string, box: RowPortBox | PerimeterPortLabelBox) => TData | null;
603
771
  }
604
772
  /**
605
773
  * The scene nodes a built body is: one container carrying the trait, and one
@@ -615,191 +783,4 @@ declare function buildBody<TData, TLayer extends string, TPose extends Bounds>(s
615
783
  specs: BodyNodeSpec<TData, TLayer, TPose>[];
616
784
  };
617
785
 
618
- /**
619
- * Where a node's ports are, in world coordinates.
620
- *
621
- * A port is placed in the node's **unrotated** bounds, then carried through
622
- * the pose's rotation — the same pivot every other kit reader uses, the AABB
623
- * center. Bounds come from the pose descriptor rather than from `pose.width`
624
- * and friends, so a node whose pose is a path, an ellipse or anything else a
625
- * consumer wired is connectable on the same terms as a rect.
626
- */
627
-
628
- /** The eight anchors almost everything wants, named the way a compass is. */
629
- declare const COMPASS: {
630
- readonly nw: {
631
- readonly u: 0;
632
- readonly v: 0;
633
- };
634
- readonly n: {
635
- readonly u: 0.5;
636
- readonly v: 0;
637
- };
638
- readonly ne: {
639
- readonly u: 1;
640
- readonly v: 0;
641
- };
642
- readonly e: {
643
- readonly u: 1;
644
- readonly v: 0.5;
645
- };
646
- readonly se: {
647
- readonly u: 1;
648
- readonly v: 1;
649
- };
650
- readonly s: {
651
- readonly u: 0.5;
652
- readonly v: 1;
653
- };
654
- readonly sw: {
655
- readonly u: 0;
656
- readonly v: 1;
657
- };
658
- readonly w: {
659
- readonly u: 0;
660
- readonly v: 0.5;
661
- };
662
- };
663
- /**
664
- * What a node carrying no `ports` gets: the four edge midpoints of its own
665
- * bounds. Every node has bounds, so every node is connectable without saying
666
- * anything.
667
- */
668
- declare const DEFAULT_PORTS: readonly PortSpec[];
669
- /** How ports are resolved against a node: its bounds, and its trait. */
670
- interface PortsOptions<TPose> {
671
- /** Reads the AABB a port is placed in, and the node's rotation. Defaults to
672
- * the kit's `AUTO_POSE_DESCRIPTOR`, which handles rect and path poses. */
673
- geometry?: PoseDescriptor<TPose>;
674
- /** How the `DiagramNode` trait is read off a node. */
675
- read?: DiagramNodeReader;
676
- }
677
- /**
678
- * Every port on `node`, resolved against `pose`.
679
- *
680
- * Pass the pose the node is **painted** at — `effectivePose(scene, node)` —
681
- * not `node.pose`. A port that answers from the document pose while the node
682
- * is mid-drag puts an edge somewhere the user can see the node is not.
683
- */
684
- declare function portsOf<TPose>(node: DiagramNodeLike, pose: TPose, opts?: PortsOptions<TPose>): Port[];
685
- /** One named port, or `undefined` when the node has no such port. */
686
- declare function portOf<TPose>(node: DiagramNodeLike, pose: TPose, portId: string, opts?: PortsOptions<TPose>): Port | undefined;
687
-
688
- /**
689
- * `force` — relaxation, for a graph with no direction to read it in.
690
- *
691
- * Seeded from where the nodes already are rather than from a fresh random
692
- * scatter: an author who has arranged half a diagram gets that arrangement
693
- * relaxed, not replaced. There is no RNG in it at all — two coincident nodes
694
- * are separated along a golden-angle spiral keyed to their index.
695
- *
696
- * The forces are the naive O(n²) kind — right for tens to hundreds of nodes,
697
- * wrong for a 10k-node graph.
698
- *
699
- * **This is the one layout that is not idempotent.** A relaxation re-run from
700
- * its own output keeps relaxing; `direction` means nothing to it. Pins and the
701
- * `tolerance` option are what keep a second pass from scrambling a settled
702
- * diagram.
703
- */
704
-
705
- /** Options for {@link force} and {@link forceRelaxation}. `direction` is
706
- * ignored. */
707
- interface ForceOptions extends LayoutOptions {
708
- /** Ticks to run. Default 300 — one full cooling schedule. */
709
- iterations?: number;
710
- /** Rest length of an edge, in world units. Default 160. */
711
- linkDistance?: number;
712
- /** How hard an edge pulls, 0..1. Default 0.35. */
713
- linkStrength?: number;
714
- /** Pairwise repulsion. Negative repels; default -3000. */
715
- charge?: number;
716
- /** How hard the graph is held around its own centroid. Default 0.02. */
717
- gravity?: number;
718
- /** Clear space kept between two boxes, in world units. Default 24. */
719
- padding?: number;
720
- /** How hard a group draws its members together, 0..1. Default 0.2. */
721
- groupStrength?: number;
722
- }
723
- /** One node as the relaxation sees it: a simulation point centered on the
724
- * node's box. */
725
- interface ForceBody extends SimulationNode {
726
- id: string;
727
- /** Half the node's box, so separation can work on the shape the author sees
728
- * rather than on a point. */
729
- hw: number;
730
- hh: number;
731
- }
732
- /**
733
- * The bodies and the force list, wound up and ready to tick.
734
- */
735
- interface ForceRelaxation {
736
- bodies: ForceBody[];
737
- byId: Map<string, ForceBody>;
738
- /** Ids the caller must not move: declared pins, held as `fx`/`fy`. */
739
- pinned: ReadonlySet<string>;
740
- sim: ReturnType<typeof createSimulation<ForceBody>>;
741
- /** Where a body's node's top-left sits, given the body's center. */
742
- placed(): Map<string, {
743
- x: number;
744
- y: number;
745
- }>;
746
- }
747
- /** Wind up a relaxation of `graph` without ticking it: bodies seeded from
748
- * where the nodes are, pins held fixed. Tick `sim` yourself and read the
749
- * top-lefts from `placed()`. */
750
- declare function forceRelaxation(graph: Graph, opts?: ForceOptions): ForceRelaxation;
751
- /** The force-directed layout. Unlike the others it is not idempotent: a
752
- * re-run keeps relaxing, so pins and `tolerance` are what hold a settled
753
- * diagram still. */
754
- declare const force: (graph: Graph, opts?: ForceOptions) => LayoutResult;
755
-
756
- /** Edge ids that close a cycle, found by a depth-first walk in graph order. */
757
- declare function backEdges(graph: Graph): Set<string>;
758
- /** Longest-path rank per node: one past its deepest predecessor. */
759
- declare function ranksOf(graph: Graph, back: ReadonlySet<string>): Map<string, number>;
760
- /** The ranked layout: each edge points one rank further along `direction`.
761
- * Within a rank, nodes keep their current cross-axis order, so a re-run
762
- * moves nothing — except under `order: 'barycenter'`, which reorders them. */
763
- declare const layered: LayoutFn;
764
-
765
- /** A graph read as trees: its roots, each node's children in order, and each node's depth. */
766
- interface Forest {
767
- roots: GraphNode[];
768
- children: Map<string, GraphNode[]>;
769
- depth: Map<string, number>;
770
- }
771
- /** The forest a depth-first walk in graph order finds. Exported because "which
772
- * node ended up whose child" is the question anyone debugging a tree layout
773
- * asks first. */
774
- declare function forestOf(graph: Graph, crossAxis: 'x' | 'y'): Forest;
775
- /** The hierarchy layout: each parent centered over its children's block, one
776
- * rank per depth. A graph that is not a tree lays out as the forest
777
- * {@link forestOf} reads from it. */
778
- declare const tree: LayoutFn;
779
-
780
- /** The outline as a closed polyline: `[x0, y0, x1, y1, …]`. */
781
- declare function outlinePolyline(outline: Outline, bounds: Bounds): number[];
782
- /**
783
- * Where the ray from `center` through `point` last crosses `poly`, or `null`
784
- * when it never does.
785
- *
786
- * The *last* crossing rather than the first: a concave outline can be crossed
787
- * more than once, and the far side is the boundary an edge should attach to.
788
- */
789
- declare function rayHit(poly: readonly number[], center: {
790
- x: number;
791
- y: number;
792
- }, point: {
793
- x: number;
794
- y: number;
795
- }): {
796
- x: number;
797
- y: number;
798
- } | null;
799
- /**
800
- * Every port moved onto `outline`. A port the ray misses — one at the node's
801
- * dead center, with no direction to cast along — is left where it was.
802
- */
803
- declare function portsOnOutline(ports: readonly Port[], outline: Outline, bounds: Bounds): Port[];
804
-
805
- export { canvasMeasure as $, type GraphNode as A, type BodySpec as B, COMPASS as C, type DiagramNodeLike as D, type GraphNodeLike as E, type ForceOptions as F, type GraphSource as G, type GroupInset as H, type LayoutAxes as I, type RowBox as J, type RowNodeData as K, type LayoutFn as L, type MeasureRowText as M, type RowPort as N, type Outline as O, type Port as P, type RowPortBox as Q, type Row as R, type RowTextStyle as S, type Slot as T, axesFor as U, backEdges as V, bodyOutline as W, bodyTrait as X, boxForContent as Y, buildBody as Z, buildGraph as _, type PortsOptions as a, contentBox as a0, createDiagramNodes as a1, dataKeyReader as a2, diagramNodeOf as a3, expandGroupEdges as a4, force as a5, forceRelaxation as a6, forestOf as a7, graphOrder as a8, insetOf as a9, isPinned as aa, layered as ab, layoutBody as ac, layoutRowPorts as ad, measureBody as ae, outlinePath as af, outlinePolyline as ag, packAcross as ah, packClusters as ai, pinnedSet as aj, portOf as ak, portsOf as al, portsOnOutline as am, rankGaps as an, ranksOf as ao, rayHit as ap, seededOrder as aq, settle as ar, sizeToBody as as, translated as at, tree as au, untilSettled as av, extent as aw, type DiagramNodeReader as b, type PortAnchor as c, type DiagramGroup as d, type PortSpec as e, type DiagramNode as f, type LayoutOptions as g, type LayoutResult as h, type Graph as i, type LayoutDirection as j, type BodyFloor as k, type BodyNodeSpec as l, type Bounds as m, type BuildBodyOptions as n, type BuildGraphOptions as o, DEFAULT_GROUP_INSET as p, DEFAULT_NODE_GAP as q, DEFAULT_PORTS as r, DEFAULT_RANK_GAP as s, DIAGRAM_TRAIT_KEY as t, type DiagramNodeEntry as u, type ForceBody as v, type ForceRelaxation as w, type Forest as x, type GraphEdge as y, type GraphGroup as z };
786
+ export { buildGraph as $, type GraphNode as A, type BodySpec as B, type GroupInset as C, type DiagramGroup as D, type EdgeLabelRoom as E, type LayoutAxes as F, type Graph as G, PORT_LABEL_GAP as H, type PerimeterPortLabelBox as I, type PortLabelPlacement as J, type PortSite as K, type LayoutPorts as L, type MeasureRowText as M, type RowBox as N, type Outline as O, type PortAnchor as P, type RowNodeData as Q, type RowTextStyle as R, type RowPort as S, type RowPortBox as T, type Slot as U, anchorOffset as V, axesFor as W, bodyOutline as X, bodyTrait as Y, boxForContent as Z, buildBody as _, type Row as a, canvasMeasure as a0, contentBox as a1, createDiagramNodes as a2, dataKeyReader as a3, diagramNodeOf as a4, expandGroupEdges as a5, graphOrder as a6, insetOf as a7, isPinned as a8, layoutBody as a9, layoutRowPorts as aa, marginOf as ab, measureBody as ac, outlinePath as ad, packAcross as ae, packClusters as af, pinnedSet as ag, planOf as ah, plannerPlan as ai, portLabelBox as aj, portLabelsOf as ak, rankGaps as al, seededOrder as am, settle as an, sizeToBody as ao, translated as ap, untilSettled as aq, extent as ar, type PortSpec as b, type LayoutFn as c, type LayoutOptions as d, type DiagramNode as e, type Port as f, type DiagramNodeLike as g, type DiagramNodeReader as h, type LayoutResult as i, type GraphSource as j, type LayoutPlanner as k, type LayoutDirection as l, type LayoutPlan as m, type GraphNodeLike as n, type BodyFloor as o, type BodyNodeSpec as p, type Bounds as q, type BuildBodyOptions as r, type BuildGraphOptions as s, DEFAULT_GROUP_INSET as t, DEFAULT_NODE_GAP as u, DEFAULT_RANK_GAP as v, DIAGRAM_TRAIT_KEY as w, type DiagramNodeEntry as x, type GraphEdge as y, type GraphGroup as z };