@mark1russell7/highways 0.1.4 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -80,7 +80,10 @@ A host layout can pack the boxes of a far view in a grid, with no gap between th
80
80
  - With the method `lines`, the lanes of a gap are the straight lines between the centers of the boxes that cross the gap.
81
81
  - `openGaps(items, gaps)` moves each box by the growth of each gap before it. The boxes keep their size, their order and their alignment, so a grid stays a grid.
82
82
  - `makeRoom(items, links, options)` gives the gaps and the open board. `maxGrowth` limits the growth of the board on each axis, as a share of its size.
83
+ - With `passes: 2`, `roomDemand` finds the lanes again on the open board and adds the growth that they need. `maxGrowth` limits the sum of the passes.
83
84
  - `roomLinks(graph)` gives the links of a graph, with the count of the edges of each pair.
85
+ - `gapLanes(items, links, options)` gives each link of a far view its own lane in each gap. The lanes are `spacing` apart, around the middle of the gap.
86
+ - The option `order` of `gapLanes` sets the lane order in a gap: `links` (the order of the list), `ends` (by the sides of the turns) or `crossings` (the default).
84
87
 
85
88
  The host layout still packs the boxes. These functions do not place a box. They only open the gaps between the boxes.
86
89
 
@@ -104,6 +107,121 @@ The benchmark measures the board `packedGridBoard`: 96 touching boxes and 132 li
104
107
 
105
108
  On the packed board, the links are short only because they go through the boxes. The method `routes` is the default, because it gives the fewest crossings for its area.
106
109
 
110
+ `sweepRoom` changes one knob at a time on the same board. `growthSettings`, `laneSettings` and `passSettings` give the settings of the benchmark.
111
+
112
+ | Knob | Setting | Area | Crossings | Mean detour | Worst detour | Lane routes through a box |
113
+ |---|---|---:|---:|---:|---:|---:|
114
+ | `maxGrowth` | 0.12 | 1.25 | 288 | 1.34 | 2.33 | 6 |
115
+ | `maxGrowth` | 0.25 | 1.46 | 284 | 1.32 | 2.14 | 2 |
116
+ | `maxGrowth` | 0.30 and more | 1.50 | 276 | 1.32 | 2.09 | 2 |
117
+ | Lane order | `links` | 1.50 | 585 | 1.34 | 2.30 | 2 |
118
+ | Lane order | `ends` | 1.50 | 551 | 1.33 | 2.19 | 2 |
119
+ | Lane order | `crossings` | 1.50 | 467 | 1.33 | 2.23 | 2 |
120
+ | Passes | 2 | 1.57 | 281 | 1.32 | 2.03 | 2 |
121
+ | Gutter | 12 | 1.58 | 282 | 1.32 | 2.03 | 2 |
122
+
123
+ The links of a gap without lanes share one line. Thus the crossings on that line do not count. With a lane for each link, the crossings show, and the order `crossings` removes 20% of them. The spacing changes the crossings less than 5%. The mean detour stays near 1.32 for each knob, because a link with legs parallel to the axes goes around the boxes.
124
+
125
+ ## Options of the zoom levels
126
+
127
+ The router `sections` makes its hubs and its zoom levels from a tree of groups. Each rule and each limit of that tree is an option. The defaults give the output of version 0.1.5.
128
+
129
+ | Option | Default | Where | What it sets |
130
+ |---|---|---|---|
131
+ | `group` | the quadtree | `sectionsRouter` | The grouping rule: a strategy of the step `group`. `groupOptions` gives its options. |
132
+ | `levels` | each depth | `sectionsRouter` | The depths of the tree that get a level with groups. The level of single nodes always comes last. |
133
+ | `stretch` | 2 | `sectionsRouter` | The hub limit. A way through the hubs is at most this many times the straight line. `Infinity` keeps each such way. |
134
+ | `stretchPad` | 4 | `sectionsRouter` | A way through the hubs can be this many margins longer than the hub limit. |
135
+ | `hubSlack` | 80 | `sectionsRouter` | A way skips a hub that is this many units farther from the target than the way was before. |
136
+ | `hubs` | `Infinity` | `sectionsRouter` | The number of hubs on each side of the lowest common section that a way passes. |
137
+ | `cap` | 8 | `buildSections` | The section limit. A section with more nodes splits in four. |
138
+ | `maxDepth` | 16 | `buildSections` | The depth limit of the tree. |
139
+ | `collapse` | 32 | `buildSections` | A cell shrinks to one quarter at most this many times without a split. |
140
+ | `tightMargin` | 1 | `levelLinks` | The margin of the second corner graph of the level links. |
141
+ | `plugInset` | 6 | `levelLinks` | A plug of a level link stays this far from the corners of its side. |
142
+ | `routesAt`, `itemAt` | 22, 120 | `pickLevel` | The far-view thresholds, in pixels on the screen. `PICK` holds them. |
143
+
144
+ `SECTIONS_DEFAULTS`, `SECTION_DEFAULTS`, `LEVEL_LINK_DEFAULTS` and `PICK` give the default numbers. `itemSizes(levels)` gives the sizes that `pickLevel(sizes, scale, options)` reads.
145
+
146
+ The built-in grouping rules are in `groupStrategies`. The rule `quadtree` splits the plane, as `buildSections` does. The rule `keys` makes one group for each key of the nodes. Its default key is the string `data.group` of a node, and a node without a key is a group alone. With the option `split`, a large group splits into a quadtree.
147
+
148
+ A consumer rule groups the nodes by its own field. Then the far view shows one item for each group:
149
+
150
+ ```ts
151
+ import { defineStrategy, keyedSections, sectionsRouter } from "@mark1russell7/highways";
152
+
153
+ const byTeam = defineStrategy({
154
+ step: "group",
155
+ name: "team",
156
+ describe: "One group for each team.",
157
+ defaults: {},
158
+ run: (graph) => keyedSections(graph.nodes, { key: (node) => String(node.data?.["team"]) }),
159
+ });
160
+ const result = sectionsRouter({ name: "teams", group: byTeam }).route(graph);
161
+ // result.levels[1] has one item for each team. The id of an item is "s:" and the team.
162
+ ```
163
+
164
+ The router examines the tree of a consumer rule with `checkSections`. Each node must be in exactly one leaf. The children of a section split its nodes, and a child is one level deeper than its parent.
165
+
166
+ The benchmark sweeps the hub limit, `hubSlack`, `cap` and the grouping rules on the case boards. Each table starts with the defaults. On `case-hub-chain`:
167
+
168
+ | `stretch` | Worst detour | Routes through hubs | Shared runs | Out and back |
169
+ |---|---:|---:|---:|---:|
170
+ | 1.25 | 1.19 | 14 | 4 | 0 |
171
+ | 1.5 | 1.39 | 20 | 8 | 0 |
172
+ | 2 (default) | 1.78 | 24 | 10 | 0 |
173
+ | 3 | 2.05 | 25 | 11 | 0 |
174
+
175
+ A smaller hub limit gives shorter routes and fewer shared runs. No route goes through a box or out and back at any value. On `case-many-groups`, the rule `keys` has one key for each group of cards. It gives 32 items at the far view, and no level link goes out and back.
176
+
177
+ ## Strategies
178
+
179
+ The steps of the pipeline that vary have strategies. Each step has one interface, so a consumer chooses a strategy by its name or adds its own.
180
+
181
+ | Step | Input | Output | Built-in strategies |
182
+ |---|---|---|---|
183
+ | `group` | `Graph` | `Section`: the tree of groups of the router `sections` | `quadtree`, `keys` |
184
+ | `router` | `Graph` | `Result` | `straight`, `plugs`, `corners`, `corners-smooth`, `trunks`, `shared`, `shared-all`, `sections`, `sections-tree` |
185
+ | `curve` | `CurveInput`: the points, the normals of the plugs and the free space | `CurveSegment[]` | `straight`, `rounded`, `spline`, `arc` |
186
+ | `room` | `RoomInput`: the boxes and the links of a board | `Gap[]` | `routes`, `lines` |
187
+
188
+ A strategy has a `step`, a `name`, a `describe` text, its `defaults` and the method `run(input, options?)`. The type of the options comes from `defaults`. Each option that a call does not give gets its default value.
189
+
190
+ - `defineStrategy({ step, name, describe, defaults, run, maxNodes? })` makes a strategy. The function `run` gets each option.
191
+ - `createRegistry(strategies)` makes a registry. Two strategies with one step and one name are an error.
192
+ - `register(registry, ...strategies)` gives a new registry. It does not change the registry before it.
193
+ - `listStrategies(registry, step)` gives the strategies of a step, in order. `findStrategy` and `getStrategy` give one strategy by its name.
194
+ - `asRouter(strategy, options?)` gives a `Router`, and `routersOf(registry)` gives the routers of a registry.
195
+ - `STRATEGIES` is the registry of `BUILT_IN_STRATEGIES`. The records `groupStrategies`, `routerStrategies`, `curveStrategies` and `roomStrategies` give each built-in strategy with its typed options.
196
+
197
+ A registry is data. No import changes it. The benchmark and the site take their lists from `STRATEGIES`, so a new built-in strategy gets its rows and its button with no other edit. A consumer makes its own registry:
198
+
199
+ ```ts
200
+ import { BUILT_IN_STRATEGIES, center, createRegistry, defineStrategy, FIXTURES, routersOf, runBenchmark } from "@mark1russell7/highways";
201
+
202
+ const elbow = defineStrategy({
203
+ step: "router",
204
+ name: "elbow",
205
+ describe: "Each edge goes along one axis, then along the other, from center to center.",
206
+ defaults: { turn: "x" as "x" | "y" },
207
+ run(graph, o) {
208
+ const box = new Map(graph.nodes.map((n) => [n.id, n.box]));
209
+ const routes = graph.edges.map((e) => {
210
+ const a = center(box.get(e.source.node)!);
211
+ const b = center(box.get(e.target.node)!);
212
+ return { edge: e.id, points: [a, o.turn === "x" ? { x: b.x, y: a.y } : { x: a.x, y: b.y }, b] };
213
+ });
214
+ return { routes };
215
+ },
216
+ });
217
+ const registry = createRegistry([...BUILT_IN_STRATEGIES, elbow]);
218
+ const rows = runBenchmark(routersOf(registry), FIXTURES.slice(0, 1)); // The rows include the router `elbow`.
219
+ ```
220
+
221
+ The built-in strategies give the same output as their functions. `curveStrategies.rounded.run({ points, space }, { radius: 8 })` gives the segments of `curveOf(points, { type: "rounded", space, radius: 8 })`. `benchCurveStrategies` and the fourth argument of `benchRoom` take a list of strategies.
222
+
223
+ The plugs have one strategy, so they have no step yet. The step `group` gives the grouping rule of the zoom levels.
224
+
107
225
  ## License
108
226
 
109
227
  MIT