Unit
Countable items as individually matched circles, in grid, force, unit-bar and beeswarm layouts.
import { unit } from 'visdelta/unit';unit() turns rows, or counts, into individually matched circles.
import { unit } from 'visdelta/unit';
import { transition } from 'visdelta/transition';
import 'visdelta/style.css';
const species = ['setosa', 'versicolor', 'virginica'];
const flowers = Array.from({ length: 45 }, (_, index) => ({
id: `flower-${index + 1}`,
species: species[index % 3],
}));
const from = unit(flowers)
.key('id')
.color('species')
.size(7)
.layout('grid', { columns: 9 });
// The same keyed units regroup into one stack per species.
const to = from.group('species').layout('bar', { columns: 5 });
const change = await transition(from, to, { target: '#chart' });
change.play({ duration: 800 }); // or change.progress(0.5)
Methods
| Method | Meaning |
|---|---|
.value(field, { unitValue?, maxUnits? }) | Expand a count field into units |
.group(field) | Declare categorical membership, without choosing colour |
.layout('grid' | 'force' | 'bar' | 'beeswarm', options?) | Arrange the same units |
.columns(number) | Set grid or unit-bar columns |
.size(number) | Set constant mark size (requested circle radius in pixels) |
.radius(number) | Compatibility alias for .size(number) |
.x(field, options?), .y(field, options?) | Declare force targets; x also positions a beeswarm |
The available layout names are exported as UNIT_LAYOUTS.
unit(rows)
.key("flowerId")
.size(6)
.layout("beeswarm", { x: "petalLength" })
.color("species");Layout options accept x and y as field names or full channel configurations,
and columns. Layout type, columns and x/y bindings form one complete planner
operation. The existing horizontal-move-then-fall animation remains inside it,
with the usual per-mark stagger. Size is an independent Appearance channel.
Compact layout options and chained .x() / .y() calls are equally supported
forms of the same declaration, not compatibility syntax:
unit(rows).layout("beeswarm", { x: "petalLength" });
unit(rows).layout("beeswarm").x("petalLength");Both write one position binding. The last explicit assignment wins, including across the two forms, and replaces the complete channel and its options:
unit(rows)
.key("flowerId")
.size(6)
.layout("beeswarm", { x: "petalLength" })
.x("petalWidth") // replaces the earlier x binding and its options
.color("species");Reversing those x assignments makes petalLength win. Omitted layout options
retain earlier settings: .x("petalWidth").layout("beeswarm") keeps
petalWidth. Equivalent final states produce the same difference and transition
route; normal overwrites do not issue warnings. .radius(6) and layout { radius: 6 } remain aliases
for .size(6) and obey the same last-assignment rule.
Currently Unit renders circles: size is their requested radius in pixels,
which layout fitting can reduce when space is limited. Field-driven size,
rectangle marks and glyph marks are not yet supported.
Data, quantity, grouping, size, color and attention remain independent operations.
One operation can still produce several semantic categories in delta().
Layouts
gridarranges units in rows ofcolumns.barcentres one stack of units on each category tick. It requires a.group()field.beeswarmplaces units without overlap along x. It requires an x field.forcerecords a D3 simulation's ticks as a reversible physical path and maps progress to its cumulative movement. With no positional channel, units are attracted to the plot centre. An explicit x or y channel supplies a per-unitforceXorforceYtarget, so a categorical field forms labelled attractor bands.
Layout changes preserve matching keys. Remaining unmatched units use a global minimum-travel assignment, and layout changes use a short, bounded per-unit stagger unless you override it.
Quantity per circle
unitValue sets how much each circle represents and defaults to 1. A positive
remainder gets one more circle, so 51 sites at unitValue: 10 render 6 circles.
Label the chart or legend with the unit value you choose. maxUnits is a
whole-chart safety cap.
Changing unitValue is a Grain change: a coarse circle is the parent of the
finer circles from the same row, and the transition splits or merges them around
the parent's centroid.
import { unit } from 'visdelta/unit';
import { transition } from 'visdelta/transition';
import 'visdelta/style.css';
const visits = [
{ clinic: 'North', patients: 24 },
{ clinic: 'South', patients: 16 },
{ clinic: 'East', patients: 12 },
];
const detailed = unit(visits)
.value('patients', { unitValue: 1 })
.key('clinic')
.group('clinic')
.size(7)
.layout('bar', { columns: 4 })
.color('clinic');
// Each coarse circle (four patients) splits into the circles it represents.
const from = detailed.value('patients', { unitValue: 4 });
const to = detailed;
const change = await transition(from, to, { target: '#chart' });
change.play({ duration: 800 }); // or change.progress(0.5)
Try it
Edit 150 Iris flowers as they leave a grid for a beeswarm in the playground.