@pond-ts/charts 0.54.0 → 0.55.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 +312 -1
- package/dist/AreaChart.d.ts +29 -22
- package/dist/BandChart.d.ts +29 -17
- package/dist/BarChart.d.ts +92 -49
- package/dist/BarChart.js +65 -8
- package/dist/BarList.d.ts +127 -0
- package/dist/BarList.js +84 -0
- package/dist/BoxList.d.ts +108 -0
- package/dist/BoxList.js +125 -0
- package/dist/BoxPlot.d.ts +63 -30
- package/dist/Candlestick.d.ts +5 -4
- package/dist/Layers.js +8 -1
- package/dist/LineChart.d.ts +29 -26
- package/dist/ListTable.d.ts +51 -0
- package/dist/ListTable.js +143 -0
- package/dist/ScatterChart.d.ts +27 -17
- package/dist/YAxis.js +15 -4
- package/dist/bars.d.ts +73 -18
- package/dist/bars.js +119 -28
- package/dist/column-names.d.ts +74 -0
- package/dist/column-names.js +2 -0
- package/dist/context.d.ts +22 -0
- package/dist/data.d.ts +19 -0
- package/dist/data.js +46 -0
- package/dist/index.d.ts +19 -13
- package/dist/index.js +21 -13
- package/dist/list-source.d.ts +61 -0
- package/dist/list-source.js +5 -0
- package/dist/list.d.ts +205 -0
- package/dist/list.js +165 -0
- package/dist/theme.d.ts +30 -0
- package/dist/theme.js +24 -0
- package/package.json +3 -3
package/dist/BoxPlot.d.ts
CHANGED
|
@@ -1,37 +1,9 @@
|
|
|
1
1
|
import { ValueSeries } from 'pond-ts';
|
|
2
2
|
import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
|
|
3
|
+
import type { NumericColumn, ValueNumericColumn } from './column-names.js';
|
|
3
4
|
import { type BoxShape } from './box.js';
|
|
4
5
|
import type { DecimateOption } from './decimate.js';
|
|
5
|
-
export interface
|
|
6
|
-
/**
|
|
7
|
-
* The source series. A `TimeSeries` plots against the time axis; a `ValueSeries`
|
|
8
|
-
* (`series.byValue('strike')`, or `ValueSeries.fromColumns` for natively
|
|
9
|
-
* value-keyed data — a per-strike IV distribution) against its value axis — the
|
|
10
|
-
* container infers which from the data, no axis-type prop (mirrors `<LineChart>`
|
|
11
|
-
* / `<ScatterChart>`). The box x-span is the key's `[begin, end)` for an
|
|
12
|
-
* interval-keyed `TimeSeries`, else synthesized from neighbour spacing (a
|
|
13
|
-
* point-keyed `TimeSeries`, or a `ValueSeries`) so the box keeps real width.
|
|
14
|
-
*/
|
|
15
|
-
series: TimeSeries<S> | ValueSeries<VS>;
|
|
16
|
-
/** Name of the numeric column for the lower whisker end (e.g. `p5` / `min`).
|
|
17
|
-
* **Required** — with `upper` it's the whisker reach. */
|
|
18
|
-
lower: string;
|
|
19
|
-
/**
|
|
20
|
-
* Name of the numeric column for the box bottom — first quartile (e.g. `p25`).
|
|
21
|
-
* **Optional:** omit `q1` **and** `q3` together for a **range-only** box — a
|
|
22
|
-
* whisker-only `lower→upper` segment, no body (a bid→ask IV mark). Giving just
|
|
23
|
-
* one of `q1`/`q3` throws.
|
|
24
|
-
*/
|
|
25
|
-
q1?: string;
|
|
26
|
-
/** Name of the numeric column for the median line (e.g. `p50`). **Optional** —
|
|
27
|
-
* omit for no centre line (independent of the box body). */
|
|
28
|
-
median?: string;
|
|
29
|
-
/** Name of the numeric column for the box top — third quartile (e.g. `p75`).
|
|
30
|
-
* **Optional** — omit with `q1` for a range-only box (see `q1`). */
|
|
31
|
-
q3?: string;
|
|
32
|
-
/** Name of the numeric column for the upper whisker end (e.g. `p95` / `max`).
|
|
33
|
-
* **Required** — with `lower` it's the whisker reach. */
|
|
34
|
-
upper: string;
|
|
6
|
+
export interface BoxPlotCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
35
7
|
/**
|
|
36
8
|
* The box series' semantic identifier — what the spread _is_ (e.g. `latency`).
|
|
37
9
|
* The theme maps it to a {@link BoxStyle} (`theme.box[as] ?? theme.box.default`
|
|
@@ -122,6 +94,66 @@ export interface BoxPlotProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
122
94
|
*/
|
|
123
95
|
index?: number;
|
|
124
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* BoxPlot's source + column props, a **union over the series kind** so the
|
|
99
|
+
* column names are checked against the schema that was actually passed
|
|
100
|
+
* ([PND-CHARTAPI]). A single member carrying `NumericColumn<S> |
|
|
101
|
+
* ValueNumericColumn<VS>` would silently widen to `string`: only one of the
|
|
102
|
+
* two generics is ever inferred, and the other falls back (measured in
|
|
103
|
+
* `spikes/charts-type-seam/`). Loosely-typed series still accept any name.
|
|
104
|
+
*/
|
|
105
|
+
type BoxPlotSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = {
|
|
106
|
+
/**
|
|
107
|
+
* The source series. **Live charts:** `series.byValue(…)` mints a
|
|
108
|
+
* *fresh* projection each call, so an inline `series={s.byValue('d')}`
|
|
109
|
+
* re-registers this layer every render — on a frequently re-rendering
|
|
110
|
+
* (e.g. scrub-driven) chart, memoize the projection (`useMemo`) so the
|
|
111
|
+
* layer isn't rebuilt each frame.
|
|
112
|
+
*/
|
|
113
|
+
series: TimeSeries<S>;
|
|
114
|
+
/** Lower whisker end (e.g. `p5` / `min`). **Required** — with `upper`
|
|
115
|
+
* it is the whisker reach. */
|
|
116
|
+
lower: NumericColumn<S>;
|
|
117
|
+
/**
|
|
118
|
+
* Box bottom — first quartile (e.g. `p25`). **Optional:** omit `q1`
|
|
119
|
+
* **and** `q3` together for a **range-only** box (a whisker-only
|
|
120
|
+
* `lower→upper` segment, no body — a bid→ask IV mark). Giving just one
|
|
121
|
+
* of the pair throws.
|
|
122
|
+
*/
|
|
123
|
+
q1?: NumericColumn<S>;
|
|
124
|
+
/** Median line (e.g. `p50`). **Optional** — omit for no centre line
|
|
125
|
+
* (independent of the box body). */
|
|
126
|
+
median?: NumericColumn<S>;
|
|
127
|
+
/** Box top — third quartile (e.g. `p75`). **Optional** — omit with `q1`
|
|
128
|
+
* for a range-only box. */
|
|
129
|
+
q3?: NumericColumn<S>;
|
|
130
|
+
/** Upper whisker end (e.g. `p95` / `max`). **Required** — with `lower`
|
|
131
|
+
* it is the whisker reach. */
|
|
132
|
+
upper: NumericColumn<S>;
|
|
133
|
+
} | {
|
|
134
|
+
series: ValueSeries<VS>;
|
|
135
|
+
/** Lower whisker end (e.g. `p5` / `min`). **Required** — with `upper`
|
|
136
|
+
* it is the whisker reach. */
|
|
137
|
+
lower: ValueNumericColumn<VS>;
|
|
138
|
+
/**
|
|
139
|
+
* Box bottom — first quartile (e.g. `p25`). **Optional:** omit `q1`
|
|
140
|
+
* **and** `q3` together for a **range-only** box (a whisker-only
|
|
141
|
+
* `lower→upper` segment, no body — a bid→ask IV mark). Giving just one
|
|
142
|
+
* of the pair throws.
|
|
143
|
+
*/
|
|
144
|
+
q1?: ValueNumericColumn<VS>;
|
|
145
|
+
/** Median line (e.g. `p50`). **Optional** — omit for no centre line
|
|
146
|
+
* (independent of the box body). */
|
|
147
|
+
median?: ValueNumericColumn<VS>;
|
|
148
|
+
/** Box top — third quartile (e.g. `p75`). **Optional** — omit with `q1`
|
|
149
|
+
* for a range-only box. */
|
|
150
|
+
q3?: ValueNumericColumn<VS>;
|
|
151
|
+
/** Upper whisker end (e.g. `p95` / `max`). **Required** — with `lower`
|
|
152
|
+
* it is the whisker reach. */
|
|
153
|
+
upper: ValueNumericColumn<VS>;
|
|
154
|
+
};
|
|
155
|
+
/** `<BoxPlot>`'s props: the shared knobs plus one series-kind source shape. */
|
|
156
|
+
export type BoxPlotProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = BoxPlotCommon<S, VS> & BoxPlotSource<S, VS>;
|
|
125
157
|
/**
|
|
126
158
|
* A discrete box-and-whisker draw layer — the bar-chart analog of the variance
|
|
127
159
|
* band. Reads **pre-computed quantile columns** of `series` (typically a
|
|
@@ -154,4 +186,5 @@ export interface BoxPlotProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
154
186
|
* ```
|
|
155
187
|
*/
|
|
156
188
|
export declare function BoxPlot<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, lower, q1, median, q3, upper, as: semantic, axis, gap, shape, showMedian, offset, capWidth, id, decimate, legend, index, }: BoxPlotProps<S, VS>): null;
|
|
189
|
+
export {};
|
|
157
190
|
//# sourceMappingURL=BoxPlot.d.ts.map
|
package/dist/Candlestick.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { SeriesSchema, TimeSeries } from 'pond-ts';
|
|
2
|
+
import type { NumericColumn } from './column-names.js';
|
|
2
3
|
import type { DecimateOption } from './decimate.js';
|
|
3
4
|
import { type CandleVariant, type ColorBy } from './ohlc.js';
|
|
4
5
|
export interface CandlestickProps<S extends SeriesSchema> {
|
|
@@ -12,13 +13,13 @@ export interface CandlestickProps<S extends SeriesSchema> {
|
|
|
12
13
|
*/
|
|
13
14
|
series: TimeSeries<S>;
|
|
14
15
|
/** Opening-price column. **Omitted ⇒ `'open'`.** */
|
|
15
|
-
open?:
|
|
16
|
+
open?: NumericColumn<S>;
|
|
16
17
|
/** Session-high column. **Omitted ⇒ `'high'`.** */
|
|
17
|
-
high?:
|
|
18
|
+
high?: NumericColumn<S>;
|
|
18
19
|
/** Session-low column. **Omitted ⇒ `'low'`.** */
|
|
19
|
-
low?:
|
|
20
|
+
low?: NumericColumn<S>;
|
|
20
21
|
/** Closing-price column. **Omitted ⇒ `'close'`.** */
|
|
21
|
-
close?:
|
|
22
|
+
close?: NumericColumn<S>;
|
|
22
23
|
/**
|
|
23
24
|
* The series' semantic identifier — what the data _is_ (e.g. a ticker). The
|
|
24
25
|
* theme maps it to a {@link CandleStyle} (`theme.candle[as] ??
|
package/dist/Layers.js
CHANGED
|
@@ -101,6 +101,13 @@ export function Layers({ children }) {
|
|
|
101
101
|
// A category axis draws no vertical gridlines — a line through each bar
|
|
102
102
|
// centre reads as noise; the bars are the structure.
|
|
103
103
|
const xTickVals = container.xKind === 'category' ? [] : xScale.ticks(xTickCount);
|
|
104
|
+
// The same rule on the other axis: a **horizontal** categorical chart
|
|
105
|
+
// ([PND-HCAT]) puts its categories on y, where the `<YAxis>` labels slot
|
|
106
|
+
// *centres* while d3's auto ticks fall on slot *boundaries* — so drawing
|
|
107
|
+
// them would both mismatch the labels and stripe each bar. Suppressed,
|
|
108
|
+
// exactly as the categorical x axis already is.
|
|
109
|
+
const yIsCategory = layers.some((e) => (e.axisId ?? defaultAxisId) === defaultAxisId &&
|
|
110
|
+
(e.layer.binCategories?.() ?? null) !== null);
|
|
104
111
|
// The reference grid — behind the data, opt-out via `grid={false}` for
|
|
105
112
|
// a clean backdrop (session dividers below stay independent of it).
|
|
106
113
|
if (container.grid) {
|
|
@@ -108,7 +115,7 @@ export function Layers({ children }) {
|
|
|
108
115
|
// single source, height-derived or an explicit `<YAxis tickCount>`), so
|
|
109
116
|
// a gridline sits under every `<YAxis>` label and no more.
|
|
110
117
|
const yCount = tickCounts.get(defaultAxisId) ?? GRID_TICKS;
|
|
111
|
-
const yTicks = gridY
|
|
118
|
+
const yTicks = gridY && !(yIsCategory && explicitY === undefined)
|
|
112
119
|
? (explicitY ?? gridY.ticks(yCount)).map((t) => gridY(t))
|
|
113
120
|
: [];
|
|
114
121
|
// On a calendar axis the verticals are the FULL grain populations —
|
package/dist/LineChart.d.ts
CHANGED
|
@@ -1,34 +1,10 @@
|
|
|
1
1
|
import { ValueSeries } from 'pond-ts';
|
|
2
2
|
import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
|
|
3
|
+
import type { NumericColumn, ValueNumericColumn } from './column-names.js';
|
|
3
4
|
import type { DecimateOption } from './decimate.js';
|
|
4
5
|
import { type Curve } from './curve.js';
|
|
5
6
|
import { type GapMode } from './gaps.js';
|
|
6
|
-
export interface
|
|
7
|
-
/**
|
|
8
|
-
* The source series. A `TimeSeries` plots against the time axis; a
|
|
9
|
-
* `ValueSeries` (`series.byValue('cumDist')`) against its value axis — the
|
|
10
|
-
* container infers which from the data, no axis-type prop. Either way the key
|
|
11
|
-
* / axis column supplies x and `column` supplies y.
|
|
12
|
-
*
|
|
13
|
-
* **Live charts:** `series.byValue(…)` mints a *fresh* projection each call, so
|
|
14
|
-
* passing `series={s.byValue('dist')}` inline re-registers this layer every
|
|
15
|
-
* render — on a frequently re-rendering (e.g. scrub-driven) chart, memoize the
|
|
16
|
-
* projection (`useMemo`) so the layer isn't rebuilt each frame.
|
|
17
|
-
*/
|
|
18
|
-
series: TimeSeries<S> | ValueSeries<VS>;
|
|
19
|
-
/** Name of the numeric value column to plot. */
|
|
20
|
-
column: string;
|
|
21
|
-
/**
|
|
22
|
-
* Optional column to **read out** at the cursor instead of the plotted
|
|
23
|
-
* `column`. The layer still plots `column`; each tracker sample additionally
|
|
24
|
-
* carries this column's value as {@link TrackerSample.readout}, so an
|
|
25
|
-
* off-chart readout can show the **source** value while the line draws a
|
|
26
|
-
* derived one — a smoothed / transformed / normalized plot with a raw-value
|
|
27
|
-
* readout (estela plots pace-space + Gaussian-smoothed, reads the native m/s).
|
|
28
|
-
* The plotted `value` (hence the in-chart cursor dot) is unchanged.
|
|
29
|
-
* **Omitted ⇒ no readout channel** (`readout` is `undefined` on the sample).
|
|
30
|
-
*/
|
|
31
|
-
readout?: string;
|
|
7
|
+
export interface LineChartCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
32
8
|
/**
|
|
33
9
|
* The series' semantic identifier — what the data _is_ / how it should read
|
|
34
10
|
* (e.g. `heartrate`, `power`, or a role name like `foam`). The theme maps it
|
|
@@ -99,6 +75,32 @@ export interface LineChartProps<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
99
75
|
*/
|
|
100
76
|
index?: number;
|
|
101
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* LineChart's source + column props, a **union over the series kind** so the
|
|
80
|
+
* column names are checked against the schema that was actually passed
|
|
81
|
+
* ([PND-CHARTAPI]). A single member carrying `NumericColumn<S> |
|
|
82
|
+
* ValueNumericColumn<VS>` would silently widen to `string`: only one of the
|
|
83
|
+
* two generics is ever inferred, and the other falls back (measured in
|
|
84
|
+
* `spikes/charts-type-seam/`). Loosely-typed series still accept any name.
|
|
85
|
+
*/
|
|
86
|
+
type LineChartSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = {
|
|
87
|
+
/**
|
|
88
|
+
* The source series. **Live charts:** `series.byValue(…)` mints a
|
|
89
|
+
* *fresh* projection each call, so an inline `series={s.byValue('d')}`
|
|
90
|
+
* re-registers this layer every render — on a frequently re-rendering
|
|
91
|
+
* (e.g. scrub-driven) chart, memoize the projection (`useMemo`) so the
|
|
92
|
+
* layer isn't rebuilt each frame.
|
|
93
|
+
*/
|
|
94
|
+
series: TimeSeries<S>;
|
|
95
|
+
column: NumericColumn<S>;
|
|
96
|
+
readout?: NumericColumn<S>;
|
|
97
|
+
} | {
|
|
98
|
+
series: ValueSeries<VS>;
|
|
99
|
+
column: ValueNumericColumn<VS>;
|
|
100
|
+
readout?: ValueNumericColumn<VS>;
|
|
101
|
+
};
|
|
102
|
+
/** `<LineChart>`'s props: the shared knobs plus one series-kind source shape. */
|
|
103
|
+
export type LineChartProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = LineChartCommon<S, VS> & LineChartSource<S, VS>;
|
|
102
104
|
/**
|
|
103
105
|
* A line draw layer. Reads `column` from `series` into a {@link ChartSeries}
|
|
104
106
|
* (columnar, gaps as NaN), registers itself into the enclosing {@link Layers}
|
|
@@ -106,4 +108,5 @@ export interface LineChartProps<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
106
108
|
* it. The line breaks at gaps rather than spanning them.
|
|
107
109
|
*/
|
|
108
110
|
export declare function LineChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, column, readout, as: semantic, axis, curve, gaps, sessionBreaks, decimate, legend, index, }: LineChartProps<S, VS>): null;
|
|
111
|
+
export {};
|
|
109
112
|
//# sourceMappingURL=LineChart.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @internal The shared row-table shell behind {@link BarList} / {@link BoxList}.
|
|
3
|
+
*
|
|
4
|
+
* Renders a real `<table>` — the point of the list family is table semantics
|
|
5
|
+
* (label cells that can be links, aligned data cells, a `colSpan` detail row
|
|
6
|
+
* for the expander, screen-reader-legible rows), which a canvas plot can't
|
|
7
|
+
* carry and which hand-rolled flex rows re-implement badly (per-row cell
|
|
8
|
+
* alignment is exactly what table layout solves). The glyph cell takes
|
|
9
|
+
* `width: 100%` so it absorbs the free width; every text cell shrinks to fit.
|
|
10
|
+
*
|
|
11
|
+
* Not exported from the package: the public surface is the two sisters, so the
|
|
12
|
+
* shared shell can evolve without a compatibility contract.
|
|
13
|
+
*/
|
|
14
|
+
import { type ReactNode } from 'react';
|
|
15
|
+
import type { ListCellSpec, ListRow } from './list.js';
|
|
16
|
+
import type { ChartTheme } from './theme.js';
|
|
17
|
+
export interface ListTableProps<R extends ListRow> {
|
|
18
|
+
/** Rows in display order (the caller sorts). */
|
|
19
|
+
readonly rows: readonly R[];
|
|
20
|
+
/** Which sister is rendering — stamped as `data-list` for styling/tests. */
|
|
21
|
+
readonly kind: 'bar' | 'box';
|
|
22
|
+
/** The glyph cell's content for one row (the bar / box lines). */
|
|
23
|
+
readonly renderGlyphs: (row: R) => ReactNode;
|
|
24
|
+
readonly before?: readonly ListCellSpec<R>[] | undefined;
|
|
25
|
+
readonly after?: readonly ListCellSpec<R>[] | undefined;
|
|
26
|
+
readonly renderExpanded?: ((row: R) => ReactNode) | undefined;
|
|
27
|
+
readonly defaultExpanded?: readonly string[] | undefined;
|
|
28
|
+
readonly onExpandToggle?: ((key: string, expanded: boolean) => void) | undefined;
|
|
29
|
+
readonly selected?: string | null | undefined;
|
|
30
|
+
readonly onRowClick?: ((row: R) => void) | undefined;
|
|
31
|
+
readonly divided?: boolean | undefined;
|
|
32
|
+
/** Draw the vertical **baseline rule** at the scale origin (the glyph
|
|
33
|
+
* cell's left edge) — the shared reference the eye aligns rows against. */
|
|
34
|
+
readonly baseline?: boolean | undefined;
|
|
35
|
+
/**
|
|
36
|
+
* Reference markers, **pre-resolved to track fractions** by the caller (the
|
|
37
|
+
* shell knows pixels, not the scale): each draws a dotted vertical rule
|
|
38
|
+
* through every row's glyph area, plus a label strip above the list when
|
|
39
|
+
* any carries a `label`. A `null` fraction (out-of-scale gap) is skipped.
|
|
40
|
+
*/
|
|
41
|
+
readonly markers?: ReadonlyArray<{
|
|
42
|
+
readonly frac: number | null;
|
|
43
|
+
readonly label?: string;
|
|
44
|
+
}> | undefined;
|
|
45
|
+
readonly theme: ChartTheme;
|
|
46
|
+
}
|
|
47
|
+
/** The shared text ink: the band-label tone when the theme has one (stronger
|
|
48
|
+
* than tick labels — these cells are primary content), else the tick ink. */
|
|
49
|
+
export declare function listInk(theme: ChartTheme): string;
|
|
50
|
+
export declare function ListTable<R extends ListRow>({ rows, kind, renderGlyphs, before, after, renderExpanded, defaultExpanded, onExpandToggle, selected, onRowClick, divided, baseline, markers, theme, }: ListTableProps<R>): import("react/jsx-runtime").JSX.Element;
|
|
51
|
+
//# sourceMappingURL=ListTable.d.ts.map
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* @internal The shared row-table shell behind {@link BarList} / {@link BoxList}.
|
|
4
|
+
*
|
|
5
|
+
* Renders a real `<table>` — the point of the list family is table semantics
|
|
6
|
+
* (label cells that can be links, aligned data cells, a `colSpan` detail row
|
|
7
|
+
* for the expander, screen-reader-legible rows), which a canvas plot can't
|
|
8
|
+
* carry and which hand-rolled flex rows re-implement badly (per-row cell
|
|
9
|
+
* alignment is exactly what table layout solves). The glyph cell takes
|
|
10
|
+
* `width: 100%` so it absorbs the free width; every text cell shrinks to fit.
|
|
11
|
+
*
|
|
12
|
+
* Not exported from the package: the public surface is the two sisters, so the
|
|
13
|
+
* shared shell can evolve without a compatibility contract.
|
|
14
|
+
*/
|
|
15
|
+
import { Fragment, useState } from 'react';
|
|
16
|
+
/** The turquoise the selected-row edge falls back to when the theme has no
|
|
17
|
+
* annotation register — the same built-in the annotation layer uses. */
|
|
18
|
+
const FALLBACK_ACCENT = '#0d9488';
|
|
19
|
+
/** The shared text ink: the band-label tone when the theme has one (stronger
|
|
20
|
+
* than tick labels — these cells are primary content), else the tick ink. */
|
|
21
|
+
export function listInk(theme) {
|
|
22
|
+
return theme.axis.band?.label ?? theme.axis.label;
|
|
23
|
+
}
|
|
24
|
+
export function ListTable({ rows, kind, renderGlyphs, before = [], after = [], renderExpanded, defaultExpanded, onExpandToggle, selected, onRowClick, divided = true, baseline = false, markers = [], theme, }) {
|
|
25
|
+
// Uncontrolled expansion, keyed on row identity so it survives a re-sort.
|
|
26
|
+
const [expanded, setExpanded] = useState(() => new Set(defaultExpanded ?? []));
|
|
27
|
+
const [hovered, setHovered] = useState(null);
|
|
28
|
+
const interactive = onRowClick !== undefined;
|
|
29
|
+
const toggle = (key) => {
|
|
30
|
+
const open = !expanded.has(key);
|
|
31
|
+
setExpanded((prev) => {
|
|
32
|
+
const next = new Set(prev);
|
|
33
|
+
if (open)
|
|
34
|
+
next.add(key);
|
|
35
|
+
else
|
|
36
|
+
next.delete(key);
|
|
37
|
+
return next;
|
|
38
|
+
});
|
|
39
|
+
onExpandToggle?.(key, open);
|
|
40
|
+
};
|
|
41
|
+
const ink = listInk(theme);
|
|
42
|
+
const accent = theme.annotation?.color ?? FALLBACK_ACCENT;
|
|
43
|
+
const divider = divided ? `1px solid ${theme.axis.grid}` : undefined;
|
|
44
|
+
// Label + before + glyph + after (+ expander) — the detail row spans them all.
|
|
45
|
+
const span = 2 + before.length + after.length + (renderExpanded ? 1 : 0);
|
|
46
|
+
const textCell = (align) => ({
|
|
47
|
+
padding: '6px 12px',
|
|
48
|
+
whiteSpace: 'nowrap',
|
|
49
|
+
textAlign: align ?? 'left',
|
|
50
|
+
verticalAlign: 'middle',
|
|
51
|
+
});
|
|
52
|
+
// The glyph cell's shared horizontal geometry — the label strip must use
|
|
53
|
+
// the SAME left/right padding (and baseline border) as the data rows, or
|
|
54
|
+
// its percentages would resolve against a different content width and the
|
|
55
|
+
// labels would sit off their rules.
|
|
56
|
+
const glyphCellStyle = (vertical) => ({
|
|
57
|
+
width: '100%',
|
|
58
|
+
padding: baseline ? `${vertical} 8px ${vertical} 5px` : `${vertical} 8px`,
|
|
59
|
+
verticalAlign: 'middle',
|
|
60
|
+
borderLeft: baseline ? `1px solid ${theme.axis.grid}` : undefined,
|
|
61
|
+
});
|
|
62
|
+
const drawnMarkers = markers.filter((m) => m.frac !== null);
|
|
63
|
+
return (_jsx("table", { "data-list": kind, style: {
|
|
64
|
+
width: '100%',
|
|
65
|
+
borderCollapse: 'collapse',
|
|
66
|
+
font: `${theme.font.size}px/${1.5} ${theme.font.family}`,
|
|
67
|
+
color: ink,
|
|
68
|
+
background: theme.background,
|
|
69
|
+
}, children: _jsxs("tbody", { children: [drawnMarkers.some((m) => m.label !== undefined) && (
|
|
70
|
+
// The marker label strip: one synthetic row above the data, its
|
|
71
|
+
// glyph cell sharing the data rows' horizontal geometry so each
|
|
72
|
+
// label centres exactly on its rule below.
|
|
73
|
+
_jsxs("tr", { "data-list-marker-labels": "", children: [_jsx("td", { style: textCell() }), before.map((cell) => (_jsx("td", { style: textCell(cell.align) }, cell.key))), _jsx("td", { style: glyphCellStyle('0px'), children: _jsx("div", { style: {
|
|
74
|
+
position: 'relative',
|
|
75
|
+
height: theme.font.size + 6,
|
|
76
|
+
}, children: drawnMarkers.map((m, mi) => m.label !== undefined && (_jsx("span", { "data-list-marker-label": "", style: {
|
|
77
|
+
position: 'absolute',
|
|
78
|
+
left: `${m.frac * 100}%`,
|
|
79
|
+
bottom: 0,
|
|
80
|
+
transform: 'translateX(-50%)',
|
|
81
|
+
whiteSpace: 'nowrap',
|
|
82
|
+
color: accent,
|
|
83
|
+
}, children: m.label }, mi))) }) }), after.map((cell) => (_jsx("td", { style: textCell(cell.align) }, cell.key))), renderExpanded !== undefined && _jsx("td", {})] })), rows.map((row, i) => {
|
|
84
|
+
const isSelected = selected != null && selected === row.key;
|
|
85
|
+
const isOpen = renderExpanded !== undefined && expanded.has(row.key);
|
|
86
|
+
return (_jsxs(Fragment, { children: [_jsxs("tr", { "data-list-row": row.key, ...(isSelected ? { 'data-selected': '' } : {}), onClick: onRowClick === undefined ? undefined : () => onRowClick(row),
|
|
87
|
+
// A clickable row is keyboard-reachable too: focusable, and
|
|
88
|
+
// Enter / Space activate it (Space's default scroll is eaten).
|
|
89
|
+
tabIndex: interactive ? 0 : undefined, onKeyDown: onRowClick === undefined
|
|
90
|
+
? undefined
|
|
91
|
+
: (e) => {
|
|
92
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
93
|
+
e.preventDefault();
|
|
94
|
+
onRowClick(row);
|
|
95
|
+
}
|
|
96
|
+
}, onPointerEnter: interactive ? () => setHovered(row.key) : undefined, onPointerLeave: interactive ? () => setHovered(null) : undefined, style: {
|
|
97
|
+
borderTop: i > 0 ? divider : undefined,
|
|
98
|
+
cursor: interactive ? 'pointer' : undefined,
|
|
99
|
+
background: interactive && hovered === row.key
|
|
100
|
+
? (theme.legend?.border ?? theme.axis.grid)
|
|
101
|
+
: undefined,
|
|
102
|
+
// The selection accent: an inset edge in the annotation
|
|
103
|
+
// register (a *user's* mark, so it takes the marks colour,
|
|
104
|
+
// not a data hue) — reads on any ground, moves no layout.
|
|
105
|
+
boxShadow: isSelected ? `inset 3px 0 0 ${accent}` : undefined,
|
|
106
|
+
}, children: [_jsx("td", { "data-list-cell": "label", style: textCell(), children: row.label ?? row.key }), before.map((cell) => (_jsx("td", { "data-list-cell": cell.key, style: textCell(cell.align), children: cell.render(row) }, cell.key))), _jsx("td", { "data-list-cell": "glyphs",
|
|
107
|
+
// 100% absorbs the table's free width; every text cell
|
|
108
|
+
// shrinks to its content, staying aligned down the list.
|
|
109
|
+
// The baseline rule marks the scale origin: left padding
|
|
110
|
+
// narrows to a 5px breath so the glyphs start just off the
|
|
111
|
+
// rule, and border-collapse joins the rows' rules into one
|
|
112
|
+
// continuous vertical — the same thin `axis.grid` ink as
|
|
113
|
+
// the row dividers, so the two read as one quiet grid.
|
|
114
|
+
style: glyphCellStyle('6px'), children: _jsxs("div", { style: { position: 'relative' }, children: [renderGlyphs(row), drawnMarkers.map((m, mi) => (
|
|
115
|
+
// One dotted segment per row, bleeding through the
|
|
116
|
+
// row's vertical padding (+ divider) so adjacent rows'
|
|
117
|
+
// segments join into one continuous rule. Annotation
|
|
118
|
+
// register — a reference is a user's mark, not data.
|
|
119
|
+
_jsx("div", { "data-list-marker": "", style: {
|
|
120
|
+
position: 'absolute',
|
|
121
|
+
top: -7,
|
|
122
|
+
bottom: -7,
|
|
123
|
+
left: `calc(${m.frac * 100}% - 0.5px)`,
|
|
124
|
+
width: 0,
|
|
125
|
+
borderLeft: `1px dotted ${accent}`,
|
|
126
|
+
pointerEvents: 'none',
|
|
127
|
+
} }, mi)))] }) }), after.map((cell) => (_jsx("td", { "data-list-cell": cell.key, style: textCell(cell.align), children: cell.render(row) }, cell.key))), renderExpanded !== undefined && (_jsx("td", { style: { padding: '0 4px', verticalAlign: 'middle' }, children: _jsx("button", { type: "button", "data-list-expander": "", "aria-expanded": isOpen, "aria-label": isOpen ? 'Collapse row' : 'Expand row', onClick: (e) => {
|
|
128
|
+
// The chevron toggles; it must not double as a row click.
|
|
129
|
+
e.stopPropagation();
|
|
130
|
+
toggle(row.key);
|
|
131
|
+
}, style: {
|
|
132
|
+
background: 'none',
|
|
133
|
+
border: 'none',
|
|
134
|
+
cursor: 'pointer',
|
|
135
|
+
color: theme.axis.label,
|
|
136
|
+
font: 'inherit',
|
|
137
|
+
padding: '2px 6px',
|
|
138
|
+
transform: isOpen ? 'rotate(90deg)' : undefined,
|
|
139
|
+
transition: 'transform 120ms',
|
|
140
|
+
}, children: "\u25B8" }) }))] }), isOpen && (_jsx("tr", { "data-list-detail": row.key, children: _jsx("td", { colSpan: span, style: { padding: '2px 12px 12px' }, children: renderExpanded(row) }) }))] }, row.key));
|
|
141
|
+
})] }) }));
|
|
142
|
+
}
|
|
143
|
+
//# sourceMappingURL=ListTable.js.map
|
package/dist/ScatterChart.d.ts
CHANGED
|
@@ -1,24 +1,9 @@
|
|
|
1
1
|
import { ValueSeries } from 'pond-ts';
|
|
2
2
|
import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
|
|
3
|
+
import type { NumericColumn, ValueNumericColumn } from './column-names.js';
|
|
3
4
|
import { type ColorEncoding, type RadiusEncoding } from './encoding.js';
|
|
4
5
|
import type { DecimateOption } from './decimate.js';
|
|
5
|
-
export interface
|
|
6
|
-
/**
|
|
7
|
-
* The source series. A `TimeSeries` scatters against the time axis; a
|
|
8
|
-
* `ValueSeries` (`series.byValue('cumDist')`, or `ValueSeries.fromColumns`
|
|
9
|
-
* for natively value-keyed data — IV marks keyed by strike) against its
|
|
10
|
-
* value axis — the container infers which from the data, no axis-type prop
|
|
11
|
-
* (mirrors `<LineChart>`). Either way the key / axis column supplies each
|
|
12
|
-
* point's x and `column` supplies y.
|
|
13
|
-
*
|
|
14
|
-
* **Live charts:** `series.byValue(…)` mints a *fresh* projection each call,
|
|
15
|
-
* so passing `series={s.byValue('dist')}` inline re-registers this layer
|
|
16
|
-
* every render — memoize the projection (`useMemo`) on a frequently
|
|
17
|
-
* re-rendering chart.
|
|
18
|
-
*/
|
|
19
|
-
series: TimeSeries<S> | ValueSeries<VS>;
|
|
20
|
-
/** Name of the numeric value column — each point's y. */
|
|
21
|
-
column: string;
|
|
6
|
+
export interface ScatterChartCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
22
7
|
/**
|
|
23
8
|
* The scatter's semantic identifier — what the marks _are_ / how they should
|
|
24
9
|
* read. The theme maps it to a {@link ScatterStyle} (`theme.scatter[as] ??
|
|
@@ -109,6 +94,30 @@ export interface ScatterChartProps<S extends SeriesSchema = SeriesSchema, VS ext
|
|
|
109
94
|
*/
|
|
110
95
|
index?: number;
|
|
111
96
|
}
|
|
97
|
+
/**
|
|
98
|
+
* ScatterChart's source + column props, a **union over the series kind** so the
|
|
99
|
+
* column names are checked against the schema that was actually passed
|
|
100
|
+
* ([PND-CHARTAPI]). A single member carrying `NumericColumn<S> |
|
|
101
|
+
* ValueNumericColumn<VS>` would silently widen to `string`: only one of the
|
|
102
|
+
* two generics is ever inferred, and the other falls back (measured in
|
|
103
|
+
* `spikes/charts-type-seam/`). Loosely-typed series still accept any name.
|
|
104
|
+
*/
|
|
105
|
+
type ScatterChartSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = {
|
|
106
|
+
/**
|
|
107
|
+
* The source series. **Live charts:** `series.byValue(…)` mints a
|
|
108
|
+
* *fresh* projection each call, so an inline `series={s.byValue('d')}`
|
|
109
|
+
* re-registers this layer every render — on a frequently re-rendering
|
|
110
|
+
* (e.g. scrub-driven) chart, memoize the projection (`useMemo`) so the
|
|
111
|
+
* layer isn't rebuilt each frame.
|
|
112
|
+
*/
|
|
113
|
+
series: TimeSeries<S>;
|
|
114
|
+
column: NumericColumn<S>;
|
|
115
|
+
} | {
|
|
116
|
+
series: ValueSeries<VS>;
|
|
117
|
+
column: ValueNumericColumn<VS>;
|
|
118
|
+
};
|
|
119
|
+
/** `<ScatterChart>`'s props: the shared knobs plus one series-kind source shape. */
|
|
120
|
+
export type ScatterChartProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = ScatterChartCommon<S, VS> & ScatterChartSource<S, VS>;
|
|
112
121
|
/**
|
|
113
122
|
* A scatter draw layer: one mark per finite point at `(x, column-value)`
|
|
114
123
|
* — x from the series' key / axis column (time or value axis) —
|
|
@@ -138,4 +147,5 @@ export interface ScatterChartProps<S extends SeriesSchema = SeriesSchema, VS ext
|
|
|
138
147
|
* ```
|
|
139
148
|
*/
|
|
140
149
|
export declare function ScatterChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, column, as: semantic, id, axis, radius, color, label, offset, decimate, legend, index, }: ScatterChartProps<S, VS>): null;
|
|
150
|
+
export {};
|
|
141
151
|
//# sourceMappingURL=ScatterChart.d.ts.map
|
package/dist/YAxis.js
CHANGED
|
@@ -72,14 +72,25 @@ export function YAxis({ id, side = 'left', label, min, max, format, ticks, tickC
|
|
|
72
72
|
// a cursor value read identically. `count` calibrates the default formatter's
|
|
73
73
|
// precision to the tick density, exactly as the axis is.
|
|
74
74
|
const fmt = yScale ? resolveAxisFormat(yScale, count, format) : String;
|
|
75
|
+
// A **horizontal categorical** layer on this axis supplies its category
|
|
76
|
+
// names ([PND-HCAT]); with no explicit `ticks`, label one per unit slot at
|
|
77
|
+
// its centre (`i + 0.5`) instead of the scale's numeric ticks — a slot index
|
|
78
|
+
// is not a number anyone wants to read. Explicit `ticks` still win, and a
|
|
79
|
+
// non-categorical row is unaffected (no layer answers, so this is `null`).
|
|
80
|
+
const layerCategories = row.layers
|
|
81
|
+
.filter((e) => (e.axisId ?? row.defaultAxisId) === id)
|
|
82
|
+
.map((e) => e.layer.binCategories?.() ?? null)
|
|
83
|
+
.find((c) => c !== null) ?? null;
|
|
75
84
|
// Explicit `{ at, label }` ticks render verbatim (each label at its `at`),
|
|
76
85
|
// overriding the auto-picked d3 ticks; otherwise label the scale's ticks via `fmt`.
|
|
77
86
|
const tickList = ticks
|
|
78
87
|
? ticks.map((t) => ({ value: t.at, label: t.label }))
|
|
79
|
-
:
|
|
80
|
-
value:
|
|
81
|
-
|
|
82
|
-
|
|
88
|
+
: layerCategories !== null
|
|
89
|
+
? layerCategories.map((label, i) => ({ value: i + 0.5, label }))
|
|
90
|
+
: (yScale ? yScale.ticks(count) : []).map((t) => ({
|
|
91
|
+
value: t,
|
|
92
|
+
label: fmt(t),
|
|
93
|
+
}));
|
|
83
94
|
// The row reserves a slot per axis column (the widest in that column across
|
|
84
95
|
// rows). Size the box to the slot and align this axis's own (narrower)
|
|
85
96
|
// content toward the plot — left axes flush right, right axes flush left — so
|