Skip to content

Large collections and motion

At most about maxMarks marks are drawn (600 by default). Above that, neighbouring items that agree on every encoded dimension share a mark, layout.itemsPerMark says how many, and the description says so too. Nothing is sampled away: every item is in exactly one mark, and a mark never mixes categories.

Try 30,000 books below. The layout stays fast, and the print looks like the 600-book one, because it is drawn with the same number of marks.

Running the code below: large-collections.tsx
large-collections.tsx
import { useMemo, useState } from "react";
import type { RidgeStyle } from "@data-profile/fingerprint";
import {
FingerprintCanvas,
FingerprintViewSwitch,
useFingerprint,
type FingerprintView,
} from "@data-profile/fingerprint/react";
import "@data-profile/fingerprint/styles.css";
import { dimensions, library } from "./library";
const views: FingerprintView[] = [
{ id: "genre", label: "Genre", encoding: { arrange: "genre" } },
{ id: "decade", label: "Published", encoding: { arrange: "decade" } },
];
const counts = [12, 120, 600, 3000, 30000];
export default function LargeCollections() {
const [count, setCount] = useState(600);
const [maxMarks, setMaxMarks] = useState(600);
const [ridges, setRidges] = useState<NonNullable<RidgeStyle["show"]>>("auto");
const items = useMemo(() => library(count), [count]);
const fp = useFingerprint({ items, dimensions, views, maxMarks, unit: "books" });
return (
<div>
<div className="demo-toolbar">
<label>
Books{" "}
<select value={count} onChange={(event) => setCount(Number(event.target.value))}>
{counts.map((value) => (
<option key={value} value={value}>
{value.toLocaleString()}
</option>
))}
</select>
</label>
<label>
maxMarks{" "}
<select value={maxMarks} onChange={(event) => setMaxMarks(Number(event.target.value))}>
{[200, 600, 1500].map((value) => (
<option key={value}>{value}</option>
))}
</select>
</label>
<label>
Ridges{" "}
<select
value={ridges}
onChange={(event) => setRidges(event.target.value as typeof ridges)}
>
{["auto", "always", "never"].map((value) => (
<option key={value}>{value}</option>
))}
</select>
</label>
<FingerprintViewSwitch views={fp.views} value={fp.viewId} onChange={fp.setViewId} />
</div>
<FingerprintCanvas {...fp.canvasProps} title="A library" ridges={{ show: ridges }} />
<p className="demo-note">
{fp.layout.nodes.length.toLocaleString()} marks for {fp.layout.itemCount.toLocaleString()}{" "}
books
{fp.layout.itemsPerMark > 1 && `: one mark per ${fp.layout.itemsPerMark} books`}.
</p>
</div>
);
}

Marks glide to their new places between views while there are at most motion.glide of them (800) and recolour gently up to motion.fade (400). Above those they change at once, because SVG cannot animate more marks smoothly. Under prefers-reduced-motion nothing animates. Pass motion={{ glide: 0, fade: 0 }} to turn it off.

The faint ridges show while a collection is too small for its marks alone to read as a fingerprint (show: "auto", fading from 60 items to 160), and ridges={{ show: "always" }} or "never" overrides that. Their opacity, color and width are yours to set too.