@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.
- package/CHANGELOG.md +3254 -0
- package/LICENSE +21 -0
- package/README.md +229 -0
- package/dist/AreaChart.d.ts +85 -0
- package/dist/AreaChart.js +119 -0
- package/dist/BandChart.d.ts +55 -0
- package/dist/BandChart.js +93 -0
- package/dist/BarChart.d.ts +72 -0
- package/dist/BarChart.js +137 -0
- package/dist/BoxPlot.d.ts +77 -0
- package/dist/BoxPlot.js +137 -0
- package/dist/Canvas.d.ts +37 -0
- package/dist/Canvas.js +39 -0
- package/dist/ChartContainer.d.ts +106 -0
- package/dist/ChartContainer.js +306 -0
- package/dist/ChartRow.d.ts +29 -0
- package/dist/ChartRow.js +215 -0
- package/dist/Layers.d.ts +22 -0
- package/dist/Layers.js +399 -0
- package/dist/LineChart.d.ts +60 -0
- package/dist/LineChart.js +105 -0
- package/dist/ScatterChart.d.ts +84 -0
- package/dist/ScatterChart.js +139 -0
- package/dist/TimeAxis.d.ts +9 -0
- package/dist/TimeAxis.js +12 -0
- package/dist/XAxis.d.ts +39 -0
- package/dist/XAxis.js +84 -0
- package/dist/YAxis.d.ts +42 -0
- package/dist/YAxis.js +86 -0
- package/dist/annotations.d.ts +110 -0
- package/dist/annotations.js +459 -0
- package/dist/area.d.ts +54 -0
- package/dist/area.js +186 -0
- package/dist/band.d.ts +31 -0
- package/dist/band.js +57 -0
- package/dist/bars.d.ts +96 -0
- package/dist/bars.js +171 -0
- package/dist/box.d.ts +59 -0
- package/dist/box.js +140 -0
- package/dist/chip.d.ts +23 -0
- package/dist/chip.js +43 -0
- package/dist/cjs-fallback.cjs +16 -0
- package/dist/context.d.ts +362 -0
- package/dist/context.js +5 -0
- package/dist/curve.d.ts +22 -0
- package/dist/curve.js +13 -0
- package/dist/data.d.ts +154 -0
- package/dist/data.js +197 -0
- package/dist/domain.d.ts +19 -0
- package/dist/domain.js +61 -0
- package/dist/encoding.d.ts +89 -0
- package/dist/encoding.js +144 -0
- package/dist/format.d.ts +53 -0
- package/dist/format.js +47 -0
- package/dist/gaps.d.ts +146 -0
- package/dist/gaps.js +209 -0
- package/dist/grid.d.ts +11 -0
- package/dist/grid.js +29 -0
- package/dist/index.d.ts +53 -0
- package/dist/index.js +34 -0
- package/dist/line.d.ts +46 -0
- package/dist/line.js +88 -0
- package/dist/range.d.ts +15 -0
- package/dist/range.js +27 -0
- package/dist/scatter.d.ts +70 -0
- package/dist/scatter.js +213 -0
- package/dist/select.d.ts +13 -0
- package/dist/select.js +23 -0
- package/dist/slots.d.ts +48 -0
- package/dist/slots.js +64 -0
- package/dist/theme.d.ts +224 -0
- package/dist/theme.js +232 -0
- package/dist/tracker.d.ts +30 -0
- package/dist/tracker.js +47 -0
- package/dist/use-slot-key.d.ts +21 -0
- package/dist/use-slot-key.js +25 -0
- package/dist/viewport.d.ts +20 -0
- package/dist/viewport.js +30 -0
- package/package.json +67 -0
package/dist/format.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Axis value formatting — the single formatter shared by an axis's tick labels
|
|
3
|
+
* and the cursor readout, so a value reads the same in both places (the readout
|
|
4
|
+
* "matches the axes"). d3-style: a format **specifier string** (e.g. `'.0%'`,
|
|
5
|
+
* `',.2f'`) is applied through the scale's own `tickFormat` (the same path d3
|
|
6
|
+
* uses for the ticks), a **function** is used verbatim, and `undefined` falls
|
|
7
|
+
* back to the scale's default `tickFormat`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* How to format an axis's values — a d3 [format specifier]
|
|
11
|
+
* (https://github.com/d3/d3-format#locale_format) string, or a custom
|
|
12
|
+
* `(value) => string` function. Omit for the scale's d3 default.
|
|
13
|
+
*/
|
|
14
|
+
export type AxisFormat = string | ((value: number) => string);
|
|
15
|
+
/** The slice of a d3 scale {@link resolveAxisFormat} needs — `tickFormat` with an
|
|
16
|
+
* optional specifier. A d3 `ScaleLinear` / `ScaleTime` satisfies it. */
|
|
17
|
+
interface Tickable {
|
|
18
|
+
tickFormat(count: number, specifier?: string): (value: number) => string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Resolve an {@link AxisFormat} (or `undefined`) to a `(value) => string`
|
|
22
|
+
* formatter, given the `scale` it formats against and the axis `count` (so the
|
|
23
|
+
* default formatter is calibrated to the tick density, exactly as the axis is):
|
|
24
|
+
*
|
|
25
|
+
* - a **function** → used as-is (the scale is ignored);
|
|
26
|
+
* - a **specifier string** → `scale.tickFormat(count, specifier)` — d3 applies
|
|
27
|
+
* the specifier, so the readout matches ticks formatted the same way;
|
|
28
|
+
* - **`undefined`** → `scale.tickFormat(count)` — the scale's default.
|
|
29
|
+
*/
|
|
30
|
+
export declare function resolveAxisFormat(scale: Tickable, count: number, format: AxisFormat | undefined): (value: number) => string;
|
|
31
|
+
/** The slice of a d3 **time** scale {@link resolveTimeFormat} needs. A d3
|
|
32
|
+
* `ScaleTime` satisfies it; its formatter takes a `Date`. */
|
|
33
|
+
interface TimeTickable {
|
|
34
|
+
tickFormat(count?: number, specifier?: string): (date: Date) => string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* The time analog of {@link resolveAxisFormat} — resolve an {@link AxisFormat}
|
|
38
|
+
* (here the string is a d3 [time specifier](https://github.com/d3/d3-time-format#locale_format),
|
|
39
|
+
* e.g. `'%H:%M'`) to an `(epochMs) => string` formatter against a d3 `scaleTime`,
|
|
40
|
+
* so the cursor-time readout matches the time-axis ticks:
|
|
41
|
+
*
|
|
42
|
+
* - a **function** → used as-is (called with epoch ms);
|
|
43
|
+
* - a **specifier string** → `scale.tickFormat(count, specifier)` (one format for
|
|
44
|
+
* every value), wrapped to take epoch ms;
|
|
45
|
+
* - **`undefined`** → `scale.tickFormat()` — d3's **multi-scale** time format (the
|
|
46
|
+
* time axis's default; no `count` so it matches `<TimeAxis>` exactly).
|
|
47
|
+
*
|
|
48
|
+
* The cursor time is epoch ms, so the resolved formatter wraps the d3 `Date`
|
|
49
|
+
* formatter in `new Date(ms)`.
|
|
50
|
+
*/
|
|
51
|
+
export declare function resolveTimeFormat(scale: TimeTickable, count: number, format: AxisFormat | undefined): (epochMs: number) => string;
|
|
52
|
+
export {};
|
|
53
|
+
//# sourceMappingURL=format.d.ts.map
|
package/dist/format.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Axis value formatting — the single formatter shared by an axis's tick labels
|
|
3
|
+
* and the cursor readout, so a value reads the same in both places (the readout
|
|
4
|
+
* "matches the axes"). d3-style: a format **specifier string** (e.g. `'.0%'`,
|
|
5
|
+
* `',.2f'`) is applied through the scale's own `tickFormat` (the same path d3
|
|
6
|
+
* uses for the ticks), a **function** is used verbatim, and `undefined` falls
|
|
7
|
+
* back to the scale's default `tickFormat`.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Resolve an {@link AxisFormat} (or `undefined`) to a `(value) => string`
|
|
11
|
+
* formatter, given the `scale` it formats against and the axis `count` (so the
|
|
12
|
+
* default formatter is calibrated to the tick density, exactly as the axis is):
|
|
13
|
+
*
|
|
14
|
+
* - a **function** → used as-is (the scale is ignored);
|
|
15
|
+
* - a **specifier string** → `scale.tickFormat(count, specifier)` — d3 applies
|
|
16
|
+
* the specifier, so the readout matches ticks formatted the same way;
|
|
17
|
+
* - **`undefined`** → `scale.tickFormat(count)` — the scale's default.
|
|
18
|
+
*/
|
|
19
|
+
export function resolveAxisFormat(scale, count, format) {
|
|
20
|
+
if (typeof format === 'function')
|
|
21
|
+
return format;
|
|
22
|
+
return format !== undefined
|
|
23
|
+
? scale.tickFormat(count, format)
|
|
24
|
+
: scale.tickFormat(count);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* The time analog of {@link resolveAxisFormat} — resolve an {@link AxisFormat}
|
|
28
|
+
* (here the string is a d3 [time specifier](https://github.com/d3/d3-time-format#locale_format),
|
|
29
|
+
* e.g. `'%H:%M'`) to an `(epochMs) => string` formatter against a d3 `scaleTime`,
|
|
30
|
+
* so the cursor-time readout matches the time-axis ticks:
|
|
31
|
+
*
|
|
32
|
+
* - a **function** → used as-is (called with epoch ms);
|
|
33
|
+
* - a **specifier string** → `scale.tickFormat(count, specifier)` (one format for
|
|
34
|
+
* every value), wrapped to take epoch ms;
|
|
35
|
+
* - **`undefined`** → `scale.tickFormat()` — d3's **multi-scale** time format (the
|
|
36
|
+
* time axis's default; no `count` so it matches `<TimeAxis>` exactly).
|
|
37
|
+
*
|
|
38
|
+
* The cursor time is epoch ms, so the resolved formatter wraps the d3 `Date`
|
|
39
|
+
* formatter in `new Date(ms)`.
|
|
40
|
+
*/
|
|
41
|
+
export function resolveTimeFormat(scale, count, format) {
|
|
42
|
+
if (typeof format === 'function')
|
|
43
|
+
return format;
|
|
44
|
+
const tf = format !== undefined ? scale.tickFormat(count, format) : scale.tickFormat();
|
|
45
|
+
return (ms) => tf(new Date(ms));
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=format.js.map
|
package/dist/gaps.d.ts
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import type { Scale } from './line.js';
|
|
2
|
+
/**
|
|
3
|
+
* How a gap-aware draw layer ({@link LineChart} / {@link AreaChart}) renders a
|
|
4
|
+
* **gap** — a run of non-finite (`NaN`) samples, the signal a coast / dropout /
|
|
5
|
+
* missing bucket leaves in the columnar data (`Number.isFinite`, never
|
|
6
|
+
* `!= null`; see `docs/rfcs/charts.md` trap #2). One concept shared across a
|
|
7
|
+
* line and its area fill, so both speak the same vocabulary.
|
|
8
|
+
*
|
|
9
|
+
* **Bands deliberately have no gap mode.** A filled envelope's break wants its
|
|
10
|
+
* own treatment (sharp edge vs. blurred), still to be designed; for now a band
|
|
11
|
+
* always breaks honestly at a gap.
|
|
12
|
+
*
|
|
13
|
+
* - **`none`** — bridge straight across the gap: an interior gap is linearly
|
|
14
|
+
* interpolated ({@link bridgeGaps}) so the line connects the bordering points
|
|
15
|
+
* and the fill / band spans the gap, as if the data were continuous. A leading
|
|
16
|
+
* / trailing gap (no finite sample on one side) stays a break — there's nothing
|
|
17
|
+
* to bridge from. This is the only mode that is *not* gap-honest — use it when
|
|
18
|
+
* the gap is an artefact to ignore (evenly-sampled data with the odd dropped
|
|
19
|
+
* read), not a real absence.
|
|
20
|
+
* - **`empty`** *(default)* — break: end the subpath at the gap, start a fresh
|
|
21
|
+
* one after it (d3-shape's `.defined`). Today's behavior; the fill / band
|
|
22
|
+
* leaves a hole. The honest default — a gap reads as a gap.
|
|
23
|
+
* - **`dashed`** — the solid segments break as in `empty`, **plus** a dashed
|
|
24
|
+
* line bridges each gap **straight** (last-good point → next-good point). The
|
|
25
|
+
* fill stays broken. Reads as "we know the value resumed here, but didn't
|
|
26
|
+
* measure between" without pretending the line was continuous.
|
|
27
|
+
* - **`step`** — the solid segments break as in `empty`, **plus** a single
|
|
28
|
+
* **flat dashed line at the average** of the two edge values bridges each gap
|
|
29
|
+
* (a horizontal `- - -`, no vertical step — see {@link drawGapSteps}). The
|
|
30
|
+
* fill stays broken. A neutral "the value sat around here" estimate — flatter
|
|
31
|
+
* and less committal than `dashed`'s straight interpolation between the edges.
|
|
32
|
+
* - **`fade`** — estela's coast look: at each gap edge the line drops to the
|
|
33
|
+
* baseline on a **vertical fade to transparent** (opaque at the line,
|
|
34
|
+
* transparent at the baseline), and fades back in on the far side. The fill
|
|
35
|
+
* stays broken. Replicates estela's `es-drop` gradient
|
|
36
|
+
* (`packages/ui/src/DataChart.tsx`) in the canvas renderer.
|
|
37
|
+
*
|
|
38
|
+
* `dashed` and `step` are the **inferred dashed connectors** — both drawn fainter
|
|
39
|
+
* than the solid line (theme `gap.connectorOpacity`, {@link DEFAULT_GAP_CONNECTOR_OPACITY})
|
|
40
|
+
* so an inferred bridge reads as secondary to measured data. Only `fade` drops to
|
|
41
|
+
* a "baseline": for a line the axis floor (the y-scale's domain lower bound,
|
|
42
|
+
* resolved at draw time), for an {@link AreaChart} its own fill baseline.
|
|
43
|
+
*/
|
|
44
|
+
export type GapMode = 'none' | 'empty' | 'dashed' | 'step' | 'fade';
|
|
45
|
+
/** The default gap mode — break at the gap, leave a hole (today's behavior). */
|
|
46
|
+
export declare const DEFAULT_GAP_MODE: GapMode;
|
|
47
|
+
/**
|
|
48
|
+
* Default opacity for the inferred dashed gap connectors (`dashed` / `step`) when
|
|
49
|
+
* a theme sets no `gap.connectorOpacity` — fainter than the solid line so the
|
|
50
|
+
* inferred bridge reads as secondary to measured data.
|
|
51
|
+
*/
|
|
52
|
+
export declare const DEFAULT_GAP_CONNECTOR_OPACITY = 0.5;
|
|
53
|
+
/**
|
|
54
|
+
* One gap in a columnar value series: the index of the last finite sample
|
|
55
|
+
* **before** the gap and the first finite sample **after** it, with their pixel
|
|
56
|
+
* coordinates already resolved. Only **interior** gaps (a finite sample on each
|
|
57
|
+
* side) become edges — a leading / trailing gap has nothing to bridge, so it's
|
|
58
|
+
* skipped (the solid path simply starts / ends at the first / last finite run,
|
|
59
|
+
* matching the d3 `.defined` break).
|
|
60
|
+
*/
|
|
61
|
+
export interface GapEdge {
|
|
62
|
+
/** Index of the last finite sample before the gap. */
|
|
63
|
+
readonly fromIndex: number;
|
|
64
|
+
/** Index of the first finite sample after the gap. */
|
|
65
|
+
readonly toIndex: number;
|
|
66
|
+
/** Pixel x of the last-good sample (`fromIndex`). */
|
|
67
|
+
readonly fromX: number;
|
|
68
|
+
/** Pixel y of the last-good sample (`fromIndex`). */
|
|
69
|
+
readonly fromY: number;
|
|
70
|
+
/** Pixel x of the next-good sample (`toIndex`). */
|
|
71
|
+
readonly toX: number;
|
|
72
|
+
/** Pixel y of the next-good sample (`toIndex`). */
|
|
73
|
+
readonly toY: number;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Return a copy of `values` with **interior** gaps (NaN runs that have a finite
|
|
77
|
+
* sample on each side) linearly interpolated across, for the `none` mode — so d3
|
|
78
|
+
* sees a continuous finite series and both the line and (for an area) the fill
|
|
79
|
+
* bridge the gap with real path ops. Leading / trailing NaNs (no finite anchor
|
|
80
|
+
* on one side) are left NaN: there's nothing to interpolate from, so they stay a
|
|
81
|
+
* break (a `lineTo`/`moveTo` with a NaN coord is dropped by the canvas spec, so
|
|
82
|
+
* leaving them NaN is the honest no-op rather than fabricating an edge value).
|
|
83
|
+
*
|
|
84
|
+
* O(N): one forward pass tracking the last finite value + index, filling the
|
|
85
|
+
* pending run when the next finite sample closes it. Allocates one `Float64Array`
|
|
86
|
+
* (only taken on the `none` path).
|
|
87
|
+
*/
|
|
88
|
+
export declare function bridgeGaps(values: Float64Array, length: number): Float64Array;
|
|
89
|
+
/**
|
|
90
|
+
* Walk a columnar series once (O(N)) and collect the **interior** gaps — runs of
|
|
91
|
+
* non-finite `value(i)` that have a finite sample on *both* sides — as
|
|
92
|
+
* {@link GapEdge}s with pixel coordinates resolved through `xScale`/`lineY`.
|
|
93
|
+
*
|
|
94
|
+
* `value(i)` reads the gap-deciding value at index `i` (for a line that's the
|
|
95
|
+
* `y` value; for a band, "finite" means *both* edges finite — pass a function
|
|
96
|
+
* that returns `NaN` unless both are). `lineY(i)` reads the pixel y the bridge
|
|
97
|
+
* should start / end at (the value line for a line / area; the upper edge for a
|
|
98
|
+
* band). Leading and trailing gaps are skipped — a bridge needs a point on each
|
|
99
|
+
* side.
|
|
100
|
+
*/
|
|
101
|
+
export declare function collectGapEdges(length: number, x: Float64Array, value: (i: number) => number, xScale: Scale, lineY: (i: number) => number): GapEdge[];
|
|
102
|
+
/**
|
|
103
|
+
* Stroke a **dashed straight bridge** across each gap (`from` → `to`), for the
|
|
104
|
+
* `dashed` mode. The solid segments are drawn separately (the `empty` pass); this
|
|
105
|
+
* adds only the bridges, dashed (and faint, via `opacity`) so they read as
|
|
106
|
+
* inferred, not measured. Bracketed by `save`/`restore` so the dash pattern,
|
|
107
|
+
* alpha, and stroke don't leak into later layers.
|
|
108
|
+
*/
|
|
109
|
+
export declare function drawGapBridges(ctx: CanvasRenderingContext2D, edges: readonly GapEdge[], color: string, width: number, opacity?: number): void;
|
|
110
|
+
/**
|
|
111
|
+
* Stroke a **flat dashed line at the average** of the two edge values across each
|
|
112
|
+
* gap, for the `step` mode: one horizontal segment at the midpoint of `fromY` and
|
|
113
|
+
* `toY`, spanning the gap (`- - -`) — no vertical step. A neutral "the value sat
|
|
114
|
+
* around here" estimate, flatter and less committal than `dashed`'s straight
|
|
115
|
+
* interpolation between the edges. Dashed (and faint, via `opacity`) so it reads
|
|
116
|
+
* as inferred. Bracketed by `save`/`restore`.
|
|
117
|
+
*/
|
|
118
|
+
export declare function drawGapSteps(ctx: CanvasRenderingContext2D, edges: readonly GapEdge[], color: string, width: number, opacity?: number): void;
|
|
119
|
+
/**
|
|
120
|
+
* Draw the **fade-to-baseline** at each gap edge for the `fade` mode — estela's
|
|
121
|
+
* coast look (`es-drop`). At the last-good point a vertical segment drops to
|
|
122
|
+
* `baselinePx`, stroked with a vertical gradient opaque (`color`) at the line and
|
|
123
|
+
* transparent at the baseline; the same fade rises at the next-good point on the
|
|
124
|
+
* far side. So the line dissolves into the floor approaching the gap and re-forms
|
|
125
|
+
* after it, rather than ending in a hard stub.
|
|
126
|
+
*
|
|
127
|
+
* estela strokes this as one SVG `<path>` with a single `objectBoundingBox`
|
|
128
|
+
* gradient spanning the path's box; canvas gradients are in user space, so we
|
|
129
|
+
* build one short vertical gradient per drop (anchored at that drop's line→base
|
|
130
|
+
* span) and stroke it. Faithful to the *visual* (a per-edge vertical fade from
|
|
131
|
+
* the line colour to nothing); the implementation differs only in that canvas
|
|
132
|
+
* needs a gradient per drop instead of one shared bounding-box gradient.
|
|
133
|
+
*
|
|
134
|
+
* {@link withAlpha} derives the transparent stop from `color` (a CSS hex); a
|
|
135
|
+
* non-hex colour falls back to `transparent`. Bracketed by `save`/`restore`.
|
|
136
|
+
*/
|
|
137
|
+
export declare function drawGapFades(ctx: CanvasRenderingContext2D, edges: readonly GapEdge[], baselinePx: number, color: string, width: number): void;
|
|
138
|
+
/**
|
|
139
|
+
* Re-express a CSS hex colour (`#rgb` / `#rrggbb`) as `rgba(...)` with the given
|
|
140
|
+
* alpha — for the transparent stop of a fade gradient. A non-hex string (named
|
|
141
|
+
* colour, already-`rgba`) can't be parsed, so at alpha 0 it falls back to the
|
|
142
|
+
* CSS keyword `transparent` (still see-through); at any other alpha it's returned
|
|
143
|
+
* unchanged. Shared by the `fade` gap mode and {@link AreaChart}'s graded fill.
|
|
144
|
+
*/
|
|
145
|
+
export declare function withAlpha(color: string, alpha: number): string;
|
|
146
|
+
//# sourceMappingURL=gaps.d.ts.map
|
package/dist/gaps.js
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/** The default gap mode — break at the gap, leave a hole (today's behavior). */
|
|
2
|
+
export const DEFAULT_GAP_MODE = 'empty';
|
|
3
|
+
/**
|
|
4
|
+
* Default opacity for the inferred dashed gap connectors (`dashed` / `step`) when
|
|
5
|
+
* a theme sets no `gap.connectorOpacity` — fainter than the solid line so the
|
|
6
|
+
* inferred bridge reads as secondary to measured data.
|
|
7
|
+
*/
|
|
8
|
+
export const DEFAULT_GAP_CONNECTOR_OPACITY = 0.5;
|
|
9
|
+
/**
|
|
10
|
+
* Return a copy of `values` with **interior** gaps (NaN runs that have a finite
|
|
11
|
+
* sample on each side) linearly interpolated across, for the `none` mode — so d3
|
|
12
|
+
* sees a continuous finite series and both the line and (for an area) the fill
|
|
13
|
+
* bridge the gap with real path ops. Leading / trailing NaNs (no finite anchor
|
|
14
|
+
* on one side) are left NaN: there's nothing to interpolate from, so they stay a
|
|
15
|
+
* break (a `lineTo`/`moveTo` with a NaN coord is dropped by the canvas spec, so
|
|
16
|
+
* leaving them NaN is the honest no-op rather than fabricating an edge value).
|
|
17
|
+
*
|
|
18
|
+
* O(N): one forward pass tracking the last finite value + index, filling the
|
|
19
|
+
* pending run when the next finite sample closes it. Allocates one `Float64Array`
|
|
20
|
+
* (only taken on the `none` path).
|
|
21
|
+
*/
|
|
22
|
+
export function bridgeGaps(values, length) {
|
|
23
|
+
const out = values.slice(0, length);
|
|
24
|
+
let lastIdx = -1; // index of the last finite value seen
|
|
25
|
+
for (let i = 0; i < length; i += 1) {
|
|
26
|
+
if (Number.isFinite(out[i])) {
|
|
27
|
+
if (lastIdx >= 0 && i - lastIdx > 1) {
|
|
28
|
+
// Fill the (lastIdx, i) interior run by linear interpolation.
|
|
29
|
+
const a = out[lastIdx];
|
|
30
|
+
const b = out[i];
|
|
31
|
+
const span = i - lastIdx;
|
|
32
|
+
for (let j = lastIdx + 1; j < i; j += 1) {
|
|
33
|
+
out[j] = a + ((b - a) * (j - lastIdx)) / span;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
lastIdx = i;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return out;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Walk a columnar series once (O(N)) and collect the **interior** gaps — runs of
|
|
43
|
+
* non-finite `value(i)` that have a finite sample on *both* sides — as
|
|
44
|
+
* {@link GapEdge}s with pixel coordinates resolved through `xScale`/`lineY`.
|
|
45
|
+
*
|
|
46
|
+
* `value(i)` reads the gap-deciding value at index `i` (for a line that's the
|
|
47
|
+
* `y` value; for a band, "finite" means *both* edges finite — pass a function
|
|
48
|
+
* that returns `NaN` unless both are). `lineY(i)` reads the pixel y the bridge
|
|
49
|
+
* should start / end at (the value line for a line / area; the upper edge for a
|
|
50
|
+
* band). Leading and trailing gaps are skipped — a bridge needs a point on each
|
|
51
|
+
* side.
|
|
52
|
+
*/
|
|
53
|
+
export function collectGapEdges(length, x, value, xScale, lineY) {
|
|
54
|
+
const edges = [];
|
|
55
|
+
let prevGood = -1; // last finite index seen
|
|
56
|
+
let inGap = false; // inside a NaN run that already has a left border
|
|
57
|
+
for (let i = 0; i < length; i += 1) {
|
|
58
|
+
if (Number.isFinite(value(i))) {
|
|
59
|
+
if (inGap && prevGood >= 0) {
|
|
60
|
+
// Close an interior gap: prevGood → i.
|
|
61
|
+
edges.push({
|
|
62
|
+
fromIndex: prevGood,
|
|
63
|
+
toIndex: i,
|
|
64
|
+
fromX: xScale(x[prevGood]),
|
|
65
|
+
fromY: lineY(prevGood),
|
|
66
|
+
toX: xScale(x[i]),
|
|
67
|
+
toY: lineY(i),
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
prevGood = i;
|
|
71
|
+
inGap = false;
|
|
72
|
+
}
|
|
73
|
+
else if (prevGood >= 0) {
|
|
74
|
+
// A gap with a left border — a candidate interior gap (closed when the
|
|
75
|
+
// next finite sample arrives; a trailing run never closes, so is skipped).
|
|
76
|
+
inGap = true;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return edges;
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Stroke a **dashed straight bridge** across each gap (`from` → `to`), for the
|
|
83
|
+
* `dashed` mode. The solid segments are drawn separately (the `empty` pass); this
|
|
84
|
+
* adds only the bridges, dashed (and faint, via `opacity`) so they read as
|
|
85
|
+
* inferred, not measured. Bracketed by `save`/`restore` so the dash pattern,
|
|
86
|
+
* alpha, and stroke don't leak into later layers.
|
|
87
|
+
*/
|
|
88
|
+
export function drawGapBridges(ctx, edges, color, width, opacity = 1) {
|
|
89
|
+
if (edges.length === 0)
|
|
90
|
+
return;
|
|
91
|
+
ctx.save();
|
|
92
|
+
ctx.strokeStyle = color;
|
|
93
|
+
ctx.lineWidth = width;
|
|
94
|
+
ctx.globalAlpha = opacity;
|
|
95
|
+
ctx.setLineDash(GAP_DASH);
|
|
96
|
+
ctx.beginPath();
|
|
97
|
+
for (const e of edges) {
|
|
98
|
+
ctx.moveTo(e.fromX, e.fromY);
|
|
99
|
+
ctx.lineTo(e.toX, e.toY);
|
|
100
|
+
}
|
|
101
|
+
ctx.stroke();
|
|
102
|
+
ctx.restore();
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Stroke a **flat dashed line at the average** of the two edge values across each
|
|
106
|
+
* gap, for the `step` mode: one horizontal segment at the midpoint of `fromY` and
|
|
107
|
+
* `toY`, spanning the gap (`- - -`) — no vertical step. A neutral "the value sat
|
|
108
|
+
* around here" estimate, flatter and less committal than `dashed`'s straight
|
|
109
|
+
* interpolation between the edges. Dashed (and faint, via `opacity`) so it reads
|
|
110
|
+
* as inferred. Bracketed by `save`/`restore`.
|
|
111
|
+
*/
|
|
112
|
+
export function drawGapSteps(ctx, edges, color, width, opacity = 1) {
|
|
113
|
+
if (edges.length === 0)
|
|
114
|
+
return;
|
|
115
|
+
ctx.save();
|
|
116
|
+
ctx.strokeStyle = color;
|
|
117
|
+
ctx.lineWidth = width;
|
|
118
|
+
ctx.globalAlpha = opacity;
|
|
119
|
+
ctx.setLineDash(GAP_DASH);
|
|
120
|
+
ctx.beginPath();
|
|
121
|
+
for (const e of edges) {
|
|
122
|
+
// The average of the two edge values; with a linear y-scale the pixel
|
|
123
|
+
// midpoint equals yScale of the value average, so no value round-trip.
|
|
124
|
+
const midY = (e.fromY + e.toY) / 2;
|
|
125
|
+
ctx.moveTo(e.fromX, midY);
|
|
126
|
+
ctx.lineTo(e.toX, midY);
|
|
127
|
+
}
|
|
128
|
+
ctx.stroke();
|
|
129
|
+
ctx.restore();
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Draw the **fade-to-baseline** at each gap edge for the `fade` mode — estela's
|
|
133
|
+
* coast look (`es-drop`). At the last-good point a vertical segment drops to
|
|
134
|
+
* `baselinePx`, stroked with a vertical gradient opaque (`color`) at the line and
|
|
135
|
+
* transparent at the baseline; the same fade rises at the next-good point on the
|
|
136
|
+
* far side. So the line dissolves into the floor approaching the gap and re-forms
|
|
137
|
+
* after it, rather than ending in a hard stub.
|
|
138
|
+
*
|
|
139
|
+
* estela strokes this as one SVG `<path>` with a single `objectBoundingBox`
|
|
140
|
+
* gradient spanning the path's box; canvas gradients are in user space, so we
|
|
141
|
+
* build one short vertical gradient per drop (anchored at that drop's line→base
|
|
142
|
+
* span) and stroke it. Faithful to the *visual* (a per-edge vertical fade from
|
|
143
|
+
* the line colour to nothing); the implementation differs only in that canvas
|
|
144
|
+
* needs a gradient per drop instead of one shared bounding-box gradient.
|
|
145
|
+
*
|
|
146
|
+
* {@link withAlpha} derives the transparent stop from `color` (a CSS hex); a
|
|
147
|
+
* non-hex colour falls back to `transparent`. Bracketed by `save`/`restore`.
|
|
148
|
+
*/
|
|
149
|
+
export function drawGapFades(ctx, edges, baselinePx, color, width) {
|
|
150
|
+
if (edges.length === 0)
|
|
151
|
+
return;
|
|
152
|
+
const transparent = withAlpha(color, 0);
|
|
153
|
+
ctx.save();
|
|
154
|
+
ctx.lineWidth = width;
|
|
155
|
+
// One vertical drop per edge endpoint: the last-good point and the next-good
|
|
156
|
+
// point each fade from the line down to the baseline.
|
|
157
|
+
for (const e of edges) {
|
|
158
|
+
for (const [px, py] of [
|
|
159
|
+
[e.fromX, e.fromY],
|
|
160
|
+
[e.toX, e.toY],
|
|
161
|
+
]) {
|
|
162
|
+
// Degenerate (the line already sits on the baseline) → nothing to fade.
|
|
163
|
+
if (Math.abs(py - baselinePx) < 1e-6)
|
|
164
|
+
continue;
|
|
165
|
+
const grad = ctx.createLinearGradient(0, py, 0, baselinePx);
|
|
166
|
+
grad.addColorStop(0, color); // opaque at the line
|
|
167
|
+
grad.addColorStop(1, transparent); // transparent at the baseline
|
|
168
|
+
ctx.strokeStyle = grad;
|
|
169
|
+
ctx.beginPath();
|
|
170
|
+
ctx.moveTo(px, py);
|
|
171
|
+
ctx.lineTo(px, baselinePx);
|
|
172
|
+
ctx.stroke();
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
ctx.restore();
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Dash pattern for the inferred-bridge modes (`dashed` / `step`): 4 on, 4 off.
|
|
179
|
+
* Mutable (not `readonly`) because `ctx.setLineDash` takes a `number[]`.
|
|
180
|
+
*/
|
|
181
|
+
const GAP_DASH = [4, 4];
|
|
182
|
+
/**
|
|
183
|
+
* Re-express a CSS hex colour (`#rgb` / `#rrggbb`) as `rgba(...)` with the given
|
|
184
|
+
* alpha — for the transparent stop of a fade gradient. A non-hex string (named
|
|
185
|
+
* colour, already-`rgba`) can't be parsed, so at alpha 0 it falls back to the
|
|
186
|
+
* CSS keyword `transparent` (still see-through); at any other alpha it's returned
|
|
187
|
+
* unchanged. Shared by the `fade` gap mode and {@link AreaChart}'s graded fill.
|
|
188
|
+
*/
|
|
189
|
+
export function withAlpha(color, alpha) {
|
|
190
|
+
const hex = color.trim();
|
|
191
|
+
const m = /^#([0-9a-f]{3}|[0-9a-f]{6})$/i.exec(hex);
|
|
192
|
+
if (m === null)
|
|
193
|
+
return alpha === 0 ? 'transparent' : color;
|
|
194
|
+
let r;
|
|
195
|
+
let g;
|
|
196
|
+
let b;
|
|
197
|
+
if (m[1].length === 3) {
|
|
198
|
+
r = parseInt(m[1][0] + m[1][0], 16);
|
|
199
|
+
g = parseInt(m[1][1] + m[1][1], 16);
|
|
200
|
+
b = parseInt(m[1][2] + m[1][2], 16);
|
|
201
|
+
}
|
|
202
|
+
else {
|
|
203
|
+
r = parseInt(m[1].slice(0, 2), 16);
|
|
204
|
+
g = parseInt(m[1].slice(2, 4), 16);
|
|
205
|
+
b = parseInt(m[1].slice(4, 6), 16);
|
|
206
|
+
}
|
|
207
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
208
|
+
}
|
|
209
|
+
//# sourceMappingURL=gaps.js.map
|
package/dist/grid.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stroke the plot's gridlines: a vertical line at each `xTicks` pixel and a
|
|
3
|
+
* horizontal line at each `yTicks` pixel, faint and dashed. Drawn behind the
|
|
4
|
+
* data layers from the same tick positions the axes label, so grid and labels
|
|
5
|
+
* line up. `+0.5` aligns each 1px stroke to the device grid for a crisp line.
|
|
6
|
+
*
|
|
7
|
+
* `save`/`restore` brackets the dash + stroke state so it doesn't leak into the
|
|
8
|
+
* data layers that draw next.
|
|
9
|
+
*/
|
|
10
|
+
export declare function drawGrid(ctx: CanvasRenderingContext2D, xTicks: readonly number[], yTicks: readonly number[], width: number, height: number, color: string, dash: readonly number[]): void;
|
|
11
|
+
//# sourceMappingURL=grid.d.ts.map
|
package/dist/grid.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Stroke the plot's gridlines: a vertical line at each `xTicks` pixel and a
|
|
3
|
+
* horizontal line at each `yTicks` pixel, faint and dashed. Drawn behind the
|
|
4
|
+
* data layers from the same tick positions the axes label, so grid and labels
|
|
5
|
+
* line up. `+0.5` aligns each 1px stroke to the device grid for a crisp line.
|
|
6
|
+
*
|
|
7
|
+
* `save`/`restore` brackets the dash + stroke state so it doesn't leak into the
|
|
8
|
+
* data layers that draw next.
|
|
9
|
+
*/
|
|
10
|
+
export function drawGrid(ctx, xTicks, yTicks, width, height, color, dash) {
|
|
11
|
+
ctx.save();
|
|
12
|
+
ctx.strokeStyle = color;
|
|
13
|
+
ctx.lineWidth = 1;
|
|
14
|
+
ctx.setLineDash([...dash]);
|
|
15
|
+
ctx.beginPath();
|
|
16
|
+
for (const x of xTicks) {
|
|
17
|
+
const px = Math.round(x) + 0.5;
|
|
18
|
+
ctx.moveTo(px, 0);
|
|
19
|
+
ctx.lineTo(px, height);
|
|
20
|
+
}
|
|
21
|
+
for (const y of yTicks) {
|
|
22
|
+
const py = Math.round(y) + 0.5;
|
|
23
|
+
ctx.moveTo(0, py);
|
|
24
|
+
ctx.lineTo(width, py);
|
|
25
|
+
}
|
|
26
|
+
ctx.stroke();
|
|
27
|
+
ctx.restore();
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=grid.js.map
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@pond-ts/charts` — the visualization end of pond.
|
|
3
|
+
*
|
|
4
|
+
* Canvas-rendered, streaming-first time-series charts with a
|
|
5
|
+
* react-timeseries-charts-style declarative layout. The architecture (hard
|
|
6
|
+
* layers: adapter → typed-array store → decimator → chunked Path2D cache →
|
|
7
|
+
* canvas renderer → React shell) is documented in the charts RFC at
|
|
8
|
+
* `docs/rfcs/charts.md`; the milestone plan lives in `PLAN.md`.
|
|
9
|
+
*
|
|
10
|
+
* **M1 — rendering spine.** The layout shell + the first draw layer:
|
|
11
|
+
* `<ChartContainer>` (time axis) → `<ChartRow>` (y-axis + canvas) →
|
|
12
|
+
* `<LineChart>` (a gap-aware line), fed from a pond `TimeSeries` via
|
|
13
|
+
* {@link fromTimeSeries}. Axes, themes, the variance band, and interactions
|
|
14
|
+
* land in M2–M4. {@link Canvas} is the low-level DPR-aware primitive the rows
|
|
15
|
+
* sit on.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
export { Canvas } from './Canvas.js';
|
|
20
|
+
export type { CanvasProps, CanvasDraw } from './Canvas.js';
|
|
21
|
+
export { ChartContainer } from './ChartContainer.js';
|
|
22
|
+
export type { ChartContainerProps } from './ChartContainer.js';
|
|
23
|
+
export { ChartRow } from './ChartRow.js';
|
|
24
|
+
export type { ChartRowProps } from './ChartRow.js';
|
|
25
|
+
export { Layers } from './Layers.js';
|
|
26
|
+
export type { LayersProps } from './Layers.js';
|
|
27
|
+
export { YAxis } from './YAxis.js';
|
|
28
|
+
export type { YAxisProps } from './YAxis.js';
|
|
29
|
+
export { XAxis } from './XAxis.js';
|
|
30
|
+
export type { XAxisProps } from './XAxis.js';
|
|
31
|
+
export { TimeAxis } from './TimeAxis.js';
|
|
32
|
+
export type { AxisFormat } from './format.js';
|
|
33
|
+
export { LineChart } from './LineChart.js';
|
|
34
|
+
export type { LineChartProps } from './LineChart.js';
|
|
35
|
+
export { BandChart } from './BandChart.js';
|
|
36
|
+
export type { BandChartProps } from './BandChart.js';
|
|
37
|
+
export { AreaChart } from './AreaChart.js';
|
|
38
|
+
export type { AreaChartProps } from './AreaChart.js';
|
|
39
|
+
export { ScatterChart } from './ScatterChart.js';
|
|
40
|
+
export type { ScatterChartProps } from './ScatterChart.js';
|
|
41
|
+
export { BoxPlot } from './BoxPlot.js';
|
|
42
|
+
export type { BoxPlotProps } from './BoxPlot.js';
|
|
43
|
+
export { BarChart } from './BarChart.js';
|
|
44
|
+
export type { BarChartProps } from './BarChart.js';
|
|
45
|
+
export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, } from './data.js';
|
|
46
|
+
export type { ChartSeries, BandSeries, BoxSeries, BoxColumns, BarSeries, } from './data.js';
|
|
47
|
+
export type { RadiusEncoding, ColorEncoding } from './encoding.js';
|
|
48
|
+
export type { Curve } from './curve.js';
|
|
49
|
+
export type { GapMode } from './gaps.js';
|
|
50
|
+
export { defaultTheme, estelaTheme } from './theme.js';
|
|
51
|
+
export type { ChartTheme, LineStyle, BandStyle, AreaStyle, ScatterStyle, BoxStyle, BarStyle, } from './theme.js';
|
|
52
|
+
export type { CursorMode, TrackerInfo, TrackerSample, SelectInfo, } from './context.js';
|
|
53
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@pond-ts/charts` — the visualization end of pond.
|
|
3
|
+
*
|
|
4
|
+
* Canvas-rendered, streaming-first time-series charts with a
|
|
5
|
+
* react-timeseries-charts-style declarative layout. The architecture (hard
|
|
6
|
+
* layers: adapter → typed-array store → decimator → chunked Path2D cache →
|
|
7
|
+
* canvas renderer → React shell) is documented in the charts RFC at
|
|
8
|
+
* `docs/rfcs/charts.md`; the milestone plan lives in `PLAN.md`.
|
|
9
|
+
*
|
|
10
|
+
* **M1 — rendering spine.** The layout shell + the first draw layer:
|
|
11
|
+
* `<ChartContainer>` (time axis) → `<ChartRow>` (y-axis + canvas) →
|
|
12
|
+
* `<LineChart>` (a gap-aware line), fed from a pond `TimeSeries` via
|
|
13
|
+
* {@link fromTimeSeries}. Axes, themes, the variance band, and interactions
|
|
14
|
+
* land in M2–M4. {@link Canvas} is the low-level DPR-aware primitive the rows
|
|
15
|
+
* sit on.
|
|
16
|
+
*
|
|
17
|
+
* @packageDocumentation
|
|
18
|
+
*/
|
|
19
|
+
export { Canvas } from './Canvas.js';
|
|
20
|
+
export { ChartContainer } from './ChartContainer.js';
|
|
21
|
+
export { ChartRow } from './ChartRow.js';
|
|
22
|
+
export { Layers } from './Layers.js';
|
|
23
|
+
export { YAxis } from './YAxis.js';
|
|
24
|
+
export { XAxis } from './XAxis.js';
|
|
25
|
+
export { TimeAxis } from './TimeAxis.js';
|
|
26
|
+
export { LineChart } from './LineChart.js';
|
|
27
|
+
export { BandChart } from './BandChart.js';
|
|
28
|
+
export { AreaChart } from './AreaChart.js';
|
|
29
|
+
export { ScatterChart } from './ScatterChart.js';
|
|
30
|
+
export { BoxPlot } from './BoxPlot.js';
|
|
31
|
+
export { BarChart } from './BarChart.js';
|
|
32
|
+
export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, } from './data.js';
|
|
33
|
+
export { defaultTheme, estelaTheme } from './theme.js';
|
|
34
|
+
//# sourceMappingURL=index.js.map
|
package/dist/line.d.ts
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { type CurveFactory } from 'd3-shape';
|
|
2
|
+
import type { ChartSeries } from './data.js';
|
|
3
|
+
import type { LineStyle } from './theme.js';
|
|
4
|
+
import { type GapMode } from './gaps.js';
|
|
5
|
+
/** Maps a data value to a pixel coordinate (a d3 scale is assignable to this). */
|
|
6
|
+
export type Scale = (value: number) => number;
|
|
7
|
+
/**
|
|
8
|
+
* The y-scale's domain lower bound (the axis floor) in pixels — where the
|
|
9
|
+
* `step` / `fade` gap bridges drop to. The runtime `yScale` is a d3
|
|
10
|
+
* `ScaleLinear` (it carries `.domain()`); read the bound through a localized,
|
|
11
|
+
* documented shape rather than widening the draw contract to d3-scale. Falls
|
|
12
|
+
* back to `0` if the scale exposes no domain.
|
|
13
|
+
*/
|
|
14
|
+
export declare function baselinePxFromScale(yScale: Scale): number;
|
|
15
|
+
/**
|
|
16
|
+
* The `[min, max]` of the **finite** values in `cs.y`, or `null` if none are
|
|
17
|
+
* finite. NaN (the gap signal) is ignored, so a coast doesn't drag the domain.
|
|
18
|
+
*/
|
|
19
|
+
export declare function yExtent(cs: ChartSeries): [number, number] | null;
|
|
20
|
+
/**
|
|
21
|
+
* Stroke a line for `cs`, mapping data→pixels through `xScale`/`yScale` and
|
|
22
|
+
* connecting points with `curve` (d3-shape; default linear).
|
|
23
|
+
*
|
|
24
|
+
* Built on d3-shape's `line()`. **Gap handling is driven by `gaps`** (a
|
|
25
|
+
* {@link GapMode}, default `'empty'`):
|
|
26
|
+
*
|
|
27
|
+
* - `'empty'` (default) — `.defined(Number.isFinite)`: a non-finite value ends
|
|
28
|
+
* the current subpath and the next finite point starts a fresh one (`moveTo`,
|
|
29
|
+
* not `lineTo`), so a coast reads as a break, not a `lineTo(NaN, …)` bridge
|
|
30
|
+
* (`docs/rfcs/charts.md` trap #2).
|
|
31
|
+
* - `'none'` — interior gaps are linearly interpolated ({@link bridgeGaps}) so
|
|
32
|
+
* the line bridges straight across (real `lineTo`s, robust to leading /
|
|
33
|
+
* trailing gaps, which stay a break). The one non-honest mode.
|
|
34
|
+
* - `'dashed'` / `'step'` / `'fade'` — the **solid** segments break exactly as
|
|
35
|
+
* in `'empty'`, then a second pass draws the inferred bridge across each
|
|
36
|
+
* interior gap: a dashed straight line, a flat dashed line at the average of
|
|
37
|
+
* the edge values, or estela's fade-to-baseline (the axis floor). `dashed` /
|
|
38
|
+
* `step` are drawn faint (`gapConnectorOpacity`); the gap edges are collected
|
|
39
|
+
* by one O(N) walk ({@link collectGapEdges}).
|
|
40
|
+
*
|
|
41
|
+
* The generator writes path ops to `ctx`; we bracket with `beginPath`/`stroke`.
|
|
42
|
+
* `cs.y` (a `Float64Array`) is the datum iterable — `y` reads the value, `x`
|
|
43
|
+
* reads `cs.x[i]` by index, so there's no per-point object allocation.
|
|
44
|
+
*/
|
|
45
|
+
export declare function drawLine(ctx: CanvasRenderingContext2D, cs: ChartSeries, xScale: Scale, yScale: Scale, style: LineStyle, curve?: CurveFactory, gaps?: GapMode, gapConnectorOpacity?: number): void;
|
|
46
|
+
//# sourceMappingURL=line.d.ts.map
|