@pond-ts/charts 0.58.0 → 0.60.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/API.md +580 -0
- package/CHANGELOG.md +339 -1
- package/dist/AreaChart.d.ts +53 -1
- package/dist/AreaChart.js +16 -3
- package/dist/BarChart.d.ts +56 -7
- package/dist/BarChart.js +88 -73
- package/dist/BarList.d.ts +22 -0
- package/dist/BarList.js +42 -9
- package/dist/ChartContainer.d.ts +175 -3
- package/dist/ChartContainer.js +190 -11
- package/dist/ChartRow.js +92 -2
- package/dist/Layers.js +14 -4
- package/dist/XAxis.js +19 -14
- package/dist/YAxis.d.ts +58 -2
- package/dist/YAxis.js +5 -3
- package/dist/area.d.ts +43 -1
- package/dist/area.js +122 -5
- package/dist/bars.d.ts +10 -3
- package/dist/bars.js +13 -9
- package/dist/context.d.ts +46 -8
- package/dist/data.d.ts +38 -0
- package/dist/data.js +43 -0
- package/dist/format.d.ts +16 -1
- package/dist/format.js +17 -2
- package/dist/index.d.ts +5 -2
- package/dist/index.js +11 -0
- package/dist/range.d.ts +14 -1
- package/dist/range.js +24 -3
- package/dist/theme.d.ts +80 -4
- package/dist/theme.js +3 -0
- package/dist/use-band-ladder.d.ts +30 -0
- package/dist/use-band-ladder.js +81 -0
- package/dist/useChartFrame.d.ts +122 -0
- package/dist/useChartFrame.js +155 -0
- package/dist/useChartLegend.d.ts +8 -0
- package/dist/viewport.d.ts +35 -2
- package/dist/viewport.js +53 -6
- package/dist/yticks.d.ts +8 -1
- package/dist/yticks.js +109 -1
- package/package.json +6 -5
package/dist/area.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { type CurveFactory } from 'd3-shape';
|
|
2
2
|
import type { ChartSeries } from './data.js';
|
|
3
3
|
import { type Scale, type TraceState } from './line.js';
|
|
4
|
+
import type { BandLadder } from './bars.js';
|
|
4
5
|
import type { AreaStyle } from './theme.js';
|
|
5
6
|
import type { LayerDrawStats } from './context.js';
|
|
6
7
|
import { type GapMode } from './gaps.js';
|
|
@@ -69,7 +70,48 @@ export declare function areaExtent(cs: ChartSeries, baseline: number | undefined
|
|
|
69
70
|
* bracketed by `save`/`restore` so they don't leak into later layers. Gap edges
|
|
70
71
|
* are collected by one O(N) walk ({@link collectGapEdges}).
|
|
71
72
|
*/
|
|
72
|
-
export declare function drawArea(ctx: CanvasRenderingContext2D, cs: ChartSeries, xScale: Scale, yScale: Scale, style: AreaStyle, baselineValue: number, curve?: CurveFactory, gaps?: GapMode, gapConnectorOpacity?: number, decimate?: DecimateOption): LayerDrawStats;
|
|
73
|
+
export declare function drawArea(ctx: CanvasRenderingContext2D, cs: ChartSeries, xScale: Scale, yScale: Scale, style: AreaStyle, baselineValue: number, curve?: CurveFactory, gaps?: GapMode, gapConnectorOpacity?: number, decimate?: DecimateOption, banding?: BandLadder): LayerDrawStats;
|
|
74
|
+
/**
|
|
75
|
+
* The banded fill + stroke for `<AreaChart thresholds>` ([PND-BANDAREA]): one
|
|
76
|
+
* vertical **hard-stop gradient in pixel space**, a colour switch at every
|
|
77
|
+
* threshold crossing — `colors[0]` between `-t0` and `+t0`, `colors[k]` over
|
|
78
|
+
* magnitudes `[t(k-1), tk)` on both sides of zero. `thresholds`/`colors` arrive
|
|
79
|
+
* as a resolved {@link BandLadder} (ascending, positive, `n + 1` colours), the
|
|
80
|
+
* same currency `drawBars` takes.
|
|
81
|
+
*
|
|
82
|
+
* A gradient rather than one clipped redraw per band, and that is the
|
|
83
|
+
* load-bearing choice: K + 1 clipped passes walk the path K + 1 times and meet
|
|
84
|
+
* themselves at every boundary with an antialiased seam, where a gradient
|
|
85
|
+
* draws the identical single path once and costs O(K) colour stops. It also
|
|
86
|
+
* bands the **outline for free** — `strokeStyle` takes the same gradient, so
|
|
87
|
+
* the value line switches hue exactly at a crossing, which no per-band clip
|
|
88
|
+
* can do without shearing the stroke.
|
|
89
|
+
*
|
|
90
|
+
* The ladder is walked on the **magnitude** and mirrored below zero, exactly
|
|
91
|
+
* as `bandSpan` does for a bar: the boundary at `±tk` separates band `k` (the
|
|
92
|
+
* zero side) from band `k + 1` (the away side). Whether "away from zero" is up
|
|
93
|
+
* or down the canvas is probed from the scale itself (`t0` vs `t0 + 1`, both
|
|
94
|
+
* positive and finite by construction), so a flipped axis bands correctly. A
|
|
95
|
+
* boundary with **no position** on the scale contributes no crossing — on a
|
|
96
|
+
* log axis the negative mirrors (and zero) simply don't exist, which is the
|
|
97
|
+
* right reading. A crossing **off the plot** clamps to the gradient's ends
|
|
98
|
+
* (a real canvas throws on stops outside `[0, 1]`), which is also what makes a
|
|
99
|
+
* zoomed-in view honest: with every visible pixel inside one band, the clamp
|
|
100
|
+
* degenerates the other stops and the whole plot paints that band's colour.
|
|
101
|
+
*
|
|
102
|
+
* Falls back to the top band's flat colour when there is nothing to anchor on
|
|
103
|
+
* (no plot height, or no boundary with a position at all) — reachable only
|
|
104
|
+
* with a degenerate scale stub, since every real axis positions a positive
|
|
105
|
+
* finite value; any flat colour is equally (in)correct there, and the top
|
|
106
|
+
* band's is at least stable.
|
|
107
|
+
*
|
|
108
|
+
* Like the bar ladder, breakpoints are **absolute data values**, so the
|
|
109
|
+
* baseline plays no part here: an area resting on a non-zero floor still bands
|
|
110
|
+
* at the same heights as its neighbours — measuring from the resolved baseline
|
|
111
|
+
* instead would silently shift every breakpoint by the floor, the quiet
|
|
112
|
+
* wrongness [PND-BANDBAR2] exists to remove.
|
|
113
|
+
*/
|
|
114
|
+
export declare function buildBandGradient(ctx: CanvasRenderingContext2D, yScale: Scale, plotHeight: number, banding: BandLadder): CanvasGradient | string;
|
|
73
115
|
/**
|
|
74
116
|
* **Is the pointer inside this area?** The filled-region counterpart of
|
|
75
117
|
* `traceHitIndex` ([PND-TRACESEL]) — returns the nearest sample's index as
|
package/dist/area.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { area as d3area, curveLinear } from 'd3-shape';
|
|
2
|
-
import { baselinePxFromScale, strokeAffinePolyline, TRACE_HIT_PX, } from './line.js';
|
|
2
|
+
import { baselinePxFromScale, plotExtentOf, strokeAffinePolyline, TRACE_HIT_PX, } from './line.js';
|
|
3
3
|
import { bridgeGaps, collectGapEdges, drawGapBridges, drawGapFades, drawGapSteps, gapUnscalable, withAlpha, DEFAULT_GAP_MODE, DEFAULT_GAP_CONNECTOR_OPACITY, } from './gaps.js';
|
|
4
4
|
import { cullChartSeries } from './culling.js';
|
|
5
5
|
import { decimateM4Cached } from './decimate.js';
|
|
@@ -155,7 +155,7 @@ export function areaExtent(cs, baseline) {
|
|
|
155
155
|
* bracketed by `save`/`restore` so they don't leak into later layers. Gap edges
|
|
156
156
|
* are collected by one O(N) walk ({@link collectGapEdges}).
|
|
157
157
|
*/
|
|
158
|
-
export function drawArea(ctx, cs, xScale, yScale, style, baselineValue, curve = curveLinear, gaps = DEFAULT_GAP_MODE, gapConnectorOpacity = DEFAULT_GAP_CONNECTOR_OPACITY, decimate = true) {
|
|
158
|
+
export function drawArea(ctx, cs, xScale, yScale, style, baselineValue, curve = curveLinear, gaps = DEFAULT_GAP_MODE, gapConnectorOpacity = DEFAULT_GAP_CONNECTOR_OPACITY, decimate = true, banding) {
|
|
159
159
|
const sourceCount = cs.length; // pre-cull, pre-decimation (for draw stats)
|
|
160
160
|
const baselinePx = yScale(baselineValue);
|
|
161
161
|
// The fill gradient's vertical extent is computed from the **full** series (a
|
|
@@ -168,7 +168,13 @@ export function drawArea(ctx, cs, xScale, yScale, style, baselineValue, curve =
|
|
|
168
168
|
// instead of re-walking O(N) — the mountain@1M ceiling the bench profile
|
|
169
169
|
// flagged. A `'none'` bridge only fills interior gaps with interpolated values
|
|
170
170
|
// that stay within the finite extent, so the plain extent is exact for it too.
|
|
171
|
-
|
|
171
|
+
//
|
|
172
|
+
// **Banded** ([PND-BANDAREA]): one hard-stop pixel-space gradient carries the
|
|
173
|
+
// whole ladder for the fill AND the outline — see {@link buildBandGradient}
|
|
174
|
+
// for why a gradient rather than one clipped redraw per band.
|
|
175
|
+
const fill = banding !== undefined
|
|
176
|
+
? buildBandGradient(ctx, yScale, plotExtentOf(ctx, xScale, yScale).height, banding)
|
|
177
|
+
: buildGradient(ctx, columnFiniteExtent(cs.y, cs.length), yScale, baselinePx, style);
|
|
172
178
|
// Clip `cs` to what draws. **Decimated** (linear curve, `decimate !== false`):
|
|
173
179
|
// cull to the visible slice, then the same {@link decimateM4} pre-pass shrinks
|
|
174
180
|
// the fill + outline + gap-bridge work to O(plot width) once dense (the §2.2
|
|
@@ -237,14 +243,17 @@ export function drawArea(ctx, cs, xScale, yScale, style, baselineValue, curve =
|
|
|
237
243
|
ctx.fill();
|
|
238
244
|
ctx.restore();
|
|
239
245
|
// The outline on top: the area's top edge as a line (breaks at the same gaps
|
|
240
|
-
// as the fill), at full opacity over the graded fill.
|
|
246
|
+
// as the fill), at full opacity over the graded fill. Banded, it strokes with
|
|
247
|
+
// the same hard-stop gradient the fill used, so the line switches hue exactly
|
|
248
|
+
// where it crosses a threshold — the whole point of the ladder is that the
|
|
249
|
+
// reader sees *where* the value sits, and the edge is the value.
|
|
241
250
|
ctx.save();
|
|
242
251
|
ctx.beginPath();
|
|
243
252
|
if (outline !== null)
|
|
244
253
|
outline(ys);
|
|
245
254
|
else
|
|
246
255
|
strokeAffinePolyline(ctx, cs.x, ys, ax, ay);
|
|
247
|
-
ctx.strokeStyle = style.color;
|
|
256
|
+
ctx.strokeStyle = banding !== undefined ? fill : style.color;
|
|
248
257
|
ctx.lineWidth = style.width;
|
|
249
258
|
ctx.stroke();
|
|
250
259
|
ctx.restore();
|
|
@@ -345,6 +354,114 @@ function buildGradient(ctx, valueExtent, yScale, baselinePx, style) {
|
|
|
345
354
|
}
|
|
346
355
|
return grad;
|
|
347
356
|
}
|
|
357
|
+
/**
|
|
358
|
+
* The banded fill + stroke for `<AreaChart thresholds>` ([PND-BANDAREA]): one
|
|
359
|
+
* vertical **hard-stop gradient in pixel space**, a colour switch at every
|
|
360
|
+
* threshold crossing — `colors[0]` between `-t0` and `+t0`, `colors[k]` over
|
|
361
|
+
* magnitudes `[t(k-1), tk)` on both sides of zero. `thresholds`/`colors` arrive
|
|
362
|
+
* as a resolved {@link BandLadder} (ascending, positive, `n + 1` colours), the
|
|
363
|
+
* same currency `drawBars` takes.
|
|
364
|
+
*
|
|
365
|
+
* A gradient rather than one clipped redraw per band, and that is the
|
|
366
|
+
* load-bearing choice: K + 1 clipped passes walk the path K + 1 times and meet
|
|
367
|
+
* themselves at every boundary with an antialiased seam, where a gradient
|
|
368
|
+
* draws the identical single path once and costs O(K) colour stops. It also
|
|
369
|
+
* bands the **outline for free** — `strokeStyle` takes the same gradient, so
|
|
370
|
+
* the value line switches hue exactly at a crossing, which no per-band clip
|
|
371
|
+
* can do without shearing the stroke.
|
|
372
|
+
*
|
|
373
|
+
* The ladder is walked on the **magnitude** and mirrored below zero, exactly
|
|
374
|
+
* as `bandSpan` does for a bar: the boundary at `±tk` separates band `k` (the
|
|
375
|
+
* zero side) from band `k + 1` (the away side). Whether "away from zero" is up
|
|
376
|
+
* or down the canvas is probed from the scale itself (`t0` vs `t0 + 1`, both
|
|
377
|
+
* positive and finite by construction), so a flipped axis bands correctly. A
|
|
378
|
+
* boundary with **no position** on the scale contributes no crossing — on a
|
|
379
|
+
* log axis the negative mirrors (and zero) simply don't exist, which is the
|
|
380
|
+
* right reading. A crossing **off the plot** clamps to the gradient's ends
|
|
381
|
+
* (a real canvas throws on stops outside `[0, 1]`), which is also what makes a
|
|
382
|
+
* zoomed-in view honest: with every visible pixel inside one band, the clamp
|
|
383
|
+
* degenerates the other stops and the whole plot paints that band's colour.
|
|
384
|
+
*
|
|
385
|
+
* Falls back to the top band's flat colour when there is nothing to anchor on
|
|
386
|
+
* (no plot height, or no boundary with a position at all) — reachable only
|
|
387
|
+
* with a degenerate scale stub, since every real axis positions a positive
|
|
388
|
+
* finite value; any flat colour is equally (in)correct there, and the top
|
|
389
|
+
* band's is at least stable.
|
|
390
|
+
*
|
|
391
|
+
* Like the bar ladder, breakpoints are **absolute data values**, so the
|
|
392
|
+
* baseline plays no part here: an area resting on a non-zero floor still bands
|
|
393
|
+
* at the same heights as its neighbours — measuring from the resolved baseline
|
|
394
|
+
* instead would silently shift every breakpoint by the floor, the quiet
|
|
395
|
+
* wrongness [PND-BANDBAR2] exists to remove.
|
|
396
|
+
*/
|
|
397
|
+
export function buildBandGradient(ctx, yScale, plotHeight, banding) {
|
|
398
|
+
const { thresholds, colors } = banding;
|
|
399
|
+
const fallback = colors[colors.length - 1];
|
|
400
|
+
// Guards NaN too — `!(x > 0)`, not `x <= 0`.
|
|
401
|
+
if (!(plotHeight > 0))
|
|
402
|
+
return fallback;
|
|
403
|
+
// Axis direction: does value increase toward smaller pixels (the canvas
|
|
404
|
+
// norm)? Probed on the ladder's own first breakpoint — positive and finite
|
|
405
|
+
// by construction, so it has a position on every axis kind (linear, log,
|
|
406
|
+
// symlog). Non-finite or equal probes default to the norm.
|
|
407
|
+
const pA = yScale(thresholds[0]);
|
|
408
|
+
const pB = yScale(thresholds[0] + 1);
|
|
409
|
+
const higherValueAtSmallerPx = !(Number.isFinite(pA) &&
|
|
410
|
+
Number.isFinite(pB) &&
|
|
411
|
+
pB > pA);
|
|
412
|
+
const crossings = [];
|
|
413
|
+
for (let k = 0; k < thresholds.length; k += 1) {
|
|
414
|
+
const zeroSide = colors[k];
|
|
415
|
+
const awaySide = colors[k + 1];
|
|
416
|
+
for (const sign of [1, -1]) {
|
|
417
|
+
const v = sign * thresholds[k];
|
|
418
|
+
const px = yScale(v);
|
|
419
|
+
if (!Number.isFinite(px))
|
|
420
|
+
continue; // no position — no crossing
|
|
421
|
+
const awayAbove = sign > 0 === higherValueAtSmallerPx;
|
|
422
|
+
crossings.push(awayAbove
|
|
423
|
+
? { px, above: awaySide, below: zeroSide, k, sign }
|
|
424
|
+
: { px, above: zeroSide, below: awaySide, k, sign });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
if (crossings.length === 0)
|
|
428
|
+
return fallback;
|
|
429
|
+
// Sort by pixel; same-pixel same-sign crossings (a duplicate breakpoint's
|
|
430
|
+
// empty band, or distinct breakpoints collapsed by an extreme zoom) order
|
|
431
|
+
// **away-band-outermost**: the away side's colour first coming from the
|
|
432
|
+
// away direction, the zero side's first coming from zero. That makes the
|
|
433
|
+
// walk below telescope — the seed reads the true outermost band and the
|
|
434
|
+
// last stop at the pixel is the true inner colour, with the skipped bands
|
|
435
|
+
// as zero-width ghosts in between — instead of seeding one band short and
|
|
436
|
+
// blending across the region below. Bars skip an empty band the same way
|
|
437
|
+
// (`bandSpanInto` clips it to nothing); a same-pixel *opposite-sign* pair
|
|
438
|
+
// (a folded scale) has no defined order and keeps insertion order.
|
|
439
|
+
crossings.sort((a, b) => {
|
|
440
|
+
if (a.px !== b.px)
|
|
441
|
+
return a.px - b.px;
|
|
442
|
+
if (a.sign !== b.sign)
|
|
443
|
+
return 0;
|
|
444
|
+
const awayFirst = a.sign > 0 === higherValueAtSmallerPx;
|
|
445
|
+
return awayFirst ? b.k - a.k : a.k - b.k;
|
|
446
|
+
});
|
|
447
|
+
const grad = ctx.createLinearGradient(0, 0, 0, plotHeight);
|
|
448
|
+
const offsetOf = (px) => {
|
|
449
|
+
const o = px / plotHeight;
|
|
450
|
+
return o < 0 ? 0 : o > 1 ? 1 : o;
|
|
451
|
+
};
|
|
452
|
+
// Each crossing is a hard stop: two stops at one offset, old colour then
|
|
453
|
+
// new. The region above the first crossing seeds the walk; clamped
|
|
454
|
+
// off-plot crossings collapse to zero-height regions at the ends, leaving
|
|
455
|
+
// the visible span in the band it actually occupies.
|
|
456
|
+
grad.addColorStop(0, crossings[0].above);
|
|
457
|
+
for (const c of crossings) {
|
|
458
|
+
const off = offsetOf(c.px);
|
|
459
|
+
grad.addColorStop(off, c.above);
|
|
460
|
+
grad.addColorStop(off, c.below);
|
|
461
|
+
}
|
|
462
|
+
grad.addColorStop(1, crossings[crossings.length - 1].below);
|
|
463
|
+
return grad;
|
|
464
|
+
}
|
|
348
465
|
/**
|
|
349
466
|
* **Is the pointer inside this area?** The filled-region counterpart of
|
|
350
467
|
* `traceHitIndex` ([PND-TRACESEL]) — returns the nearest sample's index as
|
package/dist/bars.d.ts
CHANGED
|
@@ -55,7 +55,7 @@ export declare function resolveBarBaseline(yScale: Scale): number;
|
|
|
55
55
|
* hit region are deliberately *not* the same geometry: the `gapPx` inset
|
|
56
56
|
* separates columns visually without carving a dead channel out of the target.
|
|
57
57
|
*/
|
|
58
|
-
export declare function barRect(cs: BarSeries, i: number, xScale: Scale, yScale: Scale, baseline: number, gapPx: number, minWidthPx: number): [x0: number, x1: number, yTop: number, yBottom: number] | null;
|
|
58
|
+
export declare function barRect(cs: BarSeries, i: number, xScale: Scale, yScale: Scale, baseline: number, gapPx: number, minWidthPx: number, maxWidthPx?: number): [x0: number, x1: number, yTop: number, yBottom: number] | null;
|
|
59
59
|
/**
|
|
60
60
|
* The value-space span `[lo, hi]` of **threshold band `k`** along a bar running
|
|
61
61
|
* from `base` to `v`, or `null` when the bar doesn't reach that band.
|
|
@@ -306,6 +306,9 @@ export interface StackStyle {
|
|
|
306
306
|
readonly fills: readonly string[];
|
|
307
307
|
readonly opacity: number;
|
|
308
308
|
readonly outlineWidth: number;
|
|
309
|
+
/** Cap on a segment's ink span in px, centred in the slot — the stacked
|
|
310
|
+
* counterpart of `BarStyle.maxWidth` ([PND-BARWIDTH]). Omitted ⇒ uncapped. */
|
|
311
|
+
readonly maxWidth?: number;
|
|
309
312
|
/**
|
|
310
313
|
* Optional **per-bin** fill override, aligned index-for-index to the bins
|
|
311
314
|
* (bin `b` uses `binFills[b]`), taking precedence over the per-group
|
|
@@ -457,7 +460,7 @@ export declare function stackBase(orientation: Orientation, xScale: Scale, yScal
|
|
|
457
460
|
* is unfloored. Shared by {@link drawStacks} and {@link stackAt} so the drawn rect
|
|
458
461
|
* and the hit rect are identical.
|
|
459
462
|
*/
|
|
460
|
-
export declare function segmentRect(ss: StackedBarSeries, b: number, g: number, orientation: Orientation, xScale: Scale, yScale: Scale, cumBefore: number, gapPx: number, minSpanPx: number): [x0: number, x1: number, yTop: number, yBottom: number] | null;
|
|
463
|
+
export declare function segmentRect(ss: StackedBarSeries, b: number, g: number, orientation: Orientation, xScale: Scale, yScale: Scale, cumBefore: number, gapPx: number, minSpanPx: number, maxSpanPx?: number): [x0: number, x1: number, yTop: number, yBottom: number] | null;
|
|
461
464
|
/**
|
|
462
465
|
* Fill every segment of every bin in `ss`, stacking each bin's groups from the
|
|
463
466
|
* value baseline outward (bottom → top vertical, left → right horizontal). A gap
|
|
@@ -484,5 +487,9 @@ export declare function drawStacks(ctx: CanvasRenderingContext2D, ss: StackedBar
|
|
|
484
487
|
* O(N·G) over bins × groups (no spatial index — histogram bin/group counts are
|
|
485
488
|
* small; click / hover are cheap events).
|
|
486
489
|
*/
|
|
487
|
-
export declare function stackAt(ss: StackedBarSeries, px: number, py: number, orientation: Orientation, xScale: Scale, yScale: Scale, gapPx: number, minSpanPx: number
|
|
490
|
+
export declare function stackAt(ss: StackedBarSeries, px: number, py: number, orientation: Orientation, xScale: Scale, yScale: Scale, gapPx: number, minSpanPx: number,
|
|
491
|
+
/** Must match the draw's cap ([PND-BARWIDTH]) — this function's whole
|
|
492
|
+
* contract is that its rect is the drawn rect, so a cap applied to one and
|
|
493
|
+
* not the other silently drifts the hit target off the ink. */
|
|
494
|
+
maxSpanPx?: number): [bin: number, group: number, begin: number, name: string, value: number] | null;
|
|
488
495
|
//# sourceMappingURL=bars.d.ts.map
|
package/dist/bars.js
CHANGED
|
@@ -73,11 +73,11 @@ export function resolveBarBaseline(yScale) {
|
|
|
73
73
|
* hit region are deliberately *not* the same geometry: the `gapPx` inset
|
|
74
74
|
* separates columns visually without carving a dead channel out of the target.
|
|
75
75
|
*/
|
|
76
|
-
export function barRect(cs, i, xScale, yScale, baseline, gapPx, minWidthPx) {
|
|
76
|
+
export function barRect(cs, i, xScale, yScale, baseline, gapPx, minWidthPx, maxWidthPx) {
|
|
77
77
|
const v = cs.y[i];
|
|
78
78
|
if (!Number.isFinite(v))
|
|
79
79
|
return null;
|
|
80
|
-
const [x0, x1] = barSpanPx(cs.begin[i], cs.end[i], xScale, gapPx, minWidthPx);
|
|
80
|
+
const [x0, x1] = barSpanPx(cs.begin[i], cs.end[i], xScale, gapPx, minWidthPx, maxWidthPx);
|
|
81
81
|
const yValue = yScale(v);
|
|
82
82
|
const yBase = yScale(baseline);
|
|
83
83
|
return [x0, x1, Math.min(yValue, yBase), Math.max(yValue, yBase)];
|
|
@@ -430,7 +430,7 @@ spans = NO_SPANS) {
|
|
|
430
430
|
: null;
|
|
431
431
|
let drawn = 0;
|
|
432
432
|
for (let i = vStart; i < vEnd; i += 1) {
|
|
433
|
-
const rect = barRect(cs, i, xScale, yScale, baseline, gapPx, style.minWidth);
|
|
433
|
+
const rect = barRect(cs, i, xScale, yScale, baseline, gapPx, style.minWidth, style.maxWidth);
|
|
434
434
|
if (rect === null)
|
|
435
435
|
continue;
|
|
436
436
|
const [x0, x1, yTop, yBottom] = rect;
|
|
@@ -779,7 +779,7 @@ export function stackBase(orientation, xScale, yScale) {
|
|
|
779
779
|
* is unfloored. Shared by {@link drawStacks} and {@link stackAt} so the drawn rect
|
|
780
780
|
* and the hit rect are identical.
|
|
781
781
|
*/
|
|
782
|
-
export function segmentRect(ss, b, g, orientation, xScale, yScale, cumBefore, gapPx, minSpanPx) {
|
|
782
|
+
export function segmentRect(ss, b, g, orientation, xScale, yScale, cumBefore, gapPx, minSpanPx, maxSpanPx) {
|
|
783
783
|
const G = ss.groups.length;
|
|
784
784
|
const v = ss.values[b * G + g];
|
|
785
785
|
// Skip non-finite (a gap) or zero (a zero-extent rect that can't draw or be
|
|
@@ -801,12 +801,12 @@ export function segmentRect(ss, b, g, orientation, xScale, yScale, cumBefore, ga
|
|
|
801
801
|
if (!Number.isFinite(v) || v === 0)
|
|
802
802
|
return null;
|
|
803
803
|
if (orientation === 'vertical') {
|
|
804
|
-
const [x0, x1] = barSpanPx(ss.begin[b], ss.end[b], xScale, gapPx, minSpanPx);
|
|
804
|
+
const [x0, x1] = barSpanPx(ss.begin[b], ss.end[b], xScale, gapPx, minSpanPx, maxSpanPx);
|
|
805
805
|
const yA = yScale(cumBefore);
|
|
806
806
|
const yB = yScale(cumBefore + v);
|
|
807
807
|
return [x0, x1, Math.min(yA, yB), Math.max(yA, yB)];
|
|
808
808
|
}
|
|
809
|
-
const [y0, y1] = barSpanPx(ss.begin[b], ss.end[b], yScale, gapPx, minSpanPx);
|
|
809
|
+
const [y0, y1] = barSpanPx(ss.begin[b], ss.end[b], yScale, gapPx, minSpanPx, maxSpanPx);
|
|
810
810
|
const xA = xScale(cumBefore);
|
|
811
811
|
const xB = xScale(cumBefore + v);
|
|
812
812
|
return [Math.min(xA, xB), Math.max(xA, xB), y0, y1];
|
|
@@ -865,7 +865,7 @@ spans = NO_SPANS) {
|
|
|
865
865
|
let cumNeg = base;
|
|
866
866
|
for (let g = 0; g < G; g += 1) {
|
|
867
867
|
const v = ss.values[b * G + g];
|
|
868
|
-
const rect = segmentRect(ss, b, g, orientation, xScale, yScale, v < 0 ? cumNeg : cumPos, gapPx, minSpanPx);
|
|
868
|
+
const rect = segmentRect(ss, b, g, orientation, xScale, yScale, v < 0 ? cumNeg : cumPos, gapPx, minSpanPx, style.maxWidth);
|
|
869
869
|
if (Number.isFinite(v)) {
|
|
870
870
|
if (v > 0)
|
|
871
871
|
cumPos += v;
|
|
@@ -1011,7 +1011,11 @@ spans = NO_SPANS) {
|
|
|
1011
1011
|
* O(N·G) over bins × groups (no spatial index — histogram bin/group counts are
|
|
1012
1012
|
* small; click / hover are cheap events).
|
|
1013
1013
|
*/
|
|
1014
|
-
export function stackAt(ss, px, py, orientation, xScale, yScale, gapPx, minSpanPx
|
|
1014
|
+
export function stackAt(ss, px, py, orientation, xScale, yScale, gapPx, minSpanPx,
|
|
1015
|
+
/** Must match the draw's cap ([PND-BARWIDTH]) — this function's whole
|
|
1016
|
+
* contract is that its rect is the drawn rect, so a cap applied to one and
|
|
1017
|
+
* not the other silently drifts the hit target off the ink. */
|
|
1018
|
+
maxSpanPx) {
|
|
1015
1019
|
const G = ss.groups.length;
|
|
1016
1020
|
const base = stackBase(orientation, xScale, yScale);
|
|
1017
1021
|
for (let b = 0; b < ss.length; b += 1) {
|
|
@@ -1021,7 +1025,7 @@ export function stackAt(ss, px, py, orientation, xScale, yScale, gapPx, minSpanP
|
|
|
1021
1025
|
let cumNeg = base;
|
|
1022
1026
|
for (let g = 0; g < G; g += 1) {
|
|
1023
1027
|
const v = ss.values[b * G + g];
|
|
1024
|
-
const rect = segmentRect(ss, b, g, orientation, xScale, yScale, v < 0 ? cumNeg : cumPos, gapPx, minSpanPx);
|
|
1028
|
+
const rect = segmentRect(ss, b, g, orientation, xScale, yScale, v < 0 ? cumNeg : cumPos, gapPx, minSpanPx, maxSpanPx);
|
|
1025
1029
|
if (Number.isFinite(v)) {
|
|
1026
1030
|
if (v > 0)
|
|
1027
1031
|
cumPos += v;
|
package/dist/context.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import type { ScaleContinuousNumeric, ScaleLinear, ScaleTime } from 'd3-scale';
|
|
2
|
+
import type { ScaleContinuousNumeric, ScaleLinear, ScaleLogarithmic, ScaleSymLog, ScaleTime } from 'd3-scale';
|
|
3
3
|
import type { ChartTheme } from './theme.js';
|
|
4
4
|
import type { AxisFormat, CursorFormat } from './format.js';
|
|
5
5
|
import type { LegendItemSpec } from './swatch.js';
|
|
@@ -25,6 +25,19 @@ export interface LabelPlacement {
|
|
|
25
25
|
readonly lane: number;
|
|
26
26
|
readonly label: string | null;
|
|
27
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* The container's shared x→pixel scale, in every kind it can resolve to. All
|
|
30
|
+
* five are callable (`value → px`) and expose `invert` / `ticks` /
|
|
31
|
+
* `tickFormat`, which is the whole surface the library — and a consumer
|
|
32
|
+
* reading the frame ({@link ChartFrame.xScale}) — uses; that shared shape is
|
|
33
|
+
* what lets a trading-time or ordinal axis drop in where a linear one went.
|
|
34
|
+
*
|
|
35
|
+
* Named (rather than written inline on {@link ContainerFrame.xScale}) because
|
|
36
|
+
* {@link useChartFrame} publishes it: a consumer positioning DOM chrome over
|
|
37
|
+
* the plot needs to be able to *write down the type* of the scale it maps
|
|
38
|
+
* through.
|
|
39
|
+
*/
|
|
40
|
+
export type ChartXScale = ScaleTime<number, number> | ScaleLinear<number, number> | ScaleLogarithmic<number, number> | ScaleSymLog<number, number> | TradingTimeScale | ScaleBand | ElapsedScale;
|
|
28
41
|
export interface ContainerFrame {
|
|
29
42
|
readonly timeRange: readonly [number, number];
|
|
30
43
|
readonly width: number;
|
|
@@ -355,7 +368,16 @@ export interface ContainerFrame {
|
|
|
355
368
|
* (axis labels, gridlines, cursor pill) reads durations without any consumer
|
|
356
369
|
* knowing about the mode.
|
|
357
370
|
*/
|
|
358
|
-
readonly xScale:
|
|
371
|
+
readonly xScale: ChartXScale;
|
|
372
|
+
/**
|
|
373
|
+
* Is the x scale **logarithmic** (`log` or `symlog`)?
|
|
374
|
+
*
|
|
375
|
+
* Detected structurally rather than threaded from the prop: `base()` exists
|
|
376
|
+
* on d3's log scales and on no other continuous scale, which is the same test
|
|
377
|
+
* `yticks.ts` already uses on the y side. Read by the viewport gestures,
|
|
378
|
+
* which must do their arithmetic in log space (see `ViewportOptions`).
|
|
379
|
+
*/
|
|
380
|
+
readonly xIsLog: boolean;
|
|
359
381
|
/**
|
|
360
382
|
* The discontinuity provider backing a **trading-time** x axis, if one was
|
|
361
383
|
* supplied to the container — closed-market time (weekends, holidays,
|
|
@@ -1597,17 +1619,17 @@ export interface LayerEntry {
|
|
|
1597
1619
|
}
|
|
1598
1620
|
/** A y-axis declared in a {@link ChartRow} via `<YAxis>`. */
|
|
1599
1621
|
/** Which scale a y axis maps its domain through. */
|
|
1600
|
-
export type YScaleKind = 'linear' | 'log';
|
|
1622
|
+
export type YScaleKind = 'linear' | 'log' | 'symlog';
|
|
1601
1623
|
/**
|
|
1602
|
-
* A row's resolved y scale — d3's `scaleLinear()`,
|
|
1603
|
-
*
|
|
1624
|
+
* A row's resolved y scale — d3's `scaleLinear()`, `scaleLog()` when the axis
|
|
1625
|
+
* asks for `scale="log"`, or `scaleSymlog()` for `scale="symlog"`.
|
|
1604
1626
|
*
|
|
1605
1627
|
* Deliberately the **continuous-numeric** supertype rather than `ScaleLinear`:
|
|
1606
1628
|
* every consumer (the axis labels, the row's gridlines, the cursor readout, and
|
|
1607
1629
|
* every draw layer) only ever calls it, or reads `domain` / `range` / `ticks` /
|
|
1608
|
-
* `tickFormat` / `invert` — the surface
|
|
1609
|
-
* type here is what lets a log axis be transparent to the draw
|
|
1610
|
-
* of every layer growing a branch.
|
|
1630
|
+
* `tickFormat` / `invert` — the surface all three scales share. Keeping the
|
|
1631
|
+
* shared type here is what lets a log or symlog axis be transparent to the draw
|
|
1632
|
+
* layers instead of every layer growing a branch.
|
|
1611
1633
|
*/
|
|
1612
1634
|
export type YScale = ScaleContinuousNumeric<number, number>;
|
|
1613
1635
|
export interface AxisSpec {
|
|
@@ -1617,6 +1639,11 @@ export interface AxisSpec {
|
|
|
1617
1639
|
readonly width: number;
|
|
1618
1640
|
/** Which scale the axis maps its domain through ({@link YAxisProps.scale}). */
|
|
1619
1641
|
readonly scale: YScaleKind;
|
|
1642
|
+
/** `scale="symlog"`'s linear window as a **fraction of the domain's largest
|
|
1643
|
+
* magnitude** ({@link YAxisProps.linearWindow}) — `undefined` on any other
|
|
1644
|
+
* scale. Domain-relative rather than absolute so it survives a domain change
|
|
1645
|
+
* without a recompute ([PND-SYMLOG]). */
|
|
1646
|
+
readonly linearWindow?: number | undefined;
|
|
1620
1647
|
/** Explicit domain bounds, or `undefined` to auto-fit linked layers. */
|
|
1621
1648
|
readonly min: number | undefined;
|
|
1622
1649
|
readonly max: number | undefined;
|
|
@@ -1650,6 +1677,17 @@ export interface AxisSpec {
|
|
|
1650
1677
|
*/
|
|
1651
1678
|
export interface RowFrame {
|
|
1652
1679
|
readonly height: number;
|
|
1680
|
+
/**
|
|
1681
|
+
* The plot's **top inset** within the row's box, in px — the header band
|
|
1682
|
+
* reserved when any axis in the row draws a `labelPlacement="top"` title,
|
|
1683
|
+
* and `0` when none does. The y-scales' range is `[height, topInset]`, so
|
|
1684
|
+
* the drawable plot is `topInset … height`.
|
|
1685
|
+
*
|
|
1686
|
+
* Carried on the frame (rather than staying a local in `ChartRow`) because
|
|
1687
|
+
* {@link useChartFrame} publishes it — without it a consumer placing an
|
|
1688
|
+
* overlay inside the plot would silently sit under a top axis title.
|
|
1689
|
+
*/
|
|
1690
|
+
readonly topInset: number;
|
|
1653
1691
|
readonly yScales: ReadonlyMap<string, YScale>;
|
|
1654
1692
|
/** Value formatter per axis id (resolved from the axis's {@link AxisSpec.format}
|
|
1655
1693
|
* against its scale) — used by both the tick labels and the cursor readout, so
|
package/dist/data.d.ts
CHANGED
|
@@ -499,6 +499,44 @@ export interface CategoryDatum {
|
|
|
499
499
|
readonly label: string;
|
|
500
500
|
readonly value: number;
|
|
501
501
|
}
|
|
502
|
+
/**
|
|
503
|
+
* One category of a **stacked** category chart ([PND-CATSTACK]) — a label plus a
|
|
504
|
+
* value **per group**, read by name through `<BarChart columns>`.
|
|
505
|
+
*
|
|
506
|
+
* The `values` record is the shape the list family already uses
|
|
507
|
+
* (`ListRow.values`), deliberately: "a row with named values" is one concept in
|
|
508
|
+
* this library and a category with several groups is exactly that. A missing or
|
|
509
|
+
* non-finite entry reads as a gap, so a group absent from one category is a hole
|
|
510
|
+
* rather than a zero — the same rule every other reader applies.
|
|
511
|
+
*/
|
|
512
|
+
export interface CategoryStackDatum {
|
|
513
|
+
readonly label: string;
|
|
514
|
+
readonly values: Readonly<Record<string, number | undefined>>;
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* Build a {@link StackedBarSeries} from ordered categories carrying a value
|
|
518
|
+
* **per group** — the stacked counterpart of {@link categoryStack}
|
|
519
|
+
* ([PND-CATSTACK]).
|
|
520
|
+
*
|
|
521
|
+
* Geometry is identical to the single-value case (one unit slot `[i, i+1]` per
|
|
522
|
+
* category, `marks` carrying the names), so the categorical axis derives its
|
|
523
|
+
* ordered labels exactly as before and a pinned selection still keys on the
|
|
524
|
+
* stable name. Only `groups` and the `values` layout differ, and both match
|
|
525
|
+
* {@link stacksFromColumns} — `values[i * G + g]`, bin-major — so this reaches
|
|
526
|
+
* the shipped `drawStacks` path with no new draw code.
|
|
527
|
+
*
|
|
528
|
+
* **Why this replaces a real workaround.** Composing the same picture from one
|
|
529
|
+
* `<BarChart categories>` layer per cumulative total (drawn outermost-first so
|
|
530
|
+
* each overpaints the one beneath) costs three things a first-class stack does
|
|
531
|
+
* not: hand-assembled legends, label thinning that cannot see the other layers,
|
|
532
|
+
* and — since selection entries key on `(layer id, mark)` — a controlled set
|
|
533
|
+
* that must be replicated across every segment layer, where missing one makes a
|
|
534
|
+
* selected bar recede *from the waist up*. With one layer and one `mark` per
|
|
535
|
+
* bar, that last failure is not expressible.
|
|
536
|
+
*
|
|
537
|
+
* O(n·G) with one `Float64Array` allocation, matching `stacksFromColumns`.
|
|
538
|
+
*/
|
|
539
|
+
export declare function categoryStacks(records: readonly CategoryStackDatum[], columns: readonly string[]): StackedBarSeries;
|
|
502
540
|
/**
|
|
503
541
|
* Build a {@link StackedBarSeries} (single group, `G === 1`) from an ordered list
|
|
504
542
|
* of `{ label, value }` categories — one **unit slot** `[i, i+1]` per category, in
|
package/dist/data.js
CHANGED
|
@@ -619,6 +619,49 @@ export function stacksFromBins(bins, columns, options = {}) {
|
|
|
619
619
|
}
|
|
620
620
|
return { begin, end, groups: columns, values, length: n };
|
|
621
621
|
}
|
|
622
|
+
/**
|
|
623
|
+
* Build a {@link StackedBarSeries} from ordered categories carrying a value
|
|
624
|
+
* **per group** — the stacked counterpart of {@link categoryStack}
|
|
625
|
+
* ([PND-CATSTACK]).
|
|
626
|
+
*
|
|
627
|
+
* Geometry is identical to the single-value case (one unit slot `[i, i+1]` per
|
|
628
|
+
* category, `marks` carrying the names), so the categorical axis derives its
|
|
629
|
+
* ordered labels exactly as before and a pinned selection still keys on the
|
|
630
|
+
* stable name. Only `groups` and the `values` layout differ, and both match
|
|
631
|
+
* {@link stacksFromColumns} — `values[i * G + g]`, bin-major — so this reaches
|
|
632
|
+
* the shipped `drawStacks` path with no new draw code.
|
|
633
|
+
*
|
|
634
|
+
* **Why this replaces a real workaround.** Composing the same picture from one
|
|
635
|
+
* `<BarChart categories>` layer per cumulative total (drawn outermost-first so
|
|
636
|
+
* each overpaints the one beneath) costs three things a first-class stack does
|
|
637
|
+
* not: hand-assembled legends, label thinning that cannot see the other layers,
|
|
638
|
+
* and — since selection entries key on `(layer id, mark)` — a controlled set
|
|
639
|
+
* that must be replicated across every segment layer, where missing one makes a
|
|
640
|
+
* selected bar recede *from the waist up*. With one layer and one `mark` per
|
|
641
|
+
* bar, that last failure is not expressible.
|
|
642
|
+
*
|
|
643
|
+
* O(n·G) with one `Float64Array` allocation, matching `stacksFromColumns`.
|
|
644
|
+
*/
|
|
645
|
+
export function categoryStacks(records, columns) {
|
|
646
|
+
const n = records.length;
|
|
647
|
+
const G = columns.length;
|
|
648
|
+
const begin = new Float64Array(n);
|
|
649
|
+
const end = new Float64Array(n);
|
|
650
|
+
const values = new Float64Array(n * G);
|
|
651
|
+
const marks = new Array(n);
|
|
652
|
+
for (let i = 0; i < n; i += 1) {
|
|
653
|
+
begin[i] = i;
|
|
654
|
+
end[i] = i + 1;
|
|
655
|
+
marks[i] = records[i].label;
|
|
656
|
+
const row = records[i].values;
|
|
657
|
+
for (let g = 0; g < G; g += 1) {
|
|
658
|
+
const v = row[columns[g]];
|
|
659
|
+
// A missing key and a non-finite value are the same thing here: no bar.
|
|
660
|
+
values[i * G + g] = typeof v === 'number' && Number.isFinite(v) ? v : NaN;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return { begin, end, groups: columns, values, length: n, marks };
|
|
664
|
+
}
|
|
622
665
|
/**
|
|
623
666
|
* Build a {@link StackedBarSeries} (single group, `G === 1`) from an ordered list
|
|
624
667
|
* of `{ label, value }` categories — one **unit slot** `[i, i+1]` per category, in
|
package/dist/format.d.ts
CHANGED
|
@@ -47,6 +47,9 @@ interface Tickable {
|
|
|
47
47
|
tickFormat(count: number, specifier?: string): (value: number) => string;
|
|
48
48
|
/** Present on d3's `scaleLog` and on no other continuous scale. */
|
|
49
49
|
base?: () => number;
|
|
50
|
+
/** Present on d3's `scaleSymlog` and on no other continuous scale — the linear
|
|
51
|
+
* window's half-width in data units. */
|
|
52
|
+
constant?: () => number;
|
|
50
53
|
domain?: () => number[];
|
|
51
54
|
}
|
|
52
55
|
/**
|
|
@@ -74,7 +77,19 @@ interface Tickable {
|
|
|
74
77
|
* render blank for almost every real number. A linear scale's `tickFormat`
|
|
75
78
|
* applies the specifier to whatever it is handed, which is what every consumer
|
|
76
79
|
* of this function actually wants; the axis's own tick *thinning* is handled by
|
|
77
|
-
* `
|
|
80
|
+
* `tickValues`, not here.
|
|
81
|
+
*
|
|
82
|
+
* **A symlog scale's precision comes from its knee, not its span** ([PND-SYMLOG]).
|
|
83
|
+
* `scaleSymlog.tickFormat` is `linearish`, so it derives precision from the
|
|
84
|
+
* domain — and a symlog axis is chosen precisely when the interesting values are
|
|
85
|
+
* *orders of magnitude smaller* than the domain. On `[-1, 1]` with a `0.02` knee
|
|
86
|
+
* the ladder emits `-0.02, 0, 0.02` and a span-derived formatter labels all three
|
|
87
|
+
* **`"0.0"`**: three ticks at three positions asserting the same value, on the
|
|
88
|
+
* axis whose whole purpose was to separate them. Formatting through a linear
|
|
89
|
+
* scale over `[-knee, knee]` calibrates to the smallest magnitude the ladder can
|
|
90
|
+
* emit. It changes nothing when the knee is already coarse (a 20k knee on a ±1M
|
|
91
|
+
* domain formats identically), and the cursor readout inherits the same extra
|
|
92
|
+
* precision — which is wanted, since near-zero is where a symlog readout is read.
|
|
78
93
|
*/
|
|
79
94
|
export declare function resolveAxisFormat(scale: Tickable, count: number, format: AxisFormat | undefined): (value: number) => string;
|
|
80
95
|
/** The slice of a d3 **time** scale {@link resolveTimeFormat} needs. A d3
|
package/dist/format.js
CHANGED
|
@@ -24,14 +24,29 @@ import { scaleLinear } from 'd3-scale';
|
|
|
24
24
|
* render blank for almost every real number. A linear scale's `tickFormat`
|
|
25
25
|
* applies the specifier to whatever it is handed, which is what every consumer
|
|
26
26
|
* of this function actually wants; the axis's own tick *thinning* is handled by
|
|
27
|
-
* `
|
|
27
|
+
* `tickValues`, not here.
|
|
28
|
+
*
|
|
29
|
+
* **A symlog scale's precision comes from its knee, not its span** ([PND-SYMLOG]).
|
|
30
|
+
* `scaleSymlog.tickFormat` is `linearish`, so it derives precision from the
|
|
31
|
+
* domain — and a symlog axis is chosen precisely when the interesting values are
|
|
32
|
+
* *orders of magnitude smaller* than the domain. On `[-1, 1]` with a `0.02` knee
|
|
33
|
+
* the ladder emits `-0.02, 0, 0.02` and a span-derived formatter labels all three
|
|
34
|
+
* **`"0.0"`**: three ticks at three positions asserting the same value, on the
|
|
35
|
+
* axis whose whole purpose was to separate them. Formatting through a linear
|
|
36
|
+
* scale over `[-knee, knee]` calibrates to the smallest magnitude the ladder can
|
|
37
|
+
* emit. It changes nothing when the knee is already coarse (a 20k knee on a ±1M
|
|
38
|
+
* domain formats identically), and the cursor readout inherits the same extra
|
|
39
|
+
* precision — which is wanted, since near-zero is where a symlog readout is read.
|
|
28
40
|
*/
|
|
29
41
|
export function resolveAxisFormat(scale, count, format) {
|
|
30
42
|
if (typeof format === 'function')
|
|
31
43
|
return format;
|
|
44
|
+
const knee = typeof scale.constant === 'function' ? Math.abs(scale.constant()) : 0;
|
|
32
45
|
const source = typeof scale.base === 'function' && typeof scale.domain === 'function'
|
|
33
46
|
? scaleLinear().domain(scale.domain())
|
|
34
|
-
:
|
|
47
|
+
: Number.isFinite(knee) && knee > 0
|
|
48
|
+
? scaleLinear().domain([-knee, knee])
|
|
49
|
+
: scale;
|
|
35
50
|
return format !== undefined
|
|
36
51
|
? source.tickFormat(count, format)
|
|
37
52
|
: source.tickFormat(count);
|
package/dist/index.d.ts
CHANGED
|
@@ -64,6 +64,9 @@ export type { LegendProps, LegendPlacement } from './Legend.js';
|
|
|
64
64
|
export type { SwatchSpec, LegendItemInput } from './swatch.js';
|
|
65
65
|
export { useChartLegend } from './useChartLegend.js';
|
|
66
66
|
export type { ChartLegend, LegendRow, LegendItem } from './useChartLegend.js';
|
|
67
|
+
export { useChartFrame } from './useChartFrame.js';
|
|
68
|
+
export type { ChartFrame, ChartFrameRow, ChartBands, ChartBand, } from './useChartFrame.js';
|
|
69
|
+
export type { ChartXScale } from './context.js';
|
|
67
70
|
export { scaleTradingTime } from './tradingTimeScale.js';
|
|
68
71
|
export type { TradingTimeScale, DiscontinuityProvider, TimeGrain, } from './tradingTimeScale.js';
|
|
69
72
|
export { scaleBand } from './bandScale.js';
|
|
@@ -73,8 +76,8 @@ export type { RegionProps, BaselineProps, MarkerProps, ZoneProps, } from './anno
|
|
|
73
76
|
export type { AnnotationKind, CreateSpec } from './context.js';
|
|
74
77
|
export { YAxisIndicator, createLiveValue } from './indicators.js';
|
|
75
78
|
export type { YAxisIndicatorProps, LiveValue } from './indicators.js';
|
|
76
|
-
export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, barsFromBins, ohlcFromTimeSeries, stacksFromGroups, stacksFromColumns, stacksFromBins, categoryStack, transposeRow, } from './data.js';
|
|
77
|
-
export type { ChartSeries, BandSeries, BoxSeries, BoxColumns, BarSeries, OhlcSeries, OhlcColumns, StackedBarSeries, BinRecord, StacksFromBinsOptions, CategoryDatum, RowAt, TransposeRowOptions, } from './data.js';
|
|
79
|
+
export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeries, barsFromBins, ohlcFromTimeSeries, stacksFromGroups, stacksFromColumns, stacksFromBins, categoryStack, categoryStacks, transposeRow, } from './data.js';
|
|
80
|
+
export type { ChartSeries, BandSeries, BoxSeries, BoxColumns, BarSeries, OhlcSeries, OhlcColumns, StackedBarSeries, BinRecord, StacksFromBinsOptions, CategoryDatum, CategoryStackDatum, RowAt, TransposeRowOptions, } from './data.js';
|
|
78
81
|
export type { Orientation } from './bars.js';
|
|
79
82
|
export type { RadiusEncoding, ColorEncoding } from './encoding.js';
|
|
80
83
|
export type { Curve } from './curve.js';
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,12 @@ export { Legend } from './Legend.js';
|
|
|
52
52
|
// consumers who design their own key (horizontal strips, ticker-compare,
|
|
53
53
|
// values-in-the-legend).
|
|
54
54
|
export { useChartLegend } from './useChartLegend.js';
|
|
55
|
+
// The resolved plot geometry — the plot rect, the axis gutters, the shared x
|
|
56
|
+
// scale, a row's y scales, and (on a category axis) the ordinal slot edges.
|
|
57
|
+
// What a consumer aligning DOM chrome to the plot would otherwise re-derive
|
|
58
|
+
// by mirroring the library's own gutter arithmetic — a duplicate that drifts
|
|
59
|
+
// silently the moment the library changes how a gutter is sized.
|
|
60
|
+
export { useChartFrame } from './useChartFrame.js';
|
|
55
61
|
export { scaleTradingTime } from './tradingTimeScale.js';
|
|
56
62
|
// The ordinal category (band) scale — the transpose view's "columns on x" axis.
|
|
57
63
|
export { scaleBand } from './bandScale.js';
|
|
@@ -68,6 +74,11 @@ export { fromTimeSeries, bandFromTimeSeries, boxFromTimeSeries, barsFromTimeSeri
|
|
|
68
74
|
stacksFromGroups, stacksFromColumns, stacksFromBins,
|
|
69
75
|
// Categorical row-read: one bar per `{ label, value }` on the category axis.
|
|
70
76
|
categoryStack,
|
|
77
|
+
// …and its stacked sibling: one bar per `{ label, values }`, segments named by
|
|
78
|
+
// `columns` ([PND-CATSTACK]). Public for the same reason every reader above is
|
|
79
|
+
// — a caller assembling a `StackedBarSeries` by hand needs it — and because
|
|
80
|
+
// API.md already documented it as public while `index.ts` did not export it.
|
|
81
|
+
categoryStacks,
|
|
71
82
|
// The transpose reader — one row of a wide series read across into categories.
|
|
72
83
|
transposeRow, } from './data.js';
|
|
73
84
|
export { defaultTheme, estelaTheme } from './theme.js';
|