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.
- package/LICENSE +39 -11
- package/dist/apexcharts.common.js +2 -2
- package/dist/apexcharts.core.js +579 -467
- package/dist/apexcharts.core.min.js +2 -2
- package/dist/apexcharts.esm.js +1347 -810
- package/dist/apexcharts.js +896 -267
- package/dist/apexcharts.min.js +2 -2
- package/dist/apexcharts.ssr.common.js +2 -2
- package/dist/apexcharts.ssr.esm.js +896 -267
- package/dist/area.common.js +1 -1
- package/dist/area.esm.js +1 -1
- package/dist/bar.common.js +2 -2
- package/dist/bar.esm.js +20 -10
- package/dist/bar.js +2 -2
- package/dist/boxPlot.common.js +2 -2
- package/dist/boxPlot.esm.js +20 -10
- package/dist/bubble.common.js +1 -1
- package/dist/bubble.esm.js +1 -1
- package/dist/candlestick.common.js +2 -2
- package/dist/candlestick.esm.js +20 -10
- package/dist/candlestick.js +2 -2
- package/dist/column.common.js +2 -2
- package/dist/column.esm.js +20 -10
- package/dist/core.common.js +2 -2
- package/dist/core.esm.js +579 -467
- package/dist/donut.common.js +1 -1
- package/dist/donut.esm.js +1 -1
- package/dist/dumbbell.common.js +2 -2
- package/dist/dumbbell.esm.js +20 -10
- package/dist/features/all.common.js +2 -2
- package/dist/features/all.esm.js +536 -34
- package/dist/features/annotations.common.js +1 -1
- package/dist/features/annotations.esm.js +1 -1
- package/dist/features/annotations.js +1 -1
- package/dist/features/context-menu.common.js +1 -1
- package/dist/features/context-menu.esm.js +1 -1
- package/dist/features/context-menu.js +1 -1
- package/dist/features/drilldown.common.js +1 -1
- package/dist/features/drilldown.esm.js +1 -1
- package/dist/features/drilldown.js +1 -1
- package/dist/features/dumbbell.common.js +1 -1
- package/dist/features/dumbbell.esm.js +1 -1
- package/dist/features/dumbbell.js +1 -1
- package/dist/features/exports.common.js +2 -2
- package/dist/features/exports.esm.js +12 -14
- package/dist/features/exports.js +2 -2
- package/dist/features/facet.common.js +1 -1
- package/dist/features/facet.esm.js +1 -1
- package/dist/features/facet.js +1 -1
- package/dist/features/history.common.js +1 -1
- package/dist/features/history.esm.js +1 -1
- package/dist/features/history.js +1 -1
- package/dist/features/ink.common.js +1 -1
- package/dist/features/ink.esm.js +1 -1
- package/dist/features/ink.js +1 -1
- package/dist/features/keyboard.common.js +2 -2
- package/dist/features/keyboard.esm.js +14 -1
- package/dist/features/keyboard.js +2 -2
- package/dist/features/legend.common.js +1 -1
- package/dist/features/legend.esm.js +1 -1
- package/dist/features/legend.js +1 -1
- package/dist/features/link.common.js +1 -1
- package/dist/features/link.esm.js +38 -38
- package/dist/features/link.js +1 -1
- package/dist/features/marks.common.js +1 -1
- package/dist/features/marks.esm.js +1 -1
- package/dist/features/marks.js +1 -1
- package/dist/features/measure.common.js +1 -1
- package/dist/features/measure.esm.js +1 -1
- package/dist/features/measure.js +1 -1
- package/dist/features/morph.common.js +1 -1
- package/dist/features/morph.esm.js +1 -1
- package/dist/features/morph.js +1 -1
- package/dist/features/perspectives.common.js +1 -1
- package/dist/features/perspectives.esm.js +11 -11
- package/dist/features/perspectives.js +1 -1
- package/dist/features/raincloud.common.js +1 -1
- package/dist/features/raincloud.esm.js +1 -1
- package/dist/features/raincloud.js +1 -1
- package/dist/features/renderer-canvas.common.js +1 -1
- package/dist/features/renderer-canvas.esm.js +1 -1
- package/dist/features/renderer-canvas.js +1 -1
- package/dist/features/stats.common.js +1 -1
- package/dist/features/stats.esm.js +1 -1
- package/dist/features/stats.js +1 -1
- package/dist/features/storyboard.common.js +1 -1
- package/dist/features/storyboard.esm.js +11 -11
- package/dist/features/storyboard.js +1 -1
- package/dist/features/streamgraph.common.js +1 -1
- package/dist/features/streamgraph.esm.js +1 -1
- package/dist/features/streamgraph.js +1 -1
- package/dist/features/toolbar.common.js +2 -2
- package/dist/features/toolbar.esm.js +172 -25
- package/dist/features/toolbar.js +2 -2
- package/dist/features/trellis.common.js +1 -1
- package/dist/features/trellis.esm.js +1 -1
- package/dist/features/trellis.js +1 -1
- package/dist/features/waterfall.common.js +1 -1
- package/dist/features/waterfall.esm.js +1 -1
- package/dist/features/waterfall.js +1 -1
- package/dist/features/weave.common.js +2 -2
- package/dist/features/weave.esm.js +352 -10
- package/dist/features/weave.js +2 -2
- package/dist/heatmap.common.js +1 -1
- package/dist/heatmap.esm.js +1 -1
- package/dist/heatmap.js +1 -1
- package/dist/histogram.common.js +2 -2
- package/dist/histogram.esm.js +20 -10
- package/dist/line.common.js +1 -1
- package/dist/line.esm.js +1 -1
- package/dist/line.js +1 -1
- package/dist/pictograms.common.js +1 -1
- package/dist/pictograms.esm.js +1 -1
- package/dist/pictograms.js +1 -1
- package/dist/pie.common.js +1 -1
- package/dist/pie.esm.js +1 -1
- package/dist/pie.js +1 -1
- package/dist/polarArea.common.js +1 -1
- package/dist/polarArea.esm.js +1 -1
- package/dist/radar.common.js +1 -1
- package/dist/radar.esm.js +1 -1
- package/dist/radar.js +1 -1
- package/dist/radialBar.common.js +2 -2
- package/dist/radialBar.esm.js +27 -2
- package/dist/radialBar.js +2 -2
- package/dist/raincloud.common.js +2 -2
- package/dist/raincloud.esm.js +20 -10
- package/dist/rangeArea.common.js +1 -1
- package/dist/rangeArea.esm.js +1 -1
- package/dist/rangeBar.common.js +2 -2
- package/dist/rangeBar.esm.js +20 -10
- package/dist/scatter.common.js +1 -1
- package/dist/scatter.esm.js +1 -1
- package/dist/streamgraph.common.js +1 -1
- package/dist/streamgraph.esm.js +1 -1
- package/dist/sunburst.common.js +1 -1
- package/dist/sunburst.esm.js +1 -1
- package/dist/sunburst.js +1 -1
- package/dist/treemap.common.js +1 -1
- package/dist/treemap.esm.js +1 -1
- package/dist/treemap.js +1 -1
- package/dist/unit-shapes.common.js +1 -1
- package/dist/unit-shapes.esm.js +1 -1
- package/dist/unit-shapes.js +1 -1
- package/dist/unit.common.js +1 -1
- package/dist/unit.esm.js +79 -79
- package/dist/unit.js +1 -1
- package/dist/violin.common.js +2 -2
- package/dist/violin.esm.js +20 -10
- package/dist/violin.js +2 -2
- package/dist/waterfall.common.js +2 -2
- package/dist/waterfall.esm.js +20 -10
- package/package.json +2 -2
- package/src/apexcharts.js +39 -15
- package/src/charts/Radial.js +12 -3
- package/src/charts/common/bar/Helpers.js +31 -12
- package/src/modules/Data.js +8 -2
- package/src/modules/DataLabels.js +14 -4
- package/src/modules/Exports.js +14 -12
- package/src/modules/Formatters.js +24 -1
- package/src/modules/Graphics.js +10 -0
- package/src/modules/Range.js +28 -1
- package/src/modules/Toolbar.js +112 -2
- package/src/modules/ZoomPanSelection.js +85 -7
- package/src/modules/accessibility/KeyboardNavigation.js +14 -0
- package/src/modules/axes/YAxis.js +12 -15
- package/src/modules/helpers/UpdateHelpers.js +25 -8
- package/src/modules/settings/Globals.js +5 -10
- package/src/modules/settings/Options.js +15 -0
- package/src/modules/weave/Claims.js +231 -0
- package/src/modules/weave/Drawn.js +169 -0
- package/src/modules/weave/PluginAPI.js +195 -4
- package/src/modules/weave/WeaveHost.js +76 -3
- package/src/ssr/DOMShim.js +50 -0
- package/src/types/internal.d.ts +1 -1
- package/src/utils/Utils.js +6 -9
- 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
|
-
|
|
184
|
-
|
|
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(
|
|
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
|
-
//
|
|
319
|
-
//
|
|
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
|
-
|
|
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
|
-
*
|
|
558
|
-
*
|
|
559
|
-
*
|
|
560
|
-
*
|
|
561
|
-
*
|
|
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
|
+
}
|