@pond-ts/charts 0.70.0 → 0.71.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/AGENTS.md +3 -1
- package/API.md +30 -31
- package/CHANGELOG.md +111 -1
- package/dist/AreaChart.d.ts +30 -22
- package/dist/AreaChart.js +37 -22
- package/dist/BoxPlot.js +5 -3
- package/dist/Candlestick.d.ts +3 -3
- package/dist/Candlestick.js +5 -5
- package/dist/ChartContainer.d.ts +8 -149
- package/dist/ChartContainer.js +68 -111
- package/dist/ChartRow.d.ts +1 -12
- package/dist/ChartRow.js +15 -30
- package/dist/HeatMap.d.ts +3 -3
- package/dist/HeatMap.js +3 -3
- package/dist/Layers.js +84 -34
- package/dist/XAxis.js +1 -0
- package/dist/area.d.ts +6 -5
- package/dist/area.js +13 -6
- package/dist/axis-events.d.ts +4 -4
- package/dist/brush.d.ts +13 -21
- package/dist/brush.js +22 -36
- package/dist/context.d.ts +81 -96
- package/dist/cursors.d.ts +81 -51
- package/dist/cursors.js +164 -127
- package/dist/elapsed.d.ts +1 -1
- package/dist/elapsed.js +1 -1
- package/dist/format.d.ts +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/selection-fixtures.d.ts +4 -7
- package/dist/selection-fixtures.js +0 -9
- package/dist/selection-stories.d.ts +4 -6
- package/dist/selection-stories.js +4 -4
- package/dist/selectors.d.ts +2 -3
- package/dist/selectors.js +2 -3
- package/dist/tracker.d.ts +10 -28
- package/dist/tracker.js +17 -40
- package/dist/tradingTimeScale.d.ts +2 -2
- package/package.json +3 -3
package/dist/HeatMap.d.ts
CHANGED
|
@@ -161,9 +161,9 @@ export interface HeatMapProps<S extends SeriesSchema = SeriesSchema, VS extends
|
|
|
161
161
|
* theme, and the M5 "theme tokens optional-with-default" gate has to land
|
|
162
162
|
* first. Borrowing defers that decision instead of pre-empting it.
|
|
163
163
|
*
|
|
164
|
-
* **
|
|
165
|
-
*
|
|
166
|
-
*
|
|
164
|
+
* **Mount no cursor component with it.** A `<LineCursor>` draws the shared
|
|
165
|
+
* vertical line, and on a grid that is a *second, weaker* cursor competing
|
|
166
|
+
* with the one that already works: the cell under the pointer takes an
|
|
167
167
|
* outline, which says both axes at once. The line says only x, and a heat map's
|
|
168
168
|
* x position is rarely the question. The pointer's own crosshair shape plus the
|
|
169
169
|
* cell outline is the whole affordance.
|
package/dist/HeatMap.js
CHANGED
|
@@ -61,9 +61,9 @@ function marksOf(set) {
|
|
|
61
61
|
* theme, and the M5 "theme tokens optional-with-default" gate has to land
|
|
62
62
|
* first. Borrowing defers that decision instead of pre-empting it.
|
|
63
63
|
*
|
|
64
|
-
* **
|
|
65
|
-
*
|
|
66
|
-
*
|
|
64
|
+
* **Mount no cursor component with it.** A `<LineCursor>` draws the shared
|
|
65
|
+
* vertical line, and on a grid that is a *second, weaker* cursor competing
|
|
66
|
+
* with the one that already works: the cell under the pointer takes an
|
|
67
67
|
* outline, which says both axes at once. The line says only x, and a heat map's
|
|
68
68
|
* x position is rarely the question. The pointer's own crosshair shape plus the
|
|
69
69
|
* cell outline is the whole affordance.
|
package/dist/Layers.js
CHANGED
|
@@ -2,7 +2,7 @@ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-run
|
|
|
2
2
|
import { Children, Fragment, isValidElement, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, } from 'react';
|
|
3
3
|
import { Canvas } from './Canvas.js';
|
|
4
4
|
import { drawGrid, drawDividers, dividerAlphas, thinPixels } from './grid.js';
|
|
5
|
-
import { bandRect, regionSpan } from './tracker.js';
|
|
5
|
+
import { bandRect, categorySlots, regionSpan } from './tracker.js';
|
|
6
6
|
import { effectiveCursorEntries, gestureOwner } from './cursors.js';
|
|
7
7
|
import { renderBrushBand, renderBrushRect, resolveBrushClaim, resolveRangeDrag, warnSweepShadowsRangeDrag, } from './brush.js';
|
|
8
8
|
import { resolveSelection } from './select.js';
|
|
@@ -443,12 +443,10 @@ export function Layers({ children }) {
|
|
|
443
443
|
//
|
|
444
444
|
// - `blockPreview` — the fact ("this row previews blocks"). It also scopes
|
|
445
445
|
// the resting hover to the snap block (handlePointerMove).
|
|
446
|
-
// - `restingBand` — the brush band is this row's resting CURSOR
|
|
447
|
-
//
|
|
448
|
-
//
|
|
449
|
-
//
|
|
450
|
-
// their own slots (a mounted `<RangeCursor>` already draws this same
|
|
451
|
-
// band; a `<CrosshairCursor>` keeps its crosshair).
|
|
446
|
+
// - `restingBand` — the brush band is this row's resting CURSOR when no
|
|
447
|
+
// cursor component is mounted for the row. A mounted cursor still wins
|
|
448
|
+
// and keeps its own slots (a mounted `<RangeCursor>` already draws this
|
|
449
|
+
// same band; a `<CrosshairCursor>` keeps its crosshair).
|
|
452
450
|
//
|
|
453
451
|
// Which of the two the row gets is the TOPMOST sweep-capable layer's
|
|
454
452
|
// business (§8's z-order rule again, and the same rule `beginTopmostSweep`
|
|
@@ -466,13 +464,14 @@ export function Layers({ children }) {
|
|
|
466
464
|
!rectPreview &&
|
|
467
465
|
!topmostSweepSpanOnly(layers) &&
|
|
468
466
|
layers.some((e) => e.layer.beginSweep !== undefined);
|
|
469
|
-
// The resting brush
|
|
470
|
-
//
|
|
467
|
+
// The resting brush is the row's cursor when no cursor component is
|
|
468
|
+
// mounted for it (a mounted cursor was asked for, so it wins); only its
|
|
469
|
+
// SHAPE differs. A 2-D row gets no band: its snap block is a whole x column while
|
|
471
470
|
// a drag there captures a rect, so a band would advertise a set the gesture
|
|
472
471
|
// never selects (the same reason the block hover opts out).
|
|
473
472
|
const restingBrush = (blockPreview || rectPreview) &&
|
|
474
473
|
!editingActive &&
|
|
475
|
-
effectiveCursorEntries(container.cursors, row.rowKey).
|
|
474
|
+
effectiveCursorEntries(container.cursors, row.rowKey).length === 0;
|
|
476
475
|
// …and a **transposed** row gets none yet: the resting block preview is
|
|
477
476
|
// resolved from the shared x buckets, so a y-cutting row would draw a band
|
|
478
477
|
// over a column its drag can never select. Suppressed rather than
|
|
@@ -484,6 +483,7 @@ export function Layers({ children }) {
|
|
|
484
483
|
? []
|
|
485
484
|
: effectiveCursorEntries(container.cursors, row.rowKey), [editingActive, sweeping, restingBrush, container.cursors, row.rowKey]);
|
|
486
485
|
const wantsSamples = cursorEntries.some((e) => e.wants.samples);
|
|
486
|
+
const wantsReticle = cursorEntries.some((e) => e.wants.reticle);
|
|
487
487
|
const wantsFlags = cursorEntries.some((e) => e.wants.flags);
|
|
488
488
|
const wantsBand = cursorEntries.some((e) => e.wants.band);
|
|
489
489
|
const wantsPointer = cursorEntries.some((e) => e.wants.pointer);
|
|
@@ -502,15 +502,21 @@ export function Layers({ children }) {
|
|
|
502
502
|
// slides under it. Empty when not hovering — or when no effective cursor
|
|
503
503
|
// declared a need — so the data canvas is never touched and a line-only
|
|
504
504
|
// cursor never pays the per-layer walk.
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
505
|
+
//
|
|
506
|
+
// Two lists from one walk. `samples` feeds the per-series marks (dots,
|
|
507
|
+
// chips), so a layer with a consolidated flag (BoxPlot) is left out — it
|
|
508
|
+
// renders that flag instead (its values still fan to the off-chart readout
|
|
509
|
+
// via sampleAt on the container). `reticle` feeds the crosshair's pick,
|
|
510
|
+
// which lands on ONE value, so a box's quantiles are candidates there
|
|
511
|
+
// ([PND-BOXPLT]).
|
|
512
|
+
const { samples: trackerSamples, reticle: reticleSamples } = useMemo(() => {
|
|
513
|
+
if (cursorTime === null || (!wantsSamples && !wantsReticle))
|
|
514
|
+
return { samples: [], reticle: [] };
|
|
515
|
+
const samples = [];
|
|
516
|
+
const reticle = [];
|
|
509
517
|
for (const entry of layers) {
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
// via sampleAt on the container).
|
|
513
|
-
if (entry.layer.cursorFlag)
|
|
518
|
+
const toSamples = wantsSamples && !entry.layer.cursorFlag;
|
|
519
|
+
if (!toSamples && !wantsReticle)
|
|
514
520
|
continue;
|
|
515
521
|
const axisId = entry.axisId ?? defaultAxisId;
|
|
516
522
|
const yScale = yScales.get(axisId);
|
|
@@ -526,9 +532,12 @@ export function Layers({ children }) {
|
|
|
526
532
|
const axisOffset = axisOffsets.get(axisId) ?? 0;
|
|
527
533
|
const axisColor = axisColors.get(axisId);
|
|
528
534
|
for (const s of entry.layer.sampleAt(cursorTime)) {
|
|
529
|
-
|
|
535
|
+
const r = {
|
|
530
536
|
px: xScale(s.x),
|
|
531
537
|
py: yScale(s.value),
|
|
538
|
+
x: s.x,
|
|
539
|
+
value: s.value,
|
|
540
|
+
...(s.readout !== undefined ? { readout: s.readout } : {}),
|
|
532
541
|
axisId,
|
|
533
542
|
side,
|
|
534
543
|
axisOffset,
|
|
@@ -536,13 +545,18 @@ export function Layers({ children }) {
|
|
|
536
545
|
formatted: fmt(s.value),
|
|
537
546
|
color: s.color,
|
|
538
547
|
label: s.label,
|
|
539
|
-
}
|
|
548
|
+
};
|
|
549
|
+
if (toSamples)
|
|
550
|
+
samples.push(r);
|
|
551
|
+
if (wantsReticle)
|
|
552
|
+
reticle.push(r);
|
|
540
553
|
}
|
|
541
554
|
}
|
|
542
|
-
return
|
|
555
|
+
return { samples, reticle };
|
|
543
556
|
}, [
|
|
544
557
|
cursorTime,
|
|
545
558
|
wantsSamples,
|
|
559
|
+
wantsReticle,
|
|
546
560
|
layers,
|
|
547
561
|
yScales,
|
|
548
562
|
formats,
|
|
@@ -631,7 +645,7 @@ export function Layers({ children }) {
|
|
|
631
645
|
// (#508 item 7). Trusted human-paced input hides this (React flushes
|
|
632
646
|
// trusted discrete events synchronously); the ref is correct under both.
|
|
633
647
|
// The release sink rides the ref too, so what fires is what the press
|
|
634
|
-
// resolved —
|
|
648
|
+
// resolved — the `<RangeCursor onDragRelease>`.
|
|
635
649
|
const rangeDragRef = useRef(null);
|
|
636
650
|
// The live sweep session (a mounted <MultiSelector> — RFC §8 / A7.7): the
|
|
637
651
|
// anchor + the topmost capable layer's per-drag session + the container's
|
|
@@ -841,7 +855,7 @@ export function Layers({ children }) {
|
|
|
841
855
|
const swept = sweepGesture !== null ? beginTopmostSweep(c, r) : null;
|
|
842
856
|
const drag = resolveRangeDrag(c, gestureOwner(effectiveCursorEntries(c.cursors, r.rowKey)));
|
|
843
857
|
// ONE brush recognizer arbitrates every drag claim — annotation-create,
|
|
844
|
-
// the sweep, the range drag
|
|
858
|
+
// the sweep, the range drag, pan — in a
|
|
845
859
|
// documented order (RFC A1.5 / A2.7; see brush.tsx). This handler only
|
|
846
860
|
// routes.
|
|
847
861
|
const claim = resolveBrushClaim({
|
|
@@ -895,8 +909,7 @@ export function Layers({ children }) {
|
|
|
895
909
|
}
|
|
896
910
|
return;
|
|
897
911
|
}
|
|
898
|
-
// Range drag (a drag-enabled <RangeCursor
|
|
899
|
-
// `cursor="region"` + `onRegionSelect`): anchor the selection at the
|
|
912
|
+
// Range drag (a drag-enabled <RangeCursor>): anchor the selection at the
|
|
900
913
|
// press; the band then extends as the pointer moves (bucket by bucket
|
|
901
914
|
// with a sequence, freeform without), and release commits the span to
|
|
902
915
|
// whichever sink the claim resolved. Continuous x only, and gated
|
|
@@ -1119,8 +1132,8 @@ export function Layers({ children }) {
|
|
|
1119
1132
|
const t = +c.xScale.invert(rawX);
|
|
1120
1133
|
for (let i = r.layers.length - 1; i >= 0; i -= 1) {
|
|
1121
1134
|
const entry = r.layers[i];
|
|
1122
|
-
|
|
1123
|
-
|
|
1135
|
+
// Box plots included ([PND-BOXPLT]): a box's samples sit at its
|
|
1136
|
+
// centre, so the reticle lands mid-box.
|
|
1124
1137
|
const s = entry.layer.sampleAt(t)[0];
|
|
1125
1138
|
if (s !== undefined) {
|
|
1126
1139
|
px = c.xScale(s.x);
|
|
@@ -1269,7 +1282,7 @@ export function Layers({ children }) {
|
|
|
1269
1282
|
}
|
|
1270
1283
|
// End a range drag: commit the anchor→pointer span as a one-shot range —
|
|
1271
1284
|
// to the sink the press resolved (`<RangeCursor onDragRelease>`'s
|
|
1272
|
-
// `{ x: [lo, hi] }
|
|
1285
|
+
// `{ x: [lo, hi] }`) — then
|
|
1273
1286
|
// clear the anchor: the cursor **reverts** to the single-bucket
|
|
1274
1287
|
// highlight (it does not keep the range). The anchor is read from the
|
|
1275
1288
|
// ref, never the state mirror — under a batched pointer stream the
|
|
@@ -1574,10 +1587,9 @@ export function Layers({ children }) {
|
|
|
1574
1587
|
const formattedTime = wantsTime && cursorTime !== null
|
|
1575
1588
|
? (container.formatReadout ?? formatTime)(cursorTime)
|
|
1576
1589
|
: null;
|
|
1577
|
-
// The range cursor's band
|
|
1578
|
-
// span under the pointer. With snap buckets (a sequence / a histogram's
|
|
1590
|
+
// The range cursor's band: shade the span under the pointer. With snap buckets (a sequence / a histogram's
|
|
1579
1591
|
// bins) the band snaps to the bucket (and extends bucket by bucket under a
|
|
1580
|
-
//
|
|
1592
|
+
// drag); with none it's the **freeform** case — a bare hover draws a
|
|
1581
1593
|
// plain line (`bandLine`), a drag shades the raw `[anchor, pointer]`. Edges
|
|
1582
1594
|
// map through `xScale`, so on a trading-time axis the band crops to live time.
|
|
1583
1595
|
// A live <MultiSelector> sweep shades the same band — and so does its
|
|
@@ -1594,12 +1606,27 @@ export function Layers({ children }) {
|
|
|
1594
1606
|
// not ALSO shade an x band: `sweeping` alone would resolve one from the
|
|
1595
1607
|
// pointer's bucket and lay a full-height column across the horizontal band
|
|
1596
1608
|
// the drag is actually drawing.
|
|
1597
|
-
|
|
1598
|
-
|
|
1609
|
+
//
|
|
1610
|
+
// A `<RangeCursor>` on a **category** axis shades the slot under the
|
|
1611
|
+
// pointer ([PND-ORDCURSOR]). Its buckets are the unit slots `[i, i+1)` —
|
|
1612
|
+
// a vertical bar layer already publishes exactly those as `cursorBuckets`;
|
|
1613
|
+
// a category row with no bar layer (a heat map) gets them from the band
|
|
1614
|
+
// scale's domain (`categoryBandSlots`). The range *drag* stays off there
|
|
1615
|
+
// (`resolveRangeDrag`): it reports a numeric span, and a category chart
|
|
1616
|
+
// selects slots, which is `<MultiSelector>`'s gesture.
|
|
1617
|
+
// The slot fallback is for the cursor's band only: a sweep or the resting
|
|
1618
|
+
// brush keeps reading `cursorBuckets` alone, exactly as before. Memoized on
|
|
1619
|
+
// the scale, so a pointer move doesn't rebuild the slots.
|
|
1620
|
+
const categoryBandSlots = useMemo(() => wantsBand &&
|
|
1621
|
+
container.xKind === 'category' &&
|
|
1622
|
+
container.cursorBuckets === undefined
|
|
1623
|
+
? categorySlots(xScale.domain())
|
|
1624
|
+
: undefined, [wantsBand, container.xKind, container.cursorBuckets, xScale]);
|
|
1625
|
+
const bandActive = wantsBand ||
|
|
1599
1626
|
(sweeping && sweepRect === null && sweepBandY === null) ||
|
|
1600
1627
|
restingBand;
|
|
1601
1628
|
const band = bandActive && cursorTime !== null
|
|
1602
|
-
? bandRect(container.cursorBuckets ?? [], cursorTime, (v) => xScale(v), plotWidth, container.regionAnchor ?? undefined)
|
|
1629
|
+
? bandRect(container.cursorBuckets ?? categoryBandSlots ?? [], cursorTime, (v) => xScale(v), plotWidth, container.regionAnchor ?? undefined)
|
|
1603
1630
|
: null;
|
|
1604
1631
|
// Degenerate range cursor (no buckets, not mid-drag): a plain vertical
|
|
1605
1632
|
// line. Deliberately NOT extended to `restingBand` — the resting preview is
|
|
@@ -1627,6 +1654,7 @@ export function Layers({ children }) {
|
|
|
1627
1654
|
rowKey: row.rowKey,
|
|
1628
1655
|
hoveredRowKey: cursor.cursorRowKey,
|
|
1629
1656
|
samples: trackerSamples,
|
|
1657
|
+
reticleSamples,
|
|
1630
1658
|
flags: trackerFlags,
|
|
1631
1659
|
pointer,
|
|
1632
1660
|
band,
|
|
@@ -1642,6 +1670,28 @@ export function Layers({ children }) {
|
|
|
1642
1670
|
theme: container.theme,
|
|
1643
1671
|
xAxis: null,
|
|
1644
1672
|
};
|
|
1673
|
+
// The crosshair's snap report (`<CrosshairCursor onSnap>`). The snap owner is
|
|
1674
|
+
// read from the unfiltered entries, so a row whose cursor is hidden (editing,
|
|
1675
|
+
// a sweep, the resting brush) still reports "nothing snapped" rather than
|
|
1676
|
+
// leaving the consumer on a stale point. Only the hovered row reports a
|
|
1677
|
+
// point; the other rows report `null` — unless the hovered row has the same
|
|
1678
|
+
// owner, in which case it speaks for all of them (two rows calling at once
|
|
1679
|
+
// would flap). The cursor dedupes, so repeated calls are free.
|
|
1680
|
+
const snapOwner = gestureOwner(effectiveCursorEntries(container.cursors, row.rowKey));
|
|
1681
|
+
const hoveredRowKey = cursor.cursorRowKey;
|
|
1682
|
+
useEffect(() => {
|
|
1683
|
+
const report = snapOwner?.reportSnap;
|
|
1684
|
+
if (report === undefined)
|
|
1685
|
+
return;
|
|
1686
|
+
if (hoveredRowKey === row.rowKey) {
|
|
1687
|
+
report(cursorEntries.includes(snapOwner) ? cursorRenderFrame : null);
|
|
1688
|
+
}
|
|
1689
|
+
else if (hoveredRowKey === null ||
|
|
1690
|
+
gestureOwner(effectiveCursorEntries(container.cursors, hoveredRowKey)) !==
|
|
1691
|
+
snapOwner) {
|
|
1692
|
+
report(null);
|
|
1693
|
+
}
|
|
1694
|
+
});
|
|
1645
1695
|
// Cross-row guide lines: the x-positions of annotations on the OTHER rows
|
|
1646
1696
|
// (markers + region edges), so a mark on one row reads against this row's data +
|
|
1647
1697
|
// the shared x axis. A mark's own row skips itself; baselines cast no vertical
|
package/dist/XAxis.js
CHANGED
package/dist/area.d.ts
CHANGED
|
@@ -27,8 +27,9 @@ export declare function fillAffineArea(ctx: CanvasRenderingContext2D, xs: Float6
|
|
|
27
27
|
* The `[min, max]` vertical extent an area occupies — the finite values of
|
|
28
28
|
* `cs.y` widened to include `baseline`, since the fill spans from each value to
|
|
29
29
|
* the baseline (so the baseline must be in-domain or the fill clips). `null` if
|
|
30
|
-
* no value is finite. When `baseline` is `undefined`
|
|
31
|
-
* axis's own lower bound (resolved
|
|
30
|
+
* no value is finite. When `baseline` is `undefined` (`<AreaChart
|
|
31
|
+
* baseline="floor">`) the area rests on the axis's own lower bound (resolved
|
|
32
|
+
* later), so only the values constrain the
|
|
32
33
|
* domain — matching {@link yExtent}.
|
|
33
34
|
*
|
|
34
35
|
* NaN values (the gap signal) are ignored, so a coast doesn't drag the domain.
|
|
@@ -41,10 +42,10 @@ export declare function areaExtent(cs: ChartSeries, baseline: number | undefined
|
|
|
41
42
|
*
|
|
42
43
|
* Two forms, selected by `baseline`:
|
|
43
44
|
*
|
|
44
|
-
* - **
|
|
45
|
-
* `baselineValue`): the line sits above the baseline, the shade grades down
|
|
45
|
+
* - **Floor** (`<AreaChart baseline="floor">` — the axis lower bound,
|
|
46
|
+
* supplied as the resolved `baselineValue`): the line sits above the baseline, the shade grades down
|
|
46
47
|
* from it — the estela elevation look.
|
|
47
|
-
* - **Above/below
|
|
48
|
+
* - **Above/below a level** (`baseline` = `0`, the default, or any number): positive values fill up, negative
|
|
48
49
|
* fill down (d3's `area` handles the zero crossing in one path). The gradient
|
|
49
50
|
* is anchored at the baseline pixel so each side grades *away* from the axis —
|
|
50
51
|
* opaque at the line, transparent at the axis — in both directions. Compose
|
package/dist/area.js
CHANGED
|
@@ -89,8 +89,9 @@ export function fillAffineArea(ctx, xs, ys, baselinePx, ax, ay) {
|
|
|
89
89
|
* The `[min, max]` vertical extent an area occupies — the finite values of
|
|
90
90
|
* `cs.y` widened to include `baseline`, since the fill spans from each value to
|
|
91
91
|
* the baseline (so the baseline must be in-domain or the fill clips). `null` if
|
|
92
|
-
* no value is finite. When `baseline` is `undefined`
|
|
93
|
-
* axis's own lower bound (resolved
|
|
92
|
+
* no value is finite. When `baseline` is `undefined` (`<AreaChart
|
|
93
|
+
* baseline="floor">`) the area rests on the axis's own lower bound (resolved
|
|
94
|
+
* later), so only the values constrain the
|
|
94
95
|
* domain — matching {@link yExtent}.
|
|
95
96
|
*
|
|
96
97
|
* NaN values (the gap signal) are ignored, so a coast doesn't drag the domain.
|
|
@@ -126,10 +127,10 @@ export function areaExtent(cs, baseline) {
|
|
|
126
127
|
*
|
|
127
128
|
* Two forms, selected by `baseline`:
|
|
128
129
|
*
|
|
129
|
-
* - **
|
|
130
|
-
* `baselineValue`): the line sits above the baseline, the shade grades down
|
|
130
|
+
* - **Floor** (`<AreaChart baseline="floor">` — the axis lower bound,
|
|
131
|
+
* supplied as the resolved `baselineValue`): the line sits above the baseline, the shade grades down
|
|
131
132
|
* from it — the estela elevation look.
|
|
132
|
-
* - **Above/below
|
|
133
|
+
* - **Above/below a level** (`baseline` = `0`, the default, or any number): positive values fill up, negative
|
|
133
134
|
* fill down (d3's `area` handles the zero crossing in one path). The gradient
|
|
134
135
|
* is anchored at the baseline pixel so each side grades *away* from the axis —
|
|
135
136
|
* opaque at the line, transparent at the axis — in both directions. Compose
|
|
@@ -509,7 +510,13 @@ export function areaHitIndex(cs, baseline, px, py, xScale, yScale, tolerance = T
|
|
|
509
510
|
// slope and not a staircase. Guard the degenerate same-pixel pair.
|
|
510
511
|
const t = xi === xj ? 0 : (px - xj) / (xi - xj);
|
|
511
512
|
const edgePx = yScale(yj + (yi - yj) * Math.max(0, Math.min(1, t)));
|
|
512
|
-
|
|
513
|
+
// A baseline with no position on this axis (zero on a log axis) falls back
|
|
514
|
+
// to the axis floor — the same resolution the draw uses. Without it `basePx`
|
|
515
|
+
// is NaN, both range checks below are false, and every point hits.
|
|
516
|
+
const fixedPx = baseline === undefined ? NaN : yScale(baseline);
|
|
517
|
+
const basePx = Number.isFinite(fixedPx)
|
|
518
|
+
? fixedPx
|
|
519
|
+
: baselinePxFromScale(yScale);
|
|
513
520
|
const top = Math.min(edgePx, basePx) - tolerance;
|
|
514
521
|
const bottom = Math.max(edgePx, basePx) + tolerance;
|
|
515
522
|
if (py < top || py > bottom)
|
package/dist/axis-events.d.ts
CHANGED
|
@@ -44,11 +44,11 @@ export interface AxisMouseEvent {
|
|
|
44
44
|
* elsewhere.
|
|
45
45
|
*
|
|
46
46
|
* Precisely: it is the axis's **readout** channel, the one the cursor pill
|
|
47
|
-
* uses — so it always agrees with the pill at that pixel, and a
|
|
48
|
-
* `
|
|
49
|
-
* documented precedence (`
|
|
47
|
+
* uses — so it always agrees with the pill at that pixel, and a cursor
|
|
48
|
+
* `format` shapes it exactly as it shapes the pill. That is the
|
|
49
|
+
* documented precedence (cursor `format` → axis `format` → container), and it
|
|
50
50
|
* is the one case where `label` can read differently from the tick text: a
|
|
51
|
-
* chart with a precise `
|
|
51
|
+
* chart with a precise cursor `format` over terse ticks gets the precise form
|
|
52
52
|
* here, which is the readout it asked for.
|
|
53
53
|
*/
|
|
54
54
|
label: string;
|
package/dist/brush.d.ts
CHANGED
|
@@ -28,8 +28,7 @@ import type { ContainerFrame, CursorEntry, ResolvedCursorFrame } from './context
|
|
|
28
28
|
* `DRAG_SLOP` — a plain click stays a click and selects one mark (§8.1:
|
|
29
29
|
* the two are separated by movement, not modifier).
|
|
30
30
|
* 3. **The range drag** ({@link resolveRangeDrag}) — a drag-enabled
|
|
31
|
-
* `<RangeCursor>` in the hovered row's effective cursor set
|
|
32
|
-
* legacy `cursor="region"` + `onRegionSelect` container props. Preempts
|
|
31
|
+
* `<RangeCursor>` in the hovered row's effective cursor set. Preempts
|
|
33
32
|
* pan — unless a `dragModifier` is declared **and pan is enabled**, in
|
|
34
33
|
* which case a plain drag falls through to pan and only a modifier-held
|
|
35
34
|
* drag brushes. (With pan off there is no gesture to share, so the
|
|
@@ -45,9 +44,8 @@ import type { ContainerFrame, CursorEntry, ResolvedCursorFrame } from './context
|
|
|
45
44
|
* marks), which is A1.5's "one brush engine, two components".
|
|
46
45
|
*/
|
|
47
46
|
/** What a completed range drag calls with the released `[start, end]`
|
|
48
|
-
* (axis units, `start ≤ end`). Resolved once at pointer-down
|
|
49
|
-
*
|
|
50
|
-
* `onRegionSelect` (the bare pair). */
|
|
47
|
+
* (axis units, `start ≤ end`). Resolved once at pointer-down, wrapping the
|
|
48
|
+
* `<RangeCursor>`'s `onDragRelease` (the `{ x }` payload). */
|
|
51
49
|
export interface RangeDrag {
|
|
52
50
|
readonly release: (start: number, end: number) => void;
|
|
53
51
|
/** The modifier the drag needs — only enforced while pan is enabled. */
|
|
@@ -55,23 +53,17 @@ export interface RangeDrag {
|
|
|
55
53
|
}
|
|
56
54
|
/**
|
|
57
55
|
* Resolve whether a press could start a **range drag**, and who gets the
|
|
58
|
-
* released span
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
* not frozen (`enableDrag={false}` — the OFF switch, which also suppresses
|
|
63
|
-
* the legacy fallback: the consumer wired the new API and asked for no
|
|
64
|
-
* gesture). A `<RangeCursor>` without `onDragRelease` has nothing to fire,
|
|
65
|
-
* so it does not claim — the legacy props keep working underneath it
|
|
66
|
-
* during the deprecation window (exactly the step-2 behaviour).
|
|
67
|
-
* 2. **The legacy container props** — `cursor="region"` + `onRegionSelect`
|
|
68
|
-
* (+ `regionSelectModifier`), byte-for-byte today's semantics, including
|
|
69
|
-
* the bare-pair payload.
|
|
56
|
+
* released span: the hovered row's effective gesture-owning cursor (`owner`),
|
|
57
|
+
* when it is a `<RangeCursor>` carrying `onDragRelease` and is not frozen
|
|
58
|
+
* (`enableDrag={false}` — the OFF switch). A `<RangeCursor>` without
|
|
59
|
+
* `onDragRelease` has nothing to fire, so it does not claim.
|
|
70
60
|
*
|
|
71
61
|
* Continuous x only (time or value): a category axis has no span to drag
|
|
72
|
-
* (an ordinal-slot select is
|
|
62
|
+
* (an ordinal-slot select is `<MultiSelector>`'s gesture). The range cursor's
|
|
63
|
+
* hover band still draws there — over the slot — and the container dev-warns
|
|
64
|
+
* that the drag is off ([PND-ORDCURSOR]).
|
|
73
65
|
*/
|
|
74
|
-
export declare function resolveRangeDrag(c: Pick<ContainerFrame, '
|
|
66
|
+
export declare function resolveRangeDrag(c: Pick<ContainerFrame, 'xKind'>, owner: CursorEntry | undefined): RangeDrag | null;
|
|
75
67
|
/** Who owns the drag a pointer-down might start (see the module doc's
|
|
76
68
|
* precedence order). `'none'` = the press is a potential click only. */
|
|
77
69
|
export type BrushClaim = {
|
|
@@ -110,8 +102,8 @@ export declare function resolveBrushClaim(opts: {
|
|
|
110
102
|
}): BrushClaim;
|
|
111
103
|
/**
|
|
112
104
|
* Dev-warn (once per plot surface) when a press found BOTH a mounted
|
|
113
|
-
* `<MultiSelector>` and a live range drag (a drag-enabled `<RangeCursor
|
|
114
|
-
*
|
|
105
|
+
* `<MultiSelector>` and a live range drag (a drag-enabled `<RangeCursor>`)
|
|
106
|
+
* competing for it — the sweep wins (see the module
|
|
115
107
|
* doc's precedence), and a silent shadow would hide the loser exactly the way
|
|
116
108
|
* A1.5 said docs alone couldn't.
|
|
117
109
|
*/
|
package/dist/brush.js
CHANGED
|
@@ -1,44 +1,30 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Resolve whether a press could start a **range drag**, and who gets the
|
|
4
|
-
* released span
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* not frozen (`enableDrag={false}` — the OFF switch, which also suppresses
|
|
9
|
-
* the legacy fallback: the consumer wired the new API and asked for no
|
|
10
|
-
* gesture). A `<RangeCursor>` without `onDragRelease` has nothing to fire,
|
|
11
|
-
* so it does not claim — the legacy props keep working underneath it
|
|
12
|
-
* during the deprecation window (exactly the step-2 behaviour).
|
|
13
|
-
* 2. **The legacy container props** — `cursor="region"` + `onRegionSelect`
|
|
14
|
-
* (+ `regionSelectModifier`), byte-for-byte today's semantics, including
|
|
15
|
-
* the bare-pair payload.
|
|
4
|
+
* released span: the hovered row's effective gesture-owning cursor (`owner`),
|
|
5
|
+
* when it is a `<RangeCursor>` carrying `onDragRelease` and is not frozen
|
|
6
|
+
* (`enableDrag={false}` — the OFF switch). A `<RangeCursor>` without
|
|
7
|
+
* `onDragRelease` has nothing to fire, so it does not claim.
|
|
16
8
|
*
|
|
17
9
|
* Continuous x only (time or value): a category axis has no span to drag
|
|
18
|
-
* (an ordinal-slot select is
|
|
10
|
+
* (an ordinal-slot select is `<MultiSelector>`'s gesture). The range cursor's
|
|
11
|
+
* hover band still draws there — over the slot — and the container dev-warns
|
|
12
|
+
* that the drag is off ([PND-ORDCURSOR]).
|
|
19
13
|
*/
|
|
20
14
|
export function resolveRangeDrag(c, owner) {
|
|
21
15
|
if (c.xKind !== 'time' && c.xKind !== 'value')
|
|
22
16
|
return null;
|
|
23
|
-
if (owner
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
if (c.cursor === 'region' && c.onRegionSelect !== undefined) {
|
|
35
|
-
const cb = c.onRegionSelect;
|
|
36
|
-
return {
|
|
37
|
-
release: (start, end) => cb([start, end]),
|
|
38
|
-
modifier: c.regionSelectModifier,
|
|
39
|
-
};
|
|
40
|
-
}
|
|
41
|
-
return null;
|
|
17
|
+
if (owner === undefined || !owner.onDragRelease)
|
|
18
|
+
return null;
|
|
19
|
+
// Frozen: the gesture is off without unwiring the callback (§6's
|
|
20
|
+
// `enableDrag`-as-disabler).
|
|
21
|
+
if (owner.enableDrag === false)
|
|
22
|
+
return null;
|
|
23
|
+
const cb = owner.onDragRelease;
|
|
24
|
+
return {
|
|
25
|
+
release: (start, end) => cb({ x: [start, end] }),
|
|
26
|
+
modifier: owner.dragModifier,
|
|
27
|
+
};
|
|
42
28
|
}
|
|
43
29
|
/**
|
|
44
30
|
* The claim decision at pointer-down — pure, so the precedence order is
|
|
@@ -71,8 +57,8 @@ export function resolveBrushClaim(opts) {
|
|
|
71
57
|
}
|
|
72
58
|
/**
|
|
73
59
|
* Dev-warn (once per plot surface) when a press found BOTH a mounted
|
|
74
|
-
* `<MultiSelector>` and a live range drag (a drag-enabled `<RangeCursor
|
|
75
|
-
*
|
|
60
|
+
* `<MultiSelector>` and a live range drag (a drag-enabled `<RangeCursor>`)
|
|
61
|
+
* competing for it — the sweep wins (see the module
|
|
76
62
|
* doc's precedence), and a silent shadow would hide the loser exactly the way
|
|
77
63
|
* A1.5 said docs alone couldn't.
|
|
78
64
|
*/
|
|
@@ -80,8 +66,8 @@ export function warnSweepShadowsRangeDrag(warned) {
|
|
|
80
66
|
if (warned.current)
|
|
81
67
|
return;
|
|
82
68
|
warned.current = true;
|
|
83
|
-
console.warn('[pond-charts] a <MultiSelector> and a drag-enabled <RangeCursor>
|
|
84
|
-
'
|
|
69
|
+
console.warn('[pond-charts] a <MultiSelector> and a drag-enabled <RangeCursor> are ' +
|
|
70
|
+
'both in scope for this plot — ' +
|
|
85
71
|
'the sweep claims the drag and the range drag never fires. Mount one ' +
|
|
86
72
|
'drag owner per scope, or freeze the cursor with enableDrag={false}. ' +
|
|
87
73
|
'See docs/rfcs/interaction.md A1.5 / §8.1.');
|