@tohirr/dotix
v0.2.0
Published
dot-grid data visualization for modern product UI — discrete space, continuous motion
Downloads
457
Maintainers
Readme
dotix
dot-grid data visualization for modern product UI.
The law: discrete space, continuous motion. Data only exists at grid positions — a value is a count of filled dots on a visible lattice, and the empty grid is part of the design. But motion between states is smooth and springy: dots pour in, cascade, and lift. Quantized space, fluid time.
Arrival is an event, change is a breath. A widget's initial pour is an ignition — dots flash bright and settle as the wave sweeps the board. Data changes within the session never flash: the accent core grows inside the resting cell, or shrinks away when a datapoint is lost.
Not a d3 competitor. An opinionated small vocabulary: data in, dots out.
Install & use
npm install @tohirr/dotiximport "@tohirr/dotix/theme.css"; // the design tokens — override to theme
import { dotArea } from "@tohirr/dotix";
const cleanup = dotArea(el, data, { format: (v) => "$" + v.toLocaleString() });
cleanup.update(nextData); // animates to the new state
cleanup(); // unmountsEvery widget shares that contract: dotArea, waffle, gridBars,
segMeter, dotRing, dotCalendar, dotMap, hexShare. Vanilla ESM,
zero dependencies, no build step. Theming is CSS custom properties on
:root (or any container) — see dotix/theme.css for the full token
surface: accent hue, dot roundness, lattice gap, grid presence, the
categorical set, and the sequential ramp.
Headless: the chart is the dot matrix. Widgets render the lattice, its motion, and lattice-coupled interaction (tooltips, axes) — numbers, labels, legends, and buttons are yours to build around them. The hooks:
import { waffle, dotRing, countTo } from "@tohirr/dotix";
// sync your own number to the choreography
waffle(el, { value: 68 }, {
onChange: ({ value, from, duration }) =>
countTo(numberEl, from, value, duration, (v) => v + "%"),
});
// wire your own legend to the chart, both directions
const ring = dotRing(el, series, { onHover: (i) => dimOtherRows(i) });
legendRow.addEventListener("pointerenter", () => ring.highlight(i));For ≥2-series charts, always render a legend or labels beside the chart — identity should never be color alone.
The widget series
Each widget ships first as a polished, animated, shareable demo — one signature motion each — and the library API gets extracted once the shared internals (grid model, fill order, stagger system, tooltip snapping) have emerged from real widgets.
Planned:
- [x]
dot-area— area chart as filled dots under a visible empty grid; pours in column by column, hover lifts a column, tooltip snaps to grid - [x]
grid-bars— bar chart as stacks of square bricks; one brick = one unit, the top row shows the remainder honestly, hover lifts a bar - [x]
waffle— a percentage as a 10×10 dot square; ignites along the fill order while the big number counts in lockstep — the number is the dot count - [x]
dot-ring— a donut chart as concentric rings of dots; segment borders touch, the hole holds the total (hover morphs it to the hovered share), boot draws the ring clockwise - [x]
hex-share— proportions as an organic cluster of interlocking hexagons, one hex ≈ one unit; a pie chart with an organic body — series own angular sectors, share changes sweep the boundaries around the clock, boot ignites from the center - [x]
seg-meter— segmented stripe progress meter; powers up in a fast sweep, updates tick from the old waterline like a gauge - [x]
dot-calendar— the activity heatmap; magnitude as depth on the sequential ramp, boot develops like a photograph, no-activity days are the resting lattice - [x]
dot-map— the classic dotted world map; the land is the resting lattice, data points ignite their nearest land cell (big values glow a halo), boot sweeps west→east like a sunrise
Dev
npm install
npm run devindex.html is the widget gallery. The package lives in src/lib/
(grid-core.js, theme.css, widgets/, index.js); the gallery site
lives in src/site/. Each widget is a module exporting
mount(el, data, opts) → cleanup. Shared internals — the dot cell, the
boot/live materials, the choreography applier, the count-up — live in
grid-core.js; widgets own only layout, scales, and interaction. If the
widget supports live data changes, cleanup.update(nextData) animates to the
new state — the gallery renders action buttons (e.g. "shuffle data") for any
widget that has it.
