Pipeline stages
Can a shape replace the chart type it imitates? This is a funnel chart drawn as countable dots. A tapered ribbon shows you the proportion at each stage; this shows the proportion and how many deals are actually in it.
A shape is a plain callable, so positions: heart needs no registration step. The whole kit is 48 KB raw and about 14 KB gzipped, tree-shaken per shape: importing one costs roughly 4 KB.
npm install apexcharts@^6.10.0import ApexCharts from 'apexcharts'
import { heart } from 'apexcharts/unit-shapes'
new ApexCharts(el, {
chart: { type: 'unit' },
series: [57600, 16800, 4200, 3400],
labels: ['Repeat donors', 'First-time', 'Workplace drives', 'Emergency call-ups'],
plotOptions: {
unit: { layout: 'custom', positions: heart, unitValue: 100 },
},
}).render()The catalog
Move the dot count and every plate repacks. Nothing here is an image: each plate is preview() run against the same shape the chart would use, so this page cannot disagree with the code.
39 of 39 shapes200 dots each4 series
Packing 39 shapes…
Fill an outline. The bulk of the catalog, and the default mental model.
A thickened centreline, for a thing with no interior. A dotted checkmark still reads as a checkmark, which is how a stroke degrades where a thin silhouette feature would vanish.
No outline at all, positions from maths: latitude rings, concentric bands, tiers.
outlined() gives all 29 silhouettes a hollow twin for no new artwork, since a stroked closed path is a ring. The fill order decides where each category sits, which is why battery fills like a charge meter.
outlined(heart) // trace the outline instead of filling it
heart.with({ order: 'cols' }) // where each series band lands
glyphs('1,024') // the number, drawn in that many dots
preview(heart, { series }) // -> an SVG string, no chart and no DOMglyphs()
glyphs('1,024') returns a shape that packs 1,024 dots into the figure 1,024. Type a number and watch the count and the picture stay the same fact.
1,024 dots, arranged as the figure they count.
In practice
Each one answers something worth knowing before you put a shape in front of real data: whether it can replace the chart type it imitates, whether ordered categories survive, whether a shape with no outline behaves like one that has. The line under each chart names the option doing the work.
Can a shape replace the chart type it imitates? This is a funnel chart drawn as countable dots. A tapered ribbon shows you the proportion at each stage; this shows the proportion and how many deals are actually in it.
What happens to a small category? Five ordered ratings in a five-pointed shape. The 1-star tail is 300 reviews out of 6,200, and it still gets visible dots of its own instead of thinning into nothing.
How do you colour a crowd this big? 82,000 donations at one dot per hundred, in four separate hues rather than four shades of red. At this dot size a light-to-dark ramp is almost impossible to read back.
Does a shape have to be an outline? Three of them are not. This globe computes its latitude rings from maths and has no path at all, and using it changes nothing about how you call it.
What about a thing with no interior? A heartbeat trace is a line, not an area. Ordering the fill by column runs the bands along the trace rather than slicing across it, so the buckets read left to right, fastest to slowest.
Where does each category end up inside the shape? The fill order decides, and a shape can have its own opinion about it. This one fills left to right like a charge meter, because the shape declares that, not the chart.
Another 16, each a full page with the runnable source behind it.
Your shape
The shape you have in mind is probably not one of the 39. It does not have to be: plotOptions.unit.positions is a function from the marks and the plot rectangle to places, and everything in the kit is one of those. Nothing on this page is built in.
Route 1 · you have an outline
shapeFrom(path) takes SVG path data and returns a shape. The packer does the rest: rows across the outline, each row cut into the spans that fall inside it, and the gap between dots bisected until the spans hold exactly the number of marks the data asks for.
import { shapeFrom } from 'apexcharts/unit-shapes'
const paw = shapeFrom(PAW_PATH, {
name: 'paw',
// The count below which the toes stop reading as toes. Ask for
// fewer and the chart warns rather than drawing mush.
minUnits: 180,
})
plotOptions: { unit: { layout: 'custom', positions: paw } }1 dot = 5 adoptions
Route 2 · your thing is a line
A ridge, a route or a signal has no interior, and drawing both sides of one by hand means mitring every corner. strokeFrom(path, { width }) takes the centreline instead, so the outline is six points long. A stroke also degrades kindly: run it short of dots and it becomes a dotted line, which still reads as the same line.
import { strokeFrom } from 'apexcharts/unit-shapes'
const ridge = strokeFrom('M 6 76 L 24 44 L 40 60 …', {
name: 'ridge',
width: 12,
// Bands run along the walk rather than slicing it.
order: 'cols',
})1 dot = 24 visits · bands ordered along the walk
Route 3 · you have a rule, not a picture
No outline, and no import from the kit at all: a plain function returning { id, x, y } per mark is already a valid layout. Because the marks arrive in series order and the spiral walks outward, the tiers land as rings — worth knowing when you write your own, since the order the marks come in is the order your rule sees them.
// Imports nothing. positions is just a function.
function sunflower(objects, rect) {
const cx = rect.x + rect.width / 2
const cy = rect.y + rect.height / 2
const span = Math.min(rect.width, rect.height) / 2 - 6
const golden = Math.PI * (3 - Math.sqrt(5))
return objects.map((o, i) => {
const t = Math.sqrt((i + 0.5) / objects.length)
return {
id: o.id,
x: cx + Math.cos(i * golden) * span * t,
y: cy + Math.sin(i * golden) * span * t,
}
})
}1 dot = 1 wristband
A companion module to ApexCharts that ships 39 shapes a unit chart can pack its dots into. Each shape is a plain callable handed to plotOptions.unit.positions, so there is no registration step and nothing in the chart had to learn about shapes.
Neither. A shape is a function of the marks and the plot rectangle. Rows are cut across the outline, each row split into the spans that fall inside it, and the gap between dots bisected until the spans hold exactly the number of marks the data asks for. Change the dot count and the shape repacks, which is why one outline serves 40 dots in a sparkline and 3,000 in a poster.
About 48 KB raw and 14 KB gzipped for the whole catalog, and it is tree-shaken per shape: importing one costs roughly 4 KB gzipped. Importing the catalog is for galleries and tests, not for charts.
Yes, and that is the expected case. shapeFrom(path) packs your own SVG outline with the same engine the 39 built-ins use, strokeFrom(path, { width }) takes a centreline for a thing with no interior, and a plain function returning { id, x, y } per mark is already a valid layout and imports nothing.
Every outline in the kit was drawn in the ApexCharts repository. No third-party path is admitted, permissively licensed or not, because an outline ships verbatim inside the bundle and a copied path would carry its licence notice into every consumer build. Brand marks are excluded outright, and a test enforces it.
The unit chart is a Premium chart type, so it renders in trial mode with an APEXCHARTS watermark until a valid license key is set. The shape kit itself adds no further requirement. See the pricing page for current terms.
The unit chart is a Premium chart type. Everything on this page runs on the same one import.