Renderers
A renderer draws one mark. The package comes with two: shapesRenderer, the default, with five
shapes, and dotsRenderer, where every mark is a dot and shape carries nothing. Pass the same
renderer to the legends, so their swatches match the marks.
built-in-renderers.tsximport { useMemo, useState } from "react";import { dotsRenderer, FingerprintCanvas, FingerprintLegend, shapesRenderer, useFingerprint,} from "@data-profile/fingerprint/react";import "@data-profile/fingerprint/styles.css";import { dimensions, library } from "./library";
const renderers = { shapes: shapesRenderer, dots: dotsRenderer };
export default function BuiltInRenderers() { const [name, setName] = useState<keyof typeof renderers>("shapes"); const renderer = renderers[name]; const items = useMemo(() => library(200), []); const fp = useFingerprint({ items, dimensions, // Colour by genre, shape by reading state. views: [{ id: "v", label: "Genre and state", encoding: { arrange: "genre", shape: "state" } }], }); return ( <div> <div role="group" aria-label="Renderer" className="demo-toolbar"> {(["shapes", "dots"] as const).map((option) => ( <button key={option} aria-pressed={name === option} onClick={() => setName(option)}> {option}Renderer </button> ))} </div> <div className="demo-split"> <FingerprintCanvas {...fp.canvasProps} title="A library" renderer={renderer} /> <div> {fp.layout.legends.map((group) => ( <FingerprintLegend key={group.dimensionId} {...fp.legendProps(group)} renderer={renderer} /> ))} </div> </div> </div> );}Your own marks
Section titled “Your own marks”A renderer’s mark draws one mark, centred on the origin, node.size across, with the ridge
running along the x axis; the canvas positions and rotates it. Each mark arrives with its
properties resolved:
node. |
Holds |
|---|---|
color |
The colour dimension’s colour, or currentColor when nothing drives colour |
shape |
The index of its category in the shape dimension (0, 1, 2, …); 0 when nothing drives shape; null when unknown |
lightness |
0 (faintest) to 1 along the lightness dimension, or null. markOpacity(node) turns it into an opacity |
categories |
Its category in every encoded dimension, by dimension id |
size, weight, itemIds |
Its footprint, summed weight and the items it stands for |
Here every book is a spine, and the reading state is a band’s position along it:
custom-renderer.tsximport { useMemo } from "react";import { markOpacity } from "@data-profile/fingerprint";import { FingerprintCanvas, FingerprintLegend, useFingerprint, type FingerprintRenderer,} from "@data-profile/fingerprint/react";import "@data-profile/fingerprint/styles.css";import { dimensions, library } from "./library";
// A book spine: a rounded bar along the ridge, with a band whose position shows the reading// state. The mark is drawn centred on the origin, `node.size` across; the canvas positions and// rotates it. A renderer must depend on its props alone, since marks are memoised.const spines: FingerprintRenderer = { name: "spines", mark: ({ node }) => { const length = node.size; const width = node.size * 0.42; const band = node.shape === null ? null : (node.shape - 1) * length * 0.28; return ( <g opacity={markOpacity(node)}> <rect x={-length / 2} y={-width / 2} width={length} height={width} rx={width * 0.25} fill={node.color} /> {band !== null && ( <rect x={band - length * 0.06} y={-width / 2} width={length * 0.12} height={width} fill="white" opacity={0.75} /> )} </g> ); },};
export default function CustomRenderer() { const items = useMemo(() => library(260), []); const fp = useFingerprint({ items, dimensions, views: [ { id: "v", label: "Combined", encoding: { arrange: "genre", shape: "state", lightness: "decade" }, }, ], }); return ( <div className="demo-split"> <FingerprintCanvas {...fp.canvasProps} title="A library of spines" renderer={spines} /> <div> {fp.layout.legends.map((group) => ( <FingerprintLegend key={group.dimensionId} {...fp.legendProps(group)} renderer={spines} /> ))} </div> </div> );}A renderer can also return SVG defs once per drawing (gradients, patterns), receiving an
idPrefix to keep its ids unique on the page. Marks are memoised, so a renderer must depend on its
props alone.