inkplot 0.0.0-stage → 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.
Files changed (141) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +46 -2
  3. package/dist/adapt.d.ts +10 -0
  4. package/dist/adapt.js +34 -0
  5. package/dist/annotation.d.ts +120 -0
  6. package/dist/annotation.js +267 -0
  7. package/dist/aria.d.ts +24 -0
  8. package/dist/aria.js +31 -0
  9. package/dist/attrs.d.ts +81 -0
  10. package/dist/attrs.js +84 -0
  11. package/dist/axis.d.ts +137 -0
  12. package/dist/axis.js +185 -0
  13. package/dist/bars.d.ts +80 -0
  14. package/dist/bars.js +255 -0
  15. package/dist/boxplot.d.ts +57 -0
  16. package/dist/boxplot.js +177 -0
  17. package/dist/boxzoom.d.ts +39 -0
  18. package/dist/boxzoom.js +138 -0
  19. package/dist/brush.d.ts +60 -0
  20. package/dist/brush.js +185 -0
  21. package/dist/bucket.d.ts +22 -0
  22. package/dist/bucket.js +62 -0
  23. package/dist/callout.d.ts +30 -0
  24. package/dist/callout.js +98 -0
  25. package/dist/chart.d.ts +126 -0
  26. package/dist/chart.js +257 -0
  27. package/dist/crosshair.d.ts +28 -0
  28. package/dist/crosshair.js +78 -0
  29. package/dist/cursor.d.ts +76 -0
  30. package/dist/cursor.js +344 -0
  31. package/dist/curve.d.ts +50 -0
  32. package/dist/curve.js +87 -0
  33. package/dist/curves.d.ts +104 -0
  34. package/dist/curves.js +375 -0
  35. package/dist/data.d.ts +209 -0
  36. package/dist/data.js +276 -0
  37. package/dist/decimation.d.ts +86 -0
  38. package/dist/decimation.js +410 -0
  39. package/dist/density.d.ts +55 -0
  40. package/dist/density.js +252 -0
  41. package/dist/dimension.d.ts +103 -0
  42. package/dist/dimension.js +205 -0
  43. package/dist/dom-platform.d.ts +53 -0
  44. package/dist/dom-platform.js +190 -0
  45. package/dist/element.d.ts +232 -0
  46. package/dist/element.js +414 -0
  47. package/dist/errors.d.ts +85 -0
  48. package/dist/errors.js +237 -0
  49. package/dist/events.d.ts +144 -0
  50. package/dist/events.js +19 -0
  51. package/dist/focus.d.ts +22 -0
  52. package/dist/focus.js +51 -0
  53. package/dist/furniture.d.ts +70 -0
  54. package/dist/furniture.js +166 -0
  55. package/dist/geometry.d.ts +40 -0
  56. package/dist/geometry.js +7 -0
  57. package/dist/heatmap.d.ts +57 -0
  58. package/dist/heatmap.js +207 -0
  59. package/dist/hit.d.ts +103 -0
  60. package/dist/hit.js +428 -0
  61. package/dist/index.d.ts +73 -0
  62. package/dist/index.js +73 -0
  63. package/dist/input.d.ts +42 -0
  64. package/dist/input.js +166 -0
  65. package/dist/jsx-dev-runtime.d.ts +9 -0
  66. package/dist/jsx-dev-runtime.js +13 -0
  67. package/dist/jsx-runtime.d.ts +59 -0
  68. package/dist/jsx-runtime.js +81 -0
  69. package/dist/key.d.ts +118 -0
  70. package/dist/key.js +199 -0
  71. package/dist/legend.d.ts +36 -0
  72. package/dist/legend.js +220 -0
  73. package/dist/mapped.d.ts +80 -0
  74. package/dist/mapped.js +106 -0
  75. package/dist/marker.d.ts +143 -0
  76. package/dist/marker.js +213 -0
  77. package/dist/panzoom.d.ts +119 -0
  78. package/dist/panzoom.js +467 -0
  79. package/dist/plane.d.ts +173 -0
  80. package/dist/plane.js +370 -0
  81. package/dist/platform.d.ts +113 -0
  82. package/dist/platform.js +80 -0
  83. package/dist/png.d.ts +44 -0
  84. package/dist/png.js +71 -0
  85. package/dist/pointer.d.ts +54 -0
  86. package/dist/pointer.js +132 -0
  87. package/dist/quantile.d.ts +3 -0
  88. package/dist/quantile.js +11 -0
  89. package/dist/radial.d.ts +116 -0
  90. package/dist/radial.js +336 -0
  91. package/dist/react.d.ts +441 -0
  92. package/dist/react.js +252 -0
  93. package/dist/registry.d.ts +2 -0
  94. package/dist/registry.js +51 -0
  95. package/dist/renderer.d.ts +191 -0
  96. package/dist/renderer.js +164 -0
  97. package/dist/scale.d.ts +37 -0
  98. package/dist/scale.js +19 -0
  99. package/dist/scales.d.ts +69 -0
  100. package/dist/scales.js +134 -0
  101. package/dist/serialize.d.ts +77 -0
  102. package/dist/serialize.js +321 -0
  103. package/dist/series.d.ts +304 -0
  104. package/dist/series.js +513 -0
  105. package/dist/simple.d.ts +35 -0
  106. package/dist/simple.js +20 -0
  107. package/dist/sources.d.ts +116 -0
  108. package/dist/sources.js +237 -0
  109. package/dist/stack.d.ts +61 -0
  110. package/dist/stack.js +172 -0
  111. package/dist/stats.d.ts +38 -0
  112. package/dist/stats.js +159 -0
  113. package/dist/steps.d.ts +50 -0
  114. package/dist/steps.js +154 -0
  115. package/dist/stub.d.ts +3 -0
  116. package/dist/stub.js +5 -0
  117. package/dist/style-names.d.ts +1 -0
  118. package/dist/style-names.js +6 -0
  119. package/dist/summary.d.ts +44 -0
  120. package/dist/summary.js +58 -0
  121. package/dist/surface.d.ts +94 -0
  122. package/dist/surface.js +145 -0
  123. package/dist/table.d.ts +39 -0
  124. package/dist/table.js +37 -0
  125. package/dist/text.d.ts +23 -0
  126. package/dist/text.js +44 -0
  127. package/dist/theme.d.ts +174 -0
  128. package/dist/theme.js +73 -0
  129. package/dist/themes.d.ts +9 -0
  130. package/dist/themes.js +56 -0
  131. package/dist/time-intl.d.ts +194 -0
  132. package/dist/time-intl.js +144 -0
  133. package/dist/time-temporal.d.ts +236 -0
  134. package/dist/time-temporal.js +58 -0
  135. package/dist/time.d.ts +216 -0
  136. package/dist/time.js +154 -0
  137. package/dist/trend.d.ts +151 -0
  138. package/dist/trend.js +500 -0
  139. package/dist/web-components.d.ts +62 -0
  140. package/dist/web-components.js +264 -0
  141. package/package.json +53 -3
@@ -0,0 +1,264 @@
1
+ /**
2
+ * The Web Components adapter: chart elements as custom HTML elements, so that a chart can be written, or pre-rendered, as markup.
3
+ *
4
+ * import * as inkplot from 'inkplot';
5
+ * import { defineElements } from 'inkplot/web-components';
6
+ * defineElements({ Chart: inkplot.Chart, CartesianPlane: inkplot.CartesianPlane, Axis: inkplot.Axis, … }, { prefix: 'ch' });
7
+ *
8
+ * <ch-chart style="display:block; height:300px" meta-title="Sales">
9
+ * <ch-cartesian-plane>
10
+ * <ch-axis orientation="bottom" meta-title="Month"><ch-linear-scale></ch-linear-scale></ch-axis>
11
+ * <ch-domain-series name="2024"><ch-flat-data-source data="[1,3,2]" regulardomain-step="1"></ch-flat-data-source> …
12
+ *
13
+ * Only the classes you give are defined, so nothing else is bundled. The tag is the prefix and the key, kebab-cased
14
+ * (`ch-cartesian-plane`).
15
+ *
16
+ * How it follows the DOM:
17
+ * - The element tree is the DOM tree: a wrapper finds its parent wrapper among its ancestors and joins it in document order
18
+ * when connected, and leaves when disconnected. Moving a node moves the chart element. The outermost wrapper (one with
19
+ * `attach`, a Chart) draws into a shadow root, so the wrapper elements inside it are never rendered, and the page's CSS does not reach the chart.
20
+ * Give that element a size; it is `display: block`.
21
+ * - Attributes are the element's attributes. HTML lowercases names, so `regulardomain-step` finds `regularDomain.step` (the
22
+ * class' `attrTypes` and `STYLE_NAMES` say how the names are spelled). Hyphens nest, as in attrs.ts: `meta-title`, `style-ticklabel-size`.
23
+ * Text becomes a value by the class' `attrTypes`: `STRING` stays text; otherwise `true`/`false`, numbers, and `[…]`/`{…}` (JSON)
24
+ * convert, and a present attribute with no value is true. Changing an attribute later updates the element (on the next microtask).
25
+ * - Values HTML cannot carry are set as JavaScript: `element.props = { format: fn, data: new Float64Array(…) }` on the wrapper,
26
+ * and they win over attributes. `wrapper.element` is the chart element itself, for calling its methods.
27
+ * - Callbacks (`onClick`, `onHover`, `onDrag`, `onFlush`: attributes that are functions) are events: once the page listens, with
28
+ * `addEventListener('click', …)` or `onclick=""`, the wrapper dispatches a bubbling `CustomEvent` named like the DOM's (`click`,
29
+ * `hover`, `drag`, `flush`) with the callback's argument as `detail`. (Until then no callback is set, so the chart stays serializable.)
30
+ * `on…` attributes are the DOM's own and are not read as chart attributes.
31
+ * - `style`, `class` and the other attributes every HTML element has are the page's, not the chart's: set the chart's `style` blocks with `style-…` names.
32
+ */
33
+ import { AUTO, BOOLEAN, NUMBER, STRING } from './attrs.js';
34
+ import { attributesOf, difference } from './adapt.js';
35
+ import { DOMPlatform } from './dom-platform.js';
36
+ import { STYLE_NAMES } from './style-names.js';
37
+ const lowerCache = new WeakMap();
38
+ /** The names a spec lists, by lowercase (HTML's spelling). Two names that differ only in case are an error. */
39
+ function lowerNames(spec, where = 'attribute types') {
40
+ let map = lowerCache.get(spec);
41
+ if (!map) {
42
+ map = new Map();
43
+ for (const n of Object.keys(spec)) {
44
+ const lower = n.toLowerCase();
45
+ if (map.has(lower) && map.get(lower) !== n)
46
+ throw new Error(`${where}: '${n}' and '${map.get(lower)}' differ only in case`);
47
+ map.set(lower, n);
48
+ }
49
+ lowerCache.set(spec, map);
50
+ }
51
+ return map;
52
+ }
53
+ /** The nested spec for an attribute inside the objects named by `path` (real names), or undefined where none is listed. */
54
+ function specAt(types, path) {
55
+ let spec = types;
56
+ for (const p of path) {
57
+ const next = spec?.[p];
58
+ spec = typeof next === 'object' ? next : undefined;
59
+ }
60
+ return spec;
61
+ }
62
+ /** The attributes every HTML element has, or has for the page; none is a chart attribute. */
63
+ const PAGE_ATTRIBUTES = new Set(['class', 'style', 'slot', 'part', 'is', 'hidden', 'lang', 'dir', 'tabindex', 'contenteditable', 'draggable', 'translate', 'inert', 'nonce']);
64
+ const NUMERIC = /^[+-]?(\d+\.?\d*|\.\d+)(e[+-]?\d+)?$/i;
65
+ /** A value from attribute text. `kind` says how; AUTO guesses, and is safe because every attribute that is only text is STRING. */
66
+ function convert(text, kind, name) {
67
+ switch (kind) {
68
+ case STRING: return text;
69
+ case NUMBER: {
70
+ const n = Number(text);
71
+ if (text.trim() === '' || Number.isNaN(n))
72
+ throw new TypeError(`attribute '${name}': '${text}' is not a number`);
73
+ return n;
74
+ }
75
+ case BOOLEAN: return text !== 'false';
76
+ default: {
77
+ if (text === '' || text === 'true')
78
+ return true;
79
+ if (text === 'false')
80
+ return false;
81
+ if (NUMERIC.test(text.trim()))
82
+ return Number(text);
83
+ if (/^\s*[[{]/.test(text)) {
84
+ try {
85
+ return JSON.parse(text);
86
+ }
87
+ catch (cause) {
88
+ throw new TypeError(`attribute '${name}': '${text}' is not valid JSON`, { cause });
89
+ }
90
+ }
91
+ return text;
92
+ }
93
+ }
94
+ }
95
+ const kebab = (key) => key.replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase();
96
+ /** The base class of every wrapper, made when `defineElements` first runs because `HTMLElement` may not exist before. */
97
+ let Base = null;
98
+ function makeBase() {
99
+ const observer = new MutationObserver((records) => {
100
+ for (const target of new Set(records.map((r) => r.target)))
101
+ target.sync();
102
+ });
103
+ return class Wrapper extends HTMLElement {
104
+ // Set by defineElements on each defined subclass.
105
+ static cls;
106
+ static types;
107
+ static styleNames;
108
+ #element = null;
109
+ #applied = {};
110
+ #props = {};
111
+ #handlers = new Map();
112
+ #listening = new Set();
113
+ #parent = null;
114
+ #platform = null;
115
+ platformOptions;
116
+ addEventListener(type, listener, options) {
117
+ super.addEventListener(type, listener, options);
118
+ if (!this.#listening.has(type)) {
119
+ this.#listening.add(type);
120
+ this.sync();
121
+ }
122
+ }
123
+ get element() { return this.#element; }
124
+ get props() { return this.#props; }
125
+ set props(value) { this.#props = { ...value }; this.sync(); }
126
+ /** Read the attributes (and props) and write what differs to the element, making it the first time. */
127
+ sync() {
128
+ if (!this.isConnected)
129
+ return this.#element;
130
+ const ctor = this.constructor;
131
+ const flat = {};
132
+ for (const a of Array.from(this.attributes)) {
133
+ const lower = a.name;
134
+ if (PAGE_ATTRIBUTES.has(lower) || lower.startsWith('on'))
135
+ continue;
136
+ flat[lower] = a.value;
137
+ }
138
+ const attrs = attributesOf(flat, {
139
+ name: (part, parents) => {
140
+ const spec = specAt(ctor.types, parents);
141
+ return (spec ? lowerNames(spec).get(part) : parents.length ? ctor.styleNames.get(part) : undefined) ?? part;
142
+ },
143
+ value: (path, text) => {
144
+ // oxlint-disable-next-line typescript/no-non-null-assertion -- in range: the index comes from a loop bound or a length checked in the same function
145
+ const kind = specAt(ctor.types, path.slice(0, -1))?.[path[path.length - 1]];
146
+ return convert(text, typeof kind === 'number' ? kind : AUTO, path.join('-'));
147
+ },
148
+ });
149
+ // A callback that dispatches its event, once the page listens for it (`addEventListener`, or an inline `onclick=""`).
150
+ for (const name of Object.keys(ctor.types)) {
151
+ if (!/^on[A-Z]/.test(name))
152
+ continue;
153
+ const type = name.slice(2).toLowerCase();
154
+ if (!this.#listening.has(type) && !this.hasAttribute('on' + type))
155
+ continue;
156
+ let handler = this.#handlers.get(name);
157
+ if (!handler)
158
+ this.#handlers.set(name, handler = (detail) => { this.dispatchEvent(new CustomEvent(type, { detail, bubbles: true, composed: true })); });
159
+ attrs[name] = handler;
160
+ }
161
+ for (const k of Object.keys(this.#props))
162
+ if (this.#props[k] === undefined)
163
+ delete attrs[k];
164
+ else
165
+ attrs[k] = this.#props[k];
166
+ if (!this.#element) {
167
+ this.#element = new ctor.cls(attrs);
168
+ this.#applied = attrs;
169
+ return this.#element;
170
+ }
171
+ const patch = difference(this.#applied, attrs);
172
+ if (patch)
173
+ this.#element.setAttributes(patch);
174
+ this.#applied = attrs;
175
+ return this.#element;
176
+ }
177
+ connectedCallback() {
178
+ const element = this.sync();
179
+ if (!element)
180
+ return; // only when disconnected, and this is the callback for connecting
181
+ let ancestor = this.parentElement;
182
+ while (ancestor && !(ancestor instanceof Wrapper))
183
+ ancestor = ancestor.parentElement;
184
+ this.#parent = ancestor;
185
+ if (this.#parent) {
186
+ const host = this.#parent.element;
187
+ if (!host)
188
+ return;
189
+ // Before the first later sibling tag that is already in the same parent element, else last.
190
+ let before = null;
191
+ for (let s = this.nextElementSibling; s; s = s.nextElementSibling) {
192
+ const other = s instanceof Wrapper ? s.element : null;
193
+ if (other && other.parent === host) {
194
+ before = other;
195
+ break;
196
+ }
197
+ }
198
+ host.insertBefore(element, before);
199
+ observer.observe(this, { attributes: true });
200
+ }
201
+ else if (typeof element.attach === 'function') {
202
+ const root = this.shadowRoot ?? this.attachShadow({ mode: 'open' });
203
+ root.replaceChildren();
204
+ const style = this.ownerDocument.createElement('style');
205
+ style.textContent = ':host{display:block}div{width:100%;height:100%}';
206
+ const container = this.ownerDocument.createElement('div');
207
+ root.append(style, container);
208
+ this.#platform = new DOMPlatform(container, this.platformOptions);
209
+ element.attach(this.#platform);
210
+ observer.observe(this, { attributes: true });
211
+ }
212
+ }
213
+ disconnectedCallback() {
214
+ const element = this.#element;
215
+ if (!element)
216
+ return;
217
+ if (this.#platform) {
218
+ element.detach();
219
+ this.#platform.dispose();
220
+ this.#platform = null;
221
+ }
222
+ const host = this.#parent?.element;
223
+ if (host && element.parent === host)
224
+ host.remove(element);
225
+ this.#parent = null;
226
+ }
227
+ };
228
+ }
229
+ /**
230
+ * Define a custom element for each class. `classes` maps a name to a class, and the name gives the tag. Returns the tags by name.
231
+ * Does nothing at import, so a module that imports this can be loaded where there is no DOM.
232
+ */
233
+ export function defineElements(classes, options) {
234
+ const registry = options.registry ?? customElements;
235
+ const base = (Base ??= makeBase());
236
+ const styleNames = new Map();
237
+ for (const n of [...STYLE_NAMES, ...options.names ?? []])
238
+ styleNames.set(n.toLowerCase(), n);
239
+ const tags = {};
240
+ for (const [key, cls] of Object.entries(classes)) {
241
+ const tag = `${options.prefix}-${kebab(key)}`;
242
+ tags[key] = tag;
243
+ const types = cls.attrTypes ?? {};
244
+ // The lowercase-name maps are made now, so a clash is an error here and not on first use.
245
+ const check = (spec, where) => {
246
+ lowerNames(spec, where);
247
+ for (const [n, v] of Object.entries(spec))
248
+ if (typeof v === 'object')
249
+ check(v, `${where}.${n}`);
250
+ };
251
+ check(types, key);
252
+ const wrapper = class extends base {
253
+ };
254
+ Object.assign(wrapper, { cls, types, styleNames });
255
+ const have = registry.get(tag);
256
+ if (have) {
257
+ if (have.cls === cls)
258
+ continue;
259
+ throw new Error(`<${tag}> is already defined for something else`);
260
+ }
261
+ registry.define(tag, wrapper);
262
+ }
263
+ return tags;
264
+ }
package/package.json CHANGED
@@ -1,6 +1,56 @@
1
1
  {
2
2
  "name": "inkplot",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "1.0.0-beta.1",
4
+ "description": "A small, tree-shakeable chart library for the web and servers: a tree of elements you keep, drawn as SVG.",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/neofox-solutions/inkplot.git"
9
+ },
10
+ "homepage": "https://github.com/neofox-solutions/inkplot#readme",
11
+ "bugs": {
12
+ "url": "https://github.com/neofox-solutions/inkplot/issues"
13
+ },
14
+ "keywords": [
15
+ "chart",
16
+ "charts",
17
+ "svg",
18
+ "visualization",
19
+ "plot",
20
+ "tree-shakeable",
21
+ "ssr"
22
+ ],
23
+ "type": "module",
24
+ "sideEffects": false,
25
+ "exports": {
26
+ ".": {
27
+ "types": "./dist/index.d.ts",
28
+ "default": "./dist/index.js"
29
+ },
30
+ "./jsx-runtime": {
31
+ "types": "./dist/jsx-runtime.d.ts",
32
+ "default": "./dist/jsx-runtime.js"
33
+ },
34
+ "./jsx-dev-runtime": {
35
+ "types": "./dist/jsx-dev-runtime.d.ts",
36
+ "default": "./dist/jsx-dev-runtime.js"
37
+ },
38
+ "./react": {
39
+ "types": "./dist/react.d.ts",
40
+ "default": "./dist/react.js"
41
+ },
42
+ "./web-components": {
43
+ "types": "./dist/web-components.d.ts",
44
+ "default": "./dist/web-components.js"
45
+ }
46
+ },
47
+ "types": "./dist/index.d.ts",
48
+ "peerDependencies": {
49
+ "react": ">=18"
50
+ },
51
+ "peerDependenciesMeta": {
52
+ "react": {
53
+ "optional": true
54
+ }
55
+ }
6
56
  }