@pond-ts/charts 0.53.1 → 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 +1175 -4
- package/dist/AreaChart.d.ts +30 -15
- package/dist/AreaChart.js +46 -5
- package/dist/BandChart.d.ts +29 -17
- package/dist/BarChart.d.ts +109 -57
- package/dist/BarChart.js +82 -12
- 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 +30 -16
- package/dist/LineChart.js +46 -5
- 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/affine.d.ts +35 -14
- package/dist/affine.js +34 -17
- package/dist/area.js +4 -4
- package/dist/bars.d.ts +107 -25
- package/dist/bars.js +204 -39
- package/dist/column-names.d.ts +74 -0
- package/dist/column-names.js +2 -0
- package/dist/context.d.ts +50 -7
- package/dist/data.d.ts +77 -0
- package/dist/data.js +131 -22
- package/dist/decimate.js +7 -7
- package/dist/index.d.ts +19 -13
- package/dist/index.js +21 -13
- package/dist/line.js +2 -2
- 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/AreaChart.d.ts
CHANGED
|
@@ -1,22 +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` fills against the time axis; a
|
|
9
|
-
* `ValueSeries` (`series.byValue('dist')`) against its value axis — the
|
|
10
|
-
* container infers which from the data, no axis-type prop (mirrors
|
|
11
|
-
* `<LineChart>`). Either way `column` names the numeric value to fill from.
|
|
12
|
-
*
|
|
13
|
-
* **Live charts:** `series.byValue(…)` mints a *fresh* projection each call, so
|
|
14
|
-
* an inline `series={s.byValue('dist')}` re-registers this layer every render —
|
|
15
|
-
* on a frequently re-rendering chart, memoize the projection (`useMemo`).
|
|
16
|
-
*/
|
|
17
|
-
series: TimeSeries<S> | ValueSeries<VS>;
|
|
18
|
-
/** Name of the numeric value column to fill from. */
|
|
19
|
-
column: string;
|
|
7
|
+
export interface AreaChartCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
20
8
|
/**
|
|
21
9
|
* The series' semantic identifier — what the data _is_ / how it should read
|
|
22
10
|
* (e.g. `elevation`, or a signed-traffic role like `in` / `out`). The theme
|
|
@@ -88,6 +76,32 @@ export interface AreaChartProps<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
88
76
|
*/
|
|
89
77
|
index?: number;
|
|
90
78
|
}
|
|
79
|
+
/**
|
|
80
|
+
* AreaChart's source + column props, a **union over the series kind** so the
|
|
81
|
+
* column names are checked against the schema that was actually passed
|
|
82
|
+
* ([PND-CHARTAPI]). A single member carrying `NumericColumn<S> |
|
|
83
|
+
* ValueNumericColumn<VS>` would silently widen to `string`: only one of the
|
|
84
|
+
* two generics is ever inferred, and the other falls back (measured in
|
|
85
|
+
* `spikes/charts-type-seam/`). Loosely-typed series still accept any name.
|
|
86
|
+
*/
|
|
87
|
+
type AreaChartSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = {
|
|
88
|
+
/**
|
|
89
|
+
* The source series. **Live charts:** `series.byValue(…)` mints a
|
|
90
|
+
* *fresh* projection each call, so an inline `series={s.byValue('d')}`
|
|
91
|
+
* re-registers this layer every render — on a frequently re-rendering
|
|
92
|
+
* (e.g. scrub-driven) chart, memoize the projection (`useMemo`) so the
|
|
93
|
+
* layer isn't rebuilt each frame.
|
|
94
|
+
*/
|
|
95
|
+
series: TimeSeries<S>;
|
|
96
|
+
column: NumericColumn<S>;
|
|
97
|
+
readout?: NumericColumn<S>;
|
|
98
|
+
} | {
|
|
99
|
+
series: ValueSeries<VS>;
|
|
100
|
+
column: ValueNumericColumn<VS>;
|
|
101
|
+
readout?: ValueNumericColumn<VS>;
|
|
102
|
+
};
|
|
103
|
+
/** `<AreaChart>`'s props: the shared knobs plus one series-kind source shape. */
|
|
104
|
+
export type AreaChartProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = AreaChartCommon<S, VS> & AreaChartSource<S, VS>;
|
|
91
105
|
/**
|
|
92
106
|
* An area draw layer: fills between a value `column` and a `baseline`, with a
|
|
93
107
|
* graded (gradient) shade — opaque at the line, transparent at the baseline —
|
|
@@ -108,5 +122,6 @@ export interface AreaChartProps<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
108
122
|
* </Layers>
|
|
109
123
|
* ```
|
|
110
124
|
*/
|
|
111
|
-
export declare function AreaChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, column, as: semantic, axis, baseline, curve, gaps, decimate, legend, index, }: AreaChartProps<S, VS>): null;
|
|
125
|
+
export declare function AreaChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, column, readout, as: semantic, axis, baseline, curve, gaps, decimate, legend, index, }: AreaChartProps<S, VS>): null;
|
|
126
|
+
export {};
|
|
112
127
|
//# sourceMappingURL=AreaChart.d.ts.map
|
package/dist/AreaChart.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useContext, useEffect, useMemo } from 'react';
|
|
2
2
|
import { ValueSeries } from 'pond-ts';
|
|
3
|
-
import { fromTimeSeries, fromValueSeries } from './data.js';
|
|
3
|
+
import { assertNumericColumn, fromTimeSeries, fromValueSeries, } from './data.js';
|
|
4
4
|
import { areaExtent, drawArea } from './area.js';
|
|
5
5
|
import { resolveCurve } from './curve.js';
|
|
6
6
|
import { DEFAULT_GAP_MODE, DEFAULT_GAP_CONNECTOR_OPACITY, } from './gaps.js';
|
|
@@ -36,7 +36,7 @@ function domainFloor(yScale) {
|
|
|
36
36
|
* </Layers>
|
|
37
37
|
* ```
|
|
38
38
|
*/
|
|
39
|
-
export function AreaChart({ series, column, as: semantic, axis, baseline, curve, gaps = DEFAULT_GAP_MODE, decimate = true, legend, index = 0, }) {
|
|
39
|
+
export function AreaChart({ series, column, readout, as: semantic, axis, baseline, curve, gaps = DEFAULT_GAP_MODE, decimate = true, legend, index = 0, }) {
|
|
40
40
|
const container = useContext(ContainerContext);
|
|
41
41
|
if (container === null) {
|
|
42
42
|
throw new Error('<AreaChart> must be rendered inside a <ChartContainer>');
|
|
@@ -48,6 +48,22 @@ export function AreaChart({ series, column, as: semantic, axis, baseline, curve,
|
|
|
48
48
|
const cs = useMemo(() => series instanceof ValueSeries
|
|
49
49
|
? fromValueSeries(series, column)
|
|
50
50
|
: fromTimeSeries(series, column), [series, column]);
|
|
51
|
+
// Readout column values for a value-axis series (time path reads it off the
|
|
52
|
+
// event) — the tracker reports it alongside the plotted fill so an off-chart
|
|
53
|
+
// readout can show a source value. See AreaChartProps.readout.
|
|
54
|
+
//
|
|
55
|
+
// The time path buffers nothing (it has an event, not an index), so it
|
|
56
|
+
// validates the name here instead, so a mistyped `readout` fails the same way
|
|
57
|
+
// on both axis kinds rather than throwing on one and silently doing nothing
|
|
58
|
+
// on the other. Mirrors `<LineChart>`.
|
|
59
|
+
const readoutY = useMemo(() => {
|
|
60
|
+
if (readout === undefined)
|
|
61
|
+
return undefined;
|
|
62
|
+
if (series instanceof ValueSeries)
|
|
63
|
+
return fromValueSeries(series, readout).y;
|
|
64
|
+
assertNumericColumn(series, readout);
|
|
65
|
+
return undefined;
|
|
66
|
+
}, [series, readout]);
|
|
51
67
|
// Styling: semantic identifier → theme area style. The single styling channel.
|
|
52
68
|
const { area } = container.theme;
|
|
53
69
|
const style = (semantic !== undefined ? area[semantic] : undefined) ?? area.default;
|
|
@@ -77,8 +93,19 @@ export function AreaChart({ series, column, as: semantic, axis, baseline, curve,
|
|
|
77
93
|
if (i < 0)
|
|
78
94
|
return [];
|
|
79
95
|
const v = cs.y[i];
|
|
96
|
+
const rv = readoutY?.[i];
|
|
80
97
|
return Number.isFinite(v)
|
|
81
|
-
? [
|
|
98
|
+
? [
|
|
99
|
+
{
|
|
100
|
+
x: cs.x[i],
|
|
101
|
+
value: v,
|
|
102
|
+
color: style.color,
|
|
103
|
+
label,
|
|
104
|
+
...(rv !== undefined && Number.isFinite(rv)
|
|
105
|
+
? { readout: rv }
|
|
106
|
+
: {}),
|
|
107
|
+
},
|
|
108
|
+
]
|
|
82
109
|
: [];
|
|
83
110
|
}
|
|
84
111
|
const e = series.nearest(x);
|
|
@@ -87,11 +114,23 @@ export function AreaChart({ series, column, as: semantic, axis, baseline, curve,
|
|
|
87
114
|
// get() wants a literal key; column is a runtime string. Cast the
|
|
88
115
|
// *event* (not the method — that would detach `this`) to a
|
|
89
116
|
// string-keyed get; runtime-safe read + guard.
|
|
90
|
-
const
|
|
117
|
+
const ev = e;
|
|
118
|
+
const v = ev.get(column);
|
|
119
|
+
const rv = readout !== undefined ? ev.get(readout) : undefined;
|
|
91
120
|
// The readout dot rides the value line (not the baseline), coloured by
|
|
92
121
|
// the outline stroke. A gap yields no readout (like the fill).
|
|
93
122
|
return typeof v === 'number' && Number.isFinite(v)
|
|
94
|
-
? [
|
|
123
|
+
? [
|
|
124
|
+
{
|
|
125
|
+
x: e.begin(),
|
|
126
|
+
value: v,
|
|
127
|
+
color: style.color,
|
|
128
|
+
label,
|
|
129
|
+
...(typeof rv === 'number' && Number.isFinite(rv)
|
|
130
|
+
? { readout: rv }
|
|
131
|
+
: {}),
|
|
132
|
+
},
|
|
133
|
+
]
|
|
95
134
|
: [];
|
|
96
135
|
},
|
|
97
136
|
draw: (ctx, xScale, yScale) => drawArea(ctx, cs, xScale, yScale, style,
|
|
@@ -106,6 +145,8 @@ export function AreaChart({ series, column, as: semantic, axis, baseline, curve,
|
|
|
106
145
|
cs,
|
|
107
146
|
series,
|
|
108
147
|
column,
|
|
148
|
+
readout,
|
|
149
|
+
readoutY,
|
|
109
150
|
style,
|
|
110
151
|
label,
|
|
111
152
|
baseline,
|
package/dist/BandChart.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 Curve } from './curve.js';
|
|
4
5
|
import type { DecimateOption } from './decimate.js';
|
|
5
|
-
export interface
|
|
6
|
-
/**
|
|
7
|
-
* The source series. A `TimeSeries` fills the envelope against the time axis;
|
|
8
|
-
* a `ValueSeries` (`series.byValue('dist')`) against its value axis — the
|
|
9
|
-
* container infers which from the data, no axis-type prop (mirrors
|
|
10
|
-
* `<LineChart>` / `<AreaChart>`). Either way `lower`/`upper` name the numeric
|
|
11
|
-
* edge columns.
|
|
12
|
-
*
|
|
13
|
-
* **Live charts:** `series.byValue(…)` mints a *fresh* projection each call, so
|
|
14
|
-
* an inline `series={s.byValue('dist')}` re-registers this layer every render —
|
|
15
|
-
* on a frequently re-rendering chart, memoize the projection (`useMemo`).
|
|
16
|
-
*/
|
|
17
|
-
series: TimeSeries<S> | ValueSeries<VS>;
|
|
18
|
-
/** Name of the numeric column for the band's lower edge (e.g. `p25`). */
|
|
19
|
-
lower: string;
|
|
20
|
-
/** Name of the numeric column for the band's upper edge (e.g. `p75`). */
|
|
21
|
-
upper: string;
|
|
6
|
+
export interface BandChartCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
22
7
|
/**
|
|
23
8
|
* The band's semantic identifier — what the spread _is_ (e.g. `outer` for a
|
|
24
9
|
* p5/p95 envelope, `inner` for p25/p75). The theme maps it to a
|
|
@@ -64,6 +49,32 @@ export interface BandChartProps<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
64
49
|
*/
|
|
65
50
|
index?: number;
|
|
66
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* BandChart's source + column props, a **union over the series kind** so the
|
|
54
|
+
* column names are checked against the schema that was actually passed
|
|
55
|
+
* ([PND-CHARTAPI]). A single member carrying `NumericColumn<S> |
|
|
56
|
+
* ValueNumericColumn<VS>` would silently widen to `string`: only one of the
|
|
57
|
+
* two generics is ever inferred, and the other falls back (measured in
|
|
58
|
+
* `spikes/charts-type-seam/`). Loosely-typed series still accept any name.
|
|
59
|
+
*/
|
|
60
|
+
type BandChartSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = {
|
|
61
|
+
/**
|
|
62
|
+
* The source series. **Live charts:** `series.byValue(…)` mints a
|
|
63
|
+
* *fresh* projection each call, so an inline `series={s.byValue('d')}`
|
|
64
|
+
* re-registers this layer every render — on a frequently re-rendering
|
|
65
|
+
* (e.g. scrub-driven) chart, memoize the projection (`useMemo`) so the
|
|
66
|
+
* layer isn't rebuilt each frame.
|
|
67
|
+
*/
|
|
68
|
+
series: TimeSeries<S>;
|
|
69
|
+
lower: NumericColumn<S>;
|
|
70
|
+
upper: NumericColumn<S>;
|
|
71
|
+
} | {
|
|
72
|
+
series: ValueSeries<VS>;
|
|
73
|
+
lower: ValueNumericColumn<VS>;
|
|
74
|
+
upper: ValueNumericColumn<VS>;
|
|
75
|
+
};
|
|
76
|
+
/** `<BandChart>`'s props: the shared knobs plus one series-kind source shape. */
|
|
77
|
+
export type BandChartProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = BandChartCommon<S, VS> & BandChartSource<S, VS>;
|
|
67
78
|
/**
|
|
68
79
|
* A variance-band draw layer: fills the envelope between the `lower` and `upper`
|
|
69
80
|
* columns of `series` (typically `rollingByColumn` percentiles), gap-aware, and
|
|
@@ -82,4 +93,5 @@ export interface BandChartProps<S extends SeriesSchema = SeriesSchema, VS extend
|
|
|
82
93
|
* ```
|
|
83
94
|
*/
|
|
84
95
|
export declare function BandChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, lower, upper, as: semantic, axis, curve, decimate, legend, index, }: BandChartProps<S, VS>): null;
|
|
96
|
+
export {};
|
|
85
97
|
//# sourceMappingURL=BandChart.d.ts.map
|
package/dist/BarChart.d.ts
CHANGED
|
@@ -2,55 +2,86 @@ import { ValueSeries } from 'pond-ts';
|
|
|
2
2
|
import type { SeriesSchema, TimeSeries, ValueSeriesSchema } from 'pond-ts';
|
|
3
3
|
import { type BinRecord, type CategoryDatum } from './data.js';
|
|
4
4
|
import { type Orientation } from './bars.js';
|
|
5
|
+
import type { NumericColumn, ValueNumericColumn } from './column-names.js';
|
|
5
6
|
import type { DecimateOption } from './decimate.js';
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
columns
|
|
7
|
+
/**
|
|
8
|
+
* The **mode union** — the legal (source, columns) combinations, each a
|
|
9
|
+
* separate member so an illegal mix fails to compile instead of throwing at
|
|
10
|
+
* render ([PND-CHARTAPI]; the 2026-08 API review's #1 item). Column names are
|
|
11
|
+
* schema-derived where the source is a series (see `column-names.ts`, which
|
|
12
|
+
* also explains why a loosely-typed series still accepts any string).
|
|
13
|
+
*
|
|
14
|
+
* Members, in the order a reader meets them:
|
|
15
|
+
*
|
|
16
|
+
* - **`series` + `column`** — one bar per event. A `TimeSeries`
|
|
17
|
+
* (interval/timeRange-keyed draws true spans; a point key derives width
|
|
18
|
+
* from neighbour spacing), a `ValueSeries` (`series.byValue('dist')`), or a
|
|
19
|
+
* **`ReadonlyMap<group, TimeSeries>`** (the
|
|
20
|
+
* `partitionBy(…).aggregate(…).toMap()` shape) — for a `Map`, `column` names
|
|
21
|
+
* the shared value column and the map's order is the stack order.
|
|
22
|
+
* - **`series` + `columns`** — a **wide** series stacked bottom → top (e.g.
|
|
23
|
+
* `pivotByGroup` output). Invalid with a `Map` source: there the segments
|
|
24
|
+
* *are* the groups, so use `column`.
|
|
25
|
+
* - **`bins` + `column` / `columns`** — `byColumn` bin records
|
|
26
|
+
* (`Array<{ start, end, …aggregates }>`); the names are **aggregate
|
|
27
|
+
* fields** of the record, not schema columns, so they stay `string`. Pair
|
|
28
|
+
* with `ordinal` for a band axis.
|
|
29
|
+
* - **`categories`** — an ordered `{ label, value }[]`, one bar per category.
|
|
30
|
+
* Takes **no** `column`/`columns` (each datum carries its own value).
|
|
31
|
+
* Vertical puts the categories on the ordinal **x** axis (the container's
|
|
32
|
+
* band scale); `orientation="horizontal"` puts them on **y** as unit slots
|
|
33
|
+
* and the value on x, and a `<YAxis>` with no explicit `ticks` labels one
|
|
34
|
+
* per category automatically ([PND-HCAT]).
|
|
35
|
+
*
|
|
36
|
+
* **Live charts:** `series.byValue(…)` / `.toMap()` mint fresh objects each
|
|
37
|
+
* call, so an inline `series={…}` re-registers this layer every render — on a
|
|
38
|
+
* frequently re-rendering chart, memoize the projection (`useMemo`).
|
|
39
|
+
*/
|
|
40
|
+
type BarChartSource<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = {
|
|
41
|
+
series: TimeSeries<S> | ReadonlyMap<string, TimeSeries<S>>;
|
|
42
|
+
column: NumericColumn<S>;
|
|
43
|
+
columns?: never;
|
|
44
|
+
bins?: never;
|
|
45
|
+
categories?: never;
|
|
46
|
+
} | {
|
|
47
|
+
series: ValueSeries<VS>;
|
|
48
|
+
column: ValueNumericColumn<VS>;
|
|
49
|
+
columns?: never;
|
|
50
|
+
bins?: never;
|
|
51
|
+
categories?: never;
|
|
52
|
+
} | {
|
|
53
|
+
series: TimeSeries<S>;
|
|
54
|
+
columns: readonly NumericColumn<S>[];
|
|
55
|
+
column?: never;
|
|
56
|
+
bins?: never;
|
|
57
|
+
categories?: never;
|
|
58
|
+
} | {
|
|
59
|
+
series: ValueSeries<VS>;
|
|
60
|
+
columns: readonly ValueNumericColumn<VS>[];
|
|
61
|
+
column?: never;
|
|
62
|
+
bins?: never;
|
|
63
|
+
categories?: never;
|
|
64
|
+
} | {
|
|
65
|
+
bins: readonly BinRecord[];
|
|
66
|
+
column: string;
|
|
67
|
+
columns?: never;
|
|
68
|
+
series?: never;
|
|
69
|
+
categories?: never;
|
|
70
|
+
} | {
|
|
71
|
+
bins: readonly BinRecord[];
|
|
72
|
+
columns: readonly string[];
|
|
73
|
+
column?: never;
|
|
74
|
+
series?: never;
|
|
75
|
+
categories?: never;
|
|
76
|
+
} | {
|
|
77
|
+
categories: readonly CategoryDatum[];
|
|
78
|
+
series?: never;
|
|
79
|
+
bins?: never;
|
|
80
|
+
column?: never;
|
|
81
|
+
columns?: never;
|
|
82
|
+
};
|
|
83
|
+
/** The props every {@link BarChartSource} mode shares. */
|
|
84
|
+
export interface BarChartCommon<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> {
|
|
54
85
|
/**
|
|
55
86
|
* The single series' semantic identifier — what the data _is_. The theme maps
|
|
56
87
|
* it to a {@link BarStyle} (`theme.bar[as] ?? theme.bar.default`). **Single
|
|
@@ -68,14 +99,21 @@ export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
68
99
|
*/
|
|
69
100
|
colors?: Readonly<Record<string, string>>;
|
|
70
101
|
/**
|
|
71
|
-
* **Per-
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
102
|
+
* **Per-bar** colours for a single-series chart — `binColors[i]` fills bar
|
|
103
|
+
* `i` (aligned to the bars / bins in order), overriding the `as`/theme fill.
|
|
104
|
+
* This is the way to colour heart-rate / power **zones** or value bands each
|
|
105
|
+
* their own colour, and the **direction-coloured financial volume row** — a
|
|
106
|
+
* time-axis `series` derives `binColors` from its own data (rising / falling
|
|
107
|
+
* off open vs close) so volume bars read green / red under the candles (the
|
|
108
|
+
* `colors` map above is per-**group**, for stacks). An `undefined`/short
|
|
109
|
+
* entry falls back to the theme fill. Works on any **single-series** shape —
|
|
110
|
+
* a time / value `series` (vertical or horizontal) or `bins`; on a
|
|
77
111
|
* multi-group stack it would tint every segment of a bin alike, so it's not
|
|
78
|
-
* the tool there.
|
|
112
|
+
* the tool there. A per-bar-coloured bar keeps its own colour under hover /
|
|
113
|
+
* selection (the highlight pops opacity instead of swapping the fill), and
|
|
114
|
+
* the hover / click readout reports the bar's own colour. **Disables the
|
|
115
|
+
* dense-bar envelope decimation** (see `decimate`) — an envelope rect can't
|
|
116
|
+
* carry many bars' colours, so every visible bar draws.
|
|
79
117
|
*/
|
|
80
118
|
binColors?: readonly (string | undefined)[];
|
|
81
119
|
/**
|
|
@@ -84,7 +122,10 @@ export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
84
122
|
* - `'vertical'` — bars grow **up** from a value baseline, bins on the **x**
|
|
85
123
|
* axis (time buckets, value bands). The column / time-histogram look.
|
|
86
124
|
* - `'horizontal'` — bars grow **right**, bins on the **y** axis (a band axis
|
|
87
|
-
* like heart-rate zones). Label the bands with `<YAxis ticks={[{ at, label }]}
|
|
125
|
+
* like heart-rate zones). Label the bands with `<YAxis ticks={[{ at, label }]}>`
|
|
126
|
+
* — or, with `categories`, let the `<YAxis>` derive them ([PND-HCAT]): a
|
|
127
|
+
* horizontal categorical chart hands the axis its names and they land one
|
|
128
|
+
* per slot, so a funnel / ranking needs no hand-built tick list.
|
|
88
129
|
*
|
|
89
130
|
* A `'horizontal'` chart puts the **value** on the shared x axis, so its
|
|
90
131
|
* container's x-kind is `'value'` — it cannot share a `<ChartContainer>` with
|
|
@@ -130,7 +171,9 @@ export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
130
171
|
* at that density (a perf knob, not a style); interaction still reads the source
|
|
131
172
|
* bars. Pass `false` to always draw every bar, or `{ threshold }` to tune the
|
|
132
173
|
* samples-per-pixel factor. **No-op for a stacked / multi-group histogram** (the
|
|
133
|
-
* categorical case is low-count; only the single-series path decimates)
|
|
174
|
+
* categorical case is low-count; only the single-series path decimates) — and
|
|
175
|
+
* **no-op when `binColors` is set** (an envelope rect would repaint its bars
|
|
176
|
+
* one flat colour; a per-bar-coloured layer draws every visible bar).
|
|
134
177
|
*/
|
|
135
178
|
decimate?: DecimateOption;
|
|
136
179
|
/**
|
|
@@ -150,6 +193,14 @@ export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
150
193
|
*/
|
|
151
194
|
index?: number;
|
|
152
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* `<BarChart>`'s props: the shared knobs ({@link BarChartCommon}) plus
|
|
198
|
+
* **exactly one** legal source shape ({@link BarChartSource}). Mixing sources
|
|
199
|
+
* (`series` + `bins`) or column forms (`column` + `columns`) is a **compile**
|
|
200
|
+
* error, and a column name that isn't in the series' schema fails to compile
|
|
201
|
+
* too — both were runtime throws before [PND-CHARTAPI].
|
|
202
|
+
*/
|
|
203
|
+
export type BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema> = BarChartCommon<S, VS> & BarChartSource<S, VS>;
|
|
153
204
|
/**
|
|
154
205
|
* A bar / histogram draw layer. In its simplest form, one rectangle per event
|
|
155
206
|
* spanning the key's `[begin, end]` from the axis baseline to a numeric
|
|
@@ -194,4 +245,5 @@ export interface BarChartProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
194
245
|
* ```
|
|
195
246
|
*/
|
|
196
247
|
export declare function BarChart<S extends SeriesSchema = SeriesSchema, VS extends ValueSeriesSchema = ValueSeriesSchema>({ series, bins, categories, column, columns, as: semantic, colors, binColors, orientation, ordinal, id, axis, gap, decimate, legend, index, }: BarChartProps<S, VS>): null;
|
|
248
|
+
export {};
|
|
197
249
|
//# sourceMappingURL=BarChart.d.ts.map
|
package/dist/BarChart.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { useContext, useEffect, useMemo } from 'react';
|
|
2
2
|
import { Interval, ValueSeries } from 'pond-ts';
|
|
3
|
-
import { barsFromTimeSeries, barsFromValueSeries, categoryStack, stacksFromBins, stacksFromColumns, stacksFromGroups, } from './data.js';
|
|
3
|
+
import { barsFromTimeSeries, barsFromBins, barsFromValueSeries, categoryStack, stacksFromBins, stacksFromColumns, stacksFromGroups, } from './data.js';
|
|
4
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 { legendLabelFor, useLegendItems, } from './swatch.js';
|
|
@@ -69,9 +69,6 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
69
69
|
if (column !== undefined || columns !== undefined) {
|
|
70
70
|
throw new Error('<BarChart categories> takes no `column`/`columns` (each datum carries its own value)');
|
|
71
71
|
}
|
|
72
|
-
if (orientation === 'horizontal') {
|
|
73
|
-
throw new Error('<BarChart categories> is vertical only (categories on x); horizontal category axes are not yet supported');
|
|
74
|
-
}
|
|
75
72
|
}
|
|
76
73
|
const isMap = series instanceof Map;
|
|
77
74
|
if (isMap && columns !== undefined) {
|
|
@@ -82,7 +79,14 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
82
79
|
}
|
|
83
80
|
// The single series' semantic label (its identity for the readout + selection):
|
|
84
81
|
// the `as` role, else the value column. Used only on the single path.
|
|
85
|
-
|
|
82
|
+
//
|
|
83
|
+
// [PND-BARSEM] normalizes a ONE-ENTRY `columns` onto that path, and its mark
|
|
84
|
+
// is the same mark `column` would name — so the lone entry stands in here,
|
|
85
|
+
// or `columns={['a']}` and `column="a"` would report different `SelectInfo.label`
|
|
86
|
+
// for the identical bar (found in review of #593).
|
|
87
|
+
const soleColumn = column ??
|
|
88
|
+
(columns !== undefined && columns.length === 1 ? columns[0] : undefined);
|
|
89
|
+
const label = semantic ?? soleColumn ?? id ?? 'value';
|
|
86
90
|
// Build the chart-ready data view. Single-series *vertical* stays on the
|
|
87
91
|
// original BarSeries path (its pixels are unchanged); everything else — any
|
|
88
92
|
// stack, any horizontal — builds a StackedBarSeries (G === 1 for a single
|
|
@@ -99,6 +103,17 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
99
103
|
if (cols === undefined) {
|
|
100
104
|
throw new Error('<BarChart bins> needs `column` or `columns`');
|
|
101
105
|
}
|
|
106
|
+
// [PND-BARSEM]: a ONE-column vertical histogram draws the same mark as a
|
|
107
|
+
// `series`+`column` chart, so it takes the same path — and with it the
|
|
108
|
+
// whole-slot hit target, the hover colour, the cursor readout and
|
|
109
|
+
// per-bar decimation. Capabilities follow what is drawn, not which prop
|
|
110
|
+
// produced it. Horizontal keeps the transposed stacked path.
|
|
111
|
+
if (cols.length === 1 && orientation !== 'horizontal') {
|
|
112
|
+
return {
|
|
113
|
+
kind: 'single',
|
|
114
|
+
bs: barsFromBins(bins, cols[0], { ordinal }),
|
|
115
|
+
};
|
|
116
|
+
}
|
|
102
117
|
return { kind: 'stacked', ss: stacksFromBins(bins, cols, { ordinal }) };
|
|
103
118
|
}
|
|
104
119
|
if (isMap) {
|
|
@@ -112,6 +127,18 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
112
127
|
}
|
|
113
128
|
const s = series;
|
|
114
129
|
if (columns !== undefined) {
|
|
130
|
+
// [PND-BARSEM]: a one-entry `columns` is a single series wearing the
|
|
131
|
+
// stack's clothes — same mark, so the same capabilities (see the `bins`
|
|
132
|
+
// branch above).
|
|
133
|
+
if (columns.length === 1 && orientation !== 'horizontal') {
|
|
134
|
+
const only = columns[0];
|
|
135
|
+
return {
|
|
136
|
+
kind: 'single',
|
|
137
|
+
bs: s instanceof ValueSeries
|
|
138
|
+
? barsFromValueSeries(s, only)
|
|
139
|
+
: barsFromTimeSeries(s, only),
|
|
140
|
+
};
|
|
141
|
+
}
|
|
115
142
|
return { kind: 'stacked', ss: stacksFromColumns(s, columns) };
|
|
116
143
|
}
|
|
117
144
|
if (column === undefined) {
|
|
@@ -178,7 +205,29 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
178
205
|
}, [shape, orientation, binAxisKind]);
|
|
179
206
|
const { bar } = container.theme;
|
|
180
207
|
// Single-series style: the `as` role → theme bar style (the single channel).
|
|
181
|
-
|
|
208
|
+
//
|
|
209
|
+
// A shape [PND-BARSEM] normalized onto this path (a one-column `bins`, a
|
|
210
|
+
// one-entry `columns`) used to resolve its fill through the *stacked*
|
|
211
|
+
// channel — `colors[group] ?? theme.bar[group] ?? default` — so resolving
|
|
212
|
+
// only `as` here would silently drop a caller's `colors` map and the
|
|
213
|
+
// `theme.bar[<column>]` role, changing the bars' colour with no error
|
|
214
|
+
// (found in review of #593). The column name is that shape's group name, so
|
|
215
|
+
// the same three-step lookup is applied, `as` still winning when given.
|
|
216
|
+
const singleStyle = useMemo(() => {
|
|
217
|
+
const byRole = semantic !== undefined ? bar[semantic] : undefined;
|
|
218
|
+
if (byRole !== undefined)
|
|
219
|
+
return byRole;
|
|
220
|
+
if (soleColumn !== undefined) {
|
|
221
|
+
const override = colors?.[soleColumn];
|
|
222
|
+
const byColumn = bar[soleColumn];
|
|
223
|
+
if (override !== undefined) {
|
|
224
|
+
return { ...(byColumn ?? bar.default), fill: override };
|
|
225
|
+
}
|
|
226
|
+
if (byColumn !== undefined)
|
|
227
|
+
return byColumn;
|
|
228
|
+
}
|
|
229
|
+
return bar.default;
|
|
230
|
+
}, [bar, semantic, soleColumn, colors]);
|
|
182
231
|
const gapPx = gap ?? bar.default.gap;
|
|
183
232
|
// The stacked path's bar-thickness floor comes from `bar.default` (not the `as`
|
|
184
233
|
// role — `as` is single-series only), matching how `gapPx` sources its default.
|
|
@@ -244,7 +293,9 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
244
293
|
{
|
|
245
294
|
x: (bs.begin[i] + bs.end[i]) / 2,
|
|
246
295
|
value: v,
|
|
247
|
-
|
|
296
|
+
// A per-bar colour wins over the flat fill, so the readout
|
|
297
|
+
// pill reads the bar's own colour (as the stacked path does).
|
|
298
|
+
color: binColors?.[i] ?? singleStyle.fill,
|
|
248
299
|
label,
|
|
249
300
|
},
|
|
250
301
|
];
|
|
@@ -254,20 +305,33 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
254
305
|
: {
|
|
255
306
|
hitTest: (px, py, xScale, yScale) => {
|
|
256
307
|
const baseline = resolveBarBaseline(yScale);
|
|
257
|
-
|
|
308
|
+
// No `gapPx` — the hit region is the bar's whole slot (its
|
|
309
|
+
// interval width, full plot height), not the inset rect the
|
|
310
|
+
// gap draws. See barSlotRect.
|
|
311
|
+
const hit = barAt(bs, px, py, xScale, yScale, baseline, singleStyle.minWidth);
|
|
258
312
|
if (hit === null)
|
|
259
313
|
return null;
|
|
260
|
-
const [, begin, value] = hit;
|
|
314
|
+
const [bi, begin, value] = hit;
|
|
315
|
+
// The bar's stable `mark` (its own axis key) rides the
|
|
316
|
+
// selection, so the highlight match and a controlled echo key
|
|
317
|
+
// on the *sample* rather than on the `begin` edge — which on a
|
|
318
|
+
// point-keyed series is derived geometry, not the sample's key
|
|
319
|
+
// (see BarSeries.marks). `bi` is the exact bar index from the
|
|
320
|
+
// hit, as the stacked path uses it.
|
|
321
|
+
const stableMark = bs.marks?.[bi];
|
|
261
322
|
return {
|
|
262
323
|
id,
|
|
263
324
|
key: begin,
|
|
264
325
|
value,
|
|
265
|
-
|
|
326
|
+
// The bar's own colour when per-bar coloured (stacked-path
|
|
327
|
+
// parity: the readout pill matches the pixels).
|
|
328
|
+
color: binColors?.[bi] ?? singleStyle.fill,
|
|
266
329
|
label,
|
|
330
|
+
...(stableMark !== undefined ? { mark: stableMark } : {}),
|
|
267
331
|
};
|
|
268
332
|
},
|
|
269
333
|
}),
|
|
270
|
-
draw: (ctx, xScale, yScale) => drawBars(ctx, bs, xScale, yScale, singleStyle, resolveBarBaseline(yScale), gapPx, id, selection, hover, decimate),
|
|
334
|
+
draw: (ctx, xScale, yScale) => drawBars(ctx, bs, xScale, yScale, singleStyle, resolveBarBaseline(yScale), gapPx, id, selection, hover, decimate, binColors),
|
|
271
335
|
},
|
|
272
336
|
axisId: axis,
|
|
273
337
|
index,
|
|
@@ -290,8 +354,13 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
290
354
|
...(binBuckets !== null ? { binIntervals: () => binBuckets } : {}),
|
|
291
355
|
// A categorical chart hands the container its ordered category names — the
|
|
292
356
|
// ordinal axis domain the shared band scale + label formatter build on.
|
|
357
|
+
// Categorical labels go to the axis the categories actually land on:
|
|
358
|
+
// x for a vertical chart (the container's band scale), y for a
|
|
359
|
+
// horizontal one, where they label the unit slots ([PND-HCAT]).
|
|
293
360
|
...(categoryLabels !== null
|
|
294
|
-
?
|
|
361
|
+
? vertical
|
|
362
|
+
? { xCategories: () => categoryLabels }
|
|
363
|
+
: { binCategories: () => categoryLabels }
|
|
295
364
|
: {}),
|
|
296
365
|
// No x-scrub flag for a stack / horizontal chart — hover + click read it
|
|
297
366
|
// out instead (the flag is single-series-vertical only).
|
|
@@ -335,6 +404,7 @@ export function BarChart({ series, bins, categories, column, columns, as: semant
|
|
|
335
404
|
orientation,
|
|
336
405
|
singleStyle,
|
|
337
406
|
stackStyle,
|
|
407
|
+
binColors,
|
|
338
408
|
label,
|
|
339
409
|
id,
|
|
340
410
|
gapPx,
|