@pond-ts/charts 0.31.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.
Files changed (79) hide show
  1. package/CHANGELOG.md +3254 -0
  2. package/LICENSE +21 -0
  3. package/README.md +229 -0
  4. package/dist/AreaChart.d.ts +85 -0
  5. package/dist/AreaChart.js +119 -0
  6. package/dist/BandChart.d.ts +55 -0
  7. package/dist/BandChart.js +93 -0
  8. package/dist/BarChart.d.ts +72 -0
  9. package/dist/BarChart.js +137 -0
  10. package/dist/BoxPlot.d.ts +77 -0
  11. package/dist/BoxPlot.js +137 -0
  12. package/dist/Canvas.d.ts +37 -0
  13. package/dist/Canvas.js +39 -0
  14. package/dist/ChartContainer.d.ts +106 -0
  15. package/dist/ChartContainer.js +306 -0
  16. package/dist/ChartRow.d.ts +29 -0
  17. package/dist/ChartRow.js +215 -0
  18. package/dist/Layers.d.ts +22 -0
  19. package/dist/Layers.js +399 -0
  20. package/dist/LineChart.d.ts +60 -0
  21. package/dist/LineChart.js +105 -0
  22. package/dist/ScatterChart.d.ts +84 -0
  23. package/dist/ScatterChart.js +139 -0
  24. package/dist/TimeAxis.d.ts +9 -0
  25. package/dist/TimeAxis.js +12 -0
  26. package/dist/XAxis.d.ts +39 -0
  27. package/dist/XAxis.js +84 -0
  28. package/dist/YAxis.d.ts +42 -0
  29. package/dist/YAxis.js +86 -0
  30. package/dist/annotations.d.ts +110 -0
  31. package/dist/annotations.js +459 -0
  32. package/dist/area.d.ts +54 -0
  33. package/dist/area.js +186 -0
  34. package/dist/band.d.ts +31 -0
  35. package/dist/band.js +57 -0
  36. package/dist/bars.d.ts +96 -0
  37. package/dist/bars.js +171 -0
  38. package/dist/box.d.ts +59 -0
  39. package/dist/box.js +140 -0
  40. package/dist/chip.d.ts +23 -0
  41. package/dist/chip.js +43 -0
  42. package/dist/cjs-fallback.cjs +16 -0
  43. package/dist/context.d.ts +362 -0
  44. package/dist/context.js +5 -0
  45. package/dist/curve.d.ts +22 -0
  46. package/dist/curve.js +13 -0
  47. package/dist/data.d.ts +154 -0
  48. package/dist/data.js +197 -0
  49. package/dist/domain.d.ts +19 -0
  50. package/dist/domain.js +61 -0
  51. package/dist/encoding.d.ts +89 -0
  52. package/dist/encoding.js +144 -0
  53. package/dist/format.d.ts +53 -0
  54. package/dist/format.js +47 -0
  55. package/dist/gaps.d.ts +146 -0
  56. package/dist/gaps.js +209 -0
  57. package/dist/grid.d.ts +11 -0
  58. package/dist/grid.js +29 -0
  59. package/dist/index.d.ts +53 -0
  60. package/dist/index.js +34 -0
  61. package/dist/line.d.ts +46 -0
  62. package/dist/line.js +88 -0
  63. package/dist/range.d.ts +15 -0
  64. package/dist/range.js +27 -0
  65. package/dist/scatter.d.ts +70 -0
  66. package/dist/scatter.js +213 -0
  67. package/dist/select.d.ts +13 -0
  68. package/dist/select.js +23 -0
  69. package/dist/slots.d.ts +48 -0
  70. package/dist/slots.js +64 -0
  71. package/dist/theme.d.ts +224 -0
  72. package/dist/theme.js +232 -0
  73. package/dist/tracker.d.ts +30 -0
  74. package/dist/tracker.js +47 -0
  75. package/dist/use-slot-key.d.ts +21 -0
  76. package/dist/use-slot-key.js +25 -0
  77. package/dist/viewport.d.ts +20 -0
  78. package/dist/viewport.js +30 -0
  79. package/package.json +67 -0
@@ -0,0 +1,139 @@
1
+ import { useContext, useEffect, useMemo } from 'react';
2
+ import { fromTimeSeries } from './data.js';
3
+ import { drawScatter, hitTestScatter, nearestIndex, scatterExtent, } from './scatter.js';
4
+ import { resolveEncoding, } from './encoding.js';
5
+ import { ContainerContext, LayersContext } from './context.js';
6
+ import { useSlotKey } from './use-slot-key.js';
7
+ /**
8
+ * A scatter draw layer: one mark per finite point at `(time, column-value)`,
9
+ * with **data-driven radius + colour** (the signed-off exception — encode from
10
+ * columns via scales, not a per-event style callback). Reads `column` into a
11
+ * {@link ChartSeries} (gaps as NaN → no mark), registers into the enclosing
12
+ * {@link Layers} (scaling against its `axis`), and renders nothing to the DOM —
13
+ * the row draws it.
14
+ *
15
+ * **Interactions.** Hover snaps the tracker dot to the nearest point
16
+ * (`sampleAt`), and that sample flows to the container's `onTrackerChanged` —
17
+ * the nearest-point readout. Scatter reuses the shared tracker rather than
18
+ * adding a separate `onNearest` channel, so a scatter reads out exactly like a
19
+ * line. Click selection hit-tests each point's disc (`hitTest`); the selected
20
+ * point (matching both its key and this series' label) gets a highlight ring.
21
+ *
22
+ * ```tsx
23
+ * <Layers>
24
+ * <ScatterChart
25
+ * series={s}
26
+ * column="price"
27
+ * radius={{ column: 'volume', range: [3, 14] }}
28
+ * color={{ column: 'change', range: ['#e8836b', '#15B3A6'] }}
29
+ * />
30
+ * </Layers>
31
+ * ```
32
+ */
33
+ export function ScatterChart({ series, column, as: semantic, axis, radius, color, label, index = 0, }) {
34
+ const container = useContext(ContainerContext);
35
+ if (container === null) {
36
+ throw new Error('<ScatterChart> must be rendered inside a <ChartContainer>');
37
+ }
38
+ const layers = useContext(LayersContext);
39
+ if (layers === null) {
40
+ throw new Error('<ScatterChart> must be rendered inside a <Layers>');
41
+ }
42
+ const cs = useMemo(() => fromTimeSeries(series, column), [series, column]);
43
+ // Styling: semantic identifier → theme scatter style. The single styling
44
+ // channel for the base mark.
45
+ const { scatter } = container.theme;
46
+ const style = (semantic !== undefined ? scatter[semantic] : undefined) ?? scatter.default;
47
+ // Series identity for the readout + selection match (the `as` role, else the
48
+ // column name).
49
+ const seriesLabel = semantic ?? column;
50
+ const { font } = container.theme;
51
+ // Resolve the data-driven encoding once per data/encoding change. The reader
52
+ // pulls a named numeric column to a Float64Array (gaps NaN) — the same path
53
+ // fromTimeSeries uses; an unknown / non-numeric column throws there (eager,
54
+ // so a typo surfaces at render, not silently as base-styled points).
55
+ const encoding = useMemo(() => resolveEncoding(cs, style.radius, style.color, radius, color, (col) => fromTimeSeries(series, col).y), [cs, style.radius, style.color, radius, color, series]);
56
+ // Per-point label accessor: a column name reads that field, `true` reads the
57
+ // plotted column, anything else (false / omitted) ⇒ no labels.
58
+ const labelAt = useMemo(() => {
59
+ if (label === undefined || label === false)
60
+ return undefined;
61
+ const field = label === true ? column : label;
62
+ return (i) => {
63
+ // series.at(i) is O(1) per row (columnar eventAt cache), so a label per
64
+ // point stays cheap. The field is a runtime string → cast off the literal-
65
+ // keyed get (mirrors the value reads).
66
+ const e = series.at(i);
67
+ if (e === undefined)
68
+ return undefined;
69
+ const v = e.get(field);
70
+ return v === undefined || v === null ? undefined : String(v);
71
+ };
72
+ }, [label, column, series]);
73
+ // The point's stable key is its event begin (epoch ms) — the same as cs.x[i],
74
+ // which is the key column's begin buffer. Used for selection identity.
75
+ const keyAt = useMemo(() => (i) => cs.x[i], [cs]);
76
+ const entry = useMemo(() => ({
77
+ layer: {
78
+ yExtent: () => scatterExtent(cs),
79
+ xKind: 'time',
80
+ xExtent: () => cs.length === 0 ? null : [cs.x[0], cs.x[cs.length - 1]],
81
+ sampleAt: (time) => {
82
+ // No readout past the data (tracker policy — the dot snaps to a drawn
83
+ // mark, never extrapolates past the span); bounds from the time axis.
84
+ if (cs.length === 0 ||
85
+ time < cs.x[0] ||
86
+ time > cs.x[cs.length - 1]) {
87
+ return [];
88
+ }
89
+ // Nearest *drawn* point by index (skips gaps) — O(log N). Reading by
90
+ // index gives the value, the snap-to x, and the encoded colour in one
91
+ // shot, so the readout swatch matches the mark the user sees.
92
+ const i = nearestIndex(cs, time);
93
+ if (i < 0)
94
+ return [];
95
+ return [
96
+ {
97
+ x: cs.x[i],
98
+ value: cs.y[i],
99
+ color: encoding.colorAt(i),
100
+ label: seriesLabel,
101
+ },
102
+ ];
103
+ },
104
+ hitTest: (px, py, xScale, yScale) => hitTestScatter(cs, px, py, xScale, yScale, encoding, keyAt, seriesLabel),
105
+ draw: (ctx, xScale, yScale) => drawScatter(ctx, cs, xScale, yScale, style, encoding, keyAt, labelAt, font, container.selected, seriesLabel),
106
+ },
107
+ axisId: axis,
108
+ index,
109
+ }), [
110
+ cs,
111
+ series,
112
+ column,
113
+ style,
114
+ seriesLabel,
115
+ encoding,
116
+ keyAt,
117
+ labelAt,
118
+ font,
119
+ container.selected,
120
+ axis,
121
+ index,
122
+ ]);
123
+ // A stable per-instance slot (see useSlotKey) keeps this layer's z-position
124
+ // fixed across data/style/selection updates (no jump to the front on update).
125
+ const slot = useSlotKey();
126
+ useEffect(() => () => layers.unregisterLayer(slot), [layers, slot]);
127
+ useEffect(() => {
128
+ layers.registerLayer(slot, entry);
129
+ }, [layers, slot, entry]);
130
+ // Also a tracker source: the container fans in this series' nearest-point
131
+ // value at the cursor for the (outside-the-chart) readout.
132
+ const { registerTrackerSource, unregisterTrackerSource } = container;
133
+ useEffect(() => () => unregisterTrackerSource(slot), [unregisterTrackerSource, slot]);
134
+ useEffect(() => {
135
+ registerTrackerSource(slot, entry.layer);
136
+ }, [registerTrackerSource, slot, entry.layer]);
137
+ return null;
138
+ }
139
+ //# sourceMappingURL=ScatterChart.js.map
@@ -0,0 +1,9 @@
1
+ import { type XAxisProps } from './XAxis.js';
2
+ /**
3
+ * The time-flavoured preset of {@link XAxis} — `<TimeAxis />` is `<XAxis />`.
4
+ * Kept as the familiar name for time charts (and what the container auto-renders);
5
+ * the axis kind still follows the data, so on a value container it ticks as a
6
+ * value axis. Forwards every {@link XAxisProps} (`format`, `label`, `side`, …).
7
+ */
8
+ export declare function TimeAxis(props?: XAxisProps): import("react/jsx-runtime").JSX.Element;
9
+ //# sourceMappingURL=TimeAxis.d.ts.map
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { XAxis } from './XAxis.js';
3
+ /**
4
+ * The time-flavoured preset of {@link XAxis} — `<TimeAxis />` is `<XAxis />`.
5
+ * Kept as the familiar name for time charts (and what the container auto-renders);
6
+ * the axis kind still follows the data, so on a value container it ticks as a
7
+ * value axis. Forwards every {@link XAxisProps} (`format`, `label`, `side`, …).
8
+ */
9
+ export function TimeAxis(props = {}) {
10
+ return _jsx(XAxis, { ...props });
11
+ }
12
+ //# sourceMappingURL=TimeAxis.js.map
@@ -0,0 +1,39 @@
1
+ import { type AxisFormat } from './format.js';
2
+ export interface XAxisProps {
3
+ /**
4
+ * Tick / cursor value formatting — a d3 format/time specifier string or a
5
+ * `(value) => string`. **Omitted ⇒ the container's shared formatter** (so the
6
+ * axis and the cursor readout agree), which is the d3 multi-scale time format
7
+ * for a time axis or the number default for a value axis. The specifier is
8
+ * resolved against the axis's kind (time vs value).
9
+ */
10
+ format?: AxisFormat;
11
+ /** A label drawn centred below (or above) the ticks — e.g. `Distance (m)`. */
12
+ label?: string;
13
+ /** Which edge the axis sits on. **Default `'bottom'`.** Declaration order in
14
+ * the `<ChartContainer>` places it; `side` orients the ticks + label. */
15
+ side?: 'top' | 'bottom';
16
+ /** Strip height in px. Defaults to fit the ticks (+ the label line if any). */
17
+ height?: number;
18
+ /**
19
+ * Explicit ticks — `{ at, label }` in axis-value units — instead of the
20
+ * scale's automatic ticks. The value-axis lever for e.g. lap markers placed at
21
+ * their cumulative-distance positions (`{ at: lap.endMeters, label: 'Lap 3' }`).
22
+ */
23
+ ticks?: ReadonlyArray<{
24
+ readonly at: number;
25
+ readonly label: string;
26
+ }>;
27
+ }
28
+ /**
29
+ * The shared **x axis**, a sibling of {@link YAxis} for the horizontal axis. A
30
+ * child of {@link ChartContainer}, rendered as DOM chrome (crisp text,
31
+ * themeable) under (or over) the rows, aligned to the plot. It reads the
32
+ * container's resolved `xScale` + `xKind` — so a **time** container ticks on
33
+ * wall-clock boundaries and a **value** container (a `ValueSeries` row) ticks as
34
+ * numbers, with no axis-type prop here; the kind follows the data.
35
+ *
36
+ * `<TimeAxis>` is the time-flavoured preset (`<XAxis />`).
37
+ */
38
+ export declare function XAxis({ format, label, side, height, ticks: customTicks, }?: XAxisProps): import("react/jsx-runtime").JSX.Element;
39
+ //# sourceMappingURL=XAxis.d.ts.map
package/dist/XAxis.js ADDED
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Fragment, useContext } from 'react';
3
+ import { ContainerContext } from './context.js';
4
+ import { resolveAxisFormat, resolveTimeFormat, } from './format.js';
5
+ /** Tick strip height (mark + value label) in CSS px. */
6
+ const TICK_STRIP = 22;
7
+ /** Extra height reserved for an axis `label` line. */
8
+ const LABEL_STRIP = 16;
9
+ const TICK_COUNT = 5;
10
+ /**
11
+ * The shared **x axis**, a sibling of {@link YAxis} for the horizontal axis. A
12
+ * child of {@link ChartContainer}, rendered as DOM chrome (crisp text,
13
+ * themeable) under (or over) the rows, aligned to the plot. It reads the
14
+ * container's resolved `xScale` + `xKind` — so a **time** container ticks on
15
+ * wall-clock boundaries and a **value** container (a `ValueSeries` row) ticks as
16
+ * numbers, with no axis-type prop here; the kind follows the data.
17
+ *
18
+ * `<TimeAxis>` is the time-flavoured preset (`<XAxis />`).
19
+ */
20
+ export function XAxis({ format, label, side = 'bottom', height, ticks: customTicks, } = {}) {
21
+ const container = useContext(ContainerContext);
22
+ if (container === null) {
23
+ throw new Error('<XAxis> must be rendered inside a <ChartContainer>');
24
+ }
25
+ const { xScale, plotWidth, leftGutter, theme, formatTime, xKind } = container;
26
+ // Tick formatter: an explicit `format` is resolved against the axis kind
27
+ // (a time specifier through the time scale, a number specifier through the
28
+ // value scale); otherwise the container's shared formatter — the one the
29
+ // cursor readout uses, so a tick and the cursor read identically.
30
+ const fmt = format === undefined
31
+ ? formatTime
32
+ : xKind === 'time'
33
+ ? resolveTimeFormat(xScale, TICK_COUNT, format)
34
+ : resolveAxisFormat(xScale, TICK_COUNT, format);
35
+ const placed = customTicks
36
+ ? customTicks.map((t) => ({ x: xScale(t.at), label: t.label }))
37
+ : xScale.ticks(TICK_COUNT).map((d) => ({
38
+ x: xScale(d),
39
+ label: fmt(+d),
40
+ }));
41
+ const stripHeight = height ?? TICK_STRIP + (label ? LABEL_STRIP : 0);
42
+ const onTop = side === 'top';
43
+ return (_jsxs("div", { style: {
44
+ position: 'relative',
45
+ marginLeft: `${leftGutter}px`,
46
+ width: `${plotWidth}px`,
47
+ height: `${stripHeight}px`,
48
+ // The plot-facing edge carries the rule; a top axis rules its bottom.
49
+ [onTop ? 'borderBottom' : 'borderTop']: `1px solid ${theme.axis.grid}`,
50
+ fontFamily: theme.font.family,
51
+ fontSize: `${theme.font.size}px`,
52
+ color: theme.axis.label,
53
+ }, children: [placed.map((t, i) => {
54
+ // End-align the edge labels so they stay within [0, plotWidth].
55
+ const labelTransform = i === 0
56
+ ? 'none'
57
+ : i === placed.length - 1
58
+ ? 'translateX(-100%)'
59
+ : 'translateX(-50%)';
60
+ return (_jsxs(Fragment, { children: [_jsx("div", { style: {
61
+ position: 'absolute',
62
+ left: `${t.x}px`,
63
+ [onTop ? 'bottom' : 'top']: 0,
64
+ width: '1px',
65
+ height: '4px',
66
+ background: theme.axis.grid,
67
+ } }), _jsx("div", { style: {
68
+ position: 'absolute',
69
+ left: `${t.x}px`,
70
+ [onTop ? 'bottom' : 'top']: '6px',
71
+ transform: labelTransform,
72
+ whiteSpace: 'nowrap',
73
+ }, children: t.label })] }, `${t.x}-${i}`));
74
+ }), label !== undefined && (_jsx("div", { style: {
75
+ position: 'absolute',
76
+ left: 0,
77
+ width: '100%',
78
+ textAlign: 'center',
79
+ [onTop ? 'top' : 'bottom']: 0,
80
+ opacity: 0.7,
81
+ whiteSpace: 'nowrap',
82
+ }, children: label }))] }));
83
+ }
84
+ //# sourceMappingURL=XAxis.js.map
@@ -0,0 +1,42 @@
1
+ import { type AxisFormat } from './format.js';
2
+ export interface YAxisProps {
3
+ /** Identifier a chart links to via its `axis` prop (and the first declared is
4
+ * the row's default). */
5
+ id: string;
6
+ /**
7
+ * Which side of the plot the gutter sits on. Author left axes *before*
8
+ * `<Layers>` in JSX and right axes *after* — the row lays children out in
9
+ * order. Default `left`.
10
+ */
11
+ side?: 'left' | 'right';
12
+ /** Display label / unit (e.g. `bpm`); defaults to `id`. */
13
+ label?: string;
14
+ /** Explicit domain bounds; omit to auto-fit the charts linked to this axis. */
15
+ min?: number;
16
+ max?: number;
17
+ /**
18
+ * Value formatting for the tick labels (and the cursor readout, which matches):
19
+ * a d3 format specifier string (e.g. `'.0%'`, `',.2f'`) or a `(value) => string`
20
+ * function. Omit for the scale's d3 default — which is calibrated to the tick
21
+ * step, so a between-ticks readout rounds to tick precision; pass a specifier
22
+ * (e.g. `',.2f'`) when you want finer readout precision. See {@link AxisFormat}.
23
+ */
24
+ format?: AxisFormat;
25
+ /** Gutter width in CSS pixels (default 50). */
26
+ width?: number;
27
+ /**
28
+ * @internal Declaration position among the row's children, injected by
29
+ * `ChartRow` so the first-declared axis stays the default. Do not set.
30
+ */
31
+ index?: number;
32
+ }
33
+ /**
34
+ * A y-axis for a {@link ChartRow}, rendered as DOM chrome (not canvas) so the
35
+ * text is crisp, themeable, and accessible. Registers its id / side / width /
36
+ * domain with the row, which reserves the gutter (shrinking `plotWidth`) and
37
+ * computes this axis's scale from the charts linked to it; the gutter then draws
38
+ * tick marks + labels from that scale. Charts attach via `<LineChart axis="id">`
39
+ * (default: the first axis).
40
+ */
41
+ export declare function YAxis({ id, side, label, min, max, format, width, index, }: YAxisProps): import("react/jsx-runtime").JSX.Element;
42
+ //# sourceMappingURL=YAxis.d.ts.map
package/dist/YAxis.js ADDED
@@ -0,0 +1,86 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useContext, useEffect, useMemo } from 'react';
3
+ import { ContainerContext, RowContext } from './context.js';
4
+ import { resolveAxisFormat } from './format.js';
5
+ import { useSlotKey } from './use-slot-key.js';
6
+ const DEFAULT_WIDTH = 50;
7
+ const TICK_COUNT = 5;
8
+ /**
9
+ * A y-axis for a {@link ChartRow}, rendered as DOM chrome (not canvas) so the
10
+ * text is crisp, themeable, and accessible. Registers its id / side / width /
11
+ * domain with the row, which reserves the gutter (shrinking `plotWidth`) and
12
+ * computes this axis's scale from the charts linked to it; the gutter then draws
13
+ * tick marks + labels from that scale. Charts attach via `<LineChart axis="id">`
14
+ * (default: the first axis).
15
+ */
16
+ export function YAxis({ id, side = 'left', label, min, max, format, width = DEFAULT_WIDTH, index = 0, }) {
17
+ const container = useContext(ContainerContext);
18
+ if (container === null) {
19
+ throw new Error('<YAxis> must be rendered inside a <ChartContainer>');
20
+ }
21
+ const row = useContext(RowContext);
22
+ if (row === null) {
23
+ throw new Error('<YAxis> must be rendered inside a <ChartRow>');
24
+ }
25
+ const spec = useMemo(() => ({ id, side, width, min, max, format, index }), [id, side, width, min, max, format, index]);
26
+ // A stable per-instance slot (see useSlotKey) keeps this axis in a fixed
27
+ // registry position, so a min/max/side change updates in place rather than
28
+ // re-appending (which would move the first axis behind a later one and
29
+ // silently rebind the row's default-axis charts).
30
+ const slot = useSlotKey();
31
+ const { registerAxis, unregisterAxis } = row;
32
+ // Unregister on unmount only (deps are stable, so cleanup never runs early).
33
+ useEffect(() => () => unregisterAxis(slot), [unregisterAxis, slot]);
34
+ // Register on mount + update in place on every spec change — no reorder.
35
+ useEffect(() => {
36
+ registerAxis(slot, spec);
37
+ }, [registerAxis, slot, spec]);
38
+ const { theme } = container;
39
+ const yScale = row.yScales.get(id);
40
+ const ticks = yScale ? yScale.ticks(TICK_COUNT) : [];
41
+ // Same formatter the readout uses (resolved per axis on the row), so a tick and
42
+ // a cursor value read identically.
43
+ const fmt = yScale ? resolveAxisFormat(yScale, TICK_COUNT, format) : String;
44
+ // The row reserves a slot per axis column (the widest in that column across
45
+ // rows). Size the box to the slot and align this axis's own (narrower)
46
+ // content toward the plot — left axes flush right, right axes flush left — so
47
+ // axes line up column-by-column. Keyed by this instance's slot key (not `id`,
48
+ // which may repeat across a mirror). Falls back to own width until reserved.
49
+ const slotWidth = row.axisSlots.get(slot) ?? width;
50
+ return (_jsx("div", { style: {
51
+ flex: `0 0 ${slotWidth}px`,
52
+ display: 'flex',
53
+ justifyContent: side === 'left' ? 'flex-end' : 'flex-start',
54
+ height: `${row.height}px`,
55
+ }, children: _jsxs("div", { style: {
56
+ position: 'relative',
57
+ width: `${width}px`,
58
+ height: `${row.height}px`,
59
+ fontFamily: theme.font.family,
60
+ fontSize: `${theme.font.size}px`,
61
+ color: theme.axis.label,
62
+ }, children: [yScale &&
63
+ ticks.map((t) => (_jsx("div", { style: {
64
+ position: 'absolute',
65
+ top: `${yScale(t)}px`,
66
+ [side === 'left' ? 'right' : 'left']: '4px',
67
+ transform: 'translateY(-50%)',
68
+ whiteSpace: 'nowrap',
69
+ }, children: fmt(t) }, t))), _jsx("div", { style: {
70
+ position: 'absolute',
71
+ [side === 'left' ? 'left' : 'right']: '1px',
72
+ top: 0,
73
+ bottom: 0,
74
+ width: `${theme.font.size + 2}px`,
75
+ display: 'flex',
76
+ alignItems: 'center',
77
+ justifyContent: 'center',
78
+ fontSize: `${theme.font.size - 1}px`,
79
+ opacity: 0.7,
80
+ pointerEvents: 'none',
81
+ }, children: _jsx("span", { style: {
82
+ whiteSpace: 'nowrap',
83
+ transform: `rotate(${side === 'left' ? -90 : 90}deg)`,
84
+ }, children: label ?? id }) })] }) }));
85
+ }
86
+ //# sourceMappingURL=YAxis.js.map
@@ -0,0 +1,110 @@
1
+ import { type AnnotationSpec } from './context.js';
2
+ /**
3
+ * Greedy left→right lane packing for the **top-flag** labels (markers + regions): a
4
+ * label that would overlap the one to its left drops to the next free lane below,
5
+ * so close-in-x labels stack instead of colliding (and a dragged label slides under
6
+ * its neighbour). Returns slot-key → lane (0 = top). Baselines, whose labels anchor
7
+ * at the left at their own y, don't participate.
8
+ */
9
+ export declare function computeLabelLanes(annotations: readonly AnnotationSpec[], toPixel: (axisX: number) => number): Map<symbol, number>;
10
+ export interface MarkerProps {
11
+ /** x position in axis units — epoch ms on a time axis, the value on a value
12
+ * axis. (The generalisation of the mockup's "time line": a mark at an x, time
13
+ * or value.) */
14
+ at: number;
15
+ /** Chip label; omit to auto-label with the shared x formatter (the axis's). */
16
+ label?: string;
17
+ /** Stable consumer id — a click reports it via the container's
18
+ * `onSelectAnnotation`, so the consumer can track which mark is selected. */
19
+ id?: string;
20
+ /** Controlled selection — brightens to the front (level 1). Handles are an
21
+ * edit-mode hover affordance, not a selection cue. Ignored if not `selectable`. */
22
+ selected?: boolean;
23
+ /** Whether the mark responds to hover + selection (default `true`). When
24
+ * `false` it's inert background context — drawn at the back (level 3) always,
25
+ * no hover, no select, no edit. */
26
+ selectable?: boolean;
27
+ /** Controlled hover (OR'd with pointer hover) — lets a legend row light the mark
28
+ * remotely. Pair with the container's `onHoverAnnotation` to sync both ways. */
29
+ hovered?: boolean;
30
+ /** When `true`, this mark is in **single-annotation edit** (the double-click
31
+ * target): handles stay out, it's draggable, and it reads as level 1 — while
32
+ * other marks stay static. Independent of the container's global
33
+ * `editAnnotations`. Pair with `onEditAnnotation` (the consumer holds an
34
+ * `editingId` and sets `editing={editingId === id}`). */
35
+ editing?: boolean;
36
+ /** Make the marker **editable** (in edit mode): dragging its line reports the
37
+ * new `at` (controlled — wire it back to `at`). The whole line moves. */
38
+ onChange?: (at: number) => void;
39
+ }
40
+ /** A vertical line at an x position (a time, a distance, a lap boundary). */
41
+ export declare function Marker({ at, label, id, selected, selectable, hovered, editing, onChange, }: MarkerProps): import("react/jsx-runtime").JSX.Element;
42
+ export interface BaselineProps {
43
+ /** y value in the linked axis's units. */
44
+ value: number;
45
+ /** Which `<YAxis>` (by id) to measure against; omit for the row's default axis. */
46
+ axis?: string;
47
+ /** Chip label; omit to format `value` with that axis's formatter. */
48
+ label?: string;
49
+ /** Stable consumer id — a click reports it via `onSelectAnnotation`. */
50
+ id?: string;
51
+ /** Controlled selection — brightens to the front (level 1). Handles are an
52
+ * edit-mode hover affordance, not a selection cue. Ignored if not `selectable`. */
53
+ selected?: boolean;
54
+ /** Whether the baseline responds to hover + selection (default `true`). When
55
+ * `false` it's inert background context — drawn at the back (level 3) always. */
56
+ selectable?: boolean;
57
+ /** Controlled hover (OR'd with pointer hover) — lets a legend row light the mark
58
+ * remotely. Pair with the container's `onHoverAnnotation` to sync both ways. */
59
+ hovered?: boolean;
60
+ /** When `true`, this mark is in **single-annotation edit** (the double-click
61
+ * target): handles stay out, it's draggable, and it reads as level 1 — while
62
+ * other marks stay static. Independent of the container's global
63
+ * `editAnnotations`. Pair with `onEditAnnotation` (the consumer holds an
64
+ * `editingId` and sets `editing={editingId === id}`). */
65
+ editing?: boolean;
66
+ /** Make the baseline **editable** (in edit mode): dragging it vertically reports
67
+ * the new `value` (controlled — wire it back to `value`). */
68
+ onChange?: (value: number) => void;
69
+ }
70
+ /** A horizontal line at a y value, scaled against one row axis (RTC's `Baseline`).
71
+ * Its label anchors at the left, at the line's height. */
72
+ export declare function Baseline({ value, axis, label, id, selected, selectable, hovered, editing, onChange, }: BaselineProps): import("react/jsx-runtime").JSX.Element | null;
73
+ export interface RegionProps {
74
+ /** Start x in axis units (time or value). */
75
+ from: number;
76
+ /** End x in axis units. */
77
+ to: number;
78
+ /** Chip label; omit to auto-label `from–to` with the shared x formatter. */
79
+ label?: string;
80
+ /** Stable consumer id — a click (or double-click outside edit) reports it via
81
+ * `onSelectAnnotation`. */
82
+ id?: string;
83
+ /** Controlled selection — brightens to the front (level 1; the body too). Edge
84
+ * handles are an edit-mode hover affordance, not a selection cue. Ignored if not
85
+ * `selectable`. */
86
+ selected?: boolean;
87
+ /** Whether the region responds to hover + selection (default `true`). When
88
+ * `false` it's inert background context — drawn at the back (level 3) always,
89
+ * and the double-click hit-test skips it. */
90
+ selectable?: boolean;
91
+ /** Controlled hover (OR'd with pointer hover) — lets a legend row light the mark
92
+ * remotely. Pair with the container's `onHoverAnnotation` to sync both ways. */
93
+ hovered?: boolean;
94
+ /** When `true`, this mark is in **single-annotation edit** (the double-click
95
+ * target): handles stay out, it's draggable, and it reads as level 1 — while
96
+ * other marks stay static. Independent of the container's global
97
+ * `editAnnotations`. Pair with `onEditAnnotation` (the consumer holds an
98
+ * `editingId` and sets `editing={editingId === id}`). */
99
+ editing?: boolean;
100
+ /** Make the region **editable** (in edit mode): drag the body to move it (both
101
+ * edges shift), drag an edge to resize. Reports the new `{ from, to }`. */
102
+ onChange?: (next: {
103
+ from: number;
104
+ to: number;
105
+ }) => void;
106
+ }
107
+ /** A shaded span over an x range — a lap, a zone, a selected interval. Its label
108
+ * flies as a flag off the left edge. */
109
+ export declare function Region({ from, to, label, id, selected, selectable, hovered, editing, onChange, }: RegionProps): import("react/jsx-runtime").JSX.Element;
110
+ //# sourceMappingURL=annotations.d.ts.map