apexcharts 7.3.0 → 7.5.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 (177) hide show
  1. package/LICENSE +39 -11
  2. package/dist/apexcharts.common.js +2 -2
  3. package/dist/apexcharts.core.js +579 -467
  4. package/dist/apexcharts.core.min.js +2 -2
  5. package/dist/apexcharts.esm.js +1347 -810
  6. package/dist/apexcharts.js +896 -267
  7. package/dist/apexcharts.min.js +2 -2
  8. package/dist/apexcharts.ssr.common.js +2 -2
  9. package/dist/apexcharts.ssr.esm.js +896 -267
  10. package/dist/area.common.js +1 -1
  11. package/dist/area.esm.js +1 -1
  12. package/dist/bar.common.js +2 -2
  13. package/dist/bar.esm.js +20 -10
  14. package/dist/bar.js +2 -2
  15. package/dist/boxPlot.common.js +2 -2
  16. package/dist/boxPlot.esm.js +20 -10
  17. package/dist/bubble.common.js +1 -1
  18. package/dist/bubble.esm.js +1 -1
  19. package/dist/candlestick.common.js +2 -2
  20. package/dist/candlestick.esm.js +20 -10
  21. package/dist/candlestick.js +2 -2
  22. package/dist/column.common.js +2 -2
  23. package/dist/column.esm.js +20 -10
  24. package/dist/core.common.js +2 -2
  25. package/dist/core.esm.js +579 -467
  26. package/dist/donut.common.js +1 -1
  27. package/dist/donut.esm.js +1 -1
  28. package/dist/dumbbell.common.js +2 -2
  29. package/dist/dumbbell.esm.js +20 -10
  30. package/dist/features/all.common.js +2 -2
  31. package/dist/features/all.esm.js +536 -34
  32. package/dist/features/annotations.common.js +1 -1
  33. package/dist/features/annotations.esm.js +1 -1
  34. package/dist/features/annotations.js +1 -1
  35. package/dist/features/context-menu.common.js +1 -1
  36. package/dist/features/context-menu.esm.js +1 -1
  37. package/dist/features/context-menu.js +1 -1
  38. package/dist/features/drilldown.common.js +1 -1
  39. package/dist/features/drilldown.esm.js +1 -1
  40. package/dist/features/drilldown.js +1 -1
  41. package/dist/features/dumbbell.common.js +1 -1
  42. package/dist/features/dumbbell.esm.js +1 -1
  43. package/dist/features/dumbbell.js +1 -1
  44. package/dist/features/exports.common.js +2 -2
  45. package/dist/features/exports.esm.js +12 -14
  46. package/dist/features/exports.js +2 -2
  47. package/dist/features/facet.common.js +1 -1
  48. package/dist/features/facet.esm.js +1 -1
  49. package/dist/features/facet.js +1 -1
  50. package/dist/features/history.common.js +1 -1
  51. package/dist/features/history.esm.js +1 -1
  52. package/dist/features/history.js +1 -1
  53. package/dist/features/ink.common.js +1 -1
  54. package/dist/features/ink.esm.js +1 -1
  55. package/dist/features/ink.js +1 -1
  56. package/dist/features/keyboard.common.js +2 -2
  57. package/dist/features/keyboard.esm.js +14 -1
  58. package/dist/features/keyboard.js +2 -2
  59. package/dist/features/legend.common.js +1 -1
  60. package/dist/features/legend.esm.js +1 -1
  61. package/dist/features/legend.js +1 -1
  62. package/dist/features/link.common.js +1 -1
  63. package/dist/features/link.esm.js +38 -38
  64. package/dist/features/link.js +1 -1
  65. package/dist/features/marks.common.js +1 -1
  66. package/dist/features/marks.esm.js +1 -1
  67. package/dist/features/marks.js +1 -1
  68. package/dist/features/measure.common.js +1 -1
  69. package/dist/features/measure.esm.js +1 -1
  70. package/dist/features/measure.js +1 -1
  71. package/dist/features/morph.common.js +1 -1
  72. package/dist/features/morph.esm.js +1 -1
  73. package/dist/features/morph.js +1 -1
  74. package/dist/features/perspectives.common.js +1 -1
  75. package/dist/features/perspectives.esm.js +11 -11
  76. package/dist/features/perspectives.js +1 -1
  77. package/dist/features/raincloud.common.js +1 -1
  78. package/dist/features/raincloud.esm.js +1 -1
  79. package/dist/features/raincloud.js +1 -1
  80. package/dist/features/renderer-canvas.common.js +1 -1
  81. package/dist/features/renderer-canvas.esm.js +1 -1
  82. package/dist/features/renderer-canvas.js +1 -1
  83. package/dist/features/stats.common.js +1 -1
  84. package/dist/features/stats.esm.js +1 -1
  85. package/dist/features/stats.js +1 -1
  86. package/dist/features/storyboard.common.js +1 -1
  87. package/dist/features/storyboard.esm.js +11 -11
  88. package/dist/features/storyboard.js +1 -1
  89. package/dist/features/streamgraph.common.js +1 -1
  90. package/dist/features/streamgraph.esm.js +1 -1
  91. package/dist/features/streamgraph.js +1 -1
  92. package/dist/features/toolbar.common.js +2 -2
  93. package/dist/features/toolbar.esm.js +172 -25
  94. package/dist/features/toolbar.js +2 -2
  95. package/dist/features/trellis.common.js +1 -1
  96. package/dist/features/trellis.esm.js +1 -1
  97. package/dist/features/trellis.js +1 -1
  98. package/dist/features/waterfall.common.js +1 -1
  99. package/dist/features/waterfall.esm.js +1 -1
  100. package/dist/features/waterfall.js +1 -1
  101. package/dist/features/weave.common.js +2 -2
  102. package/dist/features/weave.esm.js +352 -10
  103. package/dist/features/weave.js +2 -2
  104. package/dist/heatmap.common.js +1 -1
  105. package/dist/heatmap.esm.js +1 -1
  106. package/dist/heatmap.js +1 -1
  107. package/dist/histogram.common.js +2 -2
  108. package/dist/histogram.esm.js +20 -10
  109. package/dist/line.common.js +1 -1
  110. package/dist/line.esm.js +1 -1
  111. package/dist/line.js +1 -1
  112. package/dist/pictograms.common.js +1 -1
  113. package/dist/pictograms.esm.js +1 -1
  114. package/dist/pictograms.js +1 -1
  115. package/dist/pie.common.js +1 -1
  116. package/dist/pie.esm.js +1 -1
  117. package/dist/pie.js +1 -1
  118. package/dist/polarArea.common.js +1 -1
  119. package/dist/polarArea.esm.js +1 -1
  120. package/dist/radar.common.js +1 -1
  121. package/dist/radar.esm.js +1 -1
  122. package/dist/radar.js +1 -1
  123. package/dist/radialBar.common.js +2 -2
  124. package/dist/radialBar.esm.js +27 -2
  125. package/dist/radialBar.js +2 -2
  126. package/dist/raincloud.common.js +2 -2
  127. package/dist/raincloud.esm.js +20 -10
  128. package/dist/rangeArea.common.js +1 -1
  129. package/dist/rangeArea.esm.js +1 -1
  130. package/dist/rangeBar.common.js +2 -2
  131. package/dist/rangeBar.esm.js +20 -10
  132. package/dist/scatter.common.js +1 -1
  133. package/dist/scatter.esm.js +1 -1
  134. package/dist/streamgraph.common.js +1 -1
  135. package/dist/streamgraph.esm.js +1 -1
  136. package/dist/sunburst.common.js +1 -1
  137. package/dist/sunburst.esm.js +1 -1
  138. package/dist/sunburst.js +1 -1
  139. package/dist/treemap.common.js +1 -1
  140. package/dist/treemap.esm.js +1 -1
  141. package/dist/treemap.js +1 -1
  142. package/dist/unit-shapes.common.js +1 -1
  143. package/dist/unit-shapes.esm.js +1 -1
  144. package/dist/unit-shapes.js +1 -1
  145. package/dist/unit.common.js +1 -1
  146. package/dist/unit.esm.js +79 -79
  147. package/dist/unit.js +1 -1
  148. package/dist/violin.common.js +2 -2
  149. package/dist/violin.esm.js +20 -10
  150. package/dist/violin.js +2 -2
  151. package/dist/waterfall.common.js +2 -2
  152. package/dist/waterfall.esm.js +20 -10
  153. package/package.json +2 -2
  154. package/src/apexcharts.js +39 -15
  155. package/src/charts/Radial.js +12 -3
  156. package/src/charts/common/bar/Helpers.js +31 -12
  157. package/src/modules/Data.js +8 -2
  158. package/src/modules/DataLabels.js +14 -4
  159. package/src/modules/Exports.js +14 -12
  160. package/src/modules/Formatters.js +24 -1
  161. package/src/modules/Graphics.js +10 -0
  162. package/src/modules/Range.js +28 -1
  163. package/src/modules/Toolbar.js +112 -2
  164. package/src/modules/ZoomPanSelection.js +85 -7
  165. package/src/modules/accessibility/KeyboardNavigation.js +14 -0
  166. package/src/modules/axes/YAxis.js +12 -15
  167. package/src/modules/helpers/UpdateHelpers.js +25 -8
  168. package/src/modules/settings/Globals.js +5 -10
  169. package/src/modules/settings/Options.js +15 -0
  170. package/src/modules/weave/Claims.js +231 -0
  171. package/src/modules/weave/Drawn.js +169 -0
  172. package/src/modules/weave/PluginAPI.js +195 -4
  173. package/src/modules/weave/WeaveHost.js +76 -3
  174. package/src/ssr/DOMShim.js +50 -0
  175. package/src/types/internal.d.ts +1 -1
  176. package/src/utils/Utils.js +6 -9
  177. package/types/apexcharts.d.ts +92 -0
@@ -180,9 +180,10 @@ export default class UpdateHelpers {
180
180
  ? prevInitialSeries
181
181
  : Utils.copySeriesShallow(w.config.series)
182
182
  w.globals.initialConfig = initialConfig
183
- // lazy snapshot: deep clone deferred to first read
184
- w.globals.initialSeries = w.config.series
185
-
183
+ if (options.series) {
184
+ // lazy snapshot: deep clone deferred to first read
185
+ w.globals.initialSeries = w.config.series
186
+ }
186
187
  }
187
188
 
188
189
  // Keep legend-hidden (collapsed) series hidden across ANY series
@@ -277,6 +278,9 @@ export default class UpdateHelpers {
277
278
  w.globals.treemapRawSeries = null
278
279
  }
279
280
 
281
+ const definedSeries = newSeries
282
+ let reconciledCollapses = false
283
+
280
284
  // Keep legend-hidden (collapsed) series hidden across a data update,
281
285
  // reconciled BY CATEGORY NAME by the same code the options path uses: a
282
286
  // name still present stays hidden at its possibly-new index, one the
@@ -299,11 +303,15 @@ export default class UpdateHelpers {
299
303
  s && typeof s === 'object' ? { ...s } : s,
300
304
  )
301
305
  this.ctx.series.reconcileCollapsedByName(newSeries)
306
+ reconciledCollapses = true
302
307
  }
303
308
 
304
309
  this.ctx.data.resetParsingFlags()
305
310
  // Phase 1: return value captured; writer stubs are no-ops.
306
- const parsedState = this.ctx.data.parseData(newSeries)
311
+ const parsedState = this.ctx.data.parseData(
312
+ newSeries,
313
+ overwriteInitialSeries,
314
+ )
307
315
  this.ctx._writeParsedSeriesData(parsedState.seriesData)
308
316
  this.ctx._writeParsedRangeData(parsedState.rangeData)
309
317
  this.ctx._writeParsedCandleData(parsedState.candleData)
@@ -315,14 +323,23 @@ export default class UpdateHelpers {
315
323
  // defined. A copy, never the live array: this site used to assign
316
324
  // w.config.series itself, restoring the very alias #5118 came from, so
317
325
  // one updateSeries() would undo the capture Globals.init now makes.
318
- // initialSeries was already captured by parseData above through the
319
- // lazy-snapshot setter, so no deep clone happens here either.
326
+ // Both snapshots come off the array the caller handed in once the
327
+ // reconcile above has run, since that emptied the collapsed rows and
328
+ // parseData baselines from what it parses: the baseline would forget
329
+ // the data of exactly the series the viewer switched off.
330
+ // _updateOptions gets this for free, it reconciles after capturing.
320
331
  if (w.globals.initialConfig) {
321
332
  w.globals.initialConfig.series = Utils.copySeriesShallow(
322
- w.config.series,
333
+ reconciledCollapses ? definedSeries : w.config.series,
323
334
  )
324
335
  }
325
- w.globals.initialSeries = w.config.series
336
+ // With no reconcile, initialSeries stays as parseData left it: on the
337
+ // raw-stash and dataReducer types config.series holds rows this library
338
+ // derived, and reassigning it here made the baseline the bin counts,
339
+ // the accumulated pairs or the downsampled window (#5283).
340
+ if (reconciledCollapses) {
341
+ w.globals.initialSeries = definedSeries
342
+ }
326
343
  }
327
344
 
328
345
  // Use the fast path when the series structure is compatible:
@@ -554,16 +554,11 @@ export default class Globals {
554
554
  * which cannot reach the captured copies because each series object was
555
555
  * copied at capture time.
556
556
  *
557
- * The one in-place mutator is appendData(), whose push loop grows the shared
558
- * data array. Re-capturing after it does not undo that: the pushed points
559
- * are already in the array both snapshots point at, so a snapshot taken
560
- * before the append reads back as appended. That is the documented
561
- * behaviour of appendData(overwriteInitialSeries = true), and the `false`
562
- * case is not honoured for a separate, older reason: Data.parseData()
563
- * re-captures initialSeries unconditionally on the re-render appendData
564
- * triggers. Detaching would mean copying the data arrays, which is exactly
565
- * the per-point cost this snapshot exists to avoid. The same exception
566
- * applies to `initialConfig.series`, which is captured with the same shape.
557
+ * appendData() used to be the exception, growing the shared data array in
558
+ * place. It now replaces the array instead (`data = data.concat(newData)`):
559
+ * one array copy per call, against the per-point cost this snapshot exists
560
+ * to avoid. With that, no internal edit can reach a captured copy. The same
561
+ * holds for `initialConfig.series`, which is captured with the same shape.
567
562
  *
568
563
  * @param {Record<string, any>} globals
569
564
  */
@@ -769,6 +769,17 @@ export default class Options {
769
769
  // around the pinch centroid (matching the x-only wheel/toolbar zoom),
770
770
  // frame-by-frame rather than the 400ms wheel throttle.
771
771
  pinch: 'auto',
772
+ // Drag-to-zoom is deliberate, so it is not gated the way those two
773
+ // are: it stays on with the toolbar hidden. The reset button that
774
+ // undoes it does not, which leaves the viewer in a window with no
775
+ // exit. So while the chart IS zoomed and nothing else on screen can
776
+ // reset it, one reset control is drawn where the toolbar would have
777
+ // been, and it goes when the range does. A chart nobody zooms is
778
+ // untouched. Set false for a page that supplies its own reset (which
779
+ // also turns off the Escape shortcut), or true to force the control
780
+ // on whenever the chart is zoomed, even where `toolbar.tools.reset`
781
+ // is off.
782
+ resetControl: 'auto',
772
783
  zoomedArea: {
773
784
  fill: {
774
785
  color: '#90CAF9',
@@ -832,6 +843,10 @@ export default class Options {
832
843
  distributed: false,
833
844
  borderRadius: 0,
834
845
  borderRadiusApplication: 'around', // [around, end]
846
+ // Which segments of a stack round. 'all' caps both ends of the
847
+ // stack (baseline and far end); 'last' caps only the far end, so
848
+ // the stack sits square on the baseline. Stacked charts only.
849
+ borderRadiusWhenStacked: 'all', // [all, last]
835
850
  rangeBarOverlap: true,
836
851
  rangeBarGroupRows: false,
837
852
  hideZeroBarsWhenGrouped: false,
@@ -0,0 +1,231 @@
1
+ // @ts-check
2
+ /**
3
+ * Positional option claims: how a plugin sets an option for ITS OWN series.
4
+ *
5
+ * Some chart options are indexed by series position with no per-series escape
6
+ * hatch. `stroke.dashArray` is the clearest: a plugin that adds a projection and
7
+ * wants only the projection dashed has to write the array covering every series.
8
+ * Done in the plugin, that has four failure modes, three of which no amount of
9
+ * care can fix:
10
+ *
11
+ * 1. it flattens the caller's own dashes, unless it first reads them,
12
+ * 2. it restores a stale value if the caller changed the option meanwhile,
13
+ * 3. it leaks the write permanently if the plugin throws before restoring,
14
+ * 4. two plugins fight, and whichever releases last restores the other's write.
15
+ *
16
+ * So the plugin declares a CLAIM and the host answers the question where the
17
+ * option is READ. Nothing is written to `w.config`: the caller's configuration
18
+ * is what it always was, and a claim is an overlay consulted at the point of
19
+ * use. Releasing a claim is therefore not a restore, it is a deletion, and
20
+ * there is nothing to put back or to get wrong.
21
+ *
22
+ * ## Why this is an allowlist
23
+ *
24
+ * An unbounded `claim(path, value)` is "write anything to the caller's config",
25
+ * which is the thing this platform refuses to do. An option earns a place here
26
+ * only if it is positional, has no per-series alternative in the caller's own
27
+ * data, and is read in few enough places to resolve at all. `colors` fails the
28
+ * second test: a series carries its own `color`, so a plugin adding one sets it
29
+ * there and needs nothing from the host.
30
+ *
31
+ * ## Where the state lives
32
+ *
33
+ * On `w`, beside `dom` and `interact`, for the reason those two are there
34
+ * rather than in globals: `initGlobalVars` runs again on every `resetGlobals`,
35
+ * so anything in globals is wiped by an `updateSeries`. A claim has to survive
36
+ * a caller's update, because the point of it is that the caller's own changes
37
+ * and the plugin's claim compose instead of overwriting each other. Created
38
+ * lazily, so a chart with no claims carries nothing and a build without the
39
+ * Weave host never creates it at all.
40
+ *
41
+ * See `plans/26-weave-capabilities.md`.
42
+ *
43
+ * @module weave/Claims
44
+ */
45
+
46
+ /**
47
+ * Options a plugin may claim, and the value type each takes per series.
48
+ *
49
+ * Adding a row is a decision about the three-part test in this file's header,
50
+ * not a convenience. `tests/unit/weave-claims.spec.js` asserts the shape of
51
+ * this table so a row cannot be added without one.
52
+ *
53
+ * `dataLabels.enabledOnSeries` is a membership list in the caller's config and
54
+ * a boolean here on purpose: a claim answers "does THIS series print labels",
55
+ * which is the question the consumption site asks, rather than restating the
56
+ * caller's list shape.
57
+ */
58
+ export const CLAIMABLE = Object.freeze({
59
+ 'stroke.dashArray': Object.freeze({ type: 'number' }),
60
+ 'dataLabels.enabledOnSeries': Object.freeze({ type: 'boolean' }),
61
+ })
62
+
63
+ /**
64
+ * @typedef {Object} ClaimEntry
65
+ * @property {string|number} series a series NAME, or a position
66
+ * @property {any} value
67
+ */
68
+
69
+ /**
70
+ * @typedef {Object} ClaimRecord
71
+ * @property {string} owner
72
+ * @property {string} option
73
+ * @property {ClaimEntry[]} entries
74
+ */
75
+
76
+ /**
77
+ * The per-chart store, created on first claim.
78
+ * @param {any} w
79
+ */
80
+ function store(w) {
81
+ if (!w.weaveClaims) w.weaveClaims = { byOption: new Map() }
82
+ return w.weaveClaims
83
+ }
84
+
85
+ /** @param {any} w @param {string} option @returns {ClaimRecord[]} */
86
+ function claimsFor(w, option) {
87
+ const s = store(w)
88
+ if (!s.byOption.has(option)) s.byOption.set(option, [])
89
+ return s.byOption.get(option)
90
+ }
91
+
92
+ /**
93
+ * Keep the entries this option can actually use, warning about the rest.
94
+ *
95
+ * A bad entry is dropped rather than failing the whole claim: a plugin claiming
96
+ * ten series and getting one wrong should still dash the nine, and the warning
97
+ * names the option and the value so the tenth is findable.
98
+ *
99
+ * @param {string} option
100
+ * @param {any} entries
101
+ * @returns {ClaimEntry[]}
102
+ */
103
+ export function normaliseEntries(option, entries) {
104
+ const spec = /** @type {Record<string, {type: string}>} */ (CLAIMABLE)[option]
105
+ const out = []
106
+ for (const entry of Array.isArray(entries) ? entries : []) {
107
+ if (!entry || (typeof entry.series !== 'string' && typeof entry.series !== 'number')) {
108
+ console.warn(
109
+ `[apexcharts] claim on "${option}": each entry needs a series name or index.`,
110
+ )
111
+ continue
112
+ }
113
+ if (typeof entry.value !== spec.type) {
114
+ console.warn(
115
+ `[apexcharts] claim on "${option}": expected a ${spec.type} for series ${String(
116
+ entry.series,
117
+ )}, got ${typeof entry.value}.`,
118
+ )
119
+ continue
120
+ }
121
+ out.push({ series: entry.series, value: entry.value })
122
+ }
123
+ return out
124
+ }
125
+
126
+ /**
127
+ * Register a claim. Returns null for an option that is not claimable, so a
128
+ * plugin written against a newer host degrades instead of throwing.
129
+ *
130
+ * @param {any} w
131
+ * @param {string} owner plugin name
132
+ * @param {string} option
133
+ * @param {any} entries
134
+ * @returns {ClaimRecord|null}
135
+ */
136
+ export function addClaim(w, owner, option, entries) {
137
+ if (!Object.prototype.hasOwnProperty.call(CLAIMABLE, option)) {
138
+ console.warn(
139
+ `[apexcharts] "${option}" is not a claimable option. Claimable: ${Object.keys(
140
+ CLAIMABLE,
141
+ ).join(', ')}.`,
142
+ )
143
+ return null
144
+ }
145
+ const record = { owner, option, entries: normaliseEntries(option, entries) }
146
+ // Appended, so the LAST claim on a series wins and the order is the order
147
+ // plugins ran in. Deterministic, and independent of render order.
148
+ claimsFor(w, option).push(record)
149
+ return record
150
+ }
151
+
152
+ /**
153
+ * Drop one claim. Idempotent: releasing twice is not an error.
154
+ * @param {any} w
155
+ * @param {ClaimRecord} record
156
+ */
157
+ export function releaseClaim(w, record) {
158
+ if (!w.weaveClaims || !record) return
159
+ const list = w.weaveClaims.byOption.get(record.option)
160
+ if (!list) return
161
+ const at = list.indexOf(record)
162
+ if (at > -1) list.splice(at, 1)
163
+ }
164
+
165
+ /**
166
+ * Drop every claim held by one plugin.
167
+ *
168
+ * Called on teardown and when the host disables a plugin after repeated hook
169
+ * failures, which is what makes failure mode 3 unreachable: a plugin cannot
170
+ * leave a claim behind, however badly it behaves.
171
+ *
172
+ * @param {any} w
173
+ * @param {string} owner
174
+ */
175
+ export function releaseOwner(w, owner) {
176
+ if (!w.weaveClaims) return
177
+ for (const [option, list] of w.weaveClaims.byOption) {
178
+ const kept = list.filter((/** @type {ClaimRecord} */ c) => c.owner !== owner)
179
+ if (kept.length !== list.length) w.weaveClaims.byOption.set(option, kept)
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Which series an entry names, or -1.
185
+ *
186
+ * A NAME is resolved here rather than at claim time, which is what lets a claim
187
+ * survive the caller adding, removing or reordering series: the position a name
188
+ * resolves to is read fresh every time the option is. A number is taken as a
189
+ * position, for charts whose series have no names.
190
+ *
191
+ * @param {any} w @param {string|number} series
192
+ */
193
+ function indexOfSeries(w, series) {
194
+ if (typeof series === 'number') return series
195
+ const list = (w.config && w.config.series) || []
196
+ for (let i = 0; i < list.length; i++) {
197
+ if (list[i] && list[i].name === series) return i
198
+ }
199
+ return -1
200
+ }
201
+
202
+ /**
203
+ * The value of a claimable option for one series: a claim if one names it,
204
+ * otherwise what the caller configured.
205
+ *
206
+ * The hot path is the absence of claims, which is every chart that has no
207
+ * plugin doing this: one property check and out. Call it with the value the
208
+ * caller's config resolves to, so this file never has to know the shape of
209
+ * an option, only that a claim overrides it.
210
+ *
211
+ * @template T
212
+ * @param {any} w
213
+ * @param {string} option
214
+ * @param {number} seriesIndex
215
+ * @param {T} fallback what the caller's own config says for this series
216
+ * @returns {T}
217
+ */
218
+ export function resolveClaimed(w, option, seriesIndex, fallback) {
219
+ if (!w || !w.weaveClaims) return fallback
220
+ const list = w.weaveClaims.byOption.get(option)
221
+ if (!list || !list.length) return fallback
222
+
223
+ let resolved = fallback
224
+ // Forwards, so the last claim naming this series wins: see addClaim.
225
+ for (const claim of list) {
226
+ for (const entry of claim.entries) {
227
+ if (indexOfSeries(w, entry.series) === seriesIndex) resolved = entry.value
228
+ }
229
+ }
230
+ return resolved
231
+ }
@@ -0,0 +1,169 @@
1
+ // @ts-check
2
+ /**
3
+ * What is drawn on this chart, as one list a plugin can read.
4
+ *
5
+ * A plugin sees the chart through the facade, and the facade told it about the
6
+ * chart's series and nothing else: not the caller's annotations, not another
7
+ * feature's ink, not another plugin's overlay. So the panel everyone reaches
8
+ * for first (list what is on this chart, so a viewer can see what is there)
9
+ * could only ever enumerate the asking plugin's own tools, which its toolbar
10
+ * already shows.
11
+ *
12
+ * The alternative to this file is one narrow member per question: a Layers API,
13
+ * then an export that wants to list contents, then an a11y summary. Publishing
14
+ * the inventory once answers all three.
15
+ *
16
+ * ## Read only, deliberately
17
+ *
18
+ * Nothing here removes or hides anything. A list you can act on means letting a
19
+ * plugin change another feature's output, which is a much larger promise than
20
+ * this platform makes today and is a decision of its own. See
21
+ * `plans/26-weave-capabilities.md` §3.4.
22
+ *
23
+ * ## Partial by design, and it says so
24
+ *
25
+ * Every contributor is opt in. A feature that declares nothing is simply absent
26
+ * rather than approximated, which is why each entry carries `owner`: a reader
27
+ * can tell what the list covers instead of assuming it is exhaustive.
28
+ *
29
+ * @module weave/Drawn
30
+ */
31
+
32
+ /**
33
+ * @typedef {Object} DrawnItem
34
+ * @property {string} id stable within this chart, opaque to readers
35
+ * @property {'series'|'annotation'|'overlay'} kind
36
+ * @property {string} label a human name where one exists
37
+ * @property {string} owner 'core', or the plugin name that put it there
38
+ * @property {boolean} visible
39
+ */
40
+
41
+ /** Annotation buckets in config, in the order a reader meets them. */
42
+ const ANNOTATION_TYPES = ['xaxis', 'yaxis', 'points', 'texts', 'images']
43
+
44
+ /**
45
+ * Whether the viewer has switched this series off in the legend.
46
+ *
47
+ * Both collapse lists, because a series on a secondary axis lands in the
48
+ * ancillary one and is just as hidden to the person looking at the chart.
49
+ *
50
+ * @param {any} w @param {number} index
51
+ */
52
+ function seriesVisible(w, index) {
53
+ const gl = w.globals
54
+ return (
55
+ (gl.collapsedSeriesIndices || []).indexOf(index) < 0 &&
56
+ (gl.ancillaryCollapsedSeriesIndices || []).indexOf(index) < 0
57
+ )
58
+ }
59
+
60
+ /**
61
+ * The chart's own series, including any a plugin added.
62
+ *
63
+ * Owner comes from the same `markDerived` bookkeeping that protects a caller's
64
+ * reset, so a computed series is attributed to the plugin that computed it
65
+ * rather than to the chart.
66
+ *
67
+ * @param {any} w @param {any} host @returns {DrawnItem[]}
68
+ */
69
+ function seriesItems(w, host) {
70
+ const derived = host && host._derived
71
+ /** @param {string} name */
72
+ const ownerOf = (name) => {
73
+ if (!derived) return 'core'
74
+ for (const [plugin, names] of derived) {
75
+ if (names.indexOf(name) > -1) return plugin
76
+ }
77
+ return 'core'
78
+ }
79
+
80
+ return (w.config.series || []).map((/** @type {any} */ s, /** @type {number} */ i) => {
81
+ const label = s && s.name ? String(s.name) : `Series ${i + 1}`
82
+ return {
83
+ id: `series:${i}`,
84
+ kind: /** @type {const} */ ('series'),
85
+ label,
86
+ owner: ownerOf(label),
87
+ visible: seriesVisible(w, i),
88
+ }
89
+ })
90
+ }
91
+
92
+ /**
93
+ * The caller's annotations, and anything that draws by creating one.
94
+ *
95
+ * Ink strokes arrive here rather than through a contributor of their own: an
96
+ * ink stroke IS an annotation, written into `config.annotations` with an id of
97
+ * its own (`InkLayer.js`). Attribution is the `owner` an author set on the
98
+ * record, so a feature that creates annotations says so rather than this file
99
+ * pattern-matching ids.
100
+ *
101
+ * @param {any} w @returns {DrawnItem[]}
102
+ */
103
+ function annotationItems(w) {
104
+ const config = w.config.annotations || {}
105
+ /** @type {DrawnItem[]} */
106
+ const out = []
107
+
108
+ for (const type of ANNOTATION_TYPES) {
109
+ const list = Array.isArray(config[type]) ? config[type] : []
110
+ list.forEach((/** @type {any} */ anno, /** @type {number} */ i) => {
111
+ if (!anno) return
112
+ out.push({
113
+ // An annotation carries an id only when someone gave it one, so the
114
+ // synthesised form is what most config-declared annotations get. It is
115
+ // stable for as long as the list is, which is what a reader needs.
116
+ id: anno.id ? String(anno.id) : `annotation:${type}:${i}`,
117
+ kind: /** @type {const} */ ('annotation'),
118
+ label: labelOfAnnotation(anno, type, i),
119
+ owner: anno.owner ? String(anno.owner) : 'core',
120
+ // An annotation is drawn whenever it is in the config: there is no
121
+ // hidden state for one, unlike a series.
122
+ visible: true,
123
+ })
124
+ })
125
+ }
126
+ return out
127
+ }
128
+
129
+ /**
130
+ * The most useful name an annotation has: its label text, then its own id,
131
+ * then what kind it is. A reader is choosing between rows, so a row that says
132
+ * "Launch" beats one that says "xaxis annotation 2".
133
+ *
134
+ * @param {any} anno @param {string} type @param {number} i
135
+ */
136
+ function labelOfAnnotation(anno, type, i) {
137
+ const text = anno.label && anno.label.text
138
+ if (text) return String(text)
139
+ if (anno.text) return String(anno.text)
140
+ if (anno.id) return String(anno.id)
141
+ return `${type} annotation ${i + 1}`
142
+ }
143
+
144
+ /**
145
+ * Everything on the chart, in reading order: series, then annotations, then
146
+ * what plugins have declared.
147
+ *
148
+ * Rebuilt per call rather than cached. It is a projection of live state, and a
149
+ * remembered inventory is a list of what WAS drawn.
150
+ *
151
+ * @param {any} w @param {any} host @returns {DrawnItem[]}
152
+ */
153
+ export function collectDrawn(w, host) {
154
+ const declared = []
155
+ if (host && host._declared) {
156
+ for (const [plugin, items] of host._declared) {
157
+ for (const item of items) {
158
+ declared.push({
159
+ id: `overlay:${plugin}:${item.id}`,
160
+ kind: /** @type {const} */ ('overlay'),
161
+ label: item.label,
162
+ owner: plugin,
163
+ visible: item.visible !== false,
164
+ })
165
+ }
166
+ }
167
+ }
168
+ return [...seriesItems(w, host), ...annotationItems(w), ...declared]
169
+ }