apexcharts-skill 2.2.0 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,6 +7,8 @@
7
7
  - **Range Bar** (`'rangeBar'`) — Bars with start/end values (used for timelines, Gantt charts)
8
8
  - **Funnel** (`'funnel'`, **new in v6**): Stage-by-stage drop-off chart
9
9
  - **Pyramid** (`'pyramid'`, **new in v6**): A funnel with the wide base at the bottom
10
+ - **Waterfall** (`'waterfall'`, **new in v7.1**): Deltas that accumulate into a running total
11
+ - **Dumbbell** (`'dumbbell'`, **new in v7.1**): Two or more measures per category, joined by a connector
10
12
 
11
13
  ## Tree-Shakeable Import
12
14
 
@@ -14,10 +16,16 @@
14
16
  import ApexCharts from 'apexcharts/bar'
15
17
  // Registers: bar, column, rangeBar
16
18
  // Aliases: apexcharts/column, apexcharts/rangeBar
17
- // Also covers funnel + pyramid (they normalize to the bar engine in v6)
19
+ // Also covers funnel + pyramid (they normalize to the bar engine)
20
+
21
+ // waterfall and dumbbell (v7.1) each add a feature module on top of the bar engine:
22
+ import ApexCharts from 'apexcharts/waterfall' // == apexcharts/bar + features/waterfall
23
+ import ApexCharts from 'apexcharts/dumbbell' // == apexcharts/bar + features/dumbbell
18
24
  ```
19
25
 
20
- **v6 note:** `funnel` and `pyramid` are first-class `chart.type` aliases that render through the bar engine. Use them directly as `chart.type`; you do **not** need `plotOptions.bar.isFunnel`. `plotOptions.funnel` carries the funnel-specific shape options.
26
+ Both are **Tier 1**, so the default `apexcharts` bundle already has them and no import is needed there.
27
+
28
+ **Alias note:** `funnel` and `pyramid` are first-class `chart.type` aliases that render through the bar engine. Use them directly as `chart.type`; you do **not** need `plotOptions.bar.isFunnel`. `plotOptions.funnel` carries the funnel-specific shape options.
21
29
 
22
30
  **Histogram (v6.9):** `chart.type: 'histogram'` also renders through the bar pathway, but its series carry raw observations that the chart bins itself, and the binning needs the stats feature (`apexcharts/histogram` entry, or `apexcharts/features/stats` alongside `apexcharts/bar`). It is documented with the other statistical charts in `references/financial-charts.md`.
23
31
 
@@ -184,6 +192,105 @@ Same shape as funnel, but order values **smallest → largest** so the wide base
184
192
  }
185
193
  ```
186
194
 
195
+ ### Waterfall (v7.1)
196
+
197
+ `chart.type: 'waterfall'` **accumulates the running total for you**. The series holds the **deltas**; a row flagged `isSubtotal` or `isTotal` draws the running total from zero at that position and carries **no `y` of its own**. Connectors bridge each bar's finish to the next one's start, and rising, falling and total bars take their own colors.
198
+
199
+ ```js
200
+ {
201
+ chart: { type: 'waterfall', height: 350 },
202
+ series: [{
203
+ name: 'Operating income',
204
+ data: [
205
+ { x: 'Net revenue', y: 8786000 },
206
+ { x: 'Cost of sales', y: -2786000 },
207
+ { x: 'Gross profit', isSubtotal: true }, // no y: measured for you
208
+ { x: 'Operating expenses', y: -1786000 },
209
+ { x: 'Operating income', isTotal: true }, // no y: sum from zero
210
+ ],
211
+ }],
212
+ }
213
+ ```
214
+
215
+ **Row shape** (`ApexWaterfallPoint`, every field optional so a series can mix the two kinds freely):
216
+
217
+ | Field | Meaning |
218
+ |---|---|
219
+ | `x` | Category label, timestamp, or `Date` |
220
+ | `y` | The **step**: the signed amount it moves the running total by. Omit on a subtotal / total row. |
221
+ | `isSubtotal` | Sum of the steps since the previous subtotal / total bar |
222
+ | `isTotal` | Sum of every step from zero |
223
+ | `fillColor` | Overrides the semantic fill for this row |
224
+
225
+ **`plotOptions.waterfall`:**
226
+
227
+ ```js
228
+ plotOptions: {
229
+ waterfall: {
230
+ colors: {
231
+ positive: '#00A86F', // a step that raises the running total
232
+ negative: '#FF4560', // a step that lowers it
233
+ subtotal: undefined, // defaults to the series color from the active palette
234
+ total: undefined, // same default, so running totals stay distinct from steps
235
+ },
236
+ connectors: {
237
+ show: true, // without them the floating columns read as unrelated bars
238
+ color: undefined, // defaults to grid.borderColor, so it is theme-aware
239
+ strokeWidth: 1,
240
+ strokeDashArray: 3,
241
+ },
242
+ },
243
+ }
244
+ ```
245
+
246
+ A datum's own `fillColor` always wins over `plotOptions.waterfall.colors`.
247
+
248
+ ### Dumbbell (v7.1)
249
+
250
+ `chart.type: 'dumbbell'` compares two or more measures per category. **Each series is one measure**, and the chart joins them with a connector per category. You do not zip the values into `[low, high]` pairs.
251
+
252
+ ```js
253
+ {
254
+ chart: { type: 'dumbbell', height: 350 },
255
+ series: [
256
+ { name: '2020', data: [{ x: 'Backend', y: 92 }, { x: 'Frontend', y: 78 }] },
257
+ { name: '2025', data: [{ x: 'Backend', y: 118 }, { x: 'Frontend', y: 96 }] },
258
+ ],
259
+ plotOptions: { bar: { horizontal: true } }, // rows; omit for columns
260
+ }
261
+ ```
262
+
263
+ The connector's thickness is `plotOptions.bar.barHeight` (rows) or `columnWidth` (columns), and the size of the marked ends is `markers.size`: they are the bar and its markers, so they are configured as such.
264
+
265
+ **`plotOptions.bar.dumbbell`:**
266
+
267
+ ```js
268
+ plotOptions: {
269
+ bar: {
270
+ dumbbell: {
271
+ connector: {
272
+ color: undefined, // undefined = a gradient between the two endpoint colours,
273
+ // resolved per row so a row where the measures cross runs the right way
274
+ opacity: 0.55, // the join is context for the marked ends, not a third mark
275
+ },
276
+ dataLabels: {
277
+ enabled: true, // default on for chart.type 'dumbbell', off for the bare isDumbbell flag
278
+ offset: 6, // px clear of the marked end, outward from the connector
279
+ colorFromMarker: true,
280
+ // formatter(value, { seriesIndex, dataPointIndex, endpointIndex, w })
281
+ // NOTE: defaults to the value axis' label formatter, NOT dataLabels.formatter
282
+ // (on a range bar that one reads out end - start, which is the gap, not the endpoint)
283
+ },
284
+ tooltip: { differenceLabel: 'Difference' },
285
+ },
286
+ },
287
+ }
288
+ ```
289
+
290
+ With three or more measures only the two extremes are labelled: anything between them sits on the connector, where a label has nowhere to go that is not over the line or over its neighbour.
291
+
292
+ **Versus the older range-bar form.** `plotOptions.bar.isDumbbell` on a `rangeBar` with `y: [lo, hi]` pairs still works and is a different chart: one series, endpoint colours from `plotOptions.bar.dumbbellColors` (`[[startColor, endColor]]`). `chart.type: 'dumbbell'` takes one series per measure and colours each dot after the series it belongs to, from `colors`. Prefer the type unless you already have paired data.
293
+
187
294
  ---
188
295
 
189
296
  ## Key plotOptions.bar Options
@@ -197,7 +304,7 @@ plotOptions: {
197
304
  distributed: false, // true = different color per data point
198
305
  borderRadius: 4, // rounded corners (number or object)
199
306
  borderRadiusApplication: 'end', // 'end' | 'around'
200
- borderRadiusWhenStacked: 'last', // 'all' | 'last'
307
+ borderRadiusWhenStacked: 'all', // 'all' (default) | 'last'; stacked bars only, see the note below
201
308
 
202
309
  dataLabels: {
203
310
  position: 'top', // 'top' | 'center' | 'bottom'
@@ -215,9 +322,22 @@ plotOptions: {
215
322
  }
216
323
  ```
217
324
 
325
+ **⚠️ `borderRadiusWhenStacked`: dropped in v7.0, restored working in v7.4.** v7.0 removed it as dead config (it had not been read for some time) and let corner ownership follow the stack's **outer edge** automatically. v7.4 made it a real option again, so at v7.4+ it is live and must not be deleted from a config:
326
+
327
+ - `'all'` (the default) keeps the v7.0 look, both ends of the stack rounded. Existing charts are unchanged.
328
+ - `'last'` rounds only the far end and leaves the baseline square.
329
+
330
+ A stack of a single data point also stopped rounding its baseline in v7.4, which on a horizontal 100% stack with one category used to round the first segment on its inner edge. Related v7.0 fixes: a stacked bar's bottom cap is now the top-rounded path mirrored (caps no longer invert or pop when a series collapses and rises again), and grouped stacked charts resolve caps per group rather than chart-wide, so every group gets its own bottom and top radius.
331
+
332
+ **⚠️ `dataLabels.animate.enabled` now defaults to `true` (v7.0), bar/column only.** Labels ride to their new position on a data-change update instead of snapping there, so they reflow on the same clock as the bars, markers and axis ticks. A label that has not moved is a per-label no-op; speed and easing follow `chart.animations.dynamicAnimation`. For the pre-v7 behavior:
333
+
334
+ ```js
335
+ dataLabels: { animate: { enabled: false } }
336
+ ```
337
+
218
338
  ### Bar Chart Race (v6.4)
219
339
 
220
- A reorder update animates into a bar chart race: when you re-sort the data and update the chart, the bars slide to their new ranks and their category labels ride along automatically (whenever `dynamicAnimation` is on). Two opt-in `dataLabels` flags complete the effect. Both are off by default and apply to **bar / column only**; their speed and easing follow `chart.animations.dynamicAnimation`.
340
+ A reorder update animates into a bar chart race: when you re-sort the data and update the chart, the bars slide to their new ranks and their category labels ride along automatically (whenever `dynamicAnimation` is on). Two `dataLabels` flags complete the effect, both **bar / column only**: `animate.enabled` is **on by default** since v7.0 (it was opt-in before), and `countUp.enabled` is off. their speed and easing follow `chart.animations.dynamicAnimation`.
221
341
 
222
342
  ```js
223
343
  const options = {
@@ -309,4 +429,9 @@ series: [{
309
429
  4. **Timeline without `xaxis.type: 'datetime'`** — date-based range bars need `xaxis: { type: 'datetime' }` to render correctly.
310
430
  5. **`distributed: true` with multiple series** — distributed coloring applies to each data point independently. With multiple series, each point gets a unique color which is usually not desired. Use `distributed: true` only with single-series charts.
311
431
  6. **Funnel/pyramid value ordering**: funnel expects values ordered largest-to-smallest and pyramid smallest-to-largest. The renderer does not sort for you; unsorted data produces a jagged shape. Stage labels come from `xaxis.categories`, and per-stage labels in `dataLabels.formatter` are read via `opt.w.globals.labels[opt.dataPointIndex]`.
312
- 7. **Reaching for `plotOptions.bar.isFunnel` in v6**: unnecessary. Set `chart.type: 'funnel'` (or `'pyramid'`) and use `plotOptions.funnel` for shape options.
432
+ 7. **Reaching for `plotOptions.bar.isFunnel`**: unnecessary. Set `chart.type: 'funnel'` (or `'pyramid'`) and use `plotOptions.funnel` for shape options.
433
+ 8. **Pre-computing the running total for a waterfall** *(v7.1)*: the series carries the **deltas**; the chart accumulates. Feeding it cumulative values draws each bar from zero to the total, which is a column chart with extra steps.
434
+ 9. **Giving a waterfall subtotal row a `y`** *(v7.1)*: `isSubtotal` / `isTotal` rows are measured for you. Supply `x` and the flag, nothing else.
435
+ 10. **Zipping dumbbell values into `y: [lo, hi]`** *(v7.1)*: that is the older `plotOptions.bar.isDumbbell` range-bar form. `chart.type: 'dumbbell'` wants one series per measure, each with plain `{ x, y }` points.
436
+ 11. **Assuming `borderRadiusWhenStacked` is still removed**: it was dead config in v7.0 through v7.3 and is a working option again from v7.4 (`'all'` default, `'last'` to leave the baseline square). Guidance written for v7.0 says to delete it; on v7.4+ that silently reverts a deliberate `'last'` to `'all'`.
437
+ 12. **Expecting data labels to snap on update** *(v7.0)*: `dataLabels.animate.enabled` now defaults to `true` on bar/column. Set it to `false` for the old behavior.
@@ -7,6 +7,7 @@
7
7
  - **Scatter** (`'scatter'`) — Individual data points plotted by X/Y coordinates
8
8
  - **Bubble** (`'bubble'`) — Scatter with variable-size bubbles (requires z value)
9
9
  - **Range Area** (`'rangeArea'`) — Area chart showing a range between two values
10
+ - **Streamgraph** (`'streamgraph'`, **new in v7.1**): Series stacked as flowing bands around a chosen baseline
10
11
 
11
12
  ## Tree-Shakeable Import
12
13
 
@@ -14,8 +15,13 @@
14
15
  import ApexCharts from 'apexcharts/line'
15
16
  // Registers: line, area, scatter, bubble, rangeArea
16
17
  // Aliases: apexcharts/area, apexcharts/scatter, apexcharts/bubble, apexcharts/rangeArea
18
+
19
+ // streamgraph (v7.1) adds a feature module on top of the rangeArea engine:
20
+ import ApexCharts from 'apexcharts/streamgraph' // == apexcharts/rangeArea + features/streamgraph
17
21
  ```
18
22
 
23
+ Streamgraph is **Tier 1**, so the default `apexcharts` bundle already has it and no import is needed there.
24
+
19
25
  ---
20
26
 
21
27
  ## Data Formats (Detailed)
@@ -124,6 +130,62 @@ Always use XY format:
124
130
  }
125
131
  ```
126
132
 
133
+ ### Streamgraph (v7.1)
134
+
135
+ `chart.type: 'streamgraph'` stacks the series as flowing bands around a baseline chosen to keep the whole picture readable. Series format is the same as area; **the stacking is the chart's job, so do not set `chart.stacked`**.
136
+
137
+ ```js
138
+ {
139
+ chart: { type: 'streamgraph', height: 350 },
140
+ series: [
141
+ { name: 'Drama', data: [{ x: '2024-01-01', y: 32 }, { x: '2024-02-01', y: 41 }] },
142
+ { name: 'Comedy', data: [{ x: '2024-01-01', y: 18 }, { x: '2024-02-01', y: 26 }] },
143
+ ],
144
+ }
145
+ ```
146
+
147
+ Curves are `monotoneCubic` by default so a band never overshoots its data, each band's name is drawn inside it sized to the room that band actually has, and hovering a band fades the others. The legend and tooltip work as on any axis chart.
148
+
149
+ **`plotOptions.streamgraph`:**
150
+
151
+ ```js
152
+ plotOptions: {
153
+ streamgraph: {
154
+ offset: 'wiggle', // where the baseline goes:
155
+ // 'wiggle' (default) minimizes total weighted slope, so thick bands stay level
156
+ // 'silhouette' centres the stack on one horizontal line
157
+ // 'zero' an ordinary stacked area, on the zero line
158
+ // 'expand' normalizes each column to its own total (composition, not volume)
159
+
160
+ order: 'inside-out', // stacking order, bottom first:
161
+ // 'inside-out' (default) puts early-peaking series in the middle and fans
162
+ // later peaks outward, each to whichever side is thinner
163
+ // 'inverse' the series order, reversed
164
+ // 'none' the series order as given
165
+
166
+ hover: { show: true, opacity: 0.35 }, // what the OTHER bands drop to; much above ~0.5 and the
167
+ // hovered band stops reading as picked out
168
+
169
+ labels: {
170
+ show: true,
171
+ minWidth: 24, // a band narrower than this many px is left unlabelled rather than
172
+ // given a name truncated past the point of being a name
173
+ minFontSize: 9,
174
+ maxFontSize: 30,
175
+ style: {
176
+ fontSize: 'auto', // sizes each name to its band, bounded by min/maxFontSize.
177
+ // Give a literal ('12px') to draw every name at the same size.
178
+ fontWeight: 600,
179
+ colors: undefined, // per-series override; by default each label takes black or white,
180
+ // whichever reads on its own band
181
+ },
182
+ },
183
+ },
184
+ }
185
+ ```
186
+
187
+ `offset: 'wiggle'` with `order: 'inside-out'` is the classic streamgraph, and the pair is what keeps it readable: the middle of the stack moves least under a wiggle baseline. A faded band's name is recoloured to `chart.foreColor` rather than faded with it, because the name takes black or white by the contrast of the band at full strength.
188
+
127
189
  ---
128
190
 
129
191
  ## Key Options
@@ -192,6 +254,25 @@ xaxis: {
192
254
  }
193
255
  ```
194
256
 
257
+ ### A way out of a zoom when the toolbar is hidden (v7.4)
258
+
259
+ Hiding the toolbar does not disable drag-to-zoom (a drag is a deliberate gesture, unlike the wheel and the pinch), so a viewer can zoom a bare chart and then have nothing on screen that puts the range back.
260
+
261
+ ```js
262
+ chart: {
263
+ toolbar: { show: false },
264
+ zoom: { enabled: true, resetControl: 'auto' } // 'auto' is the default
265
+ }
266
+ ```
267
+
268
+ While the chart **is** zoomed and nothing else can reset it, one reset control is drawn where the toolbar would have been, and it goes when the range does. `Escape` resets under the same gate, and the event is left alone so a page listening for it still hears it.
269
+
270
+ - `'auto'` (default): supply both only where there is no reset button already. A chart nobody zooms is untouched, so `toolbar: { show: false }` still means an empty chart for every viewer who does not zoom.
271
+ - `true`: also force the control on where `toolbar.tools.reset` is off, which is the same dead end as a hidden toolbar.
272
+ - `false`: turn off both the control and the key, for a page that supplies its own reset.
273
+
274
+ Requires `zoom.enabled: true`. A completed drag-zoom now also focuses the chart, so the `+`, `-` and `0` keys are reachable straight after a mouse zoom.
275
+
195
276
  ---
196
277
 
197
278
  ## Complete Working Example — Line Chart with Datetime
@@ -242,6 +323,20 @@ xaxis: {
242
323
 
243
324
  1. **Bubble chart without z value** — renders with zero-size bubbles. Always provide `z` in data.
244
325
  2. **Using `xaxis.categories` with `{ x, y }` data** — categories are silently ignored when data contains x values. Choose one approach.
245
- 3. **Datetime axis with string dates** — pass timestamps (`new Date(...).getTime()`) or `Date` objects, not raw strings like `'January 2024'`. Since v6.10 a `Date` object `x` is accepted by the types and keeps millisecond resolution (previously milliseconds were truncated, collapsing points within the same second).
326
+ 3. **Datetime axis with string dates**: pass timestamps (`new Date(...).getTime()`) or `Date` objects, not raw strings like `'January 2024'`. Since v6.10 a `Date` object `x` is accepted by the types and keeps millisecond resolution (previously milliseconds were truncated, collapsing points within the same second). **Since v7.1 a `Date` x also works on a non-datetime axis**; before that it only worked when the axis was `datetime` and stringified into garbage anywhere else, and an invalid `Date` slipped through unnoticed. Both are handled now.
246
327
  4. **`stroke.curve: 'smooth'` on sparse data** — can produce visual artifacts. Use `'monotoneCubic'` for mathematically smoother interpolation.
247
328
  5. **Range Area with single value instead of array** — `y` must be `[low, high]`, not a single number.
329
+ 6. **Setting `chart.stacked` on a streamgraph** *(v7.1)*: the chart owns its own stacking, baseline and band order through `plotOptions.streamgraph`. Reach for `offset: 'zero'` if what you actually want is a plain stacked area.
330
+ 7. **A series that starts with nulls used to mis-target the tooltip**: the hovered index resolved against the drawn points rather than the data, so the tooltip read the wrong point. Fixed in v7.1.
331
+ 8. **`autoScaleYaxis` scaling to points nobody can see**: before v7.1, zooming let one point just outside the window size the whole axis, and a series with nothing in the window still contributed. Fixed in v7.1; stacked charts now also scale to the stacked totals inside the window rather than to individual series.
332
+
333
+ ## Axis and layout fixes worth knowing (v7.0 / v7.1)
334
+
335
+ These need no config change; they matter because a workaround you carried from an earlier version may now be unnecessary.
336
+
337
+ - **Logarithmic axis geometry, and `tickAmount` on a log scale** *(v7.0)*. Log-scale positions were computed against the wrong domain, and `tickAmount` was ignored outright on a log axis. Both fixed, so `yaxis: { logarithmic: true, tickAmount: n }` now does what it says.
338
+ - **Bar width came from the smallest gap inside one series** *(fixed v7.1)*. A single series with one tight pair of x values shrank every bar on the chart. A bar's slot now comes from the axis, the union of gaps across all series.
339
+ - **Stacked baselines resolve by x, not by position in the array** *(fixed v7.0)*. Two series stacked on ragged data lined up by ordinal, so a series with a missing point stacked onto the wrong neighbour.
340
+ - **A container resize arriving mid-animation was lost for good** *(fixed v7.1)*. The ResizeObserver fires once per resize and the handler dropped that callback while an animation ran, so a chart in a collapsing sidebar kept its old size.
341
+ - **Annotations on a chart with no data** *(fixed v7.0)*. Placement was gated chart-wide on `dataPoints`, so an empty chart dropped every annotation including y-axis ones that are always placeable. Gating is now per annotation.
342
+ - **A tooltip inside Shadow DOM followed the wrong element** *(fixed v7.0)*. Reading `e.target` after a deferred hover gets the shadow host, not the hovered mark.
@@ -29,8 +29,13 @@ import ApexCharts from 'apexcharts/unit'
29
29
 
30
30
  import { heart, outlined, glyphs, preview } from 'apexcharts/unit-shapes'
31
31
  // Unit-chart shape kit (v6.10), named exports tree-shaken per shape. See "Unit shapes" below.
32
+
33
+ import { person, registerMarks } from 'apexcharts/pictograms'
34
+ // Pictogram glyph collection (v7.0), one drawn mark per unit. See "Pictograms" below.
32
35
  ```
33
36
 
37
+ All of these are **Tier 1** (in the default `apexcharts` bundle). Nothing in the circular family is Tier 2.
38
+
34
39
  ---
35
40
 
36
41
  ## Data Format
@@ -150,9 +155,13 @@ A sunburst draws a hierarchy as concentric rings: the first level fills a donut
150
155
 
151
156
  Colours, `stroke`, `legend`, and `title` behave as they do on pie and donut. A sunburst can also adapt an existing `drilldown` config instead of a native `children` hierarchy.
152
157
 
158
+ A branch may **omit its own `y`** and be the sum of its children. That is the documented shape, but it only began working in v7.6: before that the non-axis parser required every datum to carry `x` and `y`, so a branch with only `children` was dropped, the series came back empty, and the chart drew nothing while warning about pie data. The samples all gave their top-level nodes a `y`, which is what hid it from the moment sunburst shipped.
159
+
160
+ **The same tree in cartesian coordinates** is the `icicle` type *(v7.6)*: one band per depth level instead of concentric rings, labels horizontal at every depth, and `direction: 'up'` for flame graphs. It is an opt-in import. See `references/grid-charts.md`.
161
+
153
162
  ### Unit / Waffle (v6.6, premium)
154
163
 
155
- A `unit` chart renders one discrete mark for every unit of value instead of a single bar or slice, so "37 of 200" reads as a countable quantity. It is a **non-axis** chart (dispatched like pie or treemap) and is **premium**: it renders fully in trial mode with an `APEXCHARTS` watermark until an entitled license is set (see `references/v6-features.md`). On every update each mark tweens from its old position to its new one, so re-grouping, filtering, or a changing count re-forms the marks.
164
+ A `unit` chart renders one discrete mark for every unit of value instead of a single bar or slice, so "37 of 200" reads as a countable quantity. It is a **non-axis** chart (dispatched like pie or treemap) and is **premium**: it renders fully in trial mode with an `APEXCHARTS` watermark until an entitled license is set (see `references/feature-platform.md`). On every update each mark tweens from its old position to its new one, so re-grouping, filtering, or a changing count re-forms the marks.
156
165
 
157
166
  Data is the same flat number array + `labels` shape as pie:
158
167
 
@@ -219,6 +228,45 @@ registerShapes([...]) // register so positions: '<name>' resolves by
219
228
 
220
229
  Every shape carries metadata on `shape` (category, kind, and `minUnits`: the count below which it stops being recognisable; asking for fewer logs a console warning naming the shape). From a script tag, `dist/unit-shapes.js` exposes the kit as the global `ApexUnitShapes` with every shape pre-registered, so `positions: 'heart'` works by name.
221
230
 
231
+ ### Pictograms (`apexcharts/pictograms`, v7.0)
232
+
233
+ A **shape** is where the units go; a **pictogram** is what one unit looks like. They are independent, so `positions: heart` with `shape: 'pictogram'` arranges glyphs into a heart, and every other pairing is equally valid.
234
+
235
+ ```js
236
+ import ApexCharts from 'apexcharts'
237
+ import { person, registerMarks } from 'apexcharts/pictograms'
238
+
239
+ registerMarks([person]) // so pictogram.mark: 'person' resolves by name
240
+
241
+ const options = {
242
+ chart: { type: 'unit' },
243
+ series: [120, 80],
244
+ labels: ['Employed', 'Unemployed'],
245
+ plotOptions: {
246
+ unit: {
247
+ shape: 'pictogram',
248
+ pictogram: {
249
+ mark: 'person', // a registered name, a { path, viewBox?, fillRule? } object,
250
+ // raw path data, or an array (one per series).
251
+ // A datum's own `mark` overrides all of it, so one crowd can mix glyphs.
252
+ fit: 'contain', // which side of the glyph binds to the dot's box: 'contain' | 'width' | 'height'
253
+ scale: 1, // nudge for glyphs that read light
254
+ padding: 0, // 0..0.9 of the pitch, opening the lattice up
255
+ fallback: 'circle', // drawn when a mark cannot be resolved
256
+ },
257
+ },
258
+ },
259
+ }
260
+ ```
261
+
262
+ A glyph is drawn as one `<path>` per unit, filled in that unit's own colour: no request, no decode, no recolour filter, which is why `'pictogram'` is the shape that scales to thousands of units. There is deliberately **no size**: a glyph is fitted to the box the dot itself would have occupied, so `plotOptions.unit.size` and `spacing` size a pictogram exactly as they size a dot.
263
+
264
+ **Shipped glyphs** (each a named export, so import the one you use): `person`, `house`, `heart`, `tree`, `droplet`, `star`, `car`, `bag`, `book`, `cup`, `bulb`, `plane`. `catalog` is every glyph and ships all of them, so avoid it in an app bundle.
265
+
266
+ **Custom glyphs:** `definePictogram({ name, path, viewBox?, fillRule?, category?, source? })` builds one, `registerMarks(defs)` registers them by name, `unregisterMarks(names)` removes them, and `registeredMarkNames()` lists what resolves. Any glyph also has `.with(overrides)` for a variant with some metadata replaced.
267
+
268
+ **Versus `shape: 'image'`:** `'image'` fetches a raster or multi-colour icon (`plotOptions.unit.image.src`, with `tint: true` to recolour a monochrome icon to the category colour). Use it for multi-colour marks that should keep their own colours; use `'pictogram'` for anything that has to scale or follow the palette.
269
+
222
270
  **Outer name labels (v6.10):** a shape packed with several categories can name them in the margin with a leader line to their own dots (the pie/donut outer-label mechanism), instead of needing a legend:
223
271
 
224
272
  ```js
@@ -445,3 +493,6 @@ plotOptions: {
445
493
  10. **Expecting `unit` / `waffle` without a license to be watermark-free**: they are premium and render an `APEXCHARTS` watermark until an entitled `premium`/`enterprise` license is set. Sunburst, by contrast, is free.
446
494
  11. **Slice click behavior changed (v6.9)**: clicking a pie/donut slice now slides it out along its own mid-angle (`plotOptions.pie.expandOffset`, default 10 px) instead of darkening it and redrawing it at a larger radius, and hover traces an outline band (`plotOptions.pie.hoverOutline`) instead of lightening the fill. Set `expandOffset: 0` to keep slices in place; set `states.hover.filter.type: 'none'` to suppress the hover band.
447
495
  12. **Unit shapes below `minUnits`**: each `apexcharts/unit-shapes` shape declares the minimum dot count at which it still reads. Feeding fewer units logs a console warning naming the shape; pick a simpler shape or raise `unitValue` so the count lands above the threshold.
496
+ 13. **Confusing a shape with a pictogram** *(v7.0)*: `plotOptions.unit.positions` (from `apexcharts/unit-shapes`) is **where** the units go; `plotOptions.unit.shape: 'pictogram'` + `pictogram.mark` (from `apexcharts/pictograms`) is **what one unit looks like**. They compose freely.
497
+ 14. **Setting a size on a pictogram** *(v7.0)*: there is no `pictogram.size`. A glyph is fitted to the box the dot would have occupied, so use `plotOptions.unit.size` and `spacing`. Use `pictogram.scale` only to nudge a glyph that reads light.
498
+ 15. **Pie / radial charts pinned to the top of a tall container** *(fixed v7.1)*: the centre came from `min(width, height)`, so a tall, narrow container put the circle at the top with the leftover space below it. The circle now centres in the height it actually has. If you carried a manual `offsetY` to work around this, remove it.