apexcharts 8.2.0 → 8.3.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.
Files changed (222) hide show
  1. package/README.md +7 -7
  2. package/dist/apexcharts.common.js +2 -2
  3. package/dist/apexcharts.core.js +271 -173
  4. package/dist/apexcharts.core.min.js +2 -2
  5. package/dist/apexcharts.css +35 -0
  6. package/dist/apexcharts.esm.js +1722 -1527
  7. package/dist/apexcharts.full.js +1459 -1075
  8. package/dist/apexcharts.full.min.js +2 -2
  9. package/dist/apexcharts.js +994 -715
  10. package/dist/apexcharts.min.js +2 -2
  11. package/dist/apexcharts.ssr.common.js +2 -2
  12. package/dist/apexcharts.ssr.esm.js +994 -715
  13. package/dist/area.common.js +2 -2
  14. package/dist/area.esm.js +91 -40
  15. package/dist/bar.common.js +2 -2
  16. package/dist/bar.esm.js +433 -394
  17. package/dist/bar.js +2 -2
  18. package/dist/boxPlot.common.js +2 -2
  19. package/dist/boxPlot.esm.js +364 -322
  20. package/dist/bubble.common.js +2 -2
  21. package/dist/bubble.esm.js +91 -40
  22. package/dist/candlestick.common.js +2 -2
  23. package/dist/candlestick.esm.js +364 -322
  24. package/dist/candlestick.js +2 -2
  25. package/dist/column.common.js +2 -2
  26. package/dist/column.esm.js +433 -394
  27. package/dist/core.common.js +2 -2
  28. package/dist/core.esm.js +271 -173
  29. package/dist/donut.common.js +1 -1
  30. package/dist/donut.esm.js +1 -1
  31. package/dist/dumbbell.common.js +2 -2
  32. package/dist/dumbbell.esm.js +433 -394
  33. package/dist/features/all.common.js +2 -2
  34. package/dist/features/all.esm.js +250 -187
  35. package/dist/features/annotations.common.js +1 -1
  36. package/dist/features/annotations.esm.js +1 -1
  37. package/dist/features/annotations.js +1 -1
  38. package/dist/features/context-menu.common.js +1 -1
  39. package/dist/features/context-menu.esm.js +1 -1
  40. package/dist/features/context-menu.js +1 -1
  41. package/dist/features/drilldown.common.js +2 -2
  42. package/dist/features/drilldown.esm.js +4 -2
  43. package/dist/features/drilldown.js +2 -2
  44. package/dist/features/dumbbell.common.js +1 -1
  45. package/dist/features/dumbbell.esm.js +1 -1
  46. package/dist/features/dumbbell.js +1 -1
  47. package/dist/features/exports.common.js +1 -1
  48. package/dist/features/exports.esm.js +1 -1
  49. package/dist/features/exports.js +1 -1
  50. package/dist/features/facet.common.js +1 -1
  51. package/dist/features/facet.esm.js +1 -1
  52. package/dist/features/facet.js +1 -1
  53. package/dist/features/highlight-filter.common.js +2 -2
  54. package/dist/features/highlight-filter.esm.js +14 -9
  55. package/dist/features/highlight-filter.js +2 -2
  56. package/dist/features/history.common.js +1 -1
  57. package/dist/features/history.esm.js +1 -1
  58. package/dist/features/history.js +1 -1
  59. package/dist/features/ink.common.js +1 -1
  60. package/dist/features/ink.esm.js +1 -1
  61. package/dist/features/ink.js +1 -1
  62. package/dist/features/keyboard.common.js +2 -2
  63. package/dist/features/keyboard.esm.js +97 -145
  64. package/dist/features/keyboard.js +2 -2
  65. package/dist/features/legend.common.js +2 -2
  66. package/dist/features/legend.esm.js +63 -43
  67. package/dist/features/legend.js +2 -2
  68. package/dist/features/link.common.js +1 -1
  69. package/dist/features/link.esm.js +1 -1
  70. package/dist/features/link.js +1 -1
  71. package/dist/features/marks.common.js +1 -1
  72. package/dist/features/marks.esm.js +1 -1
  73. package/dist/features/marks.js +1 -1
  74. package/dist/features/measure.common.js +1 -1
  75. package/dist/features/measure.esm.js +1 -1
  76. package/dist/features/measure.js +1 -1
  77. package/dist/features/morph.common.js +1 -1
  78. package/dist/features/morph.esm.js +1 -1
  79. package/dist/features/morph.js +1 -1
  80. package/dist/features/perspectives.common.js +1 -1
  81. package/dist/features/perspectives.esm.js +1 -1
  82. package/dist/features/perspectives.js +1 -1
  83. package/dist/features/raincloud.common.js +1 -1
  84. package/dist/features/raincloud.esm.js +1 -1
  85. package/dist/features/raincloud.js +1 -1
  86. package/dist/features/renderer-canvas.common.js +2 -2
  87. package/dist/features/renderer-canvas.esm.js +2 -1
  88. package/dist/features/renderer-canvas.js +2 -2
  89. package/dist/features/stats.common.js +1 -1
  90. package/dist/features/stats.esm.js +1 -1
  91. package/dist/features/stats.js +1 -1
  92. package/dist/features/storyboard.common.js +1 -1
  93. package/dist/features/storyboard.esm.js +1 -1
  94. package/dist/features/storyboard.js +1 -1
  95. package/dist/features/streamgraph.common.js +2 -2
  96. package/dist/features/streamgraph.esm.js +3 -2
  97. package/dist/features/streamgraph.js +2 -2
  98. package/dist/features/toolbar.common.js +2 -2
  99. package/dist/features/toolbar.esm.js +92 -2
  100. package/dist/features/toolbar.js +2 -2
  101. package/dist/features/trellis.common.js +2 -2
  102. package/dist/features/trellis.esm.js +83 -47
  103. package/dist/features/trellis.js +2 -2
  104. package/dist/features/waterfall.common.js +1 -1
  105. package/dist/features/waterfall.esm.js +1 -1
  106. package/dist/features/waterfall.js +1 -1
  107. package/dist/features/weave.common.js +1 -1
  108. package/dist/features/weave.esm.js +1 -1
  109. package/dist/features/weave.js +1 -1
  110. package/dist/full.common.js +2 -2
  111. package/dist/full.esm.js +950 -795
  112. package/dist/heatmap.common.js +2 -2
  113. package/dist/heatmap.esm.js +3 -17
  114. package/dist/heatmap.js +2 -2
  115. package/dist/histogram.common.js +2 -2
  116. package/dist/histogram.esm.js +433 -394
  117. package/dist/icicle.common.js +2 -2
  118. package/dist/icicle.esm.js +54 -38
  119. package/dist/icicle.js +2 -2
  120. package/dist/line.common.js +2 -2
  121. package/dist/line.esm.js +91 -40
  122. package/dist/line.js +2 -2
  123. package/dist/locales/en.json +2 -1
  124. package/dist/locales/ro.json +3 -2
  125. package/dist/pictograms.common.js +1 -1
  126. package/dist/pictograms.esm.js +1 -1
  127. package/dist/pictograms.js +1 -1
  128. package/dist/pie.common.js +1 -1
  129. package/dist/pie.esm.js +1 -1
  130. package/dist/pie.js +1 -1
  131. package/dist/polarArea.common.js +1 -1
  132. package/dist/polarArea.esm.js +1 -1
  133. package/dist/radar.common.js +2 -2
  134. package/dist/radar.esm.js +5 -4
  135. package/dist/radar.js +2 -2
  136. package/dist/radialBar.common.js +1 -1
  137. package/dist/radialBar.esm.js +1 -1
  138. package/dist/radialBar.js +1 -1
  139. package/dist/raincloud.common.js +2 -2
  140. package/dist/raincloud.esm.js +72 -26
  141. package/dist/rangeArea.common.js +2 -2
  142. package/dist/rangeArea.esm.js +91 -40
  143. package/dist/rangeBar.common.js +2 -2
  144. package/dist/rangeBar.esm.js +433 -394
  145. package/dist/scatter.common.js +2 -2
  146. package/dist/scatter.esm.js +91 -40
  147. package/dist/streamgraph.common.js +2 -2
  148. package/dist/streamgraph.esm.js +93 -41
  149. package/dist/sunburst.common.js +2 -2
  150. package/dist/sunburst.esm.js +103 -93
  151. package/dist/sunburst.js +2 -2
  152. package/dist/treemap.common.js +2 -2
  153. package/dist/treemap.esm.js +105 -86
  154. package/dist/treemap.js +2 -2
  155. package/dist/unit-shapes.common.js +1 -1
  156. package/dist/unit-shapes.esm.js +1 -1
  157. package/dist/unit-shapes.js +1 -1
  158. package/dist/unit.common.js +2 -2
  159. package/dist/unit.esm.js +280 -202
  160. package/dist/unit.js +2 -2
  161. package/dist/violin.common.js +2 -2
  162. package/dist/violin.esm.js +72 -26
  163. package/dist/violin.js +2 -2
  164. package/dist/waterfall.common.js +2 -2
  165. package/dist/waterfall.esm.js +432 -393
  166. package/package.json +1 -1
  167. package/src/apexcharts.js +13 -6
  168. package/src/assets/apexcharts.css +35 -0
  169. package/src/charts/Bar.js +62 -55
  170. package/src/charts/BarStacked.js +80 -88
  171. package/src/charts/BoxCandleStick.js +28 -31
  172. package/src/charts/Icicle.js +37 -37
  173. package/src/charts/Line.js +118 -42
  174. package/src/charts/Radar.js +5 -7
  175. package/src/charts/RangeBar.js +47 -43
  176. package/src/charts/Sunburst.js +101 -98
  177. package/src/charts/Treemap.js +108 -66
  178. package/src/charts/Unit.js +368 -250
  179. package/src/charts/Violin.js +48 -30
  180. package/src/charts/common/Breadcrumb.js +2 -2
  181. package/src/charts/common/Hierarchy.js +32 -11
  182. package/src/charts/common/Jitter.js +32 -7
  183. package/src/charts/common/bar/DataLabels.js +76 -81
  184. package/src/charts/common/bar/Helpers.js +43 -7
  185. package/src/charts/common/line/Helpers.js +12 -15
  186. package/src/charts/common/partition/NodeTooltip.js +8 -1
  187. package/src/charts/common/partition/Partition.js +33 -1
  188. package/src/charts/common/treemap/Helpers.js +3 -11
  189. package/src/locales/en.json +2 -1
  190. package/src/locales/ro.json +3 -2
  191. package/src/modules/Core.js +4 -0
  192. package/src/modules/CoreUtils.js +18 -42
  193. package/src/modules/Data.js +23 -24
  194. package/src/modules/DataLabels.js +6 -6
  195. package/src/modules/Graphics.js +62 -49
  196. package/src/modules/Markers.js +3 -1
  197. package/src/modules/Range.js +44 -29
  198. package/src/modules/RendererController.js +17 -3
  199. package/src/modules/Scales.js +32 -9
  200. package/src/modules/Series.js +39 -18
  201. package/src/modules/Theme.js +1 -1
  202. package/src/modules/ZoomPanSelection.js +119 -2
  203. package/src/modules/accessibility/KeyboardNavigation.js +114 -186
  204. package/src/modules/animations/LengthTransition.js +22 -2
  205. package/src/modules/drilldown/Breadcrumb.js +5 -2
  206. package/src/modules/helpers/Destroy.js +1 -1
  207. package/src/modules/highlightFilter/HighlightFilter.js +8 -9
  208. package/src/modules/highlightFilter/TreemapPart.js +2 -2
  209. package/src/modules/legend/HeatmapGradientLegend.js +76 -45
  210. package/src/modules/legend/Helpers.js +2 -0
  211. package/src/modules/legend/Legend.js +11 -12
  212. package/src/modules/settings/Options.js +7 -0
  213. package/src/modules/streamgraph/StreamLabels.js +1 -0
  214. package/src/modules/tooltip/Tooltip.js +20 -4
  215. package/src/modules/tooltip/Utils.js +8 -0
  216. package/src/modules/trellis/Trellis.js +6 -0
  217. package/src/modules/trellis/TrellisChrome.js +18 -7
  218. package/src/modules/trellis/TrellisSync.js +2 -0
  219. package/src/renderers/canvas/CanvasGraphics.js +1 -0
  220. package/src/ssr/DOMShim.js +10 -0
  221. package/src/types/internal.d.ts +9 -3
  222. package/types/apexcharts.d.ts +31 -12
@@ -1,7 +1,6 @@
1
1
  // @ts-check
2
2
  import Graphics from '../Graphics'
3
3
  import TooltipUtils from '../tooltip/Utils'
4
- import Utils from '../../utils/Utils'
5
4
 
6
5
  /**
7
6
  * ApexCharts KeyboardNavigation
@@ -16,7 +15,7 @@ import Utils from '../../utils/Utils'
16
15
  * heatmap, the row above / below
17
16
  * Home: first data point in current series
18
17
  * End: last data point in current series
19
- * Enter / Space: fire markerClick event (same as mouse click)
18
+ * Enter / Space: select the point and fire markerClick, as a click does
20
19
  * Escape: exit keyboard nav, return focus to SVG
21
20
  */
22
21
  export default class KeyboardNavigation {
@@ -107,12 +106,27 @@ export default class KeyboardNavigation {
107
106
  // keyboard-nav tooltip so it doesn't remain stuck on screen while the chart
108
107
  // re-renders with a different set of visible series.
109
108
  this.ctx.events.addEventListener('legendClick', this._onLegendClick)
109
+
110
+ // An update (a keyboard zoom or pan among them) replaced a focused SVG:
111
+ // focus this one, with the cursor and a dismissed tooltip as they were
112
+ // (destroy), unless focus has gone somewhere else since
113
+ const kept = w.interact.keyNav
114
+ const doc = svgEl.ownerDocument
115
+ w.interact.keyNav = null
116
+ if (kept && (!doc.activeElement || doc.activeElement === doc.body)) {
117
+ ;[this.seriesIndex, this.dataPointIndex, , this._tooltipDismissed] = kept
118
+ // Focus alone, without starting nav, if nav was not running
119
+ if (!kept[2]) this.notePointerFocus()
120
+ svgEl.focus({ preventScroll: true })
121
+ }
110
122
  }
111
123
 
112
124
  /**
113
- * Removes all event listeners. Called from chart.destroy().
125
+ * Removes all event listeners. Called from chart.destroy(), and with
126
+ * `updating` when an update is about to replace the SVG.
127
+ * @param {boolean} [updating]
114
128
  */
115
- destroy() {
129
+ destroy(updating) {
116
130
  const w = this.w
117
131
  const svgEl = w.dom.Paper && w.dom.Paper.node
118
132
 
@@ -126,6 +140,14 @@ export default class KeyboardNavigation {
126
140
 
127
141
  if (!svgEl) return
128
142
 
143
+ if (updating && svgEl.getRootNode?.().activeElement === svgEl) {
144
+ w.interact.keyNav = [
145
+ this.seriesIndex,
146
+ this.dataPointIndex,
147
+ this.active,
148
+ this._tooltipDismissed,
149
+ ]
150
+ }
129
151
  svgEl.removeEventListener('focus', this._onFocus)
130
152
  svgEl.removeEventListener('blur', this._onBlur)
131
153
  svgEl.removeEventListener('keydown', this._onKeyDown)
@@ -192,7 +214,8 @@ export default class KeyboardNavigation {
192
214
  // If the chart is zoomed, snap the cursor to the first visible data point
193
215
  // so the user doesn't start navigating outside the current viewport.
194
216
  this._snapToVisibleRange()
195
- this._showCurrentPoint()
217
+ // An update re-focusing a chart whose tooltip was dismissed (init)
218
+ if (!this._tooltipDismissed) this._showCurrentPoint()
196
219
  }
197
220
 
198
221
  _onBlur() {
@@ -249,20 +272,18 @@ export default class KeyboardNavigation {
249
272
  break
250
273
  case 'Home':
251
274
  e.preventDefault()
252
- this.dataPointIndex = 0
253
- this._skipNullForward()
275
+ this._seek(0, 1)
254
276
  this._showCurrentPoint()
255
277
  break
256
278
  case 'End':
257
279
  e.preventDefault()
258
- this.dataPointIndex = this._getDataPointCount(this.seriesIndex) - 1
259
- this._skipNullBackward()
280
+ this._seek(this._getDataPointCount(this.seriesIndex) - 1, -1)
260
281
  this._showCurrentPoint()
261
282
  break
262
283
  case 'Enter':
263
284
  case ' ':
264
285
  e.preventDefault()
265
- this._fireClick()
286
+ this._fireClick(e)
266
287
  break
267
288
  case '+':
268
289
  case '=':
@@ -323,28 +344,16 @@ export default class KeyboardNavigation {
323
344
  }
324
345
 
325
346
  _canPan() {
326
- // Pan only meaningful once the chart is zoomed in (otherwise full data is
327
- // already visible). We still allow it whenever zoom is enabled — the
328
- // toolbar's zoomUpdateOptions clamps to data bounds.
329
- return this._canZoom()
347
+ // Unzoomed, the whole domain is in view: there is nothing to pan to
348
+ return this._canZoom() && this.w.interact.zoomed
330
349
  }
331
350
 
332
351
  /**
333
- * Shift the visible x-range by ~10% in the given direction.
352
+ * Shift the visible x-range by ~10% in the given direction, as a drag pan.
334
353
  * @param {number} direction +1 = right, -1 = left
335
354
  */
336
355
  _panBy(direction) {
337
- const toolbar = this.ctx.toolbar
338
- if (!toolbar) return
339
-
340
- const r = toolbar._currentXRange()
341
- const minX = Number(r.minX)
342
- const maxX = Number(r.maxX)
343
- if (!isFinite(minX) || !isFinite(maxX) || minX === maxX) return
344
-
345
- const span = maxX - minX
346
- const step = span * 0.1 * direction
347
- toolbar.zoomUpdateOptions(minX + step, maxX + step)
356
+ this.ctx.zoomPanSelection?.panBy(direction / 10)
348
357
  this._announce(direction > 0 ? 'Panned right' : 'Panned left')
349
358
  }
350
359
 
@@ -365,13 +374,15 @@ export default class KeyboardNavigation {
365
374
  // so ↑/↓ series switching would show the same content — suppress it.
366
375
  // For irregular time series the tooltip falls back to individual mode
367
376
  // (isXoverlap returns false), so up/down navigation is meaningful there.
368
- // A heatmap or treemap tooltip is always one cell's, whatever
369
- // tooltip.shared says (and it defaults to true), so ↑/↓ is the only
370
- // way to another row.
377
+ // A heatmap or treemap tooltip is always one cell's and a radialBar's
378
+ // one ring's, whatever tooltip.shared says (and it defaults to true), so
379
+ // ↑/↓ is the only way to another row. Without a tooltip nothing shows
380
+ // the other series either.
371
381
  const ttCtx = this.w.globals.tooltip
372
382
  const type = w.config.chart.type
373
- const perCell = type === 'heatmap' || type === 'treemap'
374
- if (!perCell && ttCtx && ttCtx.tConfig && ttCtx.tConfig.shared) {
383
+ const perCell =
384
+ type === 'heatmap' || type === 'treemap' || type === 'radialBar'
385
+ if (!perCell && ttCtx?.ttItems && ttCtx.tConfig.shared) {
375
386
  const j = this.dataPointIndex
376
387
  const isActuallyShared =
377
388
  ttCtx.tooltipUtil &&
@@ -413,20 +424,7 @@ export default class KeyboardNavigation {
413
424
 
414
425
  if (dPoint !== 0) {
415
426
  // Move between data points (←/→)
416
- const dpCount = this._getDataPointCount(this.seriesIndex)
417
- let di = this.dataPointIndex + dPoint
418
-
419
- if (di < 0) di = wrapAround ? dpCount - 1 : 0
420
- if (di >= dpCount) di = wrapAround ? 0 : dpCount - 1
421
-
422
- this.dataPointIndex = di
423
-
424
- // Skip null values
425
- if (dPoint > 0) {
426
- this._skipNullForward()
427
- } else {
428
- this._skipNullBackward()
429
- }
427
+ this._seek(this.dataPointIndex + dPoint, dPoint)
430
428
 
431
429
  // If the chart is zoomed, skip data points outside the visible range
432
430
  if (!this._isDataPointVisible(this.seriesIndex, this.dataPointIndex)) {
@@ -437,40 +435,26 @@ export default class KeyboardNavigation {
437
435
  this._showCurrentPoint()
438
436
  }
439
437
 
440
- /** Advance dataPointIndex forward past any nulls */
441
- _skipNullForward() {
442
- const w = this.w
443
- const si = this.seriesIndex
444
- const dpCount = this._getDataPointCount(si)
445
- let di = this.dataPointIndex
446
- let attempts = 0
447
-
448
- // Non-axis charts (pie, etc.) have flat numeric series — no nulls to skip
449
- if (!Array.isArray(w.seriesData.series[si])) return
450
-
451
- while (attempts < dpCount && w.seriesData.series[si][di] === null) {
452
- di = (di + 1) % dpCount
453
- attempts++
454
- }
455
- this.dataPointIndex = di
456
- }
457
-
458
- /** Retreat dataPointIndex backward past any nulls */
459
- _skipNullBackward() {
438
+ /**
439
+ * Land on the first non-null point from `di` going `dir`. Past an edge it
440
+ * wraps only with wrapAround; when no point lies ahead the cursor stays.
441
+ * @param {number} di
442
+ * @param {number} dir
443
+ */
444
+ _seek(di, dir) {
460
445
  const w = this.w
461
- const si = this.seriesIndex
462
- const dpCount = this._getDataPointCount(si)
463
- let di = this.dataPointIndex
464
- let attempts = 0
465
-
466
- // Non-axis charts (pie, etc.) have flat numeric series — no nulls to skip
467
- if (!Array.isArray(w.seriesData.series[si])) return
468
-
469
- while (attempts < dpCount && w.seriesData.series[si][di] === null) {
470
- di = (di - 1 + dpCount) % dpCount
471
- attempts++
446
+ const row = w.seriesData.series[this.seriesIndex]
447
+ const n = this._getDataPointCount(this.seriesIndex)
448
+ for (let k = 0; k < n; k++, di += dir) {
449
+ if (w.config.chart.accessibility.keyboard.navigation.wrapAround) {
450
+ di = (di + n) % n
451
+ } else if (di < 0 || di >= n) return
452
+ // Non-axis charts (pie, radialBar) have flat values, no nulls
453
+ if (!Array.isArray(row) || row[di] !== null) {
454
+ this.dataPointIndex = di
455
+ return
456
+ }
472
457
  }
473
- this.dataPointIndex = di
474
458
  }
475
459
 
476
460
  // ─── Display ──────────────────────────────────────────────────────────────
@@ -480,13 +464,15 @@ export default class KeyboardNavigation {
480
464
  const w = this.w
481
465
  const ttCtx = w.globals.tooltip
482
466
 
483
- if (!ttCtx || !ttCtx.ttItems) return
484
-
467
+ // Showing a point again undoes an Escape dismissal
468
+ this._tooltipDismissed = false
485
469
  // Keep globals consistent with the rest of the system
486
470
  w.interact.capturedSeriesIndex = i
487
471
  w.interact.capturedDataPointIndex = j
488
472
 
489
473
  this._applyFocusClass(i, j)
474
+ // No tooltip to read out the point, so the live region names it
475
+ if (!ttCtx?.ttItems) return this._announce(this._buildPointLabel(i, j))
490
476
  this._showTooltip(i, j, /** @type {any} */ (ttCtx))
491
477
  }
492
478
 
@@ -571,10 +557,13 @@ export default class KeyboardNavigation {
571
557
  tooltipEl.removeAttribute('aria-hidden')
572
558
  }
573
559
 
574
- if (type === 'pie' || type === 'donut' || type === 'polarArea') {
560
+ if (
561
+ type === 'pie' ||
562
+ type === 'donut' ||
563
+ type === 'polarArea' ||
564
+ type === 'radialBar'
565
+ ) {
575
566
  this._showTooltipNonAxis(i, j, ttCtx, tooltipEl)
576
- } else if (type === 'radialBar') {
577
- this._showTooltipRadialBar(i, j, ttCtx, tooltipEl)
578
567
  } else if (type === 'heatmap' || type === 'treemap') {
579
568
  this._showTooltipHeatTree(i, j, ttCtx, tooltipEl, type)
580
569
  } else if (this._isBarLikeSeries(i)) {
@@ -1130,19 +1119,17 @@ export default class KeyboardNavigation {
1130
1119
  }
1131
1120
 
1132
1121
  /**
1133
- * pie / donut / polarArea
1122
+ * pie / donut / polarArea (a slice per point, i is 0) and radialBar (a ring
1123
+ * per series, j is 0)
1134
1124
  * @param {number} i
1135
1125
  * @param {number} j
1136
1126
  * @param {import('../tooltip/Tooltip').default} ttCtx
1137
1127
  * @param {HTMLElement} tooltipEl
1138
1128
  */
1139
1129
  _showTooltipNonAxis(i, j, ttCtx, tooltipEl) {
1140
- const w = this.w
1141
-
1142
- // For pie-like charts the series index IS the data point / slice index
1143
1130
  ttCtx.tooltipLabels.drawSeriesTexts({
1144
1131
  ttItems: ttCtx.ttItems,
1145
- i: j,
1132
+ i: i + j,
1146
1133
  shared: false,
1147
1134
  })
1148
1135
 
@@ -1151,12 +1138,9 @@ export default class KeyboardNavigation {
1151
1138
  const ttWidth = tooltipBound.width || ttCtx.tooltipRect.ttWidth || 0
1152
1139
  const ttHeight = tooltipBound.height || ttCtx.tooltipRect.ttHeight || 0
1153
1140
 
1154
- // Anchor on the arc centroid, through the same helper the mouse path uses
1155
- // so keyboard and pointer land a slice's tooltip in the same place. The
1156
- // path element carries j='${j}' (0-indexed); data:cx/cy are set on the
1157
- // path directly, not on the parent group.
1158
- const sliceEl = w.dom.baseEl.querySelector(`.apexcharts-pie-area[j='${j}']`)
1159
- const anchor = ttCtx.getSliceAnchor(sliceEl)
1141
+ // Anchor on the arc's stamped centre (data:cx/cy), through the same helper
1142
+ // the mouse path uses so keyboard and pointer land in the same place.
1143
+ const anchor = ttCtx.getSliceAnchor(this._getFocusableElement(i, j))
1160
1144
  if (anchor) {
1161
1145
  const pos = ttCtx.tooltipPosition.placeOverAnchor(
1162
1146
  anchor.x,
@@ -1169,68 +1153,6 @@ export default class KeyboardNavigation {
1169
1153
  tooltipEl.style.top = pos.y + 'px'
1170
1154
  }
1171
1155
  }
1172
- /**
1173
- * radialBar — one ring per series, single value each
1174
- * @param {number} i
1175
- * @param {any} _j
1176
- * @param {import('../tooltip/Tooltip').default} ttCtx
1177
- * @param {HTMLElement} tooltipEl
1178
- */
1179
- _showTooltipRadialBar(i, _j, ttCtx, tooltipEl) {
1180
- const w = this.w
1181
-
1182
- ttCtx.tooltipLabels.drawSeriesTexts({
1183
- ttItems: ttCtx.ttItems,
1184
- i,
1185
- shared: false,
1186
- })
1187
-
1188
- const { ttWidth = 0, ttHeight = 0 } = ttCtx.getCachedDimensions()
1189
-
1190
- // Each radial series is a ring; find the path and use its data:angle to
1191
- // compute the centroid at the midpoint of the arc.
1192
- const arcEl = w.dom.baseEl.querySelector(
1193
- `.apexcharts-radialbar-series[data\\:realIndex='${i}'] path`,
1194
- )
1195
- if (arcEl) {
1196
- const angle = parseFloat(arcEl.getAttribute('data:angle') ?? '') || 0
1197
- // Radial bars start from the top (initialAngle) and sweep clockwise
1198
- const initialAngle = w.config.plotOptions.radialBar.startAngle || 0
1199
- const midAngle = initialAngle + angle / 2
1200
-
1201
- const centerX = w.layout.gridWidth / 2
1202
- const centerY = w.layout.gridHeight / 2
1203
- const radialSize =
1204
- w.globals.radialSize ||
1205
- Math.min(w.layout.gridWidth, w.layout.gridHeight) / 2
1206
-
1207
- // Use the outer radius for this particular ring (series i)
1208
- const seriesCount = w.seriesData.series.length
1209
- const trackSize = radialSize / Math.max(seriesCount, 1)
1210
- const outerRadius = radialSize - i * trackSize
1211
- const innerRadius = outerRadius - trackSize
1212
- const ringRadius = (outerRadius + innerRadius) / 2
1213
-
1214
- const centroid = Utils.polarToCartesian(
1215
- centerX,
1216
- centerY,
1217
- ringRadius,
1218
- midAngle,
1219
- )
1220
- const x = centroid.x + (w.layout.translateX || 0)
1221
- const y = centroid.y + (w.layout.translateY || 0)
1222
-
1223
- const pos = ttCtx.tooltipPosition.placeOverAnchor(
1224
- x,
1225
- y,
1226
- ttWidth,
1227
- ttHeight,
1228
- 10,
1229
- )
1230
- tooltipEl.style.left = pos.x + 'px'
1231
- tooltipEl.style.top = pos.y + 'px'
1232
- }
1233
- }
1234
1156
  /**
1235
1157
  * heatmap / treemap — position tooltip using element bounding rect
1236
1158
  * @param {number} i
@@ -1562,11 +1484,7 @@ export default class KeyboardNavigation {
1562
1484
  const mark = this._canvasMark(i, j)
1563
1485
  if (!mark) return this._drawCanvasMarkerFocusRing(i, j)
1564
1486
  const w = this.w
1565
- // The series' own group, by realIndex (`rel` counts within a type's
1566
- // groups, which a combo has several of).
1567
- const host = Array.from(
1568
- w.dom.baseEl.querySelectorAll('.apexcharts-series'),
1569
- ).find((g) => g.getAttribute('data:realIndex') === String(i))
1487
+ const host = this._seriesGroup(i)
1570
1488
  if (!host) return null
1571
1489
  const ring = new Graphics(w, this.ctx).drawPath({
1572
1490
  d: mark.d,
@@ -1591,9 +1509,7 @@ export default class KeyboardNavigation {
1591
1509
  const marker = this._canvasMarker(i, j)
1592
1510
  if (!marker) return null
1593
1511
  const w = this.w
1594
- const host = Array.from(
1595
- w.dom.baseEl.querySelectorAll('.apexcharts-series'),
1596
- ).find((g) => g.getAttribute('data:realIndex') === String(i))
1512
+ const host = this._seriesGroup(i)
1597
1513
  if (!host) return null
1598
1514
  const ring = new Graphics(w, this.ctx).drawPath({
1599
1515
  d: marker.d,
@@ -1729,9 +1645,7 @@ export default class KeyboardNavigation {
1729
1645
  }
1730
1646
 
1731
1647
  if (type === 'radialBar') {
1732
- return baseEl.querySelector(
1733
- `.apexcharts-radialbar-series[data\\:realIndex='${i}'] path`,
1734
- )
1648
+ return baseEl.querySelector(`.apexcharts-radialbar-slice-${i}`)
1735
1649
  }
1736
1650
 
1737
1651
  if (this._isBarLikeSeries(i)) {
@@ -1739,31 +1653,44 @@ export default class KeyboardNavigation {
1739
1653
  // it, _drawCanvasFocusRing), and a jitter path that still carries its
1740
1654
  // `j` in the series is no stand-in for it.
1741
1655
  if (this._canvasMark(i, j)) return null
1742
- return baseEl.querySelector(
1743
- `.apexcharts-series[data\\:realIndex='${i}'] path[j='${j}']`,
1744
- )
1656
+ return this._seriesGroup(i)?.querySelector(`path[j='${j}']`)
1745
1657
  }
1746
1658
 
1747
1659
  // line / area / scatter / bubble / radar — try marker element
1748
- const marker = baseEl.querySelector(
1749
- `.apexcharts-series[data\\:realIndex='${i}'] .apexcharts-marker[rel='${j}']`,
1750
- )
1751
- return marker || null
1660
+ return this._seriesGroup(i)?.querySelector(`.apexcharts-marker[rel='${j}']`)
1752
1661
  }
1753
1662
 
1754
- // ─── Click / Enter ────────────────────────────────────────────────────────
1663
+ /**
1664
+ * The series' own group, by realIndex (`rel` counts within a type's groups,
1665
+ * which a combo has several of). Matched in script: not every selector
1666
+ * engine takes the escaped `data\:realIndex` attribute.
1667
+ * @param {number} i
1668
+ * @returns {Element | undefined}
1669
+ */
1670
+ _seriesGroup(i) {
1671
+ return Array.from(
1672
+ this.w.dom.baseEl.querySelectorAll('.apexcharts-series'),
1673
+ ).find((g) => g.getAttribute('data:realIndex') === String(i))
1674
+ }
1755
1675
 
1756
- _fireClick() {
1757
- const w = this.w
1758
- const ttCtx = w.globals.tooltip
1759
- if (!ttCtx) return
1676
+ // ─── Click / Enter ────────────────────────────────────────────────────────
1760
1677
 
1761
- const syntheticEvent = {
1762
- type: 'mouseup',
1763
- clientX: 0,
1764
- clientY: 0,
1765
- }
1766
- ttCtx.markerClick(syntheticEvent, this.seriesIndex, this.dataPointIndex)
1678
+ /**
1679
+ * What a click on the focused mark does: its mousedown selects it
1680
+ * (dataPointSelection), its mouseup slides a pie slice out and fires
1681
+ * markerClick.
1682
+ * @param {KeyboardEvent} e
1683
+ */
1684
+ _fireClick(e) {
1685
+ const el = /** @type {any} */ (this._focusedEl)?.instance
1686
+ const pie = this.ctx.pie
1687
+ if (el) new Graphics(this.w, this.ctx).pathMouseDown(el, e)
1688
+ if (pie && pie.getExpandOffset() > 0) pie.pieClicked(this.dataPointIndex)
1689
+ this.w.globals.tooltip?.markerClick(
1690
+ e,
1691
+ this.seriesIndex,
1692
+ this.dataPointIndex,
1693
+ )
1767
1694
  }
1768
1695
 
1769
1696
  // ─── Helpers ──────────────────────────────────────────────────────────────
@@ -1795,8 +1722,9 @@ export default class KeyboardNavigation {
1795
1722
  if (type === 'pie' || type === 'donut' || type === 'polarArea') {
1796
1723
  return w.seriesData.series.length
1797
1724
  }
1798
- const series = w.seriesData.series
1799
- return series[si] && Array.isArray(series[si]) ? series[si].length : 0
1725
+ // A radialBar ring is a series of one value
1726
+ const s = w.seriesData.series[si]
1727
+ return Array.isArray(s) ? s.length : 1
1800
1728
  }
1801
1729
 
1802
1730
  _clampCursor() {
@@ -160,7 +160,7 @@ export function joinKeys(oldKeys, newKeys) {
160
160
  * @param {string[]} keys
161
161
  * @returns {string[]}
162
162
  */
163
- function uniquifyKeys(keys) {
163
+ export function uniquifyKeys(keys) {
164
164
  /** @type {Map<string, number>} */
165
165
  const seen = new Map()
166
166
  return keys.map((k) => {
@@ -170,6 +170,26 @@ function uniquifyKeys(keys) {
170
170
  })
171
171
  }
172
172
 
173
+ /**
174
+ * Datum keys of a series, unique within it: repeated category labels (or a
175
+ * timeline's rows) would otherwise share one key, and the keyed bar morph
176
+ * would hand every duplicate the same previous bar.
177
+ *
178
+ * @param {import('../../types/internal').ChartStateW} w
179
+ * @param {number} realIndex
180
+ * @param {number} [n] how many datums (default: the parsed series length)
181
+ * @returns {string[]}
182
+ */
183
+ export function seriesKeys(
184
+ w,
185
+ realIndex,
186
+ n = w.seriesData.series[realIndex]?.length | 0,
187
+ ) {
188
+ return uniquifyKeys(
189
+ Array.from({ length: n }, (_, j) => datumKey(w, realIndex, j)),
190
+ )
191
+ }
192
+
173
193
  /**
174
194
  * The keyed join for one series against the captured previous frame, or null
175
195
  * when this render is not an animated update for that series (no frame,
@@ -212,7 +232,7 @@ export function seriesJoin(
212
232
  const oldKeys = uniquifyKeys(
213
233
  oldY.map((_, j) => frameDatumKey(frame, realIndex, j)),
214
234
  )
215
- const newKeys = uniquifyKeys(newY.map((_, j) => datumKey(w, realIndex, j)))
235
+ const newKeys = seriesKeys(w, realIndex)
216
236
  const join = joinKeys(oldKeys, newKeys)
217
237
  if (!join.ordered && !allowReorder) return null
218
238
  if (!join.changed && !includeIdentity) return null
@@ -39,8 +39,11 @@ export default class Breadcrumb {
39
39
  const elWrap = w.dom.elWrap
40
40
  if (!elWrap) return
41
41
 
42
- // Remove any stale breadcrumb first (guards against double-render).
43
- const existing = elWrap.querySelector('.apexcharts-breadcrumb')
42
+ // Remove any stale breadcrumb first (guards against double-render), but
43
+ // only its own: a zoomed treemap's shares the class.
44
+ const existing = elWrap.querySelector(
45
+ '.apexcharts-breadcrumb[aria-label^=Drill]',
46
+ )
44
47
  if (existing && existing.parentNode) {
45
48
  existing.parentNode.removeChild(existing)
46
49
  }
@@ -64,7 +64,7 @@ export default class Destroy {
64
64
  // alone leaks the listener + the dead instance on every updateOptions().
65
65
  // zoomPanSelection/toolbar were destroyed above; the full-destroy path
66
66
  // destroys keyboardNavigation via apexcharts.js before calling clear().
67
- this.ctx._keyboardNavigation?.destroy()
67
+ this.ctx._keyboardNavigation?.destroy(true)
68
68
  this.ctx._keyboardNavigation = null
69
69
  } else {
70
70
  // Full destroy — null everything so GC can collect the instances.
@@ -31,7 +31,6 @@ import Utils from '../../utils/Utils'
31
31
  import { prefersReducedMotion } from '../Animations'
32
32
  import DataLabels from '../DataLabels'
33
33
  import {
34
- datumKey,
35
34
  morphEasing,
36
35
  reconcileSeriesPaths,
37
36
  seriesJoin,
@@ -739,8 +738,10 @@ export default class HighlightFilter {
739
738
  // A chart that is not highlighting and was not a frame ago: one check.
740
739
  if (!active && !prevMap?.size) return undefined
741
740
  const { j, realIndex } = o
742
- // datumKey names the category, which every series shares at that x.
743
- const key = BAR + `${realIndex}::${datumKey(w, realIndex, j)}`
741
+ // The bar's own key: the category, which every series shares at that x,
742
+ // made unique per occurrence so a repeated category keeps its own part
743
+ // (and matches the exit ghost's name).
744
+ const key = BAR + `${realIndex}::${bar._keys(realIndex)[j]}`
744
745
  this._seen.add(key)
745
746
  const prev = prevMap?.get(key)
746
747
  const p = active ? hd.parts[realIndex]?.[j] : null
@@ -1114,8 +1115,8 @@ export default class HighlightFilter {
1114
1115
 
1115
1116
  /**
1116
1117
  * The stack a stacked bar datum joins, as the renderer resolves it
1117
- * (BarStacked drawStackedColumnPaths): its series group, then its x value
1118
- * on a numeric or datetime column axis, else its index.
1118
+ * (BarStacked): its series group, then its x value on a numeric or
1119
+ * datetime axis, else its index.
1119
1120
  * @param {number} i @param {number} j
1120
1121
  */
1121
1122
  _barKey(i, j) {
@@ -1123,9 +1124,7 @@ export default class HighlightFilter {
1123
1124
  return (
1124
1125
  w.labelData.seriesGroupIndex[i] +
1125
1126
  '|' +
1126
- (!w.config.plotOptions.bar.horizontal && w.axisFlags.isXNumeric
1127
- ? w.seriesData.seriesX[i]?.[j]
1128
- : j)
1127
+ (w.axisFlags.isXNumeric ? w.seriesData.seriesX[i]?.[j] : j)
1129
1128
  )
1130
1129
  }
1131
1130
 
@@ -1233,7 +1232,7 @@ export default class HighlightFilter {
1233
1232
  ? this._lineBase(ln, type, series, i, realIndex, ti, has ? raw : null)
1234
1233
  : null
1235
1234
  if (pp && w.config.chart.stacked) {
1236
- this._swap(ln, () => ln._recordStackTops(realIndex, pp.yArrj))
1235
+ this._swap(ln, () => ln._recordStackTops(realIndex, pp.yArrj, pp.S[i]))
1237
1236
  this._stkPrev.push(pp.yArrj)
1238
1237
  }
1239
1238
  if (exit) {
@@ -414,10 +414,10 @@ function wrapParentTip(hf, tm) {
414
414
  }
415
415
  const v = tm._tip()?.querySelector('.apexcharts-tooltip-text-y-value')
416
416
  if (!v) return
417
- let rest = `/ ${tm._formatValue(h.total)}`
417
+ let rest = `/ ${tm._formatValue(h.total, node._si)}`
418
418
  if (tt.share && h.total) rest += ` · ${Math.round(h.share * 100)}%`
419
419
  v.innerHTML =
420
- `${tm._formatValue(h.value)} ` +
420
+ `${tm._formatValue(h.value, node._si)} ` +
421
421
  `<span class="apexcharts-highlight-total" style="font-weight: 400; opacity: 0.6">${rest}</span>`
422
422
  }
423
423
  }