apexcharts-skill 2.2.0 → 3.0.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 was REMOVED in v7.0, see the note below
201
308
 
202
309
  dataLabels: {
203
310
  position: 'top', // 'top' | 'center' | 'bottom'
@@ -215,6 +322,14 @@ plotOptions: {
215
322
  }
216
323
  ```
217
324
 
325
+ **⚠️ `borderRadiusWhenStacked` was removed in v7.0.** Rounded corners on a stacked bar are no longer a setting: corner ownership follows the **outer edge** of the stack, which is what `'last'` approximated and what `'all'` got wrong on any stack whose last series was empty. An unknown option is ignored, so leaving it in a config is harmless but does nothing. Delete it. 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.
326
+
327
+ **⚠️ `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:
328
+
329
+ ```js
330
+ dataLabels: { animate: { enabled: false } }
331
+ ```
332
+
218
333
  ### Bar Chart Race (v6.4)
219
334
 
220
335
  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`.
@@ -309,4 +424,9 @@ series: [{
309
424
  4. **Timeline without `xaxis.type: 'datetime'`** — date-based range bars need `xaxis: { type: 'datetime' }` to render correctly.
310
425
  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
426
  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.
427
+ 7. **Reaching for `plotOptions.bar.isFunnel`**: unnecessary. Set `chart.type: 'funnel'` (or `'pyramid'`) and use `plotOptions.funnel` for shape options.
428
+ 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.
429
+ 9. **Giving a waterfall subtotal row a `y`** *(v7.1)*: `isSubtotal` / `isTotal` rows are measured for you. Supply `x` and the flag, nothing else.
430
+ 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.
431
+ 11. **Setting `borderRadiusWhenStacked`**: removed in v7.0. The option is ignored; stacked corner ownership follows the stack's outer edge automatically.
432
+ 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
@@ -242,6 +304,20 @@ xaxis: {
242
304
 
243
305
  1. **Bubble chart without z value** — renders with zero-size bubbles. Always provide `z` in data.
244
306
  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).
307
+ 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
308
  4. **`stroke.curve: 'smooth'` on sparse data** — can produce visual artifacts. Use `'monotoneCubic'` for mathematically smoother interpolation.
247
309
  5. **Range Area with single value instead of array** — `y` must be `[low, high]`, not a single number.
310
+ 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.
311
+ 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.
312
+ 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.
313
+
314
+ ## Axis and layout fixes worth knowing (v7.0 / v7.1)
315
+
316
+ These need no config change; they matter because a workaround you carried from an earlier version may now be unnecessary.
317
+
318
+ - **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.
319
+ - **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.
320
+ - **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.
321
+ - **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.
322
+ - **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.
323
+ - **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
@@ -152,7 +157,7 @@ Colours, `stroke`, `legend`, and `title` behave as they do on pie and donut. A s
152
157
 
153
158
  ### Unit / Waffle (v6.6, premium)
154
159
 
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.
160
+ 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
161
 
157
162
  Data is the same flat number array + `labels` shape as pie:
158
163
 
@@ -219,6 +224,45 @@ registerShapes([...]) // register so positions: '<name>' resolves by
219
224
 
220
225
  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
226
 
227
+ ### Pictograms (`apexcharts/pictograms`, v7.0)
228
+
229
+ 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.
230
+
231
+ ```js
232
+ import ApexCharts from 'apexcharts'
233
+ import { person, registerMarks } from 'apexcharts/pictograms'
234
+
235
+ registerMarks([person]) // so pictogram.mark: 'person' resolves by name
236
+
237
+ const options = {
238
+ chart: { type: 'unit' },
239
+ series: [120, 80],
240
+ labels: ['Employed', 'Unemployed'],
241
+ plotOptions: {
242
+ unit: {
243
+ shape: 'pictogram',
244
+ pictogram: {
245
+ mark: 'person', // a registered name, a { path, viewBox?, fillRule? } object,
246
+ // raw path data, or an array (one per series).
247
+ // A datum's own `mark` overrides all of it, so one crowd can mix glyphs.
248
+ fit: 'contain', // which side of the glyph binds to the dot's box: 'contain' | 'width' | 'height'
249
+ scale: 1, // nudge for glyphs that read light
250
+ padding: 0, // 0..0.9 of the pitch, opening the lattice up
251
+ fallback: 'circle', // drawn when a mark cannot be resolved
252
+ },
253
+ },
254
+ },
255
+ }
256
+ ```
257
+
258
+ 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.
259
+
260
+ **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.
261
+
262
+ **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.
263
+
264
+ **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.
265
+
222
266
  **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
267
 
224
268
  ```js
@@ -445,3 +489,6 @@ plotOptions: {
445
489
  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
490
  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
491
  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.
492
+ 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.
493
+ 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.
494
+ 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.
@@ -1,8 +1,35 @@
1
- # v6 Feature Platform: ApexCharts
1
+ # Feature Platform: ApexCharts
2
2
 
3
- ApexCharts v6 adds an opt-in, tree-shakeable feature platform on top of the v5 core. **Every v5 config keeps working unchanged.** Each feature below is off by default and ships as its own `apexcharts/features/*` entry, so it only enters your bundle when you import it. When using the full `apexcharts` bundle, all features are already present.
3
+ ApexCharts ships an opt-in feature platform on top of the chart core. **Every v5 and v6 config keeps working unchanged in v7**, apart from the two v7.0 breaking changes noted below. Each feature is off by default in config terms and ships as its own `apexcharts/features/*` entry.
4
4
 
5
- Two behaviors changed *on by default* (both respect `prefers-reduced-motion`):
5
+ ## ⚠️ Read first: two bundle tiers (v7.0)
6
+
7
+ Whether you need the `features/*` import depends on the feature's **tier**, and this changed in v7.0.
8
+
9
+ - **Tier 1** is in the default `import ApexCharts from 'apexcharts'` bundle: `exports`, `legend`, `toolbar`, `annotations`, `keyboard`, `morph`, `drilldown`, `weave`, `marks`, `facet`, `stats`, and the v7.1 `waterfall` / `dumbbell` / `streamgraph` chart types. Import these only if you started from `apexcharts/core` or a per-type entry.
10
+ - **Tier 2 is not in any bundle.** Nine features (`trellis`, `storyboard`, `perspectives`, `ink`, `renderer-canvas`, `link`, `measure`, `history`, `context-menu`) plus the `raincloud` chart type require an explicit import **whatever entry point you used**, the full bundle included.
11
+
12
+ ```js
13
+ import ApexCharts from 'apexcharts'
14
+ import 'apexcharts/features/trellis' // Tier 2: required even here
15
+ ```
16
+ ```html
17
+ <script src=".../dist/apexcharts.js"></script>
18
+ <script src=".../dist/features/trellis.js"></script>
19
+ ```
20
+
21
+ Nothing fails silently: each Tier 2 module warns in the console when its configuration is present but the module is not, and names both import routes. Where the chart can still draw something sensible it does and says what it did: a trellis renders as a single chart, `renderer: 'canvas'` falls back to SVG. `apexcharts/features/all` is the **Tier 1** set and is not a shortcut past this.
22
+
23
+ Why: 24% of the 6.10.0 default bundle was licence-gated premium code that an unlicensed user could only run watermarked, and everybody downloaded it. The default bundle went 291,654 B gzipped (6.10.0) to 252,005 B (7.0.0), and 264,326 B in 7.1.0 with the three new free chart types. See `references/tree-shaking.md` for the full tier table and per-module sizes.
24
+
25
+ ## Two v7.0 breaking changes
26
+
27
+ 1. **`plotOptions.bar.borderRadiusWhenStacked` is removed.** Rounded corners on a stacked bar are no longer a setting: corner ownership follows the **outer edge** of the stack, which is what `'last'` approximated and what `'all'` got wrong on any stack whose last series was empty. An unknown option is ignored, so leaving it in place is harmless but does nothing.
28
+ 2. **`dataLabels.animate.enabled` now defaults to `true`** (bar and 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. Speed and easing follow `chart.animations.dynamicAnimation`. A label that has not moved is a per-label no-op. Set `dataLabels: { animate: { enabled: false } }` for the old behavior.
29
+
30
+ ## Behaviors that are on by default
31
+
32
+ Carried over from v6, both respecting `prefers-reduced-motion`:
6
33
 
7
34
  1. **Coherent variable-length data transitions**: updates that add/remove data points animate as one coordinated motion instead of popping. Appended bars grow from the baseline, removed bars shrink and fade, line/area fills reshape over the union of old and new points, and markers/bubbles/axis labels ride along. Scatter and bubble now animate on dynamic updates for the first time. Disable per chart with `chart.animations.dynamicAnimation.enabled: false`; auto-skipped above `chart.animations.largeDatasetThreshold` (default 1000) and under `prefers-reduced-motion`.
8
35
  2. **Native-feeling mobile gestures**: two-finger pinch-zoom, two-finger pan, and kinetic inertia after a flick, with axis rails so a vertical swipe still scrolls the page. Configure via `chart.zoom.pinch` (default `true`) and `chart.pan.inertia` (default `true`, `friction` default `0.92`).
@@ -18,10 +45,11 @@ Two update behaviors worth knowing (both 6.9/6.10):
18
45
 
19
46
  ## Licensing: premium features and the trial watermark
20
47
 
21
- Introduced in **6.5.0**, tightened in **6.7.0**. Not every v6 feature is free. Seven feature modules plus the `unit` / `waffle` chart type are **premium** and run under a lightweight, offline license check. They keep working fully without a key (trial mode), but the chart shows an unobtrusive `APEXCHARTS` watermark until an entitled license is set.
48
+ Introduced in **6.5.0**, tightened in **6.7.0**. Not every feature is free. Eight feature modules plus two chart types are **premium** and run under a lightweight, offline license check. They keep working fully without a key (trial mode), but the chart shows an unobtrusive `APEXCHARTS` watermark until an entitled license is set.
22
49
 
23
50
  **The premium set (watermarked until entitled):**
24
51
 
52
+ - `trellis` (small multiples, premium since **7.0.0**)
25
53
  - `storyboard` (scrollytelling)
26
54
  - `link` (crossfilter / linked views)
27
55
  - `ink` (annotation authoring)
@@ -30,8 +58,11 @@ Introduced in **6.5.0**, tightened in **6.7.0**. Not every v6 feature is free. S
30
58
  - `perspectives` (shareable view state)
31
59
  - `history` (undo/redo, Rewind)
32
60
  - the `unit` / `waffle` chart type (premium since **6.6.0**, the first premium chart type)
61
+ - the `raincloud` chart type (premium since **7.1.0**)
62
+
63
+ **Everything else is free and never gated**: every other chart type (`sunburst`, and the v7.1 `waterfall` / `dumbbell` / `streamgraph`), and every other module (Weave, Strata / canvas, Marks, Facet / themes, Cadence, drilldown, streaming, exports, legend, toolbar, annotations, keyboard, stats).
33
64
 
34
- **Everything else is free and never gated**: every other chart type (including `sunburst`), and every other module (Weave, Strata / canvas, Marks, Facet / themes, Cadence, drilldown, streaming, exports, legend, toolbar, annotations, keyboard).
65
+ **Premium and Tier 2 are independent axes.** Premium is about the watermark; Tier 2 is about the bundle. `renderer-canvas` is Tier 2 and free. `waterfall`, `dumbbell` and `streamgraph` are Tier 1 and free. `unit` / `waffle` is premium but Tier 1 (it is a chart type in the default bundle). `trellis` and `raincloud` are both.
35
66
 
36
67
  ### Setting a license
37
68
 
@@ -55,9 +86,73 @@ As of **6.7.0** the premium features clear the watermark only on a `premium` or
55
86
 
56
87
  ---
57
88
 
89
+ ## Trellis: small multiples that agree to the pixel (7.0)
90
+
91
+ `import 'apexcharts/features/trellis'`: **Tier 2** (not in the default bundle) and **premium**.
92
+
93
+ One dataset split into a grid of real charts that share a scale, a legend, a toolbar and a crosshair. Setting `trellis.by` makes the chart a trellis **host**: the series array is split into one panel per facet-key value, every panel is a real chart of the host's `chart.type`, and the trellis owns everything shared.
94
+
95
+ ```js
96
+ import ApexCharts from 'apexcharts'
97
+ import 'apexcharts/features/trellis'
98
+
99
+ new ApexCharts(el, {
100
+ chart: { type: 'line' },
101
+ series: [
102
+ { name: 'Revenue', region: 'North', data: north },
103
+ { name: 'Revenue', region: 'South', data: south },
104
+ ],
105
+ trellis: { by: 'region', minPanelWidth: 260 },
106
+ }).render()
107
+ ```
108
+
109
+ `trellis.by` is the whole configuration. The grid owns the y domain (the union across panels), the x window, and a column count computed from `minPanelWidth` alone. **A series carrying no facet key repeats in every panel**, which is how you get a reference line.
110
+
111
+ **In TypeScript**, use the typed `facet` field rather than an arbitrary key, or the series objects will not typecheck:
112
+
113
+ ```ts
114
+ series: [
115
+ { name: 'Revenue', facet: 'North', data: north },
116
+ { name: 'Revenue', facet: 'South', data: south },
117
+ ],
118
+ trellis: { by: 'facet', minPanelWidth: 260 },
119
+ ```
120
+
121
+ **Facet accessors.** `by` is a key name on each series object (`facet` is the blessed typed field; any other key name works from plain JS) or a function `(series, index) => key`, which works from either. For a 2-D grid use `row` × `column` instead of `by`: every (row, column) combination in row-major order at a fixed column count, column labels once across the top, row labels once down the left. A series carrying only the row key repeats across that row.
122
+
123
+ **Tidy-row input.** `trellis.data` takes a row table instead of `series`, pivoted by the `by` / `x` / `y` / `seriesBy` **column names**. Rows win over `series` when both are given; duplicate (panel, series, x) rows keep the last and warn, so aggregate first.
124
+
125
+ | Option | Default | Notes |
126
+ |---|---|---|
127
+ | `columns` | `'auto'` | `'auto'` fits `minPanelWidth` columns into the container. |
128
+ | `minPanelWidth` | `220` | Drives `'auto'` columns and the responsive collapse. |
129
+ | `gap` / `aspectRatio` / `panelHeight` | `12` / `1.6` / none | `panelHeight` wins over `aspectRatio` and `chart.height`. |
130
+ | `order` | `'first-seen'` | Also `'asc'`, `'desc'`, an explicit `string[]`, or a comparator. |
131
+ | `limit` | none | Render only the first N panels (warns about the rest). |
132
+ | `virtualize` | `'auto'` | Mounts only panels intersecting the viewport (plus one row) once the grid exceeds **64 panels**. An unmounted cell keeps its header and a fixed-height skeleton so page height never shifts; a remount restores its zoom window. **`getPanel(key)` returns `null` for an unmounted panel.** |
133
+ | `scales` | `{ x: 'shared', y: 'shared' }` | `y` also takes `'independent-row'` / `'independent-column'` in a 2-D grid. Non-shared `y` still renders pixel-aligned panels. |
134
+ | `emptyPanels` | `'placeholder'` | Missing (row, column) combos: a real empty panel with `noData.text`, or `'skip'` (tinted blank) or `'hide'`. |
135
+ | `axes.labels` | `'edges'` | y labels on the first column, x labels on each column's bottom panel. Label *space* is always reserved everywhere so panels stay aligned. Also `'all'` / `'none'`. |
136
+ | `legend` / `toolbar` | `'shared'` | One legend (toggles a series name in every panel) and one zoom/pan/reset toolbar. `'none'` to drop. |
137
+ | `tooltip` | `'panel'` | Card in the hovered panel while the crosshair sweeps all. `'sync'` = a card per panel; `'grid'` = one card near the cursor with a row per panel, composed from the panels' own tooltips so every formatter is honored. |
138
+ | `zoom` | `'sync'` | A zoom in any panel moves every panel. |
139
+ | `promote` | `true` | Clicking a header expands that panel to full width with an "All panels" breadcrumb. |
140
+ | `radiusByTotal` | `false` | Pie/donut/polarArea only: scale each panel's radius so its **area** is proportional to the panel's total. Equal-size pies cannot encode magnitude, so set this on a pie trellis. |
141
+ | `targetTicks` | `3` | Tick-interval target for the shared nice y scale, so a small panel wears few labels. |
142
+ | `header` | shown | `{ show, formatter(key, { dimension, index, count }), style }`. |
143
+ | `panel` | none | `(key, { index, seriesNames }) => ApexOptions`, a per-panel override applied last. |
144
+
145
+ **API.** `chart.getPanels()` returns the panels in grid order; `chart.getPanel(key)` returns one panel's own ApexCharts instance (the escape hatch to every per-chart API the trellis does not re-expose); `chart.promotePanel(key)` / `chart.restorePanels()` drive the promotion from code. `ApexCharts.trellis(el, options)` is the imperative constructor and throws if the feature is not imported.
146
+
147
+ **Annotations** take `scope`: absent or `'trellis'` draws in every panel through each panel's own scale, a key or array of keys restricts it.
148
+
149
+ **Per-type guardrails** keep a shared frame honest: histograms share a bin frame, violins a bandwidth, heatmaps one colour scale and a single gradient legend.
150
+
151
+ ---
152
+
58
153
  ## Weave: public plugin platform
59
154
 
60
- `import 'apexcharts/features/weave'`
155
+ `import 'apexcharts/features/weave'`: Tier 1 (already in the default bundle).
61
156
 
62
157
  Publish reusable chart plugins to npm against a stable, versioned API. A plugin draws into its own sandboxed layer and subscribes to lifecycle hooks; it never touches raw internal state.
63
158
 
@@ -98,7 +193,7 @@ The `setup(api)` facade:
98
193
 
99
194
  ## Strata: hybrid SVG + canvas renderer
100
195
 
101
- `import 'apexcharts/features/renderer-canvas'`
196
+ `import 'apexcharts/features/renderer-canvas'`: **Tier 2** (not in the default bundle). Free.
102
197
 
103
198
  Break the SVG node ceiling without leaving SVG behind. Below a threshold the output is identical SVG; above it, only the series layer becomes a `<canvas>` while axes, grid, tooltips, annotations, and exports stay SVG.
104
199
 
@@ -121,7 +216,7 @@ Canvas is live for line, area, bar, column, scatter, and candlestick, with share
121
216
 
122
217
  ## Marks: composable custom series types
123
218
 
124
- `import 'apexcharts/features/marks'`
219
+ `import 'apexcharts/features/marks'`: Tier 1 (already in the default bundle).
125
220
 
126
221
  Register a `renderItem(ctx)` function and get a first-class series: events, shared tooltip, legend, and keyboard navigation all work with no extra wiring. Dumbbell, lollipop, and bullet ship as samples.
127
222
 
@@ -158,7 +253,7 @@ Dumbbell uses `dataType: 'rangeXY'` (datum `y` is `[start, end]`, both bounds fo
158
253
 
159
254
  ## Rewind: history and undo/redo
160
255
 
161
- `import 'apexcharts/features/history'`
256
+ `import 'apexcharts/features/history'`: **Tier 2** (not in the default bundle) and **premium**.
162
257
 
163
258
  **Premium** (watermarked in trial mode until an entitled license is set, see Licensing above).
164
259
 
@@ -181,7 +276,7 @@ chart.history.entries() // [{ id, label, at }]
181
276
 
182
277
  ## Perspectives: shareable view state
183
278
 
184
- `import 'apexcharts/features/perspectives'`
279
+ `import 'apexcharts/features/perspectives'`: **Tier 2** (not in the default bundle) and **premium**.
185
280
 
186
281
  **Premium** (watermarked in trial mode until an entitled license is set, see Licensing above).
187
282
 
@@ -208,7 +303,7 @@ const options = { chart: { perspectives: { serializeOptions: ['colors', 'title']
208
303
 
209
304
  ## Facet: design tokens and OS-aware themes
210
305
 
211
- `import 'apexcharts/features/facet'`
306
+ `import 'apexcharts/features/facet'`: Tier 1 (already in the default bundle).
212
307
 
213
308
  Charts read `--apx-*` CSS custom properties from the cascade, follow the OS light/dark and contrast preferences with no JS, and can reference named brand themes.
214
309
 
@@ -265,7 +360,7 @@ Built-in curve names: `linear`, `easeInSine`, `easeOutSine`, `easeInOutSine`, `e
265
360
 
266
361
  ## Link: crossfilter and cross-chart coordination
267
362
 
268
- `import 'apexcharts/features/link'`
363
+ `import 'apexcharts/features/link'`: **Tier 2** (not in the default bundle) and **premium**.
269
364
 
270
365
  **Premium** (watermarked in trial mode until an entitled license is set, see Licensing above).
271
366
 
@@ -303,7 +398,7 @@ const b = {
303
398
 
304
399
  ## Ink: direct-manipulation annotation authoring
305
400
 
306
- `import 'apexcharts/features/ink'`
401
+ `import 'apexcharts/features/ink'`: **Tier 2** (not in the default bundle) and **premium**.
307
402
 
308
403
  **Premium** (watermarked in trial mode until an entitled license is set, see Licensing above).
309
404
 
@@ -327,7 +422,7 @@ const options = {
327
422
 
328
423
  ## Measure: delta ruler
329
424
 
330
- `import 'apexcharts/features/measure'`
425
+ `import 'apexcharts/features/measure'`: **Tier 2** (not in the default bundle) and **premium**.
331
426
 
332
427
  **Premium** (watermarked in trial mode until an entitled license is set, see Licensing above).
333
428
 
@@ -376,7 +471,7 @@ const options = {
376
471
 
377
472
  ## Context menu: Radial Actions
378
473
 
379
- `import 'apexcharts/features/context-menu'`
474
+ `import 'apexcharts/features/context-menu'`: **Tier 2** (not in the default bundle) and **premium**.
380
475
 
381
476
  **Premium** (watermarked in trial mode until an entitled license is set, see Licensing above).
382
477
 
@@ -404,7 +499,7 @@ Built-in `annotate` / `xline` / `yline` items are ink-managed when the ink featu
404
499
 
405
500
  ## Storyboard: scroll-driven choreography (scrollytelling)
406
501
 
407
- `import 'apexcharts/features/storyboard'` (includes Perspectives)
502
+ `import 'apexcharts/features/storyboard'` (registers Perspectives too): **Tier 2** (not in the default bundle) and **premium**.
408
503
 
409
504
  **Premium** (watermarked in trial mode until an entitled license is set, see Licensing above).
410
505
 
@@ -442,7 +537,7 @@ const options = { chart: { streaming: { enabled: true, maxPoints: 100000 } } }
442
537
 
443
538
  ## Drilldown: hierarchical drill-down
444
539
 
445
- `import 'apexcharts/features/drilldown'` (add `import 'apexcharts/features/morph'` for animated cross-type transitions)
540
+ `import 'apexcharts/features/drilldown'` (add `import 'apexcharts/features/morph'` for animated cross-type transitions): both Tier 1 (already in the default bundle).
446
541
 
447
542
  Data points reference a child level via a `drilldown` id; clicking drills in, with an optional breadcrumb. Since **v6.9** drilldown also works on **line and area** charts, and levels can be resolved **async** against a backend.
448
543
 
@@ -494,7 +589,7 @@ chart.drillToRoot()
494
589
 
495
590
  ## Stats: raw-sample statistics (6.9.0)
496
591
 
497
- `import 'apexcharts/features/stats'`
592
+ `import 'apexcharts/features/stats'`: Tier 1 (already in the default bundle).
498
593
 
499
594
  Free. The statistics behind three things, kept out of core so they cost nothing unless used:
500
595
 
@@ -536,8 +631,11 @@ const options = {
536
631
 
537
632
  ## Common Pitfalls
538
633
 
539
- 1. **Forgetting the feature import**: every v6 feature is off until you `import 'apexcharts/features/<name>'` (or use the full `apexcharts` bundle). Enabling the config alone does nothing if the feature isn't in the bundle.
540
- 2. **Using `api.zeroY` in a custom mark**: it does not exist. Use `ctx.scales.y(0)` for the baseline.
541
- 3. **Expecting `mode: 'filter'` on `chart.link` without records**: highlight mode needs `chart.selection.enabled: true`; the crossfilter engine needs `ApexCharts.crossfilter({ id, records })` or a `dimension` extractor.
542
- 4. **`largeDatasetThreshold` is under `chart.animations`**, not `chart` directly.
543
- 5. **Assuming the canvas renderer supports everything**: pattern/image fills and per-point selection visuals fall back to (or stay) SVG.
634
+ 1. **Assuming the full bundle still carries everything *(the #1 v6 → v7 mistake)*.** In v6, `import ApexCharts from 'apexcharts'` was enough for every feature. In v7 the nine Tier 2 features and the `raincloud` type need their own import on top of it. Read the console: each one names both routes when its config is present without it.
635
+ 2. **Forgetting the feature import on a tree-shaken build**: a Tier 1 feature is in the default bundle but not in `apexcharts/core` or a per-type entry, so those builds still need `import 'apexcharts/features/<name>'`.
636
+ 3. **Reaching for `apexcharts/features/all` to fix either of the above**: it is the Tier 1 set only, by design (`tests/unit/feature-tier-budget.spec.js` upstream fails the build if a Tier 2 feature reappears in it).
637
+ 4. **Using `api.zeroY` in a custom mark**: it does not exist. Use `ctx.scales.y(0)` for the baseline.
638
+ 5. **Expecting `mode: 'filter'` on `chart.link` without records**: highlight mode needs `chart.selection.enabled: true`; the crossfilter engine needs `ApexCharts.crossfilter({ id, records })` or a `dimension` extractor.
639
+ 6. **Two different `largeDatasetThreshold` options.** `chart.animations.largeDatasetThreshold` (default 1000) skips update animations; `markers.largeDatasetThreshold` *(v7.0, default 0 = off)* batches a series' markers into one path. Neither lives on `chart` directly.
640
+ 7. **Assuming the canvas renderer supports everything**: pattern/image fills and per-point selection visuals fall back to (or stay) SVG.
641
+ 8. **Calling `getPanel(key)` on a virtualized trellis**: it returns `null` for a panel that is not currently mounted (grids over 64 panels virtualize by default).