@data-profile/fingerprint 0.2.0 → 0.3.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/CHANGELOG.md +14 -0
- package/README.md +2 -2
- package/dist/{chunk-eygy4k4w.js → chunk-y8jc1j2x.js} +23 -245
- package/dist/chunk-y8jc1j2x.js.map +15 -0
- package/dist/index.d.ts +47 -150
- package/dist/index.js +7 -7
- package/dist/index.js.map +4 -3
- package/dist/react/index.d.ts +2 -2
- package/dist/react/index.js +18 -7
- package/dist/react/index.js.map +5 -4
- package/package.json +4 -1
- package/dist/chunk-eygy4k4w.js.map +0 -18
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.3.0](https://github.com/SirKettle/data-profile/compare/fingerprint-v0.2.0...fingerprint-v0.3.0) (2026-10-26)
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
### Features
|
|
7
|
+
|
|
8
|
+
* add @data-profile/core, and build the fingerprint on it ([30873f9](https://github.com/SirKettle/data-profile/commit/30873f9997b51fde6eb96cd233a2c486d87d8f04))
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Dependencies
|
|
12
|
+
|
|
13
|
+
* The following workspace dependencies were updated
|
|
14
|
+
* dependencies
|
|
15
|
+
* @data-profile/core bumped from ^0.0.0 to ^0.1.0
|
|
16
|
+
|
|
3
17
|
## [0.2.0](https://github.com/SirKettle/data-profile/compare/fingerprint-v0.1.0...fingerprint-v0.2.0) (2026-10-26)
|
|
4
18
|
|
|
5
19
|
|
package/README.md
CHANGED
|
@@ -14,8 +14,8 @@ It is not device or browser fingerprinting: nothing here identifies anyone.
|
|
|
14
14
|
there are more items than marks, the print says how many items each mark stands for.
|
|
15
15
|
- **Several things at once.** Colour, shape and lightness can each show a different property.
|
|
16
16
|
- **Your own marks.** Simple shapes by default; a renderer can draw anything in SVG.
|
|
17
|
-
- **Small.**
|
|
18
|
-
or 19.
|
|
17
|
+
- **Small.** One dependency, [`@data-profile/core`](../core), which every data-profile view shares.
|
|
18
|
+
The layout works without React; the components need React 18 or 19.
|
|
19
19
|
|
|
20
20
|
## Install
|
|
21
21
|
|
|
@@ -1,29 +1,5 @@
|
|
|
1
|
-
// temp/src/palette.ts
|
|
2
|
-
var DEFAULT_PALETTE = [
|
|
3
|
-
"#2f6fde",
|
|
4
|
-
"#2bb3e6",
|
|
5
|
-
"#f26b3a",
|
|
6
|
-
"#f5b82e",
|
|
7
|
-
"#14a39a",
|
|
8
|
-
"#7a5af5",
|
|
9
|
-
"#3fae6b",
|
|
10
|
-
"#d9488f",
|
|
11
|
-
"#8c6d46",
|
|
12
|
-
"#5e8c99"
|
|
13
|
-
];
|
|
14
|
-
var UNKNOWN_COLOR = "#b8bcc4";
|
|
15
|
-
|
|
16
|
-
// temp/src/hash.ts
|
|
17
|
-
function stableHash(value) {
|
|
18
|
-
let hash = 2166136261;
|
|
19
|
-
for (let i = 0;i < value.length; i++)
|
|
20
|
-
hash = Math.imul(hash ^ value.charCodeAt(i), 16777619);
|
|
21
|
-
return hash >>> 0;
|
|
22
|
-
}
|
|
23
|
-
var unitHash = (key) => stableHash(key) / 2 ** 32;
|
|
24
|
-
var compareIds = (a, b) => a < b ? -1 : a > b ? 1 : 0;
|
|
25
|
-
|
|
26
1
|
// temp/src/form.ts
|
|
2
|
+
import { unitHash } from "@data-profile/core";
|
|
27
3
|
var clamp = (value, low, high) => Math.min(high, Math.max(low, value));
|
|
28
4
|
var quantise = (value, step = 0.2) => Math.round(value / step) * step;
|
|
29
5
|
function formFromSeed(seed) {
|
|
@@ -86,6 +62,9 @@ function formFromItems(items, dimensions, seed = "fingerprint") {
|
|
|
86
62
|
});
|
|
87
63
|
}
|
|
88
64
|
|
|
65
|
+
// temp/src/layout.ts
|
|
66
|
+
import { encodeProfile } from "@data-profile/core";
|
|
67
|
+
|
|
89
68
|
// temp/src/ridges.ts
|
|
90
69
|
var FRAME = { width: 400, height: 520, cx: 200, cy: 260, rx: 180, ry: 240 };
|
|
91
70
|
var DEG = Math.PI / 180;
|
|
@@ -348,81 +327,11 @@ function slotsAlong(lines, count) {
|
|
|
348
327
|
}
|
|
349
328
|
|
|
350
329
|
// temp/src/layout.ts
|
|
351
|
-
|
|
330
|
+
import { UNKNOWN_CATEGORY_ID } from "@data-profile/core";
|
|
331
|
+
import { DataError } from "@data-profile/core";
|
|
352
332
|
var DEFAULTS = { seed: "fingerprint", maxMarks: 600, minRidges: 6 };
|
|
353
333
|
var MARK_FILL = 0.85;
|
|
354
|
-
|
|
355
|
-
class FingerprintDataError extends Error {
|
|
356
|
-
name = "FingerprintDataError";
|
|
357
|
-
}
|
|
358
334
|
var positive = (value) => value !== undefined && Number.isFinite(value) && value >= 1 ? Math.floor(value) : undefined;
|
|
359
|
-
var DISTINCT = { color: 10, shape: 5 };
|
|
360
|
-
var prepared = new WeakMap;
|
|
361
|
-
function prepare(items) {
|
|
362
|
-
const hit = prepared.get(items);
|
|
363
|
-
if (hit)
|
|
364
|
-
return hit;
|
|
365
|
-
const seen = new Set;
|
|
366
|
-
for (const item of items) {
|
|
367
|
-
if (seen.has(item.id))
|
|
368
|
-
throw new FingerprintDataError(`Duplicate item id "${item.id}"`);
|
|
369
|
-
seen.add(item.id);
|
|
370
|
-
const weight = item.weight ?? 1;
|
|
371
|
-
if (!Number.isFinite(weight) || weight <= 0)
|
|
372
|
-
throw new FingerprintDataError(`Item "${item.id}" has weight ${weight}; weights must be positive`);
|
|
373
|
-
}
|
|
374
|
-
const byId = Uint32Array.from(items.keys()).sort((a, b) => compareIds(items[a].id, items[b].id));
|
|
375
|
-
const idRank = new Uint32Array(items.length);
|
|
376
|
-
byId.forEach((index, rank) => idRank[index] = rank);
|
|
377
|
-
const result = { idRank, ranks: new WeakMap };
|
|
378
|
-
prepared.set(items, result);
|
|
379
|
-
return result;
|
|
380
|
-
}
|
|
381
|
-
function ranksIn(items, ready, d) {
|
|
382
|
-
const hit = ready.ranks.get(d);
|
|
383
|
-
if (hit)
|
|
384
|
-
return hit;
|
|
385
|
-
const index = new Map(d.categories.map((c, i) => [c.id, i]));
|
|
386
|
-
const unknown = d.categories.length;
|
|
387
|
-
const ranks = new Int32Array(items.length);
|
|
388
|
-
items.forEach((item, i) => {
|
|
389
|
-
const value = item.values[d.id];
|
|
390
|
-
ranks[i] = (value != null ? index.get(value) : undefined) ?? unknown;
|
|
391
|
-
});
|
|
392
|
-
ready.ranks.set(d, ranks);
|
|
393
|
-
return ranks;
|
|
394
|
-
}
|
|
395
|
-
function validateDimension(dimension) {
|
|
396
|
-
const categories = new Set;
|
|
397
|
-
for (const category of dimension.categories) {
|
|
398
|
-
if (category.id === UNKNOWN_CATEGORY_ID)
|
|
399
|
-
throw new FingerprintDataError(`Category id "${UNKNOWN_CATEGORY_ID}" is reserved`);
|
|
400
|
-
if (categories.has(category.id))
|
|
401
|
-
throw new FingerprintDataError(`Duplicate category id "${category.id}" in "${dimension.id}"`);
|
|
402
|
-
categories.add(category.id);
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
function resolveEncoding(dimensions, encoding) {
|
|
406
|
-
const arrange = encoding?.arrange ?? dimensions[0]?.id;
|
|
407
|
-
if (arrange === undefined)
|
|
408
|
-
throw new FingerprintDataError("At least one dimension is needed");
|
|
409
|
-
const resolved = {
|
|
410
|
-
arrange,
|
|
411
|
-
color: encoding?.color === undefined ? arrange : encoding.color,
|
|
412
|
-
shape: encoding?.shape === undefined ? arrange : encoding.shape,
|
|
413
|
-
lightness: encoding?.lightness ?? null
|
|
414
|
-
};
|
|
415
|
-
for (const [channel, id] of Object.entries(resolved)) {
|
|
416
|
-
if (id === null)
|
|
417
|
-
continue;
|
|
418
|
-
const dimension = dimensions.find((d) => d.id === id);
|
|
419
|
-
if (!dimension)
|
|
420
|
-
throw new FingerprintDataError(`The ${channel} encoding names "${id}", which is not a dimension`);
|
|
421
|
-
if (channel === "lightness" && !dimension.ordered)
|
|
422
|
-
throw new FingerprintDataError(`"${id}" drives lightness but is not ordered; lighter and darker would read as less and more`);
|
|
423
|
-
}
|
|
424
|
-
return resolved;
|
|
425
|
-
}
|
|
426
335
|
var ridgeCache = new Map;
|
|
427
336
|
function cachedRidges(form, spacing) {
|
|
428
337
|
const key = `${JSON.stringify(form)}:${spacing}`;
|
|
@@ -441,138 +350,40 @@ function layoutFingerprint(input) {
|
|
|
441
350
|
const maxMarks = positive(input.maxMarks) ?? DEFAULTS.maxMarks;
|
|
442
351
|
const minRidges = positive(input.minRidges) ?? DEFAULTS.minRidges;
|
|
443
352
|
const form = resolveForm(seed, input.form);
|
|
444
|
-
const
|
|
445
|
-
const
|
|
446
|
-
const byId = new Map(dimensions.map((d) => [d.id, d]));
|
|
447
|
-
const used = [...new Set([encoded.color, encoded.shape, encoded.lightness, encoded.arrange])].filter((id) => id !== null).map((id) => byId.get(id));
|
|
448
|
-
used.forEach(validateDimension);
|
|
449
|
-
const priority = [...new Set([encoded.arrange, encoded.color, encoded.shape, encoded.lightness])].filter((id) => id !== null).map((id) => byId.get(id));
|
|
450
|
-
const ranks = new Map(used.map((d) => [d.id, ranksIn(items, ready, d)]));
|
|
451
|
-
const categoryAt = (d, rank) => d.categories[rank]?.id ?? UNKNOWN_CATEGORY_ID;
|
|
452
|
-
const groups = used.map((d) => {
|
|
453
|
-
const channels = ["color", "shape", "lightness", "arrange"].filter((c) => encoded[c] === d.id);
|
|
454
|
-
const drives = (c) => channels.includes(c);
|
|
455
|
-
const entry = (id, label, i, color, href) => ({
|
|
456
|
-
id,
|
|
457
|
-
label,
|
|
458
|
-
color: drives("color") ? i === null ? UNKNOWN_COLOR : color ?? DEFAULT_PALETTE[i % DEFAULT_PALETTE.length] : "currentColor",
|
|
459
|
-
shape: drives("shape") ? i : 0,
|
|
460
|
-
lightness: drives("lightness") && i !== null ? d.categories.length > 1 ? i / (d.categories.length - 1) : 1 : null,
|
|
461
|
-
href,
|
|
462
|
-
unknown: i === null,
|
|
463
|
-
count: 0,
|
|
464
|
-
weight: 0
|
|
465
|
-
});
|
|
466
|
-
return {
|
|
467
|
-
dimensionId: d.id,
|
|
468
|
-
label: d.label,
|
|
469
|
-
channels,
|
|
470
|
-
entries: [
|
|
471
|
-
...d.categories.map((c, i) => entry(c.id, c.label, i, c.color, c.href)),
|
|
472
|
-
entry(UNKNOWN_CATEGORY_ID, d.unknownLabel ?? "Unknown", null)
|
|
473
|
-
]
|
|
474
|
-
};
|
|
475
|
-
});
|
|
476
|
-
const groupOf = new Map(groups.map((g) => [g.dimensionId, g]));
|
|
477
|
-
const entryOf = (dimensionId, categoryId) => {
|
|
478
|
-
const d = byId.get(dimensionId);
|
|
479
|
-
const rank = d.categories.findIndex((c) => c.id === categoryId);
|
|
480
|
-
return groupOf.get(dimensionId).entries[rank === -1 ? d.categories.length : rank];
|
|
481
|
-
};
|
|
482
|
-
for (const d of used) {
|
|
483
|
-
const entries = groupOf.get(d.id).entries;
|
|
484
|
-
const r = ranks.get(d.id);
|
|
485
|
-
for (let i = 0;i < items.length; i++) {
|
|
486
|
-
const entry = entries[r[i]];
|
|
487
|
-
entry.count += 1;
|
|
488
|
-
entry.weight += items[i].weight ?? 1;
|
|
489
|
-
}
|
|
490
|
-
}
|
|
491
|
-
const n = items.length;
|
|
492
|
-
const comboKeys = new Float64Array(n);
|
|
493
|
-
const priorityRanks = priority.map((d) => ranks.get(d.id));
|
|
494
|
-
const radix = priority.map((d) => d.categories.length + 1);
|
|
495
|
-
for (let i = 0;i < n; i++) {
|
|
496
|
-
let key = 0;
|
|
497
|
-
for (let k = 0;k < priority.length; k++)
|
|
498
|
-
key = key * radix[k] + priorityRanks[k][i];
|
|
499
|
-
comboKeys[i] = key;
|
|
500
|
-
}
|
|
501
|
-
const order = Uint32Array.from(items.keys()).sort((a, b) => comboKeys[a] - comboKeys[b] || ready.idRank[a] - ready.idRank[b]);
|
|
502
|
-
const itemsPerMark = Math.max(1, Math.ceil(n / maxMarks));
|
|
503
|
-
const marksOf = [];
|
|
504
|
-
for (let k = 0;k < n; k++) {
|
|
505
|
-
const item = items[order[k]];
|
|
506
|
-
const last = marksOf.at(-1);
|
|
507
|
-
if (last && comboKeys[order[k]] === comboKeys[order[k - 1]] && last.members.length < itemsPerMark) {
|
|
508
|
-
last.members.push(item);
|
|
509
|
-
continue;
|
|
510
|
-
}
|
|
511
|
-
const categories = Object.fromEntries(used.map((d) => [d.id, categoryAt(d, ranks.get(d.id)[order[k]])]));
|
|
512
|
-
marksOf.push({ key: used.map((d) => categories[d.id]).join("|"), categories, members: [item] });
|
|
513
|
-
}
|
|
514
|
-
const marks = Math.max(1, marksOf.length);
|
|
353
|
+
const { marks, ...profile } = encodeProfile({ items, dimensions, encoding, maxMarks });
|
|
354
|
+
const count = Math.max(1, marks.length);
|
|
515
355
|
const widest = FRAME.rx / minRidges;
|
|
516
|
-
let spacing = Math.min(widest, Math.sqrt(Math.PI * FRAME.rx * FRAME.ry * 0.85 /
|
|
356
|
+
let spacing = Math.min(widest, Math.sqrt(Math.PI * FRAME.rx * FRAME.ry * 0.85 / count));
|
|
517
357
|
let lines = cachedRidges(form, spacing);
|
|
518
|
-
if (
|
|
358
|
+
if (marks.length > 0) {
|
|
519
359
|
const traced = lines.reduce((sum, line) => sum + polylineLength(line), 0);
|
|
520
|
-
const corrected = Math.min(widest, Math.sqrt(traced * spacing /
|
|
360
|
+
const corrected = Math.min(widest, Math.sqrt(traced * spacing / count));
|
|
521
361
|
if (Math.abs(corrected - spacing) / spacing > 0.03) {
|
|
522
362
|
spacing = corrected;
|
|
523
363
|
lines = cachedRidges(form, spacing);
|
|
524
364
|
}
|
|
525
365
|
}
|
|
526
|
-
const { slots, pitch } = slotsAlong(lines,
|
|
366
|
+
const { slots, pitch } = slotsAlong(lines, marks.length);
|
|
527
367
|
const footprint = Math.min(pitch, spacing) * MARK_FILL;
|
|
528
|
-
const
|
|
529
|
-
const
|
|
530
|
-
const perKey = new Map;
|
|
531
|
-
const nodes = marksOf.map((mark, i) => {
|
|
532
|
-
const n2 = perKey.get(mark.key) ?? 0;
|
|
533
|
-
perKey.set(mark.key, n2 + 1);
|
|
368
|
+
const heaviest = Math.max(0, ...marks.map((mark) => mark.weight));
|
|
369
|
+
const nodes = marks.map((mark, i) => {
|
|
534
370
|
const slot = slots[i];
|
|
535
|
-
const property = (channel) => {
|
|
536
|
-
const id = encoded[channel];
|
|
537
|
-
return id === null ? null : entryOf(id, mark.categories[id]);
|
|
538
|
-
};
|
|
539
371
|
return {
|
|
540
|
-
|
|
541
|
-
itemIds: mark.members.map((item) => item.id),
|
|
542
|
-
categories: mark.categories,
|
|
372
|
+
...mark,
|
|
543
373
|
x: slot.x,
|
|
544
374
|
y: slot.y,
|
|
545
375
|
rotation: slot.rotation,
|
|
546
|
-
size: footprint * Math.sqrt(
|
|
547
|
-
weight: weights[i],
|
|
548
|
-
color: property("color")?.color ?? "currentColor",
|
|
549
|
-
shape: encoded.shape === null ? 0 : property("shape").shape,
|
|
550
|
-
lightness: property("lightness")?.lightness ?? null
|
|
376
|
+
size: footprint * Math.sqrt(mark.weight / heaviest)
|
|
551
377
|
};
|
|
552
378
|
});
|
|
553
|
-
for (const group of groups)
|
|
554
|
-
group.entries = group.entries.filter((e) => !e.unknown || e.count > 0);
|
|
555
|
-
const warnings = [];
|
|
556
|
-
for (const channel of ["color", "shape"]) {
|
|
557
|
-
const id = encoded[channel];
|
|
558
|
-
if (id === null)
|
|
559
|
-
continue;
|
|
560
|
-
const shown = groupOf.get(id).entries.filter((e) => !e.unknown && e.count > 0).length;
|
|
561
|
-
if (shown > DISTINCT[channel])
|
|
562
|
-
warnings.push(`${channel === "color" ? "Colour" : "Shape"} shows ${shown} categories of "${byId.get(id).label}"; beyond ${DISTINCT[channel]} they repeat, so only the legend tells them apart.`);
|
|
563
|
-
}
|
|
564
379
|
return {
|
|
565
380
|
width: FRAME.width,
|
|
566
381
|
height: FRAME.height,
|
|
567
|
-
|
|
382
|
+
...profile,
|
|
568
383
|
nodes,
|
|
569
384
|
ridges: lines,
|
|
570
385
|
form,
|
|
571
|
-
ridgeSpacing: spacing
|
|
572
|
-
legends: groups,
|
|
573
|
-
warnings,
|
|
574
|
-
itemsPerMark,
|
|
575
|
-
itemCount: items.length
|
|
386
|
+
ridgeSpacing: spacing
|
|
576
387
|
};
|
|
577
388
|
}
|
|
578
389
|
function emptyLayout() {
|
|
@@ -591,19 +402,6 @@ function emptyLayout() {
|
|
|
591
402
|
};
|
|
592
403
|
}
|
|
593
404
|
|
|
594
|
-
// temp/src/opacity.ts
|
|
595
|
-
var markOpacity = (node) => node.lightness === null ? 1 : 0.3 + 0.7 * node.lightness;
|
|
596
|
-
function ridgeOpacity(itemCount, style = {}) {
|
|
597
|
-
const { show = "auto", fadeFrom = 60, fadeTo = 160, opacity = 0.22 } = style;
|
|
598
|
-
if (show === "never")
|
|
599
|
-
return 0;
|
|
600
|
-
if (show === "always" || itemCount <= fadeFrom)
|
|
601
|
-
return opacity;
|
|
602
|
-
if (itemCount >= fadeTo)
|
|
603
|
-
return 0;
|
|
604
|
-
return opacity * (1 - (itemCount - fadeFrom) / (fadeTo - fadeFrom));
|
|
605
|
-
}
|
|
606
|
-
|
|
607
405
|
// temp/src/callouts.ts
|
|
608
406
|
var CALLOUT_GUTTER = 190;
|
|
609
407
|
var ANCHOR_CLEARANCE = 28;
|
|
@@ -638,32 +436,12 @@ function placeCallouts(layout, annotations) {
|
|
|
638
436
|
}
|
|
639
437
|
|
|
640
438
|
// temp/src/describe.ts
|
|
641
|
-
|
|
642
|
-
color: "colour",
|
|
643
|
-
shape: "shape",
|
|
644
|
-
lightness: "lightness"
|
|
645
|
-
};
|
|
439
|
+
import { describeProfile } from "@data-profile/core";
|
|
646
440
|
function describeLayout(layout, unit = "items") {
|
|
647
|
-
|
|
648
|
-
return `No ${unit} yet.`;
|
|
649
|
-
const arrange = layout.legends.find((group) => group.dimensionId === layout.encoding.arrange);
|
|
650
|
-
const ranked = arrange.entries.filter((entry) => entry.count > 0).sort((a, b) => b.count - a.count);
|
|
651
|
-
const named = ranked.slice(0, 3).map((entry) => `${entry.label} ${entry.count.toLocaleString("en-GB")}`);
|
|
652
|
-
const rest = ranked.length - named.length;
|
|
653
|
-
const parts = [
|
|
654
|
-
`${layout.itemCount.toLocaleString("en-GB")} ${unit} by ${arrange.label.toLowerCase()}: ${named.join(", ")}${rest > 0 ? `, and ${rest} more ${rest === 1 ? "category" : "categories"}` : ""}.`
|
|
655
|
-
];
|
|
656
|
-
if (layout.legends.length > 1) {
|
|
657
|
-
const shown = layout.legends.flatMap((group) => group.channels.filter((channel) => channel !== "arrange").map((channel) => channel === "lightness" ? `lightness shows ${group.label.toLowerCase()} (fainter is earlier)` : `${PROPERTY[channel]} shows ${group.label.toLowerCase()}`));
|
|
658
|
-
if (shown.length)
|
|
659
|
-
parts.push(`${shown.join(", ").replace(/^./, (c) => c.toUpperCase())}.`);
|
|
660
|
-
}
|
|
661
|
-
if (layout.itemsPerMark > 1)
|
|
662
|
-
parts.push(`Each mark stands for up to ${layout.itemsPerMark} ${unit}.`);
|
|
663
|
-
return parts.join(" ");
|
|
441
|
+
return describeProfile(layout, unit);
|
|
664
442
|
}
|
|
665
443
|
|
|
666
|
-
export {
|
|
444
|
+
export { formFromItems, layoutFingerprint, emptyLayout, UNKNOWN_CATEGORY_ID, DataError, CALLOUT_GUTTER, CALLOUT_HEIGHT, placeCallouts, describeLayout };
|
|
667
445
|
|
|
668
|
-
//# debugId=
|
|
669
|
-
//# sourceMappingURL=chunk-
|
|
446
|
+
//# debugId=42AEEDAB3A19380564756E2164756E21
|
|
447
|
+
//# sourceMappingURL=chunk-y8jc1j2x.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../temp/src/form.ts", "../temp/src/layout.ts", "../temp/src/ridges.ts", "../temp/src/slots.ts", "../temp/src/callouts.ts", "../temp/src/describe.ts"],
|
|
4
|
+
"sourcesContent": [
|
|
5
|
+
"import { unitHash } from \"@data-profile/core\";\nimport type { FingerprintDimension, FingerprintItem, FingerprintForm } from \"./types\";\n\nconst clamp = (value: number, low: number, high: number) => Math.min(high, Math.max(low, value));\n/** Coarse steps, so a collection's fingerprint holds still as it grows by an item or two. */\nconst quantise = (value: number, step = 0.2) => Math.round(value / step) * step;\n\n/** A plausible shape from a seed alone: the default when the consumer gives none. */\nexport function formFromSeed(seed: string): FingerprintForm {\n const h = (key: string) => unitHash(`${seed}:${key}`);\n return {\n pattern: \"loop\",\n hand: h(\"hand\") < 0.5 ? -1 : 1,\n coreX: quantise(h(\"x\") * 2 - 1) * 0.5,\n coreY: quantise(h(\"y\") * 2 - 1) * 0.5,\n spread: quantise(h(\"spread\")),\n tilt: Math.round((h(\"tilt\") - 0.5) * 12),\n };\n}\n\nexport function resolveForm(seed: string, shape: Partial<FingerprintForm> = {}): FingerprintForm {\n const resolved = { ...formFromSeed(seed), ...shape };\n return {\n ...resolved,\n coreX: clamp(resolved.coreX, -1, 1),\n coreY: clamp(resolved.coreY, -1, 1),\n spread: clamp(resolved.spread, 0, 1),\n tilt: clamp(resolved.tilt, -10, 10),\n };\n}\n\n/** Mean position (0 to 1) of known values within a dimension's category order, or 0.5. */\nfunction lean(items: readonly FingerprintItem[], dimension: FingerprintDimension | undefined) {\n if (!dimension || dimension.categories.length < 2) return 0.5;\n const index = new Map(\n dimension.categories.map((c, i) => [c.id, i / (dimension.categories.length - 1)]),\n );\n let sum = 0;\n let n = 0;\n for (const item of items) {\n const at = index.get(item.values[dimension.id] ?? \"\");\n if (at !== undefined) {\n sum += at;\n n += 1;\n }\n }\n return n ? sum / n : 0.5;\n}\n\n/**\n * A shape that is the collection's own: derived only from the data, so the same collection always\n * draws the same fingerprint, and it changes slowly as the collection grows. It is a signature,\n * not a chart; say so beside it. The mapping, which a caption can state:\n *\n * - **Pattern.** An arch while there are fewer than 12 items; then a loop when one category of the\n * first dimension holds 40% or more of the items (a focused collection); otherwise a whorl.\n * - **Core across, and loop direction.** Where the second dimension's values sit, on average, in its\n * category order: earlier categories pull the core left, later ones right.\n * - **Core height.** The same for the third dimension.\n * - **Spread.** The share of all categories that have at least one item: broader reads looser.\n * - **Tilt.** From the seed only, so two equal collections still differ slightly.\n */\nexport function formFromItems(\n items: readonly FingerprintItem[],\n dimensions: readonly FingerprintDimension[],\n seed = \"fingerprint\",\n): FingerprintForm {\n const [first, second, third] = dimensions;\n let top = 0;\n if (first) {\n const counts = new Map<string, number>();\n for (const item of items) {\n const value = item.values[first.id];\n if (value != null) counts.set(value, (counts.get(value) ?? 0) + 1);\n }\n top = items.length ? Math.max(0, ...counts.values()) / items.length : 0;\n }\n const total = dimensions.reduce((sum, d) => sum + d.categories.length, 0);\n const used = dimensions.reduce(\n (sum, d) =>\n sum + d.categories.filter((c) => items.some((item) => item.values[d.id] === c.id)).length,\n 0,\n );\n const across = lean(items, second);\n return resolveForm(seed, {\n pattern: items.length < 12 ? \"arch\" : top >= 0.4 ? \"loop\" : \"whorl\",\n hand: across < 0.5 ? -1 : 1,\n coreX: quantise((across - 0.5) * 2),\n coreY: quantise((lean(items, third) - 0.5) * 2),\n spread: total ? quantise(used / total) : 0.5,\n });\n}\n",
|
|
6
|
+
"import { encodeProfile } from \"@data-profile/core\";\nimport { FRAME, ridges } from \"./ridges\";\nimport { resolveForm } from \"./form\";\nimport { polylineLength, slotsAlong } from \"./slots\";\nimport type {\n FingerprintForm,\n FingerprintLayout,\n FingerprintNode,\n LayoutInput,\n Point,\n} from \"./types\";\n\n/** The id of the category unknown values fall into. Reserved: a dimension may not use it. */\nexport { UNKNOWN_CATEGORY_ID } from \"@data-profile/core\";\n\n/**\n * Data the layout cannot draw truthfully: duplicate item ids, a weight that is not positive, a\n * reserved or duplicated category id, an encoding naming no dimension, or lightness on an unordered\n * dimension. `layoutFingerprint` throws it; `useFingerprint` returns it as `error`. Core's\n * `DataError`.\n */\nexport { DataError as FingerprintDataError } from \"@data-profile/core\";\n\n/**\n * 600 marks: the most SVG glides smoothly between views (measured in Chrome: 56-59 fps at 400,\n * 43-50 at 800, 16-27 at 1,500), and large enough that a mark stays legible at phone width.\n */\nconst DEFAULTS = { seed: \"fingerprint\", maxMarks: 600, minRidges: 6 } as const;\n/** A mark fills this share of the space between its neighbours. */\nconst MARK_FILL = 0.85;\n\n/** A usable count, or undefined for anything else (undefined, NaN, zero, negative). */\nconst positive = (value: number | undefined) =>\n value !== undefined && Number.isFinite(value) && value >= 1 ? Math.floor(value) : undefined;\n\n/**\n * Ridges depend only on the form and spacing, not on the active dimension, so switching lens\n * reuses them: the marks move, the ridges stay. A few recent results are kept; the layout stays a\n * pure function because equal inputs always gave equal ridges.\n */\nconst ridgeCache = new Map<string, Point[][]>();\nfunction cachedRidges(form: FingerprintForm, spacing: number): Point[][] {\n const key = `${JSON.stringify(form)}:${spacing}`;\n const hit = ridgeCache.get(key);\n if (hit) return hit;\n const traced = ridges(form, spacing);\n ridgeCache.set(key, traced);\n if (ridgeCache.size > 8) ridgeCache.delete(ridgeCache.keys().next().value!);\n return traced;\n}\n\n/**\n * Lays the items out along fingerprint ridges.\n *\n * `encoding` says which dimension arranges the marks and which drive colour, shape and lightness\n * (see `FingerprintEncoding`); left out, the first dimension does all but lightness. Items are\n * sorted by the arranging dimension's category order, then by each other encoded dimension's,\n * then by id, and laid along the ridges from the core outwards, so categories form runs. Mark\n * **area** is proportional to weight; positions only arrange.\n *\n * A pure function of its input: the same items, dimensions, encoding and options give the same\n * layout whatever order the items arrive in and whatever size it is drawn at. The ridges come from\n * `form` (see `formFromItems`). The items array is treated as immutable: work is cached against it.\n *\n * Above `maxMarks` items, consecutive items that agree on **every** encoded dimension share a mark\n * (`itemsPerMark` > 1), so a mark never misrepresents any item in it. Nothing is sampled away.\n */\nexport function layoutFingerprint(input: LayoutInput): FingerprintLayout {\n const { items, dimensions, encoding } = input;\n // Read field by field: spreading the input would let an explicit `undefined` (which callers pass\n // through when they have no value) replace a default, and an undefined cap means one mark per item.\n const seed = input.seed ?? DEFAULTS.seed;\n const maxMarks = positive(input.maxMarks) ?? DEFAULTS.maxMarks;\n const minRidges = positive(input.minRidges) ?? DEFAULTS.minRidges;\n const form = resolveForm(seed, input.form);\n const { marks, ...profile } = encodeProfile({ items, dimensions, encoding, maxMarks });\n\n // Ridges are spaced so that, laid end to end, they hold every mark about one spacing apart. The\n // first trace estimates their length from the frame's area; one retrace corrects the spacing from\n // the length actually traced, since ridge endings and the outline make that hard to predict.\n const count = Math.max(1, marks.length);\n const widest = FRAME.rx / minRidges;\n let spacing = Math.min(widest, Math.sqrt((Math.PI * FRAME.rx * FRAME.ry * 0.85) / count));\n let lines = cachedRidges(form, spacing);\n if (marks.length > 0) {\n const traced = lines.reduce((sum, line) => sum + polylineLength(line), 0);\n const corrected = Math.min(widest, Math.sqrt((traced * spacing) / count));\n if (Math.abs(corrected - spacing) / spacing > 0.03) {\n spacing = corrected;\n lines = cachedRidges(form, spacing);\n }\n }\n const { slots, pitch } = slotsAlong(lines, marks.length);\n const footprint = Math.min(pitch, spacing) * MARK_FILL;\n\n const heaviest = Math.max(0, ...marks.map((mark) => mark.weight));\n const nodes: FingerprintNode[] = marks.map((mark, i) => {\n const slot = slots[i]!;\n return {\n ...mark,\n x: slot.x,\n y: slot.y,\n rotation: slot.rotation,\n size: footprint * Math.sqrt(mark.weight / heaviest),\n };\n });\n\n return {\n width: FRAME.width,\n height: FRAME.height,\n ...profile,\n nodes,\n ridges: lines,\n form,\n ridgeSpacing: spacing,\n };\n}\n\n/** A layout with nothing in it: what `useFingerprint` shows while its data is invalid. */\nexport function emptyLayout(): FingerprintLayout {\n return {\n width: FRAME.width,\n height: FRAME.height,\n encoding: { arrange: \"\", color: null, shape: null, lightness: null },\n nodes: [],\n ridges: [],\n form: resolveForm(DEFAULTS.seed),\n ridgeSpacing: FRAME.rx / DEFAULTS.minRidges,\n legends: [],\n warnings: [],\n itemsPerMark: 1,\n itemCount: 0,\n };\n}\n",
|
|
7
|
+
"import type { FingerprintForm, Point } from \"./types\";\n\n/** The fixed frame every layout is drawn in: a portrait fingertip with a small margin. */\nexport const FRAME = { width: 400, height: 520, cx: 200, cy: 260, rx: 180, ry: 240 } as const;\n\nconst DEG = Math.PI / 180;\n\n/** Normalised radius of the fingertip outline through a point: 1 on the outline. */\nexport const outlineRadius = (x: number, y: number) =>\n Math.hypot((x - FRAME.cx) / FRAME.rx, (y - FRAME.cy) / FRAME.ry);\n\nconst smoothstep = (a: number, b: number, x: number) => {\n const t = Math.min(1, Math.max(0, (x - a) / (b - a)));\n return t * t * (3 - 2 * t);\n};\n\n/** Where the ridges curl (cores) and split three ways (deltas). */\nexport interface Singularities {\n centre: Point;\n cores: Point[];\n deltas: Point[];\n}\n\n/**\n * Places a shape's singular points. A loop has one core and one delta below it to one side; a\n * whorl two cores and a delta either side; an arch none.\n */\nexport function singularities(shape: FingerprintForm): Singularities {\n const { rx, ry, cx, cy } = FRAME;\n const centre: Point = [cx + shape.coreX * 0.12 * rx, cy - 0.1 * ry + shape.coreY * 0.1 * ry];\n const [x, y] = centre;\n if (shape.pattern === \"arch\") return { centre, cores: [], deltas: [] };\n if (shape.pattern === \"loop\")\n return {\n centre,\n cores: [centre],\n deltas: [\n [x + shape.hand * (0.4 + 0.25 * shape.spread) * rx, y + (0.38 + 0.12 * shape.spread) * ry],\n ],\n };\n const twist = 0.06 * rx;\n return {\n centre,\n cores: [\n [x - twist, y - shape.hand * 0.025 * ry],\n [x + twist, y + shape.hand * 0.025 * ry],\n ],\n deltas: [\n [x - (0.5 + 0.2 * shape.spread) * rx, y + 0.42 * ry],\n [x + (0.5 + 0.2 * shape.spread) * rx, y + 0.42 * ry],\n ],\n };\n}\n\n/**\n * Ridge direction at a point, in radians, modulo pi.\n *\n * Inside, the Sherlock-Monro model: half the sum of the angles to each core minus half the sum to\n * each delta, which is what makes ridges curl round a core and split at a delta. An arch has no\n * singular points, so its ridges follow a raised bump instead. Near the outline, and mostly over the\n * top, ridges bend to follow it, as they do round a fingertip.\n */\nexport function orientation(\n x: number,\n y: number,\n shape: FingerprintForm,\n s: Singularities,\n): number {\n const tilt = shape.tilt * DEG;\n let theta: number;\n if (shape.pattern === \"arch\") {\n const width = (0.35 + 0.25 * shape.spread) * FRAME.rx;\n const u = (x - s.centre[0]) / width;\n const height = 0.3 * FRAME.ry * Math.max(0, 1 - Math.abs(y - s.centre[1]) / FRAME.ry);\n theta = tilt + Math.atan(((2 * height * u) / width) * Math.exp(-u * u));\n } else {\n let sum = 0;\n for (const [px, py] of s.cores) sum += Math.atan2(y - py, x - px);\n for (const [px, py] of s.deltas) sum -= Math.atan2(y - py, x - px);\n theta = tilt + sum / 2;\n }\n // Blend towards the outline's tangent, as doubled angles so pi-periodic directions average.\n const r = outlineRadius(x, y);\n // Strongest over the top of the fingertip, easing to 40% below it: a step here kinks ridges.\n const over = smoothstep(FRAME.cy + 0.25 * FRAME.ry, FRAME.cy - 0.25 * FRAME.ry, y);\n const weight = smoothstep(0.68, 1, r) * (0.4 + 0.6 * over);\n if (weight === 0) return theta;\n const tangent = Math.atan2((x - FRAME.cx) / FRAME.rx ** 2, -(y - FRAME.cy) / FRAME.ry ** 2);\n const vx = (1 - weight) * Math.cos(2 * theta) + weight * Math.cos(2 * tangent);\n const vy = (1 - weight) * Math.sin(2 * theta) + weight * Math.sin(2 * tangent);\n return Math.atan2(vy, vx) / 2;\n}\n\n/** A spatial hash of every accepted ridge point, for the spacing checks. */\nclass PointGrid {\n private cells = new Map<number, { x: number; y: number; line: number; index: number }[]>();\n constructor(private size: number) {}\n private key = (gx: number, gy: number) => gx * 4096 + gy;\n /** Returns the cell key, so a rejected ridge can be taken out again cheaply. */\n add(x: number, y: number, line: number, index: number) {\n const k = this.key(Math.floor(x / this.size), Math.floor(y / this.size));\n const cell = this.cells.get(k);\n if (cell) cell.push({ x, y, line, index });\n else this.cells.set(k, [{ x, y, line, index }]);\n return k;\n }\n removeLine(line: number, keys: Iterable<number>) {\n for (const k of new Set(keys)) {\n const kept = (this.cells.get(k) ?? []).filter((p) => p.line !== line);\n if (kept.length) this.cells.set(k, kept);\n else this.cells.delete(k);\n }\n }\n /** True when a point closer than `distance` passes `counts`. `distance` must not exceed the cell size. */\n near(\n x: number,\n y: number,\n distance: number,\n counts: (p: { line: number; index: number }) => boolean,\n ) {\n const gx = Math.floor(x / this.size);\n const gy = Math.floor(y / this.size);\n for (let dx = -1; dx <= 1; dx++)\n for (let dy = -1; dy <= 1; dy++)\n for (const p of this.cells.get(this.key(gx + dx, gy + dy)) ?? [])\n if ((p.x - x) ** 2 + (p.y - y) ** 2 < distance ** 2 && counts(p)) return true;\n return false;\n }\n}\n\n/** Ridges stop this far inside the outline. */\nconst OUTLINE_LIMIT = 0.985;\n\n/**\n * Evenly spaced ridges along the shape's flow field (Jobard and Lefer's streamline placement),\n * core outwards. Each ridge is traced both ways from a seed until it would come within `spacing`\n * of another ridge, of a singular point, of a distant part of itself, or of the outline; new seeds\n * are tried one spacing either side of every accepted ridge. A dropped-out ridge end is a ridge\n * ending, exactly as in a real print.\n *\n * Every point of every ridge is at least about `spacing` from every other ridge, which is what lets\n * marks of that size sit on them without touching.\n */\nexport function ridges(shape: FingerprintForm, spacing: number): Point[][] {\n const s = singularities(shape);\n const step = spacing * 0.3;\n const selfGap = Math.ceil((3 * spacing) / step);\n const minLength = Math.ceil((2 * spacing) / step);\n const maxSteps = Math.ceil((4 * Math.PI * (FRAME.rx + FRAME.ry)) / step);\n const grid = new PointGrid(spacing);\n const lines: Point[][] = [];\n const singular = [...s.cores, ...s.deltas];\n\n const free = (x: number, y: number, line: number, index: number) =>\n outlineRadius(x, y) <= OUTLINE_LIMIT &&\n singular.every(([px, py]) => Math.hypot(x - px, y - py) > spacing * 0.6) &&\n !grid.near(x, y, spacing, (p) => p.line !== line || Math.abs(p.index - index) > selfGap);\n\n const direction = (x: number, y: number, previous: Point): Point => {\n const theta = orientation(x, y, shape, s);\n const v: Point = [Math.cos(theta), Math.sin(theta)];\n return v[0] * previous[0] + v[1] * previous[1] < 0 ? [-v[0], -v[1]] : v;\n };\n\n const trace = (seed: Point): Point[] | null => {\n const line = lines.length;\n const theta = orientation(seed[0], seed[1], shape, s);\n const keys = [grid.add(seed[0], seed[1], line, 0)];\n const halves = ([1, -1] as const).map((sign) => {\n const points: Point[] = [];\n let p = seed;\n let previous: Point = [sign * Math.cos(theta), sign * Math.sin(theta)];\n for (let i = 1; i <= maxSteps; i++) {\n // Second-order Runge-Kutta: sample the direction half a step ahead.\n const v1 = direction(p[0], p[1], previous);\n const v2 = direction(p[0] + (v1[0] * step) / 2, p[1] + (v1[1] * step) / 2, v1);\n // A ridge turns gently; a sharp turn in one step means the field folds here, so end it.\n if (v2[0] * previous[0] + v2[1] * previous[1] < 0.8) break;\n const q: Point = [p[0] + v2[0] * step, p[1] + v2[1] * step];\n if (!free(q[0], q[1], line, sign * i)) break;\n keys.push(grid.add(q[0], q[1], line, sign * i));\n points.push(q);\n p = q;\n previous = v2;\n }\n return points;\n });\n const points = [...halves[1]!.reverse(), seed, ...halves[0]!];\n if (points.length < minLength) {\n grid.removeLine(line, keys);\n return null;\n }\n return points;\n };\n\n const queue: Point[] = [];\n const seedFrom = (points: Point[]) => {\n const every = Math.max(1, Math.round(spacing / step / 2));\n for (let i = 1; i < points.length - 1; i += every) {\n const [ax, ay] = points[i - 1]!;\n const [bx, by] = points[i + 1]!;\n const length = Math.hypot(bx - ax, by - ay) || 1;\n const [nx, ny] = [-(by - ay) / length, (bx - ax) / length];\n const [x, y] = points[i]!;\n queue.push([x + nx * spacing, y + ny * spacing], [x - nx * spacing, y - ny * spacing]);\n }\n };\n const attempt = (seed: Point) => {\n if (!free(seed[0], seed[1], -1, 0)) return;\n const points = trace(seed);\n if (!points) return;\n lines.push(points);\n seedFrom(points);\n };\n let next = 0;\n const drain = () => {\n while (next < queue.length) attempt(queue[next++]!);\n };\n\n // Start just above the core and grow outwards; then sweep for any region the growth missed.\n queue.push([s.centre[0], s.centre[1] - spacing]);\n drain();\n const sweep: Point[] = [];\n for (let x = FRAME.cx - FRAME.rx; x <= FRAME.cx + FRAME.rx; x += spacing / 2)\n for (let y = FRAME.cy - FRAME.ry; y <= FRAME.cy + FRAME.ry; y += spacing / 2)\n if (outlineRadius(x, y) <= OUTLINE_LIMIT) sweep.push([x, y]);\n const distance = ([x, y]: Point) => Math.hypot(x - s.centre[0], y - s.centre[1]);\n sweep.sort((a, b) => distance(a) - distance(b) || a[0] - b[0] || a[1] - b[1]);\n for (const seed of sweep) {\n attempt(seed);\n drain();\n }\n\n return orderFromCore(lines, s.centre);\n}\n\n/**\n * Ridges sorted by how close they come to the core, each running clockwise round it, so marks laid\n * along them in turn form runs from the core outwards.\n */\nfunction orderFromCore(lines: Point[][], [cx, cy]: Point): Point[][] {\n const reach = (line: Point[]) => Math.min(...line.map(([x, y]) => Math.hypot(x - cx, y - cy)));\n const swept = (line: Point[]) => {\n let total = 0;\n for (let i = 1; i < line.length; i++) {\n let d =\n Math.atan2(line[i]![1] - cy, line[i]![0] - cx) -\n Math.atan2(line[i - 1]![1] - cy, line[i - 1]![0] - cx);\n if (d > Math.PI) d -= 2 * Math.PI;\n if (d < -Math.PI) d += 2 * Math.PI;\n total += d;\n }\n return total;\n };\n return lines\n .map((line) => ({ line: swept(line) < 0 ? [...line].reverse() : line, reach: reach(line) }))\n .sort(\n (a, b) => a.reach - b.reach || a.line[0]![0] - b.line[0]![0] || a.line[0]![1] - b.line[0]![1],\n )\n .map(({ line }) => line);\n}\n",
|
|
8
|
+
"import type { Point } from \"./types\";\n\nexport function polylineLength(points: readonly Point[]): number {\n let length = 0;\n for (let i = 1; i < points.length; i++)\n length += Math.hypot(points[i]![0] - points[i - 1]![0], points[i]![1] - points[i - 1]![1]);\n return length;\n}\n\n/**\n * Splits `count` across ridges so the most crowded ridge is as uncrowded as possible: every ridge\n * first gets as many slots as fit at the average spacing, then each remaining slot goes to the\n * ridge whose spacing it shrinks least (the D'Hondt rule). Ties go to the earlier ridge, so the\n * result is stable.\n */\nfunction allocate(lengths: readonly number[], count: number): number[] {\n const total = lengths.reduce((sum, length) => sum + length, 0);\n if (total === 0 || count === 0) return lengths.map(() => 0);\n const average = total / count;\n const counts = lengths.map((length) => Math.floor(length / average));\n for (let left = count - counts.reduce((sum, n) => sum + n, 0); left > 0; left--) {\n let best = 0;\n for (let i = 1; i < lengths.length; i++)\n if (lengths[i]! / (counts[i]! + 1) > lengths[best]! / (counts[best]! + 1)) best = i;\n counts[best]! += 1;\n }\n return counts;\n}\n\n/** The point and direction (degrees) at arc length `at` along a polyline. */\nfunction pointAt(line: readonly Point[], at: number) {\n let walked = 0;\n for (let i = 1; i < line.length; i++) {\n const [x0, y0] = line[i - 1]!;\n const [x1, y1] = line[i]!;\n const segment = Math.hypot(x1 - x0, y1 - y0);\n if (at <= walked + segment || i === line.length - 1) {\n const f = segment === 0 ? 0 : Math.min(1, (at - walked) / segment);\n return {\n x: x0 + (x1 - x0) * f,\n y: y0 + (y1 - y0) * f,\n rotation: (Math.atan2(y1 - y0, x1 - x0) * 180) / Math.PI,\n };\n }\n walked += segment;\n }\n const [x, y] = line[0]!;\n return { x, y, rotation: 0 };\n}\n\n/**\n * `count` positions along the ridges, in ridge order. Each ridge gets slots by `allocate` and spaces them evenly over its whole length, so a closed ring has no crowded seam where\n * its end meets its start. `pitch` is the smallest spacing between\n * neighbours on any ridge.\n */\nexport function slotsAlong(lines: readonly Point[][], count: number) {\n const lengths = lines.map(polylineLength);\n const counts = allocate(lengths, count);\n const slots: { x: number; y: number; rotation: number }[] = [];\n let pitch = Infinity;\n lines.forEach((line, k) => {\n const n = counts[k]!;\n if (n === 0) return;\n const step = lengths[k]! / n;\n // A ridge with one slot has no neighbour along it, so its length says nothing about crowding.\n if (n > 1) pitch = Math.min(pitch, step);\n for (let i = 0; i < n; i++) slots.push(pointAt(line, (i + 0.5) * step));\n });\n // Infinity when no ridge holds two slots: nothing along a ridge then limits mark size.\n return { slots, pitch };\n}\n",
|
|
9
|
+
"import type { FingerprintAnnotation, FingerprintLayout } from \"./types\";\n\n/** Horizontal room either side of the print for callout labels, in layout units. */\nexport const CALLOUT_GUTTER = 190;\n/** How far apart two callouts' anchors stay: a numbered marker is 22 units across. */\nconst ANCHOR_CLEARANCE = 28;\n/** Vertical room one callout label takes, in layout units. */\nexport const CALLOUT_HEIGHT = 96;\n\n/** Where `placeCallouts` puts one annotation, in the layout's frame. */\nexport interface PlacedCallout {\n /** The annotation placed. */\n annotation: FingerprintAnnotation;\n /** The mark the leader points at: the named categories' mark nearest their centre. */\n anchor: { x: number; y: number };\n /** Which gutter the label sits in: the side of the print its anchor is on. */\n side: \"left\" | \"right\";\n /** Top of the label, in layout units. */\n labelY: number;\n /** 1-based, in reading order: left side top to bottom, then right. */\n number: number;\n}\n\n/**\n * Where each callout goes. Its anchor is a real mark of the categories it names, so the leader\n * never points at empty paper; the label sits in the gutter on the anchor's side, as level with it\n * as the other labels allow. Annotations whose categories have no marks are left out.\n */\nexport function placeCallouts(\n layout: FingerprintLayout,\n annotations: readonly FingerprintAnnotation[],\n): PlacedCallout[] {\n // Anchors already taken: a later callout's anchor keeps this far from them, so numbered markers\n // never hide each other.\n const taken: { x: number; y: number }[] = [];\n const anchored = annotations.flatMap((annotation) => {\n const wanted = new Set(annotation.categoryIds);\n const dimensionId = annotation.dimensionId ?? layout.encoding.arrange;\n const marks = layout.nodes.filter((node) => wanted.has(node.categories[dimensionId] ?? \"\"));\n if (marks.length === 0) return [];\n const cx = marks.reduce((sum, node) => sum + node.x, 0) / marks.length;\n const cy = marks.reduce((sum, node) => sum + node.y, 0) / marks.length;\n const byCloseness = [...marks].sort(\n (a, b) => Math.hypot(a.x - cx, a.y - cy) - Math.hypot(b.x - cx, b.y - cy),\n );\n const clear = (node: { x: number; y: number }) =>\n taken.every((t) => Math.hypot(node.x - t.x, node.y - t.y) >= ANCHOR_CLEARANCE);\n const anchor = byCloseness.find(clear) ?? byCloseness[0]!;\n taken.push(anchor);\n const side: PlacedCallout[\"side\"] = anchor.x < layout.width / 2 ? \"left\" : \"right\";\n return [{ annotation, anchor: { x: anchor.x, y: anchor.y }, side }];\n });\n\n const placed: PlacedCallout[] = [];\n for (const side of [\"left\", \"right\"] as const) {\n const column = anchored.filter((c) => c.side === side).sort((a, b) => a.anchor.y - b.anchor.y);\n // Level with the anchor where possible, pushed down to clear the label above, then the whole\n // column pulled back up if the last label would run off the bottom.\n const tops: number[] = [];\n for (const callout of column)\n tops.push(\n Math.max(\n callout.anchor.y - CALLOUT_HEIGHT / 3,\n (tops.at(-1) ?? -Infinity) + CALLOUT_HEIGHT,\n ),\n );\n const overflow = Math.max(0, (tops.at(-1) ?? 0) + CALLOUT_HEIGHT - layout.height);\n column.forEach((callout, i) =>\n placed.push({ ...callout, labelY: Math.max(0, tops[i]! - overflow), number: 0 }),\n );\n }\n return placed.map((callout, i) => ({ ...callout, number: i + 1 }));\n}\n",
|
|
10
|
+
"import { describeProfile } from \"@data-profile/core\";\nimport type { FingerprintLayout } from \"./types\";\n\n/**\n * A plain-language summary of what a layout shows, for a screen reader or a caption: the total, the\n * largest categories of the arranging dimension with their counts, which dimension each visual\n * property shows when they differ, and how many items each mark stands for. Every number in it is\n * one the legends also show. Core's `describeProfile`.\n */\nexport function describeLayout(layout: FingerprintLayout, unit = \"items\"): string {\n return describeProfile(layout, unit);\n}\n"
|
|
11
|
+
],
|
|
12
|
+
"mappings": "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",
|
|
13
|
+
"debugId": "42AEEDAB3A19380564756E2164756E21",
|
|
14
|
+
"names": []
|
|
15
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,23 +1,34 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `@data-profile/fingerprint`: the layout, as plain data, with no React. Lay items out
|
|
3
3
|
* with `layoutFingerprint`, derive a form with `formFromItems`, describe it with `describeLayout`.
|
|
4
|
+
* The data types and encoding come from `@data-profile/core`.
|
|
4
5
|
*
|
|
5
6
|
* @packageDocumentation
|
|
6
|
-
* @module
|
|
7
|
+
* @module fingerprint
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
10
|
+
import type { Category } from '@data-profile/core';
|
|
11
|
+
import type { Channel } from '@data-profile/core';
|
|
12
|
+
import { DEFAULT_PALETTE } from '@data-profile/core';
|
|
13
|
+
import type { Dimension } from '@data-profile/core';
|
|
14
|
+
import type { Encoding } from '@data-profile/core';
|
|
15
|
+
import { DataError as FingerprintDataError } from '@data-profile/core';
|
|
16
|
+
import type { Item } from '@data-profile/core';
|
|
17
|
+
import type { LegendEntry } from '@data-profile/core';
|
|
18
|
+
import type { LegendGroup } from '@data-profile/core';
|
|
19
|
+
import type { Mark } from '@data-profile/core';
|
|
20
|
+
import { markOpacity } from '@data-profile/core';
|
|
21
|
+
import type { Profile } from '@data-profile/core';
|
|
22
|
+
import { UNKNOWN_CATEGORY_ID } from '@data-profile/core';
|
|
23
|
+
import { UNKNOWN_COLOR } from '@data-profile/core';
|
|
24
|
+
|
|
25
|
+
export { DEFAULT_PALETTE }
|
|
15
26
|
|
|
16
27
|
/**
|
|
17
28
|
* A plain-language summary of what a layout shows, for a screen reader or a caption: the total, the
|
|
18
29
|
* largest categories of the arranging dimension with their counts, which dimension each visual
|
|
19
30
|
* property shows when they differ, and how many items each mark stands for. Every number in it is
|
|
20
|
-
* one the legends also show.
|
|
31
|
+
* one the legends also show. Core's `describeProfile`.
|
|
21
32
|
*/
|
|
22
33
|
export declare function describeLayout(layout: FingerprintLayout, unit?: string): string;
|
|
23
34
|
|
|
@@ -38,62 +49,22 @@ export declare interface FingerprintAnnotation {
|
|
|
38
49
|
body?: string;
|
|
39
50
|
}
|
|
40
51
|
|
|
41
|
-
/** One category of a dimension. */
|
|
42
|
-
export declare
|
|
43
|
-
/** Unique within its dimension. `"~unknown"` is reserved. */
|
|
44
|
-
id: string;
|
|
45
|
-
/** Shown in legends, lists and descriptions. */
|
|
46
|
-
label: string;
|
|
47
|
-
/** Any CSS colour, custom properties included. Defaults to the package palette, by position. */
|
|
48
|
-
color?: string;
|
|
49
|
-
/** A link for this category, shown in `FingerprintList`. */
|
|
50
|
-
href?: string;
|
|
51
|
-
}
|
|
52
|
+
/** One category of a dimension. Core's `Category`. */
|
|
53
|
+
export declare type FingerprintCategory = Category;
|
|
52
54
|
|
|
53
|
-
/** The visual properties a dimension can drive. `arrange` orders marks along the ridges. */
|
|
54
|
-
export declare type FingerprintChannel =
|
|
55
|
+
/** The visual properties a dimension can drive. `arrange` orders marks along the ridges. Core's `Channel`. */
|
|
56
|
+
export declare type FingerprintChannel = Channel;
|
|
55
57
|
|
|
56
|
-
|
|
57
|
-
* Data the layout cannot draw truthfully: duplicate item ids, a weight that is not positive, a
|
|
58
|
-
* reserved or duplicated category id, an encoding naming no dimension, or lightness on an unordered
|
|
59
|
-
* dimension. `layoutFingerprint` throws it; `useFingerprint` returns it as `error`.
|
|
60
|
-
*/
|
|
61
|
-
export declare class FingerprintDataError extends Error {
|
|
62
|
-
name: string;
|
|
63
|
-
}
|
|
58
|
+
export { FingerprintDataError }
|
|
64
59
|
|
|
65
|
-
/** One way of grouping the items. Category order is the order marks are laid out in. */
|
|
66
|
-
export declare
|
|
67
|
-
/** Unique; the key items use in `values`. */
|
|
68
|
-
id: string;
|
|
69
|
-
/** Shown as the legend's heading and in descriptions. */
|
|
70
|
-
label: string;
|
|
71
|
-
/** In the order they should be drawn and listed. */
|
|
72
|
-
categories: readonly FingerprintCategory[];
|
|
73
|
-
/** Label of the category unknown values fall into. Defaults to "Unknown". */
|
|
74
|
-
unknownLabel?: string;
|
|
75
|
-
/**
|
|
76
|
-
* True when the categories run in a meaningful order (decades, sizes). Only an ordered dimension
|
|
77
|
-
* may drive lightness: on unordered categories, fainter and stronger would read as less and more.
|
|
78
|
-
*/
|
|
79
|
-
ordered?: boolean;
|
|
80
|
-
}
|
|
60
|
+
/** One way of grouping the items. Category order is the order marks are laid out in. Core's `Dimension`. */
|
|
61
|
+
export declare type FingerprintDimension = Dimension;
|
|
81
62
|
|
|
82
63
|
/**
|
|
83
|
-
* Which dimension drives which visual property, by dimension id. `
|
|
84
|
-
*
|
|
85
|
-
* property off. Size is never available: mark area is reserved for weight.
|
|
64
|
+
* Which dimension drives which visual property, by dimension id. `arrange` orders marks along the
|
|
65
|
+
* ridges. Core's `Encoding`.
|
|
86
66
|
*/
|
|
87
|
-
export declare
|
|
88
|
-
/** Orders marks along the ridges, so its categories form runs. */
|
|
89
|
-
arrange: string;
|
|
90
|
-
/** Colours each mark by this dimension's categories. Defaults to `arrange`. */
|
|
91
|
-
color?: string | null;
|
|
92
|
-
/** Gives each category of this dimension its own mark shape. Defaults to `arrange`. */
|
|
93
|
-
shape?: string | null;
|
|
94
|
-
/** Fades marks along this ordered dimension, first category faintest. Off by default. */
|
|
95
|
-
lightness?: string | null;
|
|
96
|
-
}
|
|
67
|
+
export declare type FingerprintEncoding = Encoding;
|
|
97
68
|
|
|
98
69
|
/**
|
|
99
70
|
* The print's form: its pattern and where its singular points sit. Every number is a small,
|
|
@@ -116,31 +87,21 @@ export declare interface FingerprintForm {
|
|
|
116
87
|
}
|
|
117
88
|
|
|
118
89
|
/**
|
|
119
|
-
* The fingerprint's data contract.
|
|
120
|
-
*
|
|
90
|
+
* The fingerprint's data contract. The data types are `@data-profile/core`'s, under the names this
|
|
91
|
+
* package has always used; the rest is the fingerprint's own geometry.
|
|
121
92
|
*/
|
|
122
|
-
/** One thing in the collection, drawn as one mark (below the mark cap). */
|
|
123
|
-
export declare
|
|
124
|
-
/** Stable and unique. Marks keep this identity when the view changes. */
|
|
125
|
-
id: string;
|
|
126
|
-
/** Mark area is proportional to weight. Defaults to 1; must be positive. */
|
|
127
|
-
weight?: number;
|
|
128
|
-
/**
|
|
129
|
-
* The item's category in each dimension, keyed by dimension id. `null`, `undefined`, a missing
|
|
130
|
-
* key or a category id the dimension does not list all place the item in that dimension's
|
|
131
|
-
* unknown category: unknowns are drawn, never dropped.
|
|
132
|
-
*/
|
|
133
|
-
values: Readonly<Record<string, string | null | undefined>>;
|
|
134
|
-
}
|
|
93
|
+
/** One thing in the collection, drawn as one mark (below the mark cap). Core's `Item`. */
|
|
94
|
+
export declare type FingerprintItem = Item;
|
|
135
95
|
|
|
136
|
-
/**
|
|
137
|
-
|
|
96
|
+
/**
|
|
97
|
+
* What `layoutFingerprint` returns: everything a renderer needs, in a fixed frame. Core's `Profile`
|
|
98
|
+
* with its marks placed as `nodes`, and the ridges they sit on.
|
|
99
|
+
*/
|
|
100
|
+
export declare interface FingerprintLayout extends Omit<Profile, "marks"> {
|
|
138
101
|
/** The fixed coordinate frame's width; resizing scales it, never changes it. */
|
|
139
102
|
width: number;
|
|
140
103
|
/** The fixed coordinate frame's height. */
|
|
141
104
|
height: number;
|
|
142
|
-
/** Every property resolved to a dimension id, or null when off. */
|
|
143
|
-
encoding: Required<FingerprintEncoding>;
|
|
144
105
|
/** The marks, laid along the ridges from the core outwards. */
|
|
145
106
|
nodes: FingerprintNode[];
|
|
146
107
|
/** The ridges marks sit on, core outwards, as polylines of `[x, y]` points, for renderers that draw them. */
|
|
@@ -149,61 +110,16 @@ export declare interface FingerprintLayout {
|
|
|
149
110
|
form: FingerprintForm;
|
|
150
111
|
/** Centre-to-centre distance between neighbouring ridges. */
|
|
151
112
|
ridgeSpacing: number;
|
|
152
|
-
/** One group per encoded dimension: colour first, then shape, lightness and arrangement. */
|
|
153
|
-
legends: FingerprintLegendGroup[];
|
|
154
|
-
/** Encodings that will be hard to read, such as more colours than stay distinguishable. */
|
|
155
|
-
warnings: string[];
|
|
156
|
-
/** 1 unless the item count exceeded `maxMarks`. Say so on screen when it is not 1. */
|
|
157
|
-
itemsPerMark: number;
|
|
158
|
-
/** All the items, whatever `itemsPerMark`. */
|
|
159
|
-
itemCount: number;
|
|
160
113
|
}
|
|
161
114
|
|
|
162
|
-
/** One category in a legend, with how its marks are drawn and how many items it holds. */
|
|
163
|
-
export declare
|
|
164
|
-
/** The category id, or `UNKNOWN_CATEGORY_ID`. */
|
|
165
|
-
id: string;
|
|
166
|
-
/** The category label, or the dimension's `unknownLabel`. */
|
|
167
|
-
label: string;
|
|
168
|
-
/** The colour a mark of this category gets, or `currentColor` when this dimension drives no colour. */
|
|
169
|
-
color: string;
|
|
170
|
-
/** As `FingerprintNode.shape`, for this category; `0` when this dimension drives no shape. */
|
|
171
|
-
shape: number | null;
|
|
172
|
-
/** As `FingerprintNode.lightness`, for this category; null when this dimension drives no lightness. */
|
|
173
|
-
lightness: number | null;
|
|
174
|
-
/** The category's link, if it has one. */
|
|
175
|
-
href?: string;
|
|
176
|
-
/** True for the unknown category. */
|
|
177
|
-
unknown: boolean;
|
|
178
|
-
/** Items in this category, whatever their weight. */
|
|
179
|
-
count: number;
|
|
180
|
-
/** The summed weight of those items. */
|
|
181
|
-
weight: number;
|
|
182
|
-
}
|
|
115
|
+
/** One category in a legend, with how its marks are drawn and how many items it holds. Core's `LegendEntry`. */
|
|
116
|
+
export declare type FingerprintLegendEntry = LegendEntry;
|
|
183
117
|
|
|
184
|
-
/** One dimension's legend, and which visual properties that dimension drives. */
|
|
185
|
-
export declare
|
|
186
|
-
/** The dimension's id. */
|
|
187
|
-
dimensionId: string;
|
|
188
|
-
/** The dimension's label. */
|
|
189
|
-
label: string;
|
|
190
|
-
/** What this dimension drives in the current encoding. */
|
|
191
|
-
channels: readonly FingerprintChannel[];
|
|
192
|
-
/** In category order, the unknown category last and only when it has items. */
|
|
193
|
-
entries: FingerprintLegendEntry[];
|
|
194
|
-
}
|
|
118
|
+
/** One dimension's legend, and which visual properties that dimension drives. Core's `LegendGroup`. */
|
|
119
|
+
export declare type FingerprintLegendGroup = LegendGroup;
|
|
195
120
|
|
|
196
|
-
/** A laid-out mark
|
|
197
|
-
export declare interface FingerprintNode {
|
|
198
|
-
/** The item id, or a combination key and index when the mark stands for several items. */
|
|
199
|
-
id: string;
|
|
200
|
-
/** The items this mark stands for: one, unless the collection exceeded `maxMarks`. */
|
|
201
|
-
itemIds: readonly string[];
|
|
202
|
-
/**
|
|
203
|
-
* The mark's category in every encoded dimension, keyed by dimension id; an unknown value is
|
|
204
|
-
* `UNKNOWN_CATEGORY_ID`. The arranging category is `categories[layout.encoding.arrange]`.
|
|
205
|
-
*/
|
|
206
|
-
categories: Readonly<Record<string, string>>;
|
|
121
|
+
/** A laid-out mark: core's `Mark`, placed in the layout's fixed coordinate frame. */
|
|
122
|
+
export declare interface FingerprintNode extends Mark {
|
|
207
123
|
/** Centre, in the layout's frame. */
|
|
208
124
|
x: number;
|
|
209
125
|
/** Centre, in the layout's frame. */
|
|
@@ -212,18 +128,6 @@ export declare interface FingerprintNode {
|
|
|
212
128
|
rotation: number;
|
|
213
129
|
/** Diameter of the mark's footprint. Area is proportional to weight. */
|
|
214
130
|
size: number;
|
|
215
|
-
/** The summed weight of `itemIds`. */
|
|
216
|
-
weight: number;
|
|
217
|
-
/** From the colour dimension; `currentColor` when nothing drives colour. */
|
|
218
|
-
color: string;
|
|
219
|
-
/**
|
|
220
|
-
* The mark's shape: the index of its category in the shape dimension (0, 1, 2, …). `0` also when
|
|
221
|
-
* nothing drives shape, so a plain mark is the first shape; `null` when its shape category is
|
|
222
|
-
* unknown.
|
|
223
|
-
*/
|
|
224
|
-
shape: number | null;
|
|
225
|
-
/** 0 (faintest) to 1 (full strength) along an ordered dimension; null when unknown or not encoded. */
|
|
226
|
-
lightness: number | null;
|
|
227
131
|
}
|
|
228
132
|
|
|
229
133
|
/**
|
|
@@ -288,12 +192,7 @@ export declare interface LayoutOptions {
|
|
|
288
192
|
minRidges?: number;
|
|
289
193
|
}
|
|
290
194
|
|
|
291
|
-
|
|
292
|
-
* The opacity that shows a mark's lightness: 0.3 for the first category of an ordered dimension,
|
|
293
|
-
* full strength for the last, and full strength when lightness is not encoded or unknown. Renderers
|
|
294
|
-
* use it so lightness reads the same whatever they draw.
|
|
295
|
-
*/
|
|
296
|
-
export declare const markOpacity: (node: Pick<FingerprintNode, "lightness">) => number;
|
|
195
|
+
export { markOpacity }
|
|
297
196
|
|
|
298
197
|
/**
|
|
299
198
|
* Where each callout goes. Its anchor is a real mark of the categories it names, so the leader
|
|
@@ -341,10 +240,8 @@ export declare interface RidgeStyle {
|
|
|
341
240
|
width?: number;
|
|
342
241
|
}
|
|
343
242
|
|
|
344
|
-
|
|
345
|
-
export declare const UNKNOWN_CATEGORY_ID = "~unknown";
|
|
243
|
+
export { UNKNOWN_CATEGORY_ID }
|
|
346
244
|
|
|
347
|
-
|
|
348
|
-
export declare const UNKNOWN_COLOR = "#b8bcc4";
|
|
245
|
+
export { UNKNOWN_COLOR }
|
|
349
246
|
|
|
350
247
|
export { }
|
package/dist/index.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
|
-
|
|
3
|
-
FingerprintDataError,
|
|
2
|
+
DataError,
|
|
4
3
|
UNKNOWN_CATEGORY_ID,
|
|
5
|
-
UNKNOWN_COLOR,
|
|
6
4
|
describeLayout,
|
|
7
5
|
formFromItems,
|
|
8
6
|
layoutFingerprint,
|
|
9
|
-
markOpacity,
|
|
10
7
|
placeCallouts
|
|
11
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-y8jc1j2x.js";
|
|
9
|
+
|
|
10
|
+
// temp/src/index.ts
|
|
11
|
+
import { DEFAULT_PALETTE, UNKNOWN_COLOR, markOpacity } from "@data-profile/core";
|
|
12
12
|
export {
|
|
13
13
|
placeCallouts,
|
|
14
14
|
markOpacity,
|
|
@@ -17,9 +17,9 @@ export {
|
|
|
17
17
|
describeLayout,
|
|
18
18
|
UNKNOWN_COLOR,
|
|
19
19
|
UNKNOWN_CATEGORY_ID,
|
|
20
|
-
FingerprintDataError,
|
|
20
|
+
DataError as FingerprintDataError,
|
|
21
21
|
DEFAULT_PALETTE
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
-
//# debugId=
|
|
24
|
+
//# debugId=044A7D70804C965764756E2164756E21
|
|
25
25
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": [],
|
|
3
|
+
"sources": ["../temp/src/index.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
|
+
"/**\n * `@data-profile/fingerprint`: the layout, as plain data, with no React. Lay items out\n * with `layoutFingerprint`, derive a form with `formFromItems`, describe it with `describeLayout`.\n * The data types and encoding come from `@data-profile/core`.\n *\n * @packageDocumentation\n * @module fingerprint\n */\nexport { layoutFingerprint, FingerprintDataError, UNKNOWN_CATEGORY_ID } from \"./layout\";\nexport { DEFAULT_PALETTE, UNKNOWN_COLOR, markOpacity } from \"@data-profile/core\";\nexport { formFromItems } from \"./form\";\nexport { placeCallouts, type PlacedCallout } from \"./callouts\";\nexport { describeLayout } from \"./describe\";\nexport type {\n FingerprintAnnotation,\n FingerprintCategory,\n FingerprintChannel,\n FingerprintDimension,\n FingerprintEncoding,\n FingerprintForm,\n FingerprintItem,\n FingerprintLayout,\n FingerprintLegendEntry,\n FingerprintLegendGroup,\n FingerprintNode,\n LayoutInput,\n LayoutOptions,\n RidgePattern,\n RidgeStyle,\n} from \"./types\";\n"
|
|
5
6
|
],
|
|
6
|
-
"mappings": "",
|
|
7
|
-
"debugId": "
|
|
7
|
+
"mappings": ";;;;;;;;;;AASA;",
|
|
8
|
+
"debugId": "044A7D70804C965764756E2164756E21",
|
|
8
9
|
"names": []
|
|
9
10
|
}
|
package/dist/react/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `useFingerprint` hook that wires them together, and the built-in renderers.
|
|
4
4
|
*
|
|
5
5
|
* @packageDocumentation
|
|
6
|
-
* @module react
|
|
6
|
+
* @module fingerprint/react
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { FingerprintAnnotation } from '@data-profile/fingerprint';
|
|
@@ -16,7 +16,7 @@ import type { FingerprintLayout } from '@data-profile/fingerprint';
|
|
|
16
16
|
import type { FingerprintLegendGroup } from '@data-profile/fingerprint';
|
|
17
17
|
import type { FingerprintNode } from '@data-profile/fingerprint';
|
|
18
18
|
import { JSX } from 'react';
|
|
19
|
-
import { markOpacity } from '@data-profile/
|
|
19
|
+
import { markOpacity } from '@data-profile/core';
|
|
20
20
|
import type { ReactNode } from 'react';
|
|
21
21
|
import type { RidgeStyle } from '@data-profile/fingerprint';
|
|
22
22
|
|
package/dist/react/index.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
CALLOUT_GUTTER,
|
|
3
3
|
CALLOUT_HEIGHT,
|
|
4
|
-
|
|
4
|
+
DataError,
|
|
5
5
|
describeLayout,
|
|
6
6
|
emptyLayout,
|
|
7
7
|
formFromItems,
|
|
8
8
|
layoutFingerprint,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
ridgeOpacity
|
|
12
|
-
} from "../chunk-eygy4k4w.js";
|
|
9
|
+
placeCallouts
|
|
10
|
+
} from "../chunk-y8jc1j2x.js";
|
|
13
11
|
|
|
14
12
|
// temp/src/react/FingerprintCanvas.tsx
|
|
15
13
|
import {
|
|
@@ -22,7 +20,20 @@ import {
|
|
|
22
20
|
useState
|
|
23
21
|
} from "react";
|
|
24
22
|
|
|
23
|
+
// temp/src/opacity.ts
|
|
24
|
+
function ridgeOpacity(itemCount, style = {}) {
|
|
25
|
+
const { show = "auto", fadeFrom = 60, fadeTo = 160, opacity = 0.22 } = style;
|
|
26
|
+
if (show === "never")
|
|
27
|
+
return 0;
|
|
28
|
+
if (show === "always" || itemCount <= fadeFrom)
|
|
29
|
+
return opacity;
|
|
30
|
+
if (itemCount >= fadeTo)
|
|
31
|
+
return 0;
|
|
32
|
+
return opacity * (1 - (itemCount - fadeFrom) / (fadeTo - fadeFrom));
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
// temp/src/react/renderers.tsx
|
|
36
|
+
import { markOpacity } from "@data-profile/core";
|
|
26
37
|
import { jsx } from "react/jsx-runtime";
|
|
27
38
|
var shapesRenderer = {
|
|
28
39
|
name: "shapes",
|
|
@@ -589,7 +600,7 @@ function useFingerprint({
|
|
|
589
600
|
});
|
|
590
601
|
return { layout: layout2, error: null };
|
|
591
602
|
} catch (caught) {
|
|
592
|
-
if (caught instanceof
|
|
603
|
+
if (caught instanceof DataError)
|
|
593
604
|
return { layout: emptyLayout(), error: caught };
|
|
594
605
|
throw caught;
|
|
595
606
|
}
|
|
@@ -628,5 +639,5 @@ export {
|
|
|
628
639
|
FingerprintCanvas
|
|
629
640
|
};
|
|
630
641
|
|
|
631
|
-
//# debugId=
|
|
642
|
+
//# debugId=2AE2D4ED63A9036664756E2164756E21
|
|
632
643
|
//# sourceMappingURL=index.js.map
|
package/dist/react/index.js.map
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../temp/src/react/FingerprintCanvas.tsx", "../temp/src/react/renderers.tsx", "../temp/src/react/FingerprintControls.tsx", "../temp/src/react/useFingerprint.ts"],
|
|
3
|
+
"sources": ["../temp/src/react/FingerprintCanvas.tsx", "../temp/src/opacity.ts", "../temp/src/react/renderers.tsx", "../temp/src/react/FingerprintControls.tsx", "../temp/src/react/useFingerprint.ts"],
|
|
4
4
|
"sourcesContent": [
|
|
5
5
|
"import {\n memo,\n useCallback,\n useEffect,\n useId,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n} from \"react\";\nimport { CALLOUT_GUTTER, CALLOUT_HEIGHT, placeCallouts } from \"../callouts\";\nimport { ridgeOpacity } from \"../opacity\";\nimport type {\n FingerprintAnnotation,\n FingerprintLayout,\n FingerprintNode,\n RidgeStyle,\n} from \"../types\";\nimport { shapesRenderer, type FingerprintRenderer } from \"./renderers\";\n\n/** A category of one dimension: what a legend entry or a mark selects. */\nexport interface FingerprintSelection {\n /** The dimension the category belongs to. */\n dimensionId: string;\n /** The category's id, or `UNKNOWN_CATEGORY_ID`. */\n categoryId: string;\n}\n\n/** Props of `FingerprintCanvas`. `useFingerprint`'s `canvasProps` supplies most of them. */\nexport interface FingerprintCanvasProps {\n /** What to draw: from `useFingerprint` (`canvasProps`) or `layoutFingerprint`. */\n layout: FingerprintLayout;\n /** The drawing's accessible name. */\n title: string;\n /** What it shows, in words (`describeLayout` writes one). */\n description: string;\n /** Draws each mark. Defaults to `shapesRenderer`. */\n renderer?: FingerprintRenderer;\n /** How the ridges behind the marks are drawn. Defaults to `{ show: \"auto\" }`. */\n ridges?: RidgeStyle;\n /** Marks in this category of this dimension are highlighted, the rest dimmed. */\n selected?: FingerprintSelection | null;\n /**\n * Called when a mark is clicked, with its category in the arranging dimension, or null when that\n * is already the selection.\n */\n onSelect?: (selection: FingerprintSelection | null) => void;\n /** Notes pinned to the print, each with a leader to a mark of the categories it names. */\n annotations?: readonly FingerprintAnnotation[];\n /** Where the annotations go. See `FingerprintCallouts`. */\n callouts?: FingerprintCallouts;\n /** Names what one item is, for the selection announcement. Defaults to \"items\". */\n unit?: string;\n /** How marks move when the view changes. See `FingerprintMotion`. */\n motion?: FingerprintMotion;\n /** Extra class on the canvas's wrapper. */\n className?: string;\n}\n\n/** Where callouts go. */\nexport interface FingerprintCallouts {\n /** `auto` (the default) puts them beside the print when there is room and numbered below it when not. */\n placement?: \"auto\" | \"beside\" | \"below\";\n /** Below this container width, `auto` puts callouts below. Defaults to 640 px. */\n breakpoint?: number;\n}\n\n/**\n * How marks move when the view changes. SVG animates only so many marks smoothly, so above each\n * limit the change is instant instead of stuttering. Under `prefers-reduced-motion` nothing moves.\n */\nexport interface FingerprintMotion {\n /**\n * Marks glide to their new places while there are at most this many. Defaults to 800: about what\n * SVG animates smoothly enough (43-50 fps in Chrome); 1,500 gliding marks fell to 16-27 fps. It\n * sits above the default mark cap of 600 because a layout can exceed its cap slightly.\n */\n glide?: number;\n /**\n * Colours, fills and lightness fade while there are at most this many marks. Defaults to 400:\n * fades on top of a glide cost a third of the frame rate at 600 marks, and nothing at 400.\n */\n fade?: number;\n}\n\n/** Ridge directions are equivalent half a turn apart; keeping them in one half stops marks spinning between lenses. */\nconst halfTurn = (degrees: number) => ((((degrees + 90) % 180) + 180) % 180) - 90;\n\n/** A CSS attribute-selector string literal for any category id. */\nconst cssString = (value: string) => `\"${value.replace(/[\"\\\\]/g, \"\\\\$&\").replace(/\\n/g, \"\\\\a \")}\"`;\n\n/**\n * Which element draws each mark, and in what DOM order. A mark takes the key of a previous mark\n * that held one of its items, so a view change moves elements rather than replacing them:\n * single-item marks follow their item, and grouped marks (whose ids change with every view) glide\n * from where their items were. Marks with no counterpart get a fresh key and appear.\n *\n * Carried elements also keep their previous DOM order, with new ones after them. Marks never\n * overlap, so order does not show, but React moving hundreds of SVG elements to match a new order\n * cost a 300 ms stall with 1,500 marks. Recomputed only when the layout changes; the state is\n * committed after render, so a discarded render cannot disturb it.\n */\nfunction useCarriedMarks(layout: FingerprintLayout): { key: string; node: FingerprintNode }[] {\n const committed = useRef({ keyOf: new Map<string, string>(), order: [] as string[] });\n const pending = useRef(committed.current);\n const marks = useMemo(() => {\n const previous = committed.current;\n const keyOf = new Map<string, string>();\n const nodeOf = new Map<string, FingerprintNode>();\n layout.nodes.forEach((node, i) => {\n let key: string | undefined;\n for (const id of node.itemIds) {\n const candidate = previous.keyOf.get(id);\n if (candidate !== undefined && !nodeOf.has(candidate)) {\n key = candidate;\n break;\n }\n }\n if (key === undefined) key = nodeOf.has(node.id) ? `${node.id}~${i}` : node.id;\n nodeOf.set(key, node);\n for (const id of node.itemIds) keyOf.set(id, key);\n });\n const kept = previous.order.filter((key) => nodeOf.has(key));\n const keptSet = new Set(kept);\n const order = [...kept, ...[...nodeOf.keys()].filter((key) => !keptSet.has(key))];\n pending.current = { keyOf, order };\n return order.map((key) => ({ key, node: nodeOf.get(key)! }));\n }, [layout]);\n useEffect(() => {\n committed.current = pending.current;\n }, [marks]);\n return marks;\n}\n\n/** The opacity of marks outside the selection. */\nconst DIMMED = 0.22;\n\n/**\n * One mark. Memoised: it re-renders only when its node changes (a new view or new data), never for\n * a selection, a callout or a resize. Selection is drawn by the canvas's style rule, matching the\n * `data-d{n}` attributes each mark carries for every encoded dimension.\n */\nconst Mark = memo(function Mark({\n node,\n dimensions,\n arrange,\n renderer,\n idPrefix,\n onChoose,\n}: {\n node: FingerprintNode;\n dimensions: readonly string[];\n arrange: string;\n renderer: FingerprintRenderer;\n idPrefix: string;\n onChoose?: (categoryId: string) => void;\n}) {\n const attributes: Record<string, string> = {};\n dimensions.forEach((id, k) => (attributes[`data-d${k}`] = node.categories[id] ?? \"\"));\n return (\n <g\n className=\"fp-mark\"\n data-category={node.categories[arrange]}\n {...attributes}\n onClick={onChoose ? () => onChoose(node.categories[arrange]!) : undefined}\n style={{\n transform: `translate(${node.x}px, ${node.y}px) rotate(${halfTurn(node.rotation)}deg)`,\n }}\n >\n {renderer.mark({ node, idPrefix })}\n </g>\n );\n});\n\nfunction useWidth() {\n const ref = useRef<HTMLDivElement>(null);\n const [width, setWidth] = useState<number | null>(null);\n useEffect(() => {\n const element = ref.current;\n if (!element || typeof ResizeObserver !== \"function\") return;\n const observer = new ResizeObserver(([entry]) => setWidth(entry?.contentRect.width ?? null));\n observer.observe(element);\n return () => observer.disconnect();\n }, []);\n return [ref, width] as const;\n}\n\nconst visuallyHidden: CSSProperties = {\n position: \"absolute\",\n width: 1,\n height: 1,\n overflow: \"hidden\",\n clip: \"rect(0 0 0 0)\",\n whiteSpace: \"nowrap\",\n};\n\n/**\n * The fingerprint itself: ridges, one mark per laid-out node drawn by `renderer`, and optional\n * callouts. Marks move to their new places when the layout's view changes (instantly under reduced\n * motion), and marks outside a selection dim. Both are CSS: a selection re-renders no marks. The SVG is one image to assistive technology, named and described by `title` and\n * `description`; choosing a category is done from the legend or list, which are keyboard\n * reachable, and also by pointer on a mark.\n */\nexport function FingerprintCanvas({\n layout,\n title,\n description,\n renderer = shapesRenderer,\n ridges,\n selected = null,\n onSelect,\n annotations = [],\n callouts,\n unit = \"items\",\n motion,\n className,\n}: FingerprintCanvasProps) {\n const uid = useId().replace(/[^\\w-]/g, \"\");\n const [ref, width] = useWidth();\n const placed = useMemo(() => placeCallouts(layout, annotations), [layout, annotations]);\n const mode =\n placed.length === 0\n ? \"none\"\n : (callouts?.placement ?? \"auto\") === \"auto\"\n ? width !== null && width < (callouts?.breakpoint ?? 640)\n ? \"below\"\n : \"beside\"\n : callouts!.placement!;\n const gutter = mode === \"beside\" ? CALLOUT_GUTTER : 0;\n const opacity = ridgeOpacity(layout.itemCount, ridges);\n const selectedEntry = selected\n ? layout.legends\n .find((group) => group.dimensionId === selected.dimensionId)\n ?.entries.find((entry) => entry.id === selected.categoryId)\n : undefined;\n // One stable click handler for every mark, reading the latest props, so a new selection does not\n // hand 1,500 memoised marks a new function and re-render them all.\n const latest = useRef({ selected, onSelect, dimensionId: layout.encoding.arrange });\n latest.current = { selected, onSelect, dimensionId: layout.encoding.arrange };\n const choose = useCallback((categoryId: string) => {\n const { selected: current, onSelect: select, dimensionId } = latest.current;\n select?.(\n current?.dimensionId === dimensionId && current.categoryId === categoryId\n ? null\n : { dimensionId, categoryId },\n );\n }, []);\n const dimensions = useMemo(() => layout.legends.map((g) => g.dimensionId), [layout.legends]);\n const marks = useCarriedMarks(layout);\n // Motion, cursor and selection as one rule set: a selection rewrites this text, not every mark.\n const scope = `.fp-${uid} .fp-mark`;\n const selectedIndex = selected ? dimensions.indexOf(selected.dimensionId) : -1;\n const glides = layout.nodes.length <= (motion?.glide ?? 800);\n const fades = layout.nodes.length <= (motion?.fade ?? 400);\n const css = [\n `${scope}{transition:${glides ? \"transform 700ms cubic-bezier(.2,.7,.2,1),\" : \"\"}opacity 250ms ease${onSelect ? \";cursor:pointer\" : \"\"}}`,\n fades ? `${scope}>*{transition:fill 600ms ease,fill-opacity 600ms ease,stroke 600ms ease}` : \"\",\n `@media (prefers-reduced-motion:reduce){${scope},${scope}>*{transition:none}}`,\n selected && selectedIndex >= 0\n ? `${scope}:not([data-d${selectedIndex}=${cssString(selected.categoryId)}]){opacity:${DIMMED}}`\n : \"\",\n ].join(\"\\n\");\n const ridgeLines = useMemo(\n () =>\n opacity > 0 ? (\n <g\n className=\"fp-ridges\"\n fill=\"none\"\n stroke={ridges?.color ?? \"currentColor\"}\n strokeOpacity={opacity}\n strokeWidth={layout.ridgeSpacing * (ridges?.width ?? 0.12)}\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n >\n {layout.ridges.map((ridge, i) => (\n <polyline\n key={i}\n points={ridge.map(([x, y]) => `${x.toFixed(1)},${y.toFixed(1)}`).join(\" \")}\n />\n ))}\n </g>\n ) : null,\n [opacity, ridges?.color, ridges?.width, layout.ridgeSpacing, layout.ridges],\n );\n\n return (\n <div\n ref={ref}\n className={[\"fp-canvas\", `fp-${uid}`, className].filter(Boolean).join(\" \")}\n style={{ position: \"relative\" }}\n data-selection={selected ? `${selected.dimensionId}:${selected.categoryId}` : undefined}\n >\n <style>{css}</style>\n <svg\n viewBox={`${-gutter} 0 ${layout.width + gutter * 2} ${layout.height}`}\n role=\"img\"\n aria-labelledby={`${uid}-title ${uid}-desc`}\n style={{ display: \"block\", width: \"100%\", height: \"auto\", overflow: \"visible\" }}\n >\n <title id={`${uid}-title`}>{title}</title>\n <desc id={`${uid}-desc`}>{description}</desc>\n {renderer.defs && <defs>{renderer.defs({ idPrefix: uid, legends: layout.legends })}</defs>}\n {ridgeLines}\n <g className=\"fp-marks\">\n {marks.map(({ key, node }) => (\n <Mark\n key={key}\n node={node}\n dimensions={dimensions}\n arrange={layout.encoding.arrange}\n renderer={renderer}\n idPrefix={uid}\n onChoose={onSelect ? choose : undefined}\n />\n ))}\n </g>\n {mode === \"beside\" &&\n placed.map((callout) => {\n const left = callout.side === \"left\";\n const edge = left ? -12 : layout.width + 12;\n return (\n <g key={callout.annotation.id} className=\"fp-callout\" data-side={callout.side}>\n <polyline\n className=\"fp-callout__leader\"\n points={`${edge},${callout.labelY + 14} ${left ? edge + 10 : edge - 10},${callout.labelY + 14} ${callout.anchor.x},${callout.anchor.y}`}\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.2}\n />\n <circle cx={callout.anchor.x} cy={callout.anchor.y} r={3.5} fill=\"currentColor\" />\n <foreignObject\n x={left ? -gutter : layout.width + 16}\n y={callout.labelY}\n width={gutter - 16}\n height={CALLOUT_HEIGHT}\n >\n <div\n className=\"fp-callout__label\"\n style={{ textAlign: left ? \"right\" : \"left\", fontSize: 13, lineHeight: 1.35 }}\n >\n <strong style={{ display: \"block\", fontSize: 15 }}>\n {callout.annotation.title}\n </strong>\n {callout.annotation.body && <span>{callout.annotation.body}</span>}\n </div>\n </foreignObject>\n </g>\n );\n })}\n {mode === \"below\" &&\n placed.map((callout) => (\n <g key={callout.annotation.id} className=\"fp-callout-marker\" aria-hidden=\"true\">\n <circle cx={callout.anchor.x} cy={callout.anchor.y} r={11} fill=\"currentColor\" />\n <text\n x={callout.anchor.x}\n y={callout.anchor.y}\n dy=\"0.35em\"\n textAnchor=\"middle\"\n fontSize={13}\n fontWeight={700}\n style={{ fill: \"var(--fp-callout-ink, Canvas)\" }}\n >\n {callout.number}\n </text>\n </g>\n ))}\n </svg>\n {mode === \"below\" && (\n <ol className=\"fp-callouts\">\n {placed.map((callout) => (\n <li key={callout.annotation.id} value={callout.number}>\n <strong>{callout.annotation.title}</strong>\n {callout.annotation.body && <> {callout.annotation.body}</>}\n </li>\n ))}\n </ol>\n )}\n {/* The callouts drawn beside the print are inside the image; their words are repeated here\n so a screen reader gets them too. */}\n {mode === \"beside\" && (\n <ul style={visuallyHidden}>\n {placed.map((callout) => (\n <li key={callout.annotation.id}>\n {callout.annotation.title}. {callout.annotation.body}\n </li>\n ))}\n </ul>\n )}\n <p role=\"status\" aria-live=\"polite\" style={visuallyHidden}>\n {selectedEntry\n ? `${selectedEntry.label}: ${selectedEntry.count.toLocaleString(\"en-GB\")} ${unit}.`\n : \"\"}\n </p>\n </div>\n );\n}\n",
|
|
6
|
-
"import type {
|
|
6
|
+
"import type { RidgeStyle } from \"./types\";\n\n/**\n * The ridges' opacity for a collection of `itemCount` items under `style`: constant for `always`,\n * zero for `never`, and for `auto` (the default) full until `fadeFrom` items, fading to nothing at\n * `fadeTo`.\n */\nexport function ridgeOpacity(itemCount: number, style: RidgeStyle = {}): number {\n const { show = \"auto\", fadeFrom = 60, fadeTo = 160, opacity = 0.22 } = style;\n if (show === \"never\") return 0;\n if (show === \"always\" || itemCount <= fadeFrom) return opacity;\n if (itemCount >= fadeTo) return 0;\n return opacity * (1 - (itemCount - fadeFrom) / (fadeTo - fadeFrom));\n}\n",
|
|
7
|
+
"import type { ReactNode } from \"react\";\nimport { markOpacity } from \"@data-profile/core\";\nimport type { FingerprintLegendGroup, FingerprintNode } from \"../types\";\n\nexport { markOpacity };\n\n/** What a renderer's `mark` receives. */\nexport interface FingerprintMarkProps {\n /** Carries the resolved `color`, `shape` (null when unknown) and `lightness` (null when full strength). */\n node: FingerprintNode;\n /** Prefix for any ids the renderer's `defs` declared, unique to this drawing. */\n idPrefix: string;\n}\n\n/**\n * Draws marks. `mark` draws one mark centred on the origin, `node.size` across, with the ridge\n * running along the x axis: the canvas positions and rotates it, so a renderer never changes\n * layout, counts or selection. It should honour `node.color`, `node.shape` and `node.lightness`\n * (see `markOpacity`), since those carry data. `defs` runs once per drawing. Marks are memoised and\n * selection is drawn by the canvas (dimming the rest), so `mark` must depend on its props alone.\n * The canvas transitions the fill, fill-opacity and stroke of each mark's top-level elements.\n */\nexport interface FingerprintRenderer {\n /** A name for debugging and for telling renderers apart. */\n name: string;\n /** Shared SVG definitions (gradients, patterns, symbols), rendered once per drawing. */\n defs?: (props: { idPrefix: string; legends: readonly FingerprintLegendGroup[] }) => ReactNode;\n /** Draws one mark. */\n mark: (props: FingerprintMarkProps) => ReactNode;\n}\n\n/**\n * The default: five shapes (circle, rounded square, triangle, diamond, hexagon) by `node.shape`, a\n * ring in the mark's colour when its shape category is unknown, and lightness as opacity.\n */\nexport const shapesRenderer: FingerprintRenderer = {\n name: \"shapes\",\n mark: ({ node }) => {\n const r = node.size / 2;\n const paint = {\n fill: node.color,\n fillOpacity: markOpacity(node),\n };\n if (node.shape === null)\n return (\n <circle\n r={r * 0.8}\n fill=\"none\"\n stroke={node.color}\n strokeOpacity={markOpacity(node)}\n strokeWidth={r * 0.35}\n />\n );\n switch (node.shape % 5) {\n case 1:\n return (\n <rect\n x={-r * 0.85}\n y={-r * 0.85}\n width={r * 1.7}\n height={r * 1.7}\n rx={r * 0.25}\n {...paint}\n />\n );\n case 2:\n return (\n <polygon points={`0,${-r} ${r * 0.95},${r * 0.75} ${-r * 0.95},${r * 0.75}`} {...paint} />\n );\n case 3:\n return <polygon points={`0,${-r} ${r},0 0,${r} ${-r},0`} {...paint} />;\n case 4:\n return (\n <polygon\n points={Array.from({ length: 6 }, (_, i) => {\n const a = (Math.PI / 3) * i;\n return `${(r * Math.cos(a)).toFixed(2)},${(r * Math.sin(a)).toFixed(2)}`;\n }).join(\" \")}\n {...paint}\n />\n );\n default:\n return <circle r={r} {...paint} />;\n }\n },\n};\n\n/** Every mark a dot, shape ignored. Only for when shape must carry nothing. */\nexport const dotsRenderer: FingerprintRenderer = {\n name: \"dots\",\n mark: ({ node }) =>\n node.shape === null ? (\n <circle\n r={node.size * 0.4}\n fill=\"none\"\n stroke={node.color}\n strokeOpacity={markOpacity(node)}\n strokeWidth={node.size * 0.18}\n />\n ) : (\n <circle r={node.size / 2} fill={node.color} fillOpacity={markOpacity(node)} />\n ),\n};\n",
|
|
7
8
|
"import { useId } from \"react\";\nimport type { FingerprintEncoding, FingerprintLegendGroup } from \"../types\";\nimport type { FingerprintSelection } from \"./FingerprintCanvas\";\nimport { shapesRenderer, type FingerprintRenderer } from \"./renderers\";\n\nconst join = (...names: (string | undefined | false)[]) => names.filter(Boolean).join(\" \");\n\n/** A named way of looking at the items: which dimension drives which visual property. */\nexport interface FingerprintView {\n /** Unique among the views. */\n id: string;\n /** The switch's button text. */\n label: string;\n /** Which dimension drives what in this view. */\n encoding: FingerprintEncoding;\n}\n\n/** Props of `FingerprintViewSwitch`. */\nexport interface FingerprintViewSwitchProps {\n /** The views, in button order. */\n views: readonly FingerprintView[];\n /** The active view's id. */\n value: string;\n /** Called with a view's id when its button is pressed. */\n onChange: (viewId: string) => void;\n /** The group's accessible name. Defaults to \"View\". */\n label?: string;\n /** Extra class on the group. */\n className?: string;\n}\n\n/** One pressed button per view. */\nexport function FingerprintViewSwitch({\n views,\n value,\n onChange,\n label = \"View\",\n className,\n}: FingerprintViewSwitchProps) {\n return (\n <div role=\"group\" aria-label={label} className={join(\"fp-views\", className)}>\n {views.map((view) => (\n <button\n key={view.id}\n type=\"button\"\n className=\"fp-views__view\"\n aria-pressed={view.id === value}\n onClick={() => onChange(view.id)}\n >\n {view.label}\n </button>\n ))}\n </div>\n );\n}\n\nconst isSelected = (\n selected: FingerprintSelection | null,\n group: FingerprintLegendGroup,\n id: string,\n) => selected?.dimensionId === group.dimensionId && selected.categoryId === id;\n\n/** Props of `FingerprintLegend`. `useFingerprint`'s `legendProps(group)` supplies most of them. */\nexport interface FingerprintLegendProps {\n /** One dimension's legend, from `layout.legends`. */\n group: FingerprintLegendGroup;\n /** The current selection, from `useFingerprint`. */\n selected?: FingerprintSelection | null;\n /** Called with a category of this group, or null to clear; makes each entry a toggle. */\n onSelect?: (selection: FingerprintSelection | null) => void;\n /** Extra class on the outer element. */\n className?: string;\n\n /** Draws each entry's swatch; use the canvas's renderer so they match. Defaults to `shapesRenderer`. */\n renderer?: FingerprintRenderer;\n /** Show the dimension's label above its entries. Defaults to true. */\n heading?: boolean;\n /** Show each category's count. Defaults to true. */\n counts?: boolean;\n /** Leave out categories with no items. Defaults to false. */\n hideEmpty?: boolean;\n}\n\n/**\n * One dimension's categories, each drawn as a mark of it would be in the properties this dimension\n * drives (a colour swatch for colour, a neutral shape for shape, a fading tone for lightness), and\n * each a toggle that selects it in the canvas. Render one per `layout.legends` group: the legends\n * are how a keyboard reaches a category, and what names it.\n */\nexport function FingerprintLegend({\n group,\n renderer = shapesRenderer,\n selected = null,\n onSelect,\n heading = true,\n counts = true,\n hideEmpty = false,\n className,\n}: FingerprintLegendProps) {\n const uid = useId().replace(/[^\\w-]/g, \"\");\n const headingId = `${uid}-heading`;\n return (\n <div className={join(\"fp-legend\", className)} data-channels={group.channels.join(\" \")}>\n {heading && (\n <p id={headingId} className=\"fp-legend__heading\">\n {group.label}\n </p>\n )}\n <ul\n aria-labelledby={heading ? headingId : undefined}\n aria-label={heading ? undefined : group.label}\n style={{ listStyle: \"none\", padding: 0, margin: 0 }}\n >\n {group.entries\n .filter((entry) => !hideEmpty || entry.count > 0)\n .map((entry, index) => {\n const prefix = `${uid}-${index}`;\n const swatch = (\n <svg\n viewBox=\"-7 -7 14 14\"\n width=\"1em\"\n height=\"1em\"\n aria-hidden=\"true\"\n className=\"fp-legend__swatch\"\n >\n {renderer.defs && (\n <defs>{renderer.defs({ idPrefix: prefix, legends: [group] })}</defs>\n )}\n {renderer.mark({\n node: {\n id: entry.id,\n itemIds: [],\n categories: { [group.dimensionId]: entry.id },\n x: 0,\n y: 0,\n rotation: 0,\n size: 12,\n weight: 1,\n color: entry.color,\n shape: entry.shape,\n lightness: entry.lightness,\n },\n idPrefix: prefix,\n })}\n </svg>\n );\n const text = (\n <>\n {swatch}\n <span className=\"fp-legend__label\">{entry.label}</span>\n {counts && (\n <span className=\"fp-legend__count\">{entry.count.toLocaleString(\"en-GB\")}</span>\n )}\n </>\n );\n return (\n <li key={entry.id} className=\"fp-legend__entry\">\n {onSelect ? (\n <button\n type=\"button\"\n className=\"fp-legend__toggle\"\n aria-pressed={isSelected(selected, group, entry.id)}\n onClick={() =>\n onSelect(\n isSelected(selected, group, entry.id)\n ? null\n : { dimensionId: group.dimensionId, categoryId: entry.id },\n )\n }\n >\n {text}\n </button>\n ) : (\n text\n )}\n </li>\n );\n })}\n </ul>\n </div>\n );\n}\n\n/** Props of `FingerprintList`. `useFingerprint`'s `listProps(group)` supplies most of them. */\nexport interface FingerprintListProps {\n /** One dimension's legend, from `layout.legends`. */\n group: FingerprintLegendGroup;\n /** The current selection, from `useFingerprint`. */\n selected?: FingerprintSelection | null;\n /** Called with a category of this group, or null to clear; makes each entry a toggle. */\n onSelect?: (selection: FingerprintSelection | null) => void;\n /** Extra class on the outer element. */\n className?: string;\n\n /** What one item is, e.g. \"sets\". Defaults to \"items\". */\n unit?: string;\n /** Text for each category's link, when it has an `href`. Defaults to \"Explore\". */\n linkLabel?: string;\n}\n\n/**\n * One dimension as a table: every category with its exact count and share. Render one per\n * `layout.legends` group: together they are the accessible equivalent of the drawing, and the place\n * a callout's claim can be checked.\n */\nexport function FingerprintList({\n group,\n unit = \"items\",\n linkLabel = \"Explore\",\n selected = null,\n onSelect,\n className,\n}: FingerprintListProps) {\n // Every group counts every item once, so its entries sum to the whole collection.\n const total = group.entries.reduce((sum, entry) => sum + entry.count, 0);\n const share = (count: number) => (total ? `${Math.round((count / total) * 100)}%` : \"–\");\n const links = group.entries.some((entry) => entry.href);\n return (\n <table className={join(\"fp-list\", className)}>\n <caption>\n {group.label}: {total.toLocaleString(\"en-GB\")} {unit}\n </caption>\n <thead>\n <tr>\n <th scope=\"col\">{group.label}</th>\n <th scope=\"col\">{unit[0]!.toUpperCase() + unit.slice(1)}</th>\n <th scope=\"col\">Share</th>\n {links && (\n <th scope=\"col\">\n <span className=\"fp-list__hidden\">Link</span>\n </th>\n )}\n </tr>\n </thead>\n <tbody>\n {group.entries.map((entry) => (\n <tr key={entry.id} data-selected={isSelected(selected, group, entry.id) || undefined}>\n <th scope=\"row\">\n {onSelect ? (\n <button\n type=\"button\"\n className=\"fp-list__toggle\"\n aria-pressed={isSelected(selected, group, entry.id)}\n onClick={() =>\n onSelect(\n isSelected(selected, group, entry.id)\n ? null\n : { dimensionId: group.dimensionId, categoryId: entry.id },\n )\n }\n >\n {entry.label}\n </button>\n ) : (\n entry.label\n )}\n </th>\n <td>{entry.count.toLocaleString(\"en-GB\")}</td>\n <td>{share(entry.count)}</td>\n {links && (\n <td>\n {entry.href && (\n <a href={entry.href}>\n {linkLabel}\n <span className=\"fp-list__hidden\"> {entry.label}</span>\n </a>\n )}\n </td>\n )}\n </tr>\n ))}\n </tbody>\n </table>\n );\n}\n",
|
|
8
9
|
"import { useCallback, useMemo, useState } from \"react\";\nimport { describeLayout } from \"../describe\";\nimport { formFromItems } from \"../form\";\nimport { emptyLayout, FingerprintDataError, layoutFingerprint } from \"../layout\";\nimport type {\n FingerprintDimension,\n FingerprintForm,\n FingerprintItem,\n FingerprintLayout,\n FingerprintLegendGroup,\n} from \"../types\";\nimport type { FingerprintSelection } from \"./FingerprintCanvas\";\nimport type { FingerprintView } from \"./FingerprintControls\";\n\n/** What `useFingerprint` takes. */\nexport interface UseFingerprintOptions {\n /** One per mark (below the cap), unique ids. Treated as immutable: pass a new array to change it. */\n items: readonly FingerprintItem[];\n /** Every dimension the items carry values for. */\n dimensions: readonly FingerprintDimension[];\n /**\n * The ways of looking, in switcher order. Defaults to one view per dimension, each driving\n * colour, shape and arrangement from that dimension alone.\n */\n views?: readonly FingerprintView[];\n /** The view shown first. Defaults to the first. */\n initialViewId?: string;\n /** Overrides fields of the form `formFromItems` derives from the data. */\n form?: Partial<FingerprintForm>;\n /** Seeds the form's free fields. Same seed, same picture. Defaults to \"fingerprint\". */\n seed?: string;\n /** Above this many items, marks stand for several. Defaults to 600. */\n maxMarks?: number;\n /** What one item is, for the description and announcements. Defaults to \"items\". */\n unit?: string;\n}\n\n/** Props for `FingerprintCanvas`, ready to spread; add a `title`. */\nexport interface FingerprintCanvasBinding {\n /** The current view's layout. */\n layout: FingerprintLayout;\n /** `describeLayout`'s summary of it, or the data error's message. */\n description: string;\n /** The current selection. */\n selected: FingerprintSelection | null;\n /** Updates the selection. */\n onSelect: (selection: FingerprintSelection | null) => void;\n /** What one item is. */\n unit: string;\n}\n\n/** Props for `FingerprintLegend` or `FingerprintList`, for one legend group, ready to spread. */\nexport interface FingerprintGroupBinding {\n /** The legend group. */\n group: FingerprintLegendGroup;\n /** The current selection. */\n selected: FingerprintSelection | null;\n /** Updates the selection. */\n onSelect: (selection: FingerprintSelection | null) => void;\n}\n\n/** What `useFingerprint` returns. */\nexport interface FingerprintState {\n /** The current view's layout; empty while `error` is set. */\n layout: FingerprintLayout;\n /** A plain-language summary of the layout, for the canvas's description. */\n description: string;\n /** Invalid data (duplicate ids, lightness on an unordered dimension, …), or null. */\n error: FingerprintDataError | null;\n /** The views, as given or one per dimension. */\n views: readonly FingerprintView[];\n /** The active view. */\n view: FingerprintView | undefined;\n /** The active view's id. */\n viewId: string;\n /** Switches view and clears the selection. */\n setViewId: (viewId: string) => void;\n /** The selected category of a dimension, or null. */\n selected: FingerprintSelection | null;\n /** Selects a category of a dimension, or clears with null. */\n setSelected: (selection: FingerprintSelection | null) => void;\n /** Spread onto `FingerprintCanvas`, with a `title`. */\n canvasProps: FingerprintCanvasBinding;\n /** Spread onto a `FingerprintLegend` for one group. */\n legendProps: (group: FingerprintLegendGroup) => FingerprintGroupBinding;\n /** Spread onto a `FingerprintList` for one group, with `unit` included. */\n listProps: (group: FingerprintLegendGroup) => FingerprintGroupBinding & { unit: string };\n}\n\n/**\n * The state a fingerprint needs: the active view, the selected category, the layout for both, and\n * prop bundles that wire them to the components. The form is derived from all the items and\n * dimensions, never the active view, so the ridges hold still and only the marks move when the\n * view changes. Changing view clears the selection. Invalid data comes back as `error` with an\n * empty layout, rather than throwing out of render.\n */\nexport function useFingerprint({\n items,\n dimensions,\n views: given,\n initialViewId,\n form,\n seed = \"fingerprint\",\n maxMarks,\n unit = \"items\",\n}: UseFingerprintOptions): FingerprintState {\n const views = useMemo<readonly FingerprintView[]>(\n () =>\n given ?? dimensions.map((d) => ({ id: d.id, label: d.label, encoding: { arrange: d.id } })),\n [given, dimensions],\n );\n const [viewId, setView] = useState(initialViewId ?? views[0]?.id ?? \"\");\n const [selected, setSelected] = useState<FingerprintSelection | null>(null);\n const view = views.find((v) => v.id === viewId) ?? views[0];\n const resolvedForm = useMemo(\n () => ({ ...formFromItems(items, dimensions, seed), ...form }),\n [items, dimensions, seed, form],\n );\n const { layout, error } = useMemo(() => {\n try {\n const layout = layoutFingerprint({\n items,\n dimensions,\n encoding: view?.encoding,\n seed,\n form: resolvedForm,\n maxMarks,\n });\n return { layout, error: null };\n } catch (caught) {\n if (caught instanceof FingerprintDataError) return { layout: emptyLayout(), error: caught };\n throw caught;\n }\n }, [items, dimensions, view, seed, resolvedForm, maxMarks]);\n const description = useMemo(\n () => (error ? error.message : describeLayout(layout, unit)),\n [layout, error, unit],\n );\n const setViewId = useCallback((id: string) => {\n setView(id);\n setSelected(null);\n }, []);\n const canvasProps = useMemo(\n () => ({ layout, description, selected, onSelect: setSelected, unit }),\n [layout, description, selected, unit],\n );\n const legendProps = useCallback(\n (group: FingerprintLegendGroup) => ({ group, selected, onSelect: setSelected }),\n [selected],\n );\n const listProps = useCallback(\n (group: FingerprintLegendGroup) => ({ group, selected, onSelect: setSelected, unit }),\n [selected, unit],\n );\n return {\n layout,\n description,\n error,\n views,\n view,\n viewId: view?.id ?? \"\",\n setViewId,\n selected,\n setSelected,\n canvasProps,\n legendProps,\n listProps,\n };\n}\n"
|
|
9
10
|
],
|
|
10
|
-
"mappings": "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",
|
|
11
|
-
"debugId": "
|
|
11
|
+
"mappings": ";;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACOO,SAAS,YAAY,CAAC,WAAmB,QAAoB,CAAC,GAAW;AAAA,EAC9E,QAAQ,OAAO,QAAQ,WAAW,IAAI,SAAS,KAAK,UAAU,SAAS;AAAA,EACvE,IAAI,SAAS;AAAA,IAAS,OAAO;AAAA,EAC7B,IAAI,SAAS,YAAY,aAAa;AAAA,IAAU,OAAO;AAAA,EACvD,IAAI,aAAa;AAAA,IAAQ,OAAO;AAAA,EAChC,OAAO,WAAW,KAAK,YAAY,aAAa,SAAS;AAAA;;;ACX3D;AAAA;AAkCO,IAAM,iBAAsC;AAAA,EACjD,MAAM;AAAA,EACN,MAAM,GAAG,WAAW;AAAA,IAClB,MAAM,IAAI,KAAK,OAAO;AAAA,IACtB,MAAM,QAAQ;AAAA,MACZ,MAAM,KAAK;AAAA,MACX,aAAa,YAAY,IAAI;AAAA,IAC/B;AAAA,IACA,IAAI,KAAK,UAAU;AAAA,MACjB,uBACE,IAAC,UAAD;AAAA,QACE,GAAG,IAAI;AAAA,QACP,MAAK;AAAA,QACL,QAAQ,KAAK;AAAA,QACb,eAAe,YAAY,IAAI;AAAA,QAC/B,aAAa,IAAI;AAAA,OACnB;AAAA,IAEJ,QAAQ,KAAK,QAAQ;AAAA,WACd;AAAA,QACH,uBACE,IAAC,QAAD;AAAA,UACE,GAAG,CAAC,IAAI;AAAA,UACR,GAAG,CAAC,IAAI;AAAA,UACR,OAAO,IAAI;AAAA,UACX,QAAQ,IAAI;AAAA,UACZ,IAAI,IAAI;AAAA,aACJ;AAAA,SACN;AAAA,WAEC;AAAA,QACH,uBACE,IAAC,WAAD;AAAA,UAAS,QAAQ,KAAK,CAAC,KAAK,IAAI,QAAQ,IAAI,QAAQ,CAAC,IAAI,QAAQ,IAAI;AAAA,aAAY;AAAA,SAAO;AAAA,WAEvF;AAAA,QACH,uBAAO,IAAC,WAAD;AAAA,UAAS,QAAQ,KAAK,CAAC,KAAK,SAAS,KAAK,CAAC;AAAA,aAAW;AAAA,SAAO;AAAA,WACjE;AAAA,QACH,uBACE,IAAC,WAAD;AAAA,UACE,QAAQ,MAAM,KAAK,EAAE,QAAQ,EAAE,GAAG,CAAC,GAAG,MAAM;AAAA,YAC1C,MAAM,IAAK,KAAK,KAAK,IAAK;AAAA,YAC1B,OAAO,IAAI,IAAI,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC,MAAM,IAAI,KAAK,IAAI,CAAC,GAAG,QAAQ,CAAC;AAAA,WACtE,EAAE,KAAK,GAAG;AAAA,aACP;AAAA,SACN;AAAA;AAAA,QAGF,uBAAO,IAAC,UAAD;AAAA,UAAQ;AAAA,aAAU;AAAA,SAAO;AAAA;AAAA;AAGxC;AAGO,IAAM,eAAoC;AAAA,EAC/C,MAAM;AAAA,EACN,MAAM,GAAG,WACP,KAAK,UAAU,uBACb,IAAC,UAAD;AAAA,IACE,GAAG,KAAK,OAAO;AAAA,IACf,MAAK;AAAA,IACL,QAAQ,KAAK;AAAA,IACb,eAAe,YAAY,IAAI;AAAA,IAC/B,aAAa,KAAK,OAAO;AAAA,GAC3B,oBAEA,IAAC,UAAD;AAAA,IAAQ,GAAG,KAAK,OAAO;AAAA,IAAG,MAAM,KAAK;AAAA,IAAO,aAAa,YAAY,IAAI;AAAA,GAAG;AAElF;;;;AFhBA,IAAM,WAAW,CAAC,cAAwB,UAAU,MAAM,MAAO,OAAO,MAAO;AAG/E,IAAM,YAAY,CAAC,UAAkB,IAAI,MAAM,QAAQ,UAAU,MAAM,EAAE,QAAQ,OAAO,MAAM;AAa9F,SAAS,eAAe,CAAC,QAAqE;AAAA,EAC5F,MAAM,YAAY,OAAO,EAAE,OAAO,IAAI,KAAuB,OAAO,CAAC,EAAc,CAAC;AAAA,EACpF,MAAM,UAAU,OAAO,UAAU,OAAO;AAAA,EACxC,MAAM,QAAQ,QAAQ,MAAM;AAAA,IAC1B,MAAM,WAAW,UAAU;AAAA,IAC3B,MAAM,QAAQ,IAAI;AAAA,IAClB,MAAM,SAAS,IAAI;AAAA,IACnB,OAAO,MAAM,QAAQ,CAAC,MAAM,MAAM;AAAA,MAChC,IAAI;AAAA,MACJ,WAAW,MAAM,KAAK,SAAS;AAAA,QAC7B,MAAM,YAAY,SAAS,MAAM,IAAI,EAAE;AAAA,QACvC,IAAI,cAAc,aAAa,CAAC,OAAO,IAAI,SAAS,GAAG;AAAA,UACrD,MAAM;AAAA,UACN;AAAA,QACF;AAAA,MACF;AAAA,MACA,IAAI,QAAQ;AAAA,QAAW,MAAM,OAAO,IAAI,KAAK,EAAE,IAAI,GAAG,KAAK,MAAM,MAAM,KAAK;AAAA,MAC5E,OAAO,IAAI,KAAK,IAAI;AAAA,MACpB,WAAW,MAAM,KAAK;AAAA,QAAS,MAAM,IAAI,IAAI,GAAG;AAAA,KACjD;AAAA,IACD,MAAM,OAAO,SAAS,MAAM,OAAO,CAAC,QAAQ,OAAO,IAAI,GAAG,CAAC;AAAA,IAC3D,MAAM,UAAU,IAAI,IAAI,IAAI;AAAA,IAC5B,MAAM,QAAQ,CAAC,GAAG,MAAM,GAAG,CAAC,GAAG,OAAO,KAAK,CAAC,EAAE,OAAO,CAAC,QAAQ,CAAC,QAAQ,IAAI,GAAG,CAAC,CAAC;AAAA,IAChF,QAAQ,UAAU,EAAE,OAAO,MAAM;AAAA,IACjC,OAAO,MAAM,IAAI,CAAC,SAAS,EAAE,KAAK,MAAM,OAAO,IAAI,GAAG,EAAG,EAAE;AAAA,KAC1D,CAAC,MAAM,CAAC;AAAA,EACX,UAAU,MAAM;AAAA,IACd,UAAU,UAAU,QAAQ;AAAA,KAC3B,CAAC,KAAK,CAAC;AAAA,EACV,OAAO;AAAA;AAIT,IAAM,SAAS;AAOf,IAAM,OAAO,KAAK,SAAS,KAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,GAQC;AAAA,EACD,MAAM,aAAqC,CAAC;AAAA,EAC5C,WAAW,QAAQ,CAAC,IAAI,MAAO,WAAW,SAAS,OAAO,KAAK,WAAW,OAAO,EAAG;AAAA,EACpF,uBACE,KAUE,KAVF;AAAA,IACE,WAAU;AAAA,IACV,iBAAe,KAAK,WAAW;AAAA,OAC3B;AAAA,IACJ,SAAS,WAAW,MAAM,SAAS,KAAK,WAAW,QAAS,IAAI;AAAA,IAChE,OAAO;AAAA,MACL,WAAW,aAAa,KAAK,QAAQ,KAAK,eAAe,SAAS,KAAK,QAAQ;AAAA,IACjF;AAAA,IAPF,UASG,SAAS,KAAK,EAAE,MAAM,SAAS,CAAC;AAAA,GACjC;AAAA,CAEL;AAED,SAAS,QAAQ,GAAG;AAAA,EAClB,MAAM,MAAM,OAAuB,IAAI;AAAA,EACvC,OAAO,OAAO,YAAY,SAAwB,IAAI;AAAA,EACtD,UAAU,MAAM;AAAA,IACd,MAAM,UAAU,IAAI;AAAA,IACpB,IAAI,CAAC,WAAW,OAAO,mBAAmB;AAAA,MAAY;AAAA,IACtD,MAAM,WAAW,IAAI,eAAe,EAAE,WAAW,SAAS,OAAO,YAAY,SAAS,IAAI,CAAC;AAAA,IAC3F,SAAS,QAAQ,OAAO;AAAA,IACxB,OAAO,MAAM,SAAS,WAAW;AAAA,KAChC,CAAC,CAAC;AAAA,EACL,OAAO,CAAC,KAAK,KAAK;AAAA;AAGpB,IAAM,iBAAgC;AAAA,EACpC,UAAU;AAAA,EACV,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,UAAU;AAAA,EACV,MAAM;AAAA,EACN,YAAY;AACd;AASO,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,WAAW;AAAA,EACX;AAAA,EACA,cAAc,CAAC;AAAA,EACf;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,GACyB;AAAA,EACzB,MAAM,MAAM,MAAM,EAAE,QAAQ,WAAW,EAAE;AAAA,EACzC,OAAO,KAAK,SAAS,SAAS;AAAA,EAC9B,MAAM,SAAS,QAAQ,MAAM,cAAc,QAAQ,WAAW,GAAG,CAAC,QAAQ,WAAW,CAAC;AAAA,EACtF,MAAM,OACJ,OAAO,WAAW,IACd,UACC,UAAU,aAAa,YAAY,SAClC,UAAU,QAAQ,SAAS,UAAU,cAAc,OACjD,UACA,WACF,SAAU;AAAA,EAClB,MAAM,SAAS,SAAS,WAAW,iBAAiB;AAAA,EACpD,MAAM,UAAU,aAAa,OAAO,WAAW,MAAM;AAAA,EACrD,MAAM,gBAAgB,WAClB,OAAO,QACJ,KAAK,CAAC,UAAU,MAAM,gBAAgB,SAAS,WAAW,GACzD,QAAQ,KAAK,CAAC,UAAU,MAAM,OAAO,SAAS,UAAU,IAC5D;AAAA,EAGJ,MAAM,SAAS,OAAO,EAAE,UAAU,UAAU,aAAa,OAAO,SAAS,QAAQ,CAAC;AAAA,EAClF,OAAO,UAAU,EAAE,UAAU,UAAU,aAAa,OAAO,SAAS,QAAQ;AAAA,EAC5E,MAAM,SAAS,YAAY,CAAC,eAAuB;AAAA,IACjD,QAAQ,UAAU,SAAS,UAAU,QAAQ,gBAAgB,OAAO;AAAA,IACpE,SACE,SAAS,gBAAgB,eAAe,QAAQ,eAAe,aAC3D,OACA,EAAE,aAAa,WAAW,CAChC;AAAA,KACC,CAAC,CAAC;AAAA,EACL,MAAM,aAAa,QAAQ,MAAM,OAAO,QAAQ,IAAI,CAAC,MAAM,EAAE,WAAW,GAAG,CAAC,OAAO,OAAO,CAAC;AAAA,EAC3F,MAAM,QAAQ,gBAAgB,MAAM;AAAA,EAEpC,MAAM,QAAQ,OAAO;AAAA,EACrB,MAAM,gBAAgB,WAAW,WAAW,QAAQ,SAAS,WAAW,IAAI;AAAA,EAC5E,MAAM,SAAS,OAAO,MAAM,WAAW,QAAQ,SAAS;AAAA,EACxD,MAAM,QAAQ,OAAO,MAAM,WAAW,QAAQ,QAAQ;AAAA,EACtD,MAAM,MAAM;AAAA,IACV,GAAG,oBAAoB,SAAS,8CAA8C,uBAAuB,WAAW,oBAAoB;AAAA,IACpI,QAAQ,GAAG,kFAAkF;AAAA,IAC7F,0CAA0C,SAAS;AAAA,IACnD,YAAY,iBAAiB,IACzB,GAAG,oBAAoB,iBAAiB,UAAU,SAAS,UAAU,eAAe,YACpF;AAAA,EACN,EAAE,KAAK;AAAA,CAAI;AAAA,EACX,MAAM,aAAa,QACjB,MACE,UAAU,oBACR,KAeE,KAfF;AAAA,IACE,WAAU;AAAA,IACV,MAAK;AAAA,IACL,QAAQ,QAAQ,SAAS;AAAA,IACzB,eAAe;AAAA,IACf,aAAa,OAAO,gBAAgB,QAAQ,SAAS;AAAA,IACrD,eAAc;AAAA,IACd,gBAAe;AAAA,IAPjB,UASG,OAAO,OAAO,IAAI,CAAC,OAAO,sBACzB,KAAC,YAAD;AAAA,MAEE,QAAQ,MAAM,IAAI,EAAE,GAAG,OAAO,GAAG,EAAE,QAAQ,CAAC,KAAK,EAAE,QAAQ,CAAC,GAAG,EAAE,KAAK,GAAG;AAAA,OADpE,CAEP,CACD;AAAA,GACD,IACA,MACN,CAAC,SAAS,QAAQ,OAAO,QAAQ,OAAO,OAAO,cAAc,OAAO,MAAM,CAC5E;AAAA,EAEA,uBACE,KA2GE,OA3GF;AAAA,IACE;AAAA,IACA,WAAW,CAAC,aAAa,MAAM,OAAO,SAAS,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG;AAAA,IACzE,OAAO,EAAE,UAAU,WAAW;AAAA,IAC9B,kBAAgB,WAAW,GAAG,SAAS,eAAe,SAAS,eAAe;AAAA,IAJhF,UA2GE;AAAA,sBArGA,KAAc,SAAd;AAAA,kBAAQ;AAAA,OAAM;AAAA,sBACd,KAyEE,OAzEF;AAAA,QACE,SAAS,GAAG,CAAC,YAAY,OAAO,QAAQ,SAAS,KAAK,OAAO;AAAA,QAC7D,MAAK;AAAA,QACL,mBAAiB,GAAG,aAAa;AAAA,QACjC,OAAO,EAAE,SAAS,SAAS,OAAO,QAAQ,QAAQ,QAAQ,UAAU,UAAU;AAAA,QAJhF,UAyEE;AAAA,0BAnEA,KAAoC,SAApC;AAAA,YAAO,IAAI,GAAG;AAAA,YAAd,UAA4B;AAAA,WAAQ;AAAA,0BACpC,KAAwC,QAAxC;AAAA,YAAM,IAAI,GAAG;AAAA,YAAb,UAA0B;AAAA,WAAc;AAAA,UACvC,SAAS,wBAAQ,KAAmE,QAAnE;AAAA,sBAAO,SAAS,KAAK,EAAE,UAAU,KAAK,SAAS,OAAO,QAAQ,CAAC;AAAA,WAAI;AAAA,UACpF;AAAA,0BACD,KAYE,KAZF;AAAA,YAAG,WAAU;AAAA,YAAb,UACG,MAAM,IAAI,GAAG,KAAK,2BACjB,KAAC,MAAD;AAAA,cAEE;AAAA,cACA;AAAA,cACA,SAAS,OAAO,SAAS;AAAA,cACzB;AAAA,cACA,UAAU;AAAA,cACV,UAAU,WAAW,SAAS;AAAA,eANzB,GAOP,CACD;AAAA,WACD;AAAA,UACD,SAAS,YACR,OAAO,IAAI,CAAC,YAAY;AAAA,YACtB,MAAM,OAAO,QAAQ,SAAS;AAAA,YAC9B,MAAM,OAAO,OAAO,MAAM,OAAO,QAAQ;AAAA,YACzC,uBACE,KAyBE,KAzBF;AAAA,cAA+B,WAAU;AAAA,cAAa,aAAW,QAAQ;AAAA,cAAzE,UAyBE;AAAA,gCAxBA,KAAC,YAAD;AAAA,kBACE,WAAU;AAAA,kBACV,QAAQ,GAAG,QAAQ,QAAQ,SAAS,MAAM,OAAO,OAAO,KAAK,OAAO,MAAM,QAAQ,SAAS,MAAM,QAAQ,OAAO,KAAK,QAAQ,OAAO;AAAA,kBACpI,MAAK;AAAA,kBACL,QAAO;AAAA,kBACP,aAAa;AAAA,iBACf;AAAA,gCACA,KAAC,UAAD;AAAA,kBAAQ,IAAI,QAAQ,OAAO;AAAA,kBAAG,IAAI,QAAQ,OAAO;AAAA,kBAAG,GAAG;AAAA,kBAAK,MAAK;AAAA,iBAAe;AAAA,gCAChF,KAeE,iBAfF;AAAA,kBACE,GAAG,OAAO,CAAC,SAAS,OAAO,QAAQ;AAAA,kBACnC,GAAG,QAAQ;AAAA,kBACX,OAAO,SAAS;AAAA,kBAChB,QAAQ;AAAA,kBAJV,0BAME,KAQE,OARF;AAAA,oBACE,WAAU;AAAA,oBACV,OAAO,EAAE,WAAW,OAAO,UAAU,QAAQ,UAAU,IAAI,YAAY,KAAK;AAAA,oBAF9E,UAQE;AAAA,sCAJA,KAEE,UAFF;AAAA,wBAAQ,OAAO,EAAE,SAAS,SAAS,UAAU,GAAG;AAAA,wBAAhD,UACG,QAAQ,WAAW;AAAA,uBACpB;AAAA,sBACD,QAAQ,WAAW,wBAAQ,KAAiC,QAAjC;AAAA,kCAAO,QAAQ,WAAW;AAAA,uBAAO;AAAA;AAAA,mBAC7D;AAAA,iBACF;AAAA;AAAA,eAxBI,QAAQ,WAAW,EAyBzB;AAAA,WAEL;AAAA,UACF,SAAS,WACR,OAAO,IAAI,CAAC,4BACV,KAaE,KAbF;AAAA,YAA+B,WAAU;AAAA,YAAoB,eAAY;AAAA,YAAzE,UAaE;AAAA,8BAZA,KAAC,UAAD;AAAA,gBAAQ,IAAI,QAAQ,OAAO;AAAA,gBAAG,IAAI,QAAQ,OAAO;AAAA,gBAAG,GAAG;AAAA,gBAAI,MAAK;AAAA,eAAe;AAAA,8BAC/E,KAUE,QAVF;AAAA,gBACE,GAAG,QAAQ,OAAO;AAAA,gBAClB,GAAG,QAAQ,OAAO;AAAA,gBAClB,IAAG;AAAA,gBACH,YAAW;AAAA,gBACX,UAAU;AAAA,gBACV,YAAY;AAAA,gBACZ,OAAO,EAAE,MAAM,gCAAgC;AAAA,gBAPjD,UASG,QAAQ;AAAA,eACT;AAAA;AAAA,aAZI,QAAQ,WAAW,EAazB,CACH;AAAA;AAAA,OACH;AAAA,MACD,SAAS,2BACR,KAOE,MAPF;AAAA,QAAI,WAAU;AAAA,QAAd,UACG,OAAO,IAAI,CAAC,4BACX,KAGE,MAHF;AAAA,UAAgC,OAAO,QAAQ;AAAA,UAA/C,UAGE;AAAA,4BAFA,KAAoC,UAApC;AAAA,wBAAS,QAAQ,WAAW;AAAA,aAAQ;AAAA,YACnC,QAAQ,WAAW,wBAAQ;AAAA,wBAA8B;AAAA,gBAA9B;AAAA,gBAAI,QAAQ,WAAW;AAAA;AAAA,aAAO;AAAA;AAAA,WAFnD,QAAQ,WAAW,EAG1B,CACH;AAAA,OACD;AAAA,MAIH,SAAS,4BACR,KAME,MANF;AAAA,QAAI,OAAO;AAAA,QAAX,UACG,OAAO,IAAI,CAAC,4BACX,KAEE,MAFF;AAAA,oBAEE;AAAA,YADC,QAAQ,WAAW;AAAA,YADtB;AAAA,YAC+B,QAAQ,WAAW;AAAA;AAAA,WADzC,QAAQ,WAAW,EAE1B,CACH;AAAA,OACD;AAAA,sBAEJ,KAIE,KAJF;AAAA,QAAG,MAAK;AAAA,QAAS,aAAU;AAAA,QAAS,OAAO;AAAA,QAA3C,UACG,gBACG,GAAG,cAAc,UAAU,cAAc,MAAM,eAAe,OAAO,KAAK,UAC1E;AAAA,OACJ;AAAA;AAAA,GACF;AAAA;;AGzYN,kBAAS;;AAKT,IAAM,OAAO,IAAI,UAA0C,MAAM,OAAO,OAAO,EAAE,KAAK,GAAG;AA2BlF,SAAS,qBAAqB;AAAA,EACnC;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR;AAAA,GAC6B;AAAA,EAC7B,uBACE,KAYE,OAZF;AAAA,IAAK,MAAK;AAAA,IAAQ,cAAY;AAAA,IAAO,WAAW,KAAK,YAAY,SAAS;AAAA,IAA1E,UACG,MAAM,IAAI,CAAC,yBACV,KAQE,UARF;AAAA,MAEE,MAAK;AAAA,MACL,WAAU;AAAA,MACV,gBAAc,KAAK,OAAO;AAAA,MAC1B,SAAS,MAAM,SAAS,KAAK,EAAE;AAAA,MALjC,UAOG,KAAK;AAAA,OAND,KAAK,EAOV,CACH;AAAA,GACD;AAAA;AAIN,IAAM,aAAa,CACjB,UACA,OACA,OACG,UAAU,gBAAgB,MAAM,eAAe,SAAS,eAAe;AA6BrE,SAAS,iBAAiB;AAAA,EAC/B;AAAA,EACA,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AAAA,EACA,UAAU;AAAA,EACV,SAAS;AAAA,EACT,YAAY;AAAA,EACZ;AAAA,GACyB;AAAA,EACzB,MAAM,MAAM,OAAM,EAAE,QAAQ,WAAW,EAAE;AAAA,EACzC,MAAM,YAAY,GAAG;AAAA,EACrB,uBACE,MA6EE,OA7EF;AAAA,IAAK,WAAW,KAAK,aAAa,SAAS;AAAA,IAAG,iBAAe,MAAM,SAAS,KAAK,GAAG;AAAA,IAApF,UA6EE;AAAA,MA5EC,2BACC,KAEE,KAFF;AAAA,QAAG,IAAI;AAAA,QAAW,WAAU;AAAA,QAA5B,UACG,MAAM;AAAA,OACP;AAAA,sBAEJ,KAsEE,MAtEF;AAAA,QACE,mBAAiB,UAAU,YAAY;AAAA,QACvC,cAAY,UAAU,YAAY,MAAM;AAAA,QACxC,OAAO,EAAE,WAAW,QAAQ,SAAS,GAAG,QAAQ,EAAE;AAAA,QAHpD,UAKG,MAAM,QACJ,OAAO,CAAC,UAAU,CAAC,aAAa,MAAM,QAAQ,CAAC,EAC/C,IAAI,CAAC,OAAO,UAAU;AAAA,UACrB,MAAM,SAAS,GAAG,OAAO;AAAA,UACzB,MAAM,yBACJ,MA0BE,OA1BF;AAAA,YACE,SAAQ;AAAA,YACR,OAAM;AAAA,YACN,QAAO;AAAA,YACP,eAAY;AAAA,YACZ,WAAU;AAAA,YALZ,UA0BE;AAAA,cAnBC,SAAS,wBACR,KAA+D,QAA/D;AAAA,0BAAO,SAAS,KAAK,EAAE,UAAU,QAAQ,SAAS,CAAC,KAAK,EAAE,CAAC;AAAA,eAAI;AAAA,cAEhE,SAAS,KAAK;AAAA,gBACb,MAAM;AAAA,kBACJ,IAAI,MAAM;AAAA,kBACV,SAAS,CAAC;AAAA,kBACV,YAAY,GAAG,MAAM,cAAc,MAAM,GAAG;AAAA,kBAC5C,GAAG;AAAA,kBACH,GAAG;AAAA,kBACH,UAAU;AAAA,kBACV,MAAM;AAAA,kBACN,QAAQ;AAAA,kBACR,OAAO,MAAM;AAAA,kBACb,OAAO,MAAM;AAAA,kBACb,WAAW,MAAM;AAAA,gBACnB;AAAA,gBACA,UAAU;AAAA,cACZ,CAAC;AAAA;AAAA,WACD;AAAA,UAEJ,MAAM,uBACJ;AAAA,sBAME;AAAA,cALC;AAAA,8BACD,KAAkD,QAAlD;AAAA,gBAAM,WAAU;AAAA,gBAAhB,UAAoC,MAAM;AAAA,eAAQ;AAAA,cACjD,0BACC,KAA0E,QAA1E;AAAA,gBAAM,WAAU;AAAA,gBAAhB,UAAoC,MAAM,MAAM,eAAe,OAAO;AAAA,eAAI;AAAA;AAAA,WAE5E;AAAA,UAEJ,uBACE,KAmBE,MAnBF;AAAA,YAAmB,WAAU;AAAA,YAA7B,UACG,2BACC,KAaE,UAbF;AAAA,cACE,MAAK;AAAA,cACL,WAAU;AAAA,cACV,gBAAc,WAAW,UAAU,OAAO,MAAM,EAAE;AAAA,cAClD,SAAS,MACP,SACE,WAAW,UAAU,OAAO,MAAM,EAAE,IAChC,OACA,EAAE,aAAa,MAAM,aAAa,YAAY,MAAM,GAAG,CAC7D;AAAA,cATJ,UAYG;AAAA,aACD,IAEF;AAAA,aAjBK,MAAM,EAmBb;AAAA,SAEL;AAAA,OACH;AAAA;AAAA,GACF;AAAA;AA0BC,SAAS,eAAe;AAAA,EAC7B;AAAA,EACA,OAAO;AAAA,EACP,YAAY;AAAA,EACZ,WAAW;AAAA,EACX;AAAA,EACA;AAAA,GACuB;AAAA,EAEvB,MAAM,QAAQ,MAAM,QAAQ,OAAO,CAAC,KAAK,UAAU,MAAM,MAAM,OAAO,CAAC;AAAA,EACvE,MAAM,QAAQ,CAAC,UAAmB,QAAQ,GAAG,KAAK,MAAO,QAAQ,QAAS,GAAG,OAAO;AAAA,EACpF,MAAM,QAAQ,MAAM,QAAQ,KAAK,CAAC,UAAU,MAAM,IAAI;AAAA,EACtD,uBACE,MAsDE,SAtDF;AAAA,IAAO,WAAW,KAAK,WAAW,SAAS;AAAA,IAA3C,UAsDE;AAAA,sBArDA,MAEE,WAFF;AAAA,kBAEE;AAAA,UADC,MAAM;AAAA,UADT;AAAA,UACkB,MAAM,eAAe,OAAO;AAAA,UAD9C;AAAA,UACkD;AAAA;AAAA,OAChD;AAAA,sBACF,KAWE,SAXF;AAAA,kCACE,MASE,MATF;AAAA,oBASE;AAAA,4BARA,KAA+B,MAA/B;AAAA,cAAI,OAAM;AAAA,cAAV,UAAiB,MAAM;AAAA,aAAQ;AAAA,4BAC/B,KAA0D,MAA1D;AAAA,cAAI,OAAM;AAAA,cAAV,UAAiB,KAAK,GAAI,YAAY,IAAI,KAAK,MAAM,CAAC;AAAA,aAAI;AAAA,4BAC1D,KAAuB,MAAvB;AAAA,cAAI,OAAM;AAAA,cAAV;AAAA,aAAuB;AAAA,YACtB,yBACC,KAEE,MAFF;AAAA,cAAI,OAAM;AAAA,cAAV,0BACE,KAAwC,QAAxC;AAAA,gBAAM,WAAU;AAAA,gBAAhB;AAAA,eAAwC;AAAA,aACxC;AAAA;AAAA,SAEJ;AAAA,OACF;AAAA,sBACF,KAqCE,SArCF;AAAA,kBACG,MAAM,QAAQ,IAAI,CAAC,0BAClB,MAiCE,MAjCF;AAAA,UAAmB,iBAAe,WAAW,UAAU,OAAO,MAAM,EAAE,KAAK;AAAA,UAA3E,UAiCE;AAAA,4BAhCA,KAmBE,MAnBF;AAAA,cAAI,OAAM;AAAA,cAAV,UACG,2BACC,KAaE,UAbF;AAAA,gBACE,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,gBAAc,WAAW,UAAU,OAAO,MAAM,EAAE;AAAA,gBAClD,SAAS,MACP,SACE,WAAW,UAAU,OAAO,MAAM,EAAE,IAChC,OACA,EAAE,aAAa,MAAM,aAAa,YAAY,MAAM,GAAG,CAC7D;AAAA,gBATJ,UAYG,MAAM;AAAA,eACP,IAEF,MAAM;AAAA,aAER;AAAA,4BACF,KAA2C,MAA3C;AAAA,wBAAK,MAAM,MAAM,eAAe,OAAO;AAAA,aAAI;AAAA,4BAC3C,KAA0B,MAA1B;AAAA,wBAAK,MAAM,MAAM,KAAK;AAAA,aAAI;AAAA,YACzB,yBACC,KAOE,MAPF;AAAA,wBACG,MAAM,wBACL,MAGE,KAHF;AAAA,gBAAG,MAAM,MAAM;AAAA,gBAAf,UAGE;AAAA,kBAFC;AAAA,kCACD,MAAkD,QAAlD;AAAA,oBAAM,WAAU;AAAA,oBAAhB,UAAkD;AAAA,sBAAlD;AAAA,sBAAoC,MAAM;AAAA;AAAA,mBAAQ;AAAA;AAAA,eAClD;AAAA,aAEJ;AAAA;AAAA,WA/BG,MAAM,EAiCb,CACH;AAAA,OACD;AAAA;AAAA,GACF;AAAA;;AChRN,wBAAS,yBAAa,sBAAS;AAgGxB,SAAS,cAAc;AAAA,EAC5B;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP;AAAA,EACA,OAAO;AAAA,GACmC;AAAA,EAC1C,MAAM,QAAQ,SACZ,MACE,SAAS,WAAW,IAAI,CAAC,OAAO,EAAE,IAAI,EAAE,IAAI,OAAO,EAAE,OAAO,UAAU,EAAE,SAAS,EAAE,GAAG,EAAE,EAAE,GAC5F,CAAC,OAAO,UAAU,CACpB;AAAA,EACA,OAAO,QAAQ,WAAW,UAAS,iBAAiB,MAAM,IAAI,MAAM,EAAE;AAAA,EACtE,OAAO,UAAU,eAAe,UAAsC,IAAI;AAAA,EAC1E,MAAM,OAAO,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,MAAM,KAAK,MAAM;AAAA,EACzD,MAAM,eAAe,SACnB,OAAO,KAAK,cAAc,OAAO,YAAY,IAAI,MAAM,KAAK,IAC5D,CAAC,OAAO,YAAY,MAAM,IAAI,CAChC;AAAA,EACA,QAAQ,QAAQ,UAAU,SAAQ,MAAM;AAAA,IACtC,IAAI;AAAA,MACF,MAAM,UAAS,kBAAkB;AAAA,QAC/B;AAAA,QACA;AAAA,QACA,UAAU,MAAM;AAAA,QAChB;AAAA,QACA,MAAM;AAAA,QACN;AAAA,MACF,CAAC;AAAA,MACD,OAAO,EAAE,iBAAQ,OAAO,KAAK;AAAA,MAC7B,OAAO,QAAQ;AAAA,MACf,IAAI,kBAAkB;AAAA,QAAsB,OAAO,EAAE,QAAQ,YAAY,GAAG,OAAO,OAAO;AAAA,MAC1F,MAAM;AAAA;AAAA,KAEP,CAAC,OAAO,YAAY,MAAM,MAAM,cAAc,QAAQ,CAAC;AAAA,EAC1D,MAAM,cAAc,SAClB,MAAO,QAAQ,MAAM,UAAU,eAAe,QAAQ,IAAI,GAC1D,CAAC,QAAQ,OAAO,IAAI,CACtB;AAAA,EACA,MAAM,YAAY,aAAY,CAAC,OAAe;AAAA,IAC5C,QAAQ,EAAE;AAAA,IACV,YAAY,IAAI;AAAA,KACf,CAAC,CAAC;AAAA,EACL,MAAM,cAAc,SAClB,OAAO,EAAE,QAAQ,aAAa,UAAU,UAAU,aAAa,KAAK,IACpE,CAAC,QAAQ,aAAa,UAAU,IAAI,CACtC;AAAA,EACA,MAAM,cAAc,aAClB,CAAC,WAAmC,EAAE,OAAO,UAAU,UAAU,YAAY,IAC7E,CAAC,QAAQ,CACX;AAAA,EACA,MAAM,YAAY,aAChB,CAAC,WAAmC,EAAE,OAAO,UAAU,UAAU,aAAa,KAAK,IACnF,CAAC,UAAU,IAAI,CACjB;AAAA,EACA,OAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,QAAQ,MAAM,MAAM;AAAA,IACpB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAAA;",
|
|
12
|
+
"debugId": "2AE2D4ED63A9036664756E2164756E21",
|
|
12
13
|
"names": []
|
|
13
14
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@data-profile/fingerprint",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Draw any collection as a deterministic fingerprint: one mark per item along real-looking ridges, grouped and coloured by your own categories, with pluggable renderers.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Will Thirkettle",
|
|
@@ -57,6 +57,9 @@
|
|
|
57
57
|
"lint:package": "publint && attw --pack . --profile esm-only --exclude-entrypoints ./styles.css",
|
|
58
58
|
"check:consumer": "bun run scripts/check-consumer.ts"
|
|
59
59
|
},
|
|
60
|
+
"dependencies": {
|
|
61
|
+
"@data-profile/core": "^0.1.0"
|
|
62
|
+
},
|
|
60
63
|
"peerDependencies": {
|
|
61
64
|
"react": "^18.0.0 || ^19.0.0"
|
|
62
65
|
},
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"version": 3,
|
|
3
|
-
"sources": ["../temp/src/palette.ts", "../temp/src/hash.ts", "../temp/src/form.ts", "../temp/src/ridges.ts", "../temp/src/slots.ts", "../temp/src/layout.ts", "../temp/src/opacity.ts", "../temp/src/callouts.ts", "../temp/src/describe.ts"],
|
|
4
|
-
"sourcesContent": [
|
|
5
|
-
"/**\n * The default categorical palette, for consumers that give categories no colour. Ten hues, chosen\n * to stay distinguishable on light and dark backgrounds; beyond ten they repeat, which is why the\n * legend and list, not colour, carry identity.\n */\nexport const DEFAULT_PALETTE = [\n \"#2f6fde\",\n \"#2bb3e6\",\n \"#f26b3a\",\n \"#f5b82e\",\n \"#14a39a\",\n \"#7a5af5\",\n \"#3fae6b\",\n \"#d9488f\",\n \"#8c6d46\",\n \"#5e8c99\",\n] as const;\n\n/** Unknowns are drawn in a neutral tone, outside the palette, so they never look like a category. */\nexport const UNKNOWN_COLOR = \"#b8bcc4\";\n",
|
|
6
|
-
"/** FNV-1a, 32-bit. Stable across runtimes, so a seed draws the same picture everywhere. */\nexport function stableHash(value: string): number {\n let hash = 2166136261;\n for (let i = 0; i < value.length; i++) hash = Math.imul(hash ^ value.charCodeAt(i), 16777619);\n return hash >>> 0;\n}\n\n/** A value in [0, 1) derived from a key: the only source of variation in a layout. */\nexport const unitHash = (key: string) => stableHash(key) / 2 ** 32;\n\n/** Code-unit order, so results never depend on the runtime's locale. */\nexport const compareIds = (a: string, b: string) => (a < b ? -1 : a > b ? 1 : 0);\n",
|
|
7
|
-
"import { unitHash } from \"./hash\";\nimport type { FingerprintDimension, FingerprintItem, FingerprintForm } from \"./types\";\n\nconst clamp = (value: number, low: number, high: number) => Math.min(high, Math.max(low, value));\n/** Coarse steps, so a collection's fingerprint holds still as it grows by an item or two. */\nconst quantise = (value: number, step = 0.2) => Math.round(value / step) * step;\n\n/** A plausible shape from a seed alone: the default when the consumer gives none. */\nexport function formFromSeed(seed: string): FingerprintForm {\n const h = (key: string) => unitHash(`${seed}:${key}`);\n return {\n pattern: \"loop\",\n hand: h(\"hand\") < 0.5 ? -1 : 1,\n coreX: quantise(h(\"x\") * 2 - 1) * 0.5,\n coreY: quantise(h(\"y\") * 2 - 1) * 0.5,\n spread: quantise(h(\"spread\")),\n tilt: Math.round((h(\"tilt\") - 0.5) * 12),\n };\n}\n\nexport function resolveForm(seed: string, shape: Partial<FingerprintForm> = {}): FingerprintForm {\n const resolved = { ...formFromSeed(seed), ...shape };\n return {\n ...resolved,\n coreX: clamp(resolved.coreX, -1, 1),\n coreY: clamp(resolved.coreY, -1, 1),\n spread: clamp(resolved.spread, 0, 1),\n tilt: clamp(resolved.tilt, -10, 10),\n };\n}\n\n/** Mean position (0 to 1) of known values within a dimension's category order, or 0.5. */\nfunction lean(items: readonly FingerprintItem[], dimension: FingerprintDimension | undefined) {\n if (!dimension || dimension.categories.length < 2) return 0.5;\n const index = new Map(\n dimension.categories.map((c, i) => [c.id, i / (dimension.categories.length - 1)]),\n );\n let sum = 0;\n let n = 0;\n for (const item of items) {\n const at = index.get(item.values[dimension.id] ?? \"\");\n if (at !== undefined) {\n sum += at;\n n += 1;\n }\n }\n return n ? sum / n : 0.5;\n}\n\n/**\n * A shape that is the collection's own: derived only from the data, so the same collection always\n * draws the same fingerprint, and it changes slowly as the collection grows. It is a signature,\n * not a chart; say so beside it. The mapping, which a caption can state:\n *\n * - **Pattern.** An arch while there are fewer than 12 items; then a loop when one category of the\n * first dimension holds 40% or more of the items (a focused collection); otherwise a whorl.\n * - **Core across, and loop direction.** Where the second dimension's values sit, on average, in its\n * category order: earlier categories pull the core left, later ones right.\n * - **Core height.** The same for the third dimension.\n * - **Spread.** The share of all categories that have at least one item: broader reads looser.\n * - **Tilt.** From the seed only, so two equal collections still differ slightly.\n */\nexport function formFromItems(\n items: readonly FingerprintItem[],\n dimensions: readonly FingerprintDimension[],\n seed = \"fingerprint\",\n): FingerprintForm {\n const [first, second, third] = dimensions;\n let top = 0;\n if (first) {\n const counts = new Map<string, number>();\n for (const item of items) {\n const value = item.values[first.id];\n if (value != null) counts.set(value, (counts.get(value) ?? 0) + 1);\n }\n top = items.length ? Math.max(0, ...counts.values()) / items.length : 0;\n }\n const total = dimensions.reduce((sum, d) => sum + d.categories.length, 0);\n const used = dimensions.reduce(\n (sum, d) =>\n sum + d.categories.filter((c) => items.some((item) => item.values[d.id] === c.id)).length,\n 0,\n );\n const across = lean(items, second);\n return resolveForm(seed, {\n pattern: items.length < 12 ? \"arch\" : top >= 0.4 ? \"loop\" : \"whorl\",\n hand: across < 0.5 ? -1 : 1,\n coreX: quantise((across - 0.5) * 2),\n coreY: quantise((lean(items, third) - 0.5) * 2),\n spread: total ? quantise(used / total) : 0.5,\n });\n}\n",
|
|
8
|
-
"import type { FingerprintForm, Point } from \"./types\";\n\n/** The fixed frame every layout is drawn in: a portrait fingertip with a small margin. */\nexport const FRAME = { width: 400, height: 520, cx: 200, cy: 260, rx: 180, ry: 240 } as const;\n\nconst DEG = Math.PI / 180;\n\n/** Normalised radius of the fingertip outline through a point: 1 on the outline. */\nexport const outlineRadius = (x: number, y: number) =>\n Math.hypot((x - FRAME.cx) / FRAME.rx, (y - FRAME.cy) / FRAME.ry);\n\nconst smoothstep = (a: number, b: number, x: number) => {\n const t = Math.min(1, Math.max(0, (x - a) / (b - a)));\n return t * t * (3 - 2 * t);\n};\n\n/** Where the ridges curl (cores) and split three ways (deltas). */\nexport interface Singularities {\n centre: Point;\n cores: Point[];\n deltas: Point[];\n}\n\n/**\n * Places a shape's singular points. A loop has one core and one delta below it to one side; a\n * whorl two cores and a delta either side; an arch none.\n */\nexport function singularities(shape: FingerprintForm): Singularities {\n const { rx, ry, cx, cy } = FRAME;\n const centre: Point = [cx + shape.coreX * 0.12 * rx, cy - 0.1 * ry + shape.coreY * 0.1 * ry];\n const [x, y] = centre;\n if (shape.pattern === \"arch\") return { centre, cores: [], deltas: [] };\n if (shape.pattern === \"loop\")\n return {\n centre,\n cores: [centre],\n deltas: [\n [x + shape.hand * (0.4 + 0.25 * shape.spread) * rx, y + (0.38 + 0.12 * shape.spread) * ry],\n ],\n };\n const twist = 0.06 * rx;\n return {\n centre,\n cores: [\n [x - twist, y - shape.hand * 0.025 * ry],\n [x + twist, y + shape.hand * 0.025 * ry],\n ],\n deltas: [\n [x - (0.5 + 0.2 * shape.spread) * rx, y + 0.42 * ry],\n [x + (0.5 + 0.2 * shape.spread) * rx, y + 0.42 * ry],\n ],\n };\n}\n\n/**\n * Ridge direction at a point, in radians, modulo pi.\n *\n * Inside, the Sherlock-Monro model: half the sum of the angles to each core minus half the sum to\n * each delta, which is what makes ridges curl round a core and split at a delta. An arch has no\n * singular points, so its ridges follow a raised bump instead. Near the outline, and mostly over the\n * top, ridges bend to follow it, as they do round a fingertip.\n */\nexport function orientation(\n x: number,\n y: number,\n shape: FingerprintForm,\n s: Singularities,\n): number {\n const tilt = shape.tilt * DEG;\n let theta: number;\n if (shape.pattern === \"arch\") {\n const width = (0.35 + 0.25 * shape.spread) * FRAME.rx;\n const u = (x - s.centre[0]) / width;\n const height = 0.3 * FRAME.ry * Math.max(0, 1 - Math.abs(y - s.centre[1]) / FRAME.ry);\n theta = tilt + Math.atan(((2 * height * u) / width) * Math.exp(-u * u));\n } else {\n let sum = 0;\n for (const [px, py] of s.cores) sum += Math.atan2(y - py, x - px);\n for (const [px, py] of s.deltas) sum -= Math.atan2(y - py, x - px);\n theta = tilt + sum / 2;\n }\n // Blend towards the outline's tangent, as doubled angles so pi-periodic directions average.\n const r = outlineRadius(x, y);\n // Strongest over the top of the fingertip, easing to 40% below it: a step here kinks ridges.\n const over = smoothstep(FRAME.cy + 0.25 * FRAME.ry, FRAME.cy - 0.25 * FRAME.ry, y);\n const weight = smoothstep(0.68, 1, r) * (0.4 + 0.6 * over);\n if (weight === 0) return theta;\n const tangent = Math.atan2((x - FRAME.cx) / FRAME.rx ** 2, -(y - FRAME.cy) / FRAME.ry ** 2);\n const vx = (1 - weight) * Math.cos(2 * theta) + weight * Math.cos(2 * tangent);\n const vy = (1 - weight) * Math.sin(2 * theta) + weight * Math.sin(2 * tangent);\n return Math.atan2(vy, vx) / 2;\n}\n\n/** A spatial hash of every accepted ridge point, for the spacing checks. */\nclass PointGrid {\n private cells = new Map<number, { x: number; y: number; line: number; index: number }[]>();\n constructor(private size: number) {}\n private key = (gx: number, gy: number) => gx * 4096 + gy;\n /** Returns the cell key, so a rejected ridge can be taken out again cheaply. */\n add(x: number, y: number, line: number, index: number) {\n const k = this.key(Math.floor(x / this.size), Math.floor(y / this.size));\n const cell = this.cells.get(k);\n if (cell) cell.push({ x, y, line, index });\n else this.cells.set(k, [{ x, y, line, index }]);\n return k;\n }\n removeLine(line: number, keys: Iterable<number>) {\n for (const k of new Set(keys)) {\n const kept = (this.cells.get(k) ?? []).filter((p) => p.line !== line);\n if (kept.length) this.cells.set(k, kept);\n else this.cells.delete(k);\n }\n }\n /** True when a point closer than `distance` passes `counts`. `distance` must not exceed the cell size. */\n near(\n x: number,\n y: number,\n distance: number,\n counts: (p: { line: number; index: number }) => boolean,\n ) {\n const gx = Math.floor(x / this.size);\n const gy = Math.floor(y / this.size);\n for (let dx = -1; dx <= 1; dx++)\n for (let dy = -1; dy <= 1; dy++)\n for (const p of this.cells.get(this.key(gx + dx, gy + dy)) ?? [])\n if ((p.x - x) ** 2 + (p.y - y) ** 2 < distance ** 2 && counts(p)) return true;\n return false;\n }\n}\n\n/** Ridges stop this far inside the outline. */\nconst OUTLINE_LIMIT = 0.985;\n\n/**\n * Evenly spaced ridges along the shape's flow field (Jobard and Lefer's streamline placement),\n * core outwards. Each ridge is traced both ways from a seed until it would come within `spacing`\n * of another ridge, of a singular point, of a distant part of itself, or of the outline; new seeds\n * are tried one spacing either side of every accepted ridge. A dropped-out ridge end is a ridge\n * ending, exactly as in a real print.\n *\n * Every point of every ridge is at least about `spacing` from every other ridge, which is what lets\n * marks of that size sit on them without touching.\n */\nexport function ridges(shape: FingerprintForm, spacing: number): Point[][] {\n const s = singularities(shape);\n const step = spacing * 0.3;\n const selfGap = Math.ceil((3 * spacing) / step);\n const minLength = Math.ceil((2 * spacing) / step);\n const maxSteps = Math.ceil((4 * Math.PI * (FRAME.rx + FRAME.ry)) / step);\n const grid = new PointGrid(spacing);\n const lines: Point[][] = [];\n const singular = [...s.cores, ...s.deltas];\n\n const free = (x: number, y: number, line: number, index: number) =>\n outlineRadius(x, y) <= OUTLINE_LIMIT &&\n singular.every(([px, py]) => Math.hypot(x - px, y - py) > spacing * 0.6) &&\n !grid.near(x, y, spacing, (p) => p.line !== line || Math.abs(p.index - index) > selfGap);\n\n const direction = (x: number, y: number, previous: Point): Point => {\n const theta = orientation(x, y, shape, s);\n const v: Point = [Math.cos(theta), Math.sin(theta)];\n return v[0] * previous[0] + v[1] * previous[1] < 0 ? [-v[0], -v[1]] : v;\n };\n\n const trace = (seed: Point): Point[] | null => {\n const line = lines.length;\n const theta = orientation(seed[0], seed[1], shape, s);\n const keys = [grid.add(seed[0], seed[1], line, 0)];\n const halves = ([1, -1] as const).map((sign) => {\n const points: Point[] = [];\n let p = seed;\n let previous: Point = [sign * Math.cos(theta), sign * Math.sin(theta)];\n for (let i = 1; i <= maxSteps; i++) {\n // Second-order Runge-Kutta: sample the direction half a step ahead.\n const v1 = direction(p[0], p[1], previous);\n const v2 = direction(p[0] + (v1[0] * step) / 2, p[1] + (v1[1] * step) / 2, v1);\n // A ridge turns gently; a sharp turn in one step means the field folds here, so end it.\n if (v2[0] * previous[0] + v2[1] * previous[1] < 0.8) break;\n const q: Point = [p[0] + v2[0] * step, p[1] + v2[1] * step];\n if (!free(q[0], q[1], line, sign * i)) break;\n keys.push(grid.add(q[0], q[1], line, sign * i));\n points.push(q);\n p = q;\n previous = v2;\n }\n return points;\n });\n const points = [...halves[1]!.reverse(), seed, ...halves[0]!];\n if (points.length < minLength) {\n grid.removeLine(line, keys);\n return null;\n }\n return points;\n };\n\n const queue: Point[] = [];\n const seedFrom = (points: Point[]) => {\n const every = Math.max(1, Math.round(spacing / step / 2));\n for (let i = 1; i < points.length - 1; i += every) {\n const [ax, ay] = points[i - 1]!;\n const [bx, by] = points[i + 1]!;\n const length = Math.hypot(bx - ax, by - ay) || 1;\n const [nx, ny] = [-(by - ay) / length, (bx - ax) / length];\n const [x, y] = points[i]!;\n queue.push([x + nx * spacing, y + ny * spacing], [x - nx * spacing, y - ny * spacing]);\n }\n };\n const attempt = (seed: Point) => {\n if (!free(seed[0], seed[1], -1, 0)) return;\n const points = trace(seed);\n if (!points) return;\n lines.push(points);\n seedFrom(points);\n };\n let next = 0;\n const drain = () => {\n while (next < queue.length) attempt(queue[next++]!);\n };\n\n // Start just above the core and grow outwards; then sweep for any region the growth missed.\n queue.push([s.centre[0], s.centre[1] - spacing]);\n drain();\n const sweep: Point[] = [];\n for (let x = FRAME.cx - FRAME.rx; x <= FRAME.cx + FRAME.rx; x += spacing / 2)\n for (let y = FRAME.cy - FRAME.ry; y <= FRAME.cy + FRAME.ry; y += spacing / 2)\n if (outlineRadius(x, y) <= OUTLINE_LIMIT) sweep.push([x, y]);\n const distance = ([x, y]: Point) => Math.hypot(x - s.centre[0], y - s.centre[1]);\n sweep.sort((a, b) => distance(a) - distance(b) || a[0] - b[0] || a[1] - b[1]);\n for (const seed of sweep) {\n attempt(seed);\n drain();\n }\n\n return orderFromCore(lines, s.centre);\n}\n\n/**\n * Ridges sorted by how close they come to the core, each running clockwise round it, so marks laid\n * along them in turn form runs from the core outwards.\n */\nfunction orderFromCore(lines: Point[][], [cx, cy]: Point): Point[][] {\n const reach = (line: Point[]) => Math.min(...line.map(([x, y]) => Math.hypot(x - cx, y - cy)));\n const swept = (line: Point[]) => {\n let total = 0;\n for (let i = 1; i < line.length; i++) {\n let d =\n Math.atan2(line[i]![1] - cy, line[i]![0] - cx) -\n Math.atan2(line[i - 1]![1] - cy, line[i - 1]![0] - cx);\n if (d > Math.PI) d -= 2 * Math.PI;\n if (d < -Math.PI) d += 2 * Math.PI;\n total += d;\n }\n return total;\n };\n return lines\n .map((line) => ({ line: swept(line) < 0 ? [...line].reverse() : line, reach: reach(line) }))\n .sort(\n (a, b) => a.reach - b.reach || a.line[0]![0] - b.line[0]![0] || a.line[0]![1] - b.line[0]![1],\n )\n .map(({ line }) => line);\n}\n",
|
|
9
|
-
"import type { Point } from \"./types\";\n\nexport function polylineLength(points: readonly Point[]): number {\n let length = 0;\n for (let i = 1; i < points.length; i++)\n length += Math.hypot(points[i]![0] - points[i - 1]![0], points[i]![1] - points[i - 1]![1]);\n return length;\n}\n\n/**\n * Splits `count` across ridges so the most crowded ridge is as uncrowded as possible: every ridge\n * first gets as many slots as fit at the average spacing, then each remaining slot goes to the\n * ridge whose spacing it shrinks least (the D'Hondt rule). Ties go to the earlier ridge, so the\n * result is stable.\n */\nfunction allocate(lengths: readonly number[], count: number): number[] {\n const total = lengths.reduce((sum, length) => sum + length, 0);\n if (total === 0 || count === 0) return lengths.map(() => 0);\n const average = total / count;\n const counts = lengths.map((length) => Math.floor(length / average));\n for (let left = count - counts.reduce((sum, n) => sum + n, 0); left > 0; left--) {\n let best = 0;\n for (let i = 1; i < lengths.length; i++)\n if (lengths[i]! / (counts[i]! + 1) > lengths[best]! / (counts[best]! + 1)) best = i;\n counts[best]! += 1;\n }\n return counts;\n}\n\n/** The point and direction (degrees) at arc length `at` along a polyline. */\nfunction pointAt(line: readonly Point[], at: number) {\n let walked = 0;\n for (let i = 1; i < line.length; i++) {\n const [x0, y0] = line[i - 1]!;\n const [x1, y1] = line[i]!;\n const segment = Math.hypot(x1 - x0, y1 - y0);\n if (at <= walked + segment || i === line.length - 1) {\n const f = segment === 0 ? 0 : Math.min(1, (at - walked) / segment);\n return {\n x: x0 + (x1 - x0) * f,\n y: y0 + (y1 - y0) * f,\n rotation: (Math.atan2(y1 - y0, x1 - x0) * 180) / Math.PI,\n };\n }\n walked += segment;\n }\n const [x, y] = line[0]!;\n return { x, y, rotation: 0 };\n}\n\n/**\n * `count` positions along the ridges, in ridge order. Each ridge gets slots by `allocate` and spaces them evenly over its whole length, so a closed ring has no crowded seam where\n * its end meets its start. `pitch` is the smallest spacing between\n * neighbours on any ridge.\n */\nexport function slotsAlong(lines: readonly Point[][], count: number) {\n const lengths = lines.map(polylineLength);\n const counts = allocate(lengths, count);\n const slots: { x: number; y: number; rotation: number }[] = [];\n let pitch = Infinity;\n lines.forEach((line, k) => {\n const n = counts[k]!;\n if (n === 0) return;\n const step = lengths[k]! / n;\n // A ridge with one slot has no neighbour along it, so its length says nothing about crowding.\n if (n > 1) pitch = Math.min(pitch, step);\n for (let i = 0; i < n; i++) slots.push(pointAt(line, (i + 0.5) * step));\n });\n // Infinity when no ridge holds two slots: nothing along a ridge then limits mark size.\n return { slots, pitch };\n}\n",
|
|
10
|
-
"import { compareIds } from \"./hash\";\nimport { DEFAULT_PALETTE, UNKNOWN_COLOR } from \"./palette\";\nimport { FRAME, ridges } from \"./ridges\";\nimport { resolveForm } from \"./form\";\nimport { polylineLength, slotsAlong } from \"./slots\";\nimport type {\n FingerprintChannel,\n FingerprintDimension,\n FingerprintEncoding,\n FingerprintItem,\n FingerprintLayout,\n FingerprintLegendEntry,\n FingerprintLegendGroup,\n FingerprintNode,\n FingerprintForm,\n LayoutInput,\n Point,\n} from \"./types\";\n\n/** The id of the category unknown values fall into. Reserved: a dimension may not use it. */\nexport const UNKNOWN_CATEGORY_ID = \"~unknown\";\n\n/**\n * 600 marks: the most SVG glides smoothly between views (measured in Chrome: 56-59 fps at 400,\n * 43-50 at 800, 16-27 at 1,500), and large enough that a mark stays legible at phone width.\n */\nconst DEFAULTS = { seed: \"fingerprint\", maxMarks: 600, minRidges: 6 } as const;\n/** A mark fills this share of the space between its neighbours. */\nconst MARK_FILL = 0.85;\n\n/**\n * Data the layout cannot draw truthfully: duplicate item ids, a weight that is not positive, a\n * reserved or duplicated category id, an encoding naming no dimension, or lightness on an unordered\n * dimension. `layoutFingerprint` throws it; `useFingerprint` returns it as `error`.\n */\nexport class FingerprintDataError extends Error {\n override name = \"FingerprintDataError\";\n}\n\n/** A usable count, or undefined for anything else (undefined, NaN, zero, negative). */\nconst positive = (value: number | undefined) =>\n value !== undefined && Number.isFinite(value) && value >= 1 ? Math.floor(value) : undefined;\n\n/** Beyond these, a property stops telling categories apart: palette colours repeat, and the default renderer has five shapes. */\nconst DISTINCT = { color: 10, shape: 5 } as const;\n\n/** What a layout needs of an items array whatever the view: validated once, then reused. */\ninterface PreparedItems {\n /** `idRank[i]` is item i's place in id order: the stable tie-break. */\n idRank: Uint32Array;\n /** Each dimension's category rank per item; unknown ranks after every category. */\n ranks: WeakMap<FingerprintDimension, Int32Array>;\n}\n\n/**\n * Items arrays are treated as immutable: switching view lays out the same array again, so its\n * validation, id order and per-dimension ranks are worked out once and kept with it. This is what\n * keeps a view change fast for a very large collection; only a sort of numbers remains.\n */\nconst prepared = new WeakMap<readonly FingerprintItem[], PreparedItems>();\nfunction prepare(items: readonly FingerprintItem[]): PreparedItems {\n const hit = prepared.get(items);\n if (hit) return hit;\n const seen = new Set<string>();\n for (const item of items) {\n if (seen.has(item.id)) throw new FingerprintDataError(`Duplicate item id \"${item.id}\"`);\n seen.add(item.id);\n const weight = item.weight ?? 1;\n if (!Number.isFinite(weight) || weight <= 0)\n throw new FingerprintDataError(\n `Item \"${item.id}\" has weight ${weight}; weights must be positive`,\n );\n }\n const byId = Uint32Array.from(items.keys()).sort((a, b) =>\n compareIds(items[a]!.id, items[b]!.id),\n );\n const idRank = new Uint32Array(items.length);\n byId.forEach((index, rank) => (idRank[index] = rank));\n const result = { idRank, ranks: new WeakMap<FingerprintDimension, Int32Array>() };\n prepared.set(items, result);\n return result;\n}\n\nfunction ranksIn(items: readonly FingerprintItem[], ready: PreparedItems, d: FingerprintDimension) {\n const hit = ready.ranks.get(d);\n if (hit) return hit;\n const index = new Map(d.categories.map((c, i) => [c.id, i]));\n const unknown = d.categories.length;\n const ranks = new Int32Array(items.length);\n items.forEach((item, i) => {\n const value = item.values[d.id];\n ranks[i] = (value != null ? index.get(value) : undefined) ?? unknown;\n });\n ready.ranks.set(d, ranks);\n return ranks;\n}\n\nfunction validateDimension(dimension: FingerprintDimension) {\n const categories = new Set<string>();\n for (const category of dimension.categories) {\n if (category.id === UNKNOWN_CATEGORY_ID)\n throw new FingerprintDataError(`Category id \"${UNKNOWN_CATEGORY_ID}\" is reserved`);\n if (categories.has(category.id))\n throw new FingerprintDataError(`Duplicate category id \"${category.id}\" in \"${dimension.id}\"`);\n categories.add(category.id);\n }\n}\n\n/** Fills in an encoding's defaults: colour and shape follow `arrange` unless set, lightness is off. */\nfunction resolveEncoding(\n dimensions: readonly FingerprintDimension[],\n encoding: FingerprintEncoding | undefined,\n): Required<FingerprintEncoding> {\n const arrange = encoding?.arrange ?? dimensions[0]?.id;\n if (arrange === undefined) throw new FingerprintDataError(\"At least one dimension is needed\");\n const resolved = {\n arrange,\n color: encoding?.color === undefined ? arrange : encoding.color,\n shape: encoding?.shape === undefined ? arrange : encoding.shape,\n lightness: encoding?.lightness ?? null,\n };\n for (const [channel, id] of Object.entries(resolved)) {\n if (id === null) continue;\n const dimension = dimensions.find((d) => d.id === id);\n if (!dimension)\n throw new FingerprintDataError(\n `The ${channel} encoding names \"${id}\", which is not a dimension`,\n );\n if (channel === \"lightness\" && !dimension.ordered)\n throw new FingerprintDataError(\n `\"${id}\" drives lightness but is not ordered; lighter and darker would read as less and more`,\n );\n }\n return resolved;\n}\n\n/**\n * Ridges depend only on the form and spacing, not on the active dimension, so switching lens\n * reuses them: the marks move, the ridges stay. A few recent results are kept; the layout stays a\n * pure function because equal inputs always gave equal ridges.\n */\nconst ridgeCache = new Map<string, Point[][]>();\nfunction cachedRidges(form: FingerprintForm, spacing: number): Point[][] {\n const key = `${JSON.stringify(form)}:${spacing}`;\n const hit = ridgeCache.get(key);\n if (hit) return hit;\n const traced = ridges(form, spacing);\n ridgeCache.set(key, traced);\n if (ridgeCache.size > 8) ridgeCache.delete(ridgeCache.keys().next().value!);\n return traced;\n}\n\n/**\n * Lays the items out along fingerprint ridges.\n *\n * `encoding` says which dimension arranges the marks and which drive colour, shape and lightness\n * (see `FingerprintEncoding`); left out, the first dimension does all but lightness. Items are\n * sorted by the arranging dimension's category order, then by each other encoded dimension's,\n * then by id, and laid along the ridges from the core outwards, so categories form runs. Mark\n * **area** is proportional to weight; positions only arrange.\n *\n * A pure function of its input: the same items, dimensions, encoding and options give the same\n * layout whatever order the items arrive in and whatever size it is drawn at. The ridges come from\n * `form` (see `formFromItems`). The items array is treated as immutable: work is cached against it.\n *\n * Above `maxMarks` items, consecutive items that agree on **every** encoded dimension share a mark\n * (`itemsPerMark` > 1), so a mark never misrepresents any item in it. Nothing is sampled away.\n */\nexport function layoutFingerprint(input: LayoutInput): FingerprintLayout {\n const { items, dimensions, encoding } = input;\n // Read field by field: spreading the input would let an explicit `undefined` (which callers pass\n // through when they have no value) replace a default, and an undefined cap means one mark per item.\n const seed = input.seed ?? DEFAULTS.seed;\n const maxMarks = positive(input.maxMarks) ?? DEFAULTS.maxMarks;\n const minRidges = positive(input.minRidges) ?? DEFAULTS.minRidges;\n const form = resolveForm(seed, input.form);\n const ready = prepare(items);\n const encoded = resolveEncoding(dimensions, encoding);\n const byId = new Map(dimensions.map((d) => [d.id, d]));\n // Every dimension the encoding uses, once, in legend order: colour, shape, lightness, arrange.\n const used = [...new Set([encoded.color, encoded.shape, encoded.lightness, encoded.arrange])]\n .filter((id): id is string => id !== null)\n .map((id) => byId.get(id)!);\n used.forEach(validateDimension);\n // Sort priority: arrangement first, then the other properties, so a run splits into sub-runs.\n const priority = [...new Set([encoded.arrange, encoded.color, encoded.shape, encoded.lightness])]\n .filter((id): id is string => id !== null)\n .map((id) => byId.get(id)!);\n\n const ranks = new Map(used.map((d) => [d.id, ranksIn(items, ready, d)]));\n const categoryAt = (d: FingerprintDimension, rank: number) =>\n d.categories[rank]?.id ?? UNKNOWN_CATEGORY_ID;\n\n const groups: FingerprintLegendGroup[] = used.map((d) => {\n const channels = ([\"color\", \"shape\", \"lightness\", \"arrange\"] as const).filter(\n (c) => encoded[c] === d.id,\n );\n const drives = (c: FingerprintChannel) => channels.includes(c);\n const entry = (\n id: string,\n label: string,\n i: number | null,\n color?: string,\n href?: string,\n ): FingerprintLegendEntry => ({\n id,\n label,\n color: drives(\"color\")\n ? i === null\n ? UNKNOWN_COLOR\n : (color ?? DEFAULT_PALETTE[i % DEFAULT_PALETTE.length]!)\n : \"currentColor\",\n shape: drives(\"shape\") ? i : 0,\n lightness:\n drives(\"lightness\") && i !== null\n ? d.categories.length > 1\n ? i / (d.categories.length - 1)\n : 1\n : null,\n href,\n unknown: i === null,\n count: 0,\n weight: 0,\n });\n return {\n dimensionId: d.id,\n label: d.label,\n channels,\n entries: [\n ...d.categories.map((c, i) => entry(c.id, c.label, i, c.color, c.href)),\n entry(UNKNOWN_CATEGORY_ID, d.unknownLabel ?? \"Unknown\", null),\n ],\n };\n });\n const groupOf = new Map(groups.map((g) => [g.dimensionId, g]));\n const entryOf = (dimensionId: string, categoryId: string) => {\n const d = byId.get(dimensionId)!;\n const rank = d.categories.findIndex((c) => c.id === categoryId);\n return groupOf.get(dimensionId)!.entries[rank === -1 ? d.categories.length : rank]!;\n };\n\n // Counts, per dimension, straight from the rank arrays.\n for (const d of used) {\n const entries = groupOf.get(d.id)!.entries;\n const r = ranks.get(d.id)!;\n for (let i = 0; i < items.length; i++) {\n const entry = entries[r[i]!]!;\n entry.count += 1;\n entry.weight += items[i]!.weight ?? 1;\n }\n }\n\n // Sort by the encoded categories in priority order, packed into one number per item as a\n // mixed-radix key, then by id rank. Comparing numbers in typed arrays is what keeps a view change\n // fast; comparing strings and objects took most of a second at 100,000 items in a browser.\n const n = items.length;\n const comboKeys = new Float64Array(n);\n const priorityRanks = priority.map((d) => ranks.get(d.id)!);\n const radix = priority.map((d) => d.categories.length + 1);\n for (let i = 0; i < n; i++) {\n let key = 0;\n for (let k = 0; k < priority.length; k++) key = key * radix[k]! + priorityRanks[k]![i]!;\n comboKeys[i] = key;\n }\n const order = Uint32Array.from(items.keys()).sort(\n (a, b) => comboKeys[a]! - comboKeys[b]! || ready.idRank[a]! - ready.idRank[b]!,\n );\n\n // Group consecutive items that agree on every encoded dimension into marks of at most `itemsPerMark`.\n const itemsPerMark = Math.max(1, Math.ceil(n / maxMarks));\n const marksOf: { key: string; categories: Record<string, string>; members: FingerprintItem[] }[] =\n [];\n for (let k = 0; k < n; k++) {\n const item = items[order[k]!]!;\n const last = marksOf.at(-1);\n if (\n last &&\n comboKeys[order[k]!] === comboKeys[order[k - 1]!] &&\n last.members.length < itemsPerMark\n ) {\n last.members.push(item);\n continue;\n }\n const categories = Object.fromEntries(\n used.map((d) => [d.id, categoryAt(d, ranks.get(d.id)![order[k]!]!)]),\n );\n marksOf.push({ key: used.map((d) => categories[d.id]).join(\"|\"), categories, members: [item] });\n }\n\n // Ridges are spaced so that, laid end to end, they hold every mark about one spacing apart. The\n // first trace estimates their length from the frame's area; one retrace corrects the spacing from\n // the length actually traced, since ridge endings and the outline make that hard to predict.\n const marks = Math.max(1, marksOf.length);\n const widest = FRAME.rx / minRidges;\n let spacing = Math.min(widest, Math.sqrt((Math.PI * FRAME.rx * FRAME.ry * 0.85) / marks));\n let lines = cachedRidges(form, spacing);\n if (marksOf.length > 0) {\n const traced = lines.reduce((sum, line) => sum + polylineLength(line), 0);\n const corrected = Math.min(widest, Math.sqrt((traced * spacing) / marks));\n if (Math.abs(corrected - spacing) / spacing > 0.03) {\n spacing = corrected;\n lines = cachedRidges(form, spacing);\n }\n }\n const { slots, pitch } = slotsAlong(lines, marksOf.length);\n const footprint = Math.min(pitch, spacing) * MARK_FILL;\n\n const weights = marksOf.map((mark) =>\n mark.members.reduce((sum, item) => sum + (item.weight ?? 1), 0),\n );\n const heaviest = Math.max(0, ...weights);\n const perKey = new Map<string, number>();\n const nodes: FingerprintNode[] = marksOf.map((mark, i) => {\n const n = perKey.get(mark.key) ?? 0;\n perKey.set(mark.key, n + 1);\n const slot = slots[i]!;\n const property = (channel: \"color\" | \"shape\" | \"lightness\") => {\n const id = encoded[channel];\n return id === null ? null : entryOf(id, mark.categories[id]!);\n };\n return {\n id: itemsPerMark === 1 ? mark.members[0]!.id : `${mark.key}#${n}`,\n itemIds: mark.members.map((item) => item.id),\n categories: mark.categories,\n x: slot.x,\n y: slot.y,\n rotation: slot.rotation,\n size: footprint * Math.sqrt(weights[i]! / heaviest),\n weight: weights[i]!,\n color: property(\"color\")?.color ?? \"currentColor\",\n shape: encoded.shape === null ? 0 : property(\"shape\")!.shape,\n lightness: property(\"lightness\")?.lightness ?? null,\n };\n });\n\n // The unknown entry is listed only when something is unknown.\n for (const group of groups)\n group.entries = group.entries.filter((e) => !e.unknown || e.count > 0);\n const warnings: string[] = [];\n for (const channel of [\"color\", \"shape\"] as const) {\n const id = encoded[channel];\n if (id === null) continue;\n const shown = groupOf.get(id)!.entries.filter((e) => !e.unknown && e.count > 0).length;\n if (shown > DISTINCT[channel])\n warnings.push(\n `${channel === \"color\" ? \"Colour\" : \"Shape\"} shows ${shown} categories of \"${byId.get(id)!.label}\"; beyond ${DISTINCT[channel]} they repeat, so only the legend tells them apart.`,\n );\n }\n\n return {\n width: FRAME.width,\n height: FRAME.height,\n encoding: encoded,\n nodes,\n ridges: lines,\n form,\n ridgeSpacing: spacing,\n legends: groups,\n warnings,\n itemsPerMark,\n itemCount: items.length,\n };\n}\n\n/** A layout with nothing in it: what `useFingerprint` shows while its data is invalid. */\nexport function emptyLayout(): FingerprintLayout {\n return {\n width: FRAME.width,\n height: FRAME.height,\n encoding: { arrange: \"\", color: null, shape: null, lightness: null },\n nodes: [],\n ridges: [],\n form: resolveForm(DEFAULTS.seed),\n ridgeSpacing: FRAME.rx / DEFAULTS.minRidges,\n legends: [],\n warnings: [],\n itemsPerMark: 1,\n itemCount: 0,\n };\n}\n",
|
|
11
|
-
"import type { FingerprintNode, RidgeStyle } from \"./types\";\n\n/**\n * The opacity that shows a mark's lightness: 0.3 for the first category of an ordered dimension,\n * full strength for the last, and full strength when lightness is not encoded or unknown. Renderers\n * use it so lightness reads the same whatever they draw.\n */\nexport const markOpacity = (node: Pick<FingerprintNode, \"lightness\">) =>\n node.lightness === null ? 1 : 0.3 + 0.7 * node.lightness;\n\n/**\n * The ridges' opacity for a collection of `itemCount` items under `style`: constant for `always`,\n * zero for `never`, and for `auto` (the default) full until `fadeFrom` items, fading to nothing at\n * `fadeTo`.\n */\nexport function ridgeOpacity(itemCount: number, style: RidgeStyle = {}): number {\n const { show = \"auto\", fadeFrom = 60, fadeTo = 160, opacity = 0.22 } = style;\n if (show === \"never\") return 0;\n if (show === \"always\" || itemCount <= fadeFrom) return opacity;\n if (itemCount >= fadeTo) return 0;\n return opacity * (1 - (itemCount - fadeFrom) / (fadeTo - fadeFrom));\n}\n",
|
|
12
|
-
"import type { FingerprintAnnotation, FingerprintLayout } from \"./types\";\n\n/** Horizontal room either side of the print for callout labels, in layout units. */\nexport const CALLOUT_GUTTER = 190;\n/** How far apart two callouts' anchors stay: a numbered marker is 22 units across. */\nconst ANCHOR_CLEARANCE = 28;\n/** Vertical room one callout label takes, in layout units. */\nexport const CALLOUT_HEIGHT = 96;\n\n/** Where `placeCallouts` puts one annotation, in the layout's frame. */\nexport interface PlacedCallout {\n /** The annotation placed. */\n annotation: FingerprintAnnotation;\n /** The mark the leader points at: the named categories' mark nearest their centre. */\n anchor: { x: number; y: number };\n /** Which gutter the label sits in: the side of the print its anchor is on. */\n side: \"left\" | \"right\";\n /** Top of the label, in layout units. */\n labelY: number;\n /** 1-based, in reading order: left side top to bottom, then right. */\n number: number;\n}\n\n/**\n * Where each callout goes. Its anchor is a real mark of the categories it names, so the leader\n * never points at empty paper; the label sits in the gutter on the anchor's side, as level with it\n * as the other labels allow. Annotations whose categories have no marks are left out.\n */\nexport function placeCallouts(\n layout: FingerprintLayout,\n annotations: readonly FingerprintAnnotation[],\n): PlacedCallout[] {\n // Anchors already taken: a later callout's anchor keeps this far from them, so numbered markers\n // never hide each other.\n const taken: { x: number; y: number }[] = [];\n const anchored = annotations.flatMap((annotation) => {\n const wanted = new Set(annotation.categoryIds);\n const dimensionId = annotation.dimensionId ?? layout.encoding.arrange;\n const marks = layout.nodes.filter((node) => wanted.has(node.categories[dimensionId] ?? \"\"));\n if (marks.length === 0) return [];\n const cx = marks.reduce((sum, node) => sum + node.x, 0) / marks.length;\n const cy = marks.reduce((sum, node) => sum + node.y, 0) / marks.length;\n const byCloseness = [...marks].sort(\n (a, b) => Math.hypot(a.x - cx, a.y - cy) - Math.hypot(b.x - cx, b.y - cy),\n );\n const clear = (node: { x: number; y: number }) =>\n taken.every((t) => Math.hypot(node.x - t.x, node.y - t.y) >= ANCHOR_CLEARANCE);\n const anchor = byCloseness.find(clear) ?? byCloseness[0]!;\n taken.push(anchor);\n const side: PlacedCallout[\"side\"] = anchor.x < layout.width / 2 ? \"left\" : \"right\";\n return [{ annotation, anchor: { x: anchor.x, y: anchor.y }, side }];\n });\n\n const placed: PlacedCallout[] = [];\n for (const side of [\"left\", \"right\"] as const) {\n const column = anchored.filter((c) => c.side === side).sort((a, b) => a.anchor.y - b.anchor.y);\n // Level with the anchor where possible, pushed down to clear the label above, then the whole\n // column pulled back up if the last label would run off the bottom.\n const tops: number[] = [];\n for (const callout of column)\n tops.push(\n Math.max(\n callout.anchor.y - CALLOUT_HEIGHT / 3,\n (tops.at(-1) ?? -Infinity) + CALLOUT_HEIGHT,\n ),\n );\n const overflow = Math.max(0, (tops.at(-1) ?? 0) + CALLOUT_HEIGHT - layout.height);\n column.forEach((callout, i) =>\n placed.push({ ...callout, labelY: Math.max(0, tops[i]! - overflow), number: 0 }),\n );\n }\n return placed.map((callout, i) => ({ ...callout, number: i + 1 }));\n}\n",
|
|
13
|
-
"import type { FingerprintChannel, FingerprintLayout } from \"./types\";\n\nconst PROPERTY: Record<Exclude<FingerprintChannel, \"arrange\">, string> = {\n color: \"colour\",\n shape: \"shape\",\n lightness: \"lightness\",\n};\n\n/**\n * A plain-language summary of what a layout shows, for a screen reader or a caption: the total, the\n * largest categories of the arranging dimension with their counts, which dimension each visual\n * property shows when they differ, and how many items each mark stands for. Every number in it is\n * one the legends also show.\n */\nexport function describeLayout(layout: FingerprintLayout, unit = \"items\"): string {\n if (layout.itemCount === 0) return `No ${unit} yet.`;\n const arrange = layout.legends.find((group) => group.dimensionId === layout.encoding.arrange)!;\n const ranked = arrange.entries\n .filter((entry) => entry.count > 0)\n .sort((a, b) => b.count - a.count);\n const named = ranked\n .slice(0, 3)\n .map((entry) => `${entry.label} ${entry.count.toLocaleString(\"en-GB\")}`);\n const rest = ranked.length - named.length;\n const parts = [\n `${layout.itemCount.toLocaleString(\"en-GB\")} ${unit} by ${arrange.label.toLowerCase()}: ${named.join(\", \")}${\n rest > 0 ? `, and ${rest} more ${rest === 1 ? \"category\" : \"categories\"}` : \"\"\n }.`,\n ];\n if (layout.legends.length > 1) {\n const shown = layout.legends.flatMap((group) =>\n group.channels\n .filter(\n (channel): channel is Exclude<FingerprintChannel, \"arrange\"> => channel !== \"arrange\",\n )\n .map((channel) =>\n channel === \"lightness\"\n ? `lightness shows ${group.label.toLowerCase()} (fainter is earlier)`\n : `${PROPERTY[channel]} shows ${group.label.toLowerCase()}`,\n ),\n );\n if (shown.length) parts.push(`${shown.join(\", \").replace(/^./, (c) => c.toUpperCase())}.`);\n }\n if (layout.itemsPerMark > 1)\n parts.push(`Each mark stands for up to ${layout.itemsPerMark} ${unit}.`);\n return parts.join(\" \");\n}\n"
|
|
14
|
-
],
|
|
15
|
-
"mappings": "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",
|
|
16
|
-
"debugId": "C8FF8090BDB0F57164756E2164756E21",
|
|
17
|
-
"names": []
|
|
18
|
-
}
|