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
@@ -49,6 +49,7 @@
49
49
  import ApexCharts from '../apexcharts'
50
50
  import { registerSeriesTransform } from '../modules/SeriesTransformRegistry'
51
51
  import {
52
+ deriveStat,
52
53
  derivedStats,
53
54
  fiveNumberSummary,
54
55
  isDerived,
@@ -107,22 +108,27 @@ function raincloudTransform(ser, w) {
107
108
  // as before: beside a hand-supplied density the datum keeps its own.
108
109
  const out = { ...y }
109
110
  if (needDensity) {
110
- const est = kernelDensity(values, {
111
- bandwidth: kde.bandwidth,
112
- resolution: kde.resolution,
113
- })
111
+ const est = deriveStat(
112
+ ourDensity,
113
+ key,
114
+ values,
115
+ { bandwidth: kde.bandwidth, resolution: kde.resolution },
116
+ kernelDensity,
117
+ )
114
118
  // no density, no cloud: leave the datum as it came
115
119
  if (!est) return d
116
120
  out.density = est.density
117
121
  out.points = values
118
- ourDensity.set(key, est.density)
119
122
  }
120
123
  if (needSummary) {
121
- const est = fiveNumberSummary(values, { whiskers })
122
- if (est) {
123
- out.summary = est.summary
124
- ourSummary.set(key, est.summary)
125
- }
124
+ const est = deriveStat(
125
+ ourSummary,
126
+ key,
127
+ values,
128
+ { whiskers },
129
+ fiveNumberSummary,
130
+ )
131
+ if (est) out.summary = est.summary
126
132
  }
127
133
  touched = true
128
134
  return { ...d, y: out }
@@ -45,6 +45,7 @@ import { collapsedIndices, stashSeries } from '../modules/helpers/RawStash'
45
45
  import {
46
46
  binCounts,
47
47
  computeBinning,
48
+ deriveStat,
48
49
  derivedStats,
49
50
  fiveNumberSummary,
50
51
  isDerived,
@@ -204,45 +205,19 @@ function histogramTransform(ser, w) {
204
205
  * @returns {any[]}
205
206
  */
206
207
  function boxPlotTransform(ser, w) {
207
- if (!Array.isArray(ser)) return ser
208
- const whiskers = w.config.plotOptions?.boxPlot?.whiskers || 'minmax'
209
- const derived = derivedStats(w, 'boxPlot')
210
- /** @type {Array<Array<number[] | null>>} */
211
- const observed = []
212
-
213
- const out = ser.map((/** @type {any} */ s, /** @type {number} */ i) => {
214
- // A combo's other series are not samples: a violin series carries its
215
- // jitter in `points` too, and would have its density replaced.
216
- if (!Array.isArray(s?.data) || (s.type && s.type !== 'boxPlot')) return s
217
- let touched = false
218
- /** @type {Array<number[] | null>} */
219
- const byPoint = new Array(s.data.length).fill(null)
220
- const data = s.data.map((/** @type {any} */ d, /** @type {number} */ j) => {
221
- // An existing summary wins: this is the documented input shape. Unless we
222
- // are the ones who derived it, in which case the options that shaped it
223
- // may have changed.
224
- const key = i + ':' + j
225
- if (
226
- Array.isArray(d?.y) &&
227
- d.y.length === 5 &&
228
- !isDerived(derived, key, d.y)
229
- ) {
230
- return d
231
- }
232
- const values = observationsOf(d, false)
233
- if (!values) return d
234
- const summary = fiveNumberSummary(values, { whiskers })
235
- if (!summary) return d
236
- touched = true
237
- byPoint[j] = values
238
- derived.set(key, summary.summary)
239
- return { ...d, y: summary.summary, points: values }
240
- })
241
- if (touched) observed[i] = byPoint
242
- return touched ? { ...s, data } : s
243
- })
244
- recordObservations(w, observed, ser.length)
245
- return out
208
+ return sampleTransform(
209
+ ser,
210
+ w,
211
+ 'boxPlot',
212
+ { whiskers: w.config.plotOptions?.boxPlot?.whiskers || 'minmax' },
213
+ fiveNumberSummary,
214
+ (/** @type {any} */ d) => Array.isArray(d?.y) && d.y.length === 5 && d.y,
215
+ (/** @type {any} */ d, /** @type {any} */ est, /** @type {number[]} */ values) => ({
216
+ ...d,
217
+ y: est.summary,
218
+ points: values,
219
+ }),
220
+ )
246
221
  }
247
222
 
248
223
  /**
@@ -261,43 +236,73 @@ function boxPlotTransform(ser, w) {
261
236
  * @returns {any[]}
262
237
  */
263
238
  function violinTransform(ser, w) {
264
- if (!Array.isArray(ser)) return ser
265
239
  const kde = w.config.plotOptions?.violin?.kde || {}
266
- const derived = derivedStats(w, 'violin')
240
+ return sampleTransform(
241
+ ser,
242
+ w,
243
+ 'violin',
244
+ { bandwidth: kde.bandwidth, resolution: kde.resolution },
245
+ kernelDensity,
246
+ (/** @type {any} */ d) =>
247
+ Array.isArray(d?.y?.density) && d.y.density.length && d.y.density,
248
+ (/** @type {any} */ d, /** @type {any} */ est, /** @type {number[]} */ values) => ({
249
+ ...d,
250
+ // Keep the rest of an object `y`: a summary given with the points still
251
+ // draws the box lane.
252
+ y: {
253
+ ...(d.y && typeof d.y === 'object' && !Array.isArray(d.y) ? d.y : {}),
254
+ density: est.density,
255
+ points: values,
256
+ },
257
+ }),
258
+ )
259
+ }
260
+
261
+ /**
262
+ * The shared walk of the boxPlot and violin transforms: every datum of every
263
+ * series of `type` that carries a sample and no statistic of its own (or the
264
+ * one this transform wrote last pass) gets `fn(sample, opts)`, written by
265
+ * `write`.
266
+ *
267
+ * A combo's other series are not samples: a violin series carries its jitter
268
+ * in `points` too, and a boxPlot's or a rangeArea's `y` is a flat number
269
+ * array, which a violin would estimate into a density.
270
+ *
271
+ * @param {any[]} ser
272
+ * @param {any} w
273
+ * @param {string} type
274
+ * @param {any} opts
275
+ * @param {(values: number[], opts: any) => any} fn
276
+ * @param {(d: any) => any} statOf - the datum's own statistic, or falsy
277
+ * @param {(d: any, est: any, values: number[]) => any} write
278
+ * @returns {any[]}
279
+ */
280
+ function sampleTransform(ser, w, type, opts, fn, statOf, write) {
281
+ if (!Array.isArray(ser)) return ser
282
+ const derived = derivedStats(w, type)
267
283
  /** @type {Array<Array<number[] | null>>} */
268
284
  const observed = []
269
285
 
270
286
  const out = ser.map((/** @type {any} */ s, /** @type {number} */ i) => {
271
- // A combo's other series are not samples: a boxPlot's or a rangeArea's
272
- // `y` is a flat number array too, and was estimated into a density.
273
- if (!Array.isArray(s?.data) || (s.type && s.type !== 'violin')) return s
287
+ if (!Array.isArray(s?.data) || (s.type && s.type !== type)) return s
274
288
  let touched = false
275
289
  /** @type {Array<number[] | null>} */
276
290
  const byPoint = new Array(s.data.length).fill(null)
277
291
  const data = s.data.map((/** @type {any} */ d, /** @type {number} */ j) => {
292
+ // An existing statistic wins: this is the documented input shape. Unless
293
+ // we are the ones who derived it, in which case the options that shaped
294
+ // it may have changed.
278
295
  const key = i + ':' + j
279
- if (
280
- Array.isArray(d?.y?.density) &&
281
- d.y.density.length &&
282
- !isDerived(derived, key, d.y.density)
283
- ) {
284
- return d
285
- }
286
- const values = observationsOf(d, true)
296
+ const stat = statOf(d)
297
+ if (stat && !isDerived(derived, key, stat)) return d
298
+ // Only a density-based type reads a flat number `y` as the sample.
299
+ const values = observationsOf(d, type === 'violin')
287
300
  if (!values) return d
288
- const est = kernelDensity(values, {
289
- bandwidth: kde.bandwidth,
290
- resolution: kde.resolution,
291
- })
301
+ const est = deriveStat(derived, key, values, opts, fn)
292
302
  if (!est) return d
293
303
  touched = true
294
304
  byPoint[j] = values
295
- // Keep the rest of an object `y`: a summary given with the points still
296
- // draws the box lane.
297
- const rest =
298
- d.y && typeof d.y === 'object' && !Array.isArray(d.y) ? d.y : {}
299
- derived.set(key, est.density)
300
- return { ...d, y: { ...rest, density: est.density, points: values } }
305
+ return write(d, est, values)
301
306
  })
302
307
  if (touched) observed[i] = byPoint
303
308
  return touched ? { ...s, data } : s
@@ -50,7 +50,8 @@
50
50
  "zoomOut": "Zoom Out",
51
51
  "pan": "Panning",
52
52
  "reset": "Reset Zoom",
53
- "measure": "Measure"
53
+ "measure": "Measure",
54
+ "wheelZoomHint": "Use {key} + scroll to zoom"
54
55
  }
55
56
  }
56
57
  }
@@ -49,7 +49,8 @@
49
49
  "zoomIn": "Mărire",
50
50
  "zoomOut": "Micșorare",
51
51
  "pan": "Panoramare",
52
- "reset": "Resetare zoom"
52
+ "reset": "Resetare zoom",
53
+ "measure": "Măsurare"
53
54
  }
54
55
  }
55
- }
56
+ }
@@ -30,6 +30,57 @@ function easeOutBack(t) {
30
30
  return 1 + c3 * Math.pow(t - 1, 3) + c1 * Math.pow(t - 1, 2)
31
31
  }
32
32
 
33
+ /**
34
+ * Each chart's mount frame loop: the steppers below (sweep, pop, draw) used
35
+ * to poll one requestAnimationFrame chain per element, so a line with 2,000
36
+ * markers and labels ran 4,000 callbacks a frame. They queue jobs here and
37
+ * share ONE rAF per chart per frame, which stops when the queue drains or
38
+ * the chart is destroyed. A job returns true to run again next frame, or a
39
+ * function: its writes, run once every job has done its reads. A job queued
40
+ * during a frame first runs on the next, as its own rAF did.
41
+ * State per chart: `q` the next frame's jobs, `n` the pops in flight.
42
+ * @type {WeakMap<object, any>}
43
+ */
44
+ const loops = new WeakMap()
45
+
46
+ /**
47
+ * @param {import('../types/internal').ChartStateW} w
48
+ * @param {(now: number) => any} job
49
+ * @returns {any} the chart's loop state
50
+ */
51
+ function onFrame(w, job) {
52
+ let s = loops.get(w)
53
+ if (!s) loops.set(w, (s = { n: 0, q: [] }))
54
+ // The first job queued for a frame requests it.
55
+ if (s.q.push(job) < 2) {
56
+ BrowserAPIs.requestAnimationFrame((now) => {
57
+ const jobs = s.q
58
+ s.q = []
59
+ // for...of also visits the writes appended mid-loop, after every read.
60
+ for (const j of jobs) {
61
+ // A destroyed chart drops its queue and its pop count, checked per
62
+ // job: an animationEnd handler a job reaches may destroy it.
63
+ if (w.globals.isDestroyed) {
64
+ s.n = 0
65
+ break
66
+ }
67
+ // A throw (a user handler) loses only its own job, as when each
68
+ // element had its own rAF, and is reported once the frame is done.
69
+ try {
70
+ const r = j(now)
71
+ if (r === true) onFrame(w, j)
72
+ else if (r) jobs.push(r)
73
+ } catch (e) {
74
+ setTimeout(() => {
75
+ throw e
76
+ })
77
+ }
78
+ }
79
+ })
80
+ }
81
+ return s
82
+ }
83
+
33
84
  /**
34
85
  * Cached reduced-motion query — `matchMedia` evaluation is cheap but the
35
86
  * MediaQueryList instance is reused if a chart re-renders.
@@ -116,29 +167,20 @@ export function applyProgressiveReveal(el, x, w) {
116
167
  const easedT = 1 - Math.cbrt(1 - xRatio)
117
168
  const revealDelay = easedT * drawSpeed
118
169
 
119
- const node = el.node
120
- const style = node.style
170
+ const style = el.node.style
121
171
  style.opacity = '0'
122
172
 
123
- // Anchor on the first rAF after render — the same frame `animateDraw` uses
124
- // as its t=0. All rAF callbacks scheduled in the current task receive the
125
- // same timestamp in the next frame, so reveal and draw share an origin.
126
- // Then poll rAFs (not setTimeout) so the snap happens on a frame boundary.
127
- /** @type {number | null} */
128
- let startAnchor = null
129
- /** @param {number} now */
130
- const tick = (now) => {
131
- if (startAnchor === null) startAnchor = now
132
- if (now - startAnchor >= revealDelay) {
133
- // Clear the inline opacity rather than forcing '1' — an inline style
134
- // would override the element's SVG `opacity` attribute (e.g. an
135
- // x-axis range annotation rect with `opacity: 0.4`).
136
- style.opacity = ''
137
- } else {
138
- BrowserAPIs.requestAnimationFrame(tick)
139
- }
140
- }
141
- BrowserAPIs.requestAnimationFrame(tick)
173
+ // Anchor on the first frame after render, the same frame `animateDraw` uses
174
+ // as its t=0, so reveal and draw share an origin; snap on frame boundaries.
175
+ let startAnchor = -1
176
+ onFrame(w, (/** @type {number} */ now) => {
177
+ if (startAnchor < 0) startAnchor = now
178
+ if (now - startAnchor < revealDelay) return true
179
+ // Clear the inline opacity rather than forcing '1': an inline style
180
+ // would override the element's SVG `opacity` attribute (e.g. an
181
+ // x-axis range annotation rect with `opacity: 0.4`).
182
+ style.opacity = ''
183
+ })
142
184
  return true
143
185
  }
144
186
 
@@ -301,30 +343,25 @@ export default class Animations {
301
343
  style.transform = 'scale(0)'
302
344
  style.opacity = '0'
303
345
 
346
+ // Pops land in stagger order, so the animation is over when the LAST one
347
+ // lands: completion waits for the chart's pop count to drain. A destroyed
348
+ // chart's loop stops before it, so animationEnd never fires on a
349
+ // torn-down chart (react-apexcharts#602).
304
350
  const startAt = performance.now() + delay
305
- /** @param {number} now */
306
- const step = (now) => {
307
- // Chart may have been destroyed before this frame ran (React StrictMode
308
- // mount/unmount, route change). Bail without rescheduling and without
309
- // onComplete, which would fire animationEnd on a torn-down chart. Mirrors
310
- // the guard in animateDraw (see react-apexcharts#602).
311
- if (w.globals.isDestroyed) return
351
+ const s = onFrame(w, (/** @type {number} */ now) => {
312
352
  const t = Math.max(0, Math.min(1, (now - startAt) / speed))
313
353
  style.transform = `scale(${easeOutBack(t)})`
314
354
  // Fade in over the first half so the back-out overshoot is visible
315
355
  // (rather than blasted in at full opacity from frame 1).
316
356
  style.opacity = String(Math.min(1, t * 2))
317
- if (t < 1) {
318
- BrowserAPIs.requestAnimationFrame(step)
319
- } else {
320
- style.transform = ''
321
- style.transformOrigin = ''
322
- style.transformBox = ''
323
- style.opacity = ''
324
- if (onComplete) onComplete()
325
- }
326
- }
327
- BrowserAPIs.requestAnimationFrame(step)
357
+ if (t < 1) return true
358
+ style.transform = ''
359
+ style.transformOrigin = ''
360
+ style.transformBox = ''
361
+ style.opacity = ''
362
+ if (!--s.n && onComplete) onComplete()
363
+ })
364
+ s.n++
328
365
  }
329
366
 
330
367
  /*
@@ -423,9 +460,8 @@ export default class Animations {
423
460
 
424
461
  if (!w.globals.bulkRevealScheduled) {
425
462
  w.globals.bulkRevealScheduled = true
426
- BrowserAPIs.requestAnimationFrame(() => {
427
- // chart may have been destroyed before this frame ran (see animateDraw)
428
- if (w.globals.isDestroyed) return
463
+ // the frame loop skips it on a chart destroyed before it ran
464
+ onFrame(w, () => {
429
465
  w.globals.bulkRevealScheduled = false
430
466
  const faded = w.globals.fadeRevealEls
431
467
  w.globals.fadeRevealEls = []
@@ -566,9 +602,7 @@ export default class Animations {
566
602
  node.setAttribute('mask', `url(#${maskId})`)
567
603
 
568
604
  const startAt = performance.now() + (delay || 0)
569
- /** @param {number} now */
570
- const step = (now) => {
571
- if (w.globals.isDestroyed) return
605
+ onFrame(w, (/** @type {number} */ now) => {
572
606
  const t = Math.max(0, Math.min(1, (now - startAt) / speed))
573
607
  const eased = easeOutCubic(t)
574
608
  if (isRadial) {
@@ -576,15 +610,11 @@ export default class Animations {
576
610
  } else {
577
611
  revealEl.setAttribute('width', String(eased * targetWidth))
578
612
  }
579
- if (t < 1) {
580
- BrowserAPIs.requestAnimationFrame(step)
581
- } else {
582
- node.removeAttribute('mask')
583
- if (mask.parentNode) mask.parentNode.removeChild(mask)
584
- finalize()
585
- }
586
- }
587
- BrowserAPIs.requestAnimationFrame(step)
613
+ if (t < 1) return true
614
+ node.removeAttribute('mask')
615
+ if (mask.parentNode) mask.parentNode.removeChild(mask)
616
+ finalize()
617
+ })
588
618
  }
589
619
 
590
620
  // Stroke draw via stroke-dashoffset (only safe when no custom dash pattern exists).
@@ -594,58 +624,35 @@ export default class Animations {
594
624
  node.setAttribute('stroke-dashoffset', String(len))
595
625
 
596
626
  const startAt = performance.now() + (delay || 0)
597
- /** @param {number} now */
598
- const step = (now) => {
599
- if (w.globals.isDestroyed) return
627
+ onFrame(w, (/** @type {number} */ now) => {
600
628
  const t = Math.max(0, Math.min(1, (now - startAt) / speed))
601
629
  node.setAttribute('stroke-dashoffset', String(len * (1 - easeOutCubic(t))))
602
- if (t < 1) {
603
- BrowserAPIs.requestAnimationFrame(step)
604
- } else {
605
- node.removeAttribute('stroke-dasharray')
606
- node.removeAttribute('stroke-dashoffset')
607
- finalize()
608
- }
609
- }
610
- BrowserAPIs.requestAnimationFrame(step)
630
+ if (t < 1) return true
631
+ node.removeAttribute('stroke-dasharray')
632
+ node.removeAttribute('stroke-dashoffset')
633
+ finalize()
634
+ })
611
635
  }
612
636
 
613
637
  // Defer one frame: callers (e.g. Line.js for forecast paths) set stroke-dasharray
614
- // *after* renderPaths returns, so a sync check would miss it.
615
- BrowserAPIs.requestAnimationFrame(() => {
616
- // The chart may have been destroyed before this frame ran (e.g. React
617
- // StrictMode mounts, unmounts, then remounts). Its DOM is gone, so bail
618
- // before touching it. Fixes "Cannot read properties of null (reading
619
- // 'node')" in runMaskReveal. See react-apexcharts#602.
620
- if (w.globals.isDestroyed) return
621
-
622
- if (isFill) {
623
- runMaskReveal()
624
- return
625
- }
626
-
638
+ // *after* renderPaths returns, so a sync check would miss it. The job only
639
+ // reads (dash, length) and hands back its writes, which the loop runs
640
+ // after every path's reads, so N series cost one style and layout flush,
641
+ // not N. The loop skips it on a destroyed chart, whose DOM is gone
642
+ // (react-apexcharts#602).
643
+ onFrame(w, () => {
644
+ // A stroke with a custom dash pattern keeps it and reveals via the mask
645
+ // instead of fighting dashoffset.
627
646
  const existingDash = node.getAttribute('stroke-dasharray')
628
- const hasCustomDash =
629
- !!existingDash && existingDash !== '0' && existingDash !== ''
630
- if (hasCustomDash) {
631
- // Preserve dash pattern; reveal via mask instead of fighting dashoffset.
632
- runMaskReveal()
633
- return
634
- }
647
+ if (isFill || (existingDash && existingDash !== '0')) return runMaskReveal
635
648
 
636
649
  let len = 0
637
650
  try {
638
- if (typeof node.getTotalLength === 'function') {
639
- len = node.getTotalLength()
640
- }
651
+ len = node.getTotalLength()
641
652
  } catch (_) {
642
- len = 0
643
- }
644
- if (!len) {
645
- finalize()
646
- return
653
+ // no geometry to measure: nothing to draw
647
654
  }
648
- runStrokeDraw(len)
655
+ return len ? () => runStrokeDraw(len) : finalize
649
656
  })
650
657
  }
651
658
 
@@ -65,21 +65,56 @@ export default class AxisMapping {
65
65
  }
66
66
 
67
67
  /**
68
- * Client (screen) x -> pixels from the plot origin. The origin is the svg
69
- * element's left edge plus `translateX`, never the `.apexcharts-grid` box
70
- * (fact 2 above), so the result does not depend on what the grid happens to
71
- * render. `svgWidth` is the unscaled width the svg was drawn at, so the ratio
72
- * against the measured one is the CSS zoom of any container the chart sits in.
68
+ * The plot area on screen, in client px: its corner is where the plot's
69
+ * group is translated to (translateX, translateY into the svg), and it is
70
+ * gridWidth by gridHeight. The one screen-to-plot measurement: the tooltip,
71
+ * zoom, selection and every other pointer reader map through it, so x and y
72
+ * cannot disagree under CSS zoom.
73
+ *
74
+ * Never the `.apexcharts-grid` group's box: a group measures to the union
75
+ * of what it happens to draw (fact 2 above). The plot's outline starts a
76
+ * pixel down (`Grid.drawGridArea`), so unless gridlines or row bands reach
77
+ * the top edge the box starts a pixel below the plot, and on a numeric-x bar
78
+ * chart the horizontal gridlines run `barPadForNumericAxis` out past both
79
+ * sides.
80
+ *
81
+ * `zoom` is the CSS zoom of any container the chart sits in (screen px per
82
+ * plot px): the measured svg width over `svgWidth`, the unscaled width it
83
+ * was drawn at. A distance measured on screen from this corner is divided
84
+ * by it to land in plot px, the space the series are laid out and painted in.
85
+ * With nothing mounted to measure, screen px are taken as svg px.
86
+ *
87
+ * @param {import('../types/internal').ChartStateW} w
88
+ * @returns {{ left: number, top: number, width: number, height: number, zoom: number }}
89
+ */
90
+ static plotRect(w) {
91
+ const svg =
92
+ w.dom.Paper?.node || w.dom.baseEl?.querySelector('.apexcharts-svg')
93
+ const box = (svg || w.dom.elWrap)?.getBoundingClientRect() || {
94
+ left: 0,
95
+ top: 0,
96
+ width: 0,
97
+ }
98
+ const zoom =
99
+ (svg && w.globals.svgWidth ? box.width / w.globals.svgWidth : 1) || 1
100
+ return {
101
+ left: box.left + w.layout.translateX * zoom,
102
+ top: box.top + w.layout.translateY * zoom,
103
+ width: w.layout.gridWidth * zoom,
104
+ height: w.layout.gridHeight * zoom,
105
+ zoom,
106
+ }
107
+ }
108
+
109
+ /**
110
+ * Client (screen) x -> pixels from the plot origin of {@link plotRect}.
111
+ * Pass the rect when the caller has already measured it.
73
112
  * @param {import('../types/internal').ChartStateW} w
74
113
  * @param {number} screenX
114
+ * @param {{ left: number, zoom: number }} [plot]
75
115
  * @returns {number}
76
116
  */
77
- static screenXToPlotPx(w, screenX) {
78
- const baseEl = w.dom.baseEl
79
- const svg = baseEl && baseEl.querySelector('.apexcharts-svg')
80
- if (!svg) return screenX - w.layout.translateX
81
- const svgRect = svg.getBoundingClientRect()
82
- const zoom = w.globals.svgWidth ? svgRect.width / w.globals.svgWidth : 1
83
- return (screenX - svgRect.left) / (zoom || 1) - w.layout.translateX
117
+ static screenXToPlotPx(w, screenX, plot = AxisMapping.plotRect(w)) {
118
+ return (screenX - plot.left) / plot.zoom
84
119
  }
85
120
  }
@@ -446,6 +446,10 @@ export default class Core {
446
446
  const { config: cnf, globals: gl } = w
447
447
  const { line, boxCandlestick, violin } = renderers
448
448
 
449
+ // A unit chart tweens from the slots of the last render; once another type
450
+ // has drawn over them they are not on screen to tween from.
451
+ if (cnf.chart.type !== 'unit') ctx._unitPrevDots = null
452
+
449
453
  let elGraph = []
450
454
 
451
455
  if (gl.comboCharts) {