apexcharts 8.3.0 → 8.4.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 (277) hide show
  1. package/README.md +7 -7
  2. package/dist/apexcharts.common.js +2 -2
  3. package/dist/apexcharts.core.js +20039 -19931
  4. package/dist/apexcharts.core.min.js +2 -2
  5. package/dist/apexcharts.css +3 -8
  6. package/dist/apexcharts.esm.js +950 -574
  7. package/dist/apexcharts.full.js +23401 -22504
  8. package/dist/apexcharts.full.min.js +2 -2
  9. package/dist/apexcharts.js +21723 -21287
  10. package/dist/apexcharts.min.js +2 -2
  11. package/dist/apexcharts.ssr.common.js +2 -2
  12. package/dist/apexcharts.ssr.esm.js +21758 -21242
  13. package/dist/area.common.js +2 -2
  14. package/dist/area.esm.js +118 -65
  15. package/dist/bar.common.js +2 -2
  16. package/dist/bar.esm.js +404 -218
  17. package/dist/bar.js +2 -2
  18. package/dist/boxPlot.common.js +2 -2
  19. package/dist/boxPlot.esm.js +379 -205
  20. package/dist/bubble.common.js +2 -2
  21. package/dist/bubble.esm.js +118 -65
  22. package/dist/candlestick.common.js +2 -2
  23. package/dist/candlestick.esm.js +379 -205
  24. package/dist/candlestick.js +2 -2
  25. package/dist/column.common.js +2 -2
  26. package/dist/column.esm.js +404 -218
  27. package/dist/core.common.js +2 -2
  28. package/dist/core.esm.js +20034 -19926
  29. package/dist/donut.common.js +2 -2
  30. package/dist/donut.esm.js +106 -11
  31. package/dist/dumbbell.common.js +2 -2
  32. package/dist/dumbbell.esm.js +404 -218
  33. package/dist/features/all.common.js +2 -2
  34. package/dist/features/all.esm.js +282 -184
  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 +137 -124
  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 +31 -15
  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 +206 -119
  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 +1 -1
  63. package/dist/features/keyboard.esm.js +1 -1
  64. package/dist/features/keyboard.js +1 -1
  65. package/dist/features/legend.common.js +2 -2
  66. package/dist/features/legend.esm.js +52 -28
  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 +27 -14
  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 +35 -21
  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 +160 -55
  97. package/dist/features/streamgraph.js +2 -2
  98. package/dist/features/toolbar.common.js +2 -2
  99. package/dist/features/toolbar.esm.js +78 -28
  100. package/dist/features/toolbar.js +2 -2
  101. package/dist/features/trellis.common.js +2 -2
  102. package/dist/features/trellis.esm.js +876 -543
  103. package/dist/features/trellis.js +2 -2
  104. package/dist/features/waterfall.common.js +2 -2
  105. package/dist/features/waterfall.esm.js +14 -25
  106. package/dist/features/waterfall.js +2 -2
  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 +1893 -1342
  112. package/dist/heatmap.common.js +2 -2
  113. package/dist/heatmap.esm.js +38 -54
  114. package/dist/heatmap.js +2 -2
  115. package/dist/histogram.common.js +2 -2
  116. package/dist/histogram.esm.js +455 -276
  117. package/dist/icicle.common.js +2 -2
  118. package/dist/icicle.esm.js +156 -91
  119. package/dist/icicle.js +2 -2
  120. package/dist/line.common.js +2 -2
  121. package/dist/line.esm.js +118 -65
  122. package/dist/line.js +2 -2
  123. package/dist/pictograms.common.js +2 -2
  124. package/dist/pictograms.esm.js +2 -2
  125. package/dist/pictograms.js +2 -2
  126. package/dist/pie.common.js +2 -2
  127. package/dist/pie.esm.js +106 -11
  128. package/dist/pie.js +2 -2
  129. package/dist/polarArea.common.js +2 -2
  130. package/dist/polarArea.esm.js +106 -11
  131. package/dist/radar.common.js +2 -2
  132. package/dist/radar.esm.js +23 -9
  133. package/dist/radar.js +2 -2
  134. package/dist/radialBar.common.js +2 -2
  135. package/dist/radialBar.esm.js +108 -14
  136. package/dist/radialBar.js +2 -2
  137. package/dist/raincloud.common.js +2 -2
  138. package/dist/raincloud.esm.js +39 -31
  139. package/dist/rangeArea.common.js +2 -2
  140. package/dist/rangeArea.esm.js +118 -65
  141. package/dist/rangeBar.common.js +2 -2
  142. package/dist/rangeBar.esm.js +404 -218
  143. package/dist/scatter.common.js +2 -2
  144. package/dist/scatter.esm.js +118 -65
  145. package/dist/streamgraph.common.js +2 -2
  146. package/dist/streamgraph.esm.js +276 -118
  147. package/dist/sunburst.common.js +2 -2
  148. package/dist/sunburst.esm.js +333 -116
  149. package/dist/sunburst.js +2 -2
  150. package/dist/treemap.common.js +2 -2
  151. package/dist/treemap.esm.js +155 -124
  152. package/dist/treemap.js +2 -2
  153. package/dist/unit-shapes.common.js +2 -2
  154. package/dist/unit-shapes.esm.js +2 -2
  155. package/dist/unit-shapes.js +2 -2
  156. package/dist/unit.common.js +2 -2
  157. package/dist/unit.esm.js +29 -27
  158. package/dist/unit.js +2 -2
  159. package/dist/violin.common.js +2 -2
  160. package/dist/violin.esm.js +14 -5
  161. package/dist/violin.js +2 -2
  162. package/dist/waterfall.common.js +2 -2
  163. package/dist/waterfall.esm.js +417 -242
  164. package/package.json +1 -1
  165. package/src/apexcharts.js +37 -60
  166. package/src/assets/apexcharts.css +3 -8
  167. package/src/charts/Bar.js +30 -12
  168. package/src/charts/BarStacked.js +10 -4
  169. package/src/charts/BoxCandleStick.js +8 -2
  170. package/src/charts/Icicle.js +60 -76
  171. package/src/charts/Line.js +58 -22
  172. package/src/charts/Pie.js +136 -14
  173. package/src/charts/Radar.js +6 -9
  174. package/src/charts/Radial.js +2 -6
  175. package/src/charts/RangeBar.js +13 -13
  176. package/src/charts/Scatter.js +5 -7
  177. package/src/charts/Sunburst.js +157 -135
  178. package/src/charts/Treemap.js +100 -56
  179. package/src/charts/common/Breadcrumb.js +37 -16
  180. package/src/charts/common/PreviousPaths.js +45 -0
  181. package/src/charts/common/Stats.js +30 -23
  182. package/src/charts/common/bar/DataLabels.js +85 -77
  183. package/src/charts/common/bar/Helpers.js +144 -56
  184. package/src/charts/common/line/Helpers.js +8 -11
  185. package/src/charts/common/partition/NodeTooltip.js +62 -22
  186. package/src/charts/common/partition/Partition.js +64 -19
  187. package/src/charts/common/partition/Tint.js +6 -3
  188. package/src/charts/common/partition/Transitions.js +108 -0
  189. package/src/charts/common/treemap/ColorScale.js +30 -14
  190. package/src/charts/common/treemap/Helpers.js +47 -68
  191. package/src/charts/common/treemap/Nested.js +2 -3
  192. package/src/features/raincloud.js +16 -10
  193. package/src/features/stats.js +67 -62
  194. package/src/modules/Animations.js +102 -95
  195. package/src/modules/AxisMapping.js +47 -12
  196. package/src/modules/Base.js +12 -14
  197. package/src/modules/CoreUtils.js +26 -46
  198. package/src/modules/Data.js +82 -55
  199. package/src/modules/DataLabels.js +14 -13
  200. package/src/modules/Exports.js +44 -19
  201. package/src/modules/Fill.js +17 -8
  202. package/src/modules/Filters.js +50 -50
  203. package/src/modules/Graphics.js +65 -84
  204. package/src/modules/Markers.js +57 -58
  205. package/src/modules/MorphPieces.js +16 -10
  206. package/src/modules/MorphTypeChange.js +21 -9
  207. package/src/modules/Range.js +32 -55
  208. package/src/modules/RendererController.js +4 -4
  209. package/src/modules/Responsive.js +5 -1
  210. package/src/modules/Scales.js +24 -15
  211. package/src/modules/Series.js +85 -73
  212. package/src/modules/Toolbar.js +3 -2
  213. package/src/modules/UnitLayoutRegistry.js +8 -5
  214. package/src/modules/UnitMarkRegistry.js +7 -10
  215. package/src/modules/ZoomPanSelection.js +4 -0
  216. package/src/modules/animations/AxisTransition.js +11 -4
  217. package/src/modules/animations/DataLabelTransition.js +30 -15
  218. package/src/modules/animations/Easing.js +2 -1
  219. package/src/modules/animations/LayoutTransition.js +43 -32
  220. package/src/modules/animations/LengthTransition.js +176 -84
  221. package/src/modules/annotations/Helpers.js +68 -52
  222. package/src/modules/axes/AxesUtils.js +14 -12
  223. package/src/modules/axes/Grid.js +21 -7
  224. package/src/modules/axes/XAxis.js +4 -4
  225. package/src/modules/axes/YAxis.js +12 -13
  226. package/src/modules/dimensions/Dimensions.js +9 -3
  227. package/src/modules/dimensions/Grid.js +2 -2
  228. package/src/modules/dimensions/XAxis.js +2 -5
  229. package/src/modules/drilldown/Breadcrumb.js +11 -53
  230. package/src/modules/drilldown/Drilldown.js +151 -95
  231. package/src/modules/helpers/InitCtxVariables.js +2 -1
  232. package/src/modules/helpers/UpdateHelpers.js +165 -15
  233. package/src/modules/helpers/XKey.js +56 -0
  234. package/src/modules/highlightFilter/HighlightFilter.js +98 -49
  235. package/src/modules/highlightFilter/PiePart.js +6 -3
  236. package/src/modules/highlightFilter/RadialPart.js +41 -7
  237. package/src/modules/highlightFilter/TreemapPart.js +71 -44
  238. package/src/modules/highlightFilter/util.js +23 -7
  239. package/src/modules/legend/Helpers.js +6 -1
  240. package/src/modules/legend/Legend.js +39 -13
  241. package/src/modules/license/LicenseEnforcer.js +5 -0
  242. package/src/modules/settings/Defaults.js +62 -106
  243. package/src/modules/settings/Globals.js +5 -0
  244. package/src/modules/settings/Options.js +0 -7
  245. package/src/modules/settings/TypeAliases.js +33 -16
  246. package/src/modules/streamgraph/StreamData.js +81 -35
  247. package/src/modules/streamgraph/StreamLabels.js +44 -17
  248. package/src/modules/tooltip/Labels.js +80 -80
  249. package/src/modules/tooltip/Marker.js +25 -21
  250. package/src/modules/tooltip/Position.js +88 -51
  251. package/src/modules/tooltip/Tooltip.js +229 -253
  252. package/src/modules/tooltip/Utils.js +64 -131
  253. package/src/modules/tooltip/constants.js +5 -5
  254. package/src/modules/trellis/Trellis.js +402 -248
  255. package/src/modules/trellis/TrellisChrome.js +110 -44
  256. package/src/modules/trellis/TrellisExports.js +48 -13
  257. package/src/modules/trellis/TrellisFrames.js +11 -15
  258. package/src/modules/trellis/TrellisScales.js +201 -115
  259. package/src/modules/trellis/TrellisSplit.js +72 -36
  260. package/src/modules/trellis/TrellisSync.js +41 -13
  261. package/src/modules/trellis/TrellisTooltip.js +101 -78
  262. package/src/modules/trellis/pivotRows.js +20 -6
  263. package/src/modules/waterfall/WaterfallData.js +14 -30
  264. package/src/modules/weave/WeaveHost.js +18 -12
  265. package/src/pictograms/registry.js +2 -1
  266. package/src/renderers/Renderer.js +41 -37
  267. package/src/renderers/canvas/CanvasCompositor.js +4 -1
  268. package/src/renderers/canvas/CanvasGraphics.js +56 -24
  269. package/src/ssr/RenderShim.js +129 -20
  270. package/src/svg/SVGContainer.js +3 -2
  271. package/src/svg/SVGFilter.js +26 -17
  272. package/src/types/internal.d.ts +8 -6
  273. package/src/unit-shapes/registry.js +3 -1
  274. package/src/utils/DateTime.js +5 -20
  275. package/src/utils/Utils.js +0 -14
  276. package/src/utils/nameAsHtml.js +16 -0
  277. package/types/apexcharts.d.ts +72 -13
@@ -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
@@ -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
  }
@@ -133,23 +133,21 @@ export default class Base {
133
133
  // Cleared at the top of every parse, filled by features/stats, read by
134
134
  // Weave's api.data. null on every chart that has no such transform.
135
135
  seriesObservations: null,
136
- // Waterfall accumulation — written by the waterfall series transform
136
+ // Waterfall accumulation, written by the waterfall series transform
137
137
  // (features/waterfall) each parse; empty for every other chart type.
138
- // values[i][j] = the bar's signed height (end - start), which is
139
- // the delta for a step bar and the sum for a
140
- // subtotal / total bar. This is what a label and a
141
- // tooltip show, in place of the "start - end" a
142
- // range bar would otherwise read out.
143
- // cumulative[i][j] = the running total AFTER bar j, i.e. the level the
144
- // connector to bar j+1 is drawn at.
145
- // kinds[i][j] = 'positive' | 'negative' | 'subtotal' | 'total'
146
- // geometry[i][j] = the px box the bar was actually drawn in, recorded
147
- // by RangeBar. Present (as []) only on a waterfall,
148
- // which is what tells RangeBar to record at all.
138
+ // values[i][j] = the bar's signed height (end - start), which is
139
+ // the delta for a step bar and the sum for a
140
+ // subtotal / total bar. This is what a label and a
141
+ // tooltip show, in place of the "start - end" a
142
+ // range bar would otherwise read out. Empty for a
143
+ // combo series of another type.
144
+ // geometry[i][j] = the px box the bar was actually drawn in, recorded
145
+ // by RangeBar. The connectors are drawn from these
146
+ // boxes alone (the level is the box's far edge).
147
+ // Present (as []) only on a waterfall, which is what
148
+ // tells RangeBar to record at all.
149
149
  waterfallData: {
150
150
  values: [],
151
- cumulative: [],
152
- kinds: [],
153
151
  geometry: null,
154
152
  },
155
153
  // Dumbbell endpoints, written by the dumbbell series transform
@@ -91,29 +91,21 @@ class CoreUtils {
91
91
  return total
92
92
  }
93
93
 
94
- // get total of the all values inside all series
95
94
  /**
96
- * @param {number | null} [index]
95
+ * The total of one series; 0 for an index that names no series.
96
+ * @param {number} index
97
97
  */
98
- getSeriesTotalByIndex(index = null) {
99
- if (index === null) {
100
- // non-plot chart types - pie / donut / circle
101
- return /** @type {any[]} */ (this.w.config.series).reduce(
102
- (/** @type {any} */ acc, /** @type {any} */ cur) => acc + cur,
103
- 0,
104
- )
105
- } else {
106
- const seriesAtIndex = this.w.seriesData.series[index]
107
- if (!Array.isArray(seriesAtIndex)) {
108
- // pie/donut/polarArea/radialBar called per-index from legend loop
109
- return seriesAtIndex ?? 0
110
- }
111
- // axis charts - supporting multiple series
112
- return seriesAtIndex.reduce(
113
- (/** @type {any} */ acc, /** @type {any} */ cur) => acc + cur,
114
- 0,
115
- )
98
+ getSeriesTotalByIndex(index) {
99
+ const seriesAtIndex = this.w.seriesData.series[index]
100
+ if (!Array.isArray(seriesAtIndex)) {
101
+ // pie/donut/polarArea/radialBar called per-index from legend loop
102
+ return seriesAtIndex ?? 0
116
103
  }
104
+ // axis charts - supporting multiple series
105
+ return seriesAtIndex.reduce(
106
+ (/** @type {any} */ acc, /** @type {any} */ cur) => acc + cur,
107
+ 0,
108
+ )
117
109
  }
118
110
 
119
111
  /**
@@ -328,29 +320,16 @@ class CoreUtils {
328
320
  }
329
321
 
330
322
  /**
331
- * @param {number | null} [index]
323
+ * Does the series hold no value at all? True for an index that names none.
324
+ * @param {number} index
332
325
  */
333
- isSeriesNull(index = null) {
334
- let r = []
335
- if (index === null) {
336
- // non-plot chart types - pie / donut / circle
337
- const series = /** @type {any[]} */ (this.w.config.series)
338
- r = series.filter((/** @type {any} */ d) => d !== null)
339
- } else {
340
- const seriesAtIndex = this.w.seriesData.series[index]
341
- if (Array.isArray(seriesAtIndex)) {
342
- // axis charts - supporting multiple series
343
- r = seriesAtIndex.filter((/** @type {any} */ d) => d !== null)
344
- } else {
345
- // pie/donut/polarArea/radialBar called per-index from legend loop
346
- r =
347
- seriesAtIndex !== null && seriesAtIndex !== undefined
348
- ? [seriesAtIndex]
349
- : []
350
- }
351
- }
352
-
353
- return r.length === 0
326
+ isSeriesNull(index) {
327
+ const seriesAtIndex = this.w.seriesData.series[index]
328
+ // axis charts hold an array per series; pie/donut/polarArea/radialBar a
329
+ // single value, read per index from the legend loop
330
+ return Array.isArray(seriesAtIndex)
331
+ ? seriesAtIndex.every((/** @type {any} */ d) => d === null)
332
+ : seriesAtIndex == null
354
333
  }
355
334
 
356
335
  /**
@@ -750,14 +729,15 @@ class CoreUtils {
750
729
  return 0 // Should be Number.NEGATIVE_INFINITY
751
730
  }
752
731
  const w = this.w
732
+ const boundsIndex = w.globals.isMultipleYAxis ? seriesIndex : 0
753
733
  const min_log_val =
754
- w.globals.minYArr[seriesIndex] === 0
734
+ w.globals.minYArr[boundsIndex] === 0
755
735
  ? -1 // make sure we dont calculate log of 0
756
- : this.getBaseLog(b, w.globals.minYArr[seriesIndex])
736
+ : this.getBaseLog(b, w.globals.minYArr[boundsIndex])
757
737
  const max_log_val =
758
- w.globals.maxYArr[seriesIndex] === 0
738
+ w.globals.maxYArr[boundsIndex] === 0
759
739
  ? 0 // make sure we dont calculate log of 0
760
- : this.getBaseLog(b, w.globals.maxYArr[seriesIndex])
740
+ : this.getBaseLog(b, w.globals.maxYArr[boundsIndex])
761
741
  const number_of_height_levels = max_log_val - min_log_val
762
742
  // Values below 1 used to short-circuit to `d / number_of_height_levels`,
763
743
  // returning a raw data value where the caller expects a 0..1 fraction. On a