ApexCharts 7.2, 7.3 and 7.4: The Plugin API Grows Up
Three releases went out over five days in September, and they are easier to read as one story than as three. Two of the three are almost entirely about the plugin layer, and the third is the plugin layer plus the options that came with it.
The short version: Weave, the plugin host, went from contract v1 to v5. A plugin can now find out what kind of chart it has been put on, read the caller's own data and styling before writing anything back, follow the point the viewer is pointing at, and ask the chart to make room for UI of its own. That is the difference between a plugin that draws an overlay and a plugin that can be a whole analysis layer living outside the library.
Alongside it: hexagon heatmap cells, a way back out of a zoom on a chart with no toolbar, and an option that was removed in 7.0 coming back.
| gzip | |
|---|---|
| 7.1.0 default bundle | 264,308 B |
| 7.2.0 default bundle | 266,813 B |
| 7.3.0 default bundle | 267,241 B |
| 7.4.0 default bundle | 268,407 B |
No API breaking changes across the three. Upgrading is npm install apexcharts@7.4.0.
Weave reaches v5
The rule that matters most: do not declare the newest version
Before any of the new surface, the thing that will bite people. The gate is forward-compatible: a host serves a plugin declaring an older version, and skips only a plugin that needs a newer host than itself. So apiVersion is a minimum requirement, not a statement of what you built against.
Which means declaring the newest version is actively harmful. A plugin declaring apiVersion: 4 is skipped outright by a 7.2 host rather than served a smaller API, and a skipped plugin is silent. Declare the oldest version you genuinely cannot work without, and feature-detect the rest:
ApexCharts.registerPlugin({
name: 'panel',
apiVersion: 2, // the minimum this needs
setup(api) {
if (typeof api.reserve === 'function') { // v3, optional
api.reserve({ right: 220 })
}
},
})
Before 7.2 the gate demanded an exact match, which would have disabled every v1 plugin the moment v2 landed. Plugins written against v1 run unchanged on a v5 host.
v2: enough context to decide whether you apply
Four additions, and one fix that mattered more than any of them.
The fix first. Every Weave plugin silently did nothing on a pie, donut or radialBar. Those charts hold one number per entry rather than a row of values, and the data snapshot called .map on a number. Because dispatch builds the payload up front, the per-plugin guard caught the exception and disabled the plugin, so the failure looked like the plugin's fault rather than the host's. Each slice is now presented as a one-point series, which is what it is.
api.info tells a plugin what kind of chart it is on: the type the caller asked for (surviving the aliasing that rewrites, say, raincloud to violin), whether it is an axis chart, whether x is a datetime axis, whether the bars are horizontal, and the data-label configuration. An analysis plugin cannot work on every type, and the alternative to asking was adding a series and letting the core warn at the user.
data[].raw is the caller's own data array, untouched by parsing. The normalised points are fine to read and wrong to copy, because the three accepted shapes ([1, 2], [{x, y}], [[x, y]]) are not interchangeable: hand back the wrong one and it parses to all-null and draws nothing, without an error.
api.categories gives the resolved display labels, config-first. Reading globals.categoryLabels directly returned real labels on first paint and ordinals after any updateSeries(), because the globals are populated on mount and emptied on update.
api.markDerived(names) lets a plugin declare which series on the chart are its own. The core cannot tell a computed series from the caller's, and the host uses this to keep plugin series out of the initial-series snapshot. Without it, resetSeries() hands the user a plugin's computed output as if it were their own data.
v3: asking the chart for room
A plugin that renders its own HTML beside the chart, a docked panel or a toolbar of its own, had no good way to make space for it. The chart sizes itself from the container it was handed, so a sibling inserted into that container does not narrow the chart: the chart is drawn at full width underneath it.
Every workaround is worse than the problem. Writing chart.width means owning config the caller owns, and losing it on their next updateOptions. Positioning over the chart means guessing a size you cannot know and being clipped by any ancestor with overflow: hidden. Narrowing the container means writing to the caller's own element.
api.reserve({ right: 220 }) // a gutter for your panel
api.reserve(null) // give the space back
The host does the arithmetic, in the one place that already does it. Reservations are per plugin and summed, so two plugins each asking for a right-hand gutter get one each instead of overlapping. The total is clamped to half the container per axis, because a plugin may not reduce the chart it is annotating to nothing. And it is applied after the auto-height calculation, so a side panel narrows the chart without also shortening it and shifting the page below.
v4: following the viewer
A plugin could draw on the chart and reserve room for its own UI, but it had no way to know what the viewer was doing. To react to a hover it had to hit-test the SVG itself, re-deriving from raw pixels an answer the chart had already worked out, and then disagree with the tooltip on the same pixel.
const off = api.pointer((e) => {
// e.type 'enter' | 'leave' | 'select'
// e.seriesIndex which series
// e.dataPointIndex which point
// e.category the resolved display label, e.g. 'Mar'
// e.seriesName undefined on a pie
// e.selected on 'select' only: is the point now in or out
})
It forwards the chart's own dataPointMouseEnter, dataPointMouseLeave and dataPointSelection as one normalised payload, and returns an unsubscribe. category is the resolved label rather than an index, because a plugin coordinating two charts keys on the label: an index means something different on each chart, and reading globals.labels directly reports 3 where the chart shows Mar.
Nothing here lets a plugin intercept or cancel. The chart's tooltip, its selection state and the caller's own dataPoint* events are unaffected, a handler that throws is contained, and a chart whose plugins never ask pays nothing.
v5: the caller's own dashing
api.info.stroke.dashArray reports the caller's stroke.dashArray. That sounds minor until you try to dash a derived series: the option is indexed by series position with no per-series escape hatch, so a plugin that wants its own computed series dashed has to write the whole array, and writing one without knowing what was there discards the caller's dashed lines with nothing to restore them from.
There is no "unset" to report. The option defaults to 0, and 0 already means no dashing, so putting back what you found restores exactly what was there.
Full reference: the plugins guide.
Heatmap cell shapes (7.2)
plotOptions.heatmap.shape takes 'rect' (the default), 'hexagon', 'circle' or 'diamond'.
new ApexCharts(el, {
chart: { type: 'heatmap' },
plotOptions: { heatmap: { shape: 'hexagon' } },
series: [/* … */],
}).render()
'circle' and 'diamond' are inscribed in the cell box the heatmap already lays out, so switching to one changes the mark and nothing else. 'hexagon' is a different lattice rather than a different mark: pointy-top hexagons stretched to the cell width and 4/3 of the row pitch tall, with alternate rows offset a half cell so every neighbouring pair shares a full edge. The lattice overhangs the grid, so that overhang is reserved as a grid-padding floor and the heatmap group gets its own widened clip. Offset rows never slide under the y-axis labels and nothing is sliced at the edges.
Shaped cells are SVG paths that keep the apexcharts-heatmap-rect class and the same attribute contract, so tooltips, keyboard navigation, legend range highlighting and the colour tween on a data update all work unchanged. Three constraints: corner radius applies to 'rect' only, hexagon falls back to 'rect' on a numeric or datetime x axis, and the canvas renderer declines non-rect shapes the same way it declines image fills.
See the honeycomb demo and the heatmap guide.
A way back out of a zoom (7.4)
Drag-to-zoom is a deliberate gesture, so unlike the wheel and the pinch it is not withheld when the toolbar is hidden. The reset button that undoes it is. That leaves a viewer who drags across a bare chart inside a window with nothing on screen that puts the range back, and no key anyone would guess.
So while the chart is zoomed and nothing else on screen 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. A chart nobody zooms is untouched: toolbar: { show: false } still means an empty chart for everyone who does not zoom.
chart: {
toolbar: { show: false },
zoom: { enabled: true, resetControl: 'auto' }
}
'auto' resolves the way allowMouseWheelZoom already does: a toolbar showing its reset tool counts as a way back, anything else does not. false is for a page with its own control and turns the key off too. true also forces the control on where toolbar.tools.reset is off, which is the same dead end as a hidden toolbar.
One thing fell out of this that is worth knowing on its own: a completed drag-zoom now focuses the chart. It did not before, because the drag is swallowed before the browser can move focus, so Escape reached nothing. That also puts the +, - and 0 keyboard-zoom keys within reach after a mouse zoom for the first time.
The gates on the wheel and the pinch are deliberately left where they are. This control arrives after the fact, while what an incidental wheel zoom takes first is the page scroll it swallowed, which no button hands back.
borderRadiusWhenStacked comes back (7.4)
If you read the 7.0 release notes and deleted this option, you can set it again.
It was removed in 7.0 because corner ownership was made to follow the stack's outer edge, which fixed a real bug: toggling a series used to re-resolve the rounded cap from the new state while the old bar was still on screen, inverting caps on the departing layer. That fix was right. Removing the choice along with it was not, because squaring a stack off on its baseline is a legitimate look and the new geometry had no way to ask for it.
plotOptions: {
bar: {
borderRadius: 6,
borderRadiusWhenStacked: 'last' // 'all' is the default
}
}
'all' caps both ends of the stack, the baseline and the far end. 'last' caps only the far end. Either way the segments in the middle stay square, and a grouped stacked chart resolves its outermost segments per group rather than chart-wide. On 7.0 through 7.3 the option is ignored.
Shipped in the same commit: the corners of a single-point stack. One data point used to hand its baseline segment the wrong corner, which on a horizontal stack rounded the first segment on its inner edge and left a pill in the middle of the bar.
Fixes
The initialSeries baseline. Four commits and one bug. The snapshot that resetSeries() and the toolbar's reset restore moved under the caller: on internal re-renders, on every appendData path, on the raw-stash chart types, and when a legend collapse reconcile ran. parseData now owns the capture, so the baseline is the caller's series and stays that way.
Legend collapses survive a data update (7.2). A series the viewer switched off in the legend used to come back on the next update. It stays off. This is the one deliberate behaviour change in the three releases.
CSV export. Categories that print alike collapsed into one another, and unequal-x rows were keyed through Object.prototype.
Axes. Inferred category labels were lost on the x axis, inferred numeric ticks did not honour their own semantics, and y-axis text alignment iterated the wrong thing and ran for hidden axes.
SSR. Text bounds are now estimated so the DOM shim can measure labels.
Stacked combos, annotations, tooltips. Combo columns stack on the layer below rather than on the axis; long point-annotation labels and their background boxes are clamped inside the grid; the treemap tooltip stays inside the plot area; brush selection handles stay inside the data range; and every grouped tooltip read is paired with its own chart's pointer, which fixes sparkline tooltip sync.
Dependencies. apex-commons moves from ^0.5.0 to ^0.8.0. A caret on a 0.x version pins the minor, so every published ApexCharts since 0.5.0 shipped resolved a commons three minors old, silently: nothing breaks and nothing warns when a caret cannot reach a newer minor. The bundle carries commons rather than importing it at runtime, so 7.4 is the release that actually moves it.
Prerelease tags changed (7.3)
Worth flagging separately, because it changes an install command people have written down.
npm i apexcharts@next now fails, with "No matching version". Prereleases publish under a tag naming their own release line instead, so 7.4.0-rc.1 published under rc-7.4.
A floating next has to be maintained. The moment a stable ships, it points at something older than latest, and npm i apexcharts@next quietly installs the wrong thing. Moving it back is a registry write, and npm's OIDC trusted publishing covers npm publish only, so a workflow that fixed it would need a publish-capable token in a repo that deliberately holds none. A tag naming a line never goes stale, because it states a fact rather than a position. Failing is the honest answer and better than an old one. Each prerelease's notes name its exact tag, and so does the installation guide.
Contributors
Around fifteen merged pull requests across 7.2 and 7.4, clustered on the parts of the library that are hardest to get right from the inside. Thanks to @jamalkamaladdin (the Azerbaijani locale, stacked combo columns, point-annotation bounds, brush selection handles, the treemap tooltip), @lazerg (the SSR text-bounds fix, the appendData baseline paths, stacked border-radius edge cases), @gioboa (grouped sparkline tooltip sync, the borderRadiusWhenStacked work, a tooltip perf pass), @81reap (both CSV export fixes), @lovasoa (inferred x-axis label and tick semantics), @tarzan77cz (y-axis label alignment), @aron-intframe (the initialConfig series-snapshot fix) and @GebleaAlex (README links).
Upgrading
npm install apexcharts@7.4.0
- Nothing to remove. No option was removed across the three releases.
- Check any chart that relies on a legend collapse resetting. Since 7.2 a series the viewer switched off stays off across a data update. Call
showSeries()if you need the old behaviour. - If you set
borderRadiusWhenStackedbefore 7.0, you can set it again. - If you install from
@next, switch to the release-line tag your prerelease names. - If you wrote a Weave plugin, do not raise its
apiVersionto reach the new surface. Feature-detect instead.
Frequently asked questions
Will upgrading from ApexCharts 7.1 to 7.4 break my charts?
No option was removed and no default changed in a way that alters an existing chart's output. The upgrade is `npm install apexcharts@7.4.0`. There is one deliberate behaviour change, in 7.2: a series the viewer switched off in the legend now stays off across a data update, where it used to come back. If your app relies on an update restoring every series, call `showSeries()` explicitly.
What version is the ApexCharts Weave plugin API now, and do I need to change my plugin?
The contract is at v5 as of 7.4. You do not need to change anything: every change has been additive and the version gate is forward-compatible, so a plugin declaring `apiVersion: 1` runs unchanged on a v5 host. What you should NOT do is raise your `apiVersion` to 5 to use the new surface, because a host older than the version you declare skips your plugin outright rather than serving it a smaller API. Declare the oldest version you genuinely need and feature-detect the rest, for example `typeof api.pointer === 'function'`.
How do I make a hexagon or honeycomb heatmap in ApexCharts?
Set `plotOptions.heatmap.shape` to `'hexagon'`. It draws a real tessellating honeycomb: alternate rows are offset by half a cell so neighbouring hexagons share a full edge, and the lattice's overhang is reserved as grid padding so offset rows never slide under the y-axis labels. It applies to the categorical layout only, falls back to `'rect'` on a numeric or datetime x axis, and renders as SVG rather than through the canvas renderer. `'circle'` and `'diamond'` are also available and are inscribed in the existing cell box.
Why does my chart show a reset button even though I hid the toolbar?
Because 7.4 draws one while the chart is zoomed and nothing else on screen can reset it. Drag-to-zoom stays enabled when the toolbar is hidden, but the reset button did not, which left a viewer inside a zoom with no way out. The control appears only after a zoom and disappears with it, so a chart nobody zooms is unchanged. Set `chart.zoom.resetControl: false` if your page supplies its own reset control, which turns off the Escape shortcut too.
Is borderRadiusWhenStacked back in ApexCharts?
Yes, in 7.4. It was removed in 7.0 because corner ownership was made to follow the stack's outer edge, which fixed a real bug but also removed a legitimate choice. It is back on top of the corrected geometry: `'all'` (the default) caps both ends of the stack, `'last'` caps only the far end so the stack sits square on its baseline. On 7.0 through 7.3 the option is ignored.
Why does npm install apexcharts@next fail now?
On purpose, since 7.3. Prereleases publish under a tag naming their own release line, such as `rc-7.4`, rather than under a floating `next`. A floating `next` has to be maintained: the moment a stable ships it points at something older than `latest` and quietly installs the wrong thing. A tag naming a line states a fact rather than a position, so it never goes stale. Each prerelease's notes name its exact tag.