@motionscript/charts 0.0.0-stage → 0.1.0-alpha.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/CHANGELOG.md +5 -0
- package/LICENSE +201 -0
- package/dist/bar-chart/bar-chart.d.ts +286 -0
- package/dist/bar-chart/bar-chart.d.ts.map +1 -0
- package/dist/bar-chart/bar-chart.js +610 -0
- package/dist/bar-chart/bar-chart.js.map +1 -0
- package/dist/bar-chart/category-axis.d.ts +77 -0
- package/dist/bar-chart/category-axis.d.ts.map +1 -0
- package/dist/bar-chart/category-axis.js +141 -0
- package/dist/bar-chart/category-axis.js.map +1 -0
- package/dist/bar-chart/chart-bars.d.ts +149 -0
- package/dist/bar-chart/chart-bars.d.ts.map +1 -0
- package/dist/bar-chart/chart-bars.js +236 -0
- package/dist/bar-chart/chart-bars.js.map +1 -0
- package/dist/bar-chart/index.d.ts +16 -0
- package/dist/bar-chart/index.d.ts.map +1 -0
- package/dist/bar-chart/index.js +12 -0
- package/dist/bar-chart/index.js.map +1 -0
- package/dist/bar-chart/shared.d.ts +146 -0
- package/dist/bar-chart/shared.d.ts.map +1 -0
- package/dist/bar-chart/shared.js +182 -0
- package/dist/bar-chart/shared.js.map +1 -0
- package/dist/browser/chunks/chunk-ZMBOBHYL.js +2 -0
- package/dist/browser/chunks/chunk-ZMBOBHYL.js.map +7 -0
- package/dist/browser/index.js +2 -0
- package/dist/browser/index.js.map +7 -0
- package/dist/browser/kit.js +2 -0
- package/dist/browser/kit.js.map +7 -0
- package/dist/browser/manifest.json +12 -0
- package/dist/engine.d.ts +27 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +27 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -0
- package/dist/kit/chart-border.d.ts +28 -0
- package/dist/kit/chart-border.d.ts.map +1 -0
- package/dist/kit/chart-border.js +45 -0
- package/dist/kit/chart-border.js.map +1 -0
- package/dist/kit/chart-canvas.d.ts +56 -0
- package/dist/kit/chart-canvas.d.ts.map +1 -0
- package/dist/kit/chart-canvas.js +125 -0
- package/dist/kit/chart-canvas.js.map +1 -0
- package/dist/kit/chart-legend.d.ts +126 -0
- package/dist/kit/chart-legend.d.ts.map +1 -0
- package/dist/kit/chart-legend.js +212 -0
- package/dist/kit/chart-legend.js.map +1 -0
- package/dist/kit/shared.d.ts +251 -0
- package/dist/kit/shared.d.ts.map +1 -0
- package/dist/kit/shared.js +401 -0
- package/dist/kit/shared.js.map +1 -0
- package/dist/kit/vertical-axis.d.ts +99 -0
- package/dist/kit/vertical-axis.d.ts.map +1 -0
- package/dist/kit/vertical-axis.js +176 -0
- package/dist/kit/vertical-axis.js.map +1 -0
- package/dist/line-chart/chart-line.d.ts +91 -0
- package/dist/line-chart/chart-line.d.ts.map +1 -0
- package/dist/line-chart/chart-line.js +156 -0
- package/dist/line-chart/chart-line.js.map +1 -0
- package/dist/line-chart/chart-region.d.ts +47 -0
- package/dist/line-chart/chart-region.d.ts.map +1 -0
- package/dist/line-chart/chart-region.js +101 -0
- package/dist/line-chart/chart-region.js.map +1 -0
- package/dist/line-chart/horizontal-axis.d.ts +85 -0
- package/dist/line-chart/horizontal-axis.d.ts.map +1 -0
- package/dist/line-chart/horizontal-axis.js +158 -0
- package/dist/line-chart/horizontal-axis.js.map +1 -0
- package/dist/line-chart/index.d.ts +22 -0
- package/dist/line-chart/index.d.ts.map +1 -0
- package/dist/line-chart/index.js +19 -0
- package/dist/line-chart/index.js.map +1 -0
- package/dist/line-chart/line-chart.d.ts +347 -0
- package/dist/line-chart/line-chart.d.ts.map +1 -0
- package/dist/line-chart/line-chart.js +701 -0
- package/dist/line-chart/line-chart.js.map +1 -0
- package/dist/line-chart/shared.d.ts +171 -0
- package/dist/line-chart/shared.d.ts.map +1 -0
- package/dist/line-chart/shared.js +270 -0
- package/dist/line-chart/shared.js.map +1 -0
- package/dist/line-chart/x-scale.d.ts +84 -0
- package/dist/line-chart/x-scale.d.ts.map +1 -0
- package/dist/line-chart/x-scale.js +302 -0
- package/dist/line-chart/x-scale.js.map +1 -0
- package/dist/nodes.d.ts +20 -0
- package/dist/nodes.d.ts.map +1 -0
- package/dist/nodes.js +20 -0
- package/dist/nodes.js.map +1 -0
- package/dist/pie-chart/index.d.ts +18 -0
- package/dist/pie-chart/index.d.ts.map +1 -0
- package/dist/pie-chart/index.js +14 -0
- package/dist/pie-chart/index.js.map +1 -0
- package/dist/pie-chart/pie-chart.d.ts +170 -0
- package/dist/pie-chart/pie-chart.d.ts.map +1 -0
- package/dist/pie-chart/pie-chart.js +291 -0
- package/dist/pie-chart/pie-chart.js.map +1 -0
- package/dist/pie-chart/pie-legend.d.ts +60 -0
- package/dist/pie-chart/pie-legend.d.ts.map +1 -0
- package/dist/pie-chart/pie-legend.js +131 -0
- package/dist/pie-chart/pie-legend.js.map +1 -0
- package/dist/pie-chart/pie-ring.d.ts +266 -0
- package/dist/pie-chart/pie-ring.d.ts.map +1 -0
- package/dist/pie-chart/pie-ring.js +708 -0
- package/dist/pie-chart/pie-ring.js.map +1 -0
- package/dist/pie-chart/shared.d.ts +210 -0
- package/dist/pie-chart/shared.d.ts.map +1 -0
- package/dist/pie-chart/shared.js +240 -0
- package/dist/pie-chart/shared.js.map +1 -0
- package/package.json +69 -3
- package/registry.json +23 -0
- package/src/bar-chart/bar-chart.ts +804 -0
- package/src/bar-chart/category-axis.ts +177 -0
- package/src/bar-chart/chart-bars.ts +310 -0
- package/src/bar-chart/index.ts +31 -0
- package/src/bar-chart/shared.ts +354 -0
- package/src/engine.ts +26 -0
- package/src/index.ts +5 -0
- package/src/kit/chart-border.ts +61 -0
- package/src/kit/chart-canvas.ts +157 -0
- package/src/kit/chart-legend.ts +250 -0
- package/src/kit/shared.ts +675 -0
- package/src/kit/vertical-axis.ts +224 -0
- package/src/line-chart/chart-line.ts +208 -0
- package/src/line-chart/chart-region.ts +135 -0
- package/src/line-chart/horizontal-axis.ts +202 -0
- package/src/line-chart/index.ts +39 -0
- package/src/line-chart/line-chart.ts +894 -0
- package/src/line-chart/shared.ts +459 -0
- package/src/line-chart/x-scale.ts +367 -0
- package/src/nodes.ts +20 -0
- package/src/pie-chart/index.ts +37 -0
- package/src/pie-chart/pie-chart.ts +374 -0
- package/src/pie-chart/pie-legend.ts +159 -0
- package/src/pie-chart/pie-ring.ts +903 -0
- package/src/pie-chart/shared.ts +490 -0
- package/README.md +0 -4
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a line chart's x column *means* — the one question that has to be
|
|
3
|
+
* answered before a row can be given a position along the bottom of the plot.
|
|
4
|
+
*
|
|
5
|
+
* A y column is always a magnitude. An x column is one of three things, and
|
|
6
|
+
* which one is a property of the data rather than something the author states:
|
|
7
|
+
*
|
|
8
|
+
* - **numeric** — `0, 5, 10` — plot the number.
|
|
9
|
+
* - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,
|
|
10
|
+
* so samples a month apart sit a month apart and a gap in the data reads as a
|
|
11
|
+
* gap.
|
|
12
|
+
* - **ordinal** — anything else — plot the row's position, evenly spaced.
|
|
13
|
+
*
|
|
14
|
+
* Before this, every x column went through `num()`, which reads a non-numeric
|
|
15
|
+
* cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve
|
|
16
|
+
* points on one x and drew a vertical line — no error, no empty chart, just a
|
|
17
|
+
* wrong picture. That is the failure this module exists to remove: every column
|
|
18
|
+
* now plots as *something*, and the something is the reading a person would
|
|
19
|
+
* give it.
|
|
20
|
+
*
|
|
21
|
+
* The captions come back off the rows rather than out of a formatter. A tick on
|
|
22
|
+
* a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),
|
|
23
|
+
* so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,
|
|
24
|
+
* and `2026-W12`, not the Monday it resolves to. Only a tick that is not a
|
|
25
|
+
* sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,
|
|
26
|
+
* and that is the one case {@link formatInstant} covers.
|
|
27
|
+
*/
|
|
28
|
+
import type { DataRecord } from "@motionscript/core";
|
|
29
|
+
import { type Range } from "../kit/shared.js";
|
|
30
|
+
/** How an x column's cells become positions along the axis. */
|
|
31
|
+
export type XScaleKind = "numeric" | "time" | "ordinal";
|
|
32
|
+
/**
|
|
33
|
+
* An x column resolved into positions, captions and ticks.
|
|
34
|
+
*
|
|
35
|
+
* The three kinds differ in all three answers, so they are three closures built
|
|
36
|
+
* once per (data, column) rather than a kind tag every caller switches on.
|
|
37
|
+
*/
|
|
38
|
+
export interface XScale {
|
|
39
|
+
kind: XScaleKind;
|
|
40
|
+
/** Position of row `index` along the axis, in this scale's own units. */
|
|
41
|
+
at(index: number): number;
|
|
42
|
+
/** `[min, max]` across the rows — the scale when no range is set. */
|
|
43
|
+
extent: Range;
|
|
44
|
+
/** The caption for a tick at `value`. */
|
|
45
|
+
label(value: number): string;
|
|
46
|
+
/** `divisions + 1` tick values spanning `range`, ends included. */
|
|
47
|
+
ticks(range: Range, divisions: number): number[];
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* One cell read as an instant, or `null` when it isn't one.
|
|
51
|
+
*
|
|
52
|
+
* Deliberately **not** `Date.parse`. That accepts `"5"`, `"Q3"` and a good deal
|
|
53
|
+
* else besides, differs between engines on anything non-ISO, and reads a bare
|
|
54
|
+
* `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be
|
|
55
|
+
* silently misread as time. Every form below is matched explicitly, and an
|
|
56
|
+
* ambiguous all-numeric date (`03/04/2026` — March or April, depending on where
|
|
57
|
+
* the author lives) is deliberately not one of them.
|
|
58
|
+
*/
|
|
59
|
+
export declare function parseInstant(raw: unknown): number | null;
|
|
60
|
+
/**
|
|
61
|
+
* An instant captioned at the coarsest granularity the axis's own span can tell
|
|
62
|
+
* apart — a decade of samples reads as years, a year of them as months.
|
|
63
|
+
*
|
|
64
|
+
* Only reached for a tick that is *not* one of the rows (an author-set range, a
|
|
65
|
+
* `zoomTo` part-way through). A tick that is a sample is captioned with the
|
|
66
|
+
* cell the author wrote; see the note at the top of this file.
|
|
67
|
+
*/
|
|
68
|
+
export declare function formatInstant(value: number, span: number): string;
|
|
69
|
+
/**
|
|
70
|
+
* How `field` positions the rows: the number it holds, the instant it names, or
|
|
71
|
+
* failing both, the row's own place in the file.
|
|
72
|
+
*
|
|
73
|
+
* Resolved from the data on every read rather than cached on the node: it is
|
|
74
|
+
* cheap (one pass to classify, one to position), and a cache keyed on a `data`
|
|
75
|
+
* array that `buildScene` rebuilds on every edit would miss every time anyway.
|
|
76
|
+
*/
|
|
77
|
+
export declare function resolveXScale(data: DataRecord[], field: string,
|
|
78
|
+
/**
|
|
79
|
+
* The number-format pattern for a *numeric* column. Ignored by the other two
|
|
80
|
+
* kinds, and correctly so: a tick on those is captioned with the cell the
|
|
81
|
+
* author wrote, and `Jan` has no decimal places.
|
|
82
|
+
*/
|
|
83
|
+
pattern?: string): XScale;
|
|
84
|
+
//# sourceMappingURL=x-scale.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x-scale.d.ts","sourceRoot":"","sources":["../../src/line-chart/x-scale.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AAEH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAA;AAEpD,OAAO,EAKL,KAAK,KAAK,EACX,MAAM,eAAe,CAAA;AAGtB,+DAA+D;AAC/D,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAAA;AAEvD;;;;;GAKG;AACH,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,UAAU,CAAA;IAChB,yEAAyE;IACzE,EAAE,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IACzB,qEAAqE;IACrE,MAAM,EAAE,KAAK,CAAA;IACb,yCAAyC;IACzC,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAAA;IAC5B,mEAAmE;IACnE,KAAK,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,EAAE,CAAA;CACjD;AAmDD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAkDxD;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAQjE;AAuHD;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAC3B,IAAI,EAAE,UAAU,EAAE,EAClB,KAAK,EAAE,MAAM;AACb;;;;GAIG;AACH,OAAO,CAAC,EAAE,MAAM,GACf,MAAM,CA2CR"}
|
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What a line chart's x column *means* — the one question that has to be
|
|
3
|
+
* answered before a row can be given a position along the bottom of the plot.
|
|
4
|
+
*
|
|
5
|
+
* A y column is always a magnitude. An x column is one of three things, and
|
|
6
|
+
* which one is a property of the data rather than something the author states:
|
|
7
|
+
*
|
|
8
|
+
* - **numeric** — `0, 5, 10` — plot the number.
|
|
9
|
+
* - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,
|
|
10
|
+
* so samples a month apart sit a month apart and a gap in the data reads as a
|
|
11
|
+
* gap.
|
|
12
|
+
* - **ordinal** — anything else — plot the row's position, evenly spaced.
|
|
13
|
+
*
|
|
14
|
+
* Before this, every x column went through `num()`, which reads a non-numeric
|
|
15
|
+
* cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve
|
|
16
|
+
* points on one x and drew a vertical line — no error, no empty chart, just a
|
|
17
|
+
* wrong picture. That is the failure this module exists to remove: every column
|
|
18
|
+
* now plots as *something*, and the something is the reading a person would
|
|
19
|
+
* give it.
|
|
20
|
+
*
|
|
21
|
+
* The captions come back off the rows rather than out of a formatter. A tick on
|
|
22
|
+
* a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),
|
|
23
|
+
* so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,
|
|
24
|
+
* and `2026-W12`, not the Monday it resolves to. Only a tick that is not a
|
|
25
|
+
* sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,
|
|
26
|
+
* and that is the one case {@link formatInstant} covers.
|
|
27
|
+
*/
|
|
28
|
+
import { formatMarker, markerValues, num, text, } from "../kit/shared.js";
|
|
29
|
+
import { formatNumber } from "@motionscript/core/component";
|
|
30
|
+
// ---- Time parsing --------------------------------------------------------
|
|
31
|
+
const DAY = 86_400_000;
|
|
32
|
+
const MONTHS = [
|
|
33
|
+
"january",
|
|
34
|
+
"february",
|
|
35
|
+
"march",
|
|
36
|
+
"april",
|
|
37
|
+
"may",
|
|
38
|
+
"june",
|
|
39
|
+
"july",
|
|
40
|
+
"august",
|
|
41
|
+
"september",
|
|
42
|
+
"october",
|
|
43
|
+
"november",
|
|
44
|
+
"december",
|
|
45
|
+
];
|
|
46
|
+
/**
|
|
47
|
+
* The year a cell that names no year is dated into.
|
|
48
|
+
*
|
|
49
|
+
* Any year would place `Jan…Dec` in the right order at the right spacing; a
|
|
50
|
+
* leap year is the one that also spaces February correctly against a column
|
|
51
|
+
* that *does* carry years, so the two read alike.
|
|
52
|
+
*/
|
|
53
|
+
const NOMINAL_YEAR = 2024;
|
|
54
|
+
/**
|
|
55
|
+
* Month index for any unambiguous abbreviation of a month name — `jan`, `sept`,
|
|
56
|
+
* `january` — or -1.
|
|
57
|
+
*
|
|
58
|
+
* Three letters minimum, and then a prefix match, which is what lets `sept`
|
|
59
|
+
* through without letting `janitor` through: a longer word has to still *be*
|
|
60
|
+
* the month.
|
|
61
|
+
*/
|
|
62
|
+
function monthIndex(word) {
|
|
63
|
+
if (word.length < 3)
|
|
64
|
+
return -1;
|
|
65
|
+
return MONTHS.findIndex((month) => month.startsWith(word));
|
|
66
|
+
}
|
|
67
|
+
/** The Monday of ISO week `week` in `year`, as an epoch timestamp. */
|
|
68
|
+
function isoWeekStart(year, week) {
|
|
69
|
+
// ISO week 1 is the week containing 4 January, and weeks start on Monday.
|
|
70
|
+
const jan4 = Date.UTC(year, 0, 4);
|
|
71
|
+
const weekday = (new Date(jan4).getUTCDay() + 6) % 7; // Monday = 0
|
|
72
|
+
return jan4 - weekday * DAY + (week - 1) * 7 * DAY;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* One cell read as an instant, or `null` when it isn't one.
|
|
76
|
+
*
|
|
77
|
+
* Deliberately **not** `Date.parse`. That accepts `"5"`, `"Q3"` and a good deal
|
|
78
|
+
* else besides, differs between engines on anything non-ISO, and reads a bare
|
|
79
|
+
* `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be
|
|
80
|
+
* silently misread as time. Every form below is matched explicitly, and an
|
|
81
|
+
* ambiguous all-numeric date (`03/04/2026` — March or April, depending on where
|
|
82
|
+
* the author lives) is deliberately not one of them.
|
|
83
|
+
*/
|
|
84
|
+
export function parseInstant(raw) {
|
|
85
|
+
if (typeof raw !== "string")
|
|
86
|
+
return null;
|
|
87
|
+
const s = raw.trim().toLowerCase();
|
|
88
|
+
if (s === "")
|
|
89
|
+
return null;
|
|
90
|
+
// 2026-03-14 · 2026/03/14 · 2026-03 · 2026/03
|
|
91
|
+
const ymd = /^(\d{4})[-/](\d{1,2})(?:[-/](\d{1,2}))?$/.exec(s);
|
|
92
|
+
if (ymd) {
|
|
93
|
+
const month = Number(ymd[2]);
|
|
94
|
+
const day = ymd[3] === undefined ? 1 : Number(ymd[3]);
|
|
95
|
+
if (month < 1 || month > 12 || day < 1 || day > 31)
|
|
96
|
+
return null;
|
|
97
|
+
return Date.UTC(Number(ymd[1]), month - 1, day);
|
|
98
|
+
}
|
|
99
|
+
// 2026-W12 · 2026W12 · w12 · week 12
|
|
100
|
+
const week = /^(?:(\d{4})[-\s]?)?w(?:ee)?k?[-\s]?(\d{1,2})$/.exec(s);
|
|
101
|
+
if (week) {
|
|
102
|
+
const n = Number(week[2]);
|
|
103
|
+
if (n < 1 || n > 53)
|
|
104
|
+
return null;
|
|
105
|
+
return isoWeekStart(week[1] === undefined ? NOMINAL_YEAR : Number(week[1]), n);
|
|
106
|
+
}
|
|
107
|
+
// jan · january · jan 2026 · january-2026 · 2026 jan
|
|
108
|
+
const named = /^(?:(\d{4})[-\s]+)?([a-z]+)(?:[-\s]+(\d{4}))?$/.exec(s);
|
|
109
|
+
if (named) {
|
|
110
|
+
const month = monthIndex(named[2]);
|
|
111
|
+
if (month >= 0) {
|
|
112
|
+
const year = named[1] ?? named[3];
|
|
113
|
+
const y = year === undefined ? NOMINAL_YEAR : Number(year);
|
|
114
|
+
return Date.UTC(y, month, 1);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// 14 mar 2026 · mar 14 2026 · mar 14, 2026
|
|
118
|
+
const dayNamed = /^(?:(\d{1,2})[-\s]+([a-z]+)|([a-z]+)[-\s]+(\d{1,2}),?)[-\s]+(\d{4})$/.exec(s);
|
|
119
|
+
if (dayNamed) {
|
|
120
|
+
const month = monthIndex(dayNamed[2] ?? dayNamed[3]);
|
|
121
|
+
const day = Number(dayNamed[1] ?? dayNamed[4]);
|
|
122
|
+
if (month >= 0 && day >= 1 && day <= 31) {
|
|
123
|
+
return Date.UTC(Number(dayNamed[5]), month, day);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
return null;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* An instant captioned at the coarsest granularity the axis's own span can tell
|
|
130
|
+
* apart — a decade of samples reads as years, a year of them as months.
|
|
131
|
+
*
|
|
132
|
+
* Only reached for a tick that is *not* one of the rows (an author-set range, a
|
|
133
|
+
* `zoomTo` part-way through). A tick that is a sample is captioned with the
|
|
134
|
+
* cell the author wrote; see the note at the top of this file.
|
|
135
|
+
*/
|
|
136
|
+
export function formatInstant(value, span) {
|
|
137
|
+
const date = new Date(value);
|
|
138
|
+
const year = date.getUTCFullYear();
|
|
139
|
+
const month = MONTHS[date.getUTCMonth()].slice(0, 3);
|
|
140
|
+
const label = month.charAt(0).toUpperCase() + month.slice(1);
|
|
141
|
+
if (span > 4 * 365 * DAY)
|
|
142
|
+
return String(year);
|
|
143
|
+
if (span > 60 * DAY)
|
|
144
|
+
return `${label} ${year}`;
|
|
145
|
+
return `${label} ${date.getUTCDate()}`;
|
|
146
|
+
}
|
|
147
|
+
// ---- Column reading ------------------------------------------------------
|
|
148
|
+
/**
|
|
149
|
+
* Whether every non-empty cell of `field` holds a number.
|
|
150
|
+
*
|
|
151
|
+
* The same rule `numericFields` applies, asked of one column: a single text
|
|
152
|
+
* cell in an otherwise numeric column takes the whole column off the numeric
|
|
153
|
+
* scale, because plotting that row at 0 is the failure this module is here to
|
|
154
|
+
* stop.
|
|
155
|
+
*/
|
|
156
|
+
function isNumericColumn(data, field) {
|
|
157
|
+
let sawValue = false;
|
|
158
|
+
for (const row of data) {
|
|
159
|
+
const v = row[field];
|
|
160
|
+
if (v === undefined || v === null || v === "")
|
|
161
|
+
continue;
|
|
162
|
+
if (typeof v !== "number")
|
|
163
|
+
return false;
|
|
164
|
+
sawValue = true;
|
|
165
|
+
}
|
|
166
|
+
return sawValue;
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Every non-empty cell of `field` parsed as an instant, or `null` if any one of
|
|
170
|
+
* them isn't one — in which case the column isn't a time column at all.
|
|
171
|
+
*
|
|
172
|
+
* A blank cell keeps its slot, so the returned list stays index-aligned with
|
|
173
|
+
* the rows, and takes the nearest known instant: a hole in an x column is a row
|
|
174
|
+
* whose date the file didn't say, and stacking it on the epoch would drag the
|
|
175
|
+
* whole scale back fifty years to make room for it.
|
|
176
|
+
*/
|
|
177
|
+
function instantColumn(data, field) {
|
|
178
|
+
const parsed = [];
|
|
179
|
+
let sawValue = false;
|
|
180
|
+
for (const row of data) {
|
|
181
|
+
const raw = row[field];
|
|
182
|
+
if (raw === undefined || raw === null || raw === "") {
|
|
183
|
+
parsed.push(null);
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
const t = parseInstant(raw);
|
|
187
|
+
if (t === null)
|
|
188
|
+
return null;
|
|
189
|
+
parsed.push(t);
|
|
190
|
+
sawValue = true;
|
|
191
|
+
}
|
|
192
|
+
if (!sawValue)
|
|
193
|
+
return null;
|
|
194
|
+
const first = parsed.find((t) => t !== null);
|
|
195
|
+
let last = first;
|
|
196
|
+
return parsed.map((t) => (t === null ? last : (last = t)));
|
|
197
|
+
}
|
|
198
|
+
/** `[min, max]` of `values`, or `[0, 1]` when there is nothing to span. */
|
|
199
|
+
function extentOf(values) {
|
|
200
|
+
let lo = Infinity;
|
|
201
|
+
let hi = -Infinity;
|
|
202
|
+
for (const v of values) {
|
|
203
|
+
if (v < lo)
|
|
204
|
+
lo = v;
|
|
205
|
+
if (v > hi)
|
|
206
|
+
hi = v;
|
|
207
|
+
}
|
|
208
|
+
return lo <= hi ? [lo, hi] : [0, 1];
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Ticks spanning `range`, each one **a sample** — a row the author can point at
|
|
212
|
+
* in their file — placed as near as it can be to where an evenly spaced tick
|
|
213
|
+
* would have gone.
|
|
214
|
+
*
|
|
215
|
+
* Two things have to hold at once, and picking either alone gets it wrong.
|
|
216
|
+
* Ticks must be *samples*, because a caption between two of them has nothing to
|
|
217
|
+
* say (two-thirds of the way through March is not a month, and on a category
|
|
218
|
+
* column it is not anything at all). And they must be *spread*, because the
|
|
219
|
+
* labels sit side by side in a row: taking every nth sample instead puts five
|
|
220
|
+
* dates in the two weeks a burst of rows happens to cover and prints them on
|
|
221
|
+
* top of each other, which is what a column with a gap in it does.
|
|
222
|
+
*
|
|
223
|
+
* Walking the targets rather than the samples is what satisfies both. A run of
|
|
224
|
+
* rows too close together to caption collapses to one tick — consecutive
|
|
225
|
+
* targets pick the same sample and the duplicate is dropped — so a chart asked
|
|
226
|
+
* for five ticks across a gappy column draws four honest ones instead of five
|
|
227
|
+
* overlapping.
|
|
228
|
+
*/
|
|
229
|
+
function sampledTicks(positions, [min, max], divisions) {
|
|
230
|
+
const inside = positions.filter((v) => v >= min && v <= max);
|
|
231
|
+
const unique = [...new Set(inside)].sort((a, b) => a - b);
|
|
232
|
+
if (unique.length === 0)
|
|
233
|
+
return markerValues([min, max], divisions);
|
|
234
|
+
const nearest = (target) => unique.reduce((best, v) => Math.abs(v - target) < Math.abs(best - target) ? v : best);
|
|
235
|
+
const out = [];
|
|
236
|
+
for (const target of markerValues([min, max], divisions)) {
|
|
237
|
+
const tick = nearest(target);
|
|
238
|
+
if (out[out.length - 1] !== tick)
|
|
239
|
+
out.push(tick);
|
|
240
|
+
}
|
|
241
|
+
return out;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* The caption a tick carries on a sample-based scale: the cell of the row it
|
|
245
|
+
* came from, or `fallback` when the tick isn't a sample after all.
|
|
246
|
+
*/
|
|
247
|
+
function labelBySample(positions, captions, value, fallback) {
|
|
248
|
+
const i = positions.indexOf(value);
|
|
249
|
+
return i >= 0 ? captions[i] : fallback(value);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* How `field` positions the rows: the number it holds, the instant it names, or
|
|
253
|
+
* failing both, the row's own place in the file.
|
|
254
|
+
*
|
|
255
|
+
* Resolved from the data on every read rather than cached on the node: it is
|
|
256
|
+
* cheap (one pass to classify, one to position), and a cache keyed on a `data`
|
|
257
|
+
* array that `buildScene` rebuilds on every edit would miss every time anyway.
|
|
258
|
+
*/
|
|
259
|
+
export function resolveXScale(data, field,
|
|
260
|
+
/**
|
|
261
|
+
* The number-format pattern for a *numeric* column. Ignored by the other two
|
|
262
|
+
* kinds, and correctly so: a tick on those is captioned with the cell the
|
|
263
|
+
* author wrote, and `Jan` has no decimal places.
|
|
264
|
+
*/
|
|
265
|
+
pattern) {
|
|
266
|
+
if (field !== "" && isNumericColumn(data, field)) {
|
|
267
|
+
const positions = data.map((row) => num(row, field));
|
|
268
|
+
return {
|
|
269
|
+
kind: "numeric",
|
|
270
|
+
at: (i) => positions[i] ?? 0,
|
|
271
|
+
extent: extentOf(positions),
|
|
272
|
+
label: (value) => pattern === undefined ? formatMarker(value) : formatNumber(value, pattern),
|
|
273
|
+
// Round numbers, evenly spaced — a continuous axis is read by its scale
|
|
274
|
+
// rather than by which samples happen to be on it.
|
|
275
|
+
ticks: markerValues,
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
const captions = data.map((row) => text(row, field));
|
|
279
|
+
const instants = field === "" ? null : instantColumn(data, field);
|
|
280
|
+
if (instants) {
|
|
281
|
+
const extent = extentOf(instants);
|
|
282
|
+
const span = extent[1] - extent[0];
|
|
283
|
+
return {
|
|
284
|
+
kind: "time",
|
|
285
|
+
at: (i) => instants[i] ?? 0,
|
|
286
|
+
extent,
|
|
287
|
+
label: (value) => labelBySample(instants, captions, value, (v) => formatInstant(v, span)),
|
|
288
|
+
ticks: (range, divisions) => sampledTicks(instants, range, divisions),
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const positions = data.map((_, i) => i);
|
|
292
|
+
return {
|
|
293
|
+
kind: "ordinal",
|
|
294
|
+
at: (i) => i,
|
|
295
|
+
// `[0, n-1]` rather than the tidy `[0, n]`: the first and last rows sit on
|
|
296
|
+
// the plot's edges, which is where a reader looks for them.
|
|
297
|
+
extent: positions.length > 0 ? [0, positions.length - 1] : [0, 1],
|
|
298
|
+
label: (value) => captions[Math.round(value)] ?? "",
|
|
299
|
+
ticks: (range, divisions) => sampledTicks(positions, range, divisions),
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
//# sourceMappingURL=x-scale.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"x-scale.js","sourceRoot":"","sources":["../../src/line-chart/x-scale.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * What a line chart's x column *means* — the one question that has to be\n * answered before a row can be given a position along the bottom of the plot.\n *\n * A y column is always a magnitude. An x column is one of three things, and\n * which one is a property of the data rather than something the author states:\n *\n * - **numeric** — `0, 5, 10` — plot the number.\n * - **time** — `Jan`, `2026-03`, `2026-W12`, `2026-03-14` — plot the instant,\n * so samples a month apart sit a month apart and a gap in the data reads as a\n * gap.\n * - **ordinal** — anything else — plot the row's position, evenly spaced.\n *\n * Before this, every x column went through `num()`, which reads a non-numeric\n * cell as `0`. A `Month` column of `Jan…Dec` therefore stacked all twelve\n * points on one x and drew a vertical line — no error, no empty chart, just a\n * wrong picture. That is the failure this module exists to remove: every column\n * now plots as *something*, and the something is the reading a person would\n * give it.\n *\n * The captions come back off the rows rather than out of a formatter. A tick on\n * a time or ordinal scale is chosen *from the data* (see {@link XScale.ticks}),\n * so the label it wants is the cell the author wrote — `Jan`, not `1970-01-01`,\n * and `2026-W12`, not the Monday it resolves to. Only a tick that is not a\n * sample — an author-set range, or a `zoomTo` mid-flight — needs formatting,\n * and that is the one case {@link formatInstant} covers.\n */\n\nimport type { DataRecord } from \"@motionscript/core\"\n\nimport {\n formatMarker,\n markerValues,\n num,\n text,\n type Range,\n} from \"../kit/shared\"\nimport { formatNumber } from \"@motionscript/core/component\"\n\n/** How an x column's cells become positions along the axis. */\nexport type XScaleKind = \"numeric\" | \"time\" | \"ordinal\"\n\n/**\n * An x column resolved into positions, captions and ticks.\n *\n * The three kinds differ in all three answers, so they are three closures built\n * once per (data, column) rather than a kind tag every caller switches on.\n */\nexport interface XScale {\n kind: XScaleKind\n /** Position of row `index` along the axis, in this scale's own units. */\n at(index: number): number\n /** `[min, max]` across the rows — the scale when no range is set. */\n extent: Range\n /** The caption for a tick at `value`. */\n label(value: number): string\n /** `divisions + 1` tick values spanning `range`, ends included. */\n ticks(range: Range, divisions: number): number[]\n}\n\n// ---- Time parsing --------------------------------------------------------\n\nconst DAY = 86_400_000\n\nconst MONTHS = [\n \"january\",\n \"february\",\n \"march\",\n \"april\",\n \"may\",\n \"june\",\n \"july\",\n \"august\",\n \"september\",\n \"october\",\n \"november\",\n \"december\",\n]\n\n/**\n * The year a cell that names no year is dated into.\n *\n * Any year would place `Jan…Dec` in the right order at the right spacing; a\n * leap year is the one that also spaces February correctly against a column\n * that *does* carry years, so the two read alike.\n */\nconst NOMINAL_YEAR = 2024\n\n/**\n * Month index for any unambiguous abbreviation of a month name — `jan`, `sept`,\n * `january` — or -1.\n *\n * Three letters minimum, and then a prefix match, which is what lets `sept`\n * through without letting `janitor` through: a longer word has to still *be*\n * the month.\n */\nfunction monthIndex(word: string): number {\n if (word.length < 3) return -1\n return MONTHS.findIndex((month) => month.startsWith(word))\n}\n\n/** The Monday of ISO week `week` in `year`, as an epoch timestamp. */\nfunction isoWeekStart(year: number, week: number): number {\n // ISO week 1 is the week containing 4 January, and weeks start on Monday.\n const jan4 = Date.UTC(year, 0, 4)\n const weekday = (new Date(jan4).getUTCDay() + 6) % 7 // Monday = 0\n return jan4 - weekday * DAY + (week - 1) * 7 * DAY\n}\n\n/**\n * One cell read as an instant, or `null` when it isn't one.\n *\n * Deliberately **not** `Date.parse`. That accepts `\"5\"`, `\"Q3\"` and a good deal\n * else besides, differs between engines on anything non-ISO, and reads a bare\n * `2026-03-14` as UTC but `2026/03/14` as local — three ways for a column to be\n * silently misread as time. Every form below is matched explicitly, and an\n * ambiguous all-numeric date (`03/04/2026` — March or April, depending on where\n * the author lives) is deliberately not one of them.\n */\nexport function parseInstant(raw: unknown): number | null {\n if (typeof raw !== \"string\") return null\n const s = raw.trim().toLowerCase()\n if (s === \"\") return null\n\n // 2026-03-14 · 2026/03/14 · 2026-03 · 2026/03\n const ymd = /^(\\d{4})[-/](\\d{1,2})(?:[-/](\\d{1,2}))?$/.exec(s)\n if (ymd) {\n const month = Number(ymd[2])\n const day = ymd[3] === undefined ? 1 : Number(ymd[3])\n if (month < 1 || month > 12 || day < 1 || day > 31) return null\n return Date.UTC(Number(ymd[1]), month - 1, day)\n }\n\n // 2026-W12 · 2026W12 · w12 · week 12\n const week = /^(?:(\\d{4})[-\\s]?)?w(?:ee)?k?[-\\s]?(\\d{1,2})$/.exec(s)\n if (week) {\n const n = Number(week[2])\n if (n < 1 || n > 53) return null\n return isoWeekStart(\n week[1] === undefined ? NOMINAL_YEAR : Number(week[1]),\n n\n )\n }\n\n // jan · january · jan 2026 · january-2026 · 2026 jan\n const named = /^(?:(\\d{4})[-\\s]+)?([a-z]+)(?:[-\\s]+(\\d{4}))?$/.exec(s)\n if (named) {\n const month = monthIndex(named[2])\n if (month >= 0) {\n const year = named[1] ?? named[3]\n const y = year === undefined ? NOMINAL_YEAR : Number(year)\n return Date.UTC(y, month, 1)\n }\n }\n\n // 14 mar 2026 · mar 14 2026 · mar 14, 2026\n const dayNamed =\n /^(?:(\\d{1,2})[-\\s]+([a-z]+)|([a-z]+)[-\\s]+(\\d{1,2}),?)[-\\s]+(\\d{4})$/.exec(\n s\n )\n if (dayNamed) {\n const month = monthIndex(dayNamed[2] ?? dayNamed[3])\n const day = Number(dayNamed[1] ?? dayNamed[4])\n if (month >= 0 && day >= 1 && day <= 31) {\n return Date.UTC(Number(dayNamed[5]), month, day)\n }\n }\n\n return null\n}\n\n/**\n * An instant captioned at the coarsest granularity the axis's own span can tell\n * apart — a decade of samples reads as years, a year of them as months.\n *\n * Only reached for a tick that is *not* one of the rows (an author-set range, a\n * `zoomTo` part-way through). A tick that is a sample is captioned with the\n * cell the author wrote; see the note at the top of this file.\n */\nexport function formatInstant(value: number, span: number): string {\n const date = new Date(value)\n const year = date.getUTCFullYear()\n const month = MONTHS[date.getUTCMonth()].slice(0, 3)\n const label = month.charAt(0).toUpperCase() + month.slice(1)\n if (span > 4 * 365 * DAY) return String(year)\n if (span > 60 * DAY) return `${label} ${year}`\n return `${label} ${date.getUTCDate()}`\n}\n\n// ---- Column reading ------------------------------------------------------\n\n/**\n * Whether every non-empty cell of `field` holds a number.\n *\n * The same rule `numericFields` applies, asked of one column: a single text\n * cell in an otherwise numeric column takes the whole column off the numeric\n * scale, because plotting that row at 0 is the failure this module is here to\n * stop.\n */\nfunction isNumericColumn(data: DataRecord[], field: string): boolean {\n let sawValue = false\n for (const row of data) {\n const v = row[field]\n if (v === undefined || v === null || v === \"\") continue\n if (typeof v !== \"number\") return false\n sawValue = true\n }\n return sawValue\n}\n\n/**\n * Every non-empty cell of `field` parsed as an instant, or `null` if any one of\n * them isn't one — in which case the column isn't a time column at all.\n *\n * A blank cell keeps its slot, so the returned list stays index-aligned with\n * the rows, and takes the nearest known instant: a hole in an x column is a row\n * whose date the file didn't say, and stacking it on the epoch would drag the\n * whole scale back fifty years to make room for it.\n */\nfunction instantColumn(data: DataRecord[], field: string): number[] | null {\n const parsed: (number | null)[] = []\n let sawValue = false\n for (const row of data) {\n const raw = row[field]\n if (raw === undefined || raw === null || raw === \"\") {\n parsed.push(null)\n continue\n }\n const t = parseInstant(raw)\n if (t === null) return null\n parsed.push(t)\n sawValue = true\n }\n if (!sawValue) return null\n\n const first = parsed.find((t): t is number => t !== null) as number\n let last = first\n return parsed.map((t) => (t === null ? last : (last = t)))\n}\n\n/** `[min, max]` of `values`, or `[0, 1]` when there is nothing to span. */\nfunction extentOf(values: number[]): Range {\n let lo = Infinity\n let hi = -Infinity\n for (const v of values) {\n if (v < lo) lo = v\n if (v > hi) hi = v\n }\n return lo <= hi ? [lo, hi] : [0, 1]\n}\n\n/**\n * Ticks spanning `range`, each one **a sample** — a row the author can point at\n * in their file — placed as near as it can be to where an evenly spaced tick\n * would have gone.\n *\n * Two things have to hold at once, and picking either alone gets it wrong.\n * Ticks must be *samples*, because a caption between two of them has nothing to\n * say (two-thirds of the way through March is not a month, and on a category\n * column it is not anything at all). And they must be *spread*, because the\n * labels sit side by side in a row: taking every nth sample instead puts five\n * dates in the two weeks a burst of rows happens to cover and prints them on\n * top of each other, which is what a column with a gap in it does.\n *\n * Walking the targets rather than the samples is what satisfies both. A run of\n * rows too close together to caption collapses to one tick — consecutive\n * targets pick the same sample and the duplicate is dropped — so a chart asked\n * for five ticks across a gappy column draws four honest ones instead of five\n * overlapping.\n */\nfunction sampledTicks(\n positions: number[],\n [min, max]: Range,\n divisions: number\n): number[] {\n const inside = positions.filter((v) => v >= min && v <= max)\n const unique = [...new Set(inside)].sort((a, b) => a - b)\n if (unique.length === 0) return markerValues([min, max], divisions)\n\n const nearest = (target: number) =>\n unique.reduce((best, v) =>\n Math.abs(v - target) < Math.abs(best - target) ? v : best\n )\n\n const out: number[] = []\n for (const target of markerValues([min, max], divisions)) {\n const tick = nearest(target)\n if (out[out.length - 1] !== tick) out.push(tick)\n }\n return out\n}\n\n/**\n * The caption a tick carries on a sample-based scale: the cell of the row it\n * came from, or `fallback` when the tick isn't a sample after all.\n */\nfunction labelBySample(\n positions: number[],\n captions: string[],\n value: number,\n fallback: (value: number) => string\n): string {\n const i = positions.indexOf(value)\n return i >= 0 ? captions[i] : fallback(value)\n}\n\n/**\n * How `field` positions the rows: the number it holds, the instant it names, or\n * failing both, the row's own place in the file.\n *\n * Resolved from the data on every read rather than cached on the node: it is\n * cheap (one pass to classify, one to position), and a cache keyed on a `data`\n * array that `buildScene` rebuilds on every edit would miss every time anyway.\n */\nexport function resolveXScale(\n data: DataRecord[],\n field: string,\n /**\n * The number-format pattern for a *numeric* column. Ignored by the other two\n * kinds, and correctly so: a tick on those is captioned with the cell the\n * author wrote, and `Jan` has no decimal places.\n */\n pattern?: string\n): XScale {\n if (field !== \"\" && isNumericColumn(data, field)) {\n const positions = data.map((row) => num(row, field))\n return {\n kind: \"numeric\",\n at: (i) => positions[i] ?? 0,\n extent: extentOf(positions),\n label: (value) =>\n pattern === undefined ? formatMarker(value) : formatNumber(value, pattern),\n // Round numbers, evenly spaced — a continuous axis is read by its scale\n // rather than by which samples happen to be on it.\n ticks: markerValues,\n }\n }\n\n const captions = data.map((row) => text(row, field))\n const instants = field === \"\" ? null : instantColumn(data, field)\n\n if (instants) {\n const extent = extentOf(instants)\n const span = extent[1] - extent[0]\n return {\n kind: \"time\",\n at: (i) => instants[i] ?? 0,\n extent,\n label: (value) =>\n labelBySample(instants, captions, value, (v) =>\n formatInstant(v, span)\n ),\n ticks: (range, divisions) => sampledTicks(instants, range, divisions),\n }\n }\n\n const positions = data.map((_, i) => i)\n return {\n kind: \"ordinal\",\n at: (i) => i,\n // `[0, n-1]` rather than the tidy `[0, n]`: the first and last rows sit on\n // the plot's edges, which is where a reader looks for them.\n extent: positions.length > 0 ? [0, positions.length - 1] : [0, 1],\n label: (value) => captions[Math.round(value)] ?? \"\",\n ticks: (range, divisions) => sampledTicks(positions, range, divisions),\n }\n}\n"]}
|
package/dist/nodes.d.ts
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LineChart } from "./line-chart/index.js";
|
|
2
|
+
import { BarChart } from "./bar-chart/index.js";
|
|
3
|
+
import { PieChart } from "./pie-chart/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Every node type this package publishes.
|
|
6
|
+
*
|
|
7
|
+
* A host registers these by handing the array to an engine —
|
|
8
|
+
* `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
|
|
9
|
+
* key. The decorator only *declares* that key: nothing is registered by the mere
|
|
10
|
+
* act of importing a module, so a registry holds exactly what its owner asked
|
|
11
|
+
* for and two engines in one process can differ about it.
|
|
12
|
+
*
|
|
13
|
+
* Listing class **values** is also what survives bundling. This package declares
|
|
14
|
+
* `sideEffects: false`, which licenses a bundler to drop a module nothing
|
|
15
|
+
* imports a value from, and a document names a node type by string — so
|
|
16
|
+
* `import "./line-chart/index.js"` for its side effect is exactly what gets shaken out, where an
|
|
17
|
+
* array of bindings is a data dependency that cannot be.
|
|
18
|
+
*/
|
|
19
|
+
export declare const NODES: (typeof LineChart | typeof BarChart | typeof PieChart)[];
|
|
20
|
+
//# sourceMappingURL=nodes.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodes.d.ts","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,KAAK,0DAAkC,CAAC"}
|
package/dist/nodes.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LineChart } from "./line-chart/index.js";
|
|
2
|
+
import { BarChart } from "./bar-chart/index.js";
|
|
3
|
+
import { PieChart } from "./pie-chart/index.js";
|
|
4
|
+
/**
|
|
5
|
+
* Every node type this package publishes.
|
|
6
|
+
*
|
|
7
|
+
* A host registers these by handing the array to an engine —
|
|
8
|
+
* `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`
|
|
9
|
+
* key. The decorator only *declares* that key: nothing is registered by the mere
|
|
10
|
+
* act of importing a module, so a registry holds exactly what its owner asked
|
|
11
|
+
* for and two engines in one process can differ about it.
|
|
12
|
+
*
|
|
13
|
+
* Listing class **values** is also what survives bundling. This package declares
|
|
14
|
+
* `sideEffects: false`, which licenses a bundler to drop a module nothing
|
|
15
|
+
* imports a value from, and a document names a node type by string — so
|
|
16
|
+
* `import "./line-chart/index.js"` for its side effect is exactly what gets shaken out, where an
|
|
17
|
+
* array of bindings is a data dependency that cannot be.
|
|
18
|
+
*/
|
|
19
|
+
export const NODES = [LineChart, BarChart, PieChart];
|
|
20
|
+
//# sourceMappingURL=nodes.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nodes.js","sourceRoot":"","sources":["../src/nodes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,SAAS,EAAE,QAAQ,EAAE,QAAQ,CAAC,CAAC","sourcesContent":["import { LineChart } from \"./line-chart\";\nimport { BarChart } from \"./bar-chart\";\nimport { PieChart } from \"./pie-chart\";\n\n/**\n * Every node type this package publishes.\n *\n * A host registers these by handing the array to an engine —\n * `new Engine(platform, { nodes: NODES })` — which reads each class's `@node()`\n * key. The decorator only *declares* that key: nothing is registered by the mere\n * act of importing a module, so a registry holds exactly what its owner asked\n * for and two engines in one process can differ about it.\n *\n * Listing class **values** is also what survives bundling. This package declares\n * `sideEffects: false`, which licenses a bundler to drop a module nothing\n * imports a value from, and a document names a node type by string — so\n * `import \"./line-chart\"` for its side effect is exactly what gets shaken out, where an\n * array of bindings is a data dependency that cannot be.\n */\nexport const NODES = [LineChart, BarChart, PieChart];\n"]}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pie chart: the one in the family that draws a mark per **row** rather
|
|
3
|
+
* than per column.
|
|
4
|
+
*
|
|
5
|
+
* So it shares the legend and the theme machinery with its siblings and none of
|
|
6
|
+
* the cartesian geometry. Set the theme's `ring.holeRadius` to 0 for a solid
|
|
7
|
+
* pie; it defaults to a donut, because the hole is the only place a chart has
|
|
8
|
+
* to put its conclusion.
|
|
9
|
+
*/
|
|
10
|
+
export { PieChart } from "./pie-chart.js";
|
|
11
|
+
export type { PieChartProps } from "./pie-chart.js";
|
|
12
|
+
export { PieRing } from "./pie-ring.js";
|
|
13
|
+
export type { PieRingProps } from "./pie-ring.js";
|
|
14
|
+
export { PieLegend } from "./pie-legend.js";
|
|
15
|
+
export type { PieLegendProps } from "./pie-legend.js";
|
|
16
|
+
export { DEFAULT_PIE_CHART_THEME, lerpPieChartTheme, pieArcs, relaxLabels, resolvePieChartTheme, resolvePieSlices, ringPoint, sliceTotal, sliceValue, } from "./shared.js";
|
|
17
|
+
export type { CenterConfig, LabelConfig, PieArc, PieChartConfig, PieChartTheme, PieLabelMode, PieLabelPlacement, PieSlice, RingConfig, } from "./shared.js";
|
|
18
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/pie-chart/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AACtC,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AACpC,YAAY,EAAE,YAAY,EAAE,MAAM,YAAY,CAAA;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,UAAU,CAAA;AACjB,YAAY,EACV,YAAY,EACZ,WAAW,EACX,MAAM,EACN,cAAc,EACd,aAAa,EACb,YAAY,EACZ,iBAAiB,EACjB,QAAQ,EACR,UAAU,GACX,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The pie chart: the one in the family that draws a mark per **row** rather
|
|
3
|
+
* than per column.
|
|
4
|
+
*
|
|
5
|
+
* So it shares the legend and the theme machinery with its siblings and none of
|
|
6
|
+
* the cartesian geometry. Set the theme's `ring.holeRadius` to 0 for a solid
|
|
7
|
+
* pie; it defaults to a donut, because the hole is the only place a chart has
|
|
8
|
+
* to put its conclusion.
|
|
9
|
+
*/
|
|
10
|
+
export { PieChart } from "./pie-chart.js";
|
|
11
|
+
export { PieRing } from "./pie-ring.js";
|
|
12
|
+
export { PieLegend } from "./pie-legend.js";
|
|
13
|
+
export { DEFAULT_PIE_CHART_THEME, lerpPieChartTheme, pieArcs, relaxLabels, resolvePieChartTheme, resolvePieSlices, ringPoint, sliceTotal, sliceValue, } from "./shared.js";
|
|
14
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/pie-chart/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAEtC,OAAO,EAAE,OAAO,EAAE,MAAM,YAAY,CAAA;AAEpC,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AAExC,OAAO,EACL,uBAAuB,EACvB,iBAAiB,EACjB,OAAO,EACP,WAAW,EACX,oBAAoB,EACpB,gBAAgB,EAChB,SAAS,EACT,UAAU,EACV,UAAU,GACX,MAAM,UAAU,CAAA","sourcesContent":["/**\n * The pie chart: the one in the family that draws a mark per **row** rather\n * than per column.\n *\n * So it shares the legend and the theme machinery with its siblings and none of\n * the cartesian geometry. Set the theme's `ring.holeRadius` to 0 for a solid\n * pie; it defaults to a donut, because the hole is the only place a chart has\n * to put its conclusion.\n */\nexport { PieChart } from \"./pie-chart\"\nexport type { PieChartProps } from \"./pie-chart\"\nexport { PieRing } from \"./pie-ring\"\nexport type { PieRingProps } from \"./pie-ring\"\nexport { PieLegend } from \"./pie-legend\"\nexport type { PieLegendProps } from \"./pie-legend\"\nexport {\n DEFAULT_PIE_CHART_THEME,\n lerpPieChartTheme,\n pieArcs,\n relaxLabels,\n resolvePieChartTheme,\n resolvePieSlices,\n ringPoint,\n sliceTotal,\n sliceValue,\n} from \"./shared\"\nexport type {\n CenterConfig,\n LabelConfig,\n PieArc,\n PieChartConfig,\n PieChartTheme,\n PieLabelMode,\n PieLabelPlacement,\n PieSlice,\n RingConfig,\n} from \"./shared\"\n"]}
|