@pond-ts/charts 0.69.0 → 0.71.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/AGENTS.md +3 -1
- package/API.md +55 -56
- package/CHANGELOG.md +177 -1
- package/dist/AreaChart.d.ts +30 -22
- package/dist/AreaChart.js +37 -22
- package/dist/BandChart.d.ts +20 -1
- package/dist/BandChart.js +18 -2
- package/dist/BoxPlot.js +5 -3
- package/dist/Candlestick.d.ts +3 -3
- package/dist/Candlestick.js +5 -5
- package/dist/ChartContainer.d.ts +8 -149
- package/dist/ChartContainer.js +68 -111
- package/dist/ChartRow.d.ts +1 -12
- package/dist/ChartRow.js +15 -30
- package/dist/HeatMap.d.ts +3 -3
- package/dist/HeatMap.js +3 -3
- package/dist/Layers.js +84 -34
- package/dist/XAxis.js +1 -0
- package/dist/area.d.ts +6 -5
- package/dist/area.js +13 -6
- package/dist/axis-events.d.ts +4 -4
- package/dist/band.d.ts +10 -2
- package/dist/band.js +38 -10
- package/dist/brush.d.ts +13 -21
- package/dist/brush.js +22 -36
- package/dist/context.d.ts +81 -96
- package/dist/cursors.d.ts +81 -51
- package/dist/cursors.js +164 -127
- package/dist/decimate.d.ts +10 -1
- package/dist/decimate.js +51 -10
- package/dist/elapsed.d.ts +1 -1
- package/dist/elapsed.js +1 -1
- package/dist/format.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/selection-fixtures.d.ts +4 -7
- package/dist/selection-fixtures.js +0 -9
- package/dist/selection-stories.d.ts +4 -6
- package/dist/selection-stories.js +4 -4
- package/dist/selectors.d.ts +2 -3
- package/dist/selectors.js +2 -3
- package/dist/tracker.d.ts +10 -28
- package/dist/tracker.js +17 -40
- package/dist/tradingAxis.fixture.d.ts +19 -0
- package/dist/tradingAxis.fixture.js +24 -0
- package/dist/tradingTimeScale.d.ts +2 -2
- package/package.json +3 -3
|
@@ -48,8 +48,6 @@ export const categoricalBars = {
|
|
|
48
48
|
picks: SERVICES.map((c) => ({ label: c.label, info: svc(c.label) })),
|
|
49
49
|
describe: (hit) => hit.mark ?? hit.label,
|
|
50
50
|
// No `sequence`: a time bucketing over ordinal slots is meaningless.
|
|
51
|
-
// No `rangeCursor`: it gates on a continuous x.
|
|
52
|
-
rangeCursor: false,
|
|
53
51
|
sweep: true,
|
|
54
52
|
};
|
|
55
53
|
// ── Time axis ──────────────────────────────────────────────────────────────
|
|
@@ -108,7 +106,6 @@ export const timeBars = {
|
|
|
108
106
|
* bar so the buckets line up with the data rather than with the epoch.
|
|
109
107
|
*/
|
|
110
108
|
sequence: () => Sequence.every('7d', { anchor: D0 }),
|
|
111
|
-
rangeCursor: true,
|
|
112
109
|
sweep: true,
|
|
113
110
|
};
|
|
114
111
|
// ── Stacked bars (many marks per bin) ──────────────────────────────────────
|
|
@@ -177,7 +174,6 @@ export const stackedBars = {
|
|
|
177
174
|
? isoDay(hit.key)
|
|
178
175
|
: `${isoDay(hit.key)}·${hit.label}`,
|
|
179
176
|
sequence: () => Sequence.every('7d', { anchor: D0 }),
|
|
180
|
-
rangeCursor: true,
|
|
181
177
|
sweep: true,
|
|
182
178
|
};
|
|
183
179
|
// ── Box plots (a mark with internal structure) ─────────────────────────────
|
|
@@ -258,7 +254,6 @@ const boxFixture = (name, shape, note) => ({
|
|
|
258
254
|
/** Three 10-minute boxes per bucket — coarser than the mark, so the snap has
|
|
259
255
|
* something to do (the lesson `timeBars` learned with its `'1d'`). */
|
|
260
256
|
sequence: () => Sequence.every('30m', { anchor: BOX_BASE }),
|
|
261
|
-
rangeCursor: true,
|
|
262
257
|
});
|
|
263
258
|
/** The default shape: a q1→q3 body with thin stems and end caps. The stems are
|
|
264
259
|
* the interesting part for selection — they are drawn ink far from the body. */
|
|
@@ -332,7 +327,6 @@ export const candles = {
|
|
|
332
327
|
describe: (hit) => isoDay(hit.key),
|
|
333
328
|
sweep: true,
|
|
334
329
|
sequence: () => Sequence.every('5d', { anchor: D0 }),
|
|
335
|
-
rangeCursor: true,
|
|
336
330
|
};
|
|
337
331
|
// ── The 2-D family: scatter and heat map ──────────────────────────────────
|
|
338
332
|
/** Forty points on a time axis — a scatter's marks live at `(x, y)`, not in a
|
|
@@ -402,7 +396,6 @@ export const scatterPoints = {
|
|
|
402
396
|
// A **2-D** sweep: the drag cuts a free rect, and the committed span carries
|
|
403
397
|
// a `y` window alongside `x` ([PND-INTERACT2D]).
|
|
404
398
|
sweep: true,
|
|
405
|
-
rangeCursor: true,
|
|
406
399
|
};
|
|
407
400
|
/**
|
|
408
401
|
* **A heat-map column** — a *grid* of cells, `(bin × row)`.
|
|
@@ -443,7 +436,6 @@ export const heatGrid = {
|
|
|
443
436
|
// A **2-D** sweep, snapped in both dimensions — bins on x, rows on y — so a
|
|
444
437
|
// capture is always a contiguous rectangle of cells ([PND-INTERACT2D]).
|
|
445
438
|
sweep: true,
|
|
446
|
-
rangeCursor: true,
|
|
447
439
|
};
|
|
448
440
|
// ── Trading sessions (a discontinuous time axis) ───────────────────────────
|
|
449
441
|
/** Six weekday sessions (09:30–16:00 UTC) from a Monday — five overnight seams
|
|
@@ -531,7 +523,6 @@ export const tradingSessions = {
|
|
|
531
523
|
// the same bucketing `sessions.conforming` names, so the generated story and
|
|
532
524
|
// the session pair below agree about what "conforming" means.
|
|
533
525
|
sequence: () => sessionSeq(SESSIONS),
|
|
534
|
-
rangeCursor: true,
|
|
535
526
|
sweep: true,
|
|
536
527
|
sessions: {
|
|
537
528
|
conforming: () => sessionSeq(SESSIONS),
|
|
@@ -9,15 +9,13 @@ import { type ChartFixture } from './selection-fixtures.js';
|
|
|
9
9
|
* to write the story. That is what makes walking the matrix a review technique
|
|
10
10
|
* rather than a gallery — see CLAUDE.md → "Storybook stories".
|
|
11
11
|
*
|
|
12
|
-
* Stories whose subject is *not* chart-type dependent
|
|
13
|
-
*
|
|
14
|
-
* every fixture would test nothing new.
|
|
12
|
+
* Stories whose subject is *not* chart-type dependent deliberately live in one
|
|
13
|
+
* column only; running them against every fixture would test nothing new.
|
|
15
14
|
*/
|
|
16
15
|
type Story = StoryObj;
|
|
17
16
|
/**
|
|
18
|
-
* The `<Selector>` feature set for one chart type
|
|
19
|
-
*
|
|
20
|
-
* and mounting one there costs the row its cursor entirely.
|
|
17
|
+
* The `<Selector>` feature set for one chart type, each with a `<RangeCursor>`
|
|
18
|
+
* mounted (on an ordinal axis it shades the slot under the pointer).
|
|
21
19
|
*/
|
|
22
20
|
export interface SelectorStories {
|
|
23
21
|
MountedAtContainer: Story;
|
|
@@ -6,7 +6,7 @@ import { Layers } from './Layers.js';
|
|
|
6
6
|
import { YAxis } from './YAxis.js';
|
|
7
7
|
import { Selector, MultiSelector } from './selectors.js';
|
|
8
8
|
import { isSpanSelection, sameMark, selectionContains } from './span.js';
|
|
9
|
-
import { RangeCursor } from './cursors.js';
|
|
9
|
+
import { LineCursor, RangeCursor } from './cursors.js';
|
|
10
10
|
import { caption } from './selection-fixtures.js';
|
|
11
11
|
const H = 240;
|
|
12
12
|
/**
|
|
@@ -23,7 +23,7 @@ function Chart({ fx, children, height = H, selector, ...container }) {
|
|
|
23
23
|
}
|
|
24
24
|
const list = (fx, sel) => sel.map((m) => fx.describe(m)).join(', ') || '—';
|
|
25
25
|
export function makeSelectorStories(fx) {
|
|
26
|
-
const Cursor = () =>
|
|
26
|
+
const Cursor = () => _jsx(RangeCursor, {});
|
|
27
27
|
return {
|
|
28
28
|
/** **Mounted at the container** — the ordinary case: one `<Selector>` as a
|
|
29
29
|
* child of `<ChartContainer>`, enabling the click for every row. It
|
|
@@ -40,7 +40,7 @@ export function makeSelectorStories(fx) {
|
|
|
40
40
|
MountedInRow: {
|
|
41
41
|
render: function Render() {
|
|
42
42
|
const [sel, setSel] = useState([]);
|
|
43
|
-
return (_jsxs("div", { children: [_jsxs(ChartContainer, { width: 640, ...fx.container, children: [_jsxs(ChartRow, { height: 150, children: [_jsx(YAxis, { id: fx.axis.id, label: fx.axis.label || 'primary', min: fx.axis.min, max: fx.axis.max }), _jsx(Cursor, {}), _jsx(Selector, { selected: sel, onSelect: (h) => setSel(h === null ? [] : [h]), children: _jsx(Layers, { children: fx.renderLayer('svc') }) })] }), _jsxs(ChartRow, { height: 110, children: [_jsx(YAxis, { id: fx.secondary.axis.id, label: fx.secondary.axis.label, min: fx.secondary.axis.min, max: fx.secondary.axis.max }), _jsx(Layers, { children: fx.secondary.renderLayer('err') })] })] }), _jsxs("p", { style: caption, children: ["The top row selects; the bottom row has no", ' ', _jsx("code", { children: "<Selector>" }), " in scope. ", _jsx("strong", { children: "selected:" }), ' ', list(fx, sel)] })] }));
|
|
43
|
+
return (_jsxs("div", { children: [_jsxs(ChartContainer, { width: 640, ...fx.container, children: [_jsx(LineCursor, {}), _jsxs(ChartRow, { height: 150, children: [_jsx(YAxis, { id: fx.axis.id, label: fx.axis.label || 'primary', min: fx.axis.min, max: fx.axis.max }), _jsx(Cursor, {}), _jsx(Selector, { selected: sel, onSelect: (h) => setSel(h === null ? [] : [h]), children: _jsx(Layers, { children: fx.renderLayer('svc') }) })] }), _jsxs(ChartRow, { height: 110, children: [_jsx(YAxis, { id: fx.secondary.axis.id, label: fx.secondary.axis.label, min: fx.secondary.axis.min, max: fx.secondary.axis.max }), _jsx(Layers, { children: fx.secondary.renderLayer('err') })] })] }), _jsxs("p", { style: caption, children: ["The top row selects; the bottom row has no", ' ', _jsx("code", { children: "<Selector>" }), " in scope. ", _jsx("strong", { children: "selected:" }), ' ', list(fx, sel)] })] }));
|
|
44
44
|
},
|
|
45
45
|
},
|
|
46
46
|
/** **No selector — the plot is inert.** An id-bearing, hit-testable layer
|
|
@@ -68,7 +68,7 @@ export function makeSelectorStories(fx) {
|
|
|
68
68
|
};
|
|
69
69
|
return (_jsxs("div", { children: [_jsx("div", { style: { marginBottom: 8 }, children: fx.picks.map((p) => (_jsx("button", { style: btn, onClick: () => setSel((cur) => cur.some((m) => sameMark(m, p.info))
|
|
70
70
|
? cur.filter((m) => !sameMark(m, p.info))
|
|
71
|
-
: [...cur, p.info]), children: p.label }, p.label))) }), _jsx(Chart, { fx: fx, selector: _jsx(Selector, { enabled: false, selected: sel }) }), _jsxs("p", { style: caption, children: ["The buttons drive the highlight; clicking the plot does nothing.", ' ', _jsx("strong", { children: "selected:" }), " ", list(fx, sel)] })] }));
|
|
71
|
+
: [...cur, p.info]), children: p.label }, p.label))) }), _jsx(Chart, { fx: fx, selector: _jsx(Selector, { enabled: false, selected: sel }), children: _jsx(LineCursor, {}) }), _jsxs("p", { style: caption, children: ["The buttons drive the highlight; clicking the plot does nothing.", ' ', _jsx("strong", { children: "selected:" }), " ", list(fx, sel)] })] }));
|
|
72
72
|
},
|
|
73
73
|
},
|
|
74
74
|
/** **The modifiers are reported; the policy is yours.** `onSelect`'s second
|
package/dist/selectors.d.ts
CHANGED
|
@@ -227,9 +227,8 @@ export interface MultiSelectorProps {
|
|
|
227
227
|
*
|
|
228
228
|
* - The shared brush band becomes the **resting cursor**, spanning the snap
|
|
229
229
|
* block under the pointer (the `sequence` bucket, else the layer's own
|
|
230
|
-
* bin/slot),
|
|
231
|
-
*
|
|
232
|
-
* string the consumer actually set — still wins the surface.
|
|
230
|
+
* bin/slot), when no cursor component is mounted for the row. A mounted
|
|
231
|
+
* cursor still wins the surface.
|
|
233
232
|
* - Hover is **block-scoped**: pointing at any one mark of a block lights
|
|
234
233
|
* (and reports) every mark in it. Rest and drag share one code path — the
|
|
235
234
|
* same snap buckets, the same layer session — so what the rest previews and
|
package/dist/selectors.js
CHANGED
|
@@ -173,9 +173,8 @@ export function Selector({ enabled = true, selected, hovered, onHover, onSelect,
|
|
|
173
173
|
*
|
|
174
174
|
* - The shared brush band becomes the **resting cursor**, spanning the snap
|
|
175
175
|
* block under the pointer (the `sequence` bucket, else the layer's own
|
|
176
|
-
* bin/slot),
|
|
177
|
-
*
|
|
178
|
-
* string the consumer actually set — still wins the surface.
|
|
176
|
+
* bin/slot), when no cursor component is mounted for the row. A mounted
|
|
177
|
+
* cursor still wins the surface.
|
|
179
178
|
* - Hover is **block-scoped**: pointing at any one mark of a block lights
|
|
180
179
|
* (and reports) every mark in it. Rest and drag share one code path — the
|
|
181
180
|
* same snap buckets, the same layer session — so what the rest previews and
|
package/dist/tracker.d.ts
CHANGED
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cursor geometry helpers — pure functions deciding *
|
|
3
|
-
* ({@link
|
|
2
|
+
* Cursor geometry helpers — pure functions deciding *where* the cursor sits
|
|
3
|
+
* ({@link resolveCursorX}) and which bucket it shades. The marks
|
|
4
4
|
* themselves render as an SVG overlay in `Layers` (no cursor canvas); these
|
|
5
5
|
* helpers stay pure, so they're unit-tested directly.
|
|
6
6
|
*/
|
|
7
|
-
import
|
|
8
|
-
import type { CursorMode } from './context.js';
|
|
7
|
+
import { Interval } from 'pond-ts';
|
|
9
8
|
/**
|
|
10
9
|
* The interval in the sorted, non-overlapping `buckets` that contains `t`
|
|
11
10
|
* (`begin ≤ t < end`), or `undefined` if `t` falls in no bucket. Binary search —
|
|
@@ -39,30 +38,13 @@ export declare function bandRect(buckets: readonly Interval[], t1: number, xScal
|
|
|
39
38
|
x0: number;
|
|
40
39
|
x1: number;
|
|
41
40
|
} | null;
|
|
42
|
-
/** Default cursor mode — the synced vertical line (cursor enabled on the
|
|
43
|
-
* container by default; pair with an off-chart readout via `onTrackerChanged`). */
|
|
44
|
-
export declare const DEFAULT_CURSOR_MODE: CursorMode;
|
|
45
41
|
/**
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* value flag stacked near the top of the row (drawn in `Layers`).
|
|
51
|
-
*
|
|
52
|
-
* **Superseded by the mounted cursor presets** (`cursors.tsx` — each mode's
|
|
53
|
-
* drawing now lives in its component's registered spec): `Layers` no longer
|
|
54
|
-
* reads this. Kept, with its tests, for the life of the `cursor` string-prop
|
|
55
|
-
* deprecation window as the pinned record of what each mode drew; delete both
|
|
56
|
-
* when the modes go.
|
|
42
|
+
* The unit slots `[i, i+1)` of a category axis, from its band scale's domain
|
|
43
|
+
* (`[0, n]`) — the buckets a `<RangeCursor>` snaps to there when no bar layer
|
|
44
|
+
* has published its own (a bar layer's are the same slots). Empty for an
|
|
45
|
+
* empty axis.
|
|
57
46
|
*/
|
|
58
|
-
export declare function
|
|
59
|
-
readonly line: boolean;
|
|
60
|
-
readonly dots: boolean;
|
|
61
|
-
readonly chip: 'none' | 'inline' | 'flag' | 'axis';
|
|
62
|
-
/** `region` mode: a shaded **band** over the bucket under the pointer (from
|
|
63
|
-
* `cursorSequence`), drawn by `Layers`; no line/dots/chip of its own. */
|
|
64
|
-
readonly band: boolean;
|
|
65
|
-
};
|
|
47
|
+
export declare function categorySlots(domain: readonly unknown[]): Interval[];
|
|
66
48
|
/**
|
|
67
49
|
* The crosshair's plot-pixel x from the tracker inputs. **A live local pointer
|
|
68
50
|
* always wins:** a chart the user is actively hovering (`hoverX` non-null) shows
|
|
@@ -79,8 +61,8 @@ export declare function cursorParts(mode: CursorMode): {
|
|
|
79
61
|
*
|
|
80
62
|
* `null` and `undefined` are **equivalent** — both mean "no controlled position"
|
|
81
63
|
* (a hovered chart still tracks its pointer; a non-hovered one shows nothing). To
|
|
82
|
-
* force a chart to never show a cursor at all,
|
|
83
|
-
* `trackerPosition={null}`.
|
|
64
|
+
* force a chart to never show a cursor at all, mount no cursor component, rather
|
|
65
|
+
* than relying on `trackerPosition={null}`.
|
|
84
66
|
*/
|
|
85
67
|
export declare function resolveCursorX(trackerPosition: number | null | undefined, hoverX: number | null, xScale: (time: number) => number): number | null;
|
|
86
68
|
//# sourceMappingURL=tracker.d.ts.map
|
package/dist/tracker.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Cursor geometry helpers — pure functions deciding *
|
|
3
|
-
* ({@link
|
|
2
|
+
* Cursor geometry helpers — pure functions deciding *where* the cursor sits
|
|
3
|
+
* ({@link resolveCursorX}) and which bucket it shades. The marks
|
|
4
4
|
* themselves render as an SVG overlay in `Layers` (no cursor canvas); these
|
|
5
5
|
* helpers stay pure, so they're unit-tested directly.
|
|
6
6
|
*/
|
|
7
|
+
import { Interval } from 'pond-ts';
|
|
7
8
|
/**
|
|
8
9
|
* The interval in the sorted, non-overlapping `buckets` that contains `t`
|
|
9
10
|
* (`begin ≤ t < end`), or `undefined` if `t` falls in no bucket. Binary search —
|
|
@@ -70,45 +71,21 @@ export function bandRect(buckets, t1, xScale, plotWidth, t2) {
|
|
|
70
71
|
const x1 = Math.min(plotWidth, xScale(span.end));
|
|
71
72
|
return x1 > x0 ? { x0, x1 } : null;
|
|
72
73
|
}
|
|
73
|
-
/** Default cursor mode — the synced vertical line (cursor enabled on the
|
|
74
|
-
* container by default; pair with an off-chart readout via `onTrackerChanged`). */
|
|
75
|
-
export const DEFAULT_CURSOR_MODE = 'line';
|
|
76
74
|
/**
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
* value flag stacked near the top of the row (drawn in `Layers`).
|
|
82
|
-
*
|
|
83
|
-
* **Superseded by the mounted cursor presets** (`cursors.tsx` — each mode's
|
|
84
|
-
* drawing now lives in its component's registered spec): `Layers` no longer
|
|
85
|
-
* reads this. Kept, with its tests, for the life of the `cursor` string-prop
|
|
86
|
-
* deprecation window as the pinned record of what each mode drew; delete both
|
|
87
|
-
* when the modes go.
|
|
75
|
+
* The unit slots `[i, i+1)` of a category axis, from its band scale's domain
|
|
76
|
+
* (`[0, n]`) — the buckets a `<RangeCursor>` snaps to there when no bar layer
|
|
77
|
+
* has published its own (a bar layer's are the same slots). Empty for an
|
|
78
|
+
* empty axis.
|
|
88
79
|
*/
|
|
89
|
-
export function
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
case 'inline':
|
|
97
|
-
return { ...base, dots: true, chip: 'inline' };
|
|
98
|
-
case 'flag':
|
|
99
|
-
return { ...base, dots: true, chip: 'flag' };
|
|
100
|
-
case 'crosshair':
|
|
101
|
-
// A single reticle (not per-series): `Layers` draws the dashed vertical +
|
|
102
|
-
// full-width horizontal lines, the centre dot, and one value pill itself
|
|
103
|
-
// (so no generic line/dots here); the x-time pill is on `<XAxis>`.
|
|
104
|
-
return { ...base, chip: 'axis' };
|
|
105
|
-
case 'region':
|
|
106
|
-
// A shaded band over the bucket under the pointer — `Layers` resolves the
|
|
107
|
-
// bucket from `cursorBuckets` and draws the rect (cropped through xScale).
|
|
108
|
-
return { ...base, band: true };
|
|
109
|
-
case 'none':
|
|
110
|
-
return { ...base };
|
|
80
|
+
export function categorySlots(domain) {
|
|
81
|
+
const lo = Number(domain[0]);
|
|
82
|
+
const n = Math.max(0, Math.round(Number(domain[1]) - lo));
|
|
83
|
+
const out = [];
|
|
84
|
+
for (let i = 0; i < n; i += 1) {
|
|
85
|
+
const b = lo + i;
|
|
86
|
+
out.push(new Interval({ value: b, start: b, end: b + 1 }));
|
|
111
87
|
}
|
|
88
|
+
return out;
|
|
112
89
|
}
|
|
113
90
|
/**
|
|
114
91
|
* The crosshair's plot-pixel x from the tracker inputs. **A live local pointer
|
|
@@ -126,8 +103,8 @@ export function cursorParts(mode) {
|
|
|
126
103
|
*
|
|
127
104
|
* `null` and `undefined` are **equivalent** — both mean "no controlled position"
|
|
128
105
|
* (a hovered chart still tracks its pointer; a non-hovered one shows nothing). To
|
|
129
|
-
* force a chart to never show a cursor at all,
|
|
130
|
-
* `trackerPosition={null}`.
|
|
106
|
+
* force a chart to never show a cursor at all, mount no cursor component, rather
|
|
107
|
+
* than relying on `trackerPosition={null}`.
|
|
131
108
|
*/
|
|
132
109
|
export function resolveCursorX(trackerPosition, hoverX, xScale) {
|
|
133
110
|
if (hoverX !== null)
|
|
@@ -50,6 +50,25 @@ export declare function ticks(sessions: Session[], stepMs: number): TimeSeries<t
|
|
|
50
50
|
* pen-up. (Plain {@link ticks} walks continuously across sessions, so close ≈
|
|
51
51
|
* next open and the break is invisible — this is the fixture that shows it.) */
|
|
52
52
|
export declare function gappingTicks(sessions: Session[], stepMs: number): TimeSeries<typeof tickSchema>;
|
|
53
|
+
export declare const envelopeSchema: readonly [{
|
|
54
|
+
readonly name: "time";
|
|
55
|
+
readonly kind: "time";
|
|
56
|
+
}, {
|
|
57
|
+
readonly name: "price";
|
|
58
|
+
readonly kind: "number";
|
|
59
|
+
}, {
|
|
60
|
+
readonly name: "lo";
|
|
61
|
+
readonly kind: "number";
|
|
62
|
+
}, {
|
|
63
|
+
readonly name: "hi";
|
|
64
|
+
readonly kind: "number";
|
|
65
|
+
}];
|
|
66
|
+
/** {@link gappingTicks} plus a `lo` / `hi` envelope around the price (a slowly
|
|
67
|
+
* breathing spread), so a `<BandChart>` on the trading axis shows the same
|
|
68
|
+
* overnight jump the line does: connected, the fill runs a near-vertical sliver
|
|
69
|
+
* from one session's close to the next open; with `sessionBreaks` it ends at
|
|
70
|
+
* the close and re-starts at the open. */
|
|
71
|
+
export declare function gappingEnvelope(sessions: Session[], stepMs: number): TimeSeries<typeof envelopeSchema>;
|
|
53
72
|
export declare const OHLC: {
|
|
54
73
|
readonly open: {
|
|
55
74
|
readonly from: "price";
|
|
@@ -195,6 +195,30 @@ export function gappingTicks(sessions, stepMs) {
|
|
|
195
195
|
});
|
|
196
196
|
return new TimeSeries({ name: 'ticks', schema: tickSchema, rows });
|
|
197
197
|
}
|
|
198
|
+
export const envelopeSchema = [
|
|
199
|
+
{ name: 'time', kind: 'time' },
|
|
200
|
+
{ name: 'price', kind: 'number' },
|
|
201
|
+
{ name: 'lo', kind: 'number' },
|
|
202
|
+
{ name: 'hi', kind: 'number' },
|
|
203
|
+
];
|
|
204
|
+
/** {@link gappingTicks} plus a `lo` / `hi` envelope around the price (a slowly
|
|
205
|
+
* breathing spread), so a `<BandChart>` on the trading axis shows the same
|
|
206
|
+
* overnight jump the line does: connected, the fill runs a near-vertical sliver
|
|
207
|
+
* from one session's close to the next open; with `sessionBreaks` it ends at
|
|
208
|
+
* the close and re-starts at the open. */
|
|
209
|
+
export function gappingEnvelope(sessions, stepMs) {
|
|
210
|
+
const rows = [];
|
|
211
|
+
let i = 0;
|
|
212
|
+
sessions.forEach((s, si) => {
|
|
213
|
+
const base = 100 + si * 6; // each session gaps ~6 above the last
|
|
214
|
+
for (let t = s.open; t < s.close; t += stepMs, i++) {
|
|
215
|
+
const price = base + 4 * Math.sin(i / 18) + 1.5 * Math.sin(i / 3.5);
|
|
216
|
+
const spread = 1.5 + 0.8 * Math.sin(i / 40);
|
|
217
|
+
rows.push([t, price, price - spread, price + spread]);
|
|
218
|
+
}
|
|
219
|
+
});
|
|
220
|
+
return new TimeSeries({ name: 'envelope', schema: envelopeSchema, rows });
|
|
221
|
+
}
|
|
198
222
|
export const OHLC = {
|
|
199
223
|
open: { from: 'price', using: 'first' },
|
|
200
224
|
high: { from: 'price', using: 'max' },
|
|
@@ -183,13 +183,13 @@ export interface TradingTimeScale {
|
|
|
183
183
|
* `02 AM`; a sub-day axis reads date **+** clock). This is the grain-aware
|
|
184
184
|
* default the crosshair pill, marker indicators, and annotation auto-labels
|
|
185
185
|
* use — replacing d3's multi-scale default, which showed local time-of-day
|
|
186
|
-
* for any off-local-midnight instant. A
|
|
186
|
+
* for any off-local-midnight instant. A cursor's `format` overrides it.
|
|
187
187
|
*/
|
|
188
188
|
readoutFormat(count?: number): (value: number) => string;
|
|
189
189
|
/**
|
|
190
190
|
* The axis's resolved **coarse grain** ({@link TimeGrain}) at `count` — the
|
|
191
191
|
* unit the ticks currently sit on (`year` … `second`), strides collapsed. A
|
|
192
|
-
* `
|
|
192
|
+
* cursor `format` callback receives this so it can branch on the zoom level
|
|
193
193
|
* (return a year when zoomed out, a clock when zoomed in) without
|
|
194
194
|
* re-deriving the ladder the axis already ran.
|
|
195
195
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pond-ts/charts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.71.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Canvas-rendered, streaming-first React time-series charts for pond-ts: line, area, band, bar, scatter, box, candlestick, with cursors, selection and annotations",
|
|
6
6
|
"keywords": [
|
|
@@ -62,8 +62,8 @@
|
|
|
62
62
|
"perf": "PERF_BENCH=1 playwright test perf.spec.ts --workers=1"
|
|
63
63
|
},
|
|
64
64
|
"peerDependencies": {
|
|
65
|
-
"@pond-ts/react": "^0.
|
|
66
|
-
"pond-ts": "^0.
|
|
65
|
+
"@pond-ts/react": "^0.71.0",
|
|
66
|
+
"pond-ts": "^0.71.0",
|
|
67
67
|
"react": "^18.0.0 || ^19.0.0"
|
|
68
68
|
},
|
|
69
69
|
"devDependencies": {
|