Documentation

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.

Preparing transition
0.00
unit-layout.ts
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

MethodMeaning
.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

  • grid arranges units in rows of columns.
  • bar centres one stack of units on each category tick. It requires a .group() field.
  • beeswarm places units without overlap along x. It requires an x field.
  • force records 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-unit forceX or forceY target, 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.

Preparing transition
0.00
unit-value.ts
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.

On this page