ApexCharts 6.8 is a small release. One new capability, and the rest is fixes.

That is worth saying plainly rather than dressing up: if you are not using dataLabels offsets, not rendering area sparklines, and not exporting SVG under a Content Security Policy, there is nothing here you need. If you are doing any of those, read on, because a couple of these were longstanding and one is a visible change.

Key takeaways

  • New: dataLabels.offsetX and offsetY accept a function, resolved per data point.
  • Visual change: area sparklines no longer leave an empty strip under the fill. This is deliberate, and it is the one thing to check before upgrading.
  • CSP: SVG export no longer injects a <style> element, so it works under a strict Content Security Policy.
  • Fixed: autoScaleYaxis dropping a boundary point on a brush selection, threshold gradients drifting off the threshold, and CSV export ignoring columnDelimiter on unequal-x rows.

Per-data-point dataLabels offsets

dataLabels.offsetX and dataLabels.offsetY now take number | ((opts) => number). The function receives the same { series, seriesIndex, dataPointIndex, w } argument that dataLabels.style.colors already accepts.

The problem it solves: two series with a value at the same x, whose labels land on top of each other.

// before: one offset for every label in the chart, so nudging one series
// moved the other with it
dataLabels: {
  offsetY: -12,
}

// after: resolved per point, so the two series separate
dataLabels: {
  offsetY: ({ seriesIndex }) => (seriesIndex === 0 ? -12 : 12),
}

An array would have been the obvious API, and it is worth explaining why it is not the one. dataLabels is chart-wide config, so an array keyed by data point index applies identically to every series, which is exactly the wrong axis for the collision being solved. A function also survives updateSeries, where indices captured in a pre-built array would desync from the data. Keep it pure, as it may be called more than once per label.

Resolution now runs through one shared helper across the line/area, bar, treemap and radar paths, and consolidating it fixed four latent defects on the way:

  • line, area and scatter labels all vanished when the offset was non-numeric, because x was computed above the isNaN(x) guard, so the guard could never fire
  • the slope chart branch read the raw config value instead of the resolved one, yielding a NaN x coordinate
  • radar passed its series index as the data point index, so per-point offsets shifted whole series
  • bar and rangeBar invoked the user function a second time at draw time for a value they discard

Documented on the dataLabels options page.

Area sparklines lose the gap under the fill

This is the one visible change in the release.

A sparkline reserved stroke.width / 2 of grid padding at the top and bottom unconditionally. An area sparkline's fill runs to the baseline, so that bottom inset showed as a strip of empty space under the fill: 2px at the default 4px area stroke.

The inset now reserves only what the ink cannot absorb itself:

Chart shapeReserved
Stroke traces the data points (line, area, scatter, unstacked)Only the overhang the distance from the extreme datum to the axis extreme does not already swallow
Fills drawn unstrokedNothing
stroke.show: falseNothing
Strokes to the baseline or fills the plot (bar, heatmap, candlestick, stacked)The full reservation
Every non-axis sparklineThe full reservation

Room is measured against the smallest plot the insets could leave, so the estimate errs toward over-reserving and can never clip.

Two defects found while measuring this are fixed alongside it, and both are worth knowing because neither was sparkline-specific:

  • Dimensions.gridPad aliased config.grid.padding, so layout insets were written back into your own config object, accumulated across renders, and were then read by Core.resizeNonAxisCharts as though you had asked for them. The resolved padding is now a copy, published as w.layout.gridPad.
  • the sparkline marker padding gate tested markers.size > 0, which is false for an array, since [0,6] > 0 is NaN > 0. Array-sized markers therefore got no padding and were clipped by 6.5px. It now gates on globals.markers.largestSize, covering both markers.size and markers.discrete.

If you had added compensating padding to work around the empty strip, remove it.

SVG export is CSP-safe

getSvgString() and the SVG download no longer inject a <style> element, so exports work under a strict Content Security Policy. Styles are inlined onto the elements instead.

Dropping the tag was the easy half. Keeping export fidelity was the work, and the details show why this had not been done sooner:

  • The legend stylesheet was injected into a descendant of the exported wrapper, so it was cloned and serialized anyway and still tripped CSP.
  • Transient overlays were hidden only at the first match per selector, so a chart with several (one yaxis tooltip per y-axis, an extra element for point annotations) rendered the leftovers visibly, since their opacity: 0 came from the stylesheet the export no longer carries.
  • Inline styles set by modules are no longer clobbered, which preserves legend.fontSize (the legend box is measured at that size, so a hardcoded 14px overflowed) and the heatmap gradient legend's deliberate overrides.
  • Rules the inlined subset had dropped are restored: flex-wrap and flex-direction for side and grouped-horizontal legends, alignment, legend-group display, marker positioning, the !important on hidden zero and null series, and the flip transforms used by rounded stacked bars.

With injectStyleSheet: false, which is what a strict-CSP app sets, side legends had been exporting as a single horizontal row and bottom legends had stopped wrapping. Both are fixed.

Thanks to @waterWang for the fix.

autoScaleYaxis keeps the boundary point on a brush selection

A brush selection reconstructs its x range from the selection rect's DOM bounds, so the pixel-to-timestamp round trip can land xaxis.max a sub-pixel fraction below the timestamp of the boundary data point. The y-extrema window trimmed on a strict compare, so that point was excluded from the scale while its marker and the line segment leading to it were still painted, and the line escaped or clipped at the top of the grid.

Reaching the same window by panning scaled correctly, which is what made it look arbitrary.

The trim window is now widened by one rendered pixel, expressed in data units from the current x-domain-to-pixel ratio rather than a fixed timestamp epsilon. Both edges are covered, since a sub-pixel overshoot on xaxis.min drops the leftmost point the same way. This also covers a programmatic zoomX() with fractional bounds, and the xaxis.min / xaxis.max reported to your selection event are unchanged.

Threshold gradients align with the threshold

plotOptions.line.colors.threshold gradients are now positioned over the axis range, and null values in an area chart with threshold colors are handled correctly. Three further problems in the same area are fixed:

  • the offset was derived from the data range while being mapped over the axis range, so the color transition drifted off the threshold whenever the axis extended past the data, via an explicit yaxis.min / max, a nice scale, or a shared axis
  • the anchoring was gated on a chart-global null-values flag, which re-anchored every vertical gradient in the chart, including plain gradient fills with no threshold configured. It is now keyed off the threshold feature itself.
  • chart.type: 'line', the primary consumer of plotOptions.line.colors, had the identical split-segment defect and was excluded by a type gate

Reversed axes now mirror both the boundary and the stop order, and stops are emitted in ascending order rather than relying on the SVG rule that clamps an out-of-order offset.

Thanks to @waterWang for the fix.

CSV export honours columnDelimiter

The unequal-x branch of exportToCSV pushed an array onto rows rather than a delimiter-joined string, so Array.prototype.toString stringified it with a hardcoded comma. Every data row separated the category from its first value with , while the header and remaining values used the configured delimiter:

category;series 1;series 2
0,0;
1,1;1

No parser could read that. The default , hid it entirely, which is why it went unnoticed.

Thanks to @Jaybhade for the fix.

Internal

  • resolveDataLabelOffset lives in modules/helpers/DataLabelOffset.js rather than the shared DataLabels module, so the split per-chart bundles inline it and core.js is untouched.
  • Dependency bumps: undici 7.29.0, ip-address 10.4.0.

How do I upgrade?

npm install apexcharts@latest

Or bump the version on your CDN link. The React, Vue and Angular wrappers work unchanged.

The only thing to check is the area sparkline change above. Everything else either fixes behavior you were working around or does not affect you.

Full changelog on GitHub.

Frequently asked questions

What is new in ApexCharts 6.8?

One new capability and a set of fixes. dataLabels.offsetX and dataLabels.offsetY now accept number | ((opts) => number), so a label can be offset per data point rather than per chart. The function receives the same { series, seriesIndex, dataPointIndex, w } argument that dataLabels.style.colors already accepts. The fixes cover area sparklines leaving an empty strip under the fill, autoScaleYaxis dropping a boundary point on a brush selection, SVG export not working under a strict Content Security Policy, threshold gradients drifting off the threshold, and CSV export ignoring columnDelimiter on unequal-x rows.

Are there breaking changes in ApexCharts 6.8?

No API was renamed or removed. There is one deliberate visual change: an area sparkline no longer reserves stroke padding that its fill cannot absorb, so it loses the strip of empty space that used to sit under the fill, 2px at the default 4px area stroke. Every other existing config renders as it did on 6.7.1.

Why can't I use an array for per-point dataLabels offsets?

Because dataLabels is chart-wide config, not per-series config. An array keyed by data point index would apply identically to every series, and the overlap this solves is usually between series at the same x. A function also survives updateSeries, where captured indices would desync. Keep the function pure, as it may be called more than once per label.

Does ApexCharts SVG export work with a Content Security Policy?

Yes, from 6.8.0. getSvgString() and the SVG download no longer inject a style element; styles are inlined onto the exported elements instead, so exports work under a strict CSP and with injectStyleSheet: false. Export fidelity was preserved in the process: side and grouped-horizontal legend flex rules, legend font size, marker positioning, heatmap gradient legend overrides, and the flip transforms used by rounded stacked bars are all still applied.

How do I upgrade to ApexCharts 6.8?

Run npm install apexcharts@latest, or bump the version on your CDN link. The React, Vue and Angular wrappers work unchanged. If you render area sparklines and had compensated for the empty strip under the fill, remove that compensation.