@jarenjs/charts 0.34.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/README.md +293 -0
- package/dist/types/component/index.d.ts +95 -0
- package/dist/types/core/axis.d.ts +77 -0
- package/dist/types/core/cartesian.d.ts +127 -0
- package/dist/types/core/chart.d.ts +58 -0
- package/dist/types/core/domain.d.ts +96 -0
- package/dist/types/core/marks.d.ts +86 -0
- package/dist/types/core/palette.d.ts +72 -0
- package/dist/types/core/scale.d.ts +59 -0
- package/dist/types/core/session.d.ts +85 -0
- package/dist/types/core/stream-adapter.d.ts +187 -0
- package/dist/types/index.d.ts +35 -0
- package/dist/types/transforms/benchmark-adapter.d.ts +169 -0
- package/dist/types/transforms/mermaid-adapter.d.ts +30 -0
- package/dist/types/types/bar.d.ts +213 -0
- package/dist/types/types/boxplot.d.ts +116 -0
- package/dist/types/types/candlestick.d.ts +218 -0
- package/dist/types/types/gauge.d.ts +68 -0
- package/dist/types/types/heatmap.d.ts +104 -0
- package/dist/types/types/line.d.ts +272 -0
- package/dist/types/types/map.d.ts +137 -0
- package/dist/types/types/pie.d.ts +146 -0
- package/dist/types/types/radar.d.ts +89 -0
- package/dist/types/types/sankey.d.ts +100 -0
- package/dist/types/types/scatter.d.ts +80 -0
- package/dist/types/types/streamgraph.d.ts +75 -0
- package/dist/types/types/treemap.d.ts +118 -0
- package/package.json +76 -0
- package/schemas/chart-definition.schema.json +448 -0
- package/src/component/index.js +125 -0
- package/src/core/axis.js +221 -0
- package/src/core/cartesian.js +192 -0
- package/src/core/chart.js +101 -0
- package/src/core/domain.js +123 -0
- package/src/core/marks.js +110 -0
- package/src/core/palette.js +126 -0
- package/src/core/scale.js +106 -0
- package/src/core/session.js +0 -0
- package/src/core/stream-adapter.js +613 -0
- package/src/index.js +40 -0
- package/src/transforms/benchmark-adapter.js +298 -0
- package/src/transforms/mermaid-adapter.js +19 -0
- package/src/types/bar.js +276 -0
- package/src/types/boxplot.js +216 -0
- package/src/types/candlestick.js +274 -0
- package/src/types/gauge.js +140 -0
- package/src/types/heatmap.js +176 -0
- package/src/types/line.js +349 -0
- package/src/types/map.js +378 -0
- package/src/types/pie.js +163 -0
- package/src/types/radar.js +224 -0
- package/src/types/sankey.js +391 -0
- package/src/types/scatter.js +148 -0
- package/src/types/streamgraph.js +158 -0
- package/src/types/treemap.js +322 -0
- package/styles/charts.css +83 -0
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Domain-stability policies for the streaming chart types. A
|
|
3
|
+
* unit-space AST stores every position as a fraction of its domain, so
|
|
4
|
+
* a tick that moves the domain legitimately changes every mark — a
|
|
5
|
+
* wholesale re-render is then correct. These policies exist so that
|
|
6
|
+
* most streaming ticks do NOT move the domain, which is what makes
|
|
7
|
+
* incremental re-render (the chart session) possible:
|
|
8
|
+
*
|
|
9
|
+
* - `x: { window, slide? }` — sliding x window of fixed span `window`,
|
|
10
|
+
* whose END is quantized to multiples of `slide` (default a quarter
|
|
11
|
+
* window). The domain therefore moves once per `slide` of x
|
|
12
|
+
* progress, not once per sample; samples older than the window are
|
|
13
|
+
* dropped from the plot.
|
|
14
|
+
* - `y: { min?, max? }` — pinned value bounds; samples beyond them
|
|
15
|
+
* clamp to the plot edge (the render's clamp01 already does this).
|
|
16
|
+
* - `y: 'step'` — hysteresis by quantization: the value domain snaps
|
|
17
|
+
* outward to nice-number multiples (decades under a log axis), so it
|
|
18
|
+
* changes only when a sample crosses a step boundary.
|
|
19
|
+
*
|
|
20
|
+
* All resolution is pure — policies are declared in the config and
|
|
21
|
+
* resolved from the data extremes on every build; stability comes from
|
|
22
|
+
* quantization, not hidden state.
|
|
23
|
+
*/
|
|
24
|
+
export type DomainPolicy = {
|
|
25
|
+
/**
|
|
26
|
+
* sliding x span (null = none)
|
|
27
|
+
*/
|
|
28
|
+
window: number | null;
|
|
29
|
+
/**
|
|
30
|
+
* window end quantum (null = window/4)
|
|
31
|
+
*/
|
|
32
|
+
slide: number | null;
|
|
33
|
+
/**
|
|
34
|
+
* pinned y bounds
|
|
35
|
+
*/
|
|
36
|
+
pin: {
|
|
37
|
+
min: number | null;
|
|
38
|
+
max: number | null;
|
|
39
|
+
} | null;
|
|
40
|
+
/**
|
|
41
|
+
* quantized y domain
|
|
42
|
+
*/
|
|
43
|
+
step: boolean;
|
|
44
|
+
};
|
|
45
|
+
/**
|
|
46
|
+
* @typedef {object} DomainPolicy
|
|
47
|
+
* @property {number|null} window sliding x span (null = none)
|
|
48
|
+
* @property {number|null} slide window end quantum (null = window/4)
|
|
49
|
+
* @property {{min: number|null, max: number|null}|null} pin pinned y bounds
|
|
50
|
+
* @property {boolean} step quantized y domain
|
|
51
|
+
*/
|
|
52
|
+
/**
|
|
53
|
+
* Normalize a config `domain` member into a policy object; hostile or
|
|
54
|
+
* absent input yields the all-null policy (today's behavior).
|
|
55
|
+
* @param {any} domain the config `domain` member
|
|
56
|
+
* @returns {DomainPolicy}
|
|
57
|
+
*/
|
|
58
|
+
export declare function normalizeDomainPolicy(domain: any): DomainPolicy;
|
|
59
|
+
/**
|
|
60
|
+
* Resolve the windowed x domain `[end - window, end]`: the end is the
|
|
61
|
+
* smallest multiple of the slide quantum at or above the newest sample,
|
|
62
|
+
* so it moves once per quantum, not once per sample.
|
|
63
|
+
* @param {number} xMax newest x in the data (non-finite = empty data)
|
|
64
|
+
* @param {number} window the window span (> 0)
|
|
65
|
+
* @param {number|null} slide the end quantum (null = window / 4)
|
|
66
|
+
* @returns {[number, number]}
|
|
67
|
+
*/
|
|
68
|
+
export declare function resolveWindowX(xMax: number, window: number, slide: number | null): [number, number];
|
|
69
|
+
/**
|
|
70
|
+
* Resolve the quantized (`'step'`) y domain: extremes snapped outward
|
|
71
|
+
* to multiples of a nice step of the span, so small new extremes
|
|
72
|
+
* usually land inside the current domain.
|
|
73
|
+
* @param {number} yMin @param {number} yMax data extremes (finite)
|
|
74
|
+
* @returns {[number, number]}
|
|
75
|
+
*/
|
|
76
|
+
export declare function resolveStepY(yMin: number, yMax: number): [number, number];
|
|
77
|
+
/**
|
|
78
|
+
* Resolve the quantized y domain under a log axis: decade bounds
|
|
79
|
+
* (`10^floor` / `10^ceil`), the log counterpart of {@link resolveStepY}.
|
|
80
|
+
* @param {number} yMin @param {number} yMax data extremes (> 0)
|
|
81
|
+
* @returns {[number, number]}
|
|
82
|
+
*/
|
|
83
|
+
export declare function resolveStepYLog(yMin: number, yMax: number): [number, number];
|
|
84
|
+
/**
|
|
85
|
+
* Resolve pinned y bounds over the data extremes. Under a log axis a
|
|
86
|
+
* non-positive pin is ignored (a hostile pin never breaks the scale);
|
|
87
|
+
* a pin pair that closes the domain falls back to the data extremes.
|
|
88
|
+
* @param {number} yMin @param {number} yMax data extremes
|
|
89
|
+
* @param {{min: number|null, max: number|null}} pin
|
|
90
|
+
* @param {boolean} log
|
|
91
|
+
* @returns {[number, number]}
|
|
92
|
+
*/
|
|
93
|
+
export declare function resolvePinnedY(yMin: number, yMax: number, pin: {
|
|
94
|
+
min: number | null;
|
|
95
|
+
max: number | null;
|
|
96
|
+
}, log: boolean): [number, number];
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Value marks — the two things every value-carrying mark carries.
|
|
3
|
+
*
|
|
4
|
+
* 1. A `<title>` child holding the mark's hover text. Native SSR-safe
|
|
5
|
+
* hover: it works in a static `toSvgString()` document with no
|
|
6
|
+
* script, no CSS and no app, which is why it is unconditional.
|
|
7
|
+
* 2. Opt-in pointer **bindings** (`on`, VIEW-FORMAT §4) for a host that
|
|
8
|
+
* wants a positioned floating tooltip instead. Bindings are plain
|
|
9
|
+
* JSON built at render time, so the engine stays pure — it names an
|
|
10
|
+
* action, it never calls one — and `renderToString` drops `on`
|
|
11
|
+
* entirely, so the SSR bytes are the same either way.
|
|
12
|
+
*
|
|
13
|
+
* The binding's `with` payload is the mark descriptor: its `text` (the
|
|
14
|
+
* same string the `<title>` carries, so a host needs nothing else to
|
|
15
|
+
* draw a box) plus whatever identifies the mark for a richer host.
|
|
16
|
+
*/
|
|
17
|
+
export type ChartTooltipSpec = {
|
|
18
|
+
/**
|
|
19
|
+
* action dispatched when the pointer enters a mark
|
|
20
|
+
*/
|
|
21
|
+
action: string;
|
|
22
|
+
/**
|
|
23
|
+
* action dispatched when it leaves
|
|
24
|
+
*/
|
|
25
|
+
leaveAction?: string;
|
|
26
|
+
/**
|
|
27
|
+
* enter event name (default `'pointerenter'`)
|
|
28
|
+
*/
|
|
29
|
+
enter?: string;
|
|
30
|
+
/**
|
|
31
|
+
* leave event name (default `'pointerleave'`)
|
|
32
|
+
*/
|
|
33
|
+
leave?: string;
|
|
34
|
+
/**
|
|
35
|
+
* `$event` fields to request (default
|
|
36
|
+
* `['clientX', 'clientY']` — what a floating box needs to position itself)
|
|
37
|
+
*/
|
|
38
|
+
event?: string[];
|
|
39
|
+
};
|
|
40
|
+
export type ChartTooltip = {
|
|
41
|
+
action: string;
|
|
42
|
+
leaveAction: string | null;
|
|
43
|
+
enter: string;
|
|
44
|
+
leave: string;
|
|
45
|
+
event: string[];
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Resolve a tooltip spec. Anything that does not name an action
|
|
49
|
+
* resolves to `null` — bindings off, `<title>` hover only — so a
|
|
50
|
+
* hostile or half-written spec degrades to the static rendering rather
|
|
51
|
+
* than emitting a binding no action answers.
|
|
52
|
+
* @param {ChartTooltipSpec|null|undefined|any} spec
|
|
53
|
+
* @returns {ChartTooltip|null}
|
|
54
|
+
*/
|
|
55
|
+
export declare function normalizeTooltip(spec: ChartTooltipSpec | null | undefined | any): ChartTooltip | null;
|
|
56
|
+
/**
|
|
57
|
+
* The `on` binding object for one mark, or `undefined` when tooltips
|
|
58
|
+
* are off (so callers can keep passing their props object through
|
|
59
|
+
* untouched — an unbound chart allocates nothing extra).
|
|
60
|
+
* @param {ChartTooltip|null} tooltip
|
|
61
|
+
* @param {string} text the mark's hover text
|
|
62
|
+
* @param {Record<string, any>} [descriptor] extra `with` members
|
|
63
|
+
* @returns {Record<string, any>|undefined}
|
|
64
|
+
*/
|
|
65
|
+
export declare function markBinding(tooltip: ChartTooltip | null, text: string, descriptor?: Record<string, any>): Record<string, any> | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Mark props with the tooltip bindings folded in — for a mark whose
|
|
68
|
+
* `<title>` sits among other children (a series or candle `<g>`).
|
|
69
|
+
* @param {Record<string, any>} props
|
|
70
|
+
* @param {ChartTooltip|null} tooltip
|
|
71
|
+
* @param {string} text
|
|
72
|
+
* @param {Record<string, any>} [descriptor]
|
|
73
|
+
* @returns {Record<string, any>}
|
|
74
|
+
*/
|
|
75
|
+
export declare function markProps(props: Record<string, any>, tooltip: ChartTooltip | null, text: string, descriptor?: Record<string, any>): Record<string, any>;
|
|
76
|
+
/**
|
|
77
|
+
* A leaf value mark: its element, its hover `<title>`, and the tooltip
|
|
78
|
+
* bindings when a host asked for them.
|
|
79
|
+
* @param {string} tag
|
|
80
|
+
* @param {Record<string, any>} props
|
|
81
|
+
* @param {ChartTooltip|null} tooltip
|
|
82
|
+
* @param {string} text hover text
|
|
83
|
+
* @param {Record<string, any>} [descriptor] extra `with` members
|
|
84
|
+
* @returns {any} the mark vnode
|
|
85
|
+
*/
|
|
86
|
+
export declare function valueMark(tag: string, props: Record<string, any>, tooltip: ChartTooltip | null, text: string, descriptor?: Record<string, any>): any;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Colors: the categorical palette and the theme token tables.
|
|
3
|
+
*
|
|
4
|
+
* The categorical palette is a concrete constant, not a set of theme
|
|
5
|
+
* tokens (docs/DESIGN.md §8), in the suite's anchor order — blue first,
|
|
6
|
+
* amber second, teal third, then green/red/navy/olive/slate — with no
|
|
7
|
+
* pink and no purple anywhere. It is the same palette the mermaid pie
|
|
8
|
+
* used before it delegated here, so a pie renders byte-identically
|
|
9
|
+
* through either package.
|
|
10
|
+
*
|
|
11
|
+
* The semantic tokens (text/grid/axis, the win/loss pair) resolve
|
|
12
|
+
* through the shared `resolveTheme` kernel with the `chart` prefix; the
|
|
13
|
+
* `'host'` theme links them to the site token vocabulary (docs/DESIGN.md §2,
|
|
14
|
+
* §7) so charts follow the host's light/dark flip live with no
|
|
15
|
+
* re-render. Sync invariant: the `default`/`dark` values below must
|
|
16
|
+
* match the `--chart-*` fallbacks in `styles/charts.css`.
|
|
17
|
+
*/
|
|
18
|
+
import { inkFor } from '@jarenjs/core/color';
|
|
19
|
+
export { inkFor };
|
|
20
|
+
/**
|
|
21
|
+
* The categorical series palette (docs/DESIGN.md §8 anchor order).
|
|
22
|
+
* @type {readonly string[]}
|
|
23
|
+
*/
|
|
24
|
+
export declare const CATEGORICAL: readonly string[];
|
|
25
|
+
/**
|
|
26
|
+
* Ordinal color assignment: series `i` gets the `i`-th palette entry,
|
|
27
|
+
* wrapping.
|
|
28
|
+
* @param {number} i - Series index
|
|
29
|
+
* @param {readonly string[]} [palette] - Palette to draw from
|
|
30
|
+
* @returns {string}
|
|
31
|
+
*/
|
|
32
|
+
export declare function seriesColor(i: number, palette?: readonly string[]): string;
|
|
33
|
+
/**
|
|
34
|
+
* The sequential magnitude ramp: one hue (the brand blue family),
|
|
35
|
+
* light→dark, for value-carrying fills (heatmap cells). A concrete
|
|
36
|
+
* constant like {@link CATEGORICAL}, not a theme token. The stops keep
|
|
37
|
+
* monotone perceptual lightness with visible step gaps, and both ends
|
|
38
|
+
* stay legible against the light and the dark site surface (the ramp
|
|
39
|
+
* does not flip with the theme).
|
|
40
|
+
* @type {readonly string[]}
|
|
41
|
+
*/
|
|
42
|
+
export declare const SEQUENTIAL: readonly string[];
|
|
43
|
+
/**
|
|
44
|
+
* Continuous color for a normalized magnitude: `t` in [0,1] maps onto
|
|
45
|
+
* the ramp by piecewise-linear interpolation between its stops
|
|
46
|
+
* (clamped; non-finite `t` reads as 0).
|
|
47
|
+
* @param {number} t - Normalized magnitude (0 = low, 1 = high)
|
|
48
|
+
* @param {readonly string[]} [ramp] - Ramp stops, light→dark
|
|
49
|
+
* @returns {string} a `#rrggbb` color
|
|
50
|
+
*/
|
|
51
|
+
export declare function sequentialColor(t: number, ramp?: readonly string[]): string;
|
|
52
|
+
/** @type {Record<string, Record<string, string>>} */
|
|
53
|
+
declare const THEMES: Record<string, Record<string, string>>;
|
|
54
|
+
/**
|
|
55
|
+
* Host custom-property links for the `'host'` theme: token key → the
|
|
56
|
+
* site token it follows (docs/DESIGN.md §2). Concrete defaults remain as
|
|
57
|
+
* `var()` fallbacks, so the same SVG stays standalone-valid.
|
|
58
|
+
* @type {Record<string, string>}
|
|
59
|
+
*/
|
|
60
|
+
export declare const HOST_VARS: Record<string, string>;
|
|
61
|
+
/**
|
|
62
|
+
* Resolve a chart theme. The name `'host'` resolves the default tokens
|
|
63
|
+
* linked to the host token vocabulary via {@link HOST_VARS}.
|
|
64
|
+
* @param {string | Record<string, any>} [nameOrOverrides]
|
|
65
|
+
* @returns {{ name: string, tokens: Record<string, string>, cssVars: Record<string, string> }}
|
|
66
|
+
*/
|
|
67
|
+
export declare function createTheme(nameOrOverrides?: string | Record<string, any>): {
|
|
68
|
+
name: string;
|
|
69
|
+
tokens: Record<string, string>;
|
|
70
|
+
cssVars: Record<string, string>;
|
|
71
|
+
};
|
|
72
|
+
export { THEMES };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Scales: pure `domain -> (value) => [0,1]` closures. All pixel
|
|
3
|
+
* mapping happens in the render pass; the AST and these scales stay in
|
|
4
|
+
* abstract unit space (the geometry-free contract). Construction may
|
|
5
|
+
* allocate (lookup maps); the returned functions never do.
|
|
6
|
+
*
|
|
7
|
+
* Out-of-domain inputs map outside [0,1] (linear/log/time) or to `NaN`
|
|
8
|
+
* (unknown ordinal/band categories); `polylinePath` and `num` in
|
|
9
|
+
* `@jarenjs/view/helpers` are the render-side guards that keep a NaN
|
|
10
|
+
* from ever reaching an emitted string.
|
|
11
|
+
*/
|
|
12
|
+
export type UnitScale = (value: number) => number;
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {(value: number) => number} UnitScale
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Linear scale. A zero-span domain maps every value to 0.5.
|
|
18
|
+
* @param {number} min - Domain minimum
|
|
19
|
+
* @param {number} max - Domain maximum
|
|
20
|
+
* @returns {UnitScale}
|
|
21
|
+
*/
|
|
22
|
+
export declare function scaleLinear(min: number, max: number): UnitScale;
|
|
23
|
+
/**
|
|
24
|
+
* Logarithmic scale (base 10). The domain must be strictly positive —
|
|
25
|
+
* that is checked once here, not per call; a non-positive *value* maps
|
|
26
|
+
* to NaN (log of a non-positive number), which the render guards drop.
|
|
27
|
+
* @param {number} min - Domain minimum (> 0)
|
|
28
|
+
* @param {number} max - Domain maximum (> 0)
|
|
29
|
+
* @returns {UnitScale}
|
|
30
|
+
*/
|
|
31
|
+
export declare function scaleLog(min: number, max: number): UnitScale;
|
|
32
|
+
/**
|
|
33
|
+
* Ordinal scale: each category maps to the center of its equal slot,
|
|
34
|
+
* `(i + 0.5) / n`. Unknown categories map to NaN.
|
|
35
|
+
* @param {readonly string[]} categories - Domain, in display order
|
|
36
|
+
* @returns {(category: string) => number}
|
|
37
|
+
*/
|
|
38
|
+
export declare function scaleOrdinal(categories: readonly string[]): (category: string) => number;
|
|
39
|
+
/**
|
|
40
|
+
* Band scale: each category owns an equal band with inner padding. The
|
|
41
|
+
* returned function gives the band's start; `bandwidth` and `step` are
|
|
42
|
+
* exposed as properties. Unknown categories map to NaN.
|
|
43
|
+
* @param {readonly string[]} categories - Domain, in display order
|
|
44
|
+
* @param {number} [padding] - Fraction of each step left empty (0..1)
|
|
45
|
+
* @returns {((category: string) => number) & {bandwidth: number, step: number}}
|
|
46
|
+
*/
|
|
47
|
+
export declare function scaleBand(categories: readonly string[], padding?: number): ((category: string) => number) & {
|
|
48
|
+
bandwidth: number;
|
|
49
|
+
step: number;
|
|
50
|
+
};
|
|
51
|
+
/**
|
|
52
|
+
* Time scale: a linear scale over epoch milliseconds that also accepts
|
|
53
|
+
* `Date` instances (converted once per call via `getTime`, no
|
|
54
|
+
* allocation).
|
|
55
|
+
* @param {number|Date} min - Domain minimum
|
|
56
|
+
* @param {number|Date} max - Domain maximum
|
|
57
|
+
* @returns {(value: number|Date) => number}
|
|
58
|
+
*/
|
|
59
|
+
export declare function scaleTime(min: number | Date, max: number | Date): (value: number | Date) => number;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The incremental chart session: a stateful compile pipeline for
|
|
3
|
+
* the streaming chart types that consumes a change-reporting source
|
|
4
|
+
* (`createStreamAdapter` with `{ changes: true }`, or anything with
|
|
5
|
+
* the same `takeChanges()`/`getData()` pair) and produces the next
|
|
6
|
+
* vnode tree in O(changed marks) work — WHEN the frame is "still".
|
|
7
|
+
*
|
|
8
|
+
* The stillness test is exact, not heuristic: the session re-resolves
|
|
9
|
+
* the scale domains from the updated extremes with the SAME exported
|
|
10
|
+
* helpers the wholesale build uses, and compares against the AST's
|
|
11
|
+
* recorded domain. Equal domains ⇒ every existing mark's position is
|
|
12
|
+
* unchanged, so only touched series re-render (untouched children
|
|
13
|
+
* keep their references — the view patcher skips them in O(1)).
|
|
14
|
+
* Anything else — a moved domain, a new series, a reset — falls back
|
|
15
|
+
* to a wholesale rebuild, which is the correct rendering of a frame
|
|
16
|
+
* whose scales moved, not a failure mode. Domain-stability policies
|
|
17
|
+
* (`config.domain`, `core/domain.js`) exist to make most ticks still.
|
|
18
|
+
*
|
|
19
|
+
* The correctness contract is byte equality: after every `tick()`,
|
|
20
|
+
* serializing the session's vnode equals serializing a wholesale
|
|
21
|
+
* `compileChart(config, source.getData())` of the same data —
|
|
22
|
+
* property-tested, never assumed.
|
|
23
|
+
*/
|
|
24
|
+
export type ChartSessionSource = {
|
|
25
|
+
takeChanges: () => {
|
|
26
|
+
op: string;
|
|
27
|
+
path: string;
|
|
28
|
+
value?: any;
|
|
29
|
+
}[];
|
|
30
|
+
getData: () => any;
|
|
31
|
+
};
|
|
32
|
+
export type ChartSessionResult = {
|
|
33
|
+
/**
|
|
34
|
+
* the current svg vnode (reference-stable when unchanged)
|
|
35
|
+
*/
|
|
36
|
+
vnode: any;
|
|
37
|
+
/**
|
|
38
|
+
* which path produced it
|
|
39
|
+
*/
|
|
40
|
+
mode: 'unchanged' | 'incremental' | 'rebuilt';
|
|
41
|
+
};
|
|
42
|
+
export type ChartSession = {
|
|
43
|
+
/**
|
|
44
|
+
* drain the source and produce the next frame
|
|
45
|
+
*/
|
|
46
|
+
tick: () => ChartSessionResult;
|
|
47
|
+
};
|
|
48
|
+
/**
|
|
49
|
+
* @typedef {object} ChartSessionSource
|
|
50
|
+
* @property {() => {op: string, path: string, value?: any}[]} takeChanges
|
|
51
|
+
* @property {() => any} getData
|
|
52
|
+
*/
|
|
53
|
+
/**
|
|
54
|
+
* @typedef {object} ChartSessionResult
|
|
55
|
+
* @property {any} vnode the current svg vnode (reference-stable when unchanged)
|
|
56
|
+
* @property {'unchanged'|'incremental'|'rebuilt'} mode which path produced it
|
|
57
|
+
*/
|
|
58
|
+
/**
|
|
59
|
+
* @typedef {object} ChartSession
|
|
60
|
+
* @property {() => ChartSessionResult} tick drain the source and produce the next frame
|
|
61
|
+
*/
|
|
62
|
+
/**
|
|
63
|
+
* Create an incremental session for a streaming chart. Supported
|
|
64
|
+
* `config.type`: `'line'` (append/evict traffic), `'bar'` (live counts
|
|
65
|
+
* and sums) and `'candlestick'` (keyed kline upserts). The config is
|
|
66
|
+
* treated as immutable for the session's lifetime.
|
|
67
|
+
* @param {{type: string, [k: string]: any}} config
|
|
68
|
+
* @param {ChartSessionSource} source
|
|
69
|
+
* @param {{theme?: any, palette?: readonly string[], width?: number,
|
|
70
|
+
* rootClass?: string, keyPrefix?: string,
|
|
71
|
+
* tooltip?: import('./marks.js').ChartTooltipSpec}} [options]
|
|
72
|
+
* @returns {ChartSession}
|
|
73
|
+
* @throws {TypeError} On an unsupported chart type
|
|
74
|
+
*/
|
|
75
|
+
export declare function createChartSession(config: {
|
|
76
|
+
type: string;
|
|
77
|
+
[k: string]: any;
|
|
78
|
+
}, source: ChartSessionSource, options?: {
|
|
79
|
+
theme?: any;
|
|
80
|
+
palette?: readonly string[];
|
|
81
|
+
width?: number;
|
|
82
|
+
rootClass?: string;
|
|
83
|
+
keyPrefix?: string;
|
|
84
|
+
tooltip?: import('./marks.js').ChartTooltipSpec;
|
|
85
|
+
}): ChartSession;
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file The streaming adapter: unified reader events in, chart data
|
|
3
|
+
* out. Source-agnostic by design — it consumes the `pair` event shape
|
|
4
|
+
* that the JOSL reader (`createStreamReader`) and the JSONX/strict-JSON
|
|
5
|
+
* reader (`createJsonxStreamReader`) both emit, so one adapter serves
|
|
6
|
+
* every syntax and this package never depends on a parser (it sees
|
|
7
|
+
* events, never a reader; pair it with `@jarenjs/josl` at the call
|
|
8
|
+
* site).
|
|
9
|
+
*
|
|
10
|
+
* Two record-boundary modes cover the two streaming shapes:
|
|
11
|
+
*
|
|
12
|
+
* - `recordBoundary: 'path'` — one large document arriving in chunks;
|
|
13
|
+
* a record is the subtree at `recordPath + [index]` (a JOSL `[[run]]`
|
|
14
|
+
* array-of-tables and a JSON `{"run": […]}` array produce the same
|
|
15
|
+
* pair paths — that is the point of the unified events), and closes
|
|
16
|
+
* when the event path leaves it.
|
|
17
|
+
* - `recordBoundary: 'document'` — many small complete documents over
|
|
18
|
+
* time (e.g. one WebSocket message each); every document IS one
|
|
19
|
+
* record — the fields directly under `recordPath` (default: the
|
|
20
|
+
* document root) — closed by an `endDocument()` call after the
|
|
21
|
+
* reader's `end()`. A message that fails mid-parse is discarded with
|
|
22
|
+
* `abortDocument()`, leaving the accumulated snapshot untouched.
|
|
23
|
+
*
|
|
24
|
+
* Accumulators: `line` (records → per-series points, ring-buffer
|
|
25
|
+
* eviction), `bar` (live category counts or sums), `heatmap` (the same
|
|
26
|
+
* counts or sums under TWO grouping keys — the column is `xField`, the
|
|
27
|
+
* row `seriesField`), `gauge` (the latest reading, nothing kept),
|
|
28
|
+
* `candlestick` (records keyed by open time; a re-delivered key
|
|
29
|
+
* REPLACES its candle, which is exactly how exchange kline updates
|
|
30
|
+
* behave), and `map` (whole GeoJSON Features, projected and simplified
|
|
31
|
+
* on arrival — see below).
|
|
32
|
+
*
|
|
33
|
+
* The `map` accumulator is the odd one out in two ways. Its record is
|
|
34
|
+
* not flat pair fields but a complete Feature, so it consumes the
|
|
35
|
+
* reader's `object-end` events at `recordPath + [index]` (default
|
|
36
|
+
* `['features']`) — pair the reader with `detach: ['features', '*']` so
|
|
37
|
+
* the document root keeps nothing and the accumulator is the only
|
|
38
|
+
* retention. And what it keeps is *reduced*: each feature's geometry is
|
|
39
|
+
* simplified on arrival to the vertices a drawing of the current extent
|
|
40
|
+
* could distinguish, so memory is bounded by the drawn detail, not the
|
|
41
|
+
* source detail. The projection cannot be fitted before the last
|
|
42
|
+
* feature has been seen, so the design is refit-on-growth: the running
|
|
43
|
+
* bbox sets the simplification tolerance, and when it grows enough to
|
|
44
|
+
* double the tolerance, the kept features are re-simplified against the
|
|
45
|
+
* new extent (a coarsening of already-kept vertices — never a re-read
|
|
46
|
+
* of dropped ones, which is why early features can only ever be finer
|
|
47
|
+
* than needed, not wrong).
|
|
48
|
+
*
|
|
49
|
+
* A record is emitted into the snapshot only when its required fields
|
|
50
|
+
* are present; `getData()` returns a FRESH object shaped for
|
|
51
|
+
* `compileChart(config, adapter.getData())`, so identity-keyed memos
|
|
52
|
+
* re-render per snapshot.
|
|
53
|
+
*
|
|
54
|
+
* Change reporting (`{ changes: true }`): every snapshot mutation is
|
|
55
|
+
* also buffered as an RFC 6902 operation against the `getData()`
|
|
56
|
+
* shape, collected with `takeChanges()` — the feed the incremental
|
|
57
|
+
* chart session consumes. The contract is replay equivalence: applying
|
|
58
|
+
* a `takeChanges()` batch to the previous snapshot yields exactly the
|
|
59
|
+
* next one (`reset()` buffers a whole-document replace). The ops are
|
|
60
|
+
* plain data; this module never imports a patch applier.
|
|
61
|
+
*/
|
|
62
|
+
export type StreamAdapterConfig = {
|
|
63
|
+
/**
|
|
64
|
+
* path prefix owning the
|
|
65
|
+
* records: in path mode, e.g. `['run']` for `{"run": […]}` / `[[run]]`;
|
|
66
|
+
* in document mode, the object whose direct fields form the record
|
|
67
|
+
* (e.g. `['data', 'k']` for a combined-stream kline payload)
|
|
68
|
+
*/
|
|
69
|
+
recordPath?: (string | number)[];
|
|
70
|
+
/**
|
|
71
|
+
* default 'path'
|
|
72
|
+
*/
|
|
73
|
+
recordBoundary?: 'path' | 'document';
|
|
74
|
+
/**
|
|
75
|
+
* record field for x (line/candlestick) or
|
|
76
|
+
* the category (bar) / column (heatmap)
|
|
77
|
+
*/
|
|
78
|
+
xField?: string;
|
|
79
|
+
/**
|
|
80
|
+
* record field for y (line), the summed
|
|
81
|
+
* value (bar/heatmap; omitted = count records), or the reading (gauge)
|
|
82
|
+
*/
|
|
83
|
+
yField?: string;
|
|
84
|
+
/**
|
|
85
|
+
* record field naming the series
|
|
86
|
+
* (line) or the row (heatmap)
|
|
87
|
+
*/
|
|
88
|
+
seriesField?: string;
|
|
89
|
+
/**
|
|
90
|
+
* candlestick fields (defaults
|
|
91
|
+
* 'open'/'high'/'low'/'close')
|
|
92
|
+
*/
|
|
93
|
+
openField?: string;
|
|
94
|
+
highField?: string;
|
|
95
|
+
lowField?: string;
|
|
96
|
+
closeField?: string;
|
|
97
|
+
/**
|
|
98
|
+
* ring-buffer size (line: per series;
|
|
99
|
+
* candlestick: total candles)
|
|
100
|
+
*/
|
|
101
|
+
maxPoints?: number;
|
|
102
|
+
/**
|
|
103
|
+
* buffer RFC 6902 ops per snapshot
|
|
104
|
+
* mutation for {@link StreamAdapter#takeChanges}
|
|
105
|
+
*/
|
|
106
|
+
changes?: boolean;
|
|
107
|
+
/**
|
|
108
|
+
* map: the feature property naming a
|
|
109
|
+
* feature (default 'name')
|
|
110
|
+
*/
|
|
111
|
+
labelField?: string;
|
|
112
|
+
/**
|
|
113
|
+
* map: the feature property to shade by
|
|
114
|
+
*/
|
|
115
|
+
valueField?: string;
|
|
116
|
+
/**
|
|
117
|
+
* map: simplification tolerance as a
|
|
118
|
+
* fraction of the frame's width (default the map chart's own; `false`
|
|
119
|
+
* keeps every vertex, which unbounds memory)
|
|
120
|
+
*/
|
|
121
|
+
simplify?: number | false;
|
|
122
|
+
/**
|
|
123
|
+
* map: frame width:height ratio the drawing
|
|
124
|
+
* will use (default 1.6, the map chart's own)
|
|
125
|
+
*/
|
|
126
|
+
aspect?: number;
|
|
127
|
+
};
|
|
128
|
+
export type StreamAdapter = {
|
|
129
|
+
/**
|
|
130
|
+
* the reader event sink
|
|
131
|
+
*/
|
|
132
|
+
onEvent: (event: any) => void;
|
|
133
|
+
/**
|
|
134
|
+
* close the current record
|
|
135
|
+
* (document mode: call once per completed document; path mode: call
|
|
136
|
+
* once at end of stream)
|
|
137
|
+
*/
|
|
138
|
+
endDocument: () => void;
|
|
139
|
+
/**
|
|
140
|
+
* discard the record in progress
|
|
141
|
+
* (a message that failed to parse)
|
|
142
|
+
*/
|
|
143
|
+
abortDocument: () => void;
|
|
144
|
+
/**
|
|
145
|
+
* a fresh chart-data snapshot
|
|
146
|
+
*/
|
|
147
|
+
getData: () => any;
|
|
148
|
+
/**
|
|
149
|
+
* drain the buffered ops since the last call (requires
|
|
150
|
+
* `{ changes: true }`; throws a TypeError otherwise)
|
|
151
|
+
*/
|
|
152
|
+
takeChanges: () => {
|
|
153
|
+
op: string;
|
|
154
|
+
path: string;
|
|
155
|
+
value?: any;
|
|
156
|
+
}[];
|
|
157
|
+
/**
|
|
158
|
+
* drop all accumulated state
|
|
159
|
+
*/
|
|
160
|
+
reset: () => void;
|
|
161
|
+
};
|
|
162
|
+
/**
|
|
163
|
+
* Create a streaming accumulator for a chart type.
|
|
164
|
+
* @param {'line'|'bar'|'heatmap'|'gauge'|'candlestick'|'map'} chartType
|
|
165
|
+
* @param {StreamAdapterConfig} [config]
|
|
166
|
+
* @returns {StreamAdapter}
|
|
167
|
+
*/
|
|
168
|
+
export declare function createStreamAdapter(chartType: 'line' | 'bar' | 'heatmap' | 'gauge' | 'candlestick' | 'map', config?: StreamAdapterConfig): StreamAdapter;
|
|
169
|
+
/**
|
|
170
|
+
* Lift a temporal coordinate to its epoch-millisecond number, passing every
|
|
171
|
+
* other value through untouched so a downstream `Number.isFinite` still
|
|
172
|
+
* decides what is plottable.
|
|
173
|
+
*
|
|
174
|
+
* A `Date` and an **RFC 3339 string** both lift, because both say
|
|
175
|
+
* unambiguously that they are an instant — which is what a time axis asked
|
|
176
|
+
* for, and JSON has no other way to spell one. A *numeric* string still does
|
|
177
|
+
* not: unlike `numish`, this refuses to accept `"5"` where the config asked
|
|
178
|
+
* for a number, because that is a type confusion rather than a date.
|
|
179
|
+
*
|
|
180
|
+
* A date with no time (`2026-07-27`) reads as UTC midnight, and a value
|
|
181
|
+
* carrying an offset is shifted to its instant, so points spelled in
|
|
182
|
+
* different zones land in the right order on one axis.
|
|
183
|
+
*
|
|
184
|
+
* @param {any} v
|
|
185
|
+
* @returns {any}
|
|
186
|
+
*/
|
|
187
|
+
export declare function numOf(v: any): any;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file `@jarenjs/charts` — headless charts. This is the **engine**:
|
|
3
|
+
* pure functions over data — definition + data ⇄ geometry-free AST ⇄
|
|
4
|
+
* pure-vnode SVG — that know only the `@jarenjs/view` vnode shape. It
|
|
5
|
+
* imports nothing from the component layer, `@jarenjs/app`, or the DOM
|
|
6
|
+
* (the two-layer component rule).
|
|
7
|
+
*
|
|
8
|
+
* The pipeline mirrors `@jarenjs/mermaid`:
|
|
9
|
+
*
|
|
10
|
+
* {config, data} ──build{Type}AST──▶ geometry-free AST (abstract
|
|
11
|
+
* fractions/angles, no pixels)
|
|
12
|
+
* │
|
|
13
|
+
* ▼
|
|
14
|
+
* render{Type}AST ──▶ pure-vnode SVG (toVnode /
|
|
15
|
+
* toSvgString via compileChart)
|
|
16
|
+
*/
|
|
17
|
+
export { compileChart, chartTypes } from './core/chart.js';
|
|
18
|
+
export { scaleLinear, scaleLog, scaleOrdinal, scaleBand, scaleTime } from './core/scale.js';
|
|
19
|
+
export { niceStep, axisTicksLinear, axisTicksLog, axisTicksOrdinal, formatTickValue, formatTimeTick, axisTicksTime, niceTimeStep, } from './core/axis.js';
|
|
20
|
+
export { CATEGORICAL, SEQUENTIAL, seriesColor, sequentialColor, inkFor, createTheme, THEMES, HOST_VARS } from './core/palette.js';
|
|
21
|
+
export { buildPieAST, renderPieAST } from './types/pie.js';
|
|
22
|
+
export { buildBarAST, renderBarAST } from './types/bar.js';
|
|
23
|
+
export { buildLineAST, renderLineAST } from './types/line.js';
|
|
24
|
+
export { buildScatterAST, renderScatterAST } from './types/scatter.js';
|
|
25
|
+
export { buildCandlestickAST, renderCandlestickAST } from './types/candlestick.js';
|
|
26
|
+
export { buildRadarAST, renderRadarAST } from './types/radar.js';
|
|
27
|
+
export { buildGaugeAST, renderGaugeAST } from './types/gauge.js';
|
|
28
|
+
export { buildBoxplotAST, renderBoxplotAST, quantileSorted } from './types/boxplot.js';
|
|
29
|
+
export { buildHeatmapAST, renderHeatmapAST } from './types/heatmap.js';
|
|
30
|
+
export { buildTreemapAST, renderTreemapAST } from './types/treemap.js';
|
|
31
|
+
export { buildStreamgraphAST, renderStreamgraphAST } from './types/streamgraph.js';
|
|
32
|
+
export { buildSankeyAST, renderSankeyAST } from './types/sankey.js';
|
|
33
|
+
export { buildMapAST, renderMapAST } from './types/map.js';
|
|
34
|
+
export { createStreamAdapter } from './core/stream-adapter.js';
|
|
35
|
+
export { createChartSession } from './core/session.js';
|