apexcharts 8.2.0 → 8.3.1

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 (256) hide show
  1. package/README.md +7 -7
  2. package/dist/apexcharts.common.js +2 -2
  3. package/dist/apexcharts.core.js +19793 -19713
  4. package/dist/apexcharts.core.min.js +2 -2
  5. package/dist/apexcharts.css +38 -8
  6. package/dist/apexcharts.esm.js +1882 -1625
  7. package/dist/apexcharts.full.js +22044 -21634
  8. package/dist/apexcharts.full.min.js +2 -2
  9. package/dist/apexcharts.js +21414 -21124
  10. package/dist/apexcharts.min.js +2 -2
  11. package/dist/apexcharts.ssr.common.js +2 -2
  12. package/dist/apexcharts.ssr.esm.js +21413 -21123
  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 +478 -406
  17. package/dist/bar.js +2 -2
  18. package/dist/boxPlot.common.js +2 -2
  19. package/dist/boxPlot.esm.js +409 -334
  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 +409 -334
  24. package/dist/candlestick.js +2 -2
  25. package/dist/column.common.js +2 -2
  26. package/dist/column.esm.js +478 -406
  27. package/dist/core.common.js +2 -2
  28. package/dist/core.esm.js +19793 -19713
  29. package/dist/donut.common.js +2 -2
  30. package/dist/donut.esm.js +3 -9
  31. package/dist/dumbbell.common.js +2 -2
  32. package/dist/dumbbell.esm.js +478 -406
  33. package/dist/features/all.common.js +2 -2
  34. package/dist/features/all.esm.js +442 -324
  35. package/dist/features/annotations.common.js +2 -2
  36. package/dist/features/annotations.esm.js +65 -48
  37. package/dist/features/annotations.js +2 -2
  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 +2 -2
  48. package/dist/features/exports.esm.js +10 -4
  49. package/dist/features/exports.js +2 -2
  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 +64 -44
  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 +2 -2
  78. package/dist/features/morph.esm.js +9 -5
  79. package/dist/features/morph.js +2 -2
  80. package/dist/features/perspectives.common.js +2 -2
  81. package/dist/features/perspectives.esm.js +4 -1
  82. package/dist/features/perspectives.js +2 -2
  83. package/dist/features/raincloud.common.js +2 -2
  84. package/dist/features/raincloud.esm.js +26 -27
  85. package/dist/features/raincloud.js +2 -2
  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 +2 -2
  90. package/dist/features/stats.esm.js +52 -59
  91. package/dist/features/stats.js +2 -2
  92. package/dist/features/storyboard.common.js +2 -2
  93. package/dist/features/storyboard.esm.js +4 -1
  94. package/dist/features/storyboard.js +2 -2
  95. package/dist/features/streamgraph.common.js +2 -2
  96. package/dist/features/streamgraph.esm.js +48 -14
  97. package/dist/features/streamgraph.js +2 -2
  98. package/dist/features/toolbar.common.js +2 -2
  99. package/dist/features/toolbar.esm.js +148 -18
  100. package/dist/features/toolbar.js +2 -2
  101. package/dist/features/trellis.common.js +2 -2
  102. package/dist/features/trellis.esm.js +150 -63
  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 +2 -2
  108. package/dist/features/weave.esm.js +13 -12
  109. package/dist/features/weave.js +2 -2
  110. package/dist/full.common.js +2 -2
  111. package/dist/full.esm.js +1068 -842
  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 +529 -464
  117. package/dist/icicle.common.js +2 -2
  118. package/dist/icicle.esm.js +80 -46
  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 +2 -2
  129. package/dist/pie.esm.js +3 -9
  130. package/dist/pie.js +2 -2
  131. package/dist/polarArea.common.js +2 -2
  132. package/dist/polarArea.esm.js +3 -9
  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 +2 -2
  137. package/dist/radialBar.esm.js +5 -12
  138. package/dist/radialBar.js +2 -2
  139. package/dist/raincloud.common.js +2 -2
  140. package/dist/raincloud.esm.js +97 -52
  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 +478 -406
  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 +138 -53
  149. package/dist/sunburst.common.js +2 -2
  150. package/dist/sunburst.esm.js +116 -94
  151. package/dist/sunburst.js +2 -2
  152. package/dist/treemap.common.js +2 -2
  153. package/dist/treemap.esm.js +138 -104
  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 +477 -405
  166. package/package.json +1 -1
  167. package/src/apexcharts.js +40 -59
  168. package/src/assets/apexcharts.css +38 -8
  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 +68 -47
  173. package/src/charts/Line.js +118 -42
  174. package/src/charts/Pie.js +2 -8
  175. package/src/charts/Radar.js +5 -7
  176. package/src/charts/Radial.js +2 -6
  177. package/src/charts/RangeBar.js +47 -43
  178. package/src/charts/Sunburst.js +101 -98
  179. package/src/charts/Treemap.js +150 -88
  180. package/src/charts/Unit.js +368 -250
  181. package/src/charts/Violin.js +48 -30
  182. package/src/charts/common/Breadcrumb.js +2 -2
  183. package/src/charts/common/Hierarchy.js +32 -11
  184. package/src/charts/common/Jitter.js +32 -7
  185. package/src/charts/common/Stats.js +30 -23
  186. package/src/charts/common/bar/DataLabels.js +76 -81
  187. package/src/charts/common/bar/Helpers.js +43 -7
  188. package/src/charts/common/line/Helpers.js +12 -15
  189. package/src/charts/common/partition/NodeTooltip.js +29 -7
  190. package/src/charts/common/partition/Partition.js +33 -1
  191. package/src/charts/common/treemap/Helpers.js +3 -11
  192. package/src/charts/common/treemap/Nested.js +2 -3
  193. package/src/features/raincloud.js +16 -10
  194. package/src/features/stats.js +67 -62
  195. package/src/locales/en.json +2 -1
  196. package/src/locales/ro.json +3 -2
  197. package/src/modules/Animations.js +102 -95
  198. package/src/modules/AxisMapping.js +47 -12
  199. package/src/modules/Core.js +4 -0
  200. package/src/modules/CoreUtils.js +18 -42
  201. package/src/modules/Data.js +23 -24
  202. package/src/modules/DataLabels.js +6 -6
  203. package/src/modules/Exports.js +12 -5
  204. package/src/modules/Fill.js +17 -8
  205. package/src/modules/Filters.js +50 -50
  206. package/src/modules/Graphics.js +69 -51
  207. package/src/modules/Markers.js +3 -1
  208. package/src/modules/MorphTypeChange.js +13 -7
  209. package/src/modules/Range.js +44 -29
  210. package/src/modules/RendererController.js +17 -3
  211. package/src/modules/Scales.js +32 -9
  212. package/src/modules/Series.js +41 -19
  213. package/src/modules/Theme.js +1 -1
  214. package/src/modules/Toolbar.js +3 -2
  215. package/src/modules/UnitMarkRegistry.js +0 -5
  216. package/src/modules/ZoomPanSelection.js +123 -2
  217. package/src/modules/accessibility/KeyboardNavigation.js +114 -186
  218. package/src/modules/animations/Easing.js +2 -1
  219. package/src/modules/animations/LengthTransition.js +22 -2
  220. package/src/modules/annotations/Helpers.js +68 -52
  221. package/src/modules/axes/Grid.js +21 -7
  222. package/src/modules/axes/YAxis.js +8 -9
  223. package/src/modules/dimensions/Dimensions.js +9 -3
  224. package/src/modules/dimensions/Grid.js +2 -2
  225. package/src/modules/dimensions/XAxis.js +2 -5
  226. package/src/modules/drilldown/Breadcrumb.js +5 -2
  227. package/src/modules/helpers/Destroy.js +1 -1
  228. package/src/modules/helpers/UpdateHelpers.js +33 -15
  229. package/src/modules/highlightFilter/HighlightFilter.js +8 -9
  230. package/src/modules/highlightFilter/TreemapPart.js +2 -2
  231. package/src/modules/legend/HeatmapGradientLegend.js +76 -45
  232. package/src/modules/legend/Helpers.js +8 -1
  233. package/src/modules/legend/Legend.js +11 -12
  234. package/src/modules/license/LicenseEnforcer.js +5 -0
  235. package/src/modules/settings/Defaults.js +28 -56
  236. package/src/modules/settings/Globals.js +5 -0
  237. package/src/modules/settings/Options.js +7 -7
  238. package/src/modules/settings/TypeAliases.js +33 -16
  239. package/src/modules/streamgraph/StreamLabels.js +1 -0
  240. package/src/modules/tooltip/Labels.js +73 -76
  241. package/src/modules/tooltip/Marker.js +25 -21
  242. package/src/modules/tooltip/Position.js +88 -51
  243. package/src/modules/tooltip/Tooltip.js +242 -255
  244. package/src/modules/tooltip/Utils.js +71 -130
  245. package/src/modules/tooltip/constants.js +5 -5
  246. package/src/modules/trellis/Trellis.js +6 -0
  247. package/src/modules/trellis/TrellisChrome.js +18 -7
  248. package/src/modules/trellis/TrellisSync.js +2 -0
  249. package/src/modules/trellis/TrellisTooltip.js +2 -11
  250. package/src/modules/weave/WeaveHost.js +18 -12
  251. package/src/renderers/canvas/CanvasGraphics.js +1 -0
  252. package/src/ssr/DOMShim.js +10 -0
  253. package/src/svg/SVGFilter.js +26 -17
  254. package/src/types/internal.d.ts +9 -3
  255. package/src/utils/Utils.js +0 -14
  256. package/types/apexcharts.d.ts +33 -12
@@ -406,7 +406,7 @@ export default class Theme {
406
406
  c
407
407
  ].colors =
408
408
  w.config[c].colors === undefined
409
- ? this.isColorFn
409
+ ? this.isColorFn && w.config.colors
410
410
  ? w.config.colors.slice()
411
411
  : defaultColors
412
412
  : w.config[c].colors.slice()
@@ -876,9 +876,10 @@ export default class Toolbar {
876
876
  }
877
877
 
878
878
  // if user has some series collapsed before hitting zoom reset button,
879
- // those series should stay collapsed
879
+ // those series should stay collapsed. Cloned from the peek, as
880
+ // reading initialSeries deep clones it first.
880
881
  const series = ch.ctx.series.emptyCollapsedSeries(
881
- Utils.clone(w.globals.initialSeries),
882
+ Utils.clone(w.globals._initialSeriesPeek),
882
883
  )
883
884
 
884
885
  ch.updateHelpers._updateSeries(
@@ -123,8 +123,3 @@ export function getUnitMark(name) {
123
123
  export function unregisterUnitMark(name) {
124
124
  delete getMarks()[name]
125
125
  }
126
-
127
- /** @returns {string[]} */
128
- export function registeredUnitMarkNames() {
129
- return Object.keys(getMarks())
130
- }
@@ -5,6 +5,7 @@ import Toolbar from './Toolbar'
5
5
  import AxisMapping from './AxisMapping'
6
6
  import TooltipUtils from './tooltip/Utils'
7
7
  import { Box } from '../svg/index'
8
+ import { BrowserAPIs } from '../ssr/BrowserAPIs.js'
8
9
 
9
10
  // Wheel-zoom feel: scrolling this many deltaY pixels doubles (or halves) the
10
11
  // visible x-window. 240px is about two discrete wheel notches per 2x zoom
@@ -381,6 +382,67 @@ export default class ZoomPanSelection extends Toolbar {
381
382
  return this._incidentalZoomEnabled(zoom && zoom.allowMouseWheelZoom)
382
383
  }
383
384
 
385
+ /** An explicit `true` zooms on every wheel; 'auto' needs Ctrl/Cmd. */
386
+ _wheelZoomGreedy() {
387
+ return this.w.config.chart.zoom.allowMouseWheelZoom === true
388
+ }
389
+
390
+ /**
391
+ * Tell a viewer whose bare wheel just scrolled past the chart how to zoom it.
392
+ * Shown over the chart while the scroll goes on and faded out shortly after
393
+ * it stops. Only for a mostly vertical wheel: a sideways trackpad swipe is
394
+ * not someone reaching for zoom.
395
+ *
396
+ * The node lives in elWrap and the timer on w.interact, since a re-render can
397
+ * replace both this instance and the wrapper's contents while it is up.
398
+ *
399
+ * @param {any} e
400
+ */
401
+ _showWheelHint(e) {
402
+ const w = this.w
403
+ if (w.config.chart.zoom.wheelHint === false) return
404
+ if (Math.abs(e.deltaY) <= Math.abs(e.deltaX)) return
405
+ const wrap = w.dom.elWrap
406
+ if (!wrap) return
407
+
408
+ let el = wrap.querySelector('.apexcharts-wheel-hint')
409
+ if (!el) {
410
+ const win = BrowserAPIs.getWindow()
411
+ const nav = win && win.navigator
412
+ const mac = !!nav && /Mac|iPhone|iPad/.test(nav.platform || nav.userAgent)
413
+ const text = (w.globals.locale.toolbar.wheelZoomHint || '').replace(
414
+ '{key}',
415
+ mac ? '⌘' : 'Ctrl',
416
+ )
417
+ el = BrowserAPIs.createElement('div')
418
+ el.className = 'apexcharts-wheel-hint'
419
+ el.setAttribute('aria-hidden', 'true')
420
+ const label = BrowserAPIs.createElement('span')
421
+ label.textContent = text
422
+ label.style.fontFamily = w.config.chart.fontFamily
423
+ el.appendChild(label)
424
+ wrap.appendChild(el)
425
+ // lay the hidden state out before showing it, so it fades in
426
+ void (/** @type {HTMLElement} */ (el).offsetWidth)
427
+ }
428
+ el.classList.add('apexcharts-wheel-hint-visible')
429
+
430
+ const it = w.interact
431
+ if (it.wheelHintTimer) clearTimeout(it.wheelHintTimer)
432
+ it.wheelHintTimer = setTimeout(() => this._hideWheelHint(), 1200)
433
+ }
434
+
435
+ _hideWheelHint() {
436
+ const w = this.w
437
+ const it = w.interact
438
+ if (it.wheelHintTimer) {
439
+ clearTimeout(it.wheelHintTimer)
440
+ it.wheelHintTimer = null
441
+ }
442
+ const el = w.dom.elWrap?.querySelector('.apexcharts-wheel-hint')
443
+ if (el) el.classList.remove('apexcharts-wheel-hint-visible')
444
+ }
445
+
384
446
  /**
385
447
  * Put keyboard focus on the chart, where the chart is focusable at all.
386
448
  *
@@ -454,10 +516,28 @@ export default class ZoomPanSelection extends Toolbar {
454
516
  * @param {any} e
455
517
  */
456
518
  mouseWheelEvent(e) {
457
- e.preventDefault()
458
-
459
519
  const st = this._wheel()
460
520
 
521
+ // Cooperative wheel ('auto'): a bare wheel belongs to the page, so a
522
+ // dashboard of charts scrolls past them instead of zooming each one it
523
+ // crosses. Ctrl/Cmd + wheel zooms, and so does a trackpad pinch, which
524
+ // browsers deliver as a wheel event with ctrlKey set. Once a zoom gesture
525
+ // is under way it keeps the wheel until it settles, so a trackpad's
526
+ // momentum tail after the key comes up does not spill into a page scroll.
527
+ // An explicit `true` keeps the greedy wheel (every scroll zooms).
528
+ if (
529
+ !this._wheelZoomGreedy() &&
530
+ !e.ctrlKey &&
531
+ !e.metaKey &&
532
+ st.endTimer == null
533
+ ) {
534
+ this._showWheelHint(e)
535
+ return
536
+ }
537
+
538
+ e.preventDefault()
539
+ this._hideWheelHint()
540
+
461
541
  // normalize the delta to pixels (deltaMode 1 = lines, 2 = pages)
462
542
  let dy = e.deltaY
463
543
  if (e.deltaMode === 1) dy *= 33
@@ -1295,6 +1375,10 @@ export default class ZoomPanSelection extends Toolbar {
1295
1375
  }
1296
1376
  }
1297
1377
 
1378
+ // A vertical drag, or one held at the data edge, leaves the window where
1379
+ // it is: no re-render and no `scrolled` for a pan that did not move.
1380
+ if (xLowestValue === minX && xHighestValue === maxX) return
1381
+
1298
1382
  const xaxis = {
1299
1383
  min: xLowestValue,
1300
1384
  max: xHighestValue,
@@ -1520,6 +1604,43 @@ export default class ZoomPanSelection extends Toolbar {
1520
1604
  this.ctx.events.fireEvent('scrolled', args)
1521
1605
  }
1522
1606
 
1607
+ /**
1608
+ * Pan a zoomed x-window by `fraction` of its width (keyboard Shift+Arrow),
1609
+ * clamped to the data as a drag pan is, and reported as one (`scrolled`).
1610
+ * @param {number} fraction
1611
+ */
1612
+ panBy(fraction) {
1613
+ const w = this.w
1614
+ if (!w.interact.zoomed) return
1615
+ const { min, max } = this._currentXWindow()
1616
+ let d = (max - min) * fraction
1617
+ // a category window moves by whole categories
1618
+ if (w.config.xaxis.convertedCatToNumeric) {
1619
+ d = d > 0 ? Math.ceil(d) : Math.floor(d)
1620
+ }
1621
+ // _clampBounds has none for a rangeBar: stop at the ends of its bars
1622
+ if (w.axisFlags.isRangeBar) {
1623
+ const r = w.rangeData
1624
+ let lo = Infinity
1625
+ let hi = -Infinity
1626
+ r.seriesRangeStart
1627
+ .concat(r.seriesRangeEnd)
1628
+ .flat()
1629
+ .forEach((/** @type {any} */ y) => {
1630
+ if (y != null) {
1631
+ lo = Math.min(lo, y)
1632
+ hi = Math.max(hi, y)
1633
+ }
1634
+ })
1635
+ d =
1636
+ d > 0
1637
+ ? Math.min(d, Math.max(0, hi - max))
1638
+ : Math.max(d, Math.min(0, lo - min))
1639
+ if (!d) return
1640
+ }
1641
+ if (this._applyXRange(min + d, max + d, false)) this._fireScrolled()
1642
+ }
1643
+
1523
1644
  /** @param {number} x @param {number} t */
1524
1645
  _pushSample(x, t) {
1525
1646
  const s = this._m().samples
@@ -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() {
@@ -14,7 +14,8 @@
14
14
  * pop) are intentionally NOT routed through here: `applyProgressiveReveal`
15
15
  * inverts `easeOutCubic` for reveal-sync timing, so those stay fixed. This
16
16
  * registry governs the GENERIC runner tweens (data-update value transitions,
17
- * path morphs, marker animate).
17
+ * path morphs, marker animate) and the gauge needle, which resolves
18
+ * 'easeOutBack' and 'easeOutCubic' by name.
18
19
  *
19
20
  * @module modules/animations/Easing
20
21
  */