npm apexcharts 6.8.0
💎 Version 6.8.0

5 hours ago

A minor release: dataLabels.offsetX / offsetY now accept a function, so a label can be nudged per data point instead of per chart. Everything else is a fix, spanning sparkline layout, brush auto-scaling, CSP-safe SVG export, threshold gradients and CSV export.

One deliberate visual change: area sparklines lose the empty strip under the fill (see below). Every other existing config renders as it did on 6.7.1.

✨ New

Per-data-point dataLabels offsets (#5107)

dataLabels.offsetX and dataLabels.offsetY now take number | ((opts) => number). The function receives the same { series, seriesIndex, dataPointIndex, w } signature that dataLabels.style.colors already accepts, so labels that collide between two series at the same x can be pushed apart:

dataLabels: {
  offsetY: ({ seriesIndex }) => (seriesIndex === 0 ? -12 : 12),
}

dataLabels is chart-wide config, which is why a plain array keyed by data point index could not solve this: it would apply identically to every series, and the reported overlap is between series. A function also survives updateSeries, where captured indices would otherwise desync. 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, which 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 and 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

🐛 Fixes

Area sparklines no longer leave a gap under the fill (#5137)

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. Where the stroke traces the data points (line, area, scatter, unstacked), the distance from the extreme datum to the axis extreme already swallows part or all of the overhang, so only the remainder is reserved. Fills reserve nothing since they are drawn unstroked, and stroke.show: false reserves nothing at all. Anything that strokes to the baseline or fills the plot (bar, heatmap, candlestick, stacked) keeps the full reservation, as does every non-axis sparkline. 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:

  • Dimensions.gridPad aliased config.grid.padding, so layout insets were written back into the user's own config object, accumulated across renders, and were then read by Core.resizeNonAxisCharts as though the user 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 ([0,6] > 0 is NaN > 0), so array-sized markers got no padding and were clipped by 6.5px. It now gates on globals.markers.largestSize, covering both markers.size and markers.discrete.

autoScaleYaxis no longer drops a boundary point on a brush selection (#5251)

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.

SVG export is CSP-safe (#5146)

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.

The bulk of the work was keeping export fidelity while dropping that tag. 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 that 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.

Thanks to @waterWang for the fix (#5257).

Threshold gradients align with the threshold (#5209)

plotOptions.line.colors.threshold gradients are now positioned over the axis range. Null values in an area chart with threshold colors are handled correctly, and 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 (#5261).

CSV export honours columnDelimiter on unequal-x rows

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, producing output no parser could read:

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

The default , hid it entirely, which is why it went unnoticed.

Thanks to @Jaybhade for the fix (#5253).

🔧 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 (#5250), ip-address 10.4.0 (#5249)

Full Changelog: v6.7.1...v6.8.0

Don't miss a new apexcharts release

NewReleases is sending notifications on new releases.