@pond-ts/charts 0.42.0 → 0.44.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/dist/tracker.js CHANGED
@@ -4,6 +4,72 @@
4
4
  * themselves render as an SVG overlay in `Layers` (no cursor canvas); these
5
5
  * helpers stay pure, so they're unit-tested directly.
6
6
  */
7
+ /**
8
+ * The interval in the sorted, non-overlapping `buckets` that contains `t`
9
+ * (`begin ≤ t < end`), or `undefined` if `t` falls in no bucket. Binary search —
10
+ * the `region` cursor uses it to find the bucket under the pointer.
11
+ */
12
+ export function bucketAt(buckets, t) {
13
+ let lo = 0;
14
+ let hi = buckets.length - 1;
15
+ while (lo <= hi) {
16
+ const mid = (lo + hi) >> 1;
17
+ const b = buckets[mid];
18
+ if (t < b.begin())
19
+ hi = mid - 1;
20
+ else if (t >= b.end())
21
+ lo = mid + 1;
22
+ else
23
+ return b;
24
+ }
25
+ return undefined;
26
+ }
27
+ /**
28
+ * The `[start, end)` **span** a region cursor covers, in axis units (not pixels —
29
+ * the drag-release callback reports this):
30
+ *
31
+ * - **Snapping** (`buckets` non-empty, `t1` in a bucket): the bucket at `t1`, or —
32
+ * with a drag anchor `t2` — the union of the `t1` and `t2` buckets, so a drag
33
+ * extends **bucket by bucket** either direction. A `t2` in no bucket is ignored.
34
+ * - **Freeform** (`t1` in no bucket — e.g. no `cursorSequence` at all): a drag
35
+ * spans the raw `[t1, t2]`; without a drag (`t2` omitted) there's nothing to
36
+ * shade (the cursor renders as a plain line), so it returns `null`.
37
+ */
38
+ export function regionSpan(buckets, t1, t2) {
39
+ const a = bucketAt(buckets, t1);
40
+ if (a === undefined) {
41
+ // Freeform: no bucket under t1. A drag spans the raw [t1, t2]; a bare hover
42
+ // has nothing to shade (Layers draws a line for the degenerate region cursor).
43
+ return t2 === undefined
44
+ ? null
45
+ : { start: Math.min(t1, t2), end: Math.max(t1, t2) };
46
+ }
47
+ if (t2 === undefined)
48
+ return { start: a.begin(), end: a.end() };
49
+ const b = bucketAt(buckets, t2);
50
+ if (b === undefined)
51
+ return { start: a.begin(), end: a.end() };
52
+ return {
53
+ start: Math.min(a.begin(), b.begin()),
54
+ end: Math.max(a.end(), b.end()),
55
+ };
56
+ }
57
+ /**
58
+ * The pixel band for the `region` cursor: the {@link regionSpan} for `t1` (and an
59
+ * optional drag anchor `t2`), its `[start, end)` mapped through `xScale` and
60
+ * clamped to `[0, plotWidth]`. Returns `null` when there's no span, or when the
61
+ * band has no width — including a span entirely in a **collapsed gap** on a
62
+ * trading-time scale (both edges map to the same pixel), so it draws nothing
63
+ * there rather than a zero-width sliver.
64
+ */
65
+ export function bandRect(buckets, t1, xScale, plotWidth, t2) {
66
+ const span = regionSpan(buckets, t1, t2);
67
+ if (span === null)
68
+ return null;
69
+ const x0 = Math.max(0, xScale(span.start));
70
+ const x1 = Math.min(plotWidth, xScale(span.end));
71
+ return x1 > x0 ? { x0, x1 } : null;
72
+ }
7
73
  /** Default cursor mode — the synced vertical line (cursor enabled on the
8
74
  * container by default; pair with an off-chart readout via `onTrackerChanged`). */
9
75
  export const DEFAULT_CURSOR_MODE = 'line';
@@ -15,22 +81,27 @@ export const DEFAULT_CURSOR_MODE = 'line';
15
81
  * value flag stacked near the top of the row (drawn in `Layers`).
16
82
  */
17
83
  export function cursorParts(mode) {
84
+ const base = { line: false, dots: false, chip: 'none', band: false };
18
85
  switch (mode) {
19
86
  case 'line':
20
- return { line: true, dots: false, chip: 'none' };
87
+ return { ...base, line: true };
21
88
  case 'point':
22
- return { line: false, dots: true, chip: 'none' };
89
+ return { ...base, dots: true };
23
90
  case 'inline':
24
- return { line: false, dots: true, chip: 'inline' };
91
+ return { ...base, dots: true, chip: 'inline' };
25
92
  case 'flag':
26
- return { line: false, dots: true, chip: 'flag' };
93
+ return { ...base, dots: true, chip: 'flag' };
27
94
  case 'crosshair':
28
95
  // A single reticle (not per-series): `Layers` draws the dashed vertical +
29
96
  // full-width horizontal lines, the centre dot, and one value pill itself
30
97
  // (so no generic line/dots here); the x-time pill is on `<XAxis>`.
31
- return { line: false, dots: false, chip: 'axis' };
98
+ return { ...base, chip: 'axis' };
99
+ case 'region':
100
+ // A shaded band over the bucket under the pointer — `Layers` resolves the
101
+ // bucket from `cursorBuckets` and draws the rect (cropped through xScale).
102
+ return { ...base, band: true };
32
103
  case 'none':
33
- return { line: false, dots: false, chip: 'none' };
104
+ return { ...base };
34
105
  }
35
106
  }
36
107
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pond-ts/charts",
3
- "version": "0.42.0",
3
+ "version": "0.44.0",
4
4
  "private": false,
5
5
  "description": "Canvas-rendered, streaming-first time-series charts for pond-ts",
6
6
  "license": "MIT",
@@ -38,8 +38,8 @@
38
38
  "perf": "PERF_BENCH=1 playwright test perf.spec.ts --workers=1"
39
39
  },
40
40
  "peerDependencies": {
41
- "@pond-ts/react": "^0.42.0",
42
- "pond-ts": "^0.42.0",
41
+ "@pond-ts/react": "^0.44.0",
42
+ "pond-ts": "^0.44.0",
43
43
  "react": "^18.0.0 || ^19.0.0"
44
44
  },
45
45
  "devDependencies": {