react-native-livechart 4.25.0 → 4.26.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/components/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/core/liveChartEngineTick.d.ts +2 -1
- package/dist/core/liveChartEngineTick.d.ts.map +1 -1
- package/dist/core/liveChartSeriesEngineTick.d.ts +2 -1
- package/dist/core/liveChartSeriesEngineTick.d.ts.map +1 -1
- package/dist/core/useLiveChartEngine.d.ts +3 -1
- package/dist/core/useLiveChartEngine.d.ts.map +1 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts +3 -1
- package/dist/core/useLiveChartSeriesEngine.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +8 -0
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useReferenceDrag.d.ts +3 -2
- package/dist/hooks/useReferenceDrag.d.ts.map +1 -1
- package/dist/hooks/useReferenceLineGrouping.d.ts +19 -0
- package/dist/hooks/useReferenceLineGrouping.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/math/rangeAnimation.d.ts +4 -0
- package/dist/math/rangeAnimation.d.ts.map +1 -0
- package/dist/math/referenceDrag.d.ts +17 -5
- package/dist/math/referenceDrag.d.ts.map +1 -1
- package/dist/math/referenceGroup.d.ts +2 -2
- package/dist/types.d.ts +41 -1
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/LiveChart.tsx +3 -62
- package/src/components/LiveChartSeries.tsx +4 -0
- package/src/core/liveChartEngineTick.ts +17 -6
- package/src/core/liveChartSeriesEngineTick.ts +18 -5
- package/src/core/useLiveChartEngine.ts +10 -2
- package/src/core/useLiveChartSeriesEngine.ts +6 -1
- package/src/hooks/crosshairShared.ts +29 -0
- package/src/hooks/useCrosshair.ts +15 -3
- package/src/hooks/useReferenceDrag.ts +28 -14
- package/src/hooks/useReferenceLineGrouping.ts +72 -0
- package/src/index.ts +1 -0
- package/src/math/rangeAnimation.ts +28 -0
- package/src/math/referenceDrag.ts +21 -4
- package/src/math/referenceGroup.ts +2 -2
- package/src/types.ts +42 -1
|
@@ -18,11 +18,7 @@ import {
|
|
|
18
18
|
} from "../math/referenceDrag";
|
|
19
19
|
import { referenceLineForm } from "../math/referenceLines";
|
|
20
20
|
import type { ReferenceLine } from "../types";
|
|
21
|
-
import {
|
|
22
|
-
computeScrubDotY,
|
|
23
|
-
computeValueAtY,
|
|
24
|
-
snapPrice,
|
|
25
|
-
} from "./crosshairShared";
|
|
21
|
+
import { computeValueAtY, pinnedPlotY, snapPrice } from "./crosshairShared";
|
|
26
22
|
|
|
27
23
|
/** Vertical reach (px) around a line within which a touch grabs it. */
|
|
28
24
|
const GRAB_SLOP = 14;
|
|
@@ -37,12 +33,13 @@ const EMPTY: never[] = [];
|
|
|
37
33
|
* Builds the per-line **drag** gesture for draggable Form-A reference lines: grab a
|
|
38
34
|
* line near its value-Y and drag vertically to set a new value, with optional
|
|
39
35
|
* `snap` + `bounds` clamp. Mirrors the order-ticket reticle in {@link useCrosshair}
|
|
40
|
-
* (value↔Y via `computeValueAtY` / `
|
|
36
|
+
* (value↔Y via `computeValueAtY` / `pinnedPlotY`, frozen value re-projected
|
|
41
37
|
* each frame) but per line, writing into the shared `dragValues` array the layout
|
|
42
38
|
* and overlays read.
|
|
43
39
|
*
|
|
44
40
|
* The pan uses `manualActivation`: it grabs only when a touch starts within
|
|
45
|
-
* {@link GRAB_SLOP} of a draggable line
|
|
41
|
+
* {@link GRAB_SLOP} of a draggable line (and inside its `grabRange`, when it has
|
|
42
|
+
* one), then **owns** that touch — any drag past
|
|
46
43
|
* {@link DRAG_ACTIVATE_PX} (in either axis) drags the line. A touch off every line
|
|
47
44
|
* fails fast so the chart's other gestures (scrub / scroll) run everywhere else
|
|
48
45
|
* (compose this ahead of them via `Gesture.Exclusive`). Crucially it no longer
|
|
@@ -86,6 +83,7 @@ export function useReferenceDrag(
|
|
|
86
83
|
const dMax = engine.displayMax.get();
|
|
87
84
|
const top = padding.top;
|
|
88
85
|
const bottom = ch - padding.bottom;
|
|
86
|
+
if (bottom <= top) return EMPTY;
|
|
89
87
|
const out: number[] = [];
|
|
90
88
|
for (let i = 0; i < lines.length; i++) {
|
|
91
89
|
const l = lines[i];
|
|
@@ -98,12 +96,16 @@ export function useReferenceDrag(
|
|
|
98
96
|
continue;
|
|
99
97
|
}
|
|
100
98
|
const v = dragValues.get()[i] ?? l.value;
|
|
101
|
-
|
|
102
|
-
out.push(y < 0 ? -1 : Math.min(bottom, Math.max(top, y)));
|
|
99
|
+
out.push(pinnedPlotY(v, dMin, dMax, ch, top, padding.bottom));
|
|
103
100
|
}
|
|
104
101
|
return out;
|
|
105
102
|
});
|
|
106
103
|
|
|
104
|
+
// Per-line grab x-ranges (`ReferenceLine.grabRange`), index-aligned with
|
|
105
|
+
// `lines`; null = anywhere along the line. Read by the gesture worklets below,
|
|
106
|
+
// which close over this render's `lines` as the callbacks do.
|
|
107
|
+
const grabRanges = lines.map((l) => l.grabRange ?? null);
|
|
108
|
+
|
|
107
109
|
const dragIndex = useSharedValue(-1);
|
|
108
110
|
const startX = useSharedValue(0);
|
|
109
111
|
const startY = useSharedValue(0);
|
|
@@ -175,7 +177,10 @@ export function useReferenceDrag(
|
|
|
175
177
|
"worklet";
|
|
176
178
|
const t = e.changedTouches[0];
|
|
177
179
|
if (!t) return;
|
|
178
|
-
const i = nearestDraggableIndex(handleYs.get(), t.y, GRAB_SLOP
|
|
180
|
+
const i = nearestDraggableIndex(handleYs.get(), t.y, GRAB_SLOP, {
|
|
181
|
+
x: t.x,
|
|
182
|
+
ranges: grabRanges,
|
|
183
|
+
});
|
|
179
184
|
if (i < 0) {
|
|
180
185
|
manager.fail();
|
|
181
186
|
return;
|
|
@@ -266,13 +271,22 @@ export function useReferenceDrag(
|
|
|
266
271
|
// is grabbed the answer is yes regardless of position: the scrub asks with its
|
|
267
272
|
// touch-DOWN point, which a longer drag has carried the line away from, so the
|
|
268
273
|
// geometric test alone said "no line here" and a crosshair opened mid-drag.
|
|
269
|
-
// Otherwise it is the y-reach around the handles
|
|
270
|
-
//
|
|
274
|
+
// Otherwise it is the y-reach around the handles, along the whole line — or,
|
|
275
|
+
// for a line with a `grabRange`, only inside it.
|
|
271
276
|
/* istanbul ignore next -- worklet, runs on the UI thread */
|
|
272
|
-
const hitTest = (
|
|
277
|
+
const hitTest = (x: number, y: number): boolean => {
|
|
273
278
|
"worklet";
|
|
274
279
|
if (!anyDraggable) return false;
|
|
275
|
-
return referenceDragOwnsTouch(
|
|
280
|
+
return referenceDragOwnsTouch(
|
|
281
|
+
dragIndex.get(),
|
|
282
|
+
handleYs.get(),
|
|
283
|
+
y,
|
|
284
|
+
GRAB_SLOP,
|
|
285
|
+
{
|
|
286
|
+
x,
|
|
287
|
+
ranges: grabRanges,
|
|
288
|
+
},
|
|
289
|
+
);
|
|
276
290
|
};
|
|
277
291
|
|
|
278
292
|
const gesture = Gesture.Pan()
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
2
|
+
|
|
3
|
+
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
4
|
+
import type { ChartPadding } from "../draw/line";
|
|
5
|
+
import { groupReferenceLines, type ReferenceGrouping } from "../math/referenceGroup";
|
|
6
|
+
import { referenceLineForm } from "../math/referenceLines";
|
|
7
|
+
import type { ReferenceLine } from "../types";
|
|
8
|
+
import { pinnedPlotY } from "./crosshairShared";
|
|
9
|
+
|
|
10
|
+
/** Stable result while grouping is disabled. */
|
|
11
|
+
const EMPTY_GROUPING: ReferenceGrouping = { hidden: [], groups: [] };
|
|
12
|
+
|
|
13
|
+
/** Cluster the built-in tags that can actually render in the current plot. */
|
|
14
|
+
export function useReferenceLineGrouping({
|
|
15
|
+
radius,
|
|
16
|
+
engine,
|
|
17
|
+
padding,
|
|
18
|
+
lines,
|
|
19
|
+
custom,
|
|
20
|
+
offAxisCustom,
|
|
21
|
+
dragValues,
|
|
22
|
+
}: {
|
|
23
|
+
radius: number | null;
|
|
24
|
+
engine: ChartEngineLayout;
|
|
25
|
+
padding: ChartPadding;
|
|
26
|
+
lines: ReferenceLine[];
|
|
27
|
+
custom: boolean[];
|
|
28
|
+
offAxisCustom: boolean[];
|
|
29
|
+
dragValues: SharedValue<number[]>;
|
|
30
|
+
}) {
|
|
31
|
+
const hasOffAxisBadge = lines.map((line) => Boolean(line.badge || line.offAxisBadge));
|
|
32
|
+
const result = useDerivedValue<ReferenceGrouping>(() => {
|
|
33
|
+
if (radius == null) return EMPTY_GROUPING;
|
|
34
|
+
const canvasHeight = engine.canvasHeight.get();
|
|
35
|
+
const displayMin = engine.displayMin.get();
|
|
36
|
+
const displayMax = engine.displayMax.get();
|
|
37
|
+
const top = padding.top;
|
|
38
|
+
const yPositions: number[] = [];
|
|
39
|
+
for (let index = 0; index < lines.length; index++) {
|
|
40
|
+
const line = lines[index];
|
|
41
|
+
if (
|
|
42
|
+
referenceLineForm(line) !== "line" ||
|
|
43
|
+
line.value === undefined ||
|
|
44
|
+
custom[index] ||
|
|
45
|
+
offAxisCustom[index]
|
|
46
|
+
) {
|
|
47
|
+
yPositions.push(-1);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
const value = dragValues.get()[index] ?? line.value;
|
|
51
|
+
// Plain off-axis lines are culled by useReferenceLine. Only a configured
|
|
52
|
+
// badge can contribute a visible tag to the edge's count pill.
|
|
53
|
+
if ((value < displayMin || value > displayMax) && !hasOffAxisBadge[index]) {
|
|
54
|
+
yPositions.push(-1);
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
yPositions.push(
|
|
58
|
+
pinnedPlotY(
|
|
59
|
+
value,
|
|
60
|
+
displayMin,
|
|
61
|
+
displayMax,
|
|
62
|
+
canvasHeight,
|
|
63
|
+
top,
|
|
64
|
+
padding.bottom,
|
|
65
|
+
),
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
return groupReferenceLines(yPositions, radius);
|
|
69
|
+
});
|
|
70
|
+
const hidden = useDerivedValue<boolean[]>(() => result.get().hidden);
|
|
71
|
+
return { refGroupResult: result, groupHidden: hidden };
|
|
72
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RangeAnimationConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
// Worklet closures capture local functions when the caller is initialized.
|
|
4
|
+
function resolveSpeed(smoothing: number, speed: number | undefined): number {
|
|
5
|
+
"worklet";
|
|
6
|
+
return speed === undefined || !Number.isFinite(speed)
|
|
7
|
+
? smoothing
|
|
8
|
+
: Math.max(0, Math.min(1, speed));
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/** Select per-bound easing while preserving immediate expansion and reduced motion. */
|
|
12
|
+
export function rangeAnimationSpeed(
|
|
13
|
+
smoothing: number,
|
|
14
|
+
config: RangeAnimationConfig | undefined,
|
|
15
|
+
expanding: boolean,
|
|
16
|
+
disjoint = false,
|
|
17
|
+
): number {
|
|
18
|
+
"worklet";
|
|
19
|
+
if (smoothing >= 1 || (expanding && !config?.animateExpansion)) return 1;
|
|
20
|
+
const speed = resolveSpeed(
|
|
21
|
+
smoothing,
|
|
22
|
+
expanding ? config?.expansionSmoothing : config?.contractionSmoothing,
|
|
23
|
+
);
|
|
24
|
+
// Until the ranges overlap, contraction must not outrun the expanding bound.
|
|
25
|
+
return !expanding && disjoint && config?.animateExpansion
|
|
26
|
+
? Math.min(speed, resolveSpeed(smoothing, config.expansionSmoothing))
|
|
27
|
+
: speed;
|
|
28
|
+
}
|
|
@@ -17,16 +17,29 @@ export function clampToBounds(
|
|
|
17
17
|
return Math.min(hi, Math.max(lo, value));
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Where a touch may grab each line: the touch `x` and, index-aligned with the
|
|
22
|
+
* chart's `referenceLines`, each line's `grabRange` (`[from, to]`, canvas px) —
|
|
23
|
+
* `null` / `undefined` for a line grabbed anywhere along it.
|
|
24
|
+
*/
|
|
25
|
+
export interface GrabRangeProbe {
|
|
26
|
+
x: number;
|
|
27
|
+
ranges: readonly (readonly [number, number] | null | undefined)[];
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
/**
|
|
21
31
|
* Index of the draggable line whose handle-Y is nearest the touch `y`, within
|
|
22
32
|
* `slop` px — or `-1` when none is in reach. `handleYs` is index-aligned with the
|
|
23
|
-
* chart's `referenceLines`; entries `< 0` (not draggable /
|
|
24
|
-
*
|
|
33
|
+
* chart's `referenceLines`; entries `< 0` (not draggable / not laid out) are
|
|
34
|
+
* skipped; off-screen lines pin to the nearest plot edge. Ties favor the later
|
|
35
|
+
* (topmost-drawn) line. With `grab`, a line
|
|
36
|
+
* whose `grabRange` does not contain the touch `x` is skipped too.
|
|
25
37
|
*/
|
|
26
38
|
export function nearestDraggableIndex(
|
|
27
39
|
handleYs: number[],
|
|
28
40
|
y: number,
|
|
29
41
|
slop: number,
|
|
42
|
+
grab?: GrabRangeProbe,
|
|
30
43
|
): number {
|
|
31
44
|
"worklet";
|
|
32
45
|
let best = -1;
|
|
@@ -34,6 +47,8 @@ export function nearestDraggableIndex(
|
|
|
34
47
|
for (let i = 0; i < handleYs.length; i++) {
|
|
35
48
|
const hy = handleYs[i];
|
|
36
49
|
if (hy < 0) continue;
|
|
50
|
+
const range = grab?.ranges[i];
|
|
51
|
+
if (range && (grab.x < range[0] || grab.x > range[1])) continue;
|
|
37
52
|
const d = Math.abs(hy - y);
|
|
38
53
|
if (d <= bestDist) {
|
|
39
54
|
bestDist = d;
|
|
@@ -49,17 +64,19 @@ export function nearestDraggableIndex(
|
|
|
49
64
|
* or already dragging) owns the touch outright, wherever the asking gesture says
|
|
50
65
|
* the finger is: the scrub pan can activate mid-drag and asks with its
|
|
51
66
|
* touch-DOWN point, which a longer drag has carried the line away from. With no
|
|
52
|
-
* line grabbed, it is the geometric reach test around the handles
|
|
67
|
+
* line grabbed, it is the geometric reach test around the handles (and, with
|
|
68
|
+
* `grab`, inside each line's `grabRange`).
|
|
53
69
|
*/
|
|
54
70
|
export function referenceDragOwnsTouch(
|
|
55
71
|
dragIndex: number,
|
|
56
72
|
handleYs: number[],
|
|
57
73
|
y: number,
|
|
58
74
|
slop: number,
|
|
75
|
+
grab?: GrabRangeProbe,
|
|
59
76
|
): boolean {
|
|
60
77
|
"worklet";
|
|
61
78
|
if (dragIndex >= 0) return true;
|
|
62
|
-
return nearestDraggableIndex(handleYs, y, slop) >= 0;
|
|
79
|
+
return nearestDraggableIndex(handleYs, y, slop, grab) >= 0;
|
|
63
80
|
}
|
|
64
81
|
|
|
65
82
|
/**
|
|
@@ -26,8 +26,8 @@ export interface ReferenceGrouping {
|
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
28
|
* Single-linkage cluster of reference-line handle Ys: lines whose sorted Y gaps are
|
|
29
|
-
* all `<= radius` chain into one group. Entries `< 0` (not a Form-A line /
|
|
30
|
-
*
|
|
29
|
+
* all `<= radius` chain into one group. Entries `< 0` (not a Form-A line / not laid
|
|
30
|
+
* out) are ignored. Returns which lines are collapsed (`hidden`) plus a centroid
|
|
31
31
|
* + count per multi-line cluster. A non-positive `radius` disables grouping.
|
|
32
32
|
*/
|
|
33
33
|
export function groupReferenceLines(
|
package/src/types.ts
CHANGED
|
@@ -191,6 +191,18 @@ export interface ReferenceLine {
|
|
|
191
191
|
* it controlled. No effect on bands / time bands. Default `false`.
|
|
192
192
|
*/
|
|
193
193
|
draggable?: boolean;
|
|
194
|
+
/**
|
|
195
|
+
* Where along a draggable line a press may grab it: a canvas x-range in px,
|
|
196
|
+
* `[from, to]`, ends included — typically the span of a custom tag drawn with
|
|
197
|
+
* `renderReferenceLine` (measure it with `onLayout`; a left-pinned tag starts 2 px
|
|
198
|
+
* inside the plot's left edge). Omit to grab the line anywhere along it (the
|
|
199
|
+
* default). With a range, a pan, scrub or page scroll that merely starts near the
|
|
200
|
+
* line no longer moves it; a press inside the range grabs it as before (same Y
|
|
201
|
+
* reach). `from > to` makes the line ungrabbable. A JS value: leave a little
|
|
202
|
+
* slack for a tag that changes width (off-axis, a live price). Applies only to
|
|
203
|
+
* draggable Form-A lines.
|
|
204
|
+
*/
|
|
205
|
+
grabRange?: [number, number];
|
|
194
206
|
/**
|
|
195
207
|
* Snap the dragged value to this increment (e.g. `0.01` for cents, `0.5` for a
|
|
196
208
|
* tick size) so drops land on round levels. Omit for free dragging. Applies only
|
|
@@ -334,7 +346,9 @@ export interface ReferenceLineRenderProps {
|
|
|
334
346
|
* lines whose handles fall within {@link ReferenceLineGroupingConfig.radius} px of
|
|
335
347
|
* each other collapse into a single count handle, so a cluster of nearby orders /
|
|
336
348
|
* alerts reads as one tag instead of an unreadable pile. Pass `true` for defaults
|
|
337
|
-
* or an object to tune the proximity radius.
|
|
349
|
+
* or an object to tune the proximity radius. Badged off-axis lines group at either
|
|
350
|
+
* plot edge; off-axis lines with no badge are hidden and do not count. Custom tags
|
|
351
|
+
* are excluded.
|
|
338
352
|
*/
|
|
339
353
|
export interface ReferenceLineGroupingConfig {
|
|
340
354
|
/**
|
|
@@ -2258,6 +2272,25 @@ export interface LoadingConfig {
|
|
|
2258
2272
|
axisLabels?: boolean;
|
|
2259
2273
|
}
|
|
2260
2274
|
|
|
2275
|
+
/** Y-range easing shared by both charts, independent of value/window tracking. */
|
|
2276
|
+
export interface RangeAnimationConfig {
|
|
2277
|
+
/** Ease outward bounds instead of snapping. Default `false`. */
|
|
2278
|
+
animateExpansion?: boolean;
|
|
2279
|
+
/**
|
|
2280
|
+
* Fraction of the remaining gap closed per 60fps frame when a bound expands.
|
|
2281
|
+
* Inherits `smoothing`; only used with `animateExpansion: true`. Finite values
|
|
2282
|
+
* are clamped to 0..1; non-finite values inherit `smoothing`.
|
|
2283
|
+
*/
|
|
2284
|
+
expansionSmoothing?: number;
|
|
2285
|
+
/**
|
|
2286
|
+
* Fraction of the remaining gap closed per 60fps frame when a bound contracts.
|
|
2287
|
+
* Inherits `smoothing`. Finite values are clamped to 0..1; non-finite values
|
|
2288
|
+
* inherit `smoothing`. While animated expansion moves toward a disjoint fit,
|
|
2289
|
+
* contraction is limited to the expansion speed so bounds cannot cross.
|
|
2290
|
+
*/
|
|
2291
|
+
contractionSmoothing?: number;
|
|
2292
|
+
}
|
|
2293
|
+
|
|
2261
2294
|
/** Props shared between `LiveChart` and `LiveChartSeries`. */
|
|
2262
2295
|
export interface LiveChartCoreProps {
|
|
2263
2296
|
/** Color scheme. Default `"dark"`. */
|
|
@@ -2324,6 +2357,14 @@ export interface LiveChartCoreProps {
|
|
|
2324
2357
|
* `lerpSpeed`. Default `0.08`.
|
|
2325
2358
|
*/
|
|
2326
2359
|
smoothing?: number;
|
|
2360
|
+
/**
|
|
2361
|
+
* Control Y-range expansion and contraction independently of `smoothing` for
|
|
2362
|
+
* value/window tracking. Omission preserves instant expansion and eased
|
|
2363
|
+
* contraction. Uses frame-rate-independent exponential easing, not a fixed
|
|
2364
|
+
* duration. `smoothing: 1`, explicit `snapKey` changes, static charts, and active
|
|
2365
|
+
* manual Y-scale dragging remain immediate. Axis-label fades use `metrics.grid`.
|
|
2366
|
+
*/
|
|
2367
|
+
rangeAnimation?: RangeAnimationConfig;
|
|
2327
2368
|
/**
|
|
2328
2369
|
* Snap the framing to its target in a single frame whenever this key changes —
|
|
2329
2370
|
* without giving up smooth live ticks. On a timeframe / dataset switch, the
|