@grundyjs/algiviz 0.5.0 → 0.5.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 +7 -0
- package/README.md +176 -304
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.5.1 — 2026-10-14
|
|
4
|
+
|
|
5
|
+
- Update the README around the current public API and examples; remove migration
|
|
6
|
+
instructions and detailed legacy API documentation.
|
|
7
|
+
- Correct demo input limits and clarify playback, snapshot memory and rendering.
|
|
8
|
+
- No runtime API or behavior changes.
|
|
9
|
+
|
|
3
10
|
## 0.5.0 — 2026-10-14
|
|
4
11
|
|
|
5
12
|
- Cache immutable array and quick-sort scene projections and avoid repeated scale/layout
|
package/README.md
CHANGED
|
@@ -1,10 +1,28 @@
|
|
|
1
1
|
# AlgiViz
|
|
2
2
|
|
|
3
|
-
A TypeScript toolkit for user-defined algorithm visualizations. You write the
|
|
3
|
+
A TypeScript toolkit for user-defined algorithm visualizations. You write the
|
|
4
|
+
algorithm and define your objects; AlgiViz provides array operations, playback,
|
|
5
|
+
typed scene dispatch and a ready-made Canvas array renderer. No runtime dependencies.
|
|
6
|
+
Playback works without DOM, React or Next.js. ESM only, with TypeScript declarations.
|
|
7
|
+
|
|
8
|
+
This README describes version 0.5.1.
|
|
9
|
+
|
|
10
|
+
## Install
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
npm install @grundyjs/algiviz
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
There is no root entry point or CommonJS build. Import the modules you need:
|
|
17
|
+
`/array`, `/playback`, `/scene` and `/canvas`.
|
|
18
|
+
|
|
19
|
+
The deprecated `/core` sorting API remains available for compatibility.
|
|
20
|
+
For new integrations, use the APIs below. Version 0.x may introduce breaking changes
|
|
21
|
+
in minor releases; consult [CHANGELOG.md](CHANGELOG.md) when updating.
|
|
4
22
|
|
|
5
23
|
## User-defined algorithms
|
|
6
24
|
|
|
7
|
-
|
|
25
|
+
|
|
8
26
|
New applications use these entry points, which do not import bundled algorithms:
|
|
9
27
|
|
|
10
28
|
- `@grundyjs/algiviz/array`: array operations and generator definitions.
|
|
@@ -75,12 +93,8 @@ order throw before mutating the workspace. Read snapshots rather than modifying
|
|
|
75
93
|
items yourself. `createArrayTrace(values)` exposes the same operations directly,
|
|
76
94
|
with immediate input validation, for authors writing their own generator wrapper.
|
|
77
95
|
|
|
78
|
-
|
|
79
|
-
The
|
|
80
|
-
remain available through isolated compatibility implementations using the same
|
|
81
|
-
operations; they are retained in the package until a breaking release removes them.
|
|
82
|
-
Existing insertion/bubble traces remain unchanged. `ArrayEvent` also includes the
|
|
83
|
-
new `highlight` event, so exhaustive event switches must handle it.
|
|
96
|
+
Insertion and bubble sort implementations live in [examples/algorithms.mjs](examples/algorithms.mjs).
|
|
97
|
+
The algorithms belong to the application; they require no changes to the library.
|
|
84
98
|
|
|
85
99
|
### Custom structures and renderers
|
|
86
100
|
|
|
@@ -111,7 +125,7 @@ graph/tree algorithm or operation set.
|
|
|
111
125
|
|
|
112
126
|
### User-defined objects and visualizations
|
|
113
127
|
|
|
114
|
-
|
|
128
|
+
Define a type map with your own data, then
|
|
115
129
|
provide a visualization for every type. AlgiViz matches identities and dispatches
|
|
116
130
|
objects; your handlers decide how data becomes geometry and how it interpolates.
|
|
117
131
|
|
|
@@ -180,30 +194,9 @@ with `bar` objects whose data contains `item`, `index` and `held`. The built-in
|
|
|
180
194
|
array renderer uses this same dispatcher. You can provide a different `bar`
|
|
181
195
|
visualization using `createSceneRenderer<ArrayObjects, YourContext>`.
|
|
182
196
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
pointer movement and removal. No tree-specific code is added to AlgiViz. The Renderer selector switches between Canvas and SVG without restarting playback. The SVG example retains elements by ID, restores layer order and removes stale elements on every frame, including after a seek that skips exit transitions. See [examples/tree-svg.mjs](examples/tree-svg.mjs).
|
|
187
|
-
|
|
188
|
-
For a second custom scene, open `examples/merge.html`. Its application-owned
|
|
189
|
-
[merge algorithm](examples/merge-algorithm.mjs) and
|
|
190
|
-
[renderer](examples/merge-scene.mjs) use `/playback` and `/scene` to show the main
|
|
191
|
-
array and a reusable buffer. Equal values have letter suffixes to demonstrate
|
|
192
|
-
stability. Cells use row/position IDs because copy-back temporarily duplicates
|
|
193
|
-
item identities. Play/pause, step controls, speed and seeking support up to 64
|
|
194
|
-
input values; text status, reduced motion and hidden-tab pausing are included.
|
|
195
|
-
Select Generator for forward-only playback with O(n) retained snapshot memory;
|
|
196
|
-
steps are produced on demand, and restarting creates a fresh iterator. History
|
|
197
|
-
mode enables backward stepping and seeking with up to 64 values. Generator mode
|
|
198
|
-
accepts up to 2,000 values; dense charts hide labels and text status previews the
|
|
199
|
-
first 24 cells. Switching to history requires loading an array within its limit.
|
|
200
|
-
The example stores full history, costing O(n² log n) space in the worst case;
|
|
201
|
-
the underlying algorithm uses O(n) auxiliary memory. No merge-sort package
|
|
202
|
-
export or new library API is needed.
|
|
203
|
-
|
|
204
|
-
### State-to-scene projection (since 0.5.0)
|
|
205
|
-
|
|
206
|
-
`createSceneProjector` was added in 0.5.0. It adapts
|
|
197
|
+
### State-to-scene projection
|
|
198
|
+
|
|
199
|
+
`createSceneProjector` adapts
|
|
207
200
|
playback frames from your state model to scenes without changing the algorithm:
|
|
208
201
|
|
|
209
202
|
```js
|
|
@@ -227,309 +220,188 @@ or deep-frozen. Scene validation still happens in the scene renderer.
|
|
|
227
220
|
The merge example uses this adapter to avoid rebuilding two cell scenes on every
|
|
228
221
|
animation frame. The algorithm, object types and visual behavior remain external.
|
|
229
222
|
|
|
230
|
-
##
|
|
223
|
+
## Playback and rendering
|
|
231
224
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
225
|
+
Array players and timelines use the same frame shape as generic playback:
|
|
226
|
+
`previous`, `current`, `progress`, `stepIndex` and `event`.
|
|
227
|
+
A frame describes the transition into the current step; that step's state is the
|
|
228
|
+
state after its operation.
|
|
236
229
|
|
|
237
|
-
|
|
|
230
|
+
| API | Behavior |
|
|
238
231
|
| --- | --- |
|
|
239
|
-
| `
|
|
240
|
-
| `
|
|
241
|
-
| `
|
|
242
|
-
|
|
|
243
|
-
|
|
|
244
|
-
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
```sh
|
|
265
|
-
npm install @grundyjs/algiviz
|
|
266
|
-
```
|
|
267
|
-
|
|
268
|
-
Try the [interactive insertion sort demo](https://grundyjs.ru/algorithms/insertion-sort/). Import the core from `@grundyjs/algiviz/core` and the browser renderer from `@grundyjs/algiviz/canvas`; there is no root entry point or CommonJS build. Version 0.x is an initial API and may change in later minor releases.
|
|
269
|
-
|
|
270
|
-
## Development
|
|
271
|
-
|
|
272
|
-
Requires Node.js 22 or later and npm.
|
|
273
|
-
|
|
274
|
-
GitHub Actions runs `npm test` and `npm run test:package` on pushes to `master`
|
|
275
|
-
and pull requests, using Node.js 22 and 24 on Linux and Windows. CI checks do not
|
|
276
|
-
publish packages; publishing remains tied to a GitHub Release.
|
|
277
|
-
|
|
278
|
-
```sh
|
|
279
|
-
npm install
|
|
280
|
-
npm test
|
|
281
|
-
npm pack
|
|
282
|
-
```
|
|
283
|
-
|
|
284
|
-
Build output includes ESM and TypeScript declarations. `npm pack` rebuilds automatically; `npm publish` runs the tests before packing. The archive includes only dist, README, LICENSE, CHANGELOG and package metadata. Licensed under MIT.
|
|
285
|
-
|
|
286
|
-
```js
|
|
287
|
-
import { insertionSortSteps } from "@grundyjs/algiviz/core";
|
|
288
|
-
|
|
289
|
-
const steps = insertionSortSteps([5, 2, 4, 2, 1]);
|
|
290
|
-
console.log(steps.at(-1).state.slots.map(item => item.value));
|
|
291
|
-
// [1, 2, 2, 4, 5]
|
|
292
|
-
```
|
|
293
|
-
|
|
294
|
-
Each step contains a typed event and the complete state **after** it. `start` preserves the input; `done` has no held item and a fully sorted array. Original-index ids (`item-0`, etc.) distinguish equal values and are deterministic within one trace. The input is never modified. Invalid values, including NaN, Infinity and sparse holes, throw TypeError.
|
|
295
|
-
|
|
296
|
-
`select` moves the key into `held` and leaves one null slot. `compare` compares the neighbor with the key. `shift` moves the neighbor into the hole. `insert` puts the key back. All original items occur exactly once across `slots` and `held` in every snapshot. Equal values are never shifted past each other.
|
|
297
|
-
|
|
298
|
-
`sortedPrefixLength` counts the leading occupied slots known to be sorted: initially min(1, n), during insertion it ends at the hole, after insertion it extends through the processed region. `comparisons` counts compare events. `writes` counts shift and insert events, including reinserting an unchanged key; extracting a key and clearing the hole are visualization bookkeeping, not counted writes.
|
|
299
|
-
|
|
300
|
-
The result, steps, events, snapshots, slot arrays and items are frozen at runtime. Items may be shared between snapshots because they are immutable.
|
|
301
|
-
|
|
302
|
-
The underlying sort uses O(n²) comparisons in the worst case and O(n) on already sorted input. This API stores full history, so its worst-case time and memory are O(n³), unlike ordinary in-place insertion sort. Intended for small educational inputs; the demo site limits inputs to 24 items. This core imposes no UI limit.
|
|
303
|
-
|
|
304
|
-
Tests replay every event independently, check item conservation, stability, prefix ordering, counters, frozen snapshots and 1,093 exhaustive inputs with values -1, 0 and 1 of lengths 0–6.
|
|
305
|
-
|
|
306
|
-
## Bubble sort
|
|
307
|
-
|
|
308
|
-
`bubbleSortSteps(values)` collects a frozen history; `iterateBubbleSortSteps(values)`
|
|
309
|
-
yields the same snapshots on demand. Both work with the existing timeline, player
|
|
310
|
-
and canvas renderer. Available since version 0.3.0.
|
|
232
|
+
| `createArrayPlayer(iterable, options)` | Array playback; recognizes the `done` event |
|
|
233
|
+
| `createPlayer(iterable, options)` | Generic playback; requires `isTerminal(step)` |
|
|
234
|
+
| `createArrayTimeline(steps, timing)` / `createTimeline(steps, timing)` | Retained history with `durationMs` and `sample(timeMs)` |
|
|
235
|
+
| `createArrayRenderer({ theme: "dark" \| "light" })` | Full-frame Canvas array rendering |
|
|
236
|
+
| `createSceneRenderer(handlers)` | Dispatches custom objects to your visualization handlers |
|
|
237
|
+
| `createSceneProjector(project)` | Adapts immutable state endpoints to scenes with a bounded cache |
|
|
238
|
+
|
|
239
|
+
Players read the first step at construction and expose `frame`, `advance(deltaMs)`,
|
|
240
|
+
`finished` and `dispose()`. The initial frame has progress 1.
|
|
241
|
+
`advance` accepts finite, non-negative elapsed playback time; zero consumes no steps.
|
|
242
|
+
Pause by not advancing. Apply speed by multiplying elapsed time.
|
|
243
|
+
The optional render callback runs initially and on every advance.
|
|
244
|
+
|
|
245
|
+
The source must reach a terminal step. The player closes the iterator after its
|
|
246
|
+
final transition and becomes finished after `finalHoldMs`. Premature exhaustion,
|
|
247
|
+
source errors and render errors propagate and close the iterator.
|
|
248
|
+
`dispose()` closes early and can be repeated; the frame remains readable, but
|
|
249
|
+
advancing a disposed player throws. Restart with a new iterator and player.
|
|
250
|
+
|
|
251
|
+
Timelines require an array of steps. Step i completes at `i * stepDurationMs`;
|
|
252
|
+
`finalHoldMs` extends the last frame. Sampling clamps finite times to the timeline
|
|
253
|
+
range and rejects non-finite times. There is no seeking or known total duration
|
|
254
|
+
in generator playback.
|
|
255
|
+
|
|
256
|
+
Animation scheduling belongs to your application. For example:
|
|
311
257
|
|
|
312
258
|
```js
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
const steps = bubbleSortSteps([5, 2, 4, 2, 1]);
|
|
316
|
-
const player = createSortPlayer(iterateBubbleSortSteps([5, 2, 4, 2, 1]), {
|
|
317
|
-
stepDurationMs: 350, finalHoldMs: 1000
|
|
318
|
-
});
|
|
319
|
-
```
|
|
320
|
-
|
|
321
|
-
Each left-to-right pass compares adjacent items. `compare` identifies both items;
|
|
322
|
-
`swap` contains their pre-swap `leftId`, `rightId`, `left` and `right` positions,
|
|
323
|
-
and its snapshot is after the exchange. Each swap counts as two array writes.
|
|
324
|
-
Equal items are never swapped, so the sort is stable. `held` remains null and
|
|
325
|
-
`sortedPrefixLength` is zero.
|
|
326
|
-
|
|
327
|
-
`pass` marks a completed pass, with `end` identifying its last compared position.
|
|
328
|
-
The optional `sortedSuffixLength` snapshot field counts trailing items in their
|
|
329
|
-
final positions; renderers treat an omitted value as zero for older traces.
|
|
330
|
-
A pass with no swaps ends the sort early and marks the entire array sorted.
|
|
331
|
-
`done` always has `sortedSuffixLength` equal to the array length.
|
|
332
|
-
|
|
333
|
-
Validation, lazy input copying, freezing and memory costs match insertion sort.
|
|
334
|
-
The underlying algorithm performs O(n²) comparisons in the worst case and O(n)
|
|
335
|
-
on sorted input; full snapshots still make worst-case trace generation O(n³).
|
|
336
|
-
Consumers switching on `SortEvent.type` should handle the new `swap` and `pass` events.
|
|
337
|
-
|
|
338
|
-
## Lazy steps
|
|
339
|
-
|
|
340
|
-
Use `iterateInsertionSortSteps` to consume steps on demand without retaining the
|
|
341
|
-
entire history. It yields the same immutable `SortStep` snapshots as
|
|
342
|
-
`insertionSortSteps`, which collects this iterator into a frozen array.
|
|
343
|
-
|
|
344
|
-
```js
|
|
345
|
-
import { iterateInsertionSortSteps } from "@grundyjs/algiviz/core";
|
|
346
|
-
|
|
347
|
-
const iterator = iterateInsertionSortSteps([5, 2, 4, 2, 1]);
|
|
348
|
-
const start = iterator.next().value;
|
|
349
|
-
const selected = iterator.next().value;
|
|
350
|
-
iterator.return(); // Stop early when no more steps are needed.
|
|
351
|
-
```
|
|
352
|
-
|
|
353
|
-
Creating the generator does not read the input or run the algorithm. The first
|
|
354
|
-
`next()` copies and validates the input; invalid values throw at that point.
|
|
355
|
-
Changes to the input before the first `next()` are observed; later changes do not
|
|
356
|
-
affect the iterator. Each subsequent `next()` runs only to the next step. An
|
|
357
|
-
iterator is single-use; create a new one to restart. A `for...of` loop can also
|
|
358
|
-
consume it, and `break` closes it early.
|
|
359
|
-
|
|
360
|
-
When the consumer retains only a fixed number of snapshots, memory is O(n).
|
|
361
|
-
Every snapshot still copies n slots, so consuming the complete worst-case trace
|
|
362
|
-
still takes O(n³) time. Collecting the iterator into an array restores the full
|
|
363
|
-
history memory cost. Iteration is synchronous; a long loop can block the UI.
|
|
364
|
-
|
|
365
|
-
The existing `createSortTimeline` still requires an array of steps for seeking.
|
|
366
|
-
It does not accept this iterator directly. Use `createSortPlayer` for forward-only
|
|
367
|
-
playback; the generator itself provides no timing or backward seeking.
|
|
368
|
-
|
|
369
|
-
## Sequential playback
|
|
370
|
-
|
|
371
|
-
`createSortPlayer` consumes an iterable of steps and retains only the two adjacent
|
|
372
|
-
snapshots needed for rendering. With the lazy generator, playback uses O(n)
|
|
373
|
-
memory as long as the caller does not retain old frames.
|
|
374
|
-
|
|
375
|
-
```js
|
|
376
|
-
import { createSortPlayer, iterateInsertionSortSteps } from "@grundyjs/algiviz/core";
|
|
377
|
-
import { createSortRenderer } from "@grundyjs/algiviz/canvas";
|
|
378
|
-
|
|
379
|
-
const player = createSortPlayer(iterateInsertionSortSteps([5, 2, 4, 1]), {
|
|
380
|
-
stepDurationMs: 650, finalHoldMs: 2000
|
|
381
|
-
});
|
|
382
|
-
const renderer = createSortRenderer({ theme: "dark" });
|
|
383
|
-
const ctx = canvas.getContext("2d");
|
|
384
|
-
let paused = false;
|
|
385
|
-
let speed = 1;
|
|
259
|
+
const player = createArrayPlayer(reverse.iterate([1, 2, 3, 4]), options);
|
|
386
260
|
let last;
|
|
387
261
|
let requestId;
|
|
262
|
+
let paused = false;
|
|
263
|
+
let speed = 1;
|
|
264
|
+
|
|
388
265
|
function tick(now) {
|
|
389
|
-
const delta = last === undefined ? 0 : now - last;
|
|
266
|
+
const delta = last === undefined ? 0 : Math.min(100, now - last);
|
|
390
267
|
last = now;
|
|
391
268
|
if (!paused) player.advance(delta * speed);
|
|
392
269
|
renderer.render(ctx, player.frame);
|
|
393
270
|
if (!player.finished) requestId = requestAnimationFrame(tick);
|
|
394
271
|
}
|
|
395
272
|
requestId = requestAnimationFrame(tick);
|
|
396
|
-
// Set paused = true/false or speed = 2 from your controls.
|
|
397
273
|
// On teardown: cancelAnimationFrame(requestId); player.dispose();
|
|
274
|
+
// To replay after finishing, create a new player and schedule a new loop.
|
|
398
275
|
```
|
|
399
276
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
returns a `SortFrame` compatible with the existing canvas renderer. Zero does
|
|
404
|
-
not consume steps. Pause by not advancing; multiply delta by a non-negative
|
|
405
|
-
speed factor to change speed.
|
|
406
|
-
- `finished` becomes true after the final transition and `finalHoldMs`. Further
|
|
407
|
-
advances return the final frame. There is no known total duration or seeking.
|
|
408
|
-
- The source must end with a `done` event. The player closes the iterator when
|
|
409
|
-
that transition completes; unexpected exhaustion or source errors throw and
|
|
410
|
-
close playback. `dispose()` closes early and is safe to repeat; afterward the
|
|
411
|
-
frame remains readable but `advance()` throws.
|
|
412
|
-
- Restart by disposing the old player and creating a new player and generator.
|
|
413
|
-
Large deltas synchronously consume all intervening steps, so limit the elapsed
|
|
414
|
-
delta in the UI if resuming from a background tab should not cause a long catch-up.
|
|
415
|
-
|
|
416
|
-
## Timeline and canvas
|
|
417
|
-
|
|
418
|
-
Import `createSortTimeline` from `@grundyjs/algiviz/core` and `createSortRenderer` from `@grundyjs/algiviz/canvas`.
|
|
419
|
-
Step i completes at i * stepDurationMs; finalHoldMs extends the final frame. sample clamps finite times to the timeline range and rejects non-finite times. Frames include the current event for explanations and highlights.
|
|
420
|
-
The renderer draws a full frame using the canvas bitmap dimensions and restores context state. Signed values use magnitude for bar height and retain their sign in labels. Use small inputs for readable labels.
|
|
277
|
+
This is an alternative to the player setup in the earlier array example, reusing
|
|
278
|
+
its `reverse`, `options`, `ctx` and `renderer`. Large deltas consume intervening steps
|
|
279
|
+
synchronously, so cap elapsed time or pause on hidden tabs to avoid a long catch-up.
|
|
421
280
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
281
|
+
The array renderer expects immutable snapshots. It uses Canvas bitmap dimensions
|
|
282
|
+
for layout, draws a full frame and restores context state. Negative values use
|
|
283
|
+
magnitude for bar height and retain their sign in labels; this is not a signed-axis
|
|
284
|
+
plot. Dense charts hide labels. For recording, use a canvas with fixed export
|
|
285
|
+
dimensions. Rendering does not start an animation loop or record video.
|
|
425
286
|
|
|
426
|
-
|
|
427
|
-
const renderer = createSortRenderer({ theme: "dark" });
|
|
428
|
-
renderer.render(canvas.getContext("2d"), timeline.sample(3250));
|
|
429
|
-
```
|
|
287
|
+
### Memory and generation cost
|
|
430
288
|
|
|
431
|
-
|
|
289
|
+
Array operations copy the input, validate finite numbers and reject sparse inputs.
|
|
290
|
+
`defineArrayAlgorithm` performs this work on the first iterator `next()`;
|
|
291
|
+
`createArrayTrace` validates immediately. Each iterator owns its workspace.
|
|
292
|
+
Array steps, events, snapshots and items are frozen; immutable items can be shared
|
|
293
|
+
between snapshots.
|
|
432
294
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
295
|
+
Generator playback retains adjacent snapshots, not the entire history. Array
|
|
296
|
+
playback therefore uses O(n) snapshot memory if the caller does not retain old
|
|
297
|
+
frames. Generic states and algorithm-specific storage may have different costs.
|
|
298
|
+
Each full array snapshot still copies n slots: laziness reduces retained history,
|
|
299
|
+
not the cost of producing every step. Collecting steps restores the history cost.
|
|
300
|
+
For example, a quadratic sorting trace with O(n)-sized snapshots can take O(n³)
|
|
301
|
+
time and history space. Iteration is synchronous and can block the UI.
|
|
440
302
|
|
|
303
|
+
## Browser examples
|
|
441
304
|
|
|
442
|
-
|
|
305
|
+
Build the project, serve the repository root with a static HTTP server and open
|
|
306
|
+
one of these pages. Examples are application code in the repository and are not
|
|
307
|
+
included in the npm archive.
|
|
443
308
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
309
|
+
| Example | What it demonstrates |
|
|
310
|
+
| --- | --- |
|
|
311
|
+
| [Sorting lab](examples/index.html) | Insertion and bubble sort using the array operations and renderer |
|
|
312
|
+
| [Merge sort](examples/merge.html) | Custom state and a two-row scene for the main array and buffer |
|
|
313
|
+
| [Quick sort partitions](examples/quick.html) | Lomuto and three-way partitioning with custom state and scene |
|
|
314
|
+
| [Tree traversal](examples/tree.html) | Custom nodes, edges and pointer with Canvas and retained SVG backends |
|
|
315
|
+
|
|
316
|
+
All examples reuse [demo-player.mjs](examples/demo-player.mjs) and
|
|
317
|
+
[player-settings.mjs](examples/player-settings.mjs): 650 ms per step, 1,000 ms final
|
|
318
|
+
hold, speeds 0.25× through 32×, play/pause, restart and step controls.
|
|
319
|
+
History enables backward stepping and seeking; generator mode disables both.
|
|
320
|
+
Reduced motion disables interpolation, and hidden tabs pause playback.
|
|
321
|
+
|
|
322
|
+
All sorting examples accept up to **64 finite values with history** or **2,000 in
|
|
323
|
+
generator mode**. Enter values or generate an array by count with Random, Sorted or
|
|
324
|
+
Reverse order. These are demo limits, not library restrictions. Switching a large
|
|
325
|
+
array to history requires first loading an array within the history limit.
|
|
326
|
+
Dense charts hide labels; custom sorting examples preview the first 24 cells in
|
|
327
|
+
their text status.
|
|
328
|
+
|
|
329
|
+
### Merge sort
|
|
330
|
+
|
|
331
|
+
The [algorithm](examples/merge-algorithm.mjs) and
|
|
332
|
+
[renderer](examples/merge-scene.mjs) use `/playback` and `/scene`.
|
|
333
|
+
Equal values have letter suffixes to demonstrate stability. Cells use row/position
|
|
334
|
+
IDs because copy-back temporarily duplicates item identities. Left wins ties.
|
|
335
|
+
The algorithm uses O(n) auxiliary memory; full snapshot history has an
|
|
336
|
+
O(n² log n) space upper bound.
|
|
337
|
+
|
|
338
|
+
### Quick sort
|
|
339
|
+
|
|
340
|
+
Choose Lomuto or three-way partitioning while preserving loaded values.
|
|
341
|
+
Both algorithms and their scene belong to the example. Three-way recursion skips
|
|
342
|
+
the equal band: n ≥ 2 equal items take 2n scalar checks and zero swaps, versus
|
|
343
|
+
n(n−1)/2 checks with this Lomuto variant. Neither variant guarantees stability.
|
|
344
|
+
Worst-case algorithm time is quadratic; recursion can use O(n) stack space.
|
|
345
|
+
Full snapshot history has a separate O(n³) space upper bound.
|
|
346
|
+
|
|
347
|
+
### Tree traversal
|
|
348
|
+
|
|
349
|
+
The example defines its own `node`, `edge` and `pointer` types. The pointer follows
|
|
350
|
+
tree edges through shared ancestors between visited nodes. The Renderer selector
|
|
351
|
+
switches between Canvas and SVG without restarting playback.
|
|
352
|
+
[tree-svg.mjs](examples/tree-svg.mjs) retains elements by ID, restores layer order
|
|
353
|
+
and removes stale elements even after seeking skips exit transitions.
|
|
354
|
+
No tree-specific algorithm or renderer is added to the package.
|
|
450
355
|
|
|
451
|
-
|
|
356
|
+
## Development
|
|
452
357
|
|
|
453
|
-
|
|
358
|
+
Requires Node.js 22 or later and npm.
|
|
454
359
|
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
360
|
+
```sh
|
|
361
|
+
npm ci
|
|
362
|
+
npm test
|
|
363
|
+
npm run test:package
|
|
364
|
+
```
|
|
459
365
|
|
|
460
|
-
|
|
366
|
+
`npm test` builds the library, checks TypeScript fixtures and runs the tests.
|
|
367
|
+
`npm run test:package` builds a tarball, installs it offline in an isolated temporary
|
|
368
|
+
consumer, compiles against the installed declarations and exercises public APIs,
|
|
369
|
+
including compatibility exports. The temporary consumer is removed afterward.
|
|
461
370
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
the installed declarations and runs an external algorithm and custom scene with
|
|
465
|
-
both playback modes. It also checks the array adapter and legacy compatibility.
|
|
466
|
-
The temporary consumer is removed afterward. The release workflow runs this check
|
|
467
|
-
before publishing. Source and issues: [urffin/algiviz](https://github.com/urffin/algiviz).
|
|
468
|
-
Publishing is a separate maintainer action.
|
|
469
|
-
|
|
470
|
-
### Publishing from GitHub Releases
|
|
471
|
-
|
|
472
|
-
The `.github/workflows/npm-publish.yml` workflow publishes to npm when a stable
|
|
473
|
-
GitHub Release is published (`release: published`). Drafts, prereleases and tag
|
|
474
|
-
pushes alone do not publish a package. The release tag must be `v` followed by
|
|
475
|
-
the exact version in `package.json` and `package-lock.json` (for example `v0.4.0`).
|
|
476
|
-
The workflow uses Node.js 24 and npm 11. The publish lifecycle runs the tests and
|
|
477
|
-
build before publishing with provenance, using npm trusted publishing (OIDC).
|
|
478
|
-
|
|
479
|
-
One-time setup in the npm package settings, under **Trusted Publisher**:
|
|
480
|
-
|
|
481
|
-
- Provider: GitHub Actions.
|
|
482
|
-
- Organization or user: `urffin`.
|
|
483
|
-
- Repository: `algiviz`.
|
|
484
|
-
- Workflow filename: `npm-publish.yml` (without the directory).
|
|
485
|
-
- Environment: leave empty; this workflow does not use a GitHub environment.
|
|
486
|
-
- Allow direct publishing with `npm publish` if the settings show this option.
|
|
487
|
-
|
|
488
|
-
No `NPM_TOKEN` secret is required. Keep account 2FA enabled.
|
|
489
|
-
See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
|
|
371
|
+
GitHub Actions runs both checks on pushes to `master` and pull requests, using
|
|
372
|
+
Node.js 22 and 24 on Linux and Windows. CI checks do not publish packages.
|
|
490
373
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
reuse an already published npm version; if setup failed before publication,
|
|
495
|
-
correct the settings and rerun the failed workflow.
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
All browser examples share playback defaults from examples/player-settings.mjs:
|
|
499
|
-
The browser controller in `examples/demo-player.mjs` also owns animation scheduling,
|
|
500
|
-
mode changes, stepping, seeking, restart, reduced motion and generator cleanup.
|
|
501
|
-
650 ms per step, 1,000 ms final hold, speeds 0.25× through 32×, history/generator
|
|
502
|
-
modes and forward/backward step controls. Backward stepping and seeking are
|
|
503
|
-
disabled in generator mode. Array input limits are 64 for history and 2,000
|
|
504
|
-
for generators. Reduced motion disables interpolation; hidden tabs pause.
|
|
505
|
-
All sorting demos also share array generation: choose the element count and
|
|
506
|
-
Random, Sorted or Reverse order, then Generate & load. The generated values
|
|
507
|
-
appear in the input field and obey the selected playback mode's limit.
|
|
508
|
-
|
|
509
|
-
## Quick sort partitions example
|
|
510
|
-
|
|
511
|
-
Build and serve the repository root, then open [examples/quick.html](examples/quick.html).
|
|
512
|
-
Choose Lomuto or three-way partitioning, preserving loaded values when switching.
|
|
513
|
-
Both use the shared demo player: history supports seeking; generator mode retains
|
|
514
|
-
adjacent snapshots and supports forward stepping. Speed, restart, reduced motion
|
|
515
|
-
and hidden-tab pausing follow the other examples. Both modes are capped at 64
|
|
516
|
-
finite values here because direct recursion and worst-case traces are expensive.
|
|
517
|
-
This is a demo limit, not a library restriction.
|
|
518
|
-
|
|
519
|
-
The example owns its algorithm and scene and uses the public /scene and /playback
|
|
520
|
-
APIs. Three-way recursion skips the equal band: n >= 2 equal items take 2n scalar
|
|
521
|
-
checks and zero swaps, versus n(n-1)/2 checks with this Lomuto variant. Both may
|
|
522
|
-
be unstable and take quadratic worst-case time. The recursion stack can reach
|
|
523
|
-
O(n); stored history has a separate O(n³) memory upper bound. This adds no new
|
|
524
|
-
package exports and does not require a package release.
|
|
374
|
+
`npm pack` rebuilds automatically. The archive includes dist, README, LICENSE,
|
|
375
|
+
CHANGELOG and package metadata. Source and issues:
|
|
376
|
+
[urffin/algiviz](https://github.com/urffin/algiviz). Licensed under MIT.
|
|
525
377
|
|
|
526
378
|
### Performance checks
|
|
527
379
|
|
|
528
380
|
Run `npm run benchmark` for 2,000-element insertion, bubble, merge and both quick-sort
|
|
529
381
|
variants. It reports generation of 1,000 steps, median rendering CPU time over five
|
|
530
|
-
100-frame samples
|
|
531
|
-
25,000
|
|
532
|
-
GPU measurements. Heap samples use explicit GC and include the Node process;
|
|
533
|
-
are diagnostics, not portable pass/fail thresholds.
|
|
534
|
-
|
|
535
|
-
|
|
382
|
+
100-frame samples and retained Node heap after each 5,000 playback steps, up to
|
|
383
|
+
25,000. Drawing uses a mock Canvas: these are CPU comparisons, not browser FPS or
|
|
384
|
+
GPU measurements. Heap samples use explicit GC and include the Node process;
|
|
385
|
+
they are diagnostics, not portable pass/fail thresholds.
|
|
386
|
+
|
|
387
|
+
## Release
|
|
388
|
+
|
|
389
|
+
Publishing is a maintainer action. Update the package version, lockfile and changelog;
|
|
390
|
+
run `npm test` and `npm run test:package`, commit and push, and check CI on the
|
|
391
|
+
candidate commit.
|
|
392
|
+
|
|
393
|
+
The [publish workflow](.github/workflows/npm-publish.yml) runs when a stable GitHub
|
|
394
|
+
Release is published. Drafts, prereleases and tag pushes alone do not publish.
|
|
395
|
+
Use `v` followed by the exact package version, such as `v0.5.1`, at the tested commit.
|
|
396
|
+
|
|
397
|
+
The workflow uses Node.js 24 and npm 11, checks the version and packed consumer,
|
|
398
|
+
then publishes with provenance through npm trusted publishing (OIDC).
|
|
399
|
+
The publish lifecycle also runs tests and rebuilds. No `NPM_TOKEN` secret is required.
|
|
400
|
+
|
|
401
|
+
The npm trusted publisher must match GitHub owner `urffin`, repository `algiviz`
|
|
402
|
+
and workflow `npm-publish.yml`. The workflow does not use a GitHub environment.
|
|
403
|
+
See [npm trusted publishing](https://docs.npmjs.com/trusted-publishers/).
|
|
404
|
+
|
|
405
|
+
After publication, verify the Actions run and the exact version in npm.
|
|
406
|
+
Never reuse an already published npm version. If a workflow fails, check whether
|
|
407
|
+
publication occurred before deciding whether to rerun it.
|