@mark1russell7/highways 0.9.5 → 0.9.7

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.
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Plug ranges (PORT-RANGE): a free plug of a port with `range` stays in that interval of its side.
3
+ * The plug rules read the interval of each plug, and place the plugs of a side in their order with a least gap.
4
+ */
5
+ import type { Box, Point } from "../geom.ts";
6
+ import type { End, Node, Port, Side } from "../model.ts";
7
+
8
+ /** The tolerance of the comparisons of places along a side. */
9
+ const EPS = 1e-9;
10
+
11
+ /** This function gives the axis along a side. */
12
+ export function alongAxis(side: Side): "x" | "y" {
13
+ return side === "east" || side === "west" ? "y" : "x";
14
+ }
15
+
16
+ /** This function gives the port of an end, or `undefined` when the end has no port or the node has no such port. */
17
+ export function portOfEnd(node: Node, end: End): Port | undefined {
18
+ return end.port === undefined ? undefined : node.ports?.find((p) => p.id === end.port);
19
+ }
20
+
21
+ /**
22
+ * This function gives the interval of a plug with a range, on the axis along its side, or `undefined` for a port without a range.
23
+ * The interval is the range of the port, without the corner insets of the side.
24
+ * When the range is all in a corner inset, the interval is the range.
25
+ */
26
+ export function rangeInterval(box: Box, side: Side, port: Port | undefined, inset = 6): [number, number] | undefined {
27
+ const range = port?.range;
28
+ if (range === undefined) return undefined;
29
+ const along = alongAxis(side);
30
+ const start = along === "y" ? box.y : box.x;
31
+ const len = along === "y" ? box.h : box.w;
32
+ const m = Math.min(inset, len / 4);
33
+ const lo = start + range[0] * len;
34
+ const hi = start + range[1] * len;
35
+ const a = Math.max(lo, start + m);
36
+ const b = Math.min(hi, start + len - m);
37
+ return a <= b ? [a, b] : [lo, hi];
38
+ }
39
+
40
+ /** This function moves a point of a side into the interval of its range, on the axis along the side. A point of a port without a range stays. */
41
+ export function clampToRange(box: Box, side: Side, port: Port | undefined, point: Point, inset = 6): Point {
42
+ const r = rangeInterval(box, side, port, inset);
43
+ if (!r) return point;
44
+ const along = alongAxis(side);
45
+ const v = Math.max(r[0], Math.min(r[1], point[along]));
46
+ return along === "y" ? { x: point.x, y: v } : { x: v, y: point.y };
47
+ }
48
+
49
+ /**
50
+ * This function places `ideal.length` plugs of one side in their given order. The plug `i` stays in the interval from `lo[i]` to `hi[i]`.
51
+ * Two neighbours are at least `gap` apart. When the intervals are too short for the gap, the gap shrinks evenly to the largest gap that they let.
52
+ * The sum of the squares of the moves from the ideal places is the least.
53
+ *
54
+ * When the order cannot keep each plug in its interval, the plugs take the order of their ideal places in their intervals. Thus the intervals win over the order.
55
+ * The result is in the order of `ideal`.
56
+ */
57
+ export function placeInIntervals(ideal: readonly number[], lo: readonly number[], hi: readonly number[], gap: number): number[] {
58
+ const k = ideal.length;
59
+ if (k === 0) return [];
60
+ let order = ideal.map((_, i) => i);
61
+ // A later plug with an interval wholly before the interval of an earlier plug breaks the order.
62
+ let most = -Infinity;
63
+ let broken = false;
64
+ for (const i of order) {
65
+ if (hi[i]! < most - EPS) broken = true;
66
+ most = Math.max(most, lo[i]!);
67
+ }
68
+ if (broken) {
69
+ const c = order.map((i) => Math.max(lo[i]!, Math.min(hi[i]!, ideal[i]!)));
70
+ order = [...order].sort((a, b) => c[a]! - c[b]! || a - b);
71
+ }
72
+ // The largest gap that the order lets: the plug j and the later plug i need (i - j)·s <= hi_i - lo_j.
73
+ let s = Math.max(0, gap);
74
+ for (let i = 1; i < k; i++) {
75
+ const hiI = hi[order[i]!]!;
76
+ for (let j = 0; j < i; j++) s = Math.min(s, (hiI - lo[order[j]!]!) / (i - j));
77
+ }
78
+ s = Math.max(0, s);
79
+ // With y_i = x_i - i·s, the gap becomes the rule y_i <= y_(i+1), and each y_i has the bounds L_i and U_i.
80
+ // The bounds become monotone: L_i is the most of the lower bounds before i, and U_i is the least of the upper bounds after i.
81
+ const lower = new Array<number>(k);
82
+ const upper = new Array<number>(k);
83
+ let run = -Infinity;
84
+ for (let i = 0; i < k; i++) lower[i] = run = Math.max(run, lo[order[i]!]! - i * s);
85
+ run = Infinity;
86
+ for (let i = k - 1; i >= 0; i--) upper[i] = run = Math.min(run, hi[order[i]!]! - i * s);
87
+ // Pool adjacent violators with bounds. The value of a block is its mean, clamped to the bounds of the block.
88
+ // The lower bound of a block is the lower bound of its last item, and its upper bound is the upper bound of its first item.
89
+ const sum: number[] = [];
90
+ const size: number[] = [];
91
+ const blockLo: number[] = [];
92
+ const blockHi: number[] = [];
93
+ const value: number[] = [];
94
+ const clamp = (v: number, a: number, b: number): number => Math.max(a, Math.min(b, v));
95
+ for (let i = 0; i < k; i++) {
96
+ sum.push(ideal[order[i]!]! - i * s);
97
+ size.push(1);
98
+ blockLo.push(lower[i]!);
99
+ blockHi.push(upper[i]!);
100
+ value.push(clamp(sum[sum.length - 1]!, lower[i]!, upper[i]!));
101
+ while (value.length > 1 && value[value.length - 2]! > value[value.length - 1]!) {
102
+ const t = value.length - 1;
103
+ sum[t - 1]! += sum[t]!;
104
+ size[t - 1]! += size[t]!;
105
+ blockLo[t - 1] = blockLo[t]!;
106
+ value[t - 1] = clamp(sum[t - 1]! / size[t - 1]!, blockLo[t - 1]!, blockHi[t - 1]!);
107
+ sum.pop();
108
+ size.pop();
109
+ blockLo.pop();
110
+ blockHi.pop();
111
+ value.pop();
112
+ }
113
+ }
114
+ const out = new Array<number>(k);
115
+ let i = 0;
116
+ value.forEach((v, b) => {
117
+ for (let j = 0; j < size[b]!; j++, i++) out[order[i]!] = v + i * s;
118
+ });
119
+ return out;
120
+ }
@@ -44,3 +44,45 @@ export function rowCardsBoard(options: RowCardsOptions = {}): Graph {
44
44
  }
45
45
  return { nodes, edges };
46
46
  }
47
+
48
+ /** The options of `tallCardsBoard`. */
49
+ export interface TallCardsOptions {
50
+ /** The number of rows of each card. The default is 20. */
51
+ readonly rows?: number;
52
+ /** The height of one row. The default is 55. */
53
+ readonly rowHeight?: number;
54
+ /** The number of rows that the box of the rows of each card shows. The list repeats for more cards. The default is `[3, 4, 5, 3, 4, 5]`. */
55
+ readonly visible?: readonly number[];
56
+ /** The height of the header of a card, above the box of its rows. The default is 40. */
57
+ readonly header?: number;
58
+ /** The height of the footer of a card, below the box of its rows. The default is 24. */
59
+ readonly footer?: number;
60
+ /** With `true`, each port has the range of its row (PORT-RANGE). The default is `true`. */
61
+ readonly ranges?: boolean;
62
+ }
63
+
64
+ /**
65
+ * This function gives a board of 6 cards with tall rows (PORT-RANGE, PLUG-ASSIGN), in 3 columns and 2 lines. It has the links of `rowCardsBoard`.
66
+ * The box of the rows of a card is smaller than the card, because the card has a header and a footer. The box shows only some rows.
67
+ * The port `o<row>` is on the east side, and the port `i<row>` is on the west side.
68
+ * The range of a port is the reach of its row. It goes from the top of the box of the rows plus half a row to its bottom minus half a row.
69
+ */
70
+ export function tallCardsBoard(options: TallCardsOptions = {}): Graph {
71
+ const rows = options.rows ?? 20;
72
+ const rowHeight = options.rowHeight ?? 55;
73
+ const visible = options.visible ?? [3, 4, 5, 3, 4, 5];
74
+ const header = options.header ?? 40;
75
+ const footer = options.footer ?? 24;
76
+ const ranged = options.ranges ?? true;
77
+ const base = rowCardsBoard({ rows });
78
+ const w = 160;
79
+ const tall = header + Math.max(...visible) * rowHeight + footer;
80
+ const nodes = base.nodes.map((n, c): Node => {
81
+ const v = visible[c % visible.length]!;
82
+ const h = header + v * rowHeight + footer;
83
+ const range: [number, number] = [(header + rowHeight / 2) / h, (header + v * rowHeight - rowHeight / 2) / h];
84
+ const ports = (n.ports ?? []).map((p): Port => ({ id: p.id, side: p.id.startsWith("o") ? "east" : "west", ...(ranged ? { range } : {}) }));
85
+ return { id: n.id, box: { x: (c % 3) * (w + 200), y: Math.floor(c / 3) * (tall + 160), w, h }, ports };
86
+ });
87
+ return { nodes, edges: base.edges };
88
+ }
package/src/plugs/side.ts CHANGED
@@ -8,6 +8,7 @@ import { placePlug, pointOnSide, SIDE_NORMAL, sideToward, type Plug } from "../p
8
8
  import type { PlugInput, PlugPair } from "../strategies/model.ts";
9
9
  import type { Terminal } from "../visibility.ts";
10
10
  import { WayFinder } from "../ways.ts";
11
+ import { clampToRange, rangeInterval } from "./range.ts";
11
12
 
12
13
  /** The options of `sidePlugs`. */
13
14
  export interface SidePlugOptions {
@@ -50,6 +51,12 @@ function freeSide(node: Node, end: End, toward: Point, inset: number): FreeSide
50
51
  const m = Math.min(inset, len / 4);
51
52
  const at = side === "east" ? b.x + b.w : side === "west" ? b.x : side === "south" ? b.y + b.h : b.y;
52
53
  const point = pointOnSide(b, side, toward, inset);
54
+ // A port with a range (PORT-RANGE) keeps its plug in the interval of the range.
55
+ const range = port?.range === undefined ? undefined : rangeInterval(b, side, port, inset);
56
+ if (range) {
57
+ const facing = clampToRange(b, side, port, point, inset);
58
+ return { node, side, normal: SIDE_NORMAL[side], along, at, lo: range[0], hi: range[1], facing: { point: facing, normal: SIDE_NORMAL[side] } };
59
+ }
53
60
  return { node, side, normal: SIDE_NORMAL[side], along, at, lo: start + m, hi: start + len - m, facing: { point, normal: SIDE_NORMAL[side] } };
54
61
  }
55
62
 
@@ -7,6 +7,8 @@ import { nodeIndex, portOffset, type End, type Node, type Side } from "../../mod
7
7
  import { nearestSide, placePlug, SIDE_NORMAL, sideToward, type Plug } from "../../plugs.ts";
8
8
  import type { PlugInput, PlugPair } from "../../strategies/model.ts";
9
9
  import { isotonic } from "../justify.ts";
10
+ import { placeInIntervals, rangeInterval } from "../range.ts";
11
+ import { alongOf, keyOf, pointAt, roomOf } from "../walls.ts";
10
12
 
11
13
  /** The options of the plug strategy `spread`. */
12
14
  export interface SpreadOptions {
@@ -29,6 +31,8 @@ interface Slot {
29
31
  readonly ideal: number;
30
32
  /** The index of the port in the ports of its node, then the index of the slot. Thus the rows of a card keep their order in a tie. */
31
33
  readonly rank: readonly [number, number];
34
+ /** The interval of the range of the port on the axis along the wall (PORT-RANGE), or `undefined` for a port without a range. */
35
+ readonly range?: readonly [number, number];
32
36
  /** The far end of the first end of the slot: a fixed plug, or the key of its slot. */
33
37
  far?: Plug | string;
34
38
  /** The place of the plug on the axis along the wall. */
@@ -43,47 +47,6 @@ interface Wall {
43
47
  readonly fixed: number[];
44
48
  }
45
49
 
46
- /** This function gives the axis along a side. */
47
- function alongOf(side: Side): "x" | "y" {
48
- return side === "east" || side === "west" ? "y" : "x";
49
- }
50
-
51
- /** This function gives the room of a wall: the segment of the axis along the side, without the corner insets. */
52
- function roomOf(box: Box, side: Side, inset: number): [number, number] {
53
- const along = alongOf(side);
54
- const start = along === "y" ? box.y : box.x;
55
- const len = along === "y" ? box.h : box.w;
56
- const m = Math.min(inset, len / 4);
57
- return [start + m, start + len - m];
58
- }
59
-
60
- /** This function gives the point of a side at `v` on the axis along the side. */
61
- function pointAt(box: Box, side: Side, v: number): Point {
62
- if (side === "east") return { x: box.x + box.w, y: v };
63
- if (side === "west") return { x: box.x, y: v };
64
- if (side === "south") return { x: v, y: box.y + box.h };
65
- return { x: v, y: box.y };
66
- }
67
-
68
- /**
69
- * This function gives the sort key of a far point on a wall. The key has the order of the angle from the normal of the wall, from the center of the box.
70
- * The key grows in the direction of the axis along the wall. Thus the plugs in the order of the keys do not cross at the wall.
71
- */
72
- function keyOf(box: Box, side: Side, far: Point): number {
73
- const c = center(box);
74
- const n = SIDE_NORMAL[side];
75
- const dx = far.x - c.x;
76
- const dy = far.y - c.y;
77
- const out = dx * n.x + dy * n.y;
78
- const along = alongOf(side) === "y" ? dy : dx;
79
- // A pseudo-angle with the order of the angle: a sum and one division, with no transcendental call. It goes from -2 to 2.
80
- const size = Math.abs(out) + Math.abs(along);
81
- if (size === 0) return 0;
82
- const p = along / size;
83
- if (out >= 0) return p;
84
- return along >= 0 ? 2 - p : -2 - p;
85
- }
86
-
87
50
  /**
88
51
  * This function gives the sort key of a far point for a po-leader (EDA-041): its place on the axis along the wall.
89
52
  * A far point behind the wall gets the key of `keyOf` with a large shift, so it goes to an end of the wall.
@@ -128,6 +91,7 @@ function placeWall(slots: readonly Slot[], fixed: readonly number[], lo: number,
128
91
  part = Math.max(part, p);
129
92
  parts[part]!.push(s);
130
93
  }
94
+ const ranged = slots.some((s) => s.range !== undefined);
131
95
  parts.forEach((list, p) => {
132
96
  if (list.length === 0) return;
133
97
  const a = ends[p]!;
@@ -138,6 +102,27 @@ function placeWall(slots: readonly Slot[], fixed: readonly number[], lo: number,
138
102
  const s = steps > 0 ? Math.max(0, Math.min(gap, (b - a) / steps)) : gap;
139
103
  const lower = a + fa * s;
140
104
  const upper = Math.max(lower, b - fb * s);
105
+ if (ranged) {
106
+ // Each plug with a range stays in the part of its range (PORT-RANGE). A range out of the part gives the nearest place of the part.
107
+ const span = list.map((x): [number, number] => {
108
+ if (!x.range) return [lower, upper];
109
+ const lo = Math.max(lower, x.range[0]);
110
+ const hi = Math.min(upper, x.range[1]);
111
+ if (lo <= hi) return [lo, hi];
112
+ const v = x.range[1] < lower ? lower : upper;
113
+ return [v, v];
114
+ });
115
+ const at = placeInIntervals(
116
+ list.map((x) => idealOf(x)),
117
+ span.map((r) => r[0]),
118
+ span.map((r) => r[1]),
119
+ s,
120
+ );
121
+ list.forEach((x, i) => {
122
+ x.at = at[i]!;
123
+ });
124
+ return;
125
+ }
141
126
  const at = placeInOrder(
142
127
  list.map((x) => idealOf(x)),
143
128
  lower,
@@ -205,7 +190,8 @@ export function spreadWith(input: PlugInput, options: SpreadOptions = {}, leader
205
190
  if (slots.has(key)) return key;
206
191
  const side = port?.side ?? sideToward(node.box, toward);
207
192
  const ideal = placePlug(node, end, toward).point[alongOf(side)];
208
- const slot: Slot = { node, side, ideal, rank: [port ? node.ports!.indexOf(port) : -1, slots.size], at: ideal };
193
+ const range = rangeInterval(node.box, side, port, inset);
194
+ const slot: Slot = { node, side, ideal, rank: [port ? node.ports!.indexOf(port) : -1, slots.size], at: ideal, ...(range ? { range } : {}) };
209
195
  slots.set(key, slot);
210
196
  wallOf(node, side).slots.push(slot);
211
197
  return key;
@@ -0,0 +1,47 @@
1
+ /**
2
+ * The walls of the plug strategies `spread`, `leaders` and `assign`: one side of one node, its room, and the order of its far ends.
3
+ */
4
+ import { center, type Box, type Point } from "../geom.ts";
5
+ import type { Side } from "../model.ts";
6
+ import { SIDE_NORMAL } from "../plugs.ts";
7
+
8
+ /** This function gives the axis along a side. */
9
+ export function alongOf(side: Side): "x" | "y" {
10
+ return side === "east" || side === "west" ? "y" : "x";
11
+ }
12
+
13
+ /** This function gives the room of a wall: the segment of the axis along the side, without the corner insets. */
14
+ export function roomOf(box: Box, side: Side, inset: number): [number, number] {
15
+ const along = alongOf(side);
16
+ const start = along === "y" ? box.y : box.x;
17
+ const len = along === "y" ? box.h : box.w;
18
+ const m = Math.min(inset, len / 4);
19
+ return [start + m, start + len - m];
20
+ }
21
+
22
+ /** This function gives the point of a side at `v` on the axis along the side. */
23
+ export function pointAt(box: Box, side: Side, v: number): Point {
24
+ if (side === "east") return { x: box.x + box.w, y: v };
25
+ if (side === "west") return { x: box.x, y: v };
26
+ if (side === "south") return { x: v, y: box.y + box.h };
27
+ return { x: v, y: box.y };
28
+ }
29
+
30
+ /**
31
+ * This function gives the sort key of a far point on a wall. The key has the order of the angle from the normal of the wall, from the center of the box.
32
+ * The key grows in the direction of the axis along the wall. Thus the plugs in the order of the keys do not cross at the wall.
33
+ */
34
+ export function keyOf(box: Box, side: Side, far: Point): number {
35
+ const c = center(box);
36
+ const n = SIDE_NORMAL[side];
37
+ const dx = far.x - c.x;
38
+ const dy = far.y - c.y;
39
+ const out = dx * n.x + dy * n.y;
40
+ const along = alongOf(side) === "y" ? dy : dx;
41
+ // A pseudo-angle with the order of the angle: a sum and one division, with no transcendental call. It goes from -2 to 2.
42
+ const size = Math.abs(out) + Math.abs(along);
43
+ if (size === 0) return 0;
44
+ const p = along / size;
45
+ if (out >= 0) return p;
46
+ return along >= 0 ? 2 - p : -2 - p;
47
+ }
package/src/plugs.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import { center, type Box, type Point } from "./geom.ts";
2
2
  import { fixedPortPoint, type End, type Node, type Side } from "./model.ts";
3
+ import { clampToRange } from "./plugs/range.ts";
3
4
 
4
5
  /** A plug: the point where a route leaves or enters a node, and the outward normal of its side. */
5
6
  export interface Plug {
@@ -64,12 +65,13 @@ export function pointOnSide(box: Box, side: Side, toward: Point, inset = 6): Poi
64
65
 
65
66
  /**
66
67
  * This function places the plug of one end of an edge. A fixed plug stays at its point. A plug with a side stays on that side.
67
- * The router places a free plug on the side that faces `toward`.
68
+ * The router places a free plug on the side that faces `toward`. A free plug of a port with a `range` stays in that range (`clampToRange`).
68
69
  */
69
70
  export function placePlug(node: Node, end: End, toward: Point): Plug {
70
71
  const fixed = fixedPortPoint(node, end);
71
72
  const port = end.port === undefined ? undefined : node.ports?.find((p) => p.id === end.port);
72
73
  if (fixed) return { point: fixed, normal: SIDE_NORMAL[port?.side ?? nearestSide(node.box, fixed)] };
73
74
  const side = port?.side ?? sideToward(node.box, toward);
74
- return { point: pointOnSide(node.box, side, toward), normal: SIDE_NORMAL[side] };
75
+ const point = pointOnSide(node.box, side, toward);
76
+ return { point: port?.range === undefined ? point : clampToRange(node.box, side, port, point), normal: SIDE_NORMAL[side] };
75
77
  }
@@ -83,6 +83,7 @@ import { facingEnds } from "../plugs/facing/index.ts";
83
83
  import { justifyEnds, type JustifyOptions } from "../plugs/justify.ts";
84
84
  import { sideEnds, type SidePlugOptions } from "../plugs/side.ts";
85
85
  import { SPREAD_DEFAULTS, spreadEnds, type SpreadOptions } from "../plugs/spread/index.ts";
86
+ import { ASSIGN_DEFAULTS, assignEnds, type AssignOptions } from "../plugs/assign/index.ts";
86
87
  import { LEADER_DEFAULTS, leaderEnds, type LeaderOptions } from "../plugs/leaders/index.ts";
87
88
  import { roomDemand, type RoomMethod, type RoomOptions } from "../room/index.ts";
88
89
  import { levelRoom, type LevelRoomOptions } from "../room/levels/index.ts";
@@ -103,7 +104,7 @@ import { TRACING_DEFAULTS, tracing, type TracingOptions } from "../whole/tracing
103
104
  import { createRegistry, defineStrategy, type AnyStrategy, type CurveInput, type LaneBoard, type Registry, type Strategy } from "./model.ts";
104
105
  import type { OptionSchema } from "./options.ts";
105
106
  import { AREA_SCHEMA, CONFLUENT_SCHEMA, CROSSBAR_SCHEMA, FRECHET_SCHEMA, MLSEB_SCHEMA, POWER_SCHEMA, SBEB_SCHEMA } from "./schemas.ts";
106
- import { DEPTHS_SCHEMA, JUSTIFY_SCHEMA, PIPELINE_SCHEMA, SIDE_PLUG_SCHEMA, SPREAD_SCHEMA, CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CONSENSUS_SCHEMA, CUBU_SCHEMA, CUT_SCHEMA, DIFFUSION_SCHEMA, DIVIDED_SCHEMA, EDGE_PATH_SCHEMA, FDEB_SCHEMA, FLOW_CLUSTER_SCHEMA, FLOW_FIELD_SCHEMA, GROWTH_SCHEMA, HDBSCAN_SCHEMA, HIERARCHY_SCHEMA, HIGHWAY_SCHEMA, HOUGH_SCHEMA, HUB_ARC_SCHEMA, KD_SCHEMA, KDEEB_SCHEMA, KEY_SCHEMA, LANES_SCHEMA, LEVEL_HIGHWAY_SCHEMA, LEVEL_ROOM_SCHEMA, LINK_COMMUNITY_SCHEMA, MEET_SCHEMA, MESH_SCHEMA, MINGLE_SCHEMA, NESTED_SCHEMA, OD_CELL_SCHEMA, P_HUB_SCHEMA, QUICKBUNDLES_SCHEMA, RADIUS_SCHEMA, RESISTANCE_SCHEMA, RIDGE_SCHEMA, ROAD_CLASS_SCHEMA, ROOM_SCHEMA, SCALE_SPACE_SCHEMA, SECTION_SCHEMA, SELECT_SCHEMA, SECTIONS_SCHEMA, SHARED_ALL_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, SOFT_KEY_SCHEMA, SPECTRAL_SCHEMA, SPIRAL_SCHEMA, STUB_SCHEMA, TENSION_SCHEMA, TERRITORY_SCHEMA, TRACLUS_SCHEMA, TRUNKS_SCHEMA, WEISZFELD_SCHEMA, WSPD_SCHEMA, ZIPPER_SCHEMA, STEINER_SCHEMA, GAP_SCHEMA, STREET_SCHEMA, SEARCH_SCHEMA, INTERCHANGE_SCHEMA, KNEE_SCHEMA, BRONCHIAL_SCHEMA, BOARD_PORTFOLIO_SCHEMA, VENATION_SCHEMA, BALANCE_SCHEMA, MANIFOLD_SCHEMA, LEADER_SCHEMA, DETAIL_SCHEMA, CURRICULUM_SCHEMA, SLOT_SCHEMA, PREFERENCE_SCHEMA, FIREFLY_SCHEMA, TRACING_SCHEMA } from "./schemas.ts";
107
+ import { ASSIGN_SCHEMA, DEPTHS_SCHEMA, JUSTIFY_SCHEMA, PIPELINE_SCHEMA, SIDE_PLUG_SCHEMA, SPREAD_SCHEMA, CLOTHOID_SCHEMA, CORNERS_SCHEMA, CORRIDOR_SCHEMA, CONSENSUS_SCHEMA, CUBU_SCHEMA, CUT_SCHEMA, DIFFUSION_SCHEMA, DIVIDED_SCHEMA, EDGE_PATH_SCHEMA, FDEB_SCHEMA, FLOW_CLUSTER_SCHEMA, FLOW_FIELD_SCHEMA, GROWTH_SCHEMA, HDBSCAN_SCHEMA, HIERARCHY_SCHEMA, HIGHWAY_SCHEMA, HOUGH_SCHEMA, HUB_ARC_SCHEMA, KD_SCHEMA, KDEEB_SCHEMA, KEY_SCHEMA, LANES_SCHEMA, LEVEL_HIGHWAY_SCHEMA, LEVEL_ROOM_SCHEMA, LINK_COMMUNITY_SCHEMA, MEET_SCHEMA, MESH_SCHEMA, MINGLE_SCHEMA, NESTED_SCHEMA, OD_CELL_SCHEMA, P_HUB_SCHEMA, QUICKBUNDLES_SCHEMA, RADIUS_SCHEMA, RESISTANCE_SCHEMA, RIDGE_SCHEMA, ROAD_CLASS_SCHEMA, ROOM_SCHEMA, SCALE_SPACE_SCHEMA, SECTION_SCHEMA, SELECT_SCHEMA, SECTIONS_SCHEMA, SHARED_ALL_SCHEMA, SHARED_SCHEMA, SMOOTH_SCHEMA, SOFT_KEY_SCHEMA, SPECTRAL_SCHEMA, SPIRAL_SCHEMA, STUB_SCHEMA, TENSION_SCHEMA, TERRITORY_SCHEMA, TRACLUS_SCHEMA, TRUNKS_SCHEMA, WEISZFELD_SCHEMA, WSPD_SCHEMA, ZIPPER_SCHEMA, STEINER_SCHEMA, GAP_SCHEMA, STREET_SCHEMA, SEARCH_SCHEMA, INTERCHANGE_SCHEMA, KNEE_SCHEMA, BRONCHIAL_SCHEMA, BOARD_PORTFOLIO_SCHEMA, VENATION_SCHEMA, BALANCE_SCHEMA, MANIFOLD_SCHEMA, LEADER_SCHEMA, DETAIL_SCHEMA, CURRICULUM_SCHEMA, SLOT_SCHEMA, PREFERENCE_SCHEMA, FIREFLY_SCHEMA, TRACING_SCHEMA } from "./schemas.ts";
107
108
 
108
109
  /** The options of a router strategy: the options of its router, without the name and the limit of the benchmark. */
109
110
  export type RouterPreset<O> = Omit<O, "name" | "maxNodes">;
@@ -1026,6 +1027,7 @@ export const plugStrategies: {
1026
1027
  readonly justify: Strategy<"plugs", JustifyOptions>;
1027
1028
  readonly spread: Strategy<"plugs", SpreadOptions>;
1028
1029
  readonly leaders: Strategy<"plugs", LeaderOptions>;
1030
+ readonly assign: Strategy<"plugs", AssignOptions>;
1029
1031
  } = Object.freeze({
1030
1032
  facing: defineStrategy({
1031
1033
  step: "plugs",
@@ -1067,6 +1069,14 @@ export const plugStrategies: {
1067
1069
  schema: LEADER_SCHEMA,
1068
1070
  run: (input, o) => leaderEnds(input, o),
1069
1071
  }),
1072
+ assign: defineStrategy<"plugs", AssignOptions>({
1073
+ step: "plugs",
1074
+ name: "assign",
1075
+ describe: "PLUG-ASSIGN. The plugs of each wall go into the ranges of their ports with a minimum gap. The most ends are in their range, then the order of the far ends.",
1076
+ defaults: { ...ASSIGN_DEFAULTS },
1077
+ schema: ASSIGN_SCHEMA,
1078
+ run: (input, o) => assignEnds(input, o),
1079
+ }),
1070
1080
  });
1071
1081
 
1072
1082
  /** The built-in level rules: the strategies of the step `levels`. The router `sections` makes its zoom levels with its option `level`. */
@@ -9,6 +9,7 @@ import type { CutOptions } from "../groups/cut/index.ts";
9
9
  import type { JustifyOptions } from "../plugs/justify.ts";
10
10
  import type { SidePlugOptions } from "../plugs/side.ts";
11
11
  import type { SpreadOptions } from "../plugs/spread/index.ts";
12
+ import type { AssignOptions } from "../plugs/assign/index.ts";
12
13
  import type { LeaderOptions } from "../plugs/leaders/index.ts";
13
14
  import type { BalanceOptions } from "../highways/balance/index.ts";
14
15
  import type { ManifoldOptions } from "../highways/manifolds/index.ts";
@@ -977,6 +978,12 @@ export const SPREAD_SCHEMA: OptionSchema<SpreadOptions> = {
977
978
  rounds: { type: "integer", min: 0, max: 8, step: 1, default: 2, describe: "The passes that sort each wall again by the new places of the far ends." },
978
979
  };
979
980
 
981
+ /** The declarations of the options of the plug strategy `assign` (PLUG-ASSIGN). */
982
+ export const ASSIGN_SCHEMA: OptionSchema<AssignOptions> = {
983
+ plugGap: { type: "number", min: 0, max: 24, step: 1, default: 8, describe: "The least gap between two plugs of one wall. On a short wall, the gap shrinks evenly." },
984
+ inset: { type: "number", min: 0, max: 12, step: 1, default: 6, describe: "A plug stays this far from the corners of its wall." },
985
+ };
986
+
980
987
  /** The declarations of the options of the whole strategy `pipeline`. */
981
988
  export const PIPELINE_SCHEMA: OptionSchema<PipelineOptions> = {
982
989
  router: { type: "strategy", step: "router", describe: "The router. A highway plan takes its place." },