@pond-ts/charts 0.41.0 → 0.42.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 +74 -5
- package/dist/BarChart.d.ts +117 -54
- package/dist/BarChart.js +241 -104
- package/dist/ChartContainer.d.ts +52 -6
- package/dist/ChartContainer.js +81 -4
- package/dist/Layers.js +47 -8
- package/dist/ScatterChart.d.ts +15 -3
- package/dist/ScatterChart.js +23 -5
- package/dist/bars.d.ts +96 -5
- package/dist/bars.js +157 -10
- package/dist/context.d.ts +54 -19
- package/dist/data.d.ts +102 -1
- package/dist/data.js +147 -0
- package/dist/grid.d.ts +14 -0
- package/dist/grid.js +36 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +5 -1
- package/dist/scatter.d.ts +9 -7
- package/dist/scatter.js +12 -8
- package/dist/theme.d.ts +7 -0
- package/dist/theme.js +1 -0
- package/dist/tradingTimeScale.d.ts +97 -0
- package/dist/tradingTimeScale.js +152 -0
- package/dist/viewport.d.ts +23 -0
- package/dist/viewport.js +51 -0
- package/package.json +3 -3
package/dist/BarChart.js
CHANGED
|
@@ -1,49 +1,53 @@
|
|
|
1
1
|
import { useContext, useEffect, useMemo } from 'react';
|
|
2
2
|
import { ValueSeries } from 'pond-ts';
|
|
3
|
-
import { barsFromTimeSeries, barsFromValueSeries } from './data.js';
|
|
4
|
-
import { barAt, barExtent, barIndexAtTime, drawBars, resolveBarBaseline, } from './bars.js';
|
|
3
|
+
import { barsFromTimeSeries, barsFromValueSeries, stacksFromBins, stacksFromColumns, stacksFromGroups, } from './data.js';
|
|
4
|
+
import { barAt, barExtent, barIndexAtTime, drawBars, drawStacks, resolveBarBaseline, stackAt, stackBinExtent, stackValueExtent, } from './bars.js';
|
|
5
5
|
import { ContainerContext, LayersContext, } from './context.js';
|
|
6
6
|
import { useSlotKey } from './use-slot-key.js';
|
|
7
7
|
/**
|
|
8
|
-
* A bar draw layer
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* A bar / histogram draw layer. In its simplest form, one rectangle per event
|
|
9
|
+
* spanning the key's `[begin, end]` from the axis baseline to a numeric
|
|
10
|
+
* `column`'s value (see below). It also draws **stacked** bars (a group-by
|
|
11
|
+
* dimension → segments, `columns` / a `Map` series / `bins`) and **horizontal**
|
|
12
|
+
* bars (`orientation='horizontal'`, bins on the y axis) — first-class histogram
|
|
13
|
+
* support. Registers into the enclosing {@link Layers} and renders nothing to the
|
|
14
|
+
* DOM; the row draws it.
|
|
13
15
|
*
|
|
14
|
-
* **
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
16
|
+
* **Data sources.** A time / value `TimeSeries` or `ValueSeries` (`column`), a
|
|
17
|
+
* wide series or `bins` array (`columns`), or a `Map<group, TimeSeries>`
|
|
18
|
+
* (`column`) — the last three stack. Every shape composes from pond's own
|
|
19
|
+
* aggregation (`aggregate` / `byColumn` / `partitionBy`); the histogram guide
|
|
20
|
+
* has the recipes.
|
|
18
21
|
*
|
|
19
|
-
* **
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
* so two series sharing a timestamp don't both light up — draws highlighted
|
|
23
|
-
* (outlined for the committed select, fill-only for the transient hover). Both
|
|
24
|
-
* resolve by **containment**: the tracker by the bar's `[begin, end]` time span
|
|
25
|
-
* (`barIndexAtTime`), the click by the bar's pixel rect (`barAt`) — so the
|
|
26
|
-
* readout reads the same bar you click, even across a wide bucket (they differ
|
|
27
|
-
* only by the `gap` inset, where the pixel rect is narrower than the span).
|
|
22
|
+
* **Baseline (single, vertical).** Bars rest on the zero line when the axis
|
|
23
|
+
* domain spans zero, or on the axis floor when an explicit `<YAxis min>` sits
|
|
24
|
+
* above zero (see {@link resolveBarBaseline}).
|
|
28
25
|
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
26
|
+
* **Baseline (stacked).** A stack is **cumulative from value 0** — the segments
|
|
27
|
+
* sum upward from the zero line, so its value axis **must include 0**. The
|
|
28
|
+
* auto-fit guarantees this: {@link stackValueExtent} always returns `[0, maxTotal]`.
|
|
29
|
+
* An explicit `<YAxis min>` **above** 0 is therefore unsupported for a stack — it
|
|
30
|
+
* would hide the bottom of the cumulative column; only the portion above the floor
|
|
31
|
+
* draws (clipped cleanly at the plot floor, as any bar below an explicit floor is).
|
|
32
|
+
* Segment values are assumed **non-negative** (a negative or zero segment is
|
|
33
|
+
* skipped — diverging stacks are out of scope).
|
|
34
34
|
*
|
|
35
|
-
* **
|
|
36
|
-
* (
|
|
37
|
-
*
|
|
38
|
-
*
|
|
35
|
+
* **Interaction (opt-in via `id`).** Hover lights the bar / segment under the
|
|
36
|
+
* cursor (hit-tested by pixel rect, so it works in both orientations); click
|
|
37
|
+
* selects it (outlined). A stacked segment's identity is `(id, key = bin begin,
|
|
38
|
+
* label = group)`. Both channels are controllable from outside via the container
|
|
39
|
+
* (`selected`/`onSelect`, `hovered`/`onHover`). The in-chart `flag`/`crosshair`
|
|
40
|
+
* value cursor is single-series-vertical only.
|
|
39
41
|
*
|
|
40
42
|
* ```tsx
|
|
41
43
|
* <Layers>
|
|
42
44
|
* <BarChart series={hourlyVolume} column="count" />
|
|
45
|
+
* <BarChart series={byHost} column="n" colors={{ web1: '#…' }} />
|
|
46
|
+
* <BarChart bins={powerDist} column="seconds" orientation="horizontal" ordinal />
|
|
43
47
|
* </Layers>
|
|
44
48
|
* ```
|
|
45
49
|
*/
|
|
46
|
-
export function BarChart({ series, column, as: semantic, axis, gap, index = 0, }) {
|
|
50
|
+
export function BarChart({ series, bins, column, columns, as: semantic, colors, orientation = 'vertical', ordinal = false, id, axis, gap, index = 0, }) {
|
|
47
51
|
const container = useContext(ContainerContext);
|
|
48
52
|
if (container === null) {
|
|
49
53
|
throw new Error('<BarChart> must be rendered inside a <ChartContainer>');
|
|
@@ -52,98 +56,231 @@ export function BarChart({ series, column, as: semantic, axis, gap, index = 0, }
|
|
|
52
56
|
if (layers === null) {
|
|
53
57
|
throw new Error('<BarChart> must be rendered inside a <Layers>');
|
|
54
58
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
// Validate the data-source / value-column combination up front (throws are
|
|
60
|
+
// stable across renders, so no need to memoize them).
|
|
61
|
+
if ((series === undefined) === (bins === undefined)) {
|
|
62
|
+
throw new Error('<BarChart> needs exactly one of `series` or `bins`');
|
|
63
|
+
}
|
|
64
|
+
const isMap = series instanceof Map;
|
|
65
|
+
if (isMap && columns !== undefined) {
|
|
66
|
+
throw new Error('<BarChart> with a `Map` series stacks its groups — use `column` (the shared value column), not `columns`');
|
|
67
|
+
}
|
|
68
|
+
if (column !== undefined && columns !== undefined) {
|
|
69
|
+
throw new Error('<BarChart> takes `column` or `columns`, not both');
|
|
70
|
+
}
|
|
71
|
+
// The single series' semantic label (its identity for the readout + selection):
|
|
72
|
+
// the `as` role, else the value column. Used only on the single path.
|
|
73
|
+
const label = semantic ?? column ?? id ?? 'value';
|
|
74
|
+
// Build the chart-ready data view. Single-series *vertical* stays on the
|
|
75
|
+
// original BarSeries path (its pixels are unchanged); everything else — any
|
|
76
|
+
// stack, any horizontal — builds a StackedBarSeries (G === 1 for a single
|
|
77
|
+
// horizontal bar) so one oriented draw path covers it.
|
|
78
|
+
const shape = useMemo(() => {
|
|
79
|
+
if (bins !== undefined) {
|
|
80
|
+
const cols = columns ?? (column !== undefined ? [column] : undefined);
|
|
81
|
+
if (cols === undefined) {
|
|
82
|
+
throw new Error('<BarChart bins> needs `column` or `columns`');
|
|
83
|
+
}
|
|
84
|
+
return { kind: 'stacked', ss: stacksFromBins(bins, cols, { ordinal }) };
|
|
85
|
+
}
|
|
86
|
+
if (isMap) {
|
|
87
|
+
if (column === undefined) {
|
|
88
|
+
throw new Error('<BarChart> with a `Map` series needs `column`');
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
kind: 'stacked',
|
|
92
|
+
ss: stacksFromGroups(series, column),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
const s = series;
|
|
96
|
+
if (columns !== undefined) {
|
|
97
|
+
return { kind: 'stacked', ss: stacksFromColumns(s, columns) };
|
|
98
|
+
}
|
|
99
|
+
if (column === undefined) {
|
|
100
|
+
throw new Error('<BarChart> needs `column` or `columns`');
|
|
101
|
+
}
|
|
102
|
+
if (orientation === 'horizontal') {
|
|
103
|
+
// Single horizontal bar: route through the stacked path (G === 1), naming
|
|
104
|
+
// the one group with the series' label so selection matches on it.
|
|
105
|
+
const ss = stacksFromColumns(s, [column]);
|
|
106
|
+
return { kind: 'stacked', ss: { ...ss, groups: [label] } };
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
kind: 'single',
|
|
110
|
+
bs: s instanceof ValueSeries
|
|
111
|
+
? barsFromValueSeries(s, column)
|
|
112
|
+
: barsFromTimeSeries(s, column),
|
|
113
|
+
};
|
|
114
|
+
}, [series, bins, column, columns, ordinal, orientation, isMap, label]);
|
|
115
|
+
// The bin axis kind (time vs value) — a `TimeSeries`/`Map` bins on time, a
|
|
116
|
+
// `ValueSeries`/`bins`-array on a value axis. For a vertical histogram this is
|
|
117
|
+
// the shared x-kind; a horizontal one puts the *value* on x (always 'value')
|
|
118
|
+
// and the bin axis on a linear y.
|
|
119
|
+
const binAxisKind = bins !== undefined
|
|
120
|
+
? 'value'
|
|
121
|
+
: isMap
|
|
122
|
+
? 'time'
|
|
123
|
+
: series instanceof ValueSeries
|
|
124
|
+
? 'value'
|
|
125
|
+
: 'time';
|
|
59
126
|
const { bar } = container.theme;
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
//
|
|
65
|
-
const
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
127
|
+
// Single-series style: the `as` role → theme bar style (the single channel).
|
|
128
|
+
const singleStyle = (semantic !== undefined ? bar[semantic] : undefined) ?? bar.default;
|
|
129
|
+
const gapPx = gap ?? bar.default.gap;
|
|
130
|
+
// The stacked path's bar-thickness floor comes from `bar.default` (not the `as`
|
|
131
|
+
// role — `as` is single-series only), matching how `gapPx` sources its default.
|
|
132
|
+
const stackMinWidth = bar.default.minWidth;
|
|
133
|
+
// Stacked style: per-group fills (colors override → theme role → default),
|
|
134
|
+
// plus the shared opacity / outline from the default bar style. Memoized on the
|
|
135
|
+
// groups + colours so a selection change doesn't rebuild it.
|
|
136
|
+
const groups = shape.kind === 'stacked' ? shape.ss.groups : undefined;
|
|
137
|
+
const stackStyle = useMemo(() => {
|
|
138
|
+
const base = bar.default;
|
|
139
|
+
const fills = (groups ?? []).map((g) => colors?.[g] ?? (bar[g] ?? base).fill);
|
|
140
|
+
return { fills, opacity: base.opacity, outlineWidth: base.outlineWidth };
|
|
141
|
+
}, [bar, groups, colors]);
|
|
142
|
+
// The current selection / hover, narrowed to the identity the highlight match
|
|
143
|
+
// needs. For a stack that's (id, key, label = group); the single path uses just
|
|
144
|
+
// (id, key). Read here so a change re-registers the layer → the canvas repaints.
|
|
69
145
|
const selected = container.selected;
|
|
70
|
-
const selection = useMemo(() => selected === null ? null : { key: selected.key, label: selected.label }, [selected]);
|
|
71
|
-
// The transient hover-highlight, narrowed to the match key (key + label) like
|
|
72
|
-
// the selection. Read here so a hover change re-registers the layer → the data
|
|
73
|
-
// canvas repaints with the lit bar. Deduped in the container, so this only
|
|
74
|
-
// fires on a bar transition (not every pointer move).
|
|
75
146
|
const hoveredMark = container.hovered;
|
|
147
|
+
const selection = useMemo(() => selected === null
|
|
148
|
+
? null
|
|
149
|
+
: { id: selected.id, key: selected.key, label: selected.label }, [selected]);
|
|
76
150
|
const hover = useMemo(() => hoveredMark === null
|
|
77
151
|
? null
|
|
78
|
-
: {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
{
|
|
110
|
-
x: (bs.begin[i] + bs.end[i]) / 2,
|
|
111
|
-
value: v,
|
|
112
|
-
color: style.fill,
|
|
113
|
-
label,
|
|
152
|
+
: {
|
|
153
|
+
id: hoveredMark.id,
|
|
154
|
+
key: hoveredMark.key,
|
|
155
|
+
label: hoveredMark.label,
|
|
156
|
+
}, [hoveredMark]);
|
|
157
|
+
const entry = useMemo(() => {
|
|
158
|
+
// ── Single-series, vertical: the original bar path, pixels unchanged. ──
|
|
159
|
+
if (shape.kind === 'single') {
|
|
160
|
+
const bs = shape.bs;
|
|
161
|
+
return {
|
|
162
|
+
layer: {
|
|
163
|
+
yExtent: () => barExtent(bs),
|
|
164
|
+
xKind: binAxisKind,
|
|
165
|
+
xExtent: () => bs.length === 0 ? null : [bs.begin[0], bs.end[bs.length - 1]],
|
|
166
|
+
sampleAt: (time) => {
|
|
167
|
+
if (bs.length === 0)
|
|
168
|
+
return [];
|
|
169
|
+
const i = barIndexAtTime(bs, time);
|
|
170
|
+
if (i < 0)
|
|
171
|
+
return [];
|
|
172
|
+
const v = bs.y[i];
|
|
173
|
+
if (!Number.isFinite(v))
|
|
174
|
+
return [];
|
|
175
|
+
return [
|
|
176
|
+
{
|
|
177
|
+
x: (bs.begin[i] + bs.end[i]) / 2,
|
|
178
|
+
value: v,
|
|
179
|
+
color: singleStyle.fill,
|
|
180
|
+
label,
|
|
181
|
+
},
|
|
182
|
+
];
|
|
114
183
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
184
|
+
...(id === undefined
|
|
185
|
+
? {}
|
|
186
|
+
: {
|
|
187
|
+
hitTest: (px, py, xScale, yScale) => {
|
|
188
|
+
const baseline = resolveBarBaseline(yScale);
|
|
189
|
+
const hit = barAt(bs, px, py, xScale, yScale, baseline, gapPx, singleStyle.minWidth);
|
|
190
|
+
if (hit === null)
|
|
191
|
+
return null;
|
|
192
|
+
const [, begin, value] = hit;
|
|
193
|
+
return {
|
|
194
|
+
id,
|
|
195
|
+
key: begin,
|
|
196
|
+
value,
|
|
197
|
+
color: singleStyle.fill,
|
|
198
|
+
label,
|
|
199
|
+
};
|
|
200
|
+
},
|
|
201
|
+
}),
|
|
202
|
+
draw: (ctx, xScale, yScale) => drawBars(ctx, bs, xScale, yScale, singleStyle, resolveBarBaseline(yScale), gapPx, id, selection, hover),
|
|
203
|
+
},
|
|
204
|
+
axisId: axis,
|
|
205
|
+
index,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
// ── Stacked (or single horizontal): the oriented, transposed draw path. ──
|
|
209
|
+
const ss = shape.ss;
|
|
210
|
+
const binExtent = () => stackBinExtent(ss);
|
|
211
|
+
const valueExtent = () => stackValueExtent(ss);
|
|
212
|
+
const vertical = orientation === 'vertical';
|
|
213
|
+
return {
|
|
214
|
+
layer: {
|
|
215
|
+
// Horizontal puts the value on the shared x (always 'value'); vertical
|
|
216
|
+
// keeps the bin axis on x. The bin axis on the *other* side is a linear
|
|
217
|
+
// numeric scale either way (time ms label via <YAxis ticks>).
|
|
218
|
+
xKind: vertical ? binAxisKind : 'value',
|
|
219
|
+
xExtent: vertical ? binExtent : valueExtent,
|
|
220
|
+
yExtent: vertical ? valueExtent : binExtent,
|
|
221
|
+
// No x-scrub flag for a stack / horizontal chart — hover + click read it
|
|
222
|
+
// out instead (the flag is single-series-vertical only).
|
|
223
|
+
sampleAt: () => [],
|
|
224
|
+
...(id === undefined
|
|
225
|
+
? {}
|
|
226
|
+
: {
|
|
227
|
+
hitTest: (px, py, xScale, yScale) => {
|
|
228
|
+
const hit = stackAt(ss, px, py, orientation, xScale, yScale, gapPx, stackMinWidth);
|
|
229
|
+
if (hit === null)
|
|
230
|
+
return null;
|
|
231
|
+
const [, g, begin, name, value] = hit;
|
|
232
|
+
return {
|
|
233
|
+
id,
|
|
234
|
+
key: begin,
|
|
235
|
+
value,
|
|
236
|
+
color: stackStyle.fills[g],
|
|
237
|
+
label: name,
|
|
238
|
+
};
|
|
239
|
+
},
|
|
240
|
+
}),
|
|
241
|
+
draw: (ctx, xScale, yScale) => drawStacks(ctx, ss, orientation, xScale, yScale, stackStyle, gapPx, stackMinWidth, id, selection, hover),
|
|
127
242
|
},
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
243
|
+
axisId: axis,
|
|
244
|
+
index,
|
|
245
|
+
};
|
|
246
|
+
}, [
|
|
247
|
+
shape,
|
|
248
|
+
binAxisKind,
|
|
249
|
+
orientation,
|
|
250
|
+
singleStyle,
|
|
251
|
+
stackStyle,
|
|
252
|
+
label,
|
|
253
|
+
id,
|
|
254
|
+
gapPx,
|
|
255
|
+
stackMinWidth,
|
|
256
|
+
selection,
|
|
257
|
+
hover,
|
|
258
|
+
axis,
|
|
131
259
|
index,
|
|
132
|
-
|
|
133
|
-
// A stable per-instance slot
|
|
134
|
-
//
|
|
260
|
+
]);
|
|
261
|
+
// A stable per-instance slot keeps this layer's z-position fixed across data /
|
|
262
|
+
// style / selection updates (see useSlotKey).
|
|
135
263
|
const slot = useSlotKey();
|
|
136
264
|
useEffect(() => () => layers.unregisterLayer(slot), [layers, slot]);
|
|
137
265
|
useEffect(() => {
|
|
138
266
|
layers.registerLayer(slot, entry);
|
|
139
267
|
}, [layers, slot, entry]);
|
|
140
|
-
// Also a tracker source: the container fans in this
|
|
141
|
-
//
|
|
268
|
+
// Also a tracker source: the container fans in this layer's value at the cursor
|
|
269
|
+
// for the (outside-the-chart) readout. A stacked / horizontal layer's sampleAt
|
|
270
|
+
// returns nothing, so it contributes no flag but still registers cleanly.
|
|
142
271
|
const { registerTrackerSource, unregisterTrackerSource } = container;
|
|
143
272
|
useEffect(() => () => unregisterTrackerSource(slot), [unregisterTrackerSource, slot]);
|
|
144
273
|
useEffect(() => {
|
|
145
274
|
registerTrackerSource(slot, entry.layer);
|
|
146
275
|
}, [registerTrackerSource, slot, entry.layer]);
|
|
276
|
+
// Advertise selectability (only when an `id` was given).
|
|
277
|
+
const { registerSelectable, unregisterSelectable } = container;
|
|
278
|
+
useEffect(() => {
|
|
279
|
+
if (id === undefined)
|
|
280
|
+
return;
|
|
281
|
+
registerSelectable(slot);
|
|
282
|
+
return () => unregisterSelectable(slot);
|
|
283
|
+
}, [registerSelectable, unregisterSelectable, slot, id]);
|
|
147
284
|
return null;
|
|
148
285
|
}
|
|
149
286
|
//# sourceMappingURL=BarChart.js.map
|
package/dist/ChartContainer.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
+
import { type DiscontinuityProvider, type TradingCalendarLike } from './tradingTimeScale.js';
|
|
2
3
|
import type { TimeRange } from 'pond-ts';
|
|
3
4
|
import { type AnnotationKind, type CreateSpec, type CursorMode, type SelectInfo, type TrackerInfo } from './context.js';
|
|
4
5
|
import { type AxisFormat } from './format.js';
|
|
@@ -12,6 +13,45 @@ export interface ChartContainerProps {
|
|
|
12
13
|
* the data — so a tuple stays a time domain on a time chart.
|
|
13
14
|
*/
|
|
14
15
|
range?: readonly [number, number] | TimeRange;
|
|
16
|
+
/**
|
|
17
|
+
* A **trading-calendar** discontinuity provider — closed-market time
|
|
18
|
+
* (weekends, holidays, overnight, lunch breaks) collapsed. Supply it to turn
|
|
19
|
+
* the shared x axis into a **trading-time** axis: gaps disappear and time
|
|
20
|
+
* stays proportional within each session. A `@pond-ts/financial`
|
|
21
|
+
* `TradingCalendar.discontinuities()` satisfies this structurally (charts
|
|
22
|
+
* never imports that package). The **low-level** primitive: pass
|
|
23
|
+
* `calendar.discontinuities()` (or a `{ spacing, period }` variant) directly.
|
|
24
|
+
* Only affects a **time** axis (ignored on a value axis). Takes precedence
|
|
25
|
+
* over {@link calendar} if both are given.
|
|
26
|
+
*
|
|
27
|
+
* **Pass a stable reference.** The scale (and container frame) rebuild when
|
|
28
|
+
* this prop's identity changes, so memoize it — `const disc = useMemo(() =>
|
|
29
|
+
* calendar.discontinuities(), [calendar])` — rather than calling
|
|
30
|
+
* `.discontinuities()` inline in JSX, which would rebuild every render.
|
|
31
|
+
*/
|
|
32
|
+
discontinuities?: DiscontinuityProvider;
|
|
33
|
+
/**
|
|
34
|
+
* The **high-level** sugar for {@link discontinuities}: a trading calendar the
|
|
35
|
+
* container derives the provider from itself (`calendar.discontinuities({
|
|
36
|
+
* spacing })`), so you don't wire the low-level prop. A `@pond-ts/financial`
|
|
37
|
+
* `TradingCalendar` satisfies the structural {@link TradingCalendarLike} shape
|
|
38
|
+
* (charts never imports that package). Combine with {@link spacing}. For the
|
|
39
|
+
* full option matrix (a bar `period`, a scoped `range`) use the low-level
|
|
40
|
+
* `discontinuities` prop instead. Only affects a **time** axis.
|
|
41
|
+
*
|
|
42
|
+
* The provider is memoized on `(calendar, spacing)`, so pass a **stable**
|
|
43
|
+
* calendar reference (build it once, not inline in JSX).
|
|
44
|
+
*/
|
|
45
|
+
calendar?: TradingCalendarLike;
|
|
46
|
+
/**
|
|
47
|
+
* The trading axis **metric**, when a {@link calendar} is supplied
|
|
48
|
+
* (trading-calendar RFC Q7). `'proportional'` (default) keeps time
|
|
49
|
+
* proportional within and across sessions — a half-day is half as wide.
|
|
50
|
+
* `'uniform'` gives every session equal width (the TradingView ordinal look).
|
|
51
|
+
* Ignored without `calendar` (a low-level `discontinuities` provider already
|
|
52
|
+
* carries its own metric).
|
|
53
|
+
*/
|
|
54
|
+
spacing?: 'proportional' | 'uniform';
|
|
15
55
|
/** Total width in CSS pixels (plot + axis gutters). */
|
|
16
56
|
width: number;
|
|
17
57
|
/** Vertical space between rows in CSS pixels (not under the axis). Default 0. */
|
|
@@ -46,15 +86,21 @@ export interface ChartContainerProps {
|
|
|
46
86
|
/**
|
|
47
87
|
* Controlled selection — the selected mark (echo the `onSelect` arg back), or
|
|
48
88
|
* `null`. **Omitted ⇒ uncontrolled** (a click on a selectable layer manages it
|
|
49
|
-
* internally; pass `null` to force nothing selected).
|
|
50
|
-
*
|
|
51
|
-
*
|
|
89
|
+
* internally; pass `null` to force nothing selected). A layer is **selectable
|
|
90
|
+
* only when it carries an `id`** (the stable series identity) — `BarChart` /
|
|
91
|
+
* `ScatterChart` highlight the mark matching the selection's `id` (the series)
|
|
92
|
+
* and its `key` (the sample), so two series sharing a timestamp don't both
|
|
93
|
+
* light up, and the selection survives a data update (it keys on the stable
|
|
94
|
+
* `id`, not the sample `key`). A layer with no `id` renders + reads out but
|
|
95
|
+
* can't be selected.
|
|
52
96
|
*/
|
|
53
97
|
selected?: SelectInfo | null;
|
|
54
98
|
/**
|
|
55
99
|
* Fires when a selectable layer's mark is clicked, with the hit mark, or `null`
|
|
56
|
-
* when a click misses every mark (
|
|
57
|
-
* works in both controlled and
|
|
100
|
+
* when a click misses every mark (or hits a layer with no `id` — display-only,
|
|
101
|
+
* so it reads as empty space). Notification only — works in both controlled and
|
|
102
|
+
* uncontrolled mode. If this or `selected` is set but no layer has an `id`, a
|
|
103
|
+
* dev-warning notes that nothing is selectable.
|
|
58
104
|
*/
|
|
59
105
|
onSelect?: (hit: SelectInfo | null) => void;
|
|
60
106
|
/**
|
|
@@ -180,5 +226,5 @@ export interface ChartContainerProps {
|
|
|
180
226
|
* {@link TimeAxis} at the bottom, aligned under the plots. Y axes are per-row
|
|
181
227
|
* (`<YAxis>`).
|
|
182
228
|
*/
|
|
183
|
-
export declare function ChartContainer({ range, width, rowGap, showAxis, trackerPosition, onTrackerChanged, selected, onSelect, hovered, onHover, panZoom, onTimeRangeChange, minDuration, cursor, cursorTime, crosshairSnap, editAnnotations, creating, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap, timeFormat, theme, children, }: ChartContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
229
|
+
export declare function ChartContainer({ range, width, rowGap, showAxis, trackerPosition, onTrackerChanged, selected, onSelect, hovered, onHover, panZoom, onTimeRangeChange, minDuration, cursor, cursorTime, crosshairSnap, editAnnotations, creating, onCreate, onSelectAnnotation, onHoverAnnotation, onEditAnnotation, snap, timeFormat, theme, discontinuities, calendar, spacing, children, }: ChartContainerProps): import("react/jsx-runtime").JSX.Element;
|
|
184
230
|
//# sourceMappingURL=ChartContainer.d.ts.map
|