@pond-ts/charts 0.40.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 +103 -6
- package/dist/BarChart.d.ts +117 -54
- package/dist/BarChart.js +241 -104
- package/dist/Candlestick.d.ts +93 -0
- package/dist/Candlestick.js +102 -0
- 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 +159 -1
- package/dist/data.js +208 -25
- package/dist/grid.d.ts +14 -0
- package/dist/grid.js +36 -0
- package/dist/index.d.ts +9 -3
- package/dist/index.js +6 -1
- package/dist/ohlc.d.ts +81 -0
- package/dist/ohlc.js +153 -0
- package/dist/scatter.d.ts +9 -7
- package/dist/scatter.js +12 -8
- package/dist/theme.d.ts +59 -0
- package/dist/theme.js +25 -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/bars.js
CHANGED
|
@@ -82,7 +82,8 @@ export function barRect(cs, i, xScale, yScale, baseline, gapPx, minWidthPx) {
|
|
|
82
82
|
* (inset by `gapPx`) from the resolved `baseline` to the value.
|
|
83
83
|
*
|
|
84
84
|
* A gap (non-finite value) is skipped — no bar, no zero-height sliver. A bar
|
|
85
|
-
* matching the current `selection` (same `
|
|
85
|
+
* matching the current `selection` (same sample `key` **and** the layer's own
|
|
86
|
+
* series `id` — `seriesId`; a no-id layer passes `undefined` and never matches)
|
|
86
87
|
* draws in the style's `highlight` colour **and outlined**, so a click reads back
|
|
87
88
|
* on the canvas; a bar matching `hovered` draws in `highlight` **without** the
|
|
88
89
|
* outline (a lighter "this bar is live" on pointer-over); all others use the flat
|
|
@@ -92,7 +93,7 @@ export function barRect(cs, i, xScale, yScale, baseline, gapPx, minWidthPx) {
|
|
|
92
93
|
* O(N) over the events, one fill (+ optional stroke) per bar, no per-bar
|
|
93
94
|
* allocation beyond the rect tuple.
|
|
94
95
|
*/
|
|
95
|
-
export function drawBars(ctx, cs, xScale, yScale, style, baseline, gapPx,
|
|
96
|
+
export function drawBars(ctx, cs, xScale, yScale, style, baseline, gapPx, seriesId, selection, hovered) {
|
|
96
97
|
ctx.save();
|
|
97
98
|
ctx.globalAlpha = style.opacity;
|
|
98
99
|
for (let i = 0; i < cs.length; i += 1) {
|
|
@@ -100,16 +101,18 @@ export function drawBars(ctx, cs, xScale, yScale, style, baseline, gapPx, label,
|
|
|
100
101
|
if (rect === null)
|
|
101
102
|
continue;
|
|
102
103
|
const [x0, x1, yTop, yBottom] = rect;
|
|
103
|
-
// Match by
|
|
104
|
-
//
|
|
105
|
-
// `
|
|
106
|
-
//
|
|
104
|
+
// Match by the series `id` **and** the sample `key` (begin), so two series
|
|
105
|
+
// sharing a timestamp don't both light up (a no-id, non-selectable layer
|
|
106
|
+
// passes `seriesId === undefined` and never matches). Both the committed
|
|
107
|
+
// selection and the transient hover use the `highlight` fill; only the
|
|
108
|
+
// selection adds the outline, so hover reads as a lighter "this bar is live"
|
|
109
|
+
// and select as the committed pick.
|
|
107
110
|
const selected = selection !== null &&
|
|
108
|
-
selection.
|
|
109
|
-
selection.
|
|
111
|
+
selection.id === seriesId &&
|
|
112
|
+
selection.key === cs.begin[i];
|
|
110
113
|
const isHovered = hovered !== null &&
|
|
111
|
-
hovered.
|
|
112
|
-
hovered.
|
|
114
|
+
hovered.id === seriesId &&
|
|
115
|
+
hovered.key === cs.begin[i];
|
|
113
116
|
ctx.fillStyle = selected || isHovered ? style.highlight : style.fill;
|
|
114
117
|
ctx.fillRect(x0, yTop, x1 - x0, yBottom - yTop);
|
|
115
118
|
if (selected) {
|
|
@@ -168,4 +171,148 @@ export function barAt(cs, px, py, xScale, yScale, baseline, gapPx, minWidthPx) {
|
|
|
168
171
|
}
|
|
169
172
|
return null;
|
|
170
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* The `[min, max]` extent of the **value (stacked) axis** — always `[0, maxTotal]`,
|
|
176
|
+
* where `maxTotal` is the tallest bin's summed finite non-negative segments. `0` is
|
|
177
|
+
* pulled in so the stack rests on a visible baseline (the bar analog of
|
|
178
|
+
* {@link barExtent}). An empty / all-gap series returns `[0, 1]` so the axis still
|
|
179
|
+
* has a usable domain. Feeds the y auto-fit for a vertical histogram, the x
|
|
180
|
+
* auto-fit for a horizontal one.
|
|
181
|
+
*/
|
|
182
|
+
export function stackValueExtent(ss) {
|
|
183
|
+
const G = ss.groups.length;
|
|
184
|
+
let max = 0;
|
|
185
|
+
for (let b = 0; b < ss.length; b += 1) {
|
|
186
|
+
let cum = 0;
|
|
187
|
+
for (let g = 0; g < G; g += 1) {
|
|
188
|
+
const v = ss.values[b * G + g];
|
|
189
|
+
if (Number.isFinite(v) && v > 0)
|
|
190
|
+
cum += v;
|
|
191
|
+
}
|
|
192
|
+
if (cum > max)
|
|
193
|
+
max = cum;
|
|
194
|
+
}
|
|
195
|
+
return [0, max > 0 ? max : 1];
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* The `[min, max]` extent of the **bin axis** — the first bin's `begin` to the
|
|
199
|
+
* last bin's `end` (the slots are ascending). `null` for an empty series. Feeds
|
|
200
|
+
* the x auto-fit for a vertical histogram, the y auto-fit for a horizontal one.
|
|
201
|
+
*/
|
|
202
|
+
export function stackBinExtent(ss) {
|
|
203
|
+
if (ss.length === 0)
|
|
204
|
+
return null;
|
|
205
|
+
return [ss.begin[0], ss.end[ss.length - 1]];
|
|
206
|
+
}
|
|
207
|
+
/**
|
|
208
|
+
* The pixel rect `[x0, x1, yTop, yBottom]` (ascending on both axes) of bin `b`'s
|
|
209
|
+
* segment `g`, stacked so it sits atop `cumBefore` (the summed value of the
|
|
210
|
+
* segments below it, in value units). `null` for a gap (see below). Transposes on
|
|
211
|
+
* `orientation`:
|
|
212
|
+
*
|
|
213
|
+
* - **vertical** — the bin span is horizontal (`barSpanPx` on `xScale`); the
|
|
214
|
+
* segment runs vertically from `yScale(cumBefore)` to `yScale(cumBefore + v)`.
|
|
215
|
+
* - **horizontal** — the bin span is vertical (`barSpanPx` on `yScale`); the
|
|
216
|
+
* segment runs horizontally from `xScale(cumBefore)` to `xScale(cumBefore + v)`.
|
|
217
|
+
*
|
|
218
|
+
* `null` for a **gap** — a non-finite, negative, **or zero** value: none of them
|
|
219
|
+
* draw (a zero segment has no extent), and each contributes nothing to the running
|
|
220
|
+
* total. `minSpanPx` floors the **bin** span (bar thickness); the value direction
|
|
221
|
+
* is unfloored. Shared by {@link drawStacks} and {@link stackAt} so the drawn rect
|
|
222
|
+
* and the hit rect are identical.
|
|
223
|
+
*/
|
|
224
|
+
export function segmentRect(ss, b, g, orientation, xScale, yScale, cumBefore, gapPx, minSpanPx) {
|
|
225
|
+
const G = ss.groups.length;
|
|
226
|
+
const v = ss.values[b * G + g];
|
|
227
|
+
// Skip non-finite / negative / zero: a zero segment would otherwise draw a
|
|
228
|
+
// wasted zero-extent rect (and can't be hit-tested).
|
|
229
|
+
if (!Number.isFinite(v) || v <= 0)
|
|
230
|
+
return null;
|
|
231
|
+
if (orientation === 'vertical') {
|
|
232
|
+
const [x0, x1] = barSpanPx(ss.begin[b], ss.end[b], xScale, gapPx, minSpanPx);
|
|
233
|
+
const yA = yScale(cumBefore);
|
|
234
|
+
const yB = yScale(cumBefore + v);
|
|
235
|
+
return [x0, x1, Math.min(yA, yB), Math.max(yA, yB)];
|
|
236
|
+
}
|
|
237
|
+
const [y0, y1] = barSpanPx(ss.begin[b], ss.end[b], yScale, gapPx, minSpanPx);
|
|
238
|
+
const xA = xScale(cumBefore);
|
|
239
|
+
const xB = xScale(cumBefore + v);
|
|
240
|
+
return [Math.min(xA, xB), Math.max(xA, xB), y0, y1];
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Fill every segment of every bin in `ss`, stacking each bin's groups from the
|
|
244
|
+
* value baseline outward (bottom → top vertical, left → right horizontal). A gap
|
|
245
|
+
* (non-finite / negative) segment is skipped and adds nothing to the running
|
|
246
|
+
* total, so the segments above it close the space. A segment matching the current
|
|
247
|
+
* `selection` (same series `id`, bin `key` **and** group `label`) draws in its
|
|
248
|
+
* group's `highlight` **and** outlined; one matching `hover` draws in `highlight`
|
|
249
|
+
* without the outline; all others use the flat `fill`. `globalAlpha` carries the
|
|
250
|
+
* shared opacity and is restored.
|
|
251
|
+
*
|
|
252
|
+
* O(N·G) over bins × groups, one fill (+ optional stroke) per drawn segment.
|
|
253
|
+
*/
|
|
254
|
+
export function drawStacks(ctx, ss, orientation, xScale, yScale, style, gapPx, minSpanPx, seriesId, selection, hover) {
|
|
255
|
+
const G = ss.groups.length;
|
|
256
|
+
ctx.save();
|
|
257
|
+
ctx.globalAlpha = style.opacity;
|
|
258
|
+
for (let b = 0; b < ss.length; b += 1) {
|
|
259
|
+
let cum = 0;
|
|
260
|
+
for (let g = 0; g < G; g += 1) {
|
|
261
|
+
const rect = segmentRect(ss, b, g, orientation, xScale, yScale, cum, gapPx, minSpanPx);
|
|
262
|
+
const v = ss.values[b * G + g];
|
|
263
|
+
if (Number.isFinite(v) && v > 0)
|
|
264
|
+
cum += v;
|
|
265
|
+
if (rect === null)
|
|
266
|
+
continue;
|
|
267
|
+
const [x0, x1, yTop, yBottom] = rect;
|
|
268
|
+
const matches = (m) => m !== null &&
|
|
269
|
+
m.id === seriesId &&
|
|
270
|
+
m.key === ss.begin[b] &&
|
|
271
|
+
m.label === ss.groups[g];
|
|
272
|
+
const selected = matches(selection);
|
|
273
|
+
const isHovered = matches(hover);
|
|
274
|
+
// A hovered / selected segment pops to full opacity in its own colour; a
|
|
275
|
+
// resting one draws at the shared alpha.
|
|
276
|
+
ctx.globalAlpha = selected || isHovered ? 1 : style.opacity;
|
|
277
|
+
ctx.fillStyle = style.fills[g];
|
|
278
|
+
ctx.fillRect(x0, yTop, x1 - x0, yBottom - yTop);
|
|
279
|
+
if (selected) {
|
|
280
|
+
ctx.lineWidth = style.outlineWidth;
|
|
281
|
+
ctx.strokeStyle = style.fills[g];
|
|
282
|
+
ctx.strokeRect(x0, yTop, x1 - x0, yBottom - yTop);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
ctx.restore();
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* Hit-test plot-pixel `(px, py)` against `ss`'s stacked segments — the **first**
|
|
290
|
+
* segment whose rect contains the point, or `null`. The geometry is
|
|
291
|
+
* {@link segmentRect}, so the hit rect is exactly the drawn rect. The returned
|
|
292
|
+
* tuple is `[bin, group, begin, groupName, value]` for the chart to assemble a
|
|
293
|
+
* `SelectInfo` (it owns the colour). Orientation-agnostic — it reads `(px, py)`,
|
|
294
|
+
* so a horizontal histogram hit-tests the same way a vertical one does.
|
|
295
|
+
*
|
|
296
|
+
* O(N·G) over bins × groups (no spatial index — histogram bin/group counts are
|
|
297
|
+
* small; click / hover are cheap events).
|
|
298
|
+
*/
|
|
299
|
+
export function stackAt(ss, px, py, orientation, xScale, yScale, gapPx, minSpanPx) {
|
|
300
|
+
const G = ss.groups.length;
|
|
301
|
+
for (let b = 0; b < ss.length; b += 1) {
|
|
302
|
+
let cum = 0;
|
|
303
|
+
for (let g = 0; g < G; g += 1) {
|
|
304
|
+
const rect = segmentRect(ss, b, g, orientation, xScale, yScale, cum, gapPx, minSpanPx);
|
|
305
|
+
const v = ss.values[b * G + g];
|
|
306
|
+
if (Number.isFinite(v) && v > 0)
|
|
307
|
+
cum += v;
|
|
308
|
+
if (rect === null)
|
|
309
|
+
continue;
|
|
310
|
+
const [x0, x1, yTop, yBottom] = rect;
|
|
311
|
+
if (px >= x0 && px <= x1 && py >= yTop && py <= yBottom) {
|
|
312
|
+
return [b, g, ss.begin[b], ss.groups[g], v];
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
171
318
|
//# sourceMappingURL=bars.js.map
|
package/dist/context.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ScaleLinear, ScaleTime } from 'd3-scale';
|
|
2
2
|
import type { ChartTheme } from './theme.js';
|
|
3
3
|
import type { AxisFormat } from './format.js';
|
|
4
|
+
import type { TradingTimeScale, DiscontinuityProvider } from './tradingTimeScale.js';
|
|
4
5
|
/**
|
|
5
6
|
* The frame a {@link ChartContainer} provides to its rows and the time axis.
|
|
6
7
|
* The container owns the **shared x geometry**: each side is split into *slots*
|
|
@@ -65,11 +66,11 @@ export interface ContainerFrame {
|
|
|
65
66
|
readonly crosshairSnap: boolean;
|
|
66
67
|
/**
|
|
67
68
|
* The selected mark, or `null`. Shared across rows (single selection). A layer
|
|
68
|
-
* highlights the mark matching
|
|
69
|
-
*
|
|
69
|
+
* highlights the mark matching the selection's series **`id`** and the clicked
|
|
70
|
+
* sample `key` (epoch ms) — the `id` picks the series (so two series sharing a
|
|
71
|
+
* timestamp don't both light up), the `key` picks the mark within it. A
|
|
70
72
|
* controlled `selected` prop pins it; otherwise a click on a selectable layer
|
|
71
|
-
*
|
|
72
|
-
* multi-series Bar/Scatter can target the exact clicked mark.
|
|
73
|
+
* (one with an `id`) sets it.
|
|
73
74
|
*/
|
|
74
75
|
readonly selected: SelectInfo | null;
|
|
75
76
|
/**
|
|
@@ -86,12 +87,13 @@ export interface ContainerFrame {
|
|
|
86
87
|
* from the committed `selected`. A row's pointer-move surface hit-tests its
|
|
87
88
|
* selectable layers and sets it; a layer that supports hover-highlight (Bar)
|
|
88
89
|
* draws the matching mark lit (a lighter treatment than `selected`'s outline).
|
|
89
|
-
* Set-on-change (deduped by key
|
|
90
|
-
* mark transition, not every pointer move.
|
|
90
|
+
* Set-on-change (deduped by the series `id` + sample `key`) so the data canvas
|
|
91
|
+
* repaints only on a mark transition, not every pointer move.
|
|
91
92
|
*/
|
|
92
93
|
readonly hovered: SelectInfo | null;
|
|
93
94
|
/** Set the hovered mark (or `null` to clear) from a pointer-move hit-test;
|
|
94
|
-
* deduped
|
|
95
|
+
* deduped by series `id` + sample `key`, so an unchanged mark is a no-op
|
|
96
|
+
* (no repaint). */
|
|
95
97
|
setHovered(hit: SelectInfo | null): void;
|
|
96
98
|
/** The default in-chart cursor presentation for all rows ({@link CursorMode});
|
|
97
99
|
* a row may override it via its own `cursor`. */
|
|
@@ -116,6 +118,14 @@ export interface ContainerFrame {
|
|
|
116
118
|
*/
|
|
117
119
|
registerTrackerSource(key: symbol, source: TrackerSource): void;
|
|
118
120
|
unregisterTrackerSource(key: symbol): void;
|
|
121
|
+
/**
|
|
122
|
+
* Register this layer as **selectable** — a layer calls this (keyed by its
|
|
123
|
+
* per-instance slot) only when it was given an `id`, so the container knows at
|
|
124
|
+
* least one series can be selected. Powers the dev-warn when `selected` /
|
|
125
|
+
* `onSelect` are wired but no layer carries an `id`. Unregister on unmount.
|
|
126
|
+
*/
|
|
127
|
+
registerSelectable(key: symbol): void;
|
|
128
|
+
unregisterSelectable(key: symbol): void;
|
|
119
129
|
/**
|
|
120
130
|
* Shared x→pixel scale, range `[0, plotWidth]`. A d3 `scaleTime` (default) so
|
|
121
131
|
* ticks land on wall-clock boundaries, or a `scaleLinear` when the data is
|
|
@@ -124,9 +134,20 @@ export interface ContainerFrame {
|
|
|
124
134
|
* scales are callable
|
|
125
135
|
* (`value → px`) and expose `invert`/`ticks`/`tickFormat`; consumers use only
|
|
126
136
|
* that shared surface (the cursor coerces `invert` via `+`, `<TimeAxis>` keys
|
|
127
|
-
* ticks via `+d`), so either kind drops in.
|
|
137
|
+
* ticks via `+d`), so either kind drops in. A **`scaleTradingTime`** (when the
|
|
138
|
+
* container is given `discontinuities`) is the third kind — same callable /
|
|
139
|
+
* `invert` / `ticks` / `tickFormat` surface, but the mapping runs through
|
|
140
|
+
* trading time so closed-market gaps collapse (see {@link discontinuities}).
|
|
128
141
|
*/
|
|
129
|
-
readonly xScale: ScaleTime<number, number> | ScaleLinear<number, number
|
|
142
|
+
readonly xScale: ScaleTime<number, number> | ScaleLinear<number, number> | TradingTimeScale;
|
|
143
|
+
/**
|
|
144
|
+
* The discontinuity provider backing a **trading-time** x axis, if one was
|
|
145
|
+
* supplied to the container — closed-market time (weekends, holidays,
|
|
146
|
+
* overnight, lunch breaks) collapsed. `undefined` for a normal continuous
|
|
147
|
+
* time / value axis. Pan and zoom read it to move the view in *trading* time
|
|
148
|
+
* rather than raw wall-clock ms.
|
|
149
|
+
*/
|
|
150
|
+
readonly discontinuities?: DiscontinuityProvider | undefined;
|
|
130
151
|
/**
|
|
131
152
|
* The resolved kind of the shared x scale — `'time'` (a `scaleTime`) or
|
|
132
153
|
* `'value'` (a `scaleLinear`), inferred from the layers' data. `<XAxis>` reads
|
|
@@ -324,9 +345,12 @@ export interface RowLayer {
|
|
|
324
345
|
/**
|
|
325
346
|
* Hit-test plot-pixel `(px, py)` against this layer's marks for click
|
|
326
347
|
* selection — the select-analog of {@link sampleAt}. Returns the hit mark or
|
|
327
|
-
* `null`. **Optional
|
|
328
|
-
*
|
|
329
|
-
*
|
|
348
|
+
* `null`. **Optional, and gated on the layer's `id`:** a layer only wires
|
|
349
|
+
* `hitTest` when it was given an `id` (the series identity). Layers without an
|
|
350
|
+
* `id` — or without discrete selectable marks (line, band, area) — omit it,
|
|
351
|
+
* so they render + read out but never select/hover (a click on them resolves
|
|
352
|
+
* to empty space ⇒ deselect). `xScale`/`yScale` map data→pixels (the row
|
|
353
|
+
* resolves the layer's axis scale, as for `draw`).
|
|
330
354
|
*/
|
|
331
355
|
hitTest?(px: number, py: number, xScale: (value: number) => number, yScale: (value: number) => number): SelectInfo | null;
|
|
332
356
|
/** Draw into the plot canvas. `xScale`/`yScale` map data→pixels. */
|
|
@@ -374,19 +398,30 @@ export interface TrackerSource {
|
|
|
374
398
|
xExtent(): readonly [number, number] | null;
|
|
375
399
|
}
|
|
376
400
|
/**
|
|
377
|
-
* One
|
|
378
|
-
*
|
|
379
|
-
*
|
|
380
|
-
*
|
|
401
|
+
* One selection — what {@link RowLayer.hitTest} returns and `onSelect` reports.
|
|
402
|
+
* Selection identity is the **series `id`**, not the sample: `key`/`value` are
|
|
403
|
+
* click **provenance** (the nearest sample under the pointer, informational);
|
|
404
|
+
* equality, dedup, and the controlled echo all key on `id`. Because `id` is a
|
|
405
|
+
* stable series identity — distinct from the `as` theme role, which can repeat —
|
|
406
|
+
* a selection survives a streaming data update where a sample `key` would go
|
|
407
|
+
* stale. Only layers that carry an `id` are selectable (see {@link RowLayer.hitTest}).
|
|
381
408
|
*/
|
|
382
409
|
export interface SelectInfo {
|
|
383
|
-
/**
|
|
410
|
+
/**
|
|
411
|
+
* The **series identity** — the layer's `id` prop. The selection / dedup /
|
|
412
|
+
* controlled-echo key; stable across data updates (unlike {@link key}).
|
|
413
|
+
*/
|
|
414
|
+
readonly id: string;
|
|
415
|
+
/**
|
|
416
|
+
* The clicked sample's key as epoch ms (its event's `begin`) — click
|
|
417
|
+
* **provenance**, informational. NOT the selection identity (that is {@link id}).
|
|
418
|
+
*/
|
|
384
419
|
readonly key: number;
|
|
385
|
-
/** The
|
|
420
|
+
/** The clicked sample's value (the plotted column) — provenance. */
|
|
386
421
|
readonly value: number;
|
|
387
422
|
/** The mark's resolved style colour. */
|
|
388
423
|
readonly color: string;
|
|
389
|
-
/**
|
|
424
|
+
/** Display label (`as` ?? column ?? id) — labels the selection in a readout. */
|
|
390
425
|
readonly label: string;
|
|
391
426
|
}
|
|
392
427
|
/** The hover snapshot handed to `onTrackerChanged` — the cursor time + every
|
package/dist/data.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ValueSeries } from 'pond-ts';
|
|
2
|
+
import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
|
|
2
3
|
/**
|
|
3
4
|
* A chart-ready columnar view of a series: parallel typed arrays for the time
|
|
4
5
|
* (x) and value (y) axes, plus the logical row count.
|
|
@@ -53,6 +54,32 @@ export interface BoxSeries {
|
|
|
53
54
|
readonly upper: Float64Array;
|
|
54
55
|
readonly length: number;
|
|
55
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* A chart-ready view of an OHLC series ({@link Candlestick}): the candle's
|
|
59
|
+
* horizontal slot (`x` = left edge, `xEnd` = right edge) plus the four price
|
|
60
|
+
* channels per mark — `open`/`high`/`low`/`close`. The chart derives the body
|
|
61
|
+
* extents (`min`/`max` of open/close) itself at draw time; only the four raw
|
|
62
|
+
* columns are read here.
|
|
63
|
+
*
|
|
64
|
+
* A mark is drawn only where **all four** prices are finite; any one `NaN` is a
|
|
65
|
+
* gap (the candle draws nothing — same gap contract as {@link BoxSeries}).
|
|
66
|
+
*
|
|
67
|
+
* Unlike {@link BoxSeries} (interval-keyed only), the OHLC view supports **both**
|
|
68
|
+
* key shapes, like {@link BarSeries}: an **interval**-keyed series (an
|
|
69
|
+
* `aggregate` rollup — weekly/monthly bars) uses the key's own `[begin, end)` as
|
|
70
|
+
* the slot; a **point**-keyed series (raw daily OHLCV) derives the slot from
|
|
71
|
+
* neighbour spacing (see {@link ohlcFromTimeSeries}), so it feeds straight in
|
|
72
|
+
* with no `aggregate` pass.
|
|
73
|
+
*/
|
|
74
|
+
export interface OhlcSeries {
|
|
75
|
+
readonly x: Float64Array;
|
|
76
|
+
readonly xEnd: Float64Array;
|
|
77
|
+
readonly open: Float64Array;
|
|
78
|
+
readonly high: Float64Array;
|
|
79
|
+
readonly low: Float64Array;
|
|
80
|
+
readonly close: Float64Array;
|
|
81
|
+
readonly length: number;
|
|
82
|
+
}
|
|
56
83
|
/**
|
|
57
84
|
* A chart-ready view of an interval-keyed series for bars: each mark spans
|
|
58
85
|
* `[begin[i], end[i]]` (the key's range) with height `y[i]`. Unlike
|
|
@@ -70,6 +97,32 @@ export interface BarSeries {
|
|
|
70
97
|
readonly y: Float64Array;
|
|
71
98
|
readonly length: number;
|
|
72
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* A chart-ready view of a **stacked / histogram** bar series — the multi-segment
|
|
102
|
+
* generalization of {@link BarSeries}. Each of the `length` bins spans
|
|
103
|
+
* `[begin[i], end[i]]` on the **bin axis** (time ms, a value, or a band edge) and
|
|
104
|
+
* carries one value per `group` (a stack segment). `groups` lists the segment
|
|
105
|
+
* identities **bottom → top**; `values` is a flat `length × groups.length` grid
|
|
106
|
+
* in **row-major** order, so bin `b`'s segment `g` is `values[b * groups.length + g]`.
|
|
107
|
+
*
|
|
108
|
+
* A single-series bar (the {@link BarSeries} case) is just `groups.length === 1`.
|
|
109
|
+
* Missing / non-finite segment values are `NaN` — the gap signal a stack skips
|
|
110
|
+
* (no segment, and it contributes nothing to the running total), the same
|
|
111
|
+
* `Number.isFinite` contract as {@link BarSeries}. Segment values are assumed
|
|
112
|
+
* **non-negative** (counts / durations); a negative value is treated as a gap
|
|
113
|
+
* (diverging stacks are out of scope — see the histogram guide).
|
|
114
|
+
*
|
|
115
|
+
* The bin axis is x for a **vertical** histogram (bars grow up) and y for a
|
|
116
|
+
* **horizontal** one (bars grow right); the same grid drives both — the draw
|
|
117
|
+
* layer transposes by orientation, the data does not change.
|
|
118
|
+
*/
|
|
119
|
+
export interface StackedBarSeries {
|
|
120
|
+
readonly begin: Float64Array;
|
|
121
|
+
readonly end: Float64Array;
|
|
122
|
+
readonly groups: readonly string[];
|
|
123
|
+
readonly values: Float64Array;
|
|
124
|
+
readonly length: number;
|
|
125
|
+
}
|
|
73
126
|
/** The five quantile column names a {@link boxFromTimeSeries} reads, in order. */
|
|
74
127
|
export interface BoxColumns {
|
|
75
128
|
/** Lower whisker end (e.g. `p5` / `min`). */
|
|
@@ -83,6 +136,17 @@ export interface BoxColumns {
|
|
|
83
136
|
/** Upper whisker end (e.g. `p95` / `max`). */
|
|
84
137
|
readonly upper: string;
|
|
85
138
|
}
|
|
139
|
+
/** The four OHLC column names {@link ohlcFromTimeSeries} reads. */
|
|
140
|
+
export interface OhlcColumns {
|
|
141
|
+
/** Opening price column. */
|
|
142
|
+
readonly open: string;
|
|
143
|
+
/** Session high column. */
|
|
144
|
+
readonly high: string;
|
|
145
|
+
/** Session low column. */
|
|
146
|
+
readonly low: string;
|
|
147
|
+
/** Closing price column. */
|
|
148
|
+
readonly close: string;
|
|
149
|
+
}
|
|
86
150
|
/**
|
|
87
151
|
* Build a {@link ChartSeries} from a pond `TimeSeries` by reading its columnar
|
|
88
152
|
* buffers directly — no per-event materialization. `column` names a numeric
|
|
@@ -143,6 +207,26 @@ export declare function bandFromValueSeries<VS extends ValueSeriesSchema>(series
|
|
|
143
207
|
* @throws TypeError if any quantile column is not a numeric column.
|
|
144
208
|
*/
|
|
145
209
|
export declare function boxFromTimeSeries<S extends SeriesSchema>(series: TimeSeries<S>, columns: BoxColumns): BoxSeries;
|
|
210
|
+
/**
|
|
211
|
+
* Build an {@link OhlcSeries} from a pond `TimeSeries` — four numeric price
|
|
212
|
+
* columns (`open`/`high`/`low`/`close`) plus the candle's horizontal slot.
|
|
213
|
+
*
|
|
214
|
+
* **Key-shape aware, like {@link barsFromTimeSeries}.** An **interval /
|
|
215
|
+
* timeRange**-keyed series (an `aggregate` rollup — weekly / monthly bars) uses
|
|
216
|
+
* the key's own `[begin, end)` as the slot. A **point**-keyed (`time`) series —
|
|
217
|
+
* raw daily OHLCV — has `begin === end` (zero width), so the slot is derived from
|
|
218
|
+
* neighbour spacing (each candle centred on its timestamp, reaching halfway to
|
|
219
|
+
* each neighbour; see {@link neighbourSpans}). This is the ergonomic win over the
|
|
220
|
+
* interval-only {@link boxFromTimeSeries}: raw OHLC feeds straight in with no
|
|
221
|
+
* `aggregate` pass.
|
|
222
|
+
*
|
|
223
|
+
* A key with any of the four prices missing reads as a gap (the candle draws
|
|
224
|
+
* nothing). Detected by `keyColumn().kind === 'time'`.
|
|
225
|
+
*
|
|
226
|
+
* @throws RangeError if any price column does not exist.
|
|
227
|
+
* @throws TypeError if any price column is not a numeric column.
|
|
228
|
+
*/
|
|
229
|
+
export declare function ohlcFromTimeSeries<S extends SeriesSchema>(series: TimeSeries<S>, columns: OhlcColumns): OhlcSeries;
|
|
146
230
|
/**
|
|
147
231
|
* Build a {@link BarSeries} from a pond `TimeSeries` — one bar per event, the
|
|
148
232
|
* key's `[begin, end]` as the x-span and `column` as the height.
|
|
@@ -185,4 +269,78 @@ export declare function barsFromTimeSeries<S extends SeriesSchema>(series: TimeS
|
|
|
185
269
|
* @throws TypeError if `column` is not a numeric column.
|
|
186
270
|
*/
|
|
187
271
|
export declare function barsFromValueSeries<VS extends ValueSeriesSchema>(series: ValueSeries<VS>, column: string): BarSeries;
|
|
272
|
+
/**
|
|
273
|
+
* Build a {@link StackedBarSeries} from a **`Map` of grouped series** — one series
|
|
274
|
+
* per stack group. This is the natural reader for pond's grouped-aggregate output:
|
|
275
|
+
* `series.partitionBy('host', { groups }).aggregate(Sequence.every('5m'), { n: 'count' }).toMap()`
|
|
276
|
+
* yields a `Map<host, TimeSeries>`, one interval-keyed series per host. The stack
|
|
277
|
+
* order (`groups`, bottom → top) is the map's **insertion order** (stable when you
|
|
278
|
+
* pass `partitionBy`'s `{ groups }` option).
|
|
279
|
+
*
|
|
280
|
+
* **Aligned by bucket key, not by index.** Each partition's `aggregate` spans only
|
|
281
|
+
* *its own* events' range, so the groups generally have **different** grids (host A
|
|
282
|
+
* might have buckets 0–8, host B buckets 3–9). This reader takes the **union** of
|
|
283
|
+
* every group's `[begin, end)` slots (ascending) and places each group's `column`
|
|
284
|
+
* value at the matching `begin`; a bucket a group is missing reads as a gap
|
|
285
|
+
* (`NaN`, contributing nothing to that stack). So the segments always line up on
|
|
286
|
+
* the real bucket, never on a positional accident. (Pass `aggregate`'s
|
|
287
|
+
* `{ range }` option if you want every group padded to one dense grid — the union
|
|
288
|
+
* is then that grid.) When two groups carry the **same `begin`**, the first
|
|
289
|
+
* group's `end` sets that slot's width — correct for the uniform-width buckets
|
|
290
|
+
* `aggregate` / `pivotByGroup` produce (all groups share the grid width), which is
|
|
291
|
+
* the intended input.
|
|
292
|
+
*
|
|
293
|
+
* @throws Error if `groups` is empty.
|
|
294
|
+
* @throws RangeError / TypeError (via {@link readNumericColumn}) if `column` is
|
|
295
|
+
* missing or non-numeric in any member.
|
|
296
|
+
*/
|
|
297
|
+
export declare function stacksFromGroups<S extends SeriesSchema>(groups: ReadonlyMap<string, TimeSeries<S>>, column: string): StackedBarSeries;
|
|
298
|
+
/**
|
|
299
|
+
* Build a {@link StackedBarSeries} from a **wide** series — one numeric column
|
|
300
|
+
* per stack group. This is the reader for pond's `pivotByGroup` output (long →
|
|
301
|
+
* wide reshape: each group value becomes its own column), or any series that is
|
|
302
|
+
* already wide (e.g. `in` / `out` traffic). `columns` names the segment columns
|
|
303
|
+
* **bottom → top**; a `ValueSeries` bins on its value axis (neighbour-spaced
|
|
304
|
+
* slots), a `TimeSeries` on its key (interval spans or neighbour-spaced points).
|
|
305
|
+
*
|
|
306
|
+
* @throws RangeError / TypeError if any column is missing or non-numeric.
|
|
307
|
+
*/
|
|
308
|
+
export declare function stacksFromColumns<S extends SeriesSchema, VS extends ValueSeriesSchema>(series: TimeSeries<S> | ValueSeries<VS>, columns: readonly string[]): StackedBarSeries;
|
|
309
|
+
/**
|
|
310
|
+
* A single bin record from `byColumn` — its `[start, end)` range plus the mapped
|
|
311
|
+
* aggregate columns (read by name via {@link stacksFromBins}). Deliberately just
|
|
312
|
+
* the `start`/`end` shape (no index signature) so pond's
|
|
313
|
+
* `byColumn(...): Array<{ start, end } & ReduceResult>` assigns to it structurally
|
|
314
|
+
* — the aggregate fields ride along and are read out by the reader.
|
|
315
|
+
*/
|
|
316
|
+
export type BinRecord = {
|
|
317
|
+
readonly start: number;
|
|
318
|
+
readonly end: number;
|
|
319
|
+
};
|
|
320
|
+
/** Options for {@link stacksFromBins}. */
|
|
321
|
+
export interface StacksFromBinsOptions {
|
|
322
|
+
/**
|
|
323
|
+
* Use uniform **unit slots** (`[i, i+1]`) for the bins instead of their numeric
|
|
324
|
+
* `[start, end]` edges — an **ordinal** band axis (heart-rate zones, Coggan
|
|
325
|
+
* power zones) where every band reads the same width regardless of its numeric
|
|
326
|
+
* span. The caller labels the slots via `<YAxis ticks>` at `i + 0.5`. Omitted /
|
|
327
|
+
* `false` ⇒ real numeric edges (a true value axis — power W, risk %).
|
|
328
|
+
*/
|
|
329
|
+
readonly ordinal?: boolean;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Build a {@link StackedBarSeries} from **`byColumn` bin records** — the array of
|
|
333
|
+
* `{ start, end, …aggregates }` a value-band aggregation returns
|
|
334
|
+
* (`series.byColumn('power', { width: 20 }, { seconds: { from: 'dt', using: 'sum' } })`).
|
|
335
|
+
* `columns` names the aggregate field(s) to draw as segments (`['seconds']` for a
|
|
336
|
+
* plain distribution; several for a stacked value-band histogram).
|
|
337
|
+
*
|
|
338
|
+
* By default each bin keeps its real numeric `[start, end]` edges — a true value
|
|
339
|
+
* axis (power W, risk %). Pass `{ ordinal: true }` for uniform unit slots
|
|
340
|
+
* (`[i, i+1]`) when the bins are **categories** whose numeric width shouldn't
|
|
341
|
+
* distort the layout (heart-rate zones); label them with `<YAxis ticks>`.
|
|
342
|
+
*
|
|
343
|
+
* A missing / non-finite aggregate reads as a gap (`NaN`).
|
|
344
|
+
*/
|
|
345
|
+
export declare function stacksFromBins(bins: readonly BinRecord[], columns: readonly string[], options?: StacksFromBinsOptions): StackedBarSeries;
|
|
188
346
|
//# sourceMappingURL=data.d.ts.map
|