inkplot 0.0.1-placeholder.0 → 1.0.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +45 -1
- package/dist/adapt.d.ts +10 -0
- package/dist/adapt.js +34 -0
- package/dist/annotation.d.ts +120 -0
- package/dist/annotation.js +267 -0
- package/dist/aria.d.ts +24 -0
- package/dist/aria.js +31 -0
- package/dist/attrs.d.ts +81 -0
- package/dist/attrs.js +84 -0
- package/dist/axis.d.ts +137 -0
- package/dist/axis.js +185 -0
- package/dist/bars.d.ts +80 -0
- package/dist/bars.js +255 -0
- package/dist/boxplot.d.ts +57 -0
- package/dist/boxplot.js +177 -0
- package/dist/boxzoom.d.ts +39 -0
- package/dist/boxzoom.js +138 -0
- package/dist/brush.d.ts +60 -0
- package/dist/brush.js +185 -0
- package/dist/bucket.d.ts +22 -0
- package/dist/bucket.js +62 -0
- package/dist/callout.d.ts +30 -0
- package/dist/callout.js +98 -0
- package/dist/chart.d.ts +126 -0
- package/dist/chart.js +257 -0
- package/dist/crosshair.d.ts +28 -0
- package/dist/crosshair.js +78 -0
- package/dist/cursor.d.ts +76 -0
- package/dist/cursor.js +344 -0
- package/dist/curve.d.ts +50 -0
- package/dist/curve.js +87 -0
- package/dist/curves.d.ts +104 -0
- package/dist/curves.js +375 -0
- package/dist/data.d.ts +209 -0
- package/dist/data.js +276 -0
- package/dist/decimation.d.ts +86 -0
- package/dist/decimation.js +410 -0
- package/dist/density.d.ts +55 -0
- package/dist/density.js +252 -0
- package/dist/dimension.d.ts +103 -0
- package/dist/dimension.js +205 -0
- package/dist/dom-platform.d.ts +53 -0
- package/dist/dom-platform.js +190 -0
- package/dist/element.d.ts +232 -0
- package/dist/element.js +414 -0
- package/dist/errors.d.ts +85 -0
- package/dist/errors.js +237 -0
- package/dist/events.d.ts +144 -0
- package/dist/events.js +19 -0
- package/dist/focus.d.ts +22 -0
- package/dist/focus.js +51 -0
- package/dist/furniture.d.ts +70 -0
- package/dist/furniture.js +166 -0
- package/dist/geometry.d.ts +40 -0
- package/dist/geometry.js +7 -0
- package/dist/heatmap.d.ts +57 -0
- package/dist/heatmap.js +207 -0
- package/dist/hit.d.ts +103 -0
- package/dist/hit.js +428 -0
- package/dist/index.d.ts +73 -0
- package/dist/index.js +73 -0
- package/dist/input.d.ts +42 -0
- package/dist/input.js +166 -0
- package/dist/jsx-dev-runtime.d.ts +9 -0
- package/dist/jsx-dev-runtime.js +13 -0
- package/dist/jsx-runtime.d.ts +59 -0
- package/dist/jsx-runtime.js +81 -0
- package/dist/key.d.ts +118 -0
- package/dist/key.js +199 -0
- package/dist/legend.d.ts +36 -0
- package/dist/legend.js +220 -0
- package/dist/mapped.d.ts +80 -0
- package/dist/mapped.js +106 -0
- package/dist/marker.d.ts +143 -0
- package/dist/marker.js +213 -0
- package/dist/panzoom.d.ts +119 -0
- package/dist/panzoom.js +467 -0
- package/dist/plane.d.ts +173 -0
- package/dist/plane.js +370 -0
- package/dist/platform.d.ts +113 -0
- package/dist/platform.js +80 -0
- package/dist/png.d.ts +44 -0
- package/dist/png.js +71 -0
- package/dist/pointer.d.ts +54 -0
- package/dist/pointer.js +132 -0
- package/dist/quantile.d.ts +3 -0
- package/dist/quantile.js +11 -0
- package/dist/radial.d.ts +116 -0
- package/dist/radial.js +336 -0
- package/dist/react.d.ts +441 -0
- package/dist/react.js +252 -0
- package/dist/registry.d.ts +2 -0
- package/dist/registry.js +51 -0
- package/dist/renderer.d.ts +191 -0
- package/dist/renderer.js +164 -0
- package/dist/scale.d.ts +37 -0
- package/dist/scale.js +19 -0
- package/dist/scales.d.ts +69 -0
- package/dist/scales.js +134 -0
- package/dist/serialize.d.ts +77 -0
- package/dist/serialize.js +321 -0
- package/dist/series.d.ts +304 -0
- package/dist/series.js +513 -0
- package/dist/simple.d.ts +35 -0
- package/dist/simple.js +20 -0
- package/dist/sources.d.ts +116 -0
- package/dist/sources.js +237 -0
- package/dist/stack.d.ts +61 -0
- package/dist/stack.js +172 -0
- package/dist/stats.d.ts +38 -0
- package/dist/stats.js +159 -0
- package/dist/steps.d.ts +50 -0
- package/dist/steps.js +154 -0
- package/dist/stub.d.ts +3 -0
- package/dist/stub.js +5 -0
- package/dist/style-names.d.ts +1 -0
- package/dist/style-names.js +6 -0
- package/dist/summary.d.ts +44 -0
- package/dist/summary.js +58 -0
- package/dist/surface.d.ts +94 -0
- package/dist/surface.js +145 -0
- package/dist/table.d.ts +39 -0
- package/dist/table.js +37 -0
- package/dist/text.d.ts +23 -0
- package/dist/text.js +44 -0
- package/dist/theme.d.ts +174 -0
- package/dist/theme.js +73 -0
- package/dist/themes.d.ts +9 -0
- package/dist/themes.js +56 -0
- package/dist/time-intl.d.ts +194 -0
- package/dist/time-intl.js +144 -0
- package/dist/time-temporal.d.ts +236 -0
- package/dist/time-temporal.js +58 -0
- package/dist/time.d.ts +216 -0
- package/dist/time.js +154 -0
- package/dist/trend.d.ts +151 -0
- package/dist/trend.js +500 -0
- package/dist/web-components.d.ts +62 -0
- package/dist/web-components.js +264 -0
- package/package.json +48 -3
package/dist/element.js
ADDED
|
@@ -0,0 +1,414 @@
|
|
|
1
|
+
import { EVENT_ARRANGE, EVENT_ATTACH, EVENT_MEASURE, EVENT_PAINT, makeEvent } from './events.js';
|
|
2
|
+
import { resolveStyle } from './theme.js';
|
|
3
|
+
import { Surface } from './surface.js';
|
|
4
|
+
import { STRING } from './attrs.js';
|
|
5
|
+
/** The answer of an element that did not respond to MEASURE: it wants nothing and can take anything. */
|
|
6
|
+
const NO_REQUEST = { min: { width: 0, height: 0 }, max: { width: Infinity, height: Infinity } };
|
|
7
|
+
/**
|
|
8
|
+
* A node with three links: its parent, its first child, and its next sibling. Nothing else is stored, so
|
|
9
|
+
* finding a predecessor or the tail walks the siblings, which is cheap at the sizes charts have.
|
|
10
|
+
*/
|
|
11
|
+
export class ChartElement {
|
|
12
|
+
/** How HTML attributes of this element become values; see attrs.ts. Subclasses repeat these by spreading and add their own. */
|
|
13
|
+
/**
|
|
14
|
+
* For an element that takes pointer, key or focus input: set this to `inputOf` (from input.ts), and the chart is made to listen for
|
|
15
|
+
* input as the element joins it, before the element's own ATTACH handler runs. It is the function and not a flag so that
|
|
16
|
+
* declaring it brings the input code into a bundle, which a chart with no such element never carries.
|
|
17
|
+
*/
|
|
18
|
+
static takesInput = null;
|
|
19
|
+
static attrTypes = { id: STRING, meta: { title: STRING, description: STRING, role: STRING } };
|
|
20
|
+
#attrs;
|
|
21
|
+
#parent = null;
|
|
22
|
+
#first = null;
|
|
23
|
+
#next = null;
|
|
24
|
+
#surface = null;
|
|
25
|
+
#ctx = null;
|
|
26
|
+
#hidden = false;
|
|
27
|
+
/**
|
|
28
|
+
* `new X(attrs?, ...children)`. The attrs object is recognised by not being an element, and is copied so that
|
|
29
|
+
* the caller's object is never mutated by later `setAttribute` calls.
|
|
30
|
+
*/
|
|
31
|
+
constructor(...args) {
|
|
32
|
+
const list = args;
|
|
33
|
+
let i = 0;
|
|
34
|
+
const first = list[0];
|
|
35
|
+
if (first === undefined || (typeof first === 'object' && first !== null && !(first instanceof ChartElement))) {
|
|
36
|
+
this.#attrs = { ...first };
|
|
37
|
+
i = 1;
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.#attrs = {};
|
|
41
|
+
}
|
|
42
|
+
for (; i < list.length; i++)
|
|
43
|
+
this.link(list[i], null);
|
|
44
|
+
}
|
|
45
|
+
/** Read-only view; change attributes through `setAttribute(s)` so the chart learns it is stale. */
|
|
46
|
+
get attrs() { return this.#attrs; }
|
|
47
|
+
get parent() { return this.#parent; }
|
|
48
|
+
get firstChild() { return this.#first; }
|
|
49
|
+
get nextSibling() { return this.#next; }
|
|
50
|
+
/** For elements that only make sense inside a parent (an axis in a plane): fails loudly rather than returning null. */
|
|
51
|
+
requireParent() {
|
|
52
|
+
if (!this.#parent)
|
|
53
|
+
fail(`${this.constructor.name} has no parent`);
|
|
54
|
+
return this.#parent;
|
|
55
|
+
}
|
|
56
|
+
/** The root chart, if this element is in a tree that has one. */
|
|
57
|
+
get chart() { return this.#parent ? this.#parent.chart : null; }
|
|
58
|
+
/** Like `chart`, for code that only runs inside a chart (painting, input): fails loudly rather than returning null. */
|
|
59
|
+
requireChart() { return this.chart ?? fail(`${this.constructor.name} is not in a chart`); }
|
|
60
|
+
get isAttached() { return this.#ctx !== null; }
|
|
61
|
+
/** The children in order. Do not remove the element you are on and the one after it while iterating. */
|
|
62
|
+
*[Symbol.iterator]() {
|
|
63
|
+
for (let c = this.#first; c;) {
|
|
64
|
+
const next = c.#next;
|
|
65
|
+
yield c;
|
|
66
|
+
c = next;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* How `console.log`, `util.inspect` and a failing `assert` print an element: its class, id and child count, not the tree, the
|
|
71
|
+
* platform and everything else it can reach. Without this, one failed `assert.equal(thing, null)` on an object that holds an
|
|
72
|
+
* element (a plane in a hit, a cursor state) prints the whole chart and can run a test out of memory.
|
|
73
|
+
*/
|
|
74
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
75
|
+
let n = 0;
|
|
76
|
+
for (let c = this.#first; c; c = c.#next)
|
|
77
|
+
n++;
|
|
78
|
+
const id = this.#attrs.id;
|
|
79
|
+
return `${this.constructor.name}${id ? `#${id}` : ''}${n ? `(${n})` : ''}`;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* A fixed child, by position, checked where it is used: it has to be one of `classes` (an instance of any, subclasses
|
|
83
|
+
* included). The classes must be ones the declared child type allows, so a wrong one is a compile error; a wrong or missing child
|
|
84
|
+
* is a run-time error that says what was expected and what was found, wherever the tree was built (`new`, JSX, React, markup).
|
|
85
|
+
*/
|
|
86
|
+
child(index, ...classes) {
|
|
87
|
+
const found = this.nth(index);
|
|
88
|
+
const names = classes.map((k) => k.name || 'a different class').join(' or ');
|
|
89
|
+
if (!found)
|
|
90
|
+
fail(`${this.constructor.name}: has no child ${index} (it needs ${names})`);
|
|
91
|
+
if (!classes.some((k) => found instanceof k))
|
|
92
|
+
fail(`${this.constructor.name}: child ${index} should be ${names}, but is ${found.constructor.name}`);
|
|
93
|
+
return found;
|
|
94
|
+
}
|
|
95
|
+
nth(index) {
|
|
96
|
+
let c = this.#first;
|
|
97
|
+
for (let i = 0; c && i < index; i++)
|
|
98
|
+
c = c.#next;
|
|
99
|
+
return c;
|
|
100
|
+
}
|
|
101
|
+
/** What this element draws with. Created on first use, so an element that draws nothing costs nothing. */
|
|
102
|
+
get surface() {
|
|
103
|
+
const ctx = this.#ctx ?? fail(`${this.constructor.name} is not attached`);
|
|
104
|
+
if (!this.#surface) {
|
|
105
|
+
this.#surface = new Surface(ctx.platform, ctx.counters, {
|
|
106
|
+
container: () => this.container(),
|
|
107
|
+
end: () => this.end(),
|
|
108
|
+
childrenFirst: () => this.firstOfChildren(),
|
|
109
|
+
});
|
|
110
|
+
if (this.#hidden)
|
|
111
|
+
this.#surface.setHidden(true);
|
|
112
|
+
}
|
|
113
|
+
return this.#surface;
|
|
114
|
+
}
|
|
115
|
+
/** The flush counters if attached, else null: for code that also runs when a node is read outside a chart. */
|
|
116
|
+
get countersOrNull() { return this.#ctx?.counters ?? null; }
|
|
117
|
+
/** The chart's flush counters. Only valid between attach and detach. */
|
|
118
|
+
get counters() {
|
|
119
|
+
return (this.#ctx ?? fail(`${this.constructor.name} is not attached`)).counters;
|
|
120
|
+
}
|
|
121
|
+
/** Setting `undefined` removes the attribute, so defaults apply again. Unchanged values do not invalidate. */
|
|
122
|
+
/**
|
|
123
|
+
* This element's look for a chain of selectors, most specific first, ending in a root: all of that root's properties,
|
|
124
|
+
* settled (see theme.ts for the order). `local` is the element's own attributes that are style properties; `accent` is
|
|
125
|
+
* a default colour from the palette that anything named in a theme beats.
|
|
126
|
+
*/
|
|
127
|
+
styleFor(chain, local, accent) {
|
|
128
|
+
const chart = this.chart ?? fail(`${this.constructor.name} is not in a chart`);
|
|
129
|
+
const scopes = [];
|
|
130
|
+
// oxlint-disable-next-line typescript/no-this-alias -- a walk up the parent chain that starts at this element
|
|
131
|
+
for (let e = this; e; e = e.#parent)
|
|
132
|
+
scopes.push(e.#attrs.style);
|
|
133
|
+
return resolveStyle(chain, scopes, chart.attrs.theme, local, accent);
|
|
134
|
+
}
|
|
135
|
+
getAttribute(name) { return this.#attrs[name]; }
|
|
136
|
+
setAttribute(name, value) {
|
|
137
|
+
if (this.put(name, value))
|
|
138
|
+
this.invalidate();
|
|
139
|
+
return this;
|
|
140
|
+
}
|
|
141
|
+
setAttributes(patch) {
|
|
142
|
+
let changed = false;
|
|
143
|
+
for (const name of Object.keys(patch))
|
|
144
|
+
changed = this.put(name, patch[name]) || changed;
|
|
145
|
+
if (changed)
|
|
146
|
+
this.invalidate();
|
|
147
|
+
return this;
|
|
148
|
+
}
|
|
149
|
+
put(name, value) {
|
|
150
|
+
if (this.#attrs[name] === value)
|
|
151
|
+
return false;
|
|
152
|
+
if (value === undefined)
|
|
153
|
+
delete this.#attrs[name];
|
|
154
|
+
else
|
|
155
|
+
this.#attrs[name] = value;
|
|
156
|
+
return true;
|
|
157
|
+
}
|
|
158
|
+
/** Put `child` in front of `before` (null: at the end). Finding the place walks the siblings. */
|
|
159
|
+
link(child, before) {
|
|
160
|
+
if (child.#parent)
|
|
161
|
+
fail('element already has a parent');
|
|
162
|
+
let prev = null;
|
|
163
|
+
for (let c = this.#first; c && c !== before; c = c.#next)
|
|
164
|
+
prev = c;
|
|
165
|
+
child.#parent = this;
|
|
166
|
+
child.#next = before;
|
|
167
|
+
if (prev)
|
|
168
|
+
prev.#next = child;
|
|
169
|
+
else
|
|
170
|
+
this.#first = child;
|
|
171
|
+
}
|
|
172
|
+
unlink(child) {
|
|
173
|
+
let prev = null;
|
|
174
|
+
let c = this.#first;
|
|
175
|
+
for (; c && c !== child; c = c.#next)
|
|
176
|
+
prev = c;
|
|
177
|
+
if (!c)
|
|
178
|
+
fail('element is not a child of this element');
|
|
179
|
+
if (prev)
|
|
180
|
+
prev.#next = child.#next;
|
|
181
|
+
else
|
|
182
|
+
this.#first = child.#next;
|
|
183
|
+
child.#next = child.#parent = null;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Add children that can come and go. Fixed children (an axis' scale, a series' data) are given to the
|
|
187
|
+
* constructor and replaced with `setChild`; the types do not allow adding or removing them.
|
|
188
|
+
*/
|
|
189
|
+
append(...children) {
|
|
190
|
+
for (const c of children)
|
|
191
|
+
this.insertBefore(c, null);
|
|
192
|
+
return this;
|
|
193
|
+
}
|
|
194
|
+
/** Exactly `Node.insertBefore`: `before` is one of this element's children, or null to add at the end. */
|
|
195
|
+
insertBefore(child, before) {
|
|
196
|
+
this.link(child, before);
|
|
197
|
+
if (this.#ctx)
|
|
198
|
+
child.mount(this.#ctx);
|
|
199
|
+
this.invalidate();
|
|
200
|
+
return this;
|
|
201
|
+
}
|
|
202
|
+
remove(child) {
|
|
203
|
+
child.unmount(true);
|
|
204
|
+
this.unlink(child);
|
|
205
|
+
this.invalidate();
|
|
206
|
+
return this;
|
|
207
|
+
}
|
|
208
|
+
/** Swap one of the fixed children for another of the same kind. */
|
|
209
|
+
setChild(index, child) {
|
|
210
|
+
const old = this.nth(index) ?? fail(`${this.constructor.name}: has no child ${index} to replace`);
|
|
211
|
+
this.link(child, old);
|
|
212
|
+
old.unmount(true);
|
|
213
|
+
this.unlink(old);
|
|
214
|
+
if (this.#ctx)
|
|
215
|
+
child.mount(this.#ctx);
|
|
216
|
+
this.invalidate();
|
|
217
|
+
return this;
|
|
218
|
+
}
|
|
219
|
+
/** Convenience for `parent.remove(this)` when the caller holds only the child. */
|
|
220
|
+
detachFromParent() {
|
|
221
|
+
this.#parent?.remove(this);
|
|
222
|
+
return this;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Something about this element is stale. The request travels to the root, where a chart on a
|
|
226
|
+
* scheduling platform arranges a flush. Each flush re-runs layout; fills are cached and unchanged
|
|
227
|
+
* attribute writes skipped, which is where the saving is.
|
|
228
|
+
*/
|
|
229
|
+
invalidate() {
|
|
230
|
+
this.#parent?.invalidate();
|
|
231
|
+
}
|
|
232
|
+
/** The single extension point. Switch on `event.type`; ignore what you don't handle. */
|
|
233
|
+
onEvent(_event) { }
|
|
234
|
+
/** Deliver an event to `root` (default: this element) and everything beneath it, parents first. */
|
|
235
|
+
broadcast(init, root = this) {
|
|
236
|
+
const ev = makeEvent(init, root);
|
|
237
|
+
root.cascade(ev);
|
|
238
|
+
return ev;
|
|
239
|
+
}
|
|
240
|
+
/** Parents are visited before their children, so a parent can set up state its children read in the same event. */
|
|
241
|
+
cascade(ev) {
|
|
242
|
+
this.receive(ev);
|
|
243
|
+
for (let c = this.#first; c;) {
|
|
244
|
+
const next = c.#next;
|
|
245
|
+
c.cascade(ev);
|
|
246
|
+
c = next;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
/** Ask a child how much space it needs. */
|
|
250
|
+
measureChild(child, available) {
|
|
251
|
+
const ev = makeEvent({ type: EVENT_MEASURE, available, request: NO_REQUEST }, child);
|
|
252
|
+
child.receive(ev);
|
|
253
|
+
return ev.request;
|
|
254
|
+
}
|
|
255
|
+
/** Tell a child where it is. */
|
|
256
|
+
arrangeChild(child, rect) {
|
|
257
|
+
child.receive(makeEvent({ type: EVENT_ARRANGE, rect }, child));
|
|
258
|
+
}
|
|
259
|
+
/** The one entry point for events: the base class handles what every element shares, then the subclass sees it. */
|
|
260
|
+
receive(ev) {
|
|
261
|
+
switch (ev.type) {
|
|
262
|
+
case EVENT_PAINT: {
|
|
263
|
+
// Visibility is the one thing every element draws, so the base class owns it.
|
|
264
|
+
const hidden = this.isHidden();
|
|
265
|
+
if (hidden !== this.#hidden) {
|
|
266
|
+
this.#hidden = hidden;
|
|
267
|
+
this.#surface?.setHidden(hidden);
|
|
268
|
+
}
|
|
269
|
+
break;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
this.onEvent(ev);
|
|
273
|
+
}
|
|
274
|
+
/** Put this tree on the platform. The root calls it; elements added later are mounted by their parent. */
|
|
275
|
+
mount(ctx) {
|
|
276
|
+
this.#ctx = ctx;
|
|
277
|
+
const takes = this.constructor.takesInput, chart = this.chart;
|
|
278
|
+
if (takes && chart)
|
|
279
|
+
takes(chart);
|
|
280
|
+
this.receive(makeEvent({ type: EVENT_ATTACH, platform: ctx.platform }, this));
|
|
281
|
+
for (let c = this.#first; c; c = c.#next)
|
|
282
|
+
c.mount(ctx);
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* `removeNodes` is false once an ancestor's wrapper is being removed: everything inside goes with it.
|
|
286
|
+
* Without a wrapper, an element's nodes sit in a shared container and have to be taken out one by one.
|
|
287
|
+
*/
|
|
288
|
+
unmount(removeNodes) {
|
|
289
|
+
const ownsWrapper = !!this.#surface?.wrapperRoot;
|
|
290
|
+
for (let c = this.#first; c; c = c.#next)
|
|
291
|
+
c.unmount(removeNodes && !ownsWrapper);
|
|
292
|
+
if (!this.#ctx)
|
|
293
|
+
return;
|
|
294
|
+
if (removeNodes)
|
|
295
|
+
this.#surface?.dispose();
|
|
296
|
+
this.#surface = null;
|
|
297
|
+
this.#ctx = null;
|
|
298
|
+
}
|
|
299
|
+
// ─── Placement: where nodes go, worked out from the live tree (see the class comment) ───
|
|
300
|
+
/** The first node of this element's content, or null if it and everything below it has drawn nothing. */
|
|
301
|
+
first() {
|
|
302
|
+
const s = this.#surface;
|
|
303
|
+
return s?.wrapperRoot ?? s?.firstBelow ?? this.firstOfChildren() ?? s?.firstAbove ?? null;
|
|
304
|
+
}
|
|
305
|
+
firstOfChildren() {
|
|
306
|
+
for (let c = this.#first; c; c = c.#next) {
|
|
307
|
+
const node = c.first();
|
|
308
|
+
if (node)
|
|
309
|
+
return node;
|
|
310
|
+
}
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
/** The node this element's content ends before: whatever the next element that has drawn starts with. */
|
|
314
|
+
end() {
|
|
315
|
+
for (let s = this.#next; s; s = s.#next) {
|
|
316
|
+
const node = s.first();
|
|
317
|
+
if (node)
|
|
318
|
+
return node;
|
|
319
|
+
}
|
|
320
|
+
const p = this.#parent;
|
|
321
|
+
if (!p)
|
|
322
|
+
return null;
|
|
323
|
+
return p.#surface?.wrapperRoot ? null : p.end();
|
|
324
|
+
}
|
|
325
|
+
/** The node this element's own nodes go into when it has no wrapper. */
|
|
326
|
+
container() {
|
|
327
|
+
for (let p = this.#parent; p; p = p.#parent) {
|
|
328
|
+
const wrapper = p.#surface?.wrapperRoot;
|
|
329
|
+
if (wrapper)
|
|
330
|
+
return wrapper;
|
|
331
|
+
}
|
|
332
|
+
return (this.#ctx ?? fail('element is not attached')).platform.root;
|
|
333
|
+
}
|
|
334
|
+
/** Hidden if it, or an ancestor without a wrapper to hide for it, says `visible: false`. */
|
|
335
|
+
isHidden() {
|
|
336
|
+
// oxlint-disable-next-line typescript/no-this-alias -- a walk up the parent chain that starts at this element
|
|
337
|
+
for (let e = this; e; e = e.#parent) {
|
|
338
|
+
if (e.#attrs.visible === false)
|
|
339
|
+
return e === this || !e.#surface?.wrapperRoot;
|
|
340
|
+
if (e !== this && e.#surface?.wrapperRoot)
|
|
341
|
+
return false;
|
|
342
|
+
}
|
|
343
|
+
return false;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
// ─── Tree utilities ───
|
|
347
|
+
// Plain functions rather than methods so unused ones tree-shake away.
|
|
348
|
+
/** Depth-first, parents before children. Returning `false` from `visit` skips that element's subtree. */
|
|
349
|
+
export function walk(root, visit, depth = 0) {
|
|
350
|
+
if (visit(root, depth) === false)
|
|
351
|
+
return;
|
|
352
|
+
for (const c of root)
|
|
353
|
+
walk(c, visit, depth + 1);
|
|
354
|
+
}
|
|
355
|
+
/** The first element under `root` (or `root` itself) with this id. */
|
|
356
|
+
export function findById(root, id) {
|
|
357
|
+
let found;
|
|
358
|
+
walk(root, (e) => { if (found)
|
|
359
|
+
return false; if (e.attrs.id === id) {
|
|
360
|
+
found = e;
|
|
361
|
+
return false;
|
|
362
|
+
} });
|
|
363
|
+
return found;
|
|
364
|
+
}
|
|
365
|
+
/** Every element below `root` of a class, in tree order. */
|
|
366
|
+
export function descendantsOfType(root, type) {
|
|
367
|
+
const out = [];
|
|
368
|
+
walk(root, (e) => { if (e !== root && e instanceof type)
|
|
369
|
+
out.push(e); });
|
|
370
|
+
return out;
|
|
371
|
+
}
|
|
372
|
+
/** The first element below `root` of a class, stopping the walk as soon as it is found. */
|
|
373
|
+
export function firstDescendantOfType(root, type) {
|
|
374
|
+
let found;
|
|
375
|
+
walk(root, (e) => { if (found)
|
|
376
|
+
return false; if (e !== root && e instanceof type) {
|
|
377
|
+
found = e;
|
|
378
|
+
return false;
|
|
379
|
+
} });
|
|
380
|
+
return found;
|
|
381
|
+
}
|
|
382
|
+
/** The direct children of a class; use it when the parent's children are mixed (an axis next to series). */
|
|
383
|
+
export function childrenOfType(parent, type) {
|
|
384
|
+
return [...parent].filter((c) => c instanceof type);
|
|
385
|
+
}
|
|
386
|
+
/** The nearest enclosing element of a class: how a child finds "its" plane or chart without being handed one. */
|
|
387
|
+
export function closestAncestorOfType(from, type) {
|
|
388
|
+
for (let e = from.parent; e; e = e.parent)
|
|
389
|
+
if (e instanceof type)
|
|
390
|
+
return e;
|
|
391
|
+
return undefined;
|
|
392
|
+
}
|
|
393
|
+
export function resolveRef(ref, from, type) {
|
|
394
|
+
if (ref === undefined)
|
|
395
|
+
return undefined;
|
|
396
|
+
let el;
|
|
397
|
+
if (typeof ref === 'string') {
|
|
398
|
+
let root = from;
|
|
399
|
+
while (root.parent)
|
|
400
|
+
root = root.parent;
|
|
401
|
+
el = findById(root, ref);
|
|
402
|
+
if (!el)
|
|
403
|
+
fail(`no element with id "${ref}"`);
|
|
404
|
+
}
|
|
405
|
+
else {
|
|
406
|
+
el = ref;
|
|
407
|
+
}
|
|
408
|
+
if (type && !(el instanceof type)) {
|
|
409
|
+
fail(`element "${typeof ref === 'string' ? ref : ref.attrs.id ?? '?'}" is not a ${type.name}`);
|
|
410
|
+
}
|
|
411
|
+
return el;
|
|
412
|
+
}
|
|
413
|
+
/** `throw` as an expression, so a missing value can be reported inline: `x ?? fail('no x')`. */
|
|
414
|
+
export function fail(message) { throw new Error(message); }
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { SeriesRenderer, type SeriesRendererAttributes, type Spread, type SpreadView } from './renderer.ts';
|
|
2
|
+
import type { Series } from './series.ts';
|
|
3
|
+
import type { Surface } from './surface.ts';
|
|
4
|
+
import type { PlatformNode } from './platform.ts';
|
|
5
|
+
export interface ErrorAttributes extends Omit<SeriesRendererAttributes, 'bind'> {
|
|
6
|
+
/** The channel with how far above the value each point may be. Required. */
|
|
7
|
+
error: string;
|
|
8
|
+
/** The channel with how far below. Default: the same as `error`. */
|
|
9
|
+
negativeError?: string;
|
|
10
|
+
/** What the numbers mean: units of the value (default), or a fraction of it. */
|
|
11
|
+
mode?: 'absolute' | 'relative';
|
|
12
|
+
/** Which way the error runs. Default 'value'. */
|
|
13
|
+
along?: 'value' | 'domain';
|
|
14
|
+
}
|
|
15
|
+
export interface ErrorBarsAttributes extends ErrorAttributes {
|
|
16
|
+
/** Width in pixels of the cap at each end of a whisker; 0 for none. Default 8. */
|
|
17
|
+
capWidth?: number;
|
|
18
|
+
}
|
|
19
|
+
export interface ErrorBandAttributes extends ErrorAttributes {
|
|
20
|
+
}
|
|
21
|
+
/** What error renderers share: the channels, the rows' spread, and the placed ends. */
|
|
22
|
+
declare abstract class ErrorRenderer<A extends ErrorAttributes> extends SeriesRenderer<A> {
|
|
23
|
+
get channelNames(): readonly string[];
|
|
24
|
+
private get negative();
|
|
25
|
+
spread(view: SpreadView): Spread | null;
|
|
26
|
+
limit(series: Series): Spread | null;
|
|
27
|
+
/**
|
|
28
|
+
* The places in plot pixels of the lower end, the upper end, and a point a little further along the cap's direction from
|
|
29
|
+
* each (which says which way the cap runs on this plane), for every row: `[lo, hi, loAlong, hiAlong]` of one row after another,
|
|
30
|
+
* NaN for a row with no bar.
|
|
31
|
+
*/
|
|
32
|
+
protected ends(series: Series): {
|
|
33
|
+
n: number;
|
|
34
|
+
pts: Float64Array;
|
|
35
|
+
} | null;
|
|
36
|
+
}
|
|
37
|
+
/** A whisker from the lower end to the upper, with a cap across each, as one path. */
|
|
38
|
+
export declare class ErrorBars extends ErrorRenderer<ErrorBarsAttributes> {
|
|
39
|
+
static attrTypes: {
|
|
40
|
+
fill: 1;
|
|
41
|
+
maxNodes: 2;
|
|
42
|
+
stroke: 1;
|
|
43
|
+
strokeWidth: 2;
|
|
44
|
+
id: 1;
|
|
45
|
+
meta: {
|
|
46
|
+
title: 1;
|
|
47
|
+
description: 1;
|
|
48
|
+
role: 1;
|
|
49
|
+
};
|
|
50
|
+
error: 1;
|
|
51
|
+
negativeError: 1;
|
|
52
|
+
capWidth: 2;
|
|
53
|
+
mode: 1;
|
|
54
|
+
along: 1;
|
|
55
|
+
};
|
|
56
|
+
/** Neutral by default (the theme's line colour), so that it can be seen on the bars it goes with. */
|
|
57
|
+
private look;
|
|
58
|
+
get reach(): number;
|
|
59
|
+
sample(s: Surface, parent: PlatformNode, key: string, width: number, height: number): void;
|
|
60
|
+
protected draw(): void;
|
|
61
|
+
}
|
|
62
|
+
/** The region between the lower and the upper ends, as one path; a gap splits it. */
|
|
63
|
+
export declare class ErrorBand extends ErrorRenderer<ErrorBandAttributes> {
|
|
64
|
+
static attrTypes: {
|
|
65
|
+
fill: 1;
|
|
66
|
+
maxNodes: 2;
|
|
67
|
+
stroke: 1;
|
|
68
|
+
strokeWidth: 2;
|
|
69
|
+
id: 1;
|
|
70
|
+
meta: {
|
|
71
|
+
title: 1;
|
|
72
|
+
description: 1;
|
|
73
|
+
role: 1;
|
|
74
|
+
};
|
|
75
|
+
error: 1;
|
|
76
|
+
negativeError: 1;
|
|
77
|
+
mode: 1;
|
|
78
|
+
along: 1;
|
|
79
|
+
};
|
|
80
|
+
private look;
|
|
81
|
+
get swatch(): string;
|
|
82
|
+
sample(s: Surface, parent: PlatformNode, key: string, width: number, height: number): void;
|
|
83
|
+
protected draw(): void;
|
|
84
|
+
}
|
|
85
|
+
export {};
|