Two default changes to know about before upgrading, both about data labels.
Data labels that land on each other are now nudged apart. A chart whose labels already clear each other is untouched, but anywhere two of them currently overlap, they will move. Turn it off with:
dataLabels: { avoidOverlap: false }
This matters most on a chart with two y-axes, where the axes are scaled independently and a collision is therefore not a question of how close the values are: a column at 59.5K and a line at 68K can land on the same pixel row while 51K and 51.3K sit well apart. Labels separate along the value axis, so a horizontal bar's move sideways rather than across its rows. A pair that cannot be separated is left overlapping rather than quietly dropped, since removing a value is worse than the overlap; pass { hide: true } if you would rather lose one. Rotated labels and the radial types keep their own placement.
It is on by default because a label sitting on another is never what the chart meant to say, and the pass does nothing to a chart that does not need it. Across the 320 demo charts that predate it, not one renders differently.
A waterfall's data labels have lost their pale chip. The ink follows chart.foreColor instead, which reads over a rising, falling or total bar in either theme, and on the small steps whose label sits outside the bar. The chip is still there if you want it:
dataLabels: { background: { enabled: true } }
It is worth more than it was, incidentally. The chip used to inherit the range column's white ink, and a chip takes its fill from its text, so on a light theme it was white on white.
Two performance fixes ship without a section of their own. A CPU profile of a hover sweep over a 700-series chart put 38% of all hover time inside native querySelector and another 10% in forced layout, from lookups repeated per marker rather than per gesture; both are now done once per gesture. Separately, data label backgrounds are measured in one pass instead of relaying the SVG out once per label, which on thousands of labels is the difference between seconds and milliseconds.
dataLabels.style.colors entries may be functions. That has always been true at runtime and the documentation has always said so; the TypeScript declaration said string[] and now agrees.
| gzip | |
|---|---|
| 7.7.0 default bundle | 272,129 B |
| 7.8.0 default bundle | 273,832 B |
Both are dist/apexcharts.min.js gzipped at the default level, which is the figure npm run build prints.
Upgrading is npm install apexcharts@7.8.0.
✨ New
Drop the chip behind data labels
A waterfall printed every step on a pale rounded chip. On a dense bridge that is a second rectangle per bar competing with the bar it names, and the type is usually a dense bridge.
The chip was not decoration, though, so removing it alone would reopen what it was added to fix. Small steps are normal in a waterfall, and a label wider or taller than its bar is placed OUTSIDE it; the range-column defaults the type inherits draw labels in white, so those labels became white text on the chart background and the smallest steps of a P&L bridge simply vanished.
So the ink moves instead of hiding behind a chip. dataLabels.style.colors now carries a function that resolves to chart.foreColor, which is dark on a light theme and light on a dark one, and reads over an increase, decrease or total bar either way. Verified in both modes: #373d3f on light, #f6f7f8 on dark, and no rect in the DOM.
An entry in that array has always been allowed to be a function - the resolution in plotDataLabelsText calls one if it finds one - but the declaration said string[]. It now says what the implementation does.
Set dataLabels.background.enabled to get the chip back.
Keep labels from landing on each other
Reported from the field: a dual-axis chart printed "$59.53K$59.53K" where a column series and a line series coincided, and a waterfall ran its currency labels together between adjacent quarters.
dataLabelsCorrection looks like it already handles this, but it cannot. It is keyed on dataLabelsRects[i], one series, and it runs WHILE that series is plotting, so the labels it would collide with do not exist yet. It also measures the raw text rather than the background pill, which is why a few pixels of pill overlap got through even within one series.
dataLabels.avoidOverlap is a pass over every label once they are all drawn, before the pills are cut. It is on by default: a label sitting on another is never what the chart meant to say, and the pass is a no-op on a chart whose labels already clear each other.
The thing worth knowing for a multi-axis chart is that collisions are NOT a function of how close the values are. The two axes scale independently, so only pixels decide. Sweeping the line series from -30% to +30% of the columns in 1% steps: collides -27%..0%, clean +1%..+11%, collides again +12%..+17%, clean after that. 34 of 61 configurations collided, 88 pairs in total, 0 after.
Design, each point of which was a defect found by auditing 25 configurations across types, orientations and label positions:
- Separation runs along the VALUE axis, not a fixed vertical. On a horizontal
bar the vertical axis is the CATEGORY axis, so nudging there walks a label
into the next row; those transpose and move along x instead. Vertical-only
separation cost 8 of 18 labels on a crowded horizontal bar. - Dropping a label is opt-in (
{hide: true}). A default-on pass that silently
deletes a value is worse than the overlap it set out to fix, so an
unseparable pair is left exactly as it renders today. - Boxes are measured with
getBoundingClientRect, notgetBBox. getBBox is
taken before the element's own transform, so a label under
plotOptions.bar.dataLabels.orientation: 'vertical'measures 29x14 while it
occupies 14x29. getBBox remains the SSR fallback, where the DOM shim
estimates text extents but has no client rect. - A rotated label is an obstacle, never a mover: its
yattribute runs along
its own rotated axis, so writing to it would slide it sideways. - Radial types opt out. Radar lost 3 of 12 labels to a vertical nudge before
the skip; pie and friends run their own de-overlap already. - Pairwise relaxation, not column packing, which would chain a dense row into
one tall stack through its neighbours. Each pair splits the push and each
label is capped atmaxShiftfrom its own mark. - Bounds are the plot with no slack, since past that edge a label is clipped.
A label that legitimately starts outside, the one above a bar that reaches
the top of the grid, keeps its place: the clamp restricts movement and never
forces it.
Geometry lives in its own helper over plain numbers, so it is tested without a DOM; the DOM half needs real text metrics and is tested in the browser. Both suites assert the collision is still there with the option off, so neither can pass vacuously.
Two samples carry currency formatters and the reported shapes: a 15-step waterfall bridge and the dual-axis combo.
🐛 Fixes
Serve the ajax demos from our own db.json
Both ajax demos fetched http://my-json-server.typicode.com/apexcharts/ apexcharts.js/yearly, a third-party fake-API. That service is gone: it answers 530 with Cloudflare error 1016, an origin DNS failure, over http and https alike. So the demos have been showing "Loading..." forever on the site, and Test Reproducibility has failed on misc/axios on every push since 2026-10-01, which is why main has been red for seven runs. The e2e runner fails a sample on console errors, and a dead fetch logs one.
my-json-server was only ever serving db.json out of this repository, and that file is still here, so the fix is to read it from a CDN that serves this repository directly. The payload is the whole object rather than one key, so both demos now take .yearly off it.
The data is identical, which the snapshots confirm: both samples pass against the references captured while the old endpoint was alive, with no baseline update. The full suite is 322 passing, 0 failing - the first clean e2e run in a while, since these two were the standing failures.
These demos still pull jquery and axios themselves from cdnjs, so they are not free of the network, but they no longer depend on a service nobody here controls.