@erd-studio/renderer 0.3.0 → 0.4.1
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/CHANGELOG.md +49 -0
- package/README.md +8 -3
- package/dist/{chunk-DUQ2TTQH.js → chunk-64BVOHOR.js} +4 -3
- package/dist/chunk-JDL3BFJZ.js +10479 -0
- package/dist/{chunk-WOTSQZCQ.js → chunk-YLXBIRPE.js} +132 -2
- package/dist/edge-routing-worker.js +1 -0
- package/dist/editor.js +43 -3
- package/dist/index.js +14 -3
- package/dist/sizing.js +2 -2
- package/dist/store.js +1 -1
- package/dist/styles.css +912 -359
- package/dist/types/ErdCanvas.d.ts +38 -1
- package/dist/types/canvas/CanvasBackdrop.d.ts +3 -2
- package/dist/types/canvas/EdgeRouting.d.ts +56 -0
- package/dist/types/canvas/useCanvasGraph.d.ts +7 -1
- package/dist/types/canvas/useHoverFocus.d.ts +64 -0
- package/dist/types/components/DetailPanel/AliasEditor.d.ts +15 -1
- package/dist/types/components/DetailPanel/ColumnEditor.d.ts +18 -2
- package/dist/types/components/DetailPanel/DescriptionEditor.d.ts +1 -1
- package/dist/types/components/DetailPanel/DetailPanel.d.ts +31 -2
- package/dist/types/components/DetailPanel/FieldsSection.d.ts +68 -0
- package/dist/types/components/Graph/EdgeFlow.d.ts +55 -0
- package/dist/types/components/Graph/EdgeLabels.d.ts +18 -0
- package/dist/types/components/Graph/FkEdge.d.ts +8 -1
- package/dist/types/components/Graph/ModelNode.d.ts +1 -1
- package/dist/types/components/Legend/Legend.d.ts +34 -3
- package/dist/types/components/StatusBar/StatusBar.d.ts +45 -0
- package/dist/types/components/common/ColumnRowEditor.d.ts +18 -2
- package/dist/types/components/common/KeyBadgeGroup.d.ts +5 -1
- package/dist/types/editor.d.ts +8 -2
- package/dist/types/hooks/usePrefersReducedMotion.d.ts +13 -0
- package/dist/types/index.d.ts +2 -1
- package/dist/types/lib/badgeLabels.d.ts +6 -0
- package/dist/types/lib/canvasContext.d.ts +20 -0
- package/dist/types/lib/canvasFocus.d.ts +66 -0
- package/dist/types/lib/edgeFlow.d.ts +84 -0
- package/dist/types/lib/edgeRouting/index.d.ts +146 -0
- package/dist/types/lib/edgeRouting/input.d.ts +35 -0
- package/dist/types/lib/edgeRouting/lineStyle.d.ts +16 -0
- package/dist/types/lib/edgeRouting/render/corners.d.ts +52 -0
- package/dist/types/lib/edgeRouting/render/crossings.d.ts +27 -0
- package/dist/types/lib/edgeRouting/render/curveCrossings.d.ts +35 -0
- package/dist/types/lib/edgeRouting/render/pathRender.d.ts +74 -0
- package/dist/types/lib/edgeRouting/render/tunnels.d.ts +49 -0
- package/dist/types/lib/edgeRouting/routeAll.d.ts +20 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/geom.d.ts +58 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/grid.d.ts +77 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/nudge.d.ts +62 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/params.d.ts +152 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/router.d.ts +51 -0
- package/dist/types/lib/edgeRouting/routers/orthogonal/search.d.ts +81 -0
- package/dist/types/lib/edgeRouting/routers/orthogonalAvoid.d.ts +11 -0
- package/dist/types/lib/edgeRouting/types.d.ts +113 -0
- package/dist/types/lib/edgeRouting/worker/client.d.ts +38 -0
- package/dist/types/lib/edgeRouting/worker/entry.d.ts +1 -0
- package/dist/types/lib/edgeRouting/worker/protocol.d.ts +23 -0
- package/dist/types/lib/hoverIntent.d.ts +44 -0
- package/dist/types/lib/layerCues.d.ts +26 -0
- package/dist/types/lib/nodeOverlays.d.ts +6 -0
- package/dist/types/lib/nodeSizing.d.ts +1 -1
- package/dist/types/lib/pathGeometry.d.ts +45 -0
- package/dist/types/lib/reuseUnchanged.d.ts +28 -0
- package/dist/types/lib/roleOptions.d.ts +13 -0
- package/dist/types/store/editorStore.d.ts +66 -1
- package/dist/types/types/graph.d.ts +20 -1
- package/package.json +3 -2
- package/dist/chunk-HWA32XG5.js +0 -5652
- package/dist/types/components/DetailPanel/GrainEditor.d.ts +0 -16
- package/dist/types/components/DetailPanel/MetaEditor.d.ts +0 -21
- package/dist/types/components/DetailPanel/ModelRationale.d.ts +0 -27
- package/dist/types/components/DetailPanel/RationaleField.d.ts +0 -28
- package/dist/types/components/DetailPanel/RoleEditor.d.ts +0 -13
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge routing — turn routed polylines into SVG paths.
|
|
3
|
+
*
|
|
4
|
+
* Owns the corner style (rounded / curved / sharp) and the crossing
|
|
5
|
+
* treatment (hops / halo). Crossings need every line at once, so this
|
|
6
|
+
* takes the whole map.
|
|
7
|
+
*
|
|
8
|
+
* Who goes over at a crossing: **the more horizontal segment** (the later
|
|
9
|
+
* line id on a tie), as on a circuit schematic. One fixed rule reads at a
|
|
10
|
+
* glance — every bridge is a bump on a horizontal run, every vertical run
|
|
11
|
+
* passes under — where "whichever line is later" scatters bumps in both
|
|
12
|
+
* directions with no visible logic. Horizontal runs are also the long,
|
|
13
|
+
* calm stretches between cards, so a bump there disturbs the least. When the
|
|
14
|
+
* over line has no room at that spot (a corner or its end is too close), the
|
|
15
|
+
* other line takes the treatment; when neither has room, the crossing is
|
|
16
|
+
* drawn plain.
|
|
17
|
+
*
|
|
18
|
+
* hops the over line jumps the under one with a semicircle of
|
|
19
|
+
* HOP_RADIUS, drawn as two cubics (EdgeFlow's `measurePath` reads
|
|
20
|
+
* M L H V Q C only, so no `A`). Crossings closer than
|
|
21
|
+
* 2 × HOP_RADIUS + MERGE_SLACK merge into one flat-topped bridge.
|
|
22
|
+
* halo `halo: true`: FkEdge draws a canvas-coloured casing under every
|
|
23
|
+
* line. Left to paint order, the casing would let whichever line
|
|
24
|
+
* React Flow draws LATER win each crossing, a different rule from
|
|
25
|
+
* hops and a random-looking one. So the under line is also cut
|
|
26
|
+
* (`strokeD`, HALO_CUT_HALF either side) and FkEdge draws both its
|
|
27
|
+
* casing and its stroke from that: an under line has no casing at
|
|
28
|
+
* the crossing, so it can never paint over the line above it. `d`
|
|
29
|
+
* stays ONE continuous subpath (EdgeFlow and the hit area need one).
|
|
30
|
+
*
|
|
31
|
+
* Paths use absolute M / L / Q / C only, one subpath (`strokeD` aside), and
|
|
32
|
+
* numbers rounded to 0.01 px.
|
|
33
|
+
*/
|
|
34
|
+
import type { CornerMode, CrossingMode, Point, Rect, RoutedLine } from '../types.js';
|
|
35
|
+
export interface RenderOptions {
|
|
36
|
+
crossings: CrossingMode;
|
|
37
|
+
corners: CornerMode;
|
|
38
|
+
/**
|
|
39
|
+
* The cards (optional). With them, a curved corner that would cut into a
|
|
40
|
+
* card is tightened until it clears.
|
|
41
|
+
*/
|
|
42
|
+
obstacles?: readonly Rect[];
|
|
43
|
+
/**
|
|
44
|
+
* The canvas zoom (default 1). Hops are sized in screen px, so they stay
|
|
45
|
+
* readable as bridges when the diagram is zoomed out (see `hopRadiusFor`).
|
|
46
|
+
*/
|
|
47
|
+
zoom?: number;
|
|
48
|
+
}
|
|
49
|
+
export interface RenderedPath {
|
|
50
|
+
/** One continuous subpath: draw the halo and the hit area with it; EdgeFlow follows it. */
|
|
51
|
+
d: string;
|
|
52
|
+
mid: Point;
|
|
53
|
+
halo?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* crossings = 'halo', on a line that passes under another: `d`
|
|
56
|
+
* with the breaks cut out (several `M` subpaths). Draw the visible stroke
|
|
57
|
+
* (and the halo casing) with this when present; keep `d` for EdgeFlow and
|
|
58
|
+
* the hit area.
|
|
59
|
+
*/
|
|
60
|
+
strokeD?: string;
|
|
61
|
+
}
|
|
62
|
+
/** Radius (px) of a hop's semicircle at zoom 1. */
|
|
63
|
+
export declare const HOP_RADIUS = 6;
|
|
64
|
+
/** Hop radius in flow px for a canvas zoom: HOP_SCREEN_RADIUS on screen, between HOP_RADIUS and HOP_MAX_RADIUS. */
|
|
65
|
+
export declare function hopRadiusFor(zoom: number | undefined): number;
|
|
66
|
+
/** Half the cut (px) in an under line in halo mode: the casing's half width (3.5) plus a hair. */
|
|
67
|
+
export declare const HALO_CUT_HALF = 4.5;
|
|
68
|
+
/** Drop repeated points and points in the middle of a straight run. */
|
|
69
|
+
export declare function simplify(points: readonly Point[]): Point[];
|
|
70
|
+
/** A polyline with each corner rounded by up to `radius`, no crossings treatment. */
|
|
71
|
+
export declare function roundedPath(points: readonly Point[], radius?: number): string;
|
|
72
|
+
/** The point halfway along a polyline, by length. */
|
|
73
|
+
export declare function polylineMidpoint(points: readonly Point[]): Point;
|
|
74
|
+
export declare function renderPaths(lines: Map<string, RoutedLine>, opts: RenderOptions): Map<string, RenderedPath>;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge routing — "x-ray": the stretches of a line that pass under a card.
|
|
3
|
+
*
|
|
4
|
+
* Cards are drawn above the lines, so any stretch of a line inside a card's
|
|
5
|
+
* box is hidden. `computeTunnels` clips the line's polyline against every
|
|
6
|
+
* box (tables and sticky notes alike, the line's own two ends included: a
|
|
7
|
+
* line that doubles back across its own table is hidden there too) and
|
|
8
|
+
* returns each hidden stretch as a polyline. `EdgeOverlay` draws them above
|
|
9
|
+
* the cards, with a "portal" where the line dives under a border and where it
|
|
10
|
+
* comes back out.
|
|
11
|
+
*
|
|
12
|
+
* - Boxes are shrunk by `EDGE_TOLERANCE` first, so a line that grazes a
|
|
13
|
+
* corner is not "under" anything; a straight stretch parallel to a border
|
|
14
|
+
* and within `BORDER_HUG` of it runs "along" the border, not under the card.
|
|
15
|
+
* - Overlapping or nearly touching boxes (a gap under `MERGE_GAP`) give one
|
|
16
|
+
* tunnel, not a row of portals 4 px apart.
|
|
17
|
+
* - Stretches shorter than `MIN_TUNNEL` (a rounded corner clipping a box
|
|
18
|
+
* corner) are dropped.
|
|
19
|
+
* - A stretch at the line's own end that is only under the line's own two
|
|
20
|
+
* cards is dropped: when two related cards nearly touch, the 20 px stub
|
|
21
|
+
* starts inside the other card and the line hooks round inside the pair.
|
|
22
|
+
* Showing that hook says nothing the reader does not know (the line joins
|
|
23
|
+
* those two cards) and puts dashes in two headers.
|
|
24
|
+
*
|
|
25
|
+
* Pure and allocation-light: it runs for every line on every frame of a drag.
|
|
26
|
+
* A box is tested against a segment only when their bounding boxes meet.
|
|
27
|
+
*/
|
|
28
|
+
import type { EdgeSpec, Point, Rect, RoutedLine } from '../types.js';
|
|
29
|
+
/** A line closer than this (px) to a border counts as outside the box. */
|
|
30
|
+
export declare const EDGE_TOLERANCE = 1;
|
|
31
|
+
/**
|
|
32
|
+
* A straight stretch running parallel to a border and closer to it than this
|
|
33
|
+
* (px) is "along the border", not under the card: drawn, its ghost reads as
|
|
34
|
+
* a second, dashed border a few px inside the real one (seen on 'big': 127 px
|
|
35
|
+
* of it 3 px inside dim_supplier's bottom). Like every threshold it flips
|
|
36
|
+
* somewhere during a drag; pops stay rare (~1 in 1500 nudges on the scenarios).
|
|
37
|
+
*/
|
|
38
|
+
export declare const BORDER_HUG = 6;
|
|
39
|
+
/** Hidden stretches closer together than this (px) along the line become one tunnel. */
|
|
40
|
+
export declare const MERGE_GAP = 12;
|
|
41
|
+
/** Hidden stretches shorter than this (px) are not worth a tunnel. */
|
|
42
|
+
export declare const MIN_TUNNEL = 6;
|
|
43
|
+
/**
|
|
44
|
+
* The hidden stretches of `line` as polylines: hidden by `obstacles` (every
|
|
45
|
+
* card box). Each polyline starts where the line dives under a border and ends
|
|
46
|
+
* where it comes back out (or where the line itself starts / ends, if an end
|
|
47
|
+
* sits inside another card).
|
|
48
|
+
*/
|
|
49
|
+
export declare function computeTunnels(line: RoutedLine, spec: EdgeSpec | undefined, obstacles: Rect[]): Point[][];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `routeAll`: route and draw every line of one diagram in one pass. Its own
|
|
3
|
+
* module (no React Flow, no React) so the line-routing worker can bundle it
|
|
4
|
+
* without the canvas code around it.
|
|
5
|
+
*/
|
|
6
|
+
import type { LineRouter, LineStyleOptions, RouteResult, RoutingInput } from './types.js';
|
|
7
|
+
/** A router that throws must never take the canvas down: fall back. */
|
|
8
|
+
export declare function safely<T>(fn: () => T, fallback: T, onError: (err: unknown) => void): T;
|
|
9
|
+
export interface RouteAllOptions {
|
|
10
|
+
/** The router (and its memory of the last pass). Default: a fresh route-around router, so the call keeps no state. */
|
|
11
|
+
router?: LineRouter;
|
|
12
|
+
/** Clock for `millis` (default: none, `millis` is 0). */
|
|
13
|
+
now?: () => number;
|
|
14
|
+
/** The canvas zoom: hops are sized for it (`hopRadiusFor`). Default 1. */
|
|
15
|
+
zoom?: number;
|
|
16
|
+
/** Called with a router or renderer error (the pass still returns, with fewer or no routes). */
|
|
17
|
+
onError?: (err: unknown) => void;
|
|
18
|
+
}
|
|
19
|
+
/** Route and draw every line of `input` in one pass. Never throws. */
|
|
20
|
+
export declare function routeAll(input: RoutingInput, options: LineStyleOptions, ctx?: RouteAllOptions): RouteResult;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-around router — small geometry helpers shared by the grid, the search
|
|
3
|
+
* and the nudging pass. Orthogonal only: every segment is horizontal or vertical.
|
|
4
|
+
*/
|
|
5
|
+
import { type Anchor, type Point, type Rect, type Side } from "../../types.js";
|
|
6
|
+
/** Leg orientation: 0 = horizontal, 1 = vertical. */
|
|
7
|
+
export type Orient = 0 | 1;
|
|
8
|
+
/** The sides tried for a port, in a fixed order (determinism). */
|
|
9
|
+
export declare const SIDES: readonly Side[];
|
|
10
|
+
/** Orientation of the straight leg that leaves (or enters) a side. */
|
|
11
|
+
export declare function legOrient(side: Side): Orient;
|
|
12
|
+
/** Direction of travel along that leg when leaving the card (+1 = towards +x / +y). */
|
|
13
|
+
export declare function outDir(side: Side): 1 | -1;
|
|
14
|
+
/** Where a side lies across its leg: x of a left/right side, y of a top/bottom side. */
|
|
15
|
+
export declare function sideLine(r: Rect, side: Side): number;
|
|
16
|
+
/** The span of a side along itself: [y0, y1] for left/right, [x0, x1] for top/bottom. */
|
|
17
|
+
export declare function sideSpan(r: Rect, side: Side): [number, number];
|
|
18
|
+
/** Where anchors may sit along a side: the span minus a corner inset (the centre for a short side). */
|
|
19
|
+
export declare function anchorRange(r: Rect, side: Side): [number, number];
|
|
20
|
+
export declare function sideMid(r: Rect, side: Side): number;
|
|
21
|
+
/** The port: the point `c` along the side, pushed PATH_GAP out (where the drawn line starts). */
|
|
22
|
+
export declare function portPoint(r: Rect, side: Side, c: number): Point;
|
|
23
|
+
/** The anchor on the card border behind a port. */
|
|
24
|
+
export declare function anchorBehind(port: Point, side: Side): Anchor;
|
|
25
|
+
export declare function pad(r: Rect, by: number): {
|
|
26
|
+
x0: number;
|
|
27
|
+
y0: number;
|
|
28
|
+
x1: number;
|
|
29
|
+
y1: number;
|
|
30
|
+
};
|
|
31
|
+
/** Do two rects (each grown by its own amount) overlap with positive area? */
|
|
32
|
+
export declare function rectsOverlap(a: Rect, growA: number, b: Rect, growB: number): boolean;
|
|
33
|
+
/** Drop repeated points and the middle point of any straight run. */
|
|
34
|
+
export declare function simplifyOrthogonal(points: Point[]): Point[];
|
|
35
|
+
/**
|
|
36
|
+
* Does the axis-aligned segment a→b pass through the inside of `r` (shrunk by
|
|
37
|
+
* `tol` on every side)? Touching the border does not count.
|
|
38
|
+
*/
|
|
39
|
+
export declare function segmentEntersRect(a: Point, b: Point, r: Rect, tol: number): boolean;
|
|
40
|
+
/**
|
|
41
|
+
* A uniform bucket grid over the cards, for "which cards are near this
|
|
42
|
+
* segment" queries in the nudging and validation passes.
|
|
43
|
+
*/
|
|
44
|
+
export declare class Buckets {
|
|
45
|
+
readonly rects: readonly Rect[];
|
|
46
|
+
private readonly x0;
|
|
47
|
+
private readonly y0;
|
|
48
|
+
private readonly cols;
|
|
49
|
+
private readonly rows;
|
|
50
|
+
private readonly cells;
|
|
51
|
+
private readonly seen;
|
|
52
|
+
private stamp;
|
|
53
|
+
private readonly cell;
|
|
54
|
+
constructor(rects: readonly Rect[], cell?: number);
|
|
55
|
+
private cellOf;
|
|
56
|
+
/** Indices of the rects whose bucket cells meet the box (a superset of the rects that meet it). */
|
|
57
|
+
query(x0: number, y0: number, x1: number, y1: number, out?: number[]): number[];
|
|
58
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-around router — the routing grid.
|
|
3
|
+
*
|
|
4
|
+
* A sparse orthogonal grid (Wybrow–Marriott–Stuckey style): its lines are the
|
|
5
|
+
* padded edges of every card, channel midlines between neighbouring cards,
|
|
6
|
+
* the centre of every card side, and the line where two related cards'
|
|
7
|
+
* sides overlap (so a straight line between them is on offer). A node is
|
|
8
|
+
* every crossing of an x-line and a y-line. Every padded card's edges are
|
|
9
|
+
* grid lines, so each elementary grid edge is either wholly inside a padded
|
|
10
|
+
* card or wholly outside it; `hBlk` / `vBlk` count the padded cards each one
|
|
11
|
+
* runs through, so a card can be lifted out again (`markRect(…, -1)`) for a
|
|
12
|
+
* relaxed retry.
|
|
13
|
+
*
|
|
14
|
+
* Node index n = j * nx + i (column i, row j).
|
|
15
|
+
* hBlk[n] the edge from (i, j) to (i + 1, j)
|
|
16
|
+
* vBlk[n] the edge from (i, j) to (i, j + 1)
|
|
17
|
+
* hUse / vUse how many lines routed so far run along that edge.
|
|
18
|
+
*/
|
|
19
|
+
export interface Grid {
|
|
20
|
+
xs: Float64Array;
|
|
21
|
+
ys: Float64Array;
|
|
22
|
+
nx: number;
|
|
23
|
+
ny: number;
|
|
24
|
+
hBlk: Uint16Array;
|
|
25
|
+
vBlk: Uint16Array;
|
|
26
|
+
hUse: Uint8Array;
|
|
27
|
+
vUse: Uint8Array;
|
|
28
|
+
/** Anchor zones: the band in front of a card side where lines attach (closed to other lines). */
|
|
29
|
+
hZone: Uint8Array;
|
|
30
|
+
vZone: Uint8Array;
|
|
31
|
+
}
|
|
32
|
+
/** Sort and merge values closer than MERGE_EPS (each cluster keeps its smallest value). */
|
|
33
|
+
export declare function uniqueSorted(values: number[]): Float64Array;
|
|
34
|
+
/**
|
|
35
|
+
* The grid lines: every `hard` value (a padded card edge, which the blocking
|
|
36
|
+
* needs exactly) plus each `soft` value (side centres, midlines, straight-line
|
|
37
|
+
* options) that is more than `softEps` from every line kept so far. A cluster
|
|
38
|
+
* of lines a few px apart adds nodes but no real routing choice, and A* pays
|
|
39
|
+
* for every node.
|
|
40
|
+
*/
|
|
41
|
+
export declare function gridLines(hard: number[], soft: number[], softEps: number): Float64Array;
|
|
42
|
+
/** The largest index i with arr[i] <= v (+ a hair), or -1. */
|
|
43
|
+
export declare function floorIndex(arr: Float64Array, v: number): number;
|
|
44
|
+
/** The smallest index i with arr[i] >= v (- a hair), or arr.length. */
|
|
45
|
+
export declare function ceilIndex(arr: Float64Array, v: number): number;
|
|
46
|
+
export declare function makeGrid(xs: Float64Array, ys: Float64Array): Grid;
|
|
47
|
+
/**
|
|
48
|
+
* Add (delta +1) or remove (-1) a padded card: every grid edge strictly
|
|
49
|
+
* inside the box. The box's edges must be grid lines (they snap to the line
|
|
50
|
+
* they were merged into).
|
|
51
|
+
*/
|
|
52
|
+
export declare function markRect(g: Grid, x0: number, y0: number, x1: number, y1: number, delta: 1 | -1): void;
|
|
53
|
+
/** Count a routed line's grid edges (consecutive nodes of a search result). */
|
|
54
|
+
export declare function markUse(g: Grid, nodes: readonly number[]): void;
|
|
55
|
+
/**
|
|
56
|
+
* Close (delta +1) or reopen (-1) a box that is not grid-aligned: every grid
|
|
57
|
+
* edge that runs inside it, or crosses into it, is marked. Used for the
|
|
58
|
+
* anchor zone in front of a card side.
|
|
59
|
+
*/
|
|
60
|
+
export declare function markZone(g: Grid, x0: number, y0: number, x1: number, y1: number, delta: 1 | -1): void;
|
|
61
|
+
/**
|
|
62
|
+
* Block (+1) or unblock (-1) a box that is not grid-aligned, like `markZone`
|
|
63
|
+
* but in the hard block counts (used for the band round a line's own cards).
|
|
64
|
+
*/
|
|
65
|
+
export declare function markBand(g: Grid, x0: number, y0: number, x1: number, y1: number, delta: 1 | -1): void;
|
|
66
|
+
/** The index of the value within `eps` of `v` (the nearest one), or -1. */
|
|
67
|
+
export declare function nearIndex(arr: Float64Array, v: number, eps: number): number;
|
|
68
|
+
/**
|
|
69
|
+
* Count a polyline's grid edges as used, from its geometry (a route kept from
|
|
70
|
+
* an earlier pass, whose grid has changed since, or one routed on another
|
|
71
|
+
* line's local grid). Segments not within `eps` of a grid line are skipped:
|
|
72
|
+
* usage is only a hint to the lines routed after it.
|
|
73
|
+
*/
|
|
74
|
+
export declare function markPathUse(g: Grid, pts: readonly {
|
|
75
|
+
x: number;
|
|
76
|
+
y: number;
|
|
77
|
+
}[], eps?: number): void;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-around router — after every line has a route, make the routes read
|
|
3
|
+
* as separate wires.
|
|
4
|
+
*
|
|
5
|
+
* For one dimension at a time (vertical segments, then horizontal):
|
|
6
|
+
*
|
|
7
|
+
* 1. Centre. A middle segment with both neighbours heading the same way (a
|
|
8
|
+
* "Z") can slide anywhere between them without changing the length or the
|
|
9
|
+
* bends; slide it to the middle of the free space (between the cards
|
|
10
|
+
* around it, and between its neighbours' far ends). Lines then run down
|
|
11
|
+
* the middle of a channel, and a step between two cards turns half-way,
|
|
12
|
+
* as today's smooth step does.
|
|
13
|
+
*
|
|
14
|
+
* 2. Nudge. Segments that share a channel (parallel, overlapping, within a
|
|
15
|
+
* few px) and the end segments that meet one card side are put in an
|
|
16
|
+
* order that does not make them cross, then spread apart: LINE_SEP in a
|
|
17
|
+
* channel, ANCHOR_SEP between anchors on a card side (moving an end
|
|
18
|
+
* segment slides its anchor along the side). The order comes from where
|
|
19
|
+
* the lines go at the ends of the shared stretch (Wybrow, Marriott &
|
|
20
|
+
* Stuckey's "ordering paths in a shared channel"): if one line turns off
|
|
21
|
+
* first, the other must sit on the far side of that turn. Where two lines
|
|
22
|
+
* turn together, the comparison follows them round the corner.
|
|
23
|
+
*
|
|
24
|
+
* Every move is bounded so a segment never comes closer than CLEAR to a
|
|
25
|
+
* card (sweep-free), anchors stay off the corners, and the first / last bend
|
|
26
|
+
* stays MIN_LEG past the stub.
|
|
27
|
+
*/
|
|
28
|
+
import type { Point, Rect, Side } from "../../types.js";
|
|
29
|
+
import { type Buckets } from "./geom.js";
|
|
30
|
+
export interface NudgePath {
|
|
31
|
+
/** From the source port to the target port; mutated in place. */
|
|
32
|
+
pts: Point[];
|
|
33
|
+
src: Rect;
|
|
34
|
+
srcSide: Side;
|
|
35
|
+
tgt: Rect;
|
|
36
|
+
tgtSide: Side;
|
|
37
|
+
/** Tie-break order (by edge id). */
|
|
38
|
+
rank: number;
|
|
39
|
+
/** Indices of cards this line may pass under (ends buried under another card). */
|
|
40
|
+
ignore: ReadonlySet<number> | null;
|
|
41
|
+
/** Indices of its own two cards: kept OWN_CLEAR away, except along the stubs. */
|
|
42
|
+
own: readonly [number, number];
|
|
43
|
+
}
|
|
44
|
+
export interface NudgeContext {
|
|
45
|
+
/** Cards, self-loops, then (from `zoneFrom` on) anchor zones. */
|
|
46
|
+
rects: readonly Rect[];
|
|
47
|
+
buckets: Buckets;
|
|
48
|
+
/** Index of the first anchor zone in `rects`: zones need no extra clearance. */
|
|
49
|
+
zoneFrom: number;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Where a line steps sideways (segment k between two parallel segments that
|
|
53
|
+
* head the same way, a "Z"), slide one of the two onto the other's line:
|
|
54
|
+
* two bends fewer and never longer. Allowed when the area the segment
|
|
55
|
+
* sweeps is clear of cards, loops and anchor zones (by MARGIN), an end
|
|
56
|
+
* segment's anchor stays on its side, the neighbour beyond keeps its
|
|
57
|
+
* direction and length (a stub keeps MIN_LEG), and the line crosses no more
|
|
58
|
+
* of the others than before. Weighted A* and the grid both leave such jogs.
|
|
59
|
+
*/
|
|
60
|
+
export declare function straightenPass(paths: NudgePath[], ctx: NudgeContext): void;
|
|
61
|
+
export declare function centrePass(paths: NudgePath[], vert: boolean, ctx: NudgeContext): void;
|
|
62
|
+
export declare function nudgePass(paths: NudgePath[], vert: boolean, ctx: NudgeContext): void;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-around router — tuning knobs, in flow px (or px-equivalent costs).
|
|
3
|
+
*
|
|
4
|
+
* card ┃<- PATH_GAP 20 ->● stub end ── MIN_LEG 10 ──┐ first bend (≥ 30 out)
|
|
5
|
+
* ┃<- MARGIN 16 -> routes keep this far from every card
|
|
6
|
+
* ┃<- ZONE 42 -> no other line runs in front of a side with stubs
|
|
7
|
+
*
|
|
8
|
+
* The values were tuned against hand-placed scenarios
|
|
9
|
+
* (`test/fixtures/edgeRouting/scenarios.ts`): screenshots, crossings / bends /
|
|
10
|
+
* length, and A* expansions.
|
|
11
|
+
*/
|
|
12
|
+
/** Routes keep at least this far from every card (the routing grid pads each card by it). */
|
|
13
|
+
export declare const MARGIN = 16;
|
|
14
|
+
/** Nudging parallel lines apart never brings a line closer than this to a card. */
|
|
15
|
+
export declare const CLEAR = 16;
|
|
16
|
+
/** Gap between parallel lines that share a channel (fit-to-view often draws at ~0.7×). */
|
|
17
|
+
export declare const LINE_SEP = 12;
|
|
18
|
+
/** Gap between twins (two lines joining the same two cards): their role labels must clear each other. */
|
|
19
|
+
export declare const TWIN_SEP = 30;
|
|
20
|
+
/** Gap between two anchors on one card side: room for the `*` / `1` of each. */
|
|
21
|
+
export declare const ANCHOR_SEP = 24;
|
|
22
|
+
/** Anchors keep this far from a card's corners. */
|
|
23
|
+
export declare const ANCHOR_INSET = 20;
|
|
24
|
+
/** Straight run past the stub before a line may bend. */
|
|
25
|
+
export declare const MIN_LEG = 10;
|
|
26
|
+
/** Shortest run the centring pass leaves next to a bend. */
|
|
27
|
+
export declare const MIN_RUN = 10;
|
|
28
|
+
/**
|
|
29
|
+
* A line keeps this far from its own two cards everywhere but along its two
|
|
30
|
+
* stubs (PATH_GAP + MIN_LEG - 1): it never runs between a card and its stubs.
|
|
31
|
+
*/
|
|
32
|
+
export declare const OWN_CLEAR = 29;
|
|
33
|
+
/**
|
|
34
|
+
* Depth of the anchor zone in front of a card side that has lines attached:
|
|
35
|
+
* the stubs, their `*` / `1` and their first bends live there, and a line
|
|
36
|
+
* running past inside it reads as joined to them. Other lines pay ZONE_COST
|
|
37
|
+
* to run there; a line's own two cards' zones are open to it.
|
|
38
|
+
*/
|
|
39
|
+
export declare const ZONE = 42;
|
|
40
|
+
/**
|
|
41
|
+
* Extra cost per px of running inside another card side's anchor zone (< 0:
|
|
42
|
+
* zones are closed outright). Closed zones send lines in a crowded diagram on
|
|
43
|
+
* long detours round the whole thing; at 2 a line cuts through only when the
|
|
44
|
+
* way round is about three times as long.
|
|
45
|
+
*/
|
|
46
|
+
export declare const ZONE_COST = 2;
|
|
47
|
+
/** Cost of one bend, in px of length. */
|
|
48
|
+
export declare const BEND = 40;
|
|
49
|
+
/**
|
|
50
|
+
* Cost of crossing a line routed earlier: a crossing is worth a two-bend
|
|
51
|
+
* detour (at 40 the tangle scenario had twice the crossings).
|
|
52
|
+
*/
|
|
53
|
+
export declare const CROSS = 80;
|
|
54
|
+
/**
|
|
55
|
+
* A crossing on another line's stub (inside an anchor zone, or on this line's
|
|
56
|
+
* own leg) costs this many times CROSS: right next to a `*` / `1` it reads as
|
|
57
|
+
* a T junction, as if the lines joined there.
|
|
58
|
+
*/
|
|
59
|
+
export declare const STUB_CROSS = 3;
|
|
60
|
+
/** Extra cost per px of grid edge earlier lines already use (spreads lines over the channels). */
|
|
61
|
+
export declare const SHARE = 0.25;
|
|
62
|
+
/**
|
|
63
|
+
* Cost of an anchor slot right on top of another line's anchor on the same
|
|
64
|
+
* side (falling to 0 at ANCHOR_SEP away): a side has only so much room, so a
|
|
65
|
+
* later line takes a free slot or another side. Twins are exempt (they pair up).
|
|
66
|
+
*/
|
|
67
|
+
export declare const SLOT = 400;
|
|
68
|
+
/**
|
|
69
|
+
* Hysteresis for a line routed again (its card moved, or a card came near it):
|
|
70
|
+
* taking a side other than the one it had costs STICK_SIDE (more than a bend,
|
|
71
|
+
* less than two), and a slot on its old side costs STICK_SLOT per px away from
|
|
72
|
+
* where its anchor was (carried along with the card). Without these a drag
|
|
73
|
+
* re-plans every line on the moved card from scratch each frame, and a 1 px
|
|
74
|
+
* move can swap the grid line a slot sits on: anchors jumped 20-50 px and
|
|
75
|
+
* lines flipped sides on ~10% of drag frames in the tangle and 40-card scenarios.
|
|
76
|
+
*/
|
|
77
|
+
export declare const STICK_SIDE = 100;
|
|
78
|
+
export declare const STICK_SLOT = 2;
|
|
79
|
+
/** …applied only when at most this many boxes changed since the last pass (`changed` holds each moved box twice: old and new). */
|
|
80
|
+
export declare const STICK_MAX_CHANGED = 8;
|
|
81
|
+
/** Cost per px an anchor sits off the centre of its side: a straight line is worth a shift… */
|
|
82
|
+
export declare const SHIFT = 1.1;
|
|
83
|
+
/** …plus this per px², so an aligned straight line settles in the middle of the overlap. */
|
|
84
|
+
export declare const SHIFT_SQ = 0.002;
|
|
85
|
+
/** Grid lines closer than this are one line. */
|
|
86
|
+
export declare const MERGE_EPS = 1;
|
|
87
|
+
/**
|
|
88
|
+
* A soft grid line (side centre, midline, straight-line option) within this
|
|
89
|
+
* of another line is dropped: a cluster of near-identical lines adds A* nodes,
|
|
90
|
+
* not routing choices (on the 40-card scenario this alone cut expansions by 3×).
|
|
91
|
+
*/
|
|
92
|
+
export declare const SOFT_EPS = 12;
|
|
93
|
+
/** Parallel segments closer than this share a channel: they are ordered by where they go. */
|
|
94
|
+
export declare const NEAR = 3;
|
|
95
|
+
/** Segments this close that a group's spreading swapped are re-solved together. */
|
|
96
|
+
export declare const RELATE_WINDOW = 40;
|
|
97
|
+
/** The furthest the nudging pass moves a segment. */
|
|
98
|
+
export declare const MAX_SHIFT = 120;
|
|
99
|
+
/**
|
|
100
|
+
* Weighted A*: the heuristic is scaled by this. Crossing and sharing costs
|
|
101
|
+
* make the length + bends estimate loose, and an unweighted search floods
|
|
102
|
+
* the channels; 1.1 keeps the routes a designer would draw (1.2 already
|
|
103
|
+
* sends the odd long line the long way round).
|
|
104
|
+
*/
|
|
105
|
+
export declare const HEURISTIC_WEIGHT = 1.1;
|
|
106
|
+
/**
|
|
107
|
+
* Lines spanning more than LONG_FROM px (centre to centre, Manhattan) get up
|
|
108
|
+
* to LONG_EXTRA more weight, reached LONG_RAMP px further on: on the 40-card
|
|
109
|
+
* scenario a quarter fewer expansions for a few more crossings, while every
|
|
110
|
+
* line of a normal diagram (the star's longest spans 1,600 px) stays at 1.1.
|
|
111
|
+
*/
|
|
112
|
+
export declare const LONG_FROM = 1600;
|
|
113
|
+
export declare const LONG_RAMP = 1200;
|
|
114
|
+
export declare const LONG_EXTRA = 0.3;
|
|
115
|
+
/**
|
|
116
|
+
* …and their crossings cost up to this much less: across a whole diagram
|
|
117
|
+
* most are unavoidable, and pricing them in full doubles the search.
|
|
118
|
+
*/
|
|
119
|
+
export declare const LONG_CROSS_CUT = 0.7;
|
|
120
|
+
/** A* gives up (and the line falls back) after this many expansions. */
|
|
121
|
+
export declare const MAX_POPS = 40000;
|
|
122
|
+
/**
|
|
123
|
+
* Diagrams with more cards (and self-loops) than this route each line on its
|
|
124
|
+
* own local grid (router.ts, `localGrid`): one grid over the whole diagram
|
|
125
|
+
* grows with the square of its size (every card's edges cross every other
|
|
126
|
+
* card's), to millions of nodes on an uneven 1,000-card layout.
|
|
127
|
+
*/
|
|
128
|
+
export declare const LOCAL_FROM_CARDS = 150;
|
|
129
|
+
/** A local grid starts this far (px) round its line's two cards: about one card and a gap. */
|
|
130
|
+
export declare const LOCAL_MARGIN = 400;
|
|
131
|
+
/** …and its margin grows by this factor each time the route runs along its edge. */
|
|
132
|
+
export declare const LOCAL_GROWTH = 2.5;
|
|
133
|
+
/** A local grid larger than this (nodes) is not built: the line keeps what it found, or falls back. */
|
|
134
|
+
export declare const LOCAL_MAX_NODES = 1500000;
|
|
135
|
+
/**
|
|
136
|
+
* On local grids (large diagrams), long lines are greedier still: from
|
|
137
|
+
* LOCAL_LONG_FROM px they get up to LOCAL_LONG_EXTRA more heuristic weight
|
|
138
|
+
* and crossings up to LOCAL_LONG_CROSS_CUT cheaper. A line across a 2,000-card
|
|
139
|
+
* layered diagram otherwise runs out of MAX_POPS among the other lines and
|
|
140
|
+
* falls back (161 of 2,732 did); with these none do, for ~2.5% more crossings.
|
|
141
|
+
*/
|
|
142
|
+
export declare const LOCAL_LONG_FROM = 1200;
|
|
143
|
+
export declare const LOCAL_LONG_EXTRA = 0.9;
|
|
144
|
+
export declare const LOCAL_LONG_CROSS_CUT = 0.85;
|
|
145
|
+
/**
|
|
146
|
+
* On local grids, a line whose search runs out of MAX_POPS gets one greedy
|
|
147
|
+
* retry before it falls back: this heuristic weight, and crossings at this
|
|
148
|
+
* fraction of CROSS. A few more crossings beat a line drawn through every card
|
|
149
|
+
* between its two ends (lines between neighbouring columns 5,000 px apart).
|
|
150
|
+
*/
|
|
151
|
+
export declare const RESCUE_WEIGHT = 3;
|
|
152
|
+
export declare const RESCUE_CROSS = 0.1;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-around router — the whole pass.
|
|
3
|
+
*
|
|
4
|
+
* 1. Grid: the padded cards' edges, channel midlines, side centres, and per
|
|
5
|
+
* line the middle of the gap between its two cards and the straight-line
|
|
6
|
+
* option where their sides overlap (grid.ts). Self-loops are left to the
|
|
7
|
+
* canvas, but the space their loop takes is an obstacle.
|
|
8
|
+
* 2. Route every line, shortest first, with A* from any free anchor slot on
|
|
9
|
+
* any side of its source to any on its target (search.ts). The router
|
|
10
|
+
* picks the side: a line may leave the side the transformer chose when
|
|
11
|
+
* another is clearly better. Lines routed later pay to cross or run
|
|
12
|
+
* along earlier ones, so they spread over the free channels, and once a
|
|
13
|
+
* card side has a line attached, the zone in front of it (stubs and
|
|
14
|
+
* `*` / `1` labels) is closed to every other line.
|
|
15
|
+
* Before the first line is routed, the zones in front of the sides the
|
|
16
|
+
* canvas picked for every line are marked too, and a crossing on a stub
|
|
17
|
+
* (between a card and its `*` / `1`, or on the leg out of it) costs
|
|
18
|
+
* STUB_CROSS times more: it reads as a T junction.
|
|
19
|
+
* In a drag, lines away from the moved card keep their routes; a line
|
|
20
|
+
* routed again prefers the sides and slots it had (hysteresis), so it
|
|
21
|
+
* does not jump about as its card moves a pixel at a time.
|
|
22
|
+
* 3. Nudge: centre free segments, then order and spread lines that share a
|
|
23
|
+
* channel or a card side, so they read as separate wires and never
|
|
24
|
+
* cross right at a card (nudge.ts).
|
|
25
|
+
* 4. Check: no segment may pass through a card other than one the line had
|
|
26
|
+
* to ignore. A line that can't be routed at all (its end is buried under
|
|
27
|
+
* another card) falls back to today's simple three-segment path.
|
|
28
|
+
*/
|
|
29
|
+
import { type RoutedLine, type RoutingInput } from "../../types.js";
|
|
30
|
+
export interface AvoidResult {
|
|
31
|
+
lines: Map<string, RoutedLine>;
|
|
32
|
+
/** Lines with no route around the cards: they got today's three-segment path. */
|
|
33
|
+
fallbacks: Set<string>;
|
|
34
|
+
/** Lines routed only by letting them pass under cards that crowd their ends. */
|
|
35
|
+
relaxed: Map<string, Set<string>>;
|
|
36
|
+
/** Grid size and A* expansions, for tests and the perf readout. */
|
|
37
|
+
stats: {
|
|
38
|
+
gridX: number;
|
|
39
|
+
gridY: number;
|
|
40
|
+
pops: number;
|
|
41
|
+
reused: number;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
/** A route-around router with its own memory of the last pass. */
|
|
45
|
+
export interface AvoidRouter {
|
|
46
|
+
/** Route every line; lines away from what changed since the last pass keep their routes. */
|
|
47
|
+
route(input: RoutingInput): AvoidResult;
|
|
48
|
+
/** Drop the last pass: the next `route` routes every line from scratch. */
|
|
49
|
+
reset(): void;
|
|
50
|
+
}
|
|
51
|
+
export declare function createAvoidRouter(): AvoidRouter;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Route-around router — A* over the routing grid.
|
|
3
|
+
*
|
|
4
|
+
* A state is (grid node, heading): bends cost BEND and a line may never
|
|
5
|
+
* double back on itself, so the search needs to know which way it is going.
|
|
6
|
+
* Many sources and many targets: every allowed anchor slot on every free
|
|
7
|
+
* side of the two cards is a seed / a goal (`Port`), each carrying its own
|
|
8
|
+
* cost (how far the anchor sits off the side's centre, plus the straight leg
|
|
9
|
+
* to the grid). The search stops at the cheapest complete route: a goal
|
|
10
|
+
* reached travelling along its leg towards the card, or turning onto it (one
|
|
11
|
+
* more bend).
|
|
12
|
+
*
|
|
13
|
+
* Costs: length, BEND per bend, CROSS per line routed earlier that this one
|
|
14
|
+
* crosses, SHARE per px of grid edge that earlier lines already use, and
|
|
15
|
+
* ZONE_COST per px run inside another card side's anchor zone. The
|
|
16
|
+
* heuristic is scaled by HEURISTIC_WEIGHT (bounded-suboptimal A*).
|
|
17
|
+
*
|
|
18
|
+
* The heuristic is the classic orthogonal-routing lower bound (as libavoid
|
|
19
|
+
* uses): for each side of the target, the Manhattan distance to its port
|
|
20
|
+
* line plus BEND × the fewest bends that can still arrive on it square-on
|
|
21
|
+
* from the current heading. Without the bend count a Z-shaped route (two
|
|
22
|
+
* bends) leaves every one-bend state between the cards cheaper than the
|
|
23
|
+
* answer, and A* floods the whole rectangle between them.
|
|
24
|
+
*
|
|
25
|
+
* Typed arrays are reused across searches with a stamp, so a search costs
|
|
26
|
+
* only what it touches.
|
|
27
|
+
*/
|
|
28
|
+
import type { Point, Side } from "../../types.js";
|
|
29
|
+
import type { Grid } from "./grid.js";
|
|
30
|
+
import type { Orient } from "./geom.js";
|
|
31
|
+
export interface Port {
|
|
32
|
+
side: Side;
|
|
33
|
+
/** The anchor's coordinate along the side. */
|
|
34
|
+
c: number;
|
|
35
|
+
/** The stub point (PATH_GAP off the card), where the drawn line starts / ends. */
|
|
36
|
+
point: Point;
|
|
37
|
+
/** The grid node at the far end of the straight leg (≥ MIN_LEG from the stub). */
|
|
38
|
+
node: number;
|
|
39
|
+
/** Orientation of the leg. */
|
|
40
|
+
orient: Orient;
|
|
41
|
+
/** Direction of travel along the leg: away from the card for a seed, towards it for a goal. */
|
|
42
|
+
dir: 1 | -1;
|
|
43
|
+
/** Anchor penalty + leg length (+ crossings of lines already routed across the leg). */
|
|
44
|
+
cost: number;
|
|
45
|
+
/** The slot's grid line (row for a horizontal leg, column for a vertical one). */
|
|
46
|
+
lane: number;
|
|
47
|
+
/** Elementary grid edges the leg covers along its lane: [legLo, legHi]. */
|
|
48
|
+
legLo: number;
|
|
49
|
+
legHi: number;
|
|
50
|
+
}
|
|
51
|
+
export interface Found {
|
|
52
|
+
/** Grid nodes from the seed's node to the goal's node. */
|
|
53
|
+
nodes: number[];
|
|
54
|
+
src: Port;
|
|
55
|
+
tgt: Port;
|
|
56
|
+
cost: number;
|
|
57
|
+
}
|
|
58
|
+
export declare class Searcher {
|
|
59
|
+
private cap;
|
|
60
|
+
private gScore;
|
|
61
|
+
private stamp;
|
|
62
|
+
private parent;
|
|
63
|
+
private seedOf;
|
|
64
|
+
private goalStamp;
|
|
65
|
+
private goalOf;
|
|
66
|
+
private id;
|
|
67
|
+
private readonly heap;
|
|
68
|
+
/** Expansions in the last search (for perf tests). */
|
|
69
|
+
lastPops: number;
|
|
70
|
+
/** Target sides for the heuristic: [ux, sgn, up, lo, hi] × up to 4. */
|
|
71
|
+
private readonly targetBuf;
|
|
72
|
+
private zeroBuf;
|
|
73
|
+
private zeros;
|
|
74
|
+
private ensure;
|
|
75
|
+
/**
|
|
76
|
+
* `zones`: keep out of the anchor zones in front of card sides other lines
|
|
77
|
+
* attach to (off for a last-ditch retry).
|
|
78
|
+
*/
|
|
79
|
+
search(grid: Grid, seeds: readonly Port[], goals: readonly Port[], maxPops: number, zones?: boolean, weight?: number, cross?: number): Found | null;
|
|
80
|
+
private build;
|
|
81
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Edge routing — the router: orthogonal routing around every table card, with
|
|
3
|
+
* parallel lines in a shared corridor nudged apart.
|
|
4
|
+
*
|
|
5
|
+
* Every line is routed on a sparse orthogonal grid around the padded cards
|
|
6
|
+
* (A*, length + bends + crossings), may leave or enter whichever side of its
|
|
7
|
+
* card is best, and is then nudged so lines that share a channel or a card
|
|
8
|
+
* side read as separate wires and never cross right at a card. See
|
|
9
|
+
* `orthogonal/router.ts` for the pass and `orthogonal/params.ts` for the knobs.
|
|
10
|
+
*/
|
|
11
|
+
export { createAvoidRouter, type AvoidRouter, type AvoidResult } from './orthogonal/router.js';
|