react-native-livechart 4.3.0 → 4.5.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/components/LoadingOverlay.d.ts +9 -1
- package/dist/components/LoadingOverlay.d.ts.map +1 -1
- package/dist/components/MarkerOverlay.d.ts.map +1 -1
- package/dist/constants.d.ts +14 -0
- package/dist/constants.d.ts.map +1 -1
- package/dist/core/resolveConfig.d.ts +35 -1
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/draw/markerAtlas.d.ts +9 -2
- package/dist/draw/markerAtlas.d.ts.map +1 -1
- package/dist/hooks/useChartPaths.d.ts +5 -1
- package/dist/hooks/useChartPaths.d.ts.map +1 -1
- package/dist/hooks/useChartReveal.d.ts +3 -1
- package/dist/hooks/useChartReveal.d.ts.map +1 -1
- package/dist/hooks/useMarkers.d.ts.map +1 -1
- package/dist/hooks/useModeBlend.d.ts +4 -1
- package/dist/hooks/useModeBlend.d.ts.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/math/markerCluster.d.ts +8 -1
- package/dist/math/markerCluster.d.ts.map +1 -1
- package/dist/math/squiggly.d.ts +8 -5
- package/dist/math/squiggly.d.ts.map +1 -1
- package/dist/types.d.ts +128 -2
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/LiveChart.tsx +126 -42
- package/src/components/LiveChartSeries.tsx +95 -26
- package/src/components/LoadingOverlay.tsx +25 -6
- package/src/components/MarkerOverlay.tsx +135 -37
- package/src/constants.ts +17 -0
- package/src/core/resolveConfig.ts +67 -0
- package/src/draw/markerAtlas.ts +30 -1
- package/src/hooks/useChartPaths.ts +11 -1
- package/src/hooks/useChartReveal.ts +7 -2
- package/src/hooks/useMarkers.ts +2 -0
- package/src/hooks/useModeBlend.ts +5 -3
- package/src/index.ts +3 -0
- package/src/math/markerCluster.ts +8 -1
- package/src/math/squiggly.ts +21 -7
- package/src/types.ts +131 -2
|
@@ -14,6 +14,7 @@ import type {
|
|
|
14
14
|
LineStyleConfig,
|
|
15
15
|
LiveChartMetrics,
|
|
16
16
|
LiveChartMetricsOverride,
|
|
17
|
+
LoadingConfig,
|
|
17
18
|
MarkerClusterConfig,
|
|
18
19
|
DotConfig,
|
|
19
20
|
DotRingConfig,
|
|
@@ -29,6 +30,7 @@ import type {
|
|
|
29
30
|
ThresholdConfig,
|
|
30
31
|
ThresholdLineConfig,
|
|
31
32
|
TradeEvent,
|
|
33
|
+
TransitionConfig,
|
|
32
34
|
ValueLineConfig,
|
|
33
35
|
VolumeConfig,
|
|
34
36
|
XAxisConfig,
|
|
@@ -45,6 +47,8 @@ import {
|
|
|
45
47
|
EMPTY_STATE_METRICS_DEFAULTS,
|
|
46
48
|
FADE_EDGE_WIDTH,
|
|
47
49
|
GRID_METRICS_DEFAULTS,
|
|
50
|
+
LOADING_WAVE_AMPLITUDE,
|
|
51
|
+
LOADING_WAVE_SPEED,
|
|
48
52
|
MOTION_METRICS_DEFAULTS,
|
|
49
53
|
RETURN_TO_LIVE_MS,
|
|
50
54
|
} from "../constants";
|
|
@@ -165,6 +169,8 @@ export interface ResolvedScrubConfig {
|
|
|
165
169
|
tooltipShowTime: boolean;
|
|
166
170
|
/** Press-and-hold delay (ms) before scrubbing activates. 0 = immediate. */
|
|
167
171
|
panGestureDelay: number;
|
|
172
|
+
/** Fade markers + reference lines out while scrubbing. */
|
|
173
|
+
hideOverlaysOnScrub: boolean;
|
|
168
174
|
}
|
|
169
175
|
|
|
170
176
|
export interface ResolvedScrubActionConfig {
|
|
@@ -477,6 +483,32 @@ export function resolveReturnToLiveMs(
|
|
|
477
483
|
return d > 0 ? d : 0;
|
|
478
484
|
}
|
|
479
485
|
|
|
486
|
+
/**
|
|
487
|
+
* Resolved transition durations. `undefined` for a field means "use the
|
|
488
|
+
* component's built-in default" (so we don't duplicate the default constants
|
|
489
|
+
* here); a number is an explicit duration in ms (clamped to ≥ 0).
|
|
490
|
+
*/
|
|
491
|
+
export interface ResolvedTransitionConfig {
|
|
492
|
+
reveal: number | undefined;
|
|
493
|
+
mode: number | undefined;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
const clampMs = (v: number | undefined): number | undefined =>
|
|
497
|
+
v == null ? undefined : v > 0 ? v : 0;
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Resolves the `transitions` prop. `false` → all transitions instant (`0`);
|
|
501
|
+
* `true` / omitted → defaults (both `undefined` = use the built-in durations);
|
|
502
|
+
* an object → per-transition overrides (an omitted field keeps its default).
|
|
503
|
+
*/
|
|
504
|
+
export function resolveTransitions(
|
|
505
|
+
prop: boolean | TransitionConfig | undefined,
|
|
506
|
+
): ResolvedTransitionConfig {
|
|
507
|
+
if (prop === false) return { reveal: 0, mode: 0 };
|
|
508
|
+
if (prop == null || prop === true) return { reveal: undefined, mode: undefined };
|
|
509
|
+
return { reveal: clampMs(prop.reveal), mode: clampMs(prop.mode) };
|
|
510
|
+
}
|
|
511
|
+
|
|
480
512
|
const AXIS_LABEL_DEFAULTS: ResolvedAxisLabelConfig = {
|
|
481
513
|
format: undefined,
|
|
482
514
|
color: undefined,
|
|
@@ -571,6 +603,7 @@ const SCRUB_DEFAULTS: ResolvedScrubConfig = {
|
|
|
571
603
|
tooltipShowValue: true,
|
|
572
604
|
tooltipShowTime: true,
|
|
573
605
|
panGestureDelay: 0,
|
|
606
|
+
hideOverlaysOnScrub: false,
|
|
574
607
|
};
|
|
575
608
|
|
|
576
609
|
/**
|
|
@@ -590,6 +623,36 @@ export function resolveScrub(
|
|
|
590
623
|
return resolved;
|
|
591
624
|
}
|
|
592
625
|
|
|
626
|
+
export interface ResolvedLoadingConfig {
|
|
627
|
+
/** undefined → palette.gridLine */
|
|
628
|
+
color: string | undefined;
|
|
629
|
+
/** undefined → the chart's line strokeWidth */
|
|
630
|
+
strokeWidth: number | undefined;
|
|
631
|
+
/** Base breathing-wave amplitude (px). */
|
|
632
|
+
amplitude: number;
|
|
633
|
+
/** Breathing-wave speed multiplier. */
|
|
634
|
+
speed: number;
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const LOADING_DEFAULTS: ResolvedLoadingConfig = {
|
|
638
|
+
color: undefined,
|
|
639
|
+
strokeWidth: undefined,
|
|
640
|
+
amplitude: LOADING_WAVE_AMPLITUDE,
|
|
641
|
+
speed: LOADING_WAVE_SPEED,
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
/**
|
|
645
|
+
* Resolves the `loading` prop to a fully-typed config or null (not loading).
|
|
646
|
+
* `true` → defaults (loading on, built-in look), object → merged with defaults
|
|
647
|
+
* (loading on, restyled), `false` / omitted → null. So a non-null result is the
|
|
648
|
+
* "is loading" flag and carries the resolved styling.
|
|
649
|
+
*/
|
|
650
|
+
export function resolveLoading(
|
|
651
|
+
prop: boolean | LoadingConfig | undefined,
|
|
652
|
+
): ResolvedLoadingConfig | null {
|
|
653
|
+
return resolveToggle(prop, LOADING_DEFAULTS, false);
|
|
654
|
+
}
|
|
655
|
+
|
|
593
656
|
const SCRUB_ACTION_DEFAULTS: ResolvedScrubActionConfig = {
|
|
594
657
|
icon: "+",
|
|
595
658
|
background: undefined,
|
|
@@ -634,6 +697,8 @@ export function resolveMarkerCluster(
|
|
|
634
697
|
overlap: clamp01(prop.overlap ?? MARKER_CLUSTER_OVERLAP),
|
|
635
698
|
gap: MARKER_CLUSTER_GAP,
|
|
636
699
|
maxBeforeGroup: prop.maxBeforeGroup ?? MARKER_CLUSTER_MAX_BEFORE_GROUP,
|
|
700
|
+
groupBadge: prop.groupBadge ?? "count",
|
|
701
|
+
showGroupCount: prop.showGroupCount ?? false,
|
|
637
702
|
};
|
|
638
703
|
}
|
|
639
704
|
return {
|
|
@@ -642,6 +707,8 @@ export function resolveMarkerCluster(
|
|
|
642
707
|
overlap: MARKER_CLUSTER_OVERLAP,
|
|
643
708
|
gap: MARKER_CLUSTER_GAP,
|
|
644
709
|
maxBeforeGroup: MARKER_CLUSTER_MAX_BEFORE_GROUP,
|
|
710
|
+
groupBadge: "count",
|
|
711
|
+
showGroupCount: false,
|
|
645
712
|
};
|
|
646
713
|
}
|
|
647
714
|
|
package/src/draw/markerAtlas.ts
CHANGED
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
type SkPaint,
|
|
11
11
|
type SkRect,
|
|
12
12
|
} from "@shopify/react-native-skia";
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
LiveChartPalette,
|
|
15
|
+
Marker,
|
|
16
|
+
MarkerGroupBadge,
|
|
17
|
+
MarkerKind,
|
|
18
|
+
} from "../types";
|
|
14
19
|
|
|
15
20
|
/** Default icon box (px) when `marker.size` is unset. */
|
|
16
21
|
export const DEFAULT_ICON_SIZE = 16;
|
|
@@ -37,6 +42,10 @@ export function groupGlyphSig(ch: string): string {
|
|
|
37
42
|
return `gd\x1f${ch}`;
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
/** Atlas cell sig for a dedicated group badge (one per chart — its own custom
|
|
46
|
+
* image/icon, independent of the member markers). */
|
|
47
|
+
export const GROUP_BADGE_SIG = "grpbadge";
|
|
48
|
+
|
|
40
49
|
/** Atlas cell sig for the round count-badge background (one fixed circle per color). */
|
|
41
50
|
export function groupBgSig(color: string): string {
|
|
42
51
|
"worklet";
|
|
@@ -362,6 +371,10 @@ export function buildMarkerAtlas(
|
|
|
362
371
|
/** Also bake count-badge cells (digits + per-color backgrounds) for
|
|
363
372
|
* `markerCluster: "stacked"` collapse. Off by default — no atlas bloat. */
|
|
364
373
|
withGroups = false,
|
|
374
|
+
/** A dedicated group badge (custom image/icon) to bake one cell for, under
|
|
375
|
+
* {@link GROUP_BADGE_SIG} — drawn for a collapsed cluster instead of the count.
|
|
376
|
+
* Only baked when it carries an `image` or `icon`. */
|
|
377
|
+
groupBadge?: MarkerGroupBadge,
|
|
365
378
|
): MarkerAtlas {
|
|
366
379
|
const seen = new Set<string>();
|
|
367
380
|
const specs: { sig: string; spec: CellSpec }[] = [];
|
|
@@ -374,6 +387,22 @@ export function buildMarkerAtlas(
|
|
|
374
387
|
specs.push({ sig, spec: cellSpec(m, palette, font) });
|
|
375
388
|
}
|
|
376
389
|
|
|
390
|
+
// Dedicated group badge: bake one cell from its custom image/icon (reusing the
|
|
391
|
+
// marker cell geometry) so a collapsed cluster can blit it like any other glyph.
|
|
392
|
+
if (groupBadge && (groupBadge.image || groupBadge.icon)) {
|
|
393
|
+
const synthetic: Marker = {
|
|
394
|
+
id: GROUP_BADGE_SIG,
|
|
395
|
+
time: 0,
|
|
396
|
+
kind: "trade",
|
|
397
|
+
image: groupBadge.image,
|
|
398
|
+
icon: groupBadge.icon,
|
|
399
|
+
color: groupBadge.color,
|
|
400
|
+
pill: groupBadge.pill,
|
|
401
|
+
size: groupBadge.size,
|
|
402
|
+
};
|
|
403
|
+
specs.push({ sig: GROUP_BADGE_SIG, spec: cellSpec(synthetic, palette, font) });
|
|
404
|
+
}
|
|
405
|
+
|
|
377
406
|
// Count-badge cells: 10 uniform white digit cells shared across colors, plus one
|
|
378
407
|
// fixed round background per cluster color. A collapsed group composes its number
|
|
379
408
|
// (centered, ≤ 2 digits) over the circle in the same `drawAtlas`.
|
|
@@ -35,6 +35,10 @@ export function useChartPaths(
|
|
|
35
35
|
*/
|
|
36
36
|
edgeValue?: SharedValue<number>,
|
|
37
37
|
followViewEdge = false,
|
|
38
|
+
/** Loading squiggle wave amplitude (px) for the reveal morph. Default 14. */
|
|
39
|
+
squiggleAmplitude = 14,
|
|
40
|
+
/** Loading squiggle wave speed multiplier for the reveal morph. Default 1. */
|
|
41
|
+
squiggleSpeed = 1,
|
|
38
42
|
) {
|
|
39
43
|
const lineBuilder = usePathBuilder();
|
|
40
44
|
const fillBuilder = usePathBuilder();
|
|
@@ -86,7 +90,13 @@ export function useChartPaths(
|
|
|
86
90
|
// Compute squiggly Y values at the same X positions as the real line
|
|
87
91
|
const centerY =
|
|
88
92
|
(engine.canvasHeight.get() - padding.bottom + padding.top) / 2;
|
|
89
|
-
const squigglyPts = squigglifyPts(
|
|
93
|
+
const squigglyPts = squigglifyPts(
|
|
94
|
+
realPts,
|
|
95
|
+
engine.timestamp.get(),
|
|
96
|
+
centerY,
|
|
97
|
+
squiggleAmplitude,
|
|
98
|
+
squiggleSpeed,
|
|
99
|
+
);
|
|
90
100
|
|
|
91
101
|
// Blend center-out: centre of chart reveals first, edges last
|
|
92
102
|
return blendPtsY(
|
|
@@ -60,6 +60,8 @@ export function useChartReveal(
|
|
|
60
60
|
hasData: SharedValue<boolean>,
|
|
61
61
|
/** Static charts skip the entry ramp: morphT snaps to its target, no `withTiming`. */
|
|
62
62
|
isStatic = false,
|
|
63
|
+
/** Reveal/collapse duration (ms). Default {@link CHART_REVEAL_DURATION_MS}; `0` snaps. */
|
|
64
|
+
revealDuration: number = CHART_REVEAL_DURATION_MS,
|
|
63
65
|
): ChartRevealState {
|
|
64
66
|
// Best-guess initial so the common case — a live chart that already has data
|
|
65
67
|
// and isn't loading — paints fully revealed with no flash. The reaction below
|
|
@@ -85,15 +87,18 @@ export function useChartReveal(
|
|
|
85
87
|
return;
|
|
86
88
|
}
|
|
87
89
|
if (prev !== chartVisible) {
|
|
90
|
+
// 0ms → withTiming resolves on the next frame (effectively a snap), so an
|
|
91
|
+
// explicit `transitions={{ reveal: 0 }}` / `transitions={false}` removes
|
|
92
|
+
// the grow-in without a special-case branch.
|
|
88
93
|
morphT.set(
|
|
89
94
|
withTiming(chartVisible ? 1 : 0, {
|
|
90
|
-
duration:
|
|
95
|
+
duration: revealDuration,
|
|
91
96
|
easing: Easing.out(Easing.cubic),
|
|
92
97
|
}),
|
|
93
98
|
);
|
|
94
99
|
}
|
|
95
100
|
},
|
|
96
|
-
[hasData, isStatic],
|
|
101
|
+
[hasData, isStatic, revealDuration],
|
|
97
102
|
);
|
|
98
103
|
|
|
99
104
|
const isEmpty = useDerivedValue(() => !isLoading.get() && !hasData.get());
|
package/src/hooks/useMarkers.ts
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
type SharedValue,
|
|
8
8
|
} from "react-native-reanimated";
|
|
9
9
|
|
|
10
|
-
const MODE_BLEND_DURATION_MS = 300;
|
|
10
|
+
export const MODE_BLEND_DURATION_MS = 300;
|
|
11
11
|
|
|
12
12
|
export interface ModeBlendState {
|
|
13
13
|
/** 0 = fully line, 1 = fully candle */
|
|
@@ -28,17 +28,19 @@ export interface ModeBlendState {
|
|
|
28
28
|
export function useModeBlend(
|
|
29
29
|
isCandle: boolean,
|
|
30
30
|
lineOpacity: SharedValue<number>,
|
|
31
|
+
/** Crossfade duration (ms). Default {@link MODE_BLEND_DURATION_MS}; `0` snaps. */
|
|
32
|
+
duration: number = MODE_BLEND_DURATION_MS,
|
|
31
33
|
): ModeBlendState {
|
|
32
34
|
const modeBlend = useSharedValue(isCandle ? 1 : 0);
|
|
33
35
|
|
|
34
36
|
useEffect(() => {
|
|
35
37
|
modeBlend.set(
|
|
36
38
|
withTiming(isCandle ? 1 : 0, {
|
|
37
|
-
duration
|
|
39
|
+
duration,
|
|
38
40
|
easing: Easing.inOut(Easing.ease),
|
|
39
41
|
}),
|
|
40
42
|
);
|
|
41
|
-
}, [isCandle, modeBlend]);
|
|
43
|
+
}, [isCandle, modeBlend, duration]);
|
|
42
44
|
|
|
43
45
|
const lineGroupOpacity = useDerivedValue(
|
|
44
46
|
() => lineOpacity.get() * (1 - modeBlend.get()),
|
package/src/index.ts
CHANGED
|
@@ -64,8 +64,10 @@ export type {
|
|
|
64
64
|
LiveChartPoint,
|
|
65
65
|
LiveChartProps,
|
|
66
66
|
LiveChartSeriesProps,
|
|
67
|
+
LoadingConfig,
|
|
67
68
|
Marker,
|
|
68
69
|
MarkerClusterConfig,
|
|
70
|
+
MarkerGroupBadge,
|
|
69
71
|
MarkerKind,
|
|
70
72
|
MarkerPressEvent,
|
|
71
73
|
MarkerRenderContext,
|
|
@@ -96,6 +98,7 @@ export type {
|
|
|
96
98
|
ThresholdLineConfig,
|
|
97
99
|
TooltipRenderProps,
|
|
98
100
|
TradeEvent,
|
|
101
|
+
TransitionConfig,
|
|
99
102
|
ValueLineConfig,
|
|
100
103
|
VisibleRange,
|
|
101
104
|
VolumeConfig,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { Marker, MarkerSide } from "../types";
|
|
1
|
+
import type { Marker, MarkerGroupBadge, MarkerSide } from "../types";
|
|
2
2
|
import type { ProjectedMarker } from "./markers";
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -20,6 +20,13 @@ export interface ResolvedMarkerCluster {
|
|
|
20
20
|
gap: number;
|
|
21
21
|
/** Collapse a co-located run to a single count badge once it exceeds this many. */
|
|
22
22
|
maxBeforeGroup: number;
|
|
23
|
+
/** What a collapsed group draws: `"count"` = the round count badge (default);
|
|
24
|
+
* `"marker"` = the representative marker's own glyph; a {@link MarkerGroupBadge}
|
|
25
|
+
* = a dedicated badge (custom image/icon) independent of the members. */
|
|
26
|
+
groupBadge: "count" | "marker" | MarkerGroupBadge;
|
|
27
|
+
/** With `groupBadge: "marker"` or a dedicated badge, also stamp the member count
|
|
28
|
+
* in the glyph's top-right corner. Ignored for `"count"`. */
|
|
29
|
+
showGroupCount: boolean;
|
|
23
30
|
}
|
|
24
31
|
|
|
25
32
|
export interface ClusterMarkersOpts {
|
package/src/math/squiggly.ts
CHANGED
|
@@ -4,16 +4,26 @@ import type { ChartPadding } from "../draw/line";
|
|
|
4
4
|
* Composite sine squiggly — two overlapping frequencies with a breathing
|
|
5
5
|
* amplitude envelope. Matches the original web LiveChart loading animation.
|
|
6
6
|
*
|
|
7
|
-
* amplitude =
|
|
8
|
-
* y = centerY + amplitude * (sin(0.035*x + 1.2*t) + 0.45*sin(0.08*x + 2.1*t))
|
|
7
|
+
* amplitude = base * (0.4 + 0.6 * sin(0.8 * t·speed)) // 0.4×→1.0× base
|
|
8
|
+
* y = centerY + amplitude * (sin(0.035*x + 1.2*t·speed) + 0.45*sin(0.08*x + 2.1*t·speed))
|
|
9
|
+
*
|
|
10
|
+
* `base` (default `14`) scales the wave height; `speed` (default `1`) scales the
|
|
11
|
+
* time phase so the ripple/breathing runs faster or slower (`0` freezes it).
|
|
9
12
|
*/
|
|
10
|
-
export function squigglyYAt(
|
|
13
|
+
export function squigglyYAt(
|
|
14
|
+
x: number,
|
|
15
|
+
centerY: number,
|
|
16
|
+
t: number,
|
|
17
|
+
base = 14,
|
|
18
|
+
speed = 1,
|
|
19
|
+
): number {
|
|
11
20
|
"worklet";
|
|
12
|
-
const
|
|
21
|
+
const ts = t * speed;
|
|
22
|
+
const amplitude = base * (0.4 + 0.6 * Math.sin(0.8 * ts));
|
|
13
23
|
return (
|
|
14
24
|
centerY +
|
|
15
25
|
amplitude *
|
|
16
|
-
(Math.sin(0.035 * x + 1.2 *
|
|
26
|
+
(Math.sin(0.035 * x + 1.2 * ts) + 0.45 * Math.sin(0.08 * x + 2.1 * ts))
|
|
17
27
|
);
|
|
18
28
|
}
|
|
19
29
|
|
|
@@ -27,6 +37,8 @@ export function buildSquigglyPts(
|
|
|
27
37
|
canvasHeight: number,
|
|
28
38
|
padding: ChartPadding,
|
|
29
39
|
t: number,
|
|
40
|
+
base = 14,
|
|
41
|
+
speed = 1,
|
|
30
42
|
): number[] {
|
|
31
43
|
"worklet";
|
|
32
44
|
const leftEdge = padding.left;
|
|
@@ -41,7 +53,7 @@ export function buildSquigglyPts(
|
|
|
41
53
|
for (let i = 0; i < count; i++) {
|
|
42
54
|
const x = leftEdge + Math.min(i * step, chartW);
|
|
43
55
|
pts[i * 2] = x;
|
|
44
|
-
pts[i * 2 + 1] = squigglyYAt(x, centerY, t);
|
|
56
|
+
pts[i * 2 + 1] = squigglyYAt(x, centerY, t, base, speed);
|
|
45
57
|
}
|
|
46
58
|
return pts;
|
|
47
59
|
}
|
|
@@ -54,13 +66,15 @@ export function squigglifyPts(
|
|
|
54
66
|
flatPts: number[],
|
|
55
67
|
t: number,
|
|
56
68
|
centerY: number,
|
|
69
|
+
base = 14,
|
|
70
|
+
speed = 1,
|
|
57
71
|
): number[] {
|
|
58
72
|
"worklet";
|
|
59
73
|
const n = flatPts.length;
|
|
60
74
|
const out: number[] = new Array(n);
|
|
61
75
|
for (let i = 0; i < n; i += 2) {
|
|
62
76
|
out[i] = flatPts[i];
|
|
63
|
-
out[i + 1] = squigglyYAt(flatPts[i], centerY, t);
|
|
77
|
+
out[i + 1] = squigglyYAt(flatPts[i], centerY, t, base, speed);
|
|
64
78
|
}
|
|
65
79
|
return out;
|
|
66
80
|
}
|
package/src/types.ts
CHANGED
|
@@ -720,6 +720,23 @@ export interface ScrubConfig {
|
|
|
720
720
|
* Default `0`.
|
|
721
721
|
*/
|
|
722
722
|
panGestureDelay?: number;
|
|
723
|
+
/**
|
|
724
|
+
* Fade the annotation overlays — buy/sell **markers** and **reference lines**
|
|
725
|
+
* (both the built-in Skia tags/lines and any custom `renderMarker` /
|
|
726
|
+
* `renderReferenceLine` RN views) — out while scrubbing, so they don't clutter
|
|
727
|
+
* the crosshair read-out. Reverses on release.
|
|
728
|
+
*
|
|
729
|
+
* The fade is driven by the **scrub-active** state (not the crosshair's
|
|
730
|
+
* edge-proximity fade, which would resurface the overlays as the crosshair
|
|
731
|
+
* nears the live dot) and eased on the UI thread over `SCRUB_OVERLAY_FADE_MS`.
|
|
732
|
+
* It animates only a **group opacity** — the marker atlas and reference-line
|
|
733
|
+
* geometry are left intact (still one batched draw each), so it's far cheaper
|
|
734
|
+
* than emptying / rebuilding overlay data per scrub. The leading dot is
|
|
735
|
+
* governed separately by `selectionDot`.
|
|
736
|
+
*
|
|
737
|
+
* `false` / omitted keeps the overlays visible while scrubbing (default).
|
|
738
|
+
*/
|
|
739
|
+
hideOverlaysOnScrub?: boolean;
|
|
723
740
|
}
|
|
724
741
|
|
|
725
742
|
/**
|
|
@@ -1133,6 +1150,53 @@ export interface MarkerClusterConfig {
|
|
|
1133
1150
|
/** Collapse a co-located run to a single count badge once it exceeds this many.
|
|
1134
1151
|
* Default `5`. */
|
|
1135
1152
|
maxBeforeGroup?: number;
|
|
1153
|
+
/**
|
|
1154
|
+
* What a collapsed cluster draws in place of its members:
|
|
1155
|
+
* - `"count"` (default) — the built-in round count badge (a circle with the
|
|
1156
|
+
* member count inside).
|
|
1157
|
+
* - `"marker"` — the **representative marker's own glyph** (its `image`, `icon`
|
|
1158
|
+
* /`pill`, or `kind` shape). The representative is the newest marker in the run,
|
|
1159
|
+
* so a run of buy pills collapses to a buy pill — handy when the group should
|
|
1160
|
+
* look like its members.
|
|
1161
|
+
* - {@link MarkerGroupBadge} — a **dedicated group badge you supply** (your own
|
|
1162
|
+
* Skia `image`, or an `icon`/`pill`), independent of the member markers. Use
|
|
1163
|
+
* this when the collapse should look *different* from the individual markers —
|
|
1164
|
+
* e.g. tiny dots that collapse into a distinct "Buy 5" badge image.
|
|
1165
|
+
*
|
|
1166
|
+
* All three are Skia-drawn in the same `drawAtlas` batch (not a `renderMarker`
|
|
1167
|
+
* RN overlay). Pair any non-`"count"` form with {@link showGroupCount} for a
|
|
1168
|
+
* corner count. Default `"count"`.
|
|
1169
|
+
*/
|
|
1170
|
+
groupBadge?: "count" | "marker" | MarkerGroupBadge;
|
|
1171
|
+
/**
|
|
1172
|
+
* When {@link groupBadge} is `"marker"` or a {@link MarkerGroupBadge}, also stamp
|
|
1173
|
+
* the member count as a small badge in the glyph's top-right corner — the
|
|
1174
|
+
* "Buy **5**" look. Ignored when `groupBadge` is `"count"` (the count *is* the
|
|
1175
|
+
* badge). Default `false`.
|
|
1176
|
+
*/
|
|
1177
|
+
showGroupCount?: boolean;
|
|
1178
|
+
}
|
|
1179
|
+
|
|
1180
|
+
/**
|
|
1181
|
+
* A dedicated badge drawn for a collapsed marker cluster — your own Skia design,
|
|
1182
|
+
* independent of the member markers. Pass it as
|
|
1183
|
+
* {@link MarkerClusterConfig.groupBadge}. Glyph precedence mirrors a single marker:
|
|
1184
|
+
* `image` → `icon` (`pill`) → a filled dot. Requires `image` or `icon` (otherwise
|
|
1185
|
+
* the group falls back to the count badge).
|
|
1186
|
+
*/
|
|
1187
|
+
export interface MarkerGroupBadge {
|
|
1188
|
+
/** Custom Skia image for the collapsed group (e.g. from `useImage`). Takes
|
|
1189
|
+
* precedence over {@link icon}. */
|
|
1190
|
+
image?: SkImage;
|
|
1191
|
+
/** Text / emoji glyph for the collapsed group (used when {@link image} is unset).
|
|
1192
|
+
* Rendered with the chart font. */
|
|
1193
|
+
icon?: string;
|
|
1194
|
+
/** Color of the `icon` (and its `pill`). Defaults to a palette accent. */
|
|
1195
|
+
color?: string;
|
|
1196
|
+
/** Wrap the `icon` in a filled circular badge in {@link color} (icon drawn white). */
|
|
1197
|
+
pill?: boolean;
|
|
1198
|
+
/** Glyph box size in px (icon font size, or image width+height). Default `16`. */
|
|
1199
|
+
size?: number;
|
|
1136
1200
|
}
|
|
1137
1201
|
|
|
1138
1202
|
/** Context passed to `renderMarker` alongside the marker (cluster / position state). */
|
|
@@ -1539,6 +1603,55 @@ export interface VisibleRange {
|
|
|
1539
1603
|
following: boolean;
|
|
1540
1604
|
}
|
|
1541
1605
|
|
|
1606
|
+
/**
|
|
1607
|
+
* Per-transition animation durations (ms). Passed as the object form of
|
|
1608
|
+
* {@link LiveChartCoreProps.transitions} to tune or disable the built-in
|
|
1609
|
+
* animations. Omit a field to keep its default; `0` makes that transition
|
|
1610
|
+
* instant (snap). Setting `transitions={false}` is shorthand for all `0`.
|
|
1611
|
+
*/
|
|
1612
|
+
export interface TransitionConfig {
|
|
1613
|
+
/**
|
|
1614
|
+
* Reveal / collapse transition — the grow-in when data first appears (and the
|
|
1615
|
+
* fade-out when it goes away or `loading` toggles). This is what plays on a
|
|
1616
|
+
* timeframe change and when a `line` chart's data appears (e.g. switching from
|
|
1617
|
+
* candle to line). Default `600`. `0` = instant.
|
|
1618
|
+
*/
|
|
1619
|
+
reveal?: number;
|
|
1620
|
+
/**
|
|
1621
|
+
* Candle ↔ line crossfade duration when `mode` changes. Single-series
|
|
1622
|
+
* `LiveChart` only (multi-series is always lines). Default `300`. `0` = instant.
|
|
1623
|
+
*/
|
|
1624
|
+
mode?: number;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
/**
|
|
1628
|
+
* Styling for the breathing-line loading shell (the {@link LiveChartCoreProps.loading}
|
|
1629
|
+
* state). Every field is optional — pass it as the object form of `loading` to
|
|
1630
|
+
* restyle the shell; omit a field to keep its default.
|
|
1631
|
+
*/
|
|
1632
|
+
export interface LoadingConfig {
|
|
1633
|
+
/**
|
|
1634
|
+
* Color of the loading squiggle **and** the skeleton Y-axis placeholders.
|
|
1635
|
+
* Default: the theme's `gridLine` color.
|
|
1636
|
+
*/
|
|
1637
|
+
color?: string;
|
|
1638
|
+
/**
|
|
1639
|
+
* Stroke width (px) of the loading squiggle. Default: the chart's line
|
|
1640
|
+
* `strokeWidth`.
|
|
1641
|
+
*/
|
|
1642
|
+
strokeWidth?: number;
|
|
1643
|
+
/**
|
|
1644
|
+
* Base wave amplitude (px) of the breathing squiggle — it breathes between
|
|
1645
|
+
* `0.4×` and `1.0×` this. Default `14`.
|
|
1646
|
+
*/
|
|
1647
|
+
amplitude?: number;
|
|
1648
|
+
/**
|
|
1649
|
+
* Multiplier on the breathing-wave animation cadence: `>1` ripples faster,
|
|
1650
|
+
* `<1` slower, `0` freezes it. Default `1`.
|
|
1651
|
+
*/
|
|
1652
|
+
speed?: number;
|
|
1653
|
+
}
|
|
1654
|
+
|
|
1542
1655
|
/** Props shared between `LiveChart` and `LiveChartSeries`. */
|
|
1543
1656
|
export interface LiveChartCoreProps {
|
|
1544
1657
|
/** Color scheme. Default `"dark"`. */
|
|
@@ -1555,11 +1668,27 @@ export interface LiveChartCoreProps {
|
|
|
1555
1668
|
timeWindow?: number;
|
|
1556
1669
|
/** Freeze chart scrolling. Resume catches up to real time. Default `false`. */
|
|
1557
1670
|
paused?: boolean;
|
|
1671
|
+
/**
|
|
1672
|
+
* Tune or disable the built-in transition animations. `true` / omitted keeps
|
|
1673
|
+
* the defaults; `false` makes them all instant (no grow-in on data
|
|
1674
|
+
* appear/timeframe change, no candle↔line crossfade); a {@link TransitionConfig}
|
|
1675
|
+
* sets per-transition durations in ms (`reveal`, `mode`), where `0` snaps that
|
|
1676
|
+
* one. Live value tracking is governed separately by `smoothing`; static-entry
|
|
1677
|
+
* suppression by `static`.
|
|
1678
|
+
*/
|
|
1679
|
+
transitions?: boolean | TransitionConfig;
|
|
1558
1680
|
/**
|
|
1559
1681
|
* Breathing-line loading shell. When this becomes `false`, the chart reveals
|
|
1560
1682
|
* only if there is data (≥2 line points or ≥2 committed candles).
|
|
1561
|
-
|
|
1562
|
-
|
|
1683
|
+
*
|
|
1684
|
+
* `true` shows the shell with the defaults; pass a {@link LoadingConfig} to
|
|
1685
|
+
* restyle it — `color` / `strokeWidth` for the squiggle + skeleton, `amplitude`
|
|
1686
|
+
* / `speed` for the breathing wave. `false` / omitted is "not loading". Toggle
|
|
1687
|
+
* between the config and `false` as data loads (e.g. `loading={isLoading && cfg}`).
|
|
1688
|
+
* The loading→live reveal duration is governed separately by
|
|
1689
|
+
* {@link TransitionConfig.reveal} (the `transitions` prop).
|
|
1690
|
+
*/
|
|
1691
|
+
loading?: boolean | LoadingConfig;
|
|
1563
1692
|
/**
|
|
1564
1693
|
* Value-lerp speed — how quickly the drawn value, time window, and Y-range chase
|
|
1565
1694
|
* their targets each frame (0 = frozen, 1 = instant). Equivalent to liveline's
|