@coinbase/cds-mobile-visualization 3.4.0-beta.4 → 3.4.0-beta.6
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/CHANGELOG.md +13 -0
- package/dts/chart/CartesianChart.d.ts +57 -33
- package/dts/chart/CartesianChart.d.ts.map +1 -1
- package/dts/chart/ChartContextBridge.d.ts +28 -0
- package/dts/chart/ChartContextBridge.d.ts.map +1 -0
- package/dts/chart/Path.d.ts +77 -34
- package/dts/chart/Path.d.ts.map +1 -1
- package/dts/chart/PeriodSelector.d.ts +1 -1
- package/dts/chart/PeriodSelector.d.ts.map +1 -1
- package/dts/chart/area/Area.d.ts +42 -27
- package/dts/chart/area/Area.d.ts.map +1 -1
- package/dts/chart/area/AreaChart.d.ts +51 -10
- package/dts/chart/area/AreaChart.d.ts.map +1 -1
- package/dts/chart/area/DottedArea.d.ts +21 -2
- package/dts/chart/area/DottedArea.d.ts.map +1 -1
- package/dts/chart/area/GradientArea.d.ts +19 -13
- package/dts/chart/area/GradientArea.d.ts.map +1 -1
- package/dts/chart/area/SolidArea.d.ts +17 -2
- package/dts/chart/area/SolidArea.d.ts.map +1 -1
- package/dts/chart/axis/Axis.d.ts +68 -78
- package/dts/chart/axis/Axis.d.ts.map +1 -1
- package/dts/chart/axis/DefaultAxisTickLabel.d.ts +8 -0
- package/dts/chart/axis/DefaultAxisTickLabel.d.ts.map +1 -0
- package/dts/chart/axis/XAxis.d.ts +1 -1
- package/dts/chart/axis/XAxis.d.ts.map +1 -1
- package/dts/chart/axis/YAxis.d.ts +2 -2
- package/dts/chart/axis/YAxis.d.ts.map +1 -1
- package/dts/chart/axis/index.d.ts +1 -0
- package/dts/chart/axis/index.d.ts.map +1 -1
- package/dts/chart/bar/Bar.d.ts +16 -13
- package/dts/chart/bar/Bar.d.ts.map +1 -1
- package/dts/chart/bar/BarChart.d.ts +36 -20
- package/dts/chart/bar/BarChart.d.ts.map +1 -1
- package/dts/chart/bar/BarPlot.d.ts +2 -1
- package/dts/chart/bar/BarPlot.d.ts.map +1 -1
- package/dts/chart/bar/BarStack.d.ts +39 -48
- package/dts/chart/bar/BarStack.d.ts.map +1 -1
- package/dts/chart/bar/BarStackGroup.d.ts +1 -0
- package/dts/chart/bar/BarStackGroup.d.ts.map +1 -1
- package/dts/chart/bar/DefaultBar.d.ts +1 -1
- package/dts/chart/bar/DefaultBar.d.ts.map +1 -1
- package/dts/chart/bar/DefaultBarStack.d.ts.map +1 -1
- package/dts/chart/gradient/Gradient.d.ts +25 -0
- package/dts/chart/gradient/Gradient.d.ts.map +1 -0
- package/dts/chart/gradient/index.d.ts +2 -0
- package/dts/chart/gradient/index.d.ts.map +1 -0
- package/dts/chart/index.d.ts +3 -1
- package/dts/chart/index.d.ts.map +1 -1
- package/dts/chart/line/DefaultReferenceLineLabel.d.ts +9 -0
- package/dts/chart/line/DefaultReferenceLineLabel.d.ts.map +1 -0
- package/dts/chart/line/DottedLine.d.ts +13 -5
- package/dts/chart/line/DottedLine.d.ts.map +1 -1
- package/dts/chart/line/Line.d.ts +62 -25
- package/dts/chart/line/Line.d.ts.map +1 -1
- package/dts/chart/line/LineChart.d.ts +43 -9
- package/dts/chart/line/LineChart.d.ts.map +1 -1
- package/dts/chart/line/ReferenceLine.d.ts +65 -22
- package/dts/chart/line/ReferenceLine.d.ts.map +1 -1
- package/dts/chart/line/SolidLine.d.ts +8 -5
- package/dts/chart/line/SolidLine.d.ts.map +1 -1
- package/dts/chart/line/index.d.ts +1 -1
- package/dts/chart/line/index.d.ts.map +1 -1
- package/dts/chart/point/DefaultPointLabel.d.ts +10 -0
- package/dts/chart/point/DefaultPointLabel.d.ts.map +1 -0
- package/dts/chart/point/Point.d.ts +120 -0
- package/dts/chart/point/Point.d.ts.map +1 -0
- package/dts/chart/point/index.d.ts +3 -0
- package/dts/chart/point/index.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts +8 -0
- package/dts/chart/scrubber/DefaultScrubberBeacon.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts +12 -0
- package/dts/chart/scrubber/DefaultScrubberBeaconLabel.d.ts.map +1 -0
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts +11 -0
- package/dts/chart/scrubber/DefaultScrubberLabel.d.ts.map +1 -0
- package/dts/chart/scrubber/Scrubber.d.ts +168 -41
- package/dts/chart/scrubber/Scrubber.d.ts.map +1 -1
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts +44 -0
- package/dts/chart/scrubber/ScrubberBeaconGroup.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts +31 -0
- package/dts/chart/scrubber/ScrubberBeaconLabelGroup.d.ts.map +1 -0
- package/dts/chart/scrubber/ScrubberProvider.d.ts +6 -3
- package/dts/chart/scrubber/ScrubberProvider.d.ts.map +1 -1
- package/dts/chart/scrubber/index.d.ts +3 -0
- package/dts/chart/scrubber/index.d.ts.map +1 -1
- package/dts/chart/text/ChartText.d.ts +151 -77
- package/dts/chart/text/ChartText.d.ts.map +1 -1
- package/dts/chart/text/{SmartChartTextGroup.d.ts → ChartTextGroup.d.ts} +9 -3
- package/dts/chart/text/ChartTextGroup.d.ts.map +1 -0
- package/dts/chart/text/index.d.ts +1 -1
- package/dts/chart/text/index.d.ts.map +1 -1
- package/dts/chart/utils/chart.d.ts +34 -7
- package/dts/chart/utils/chart.d.ts.map +1 -1
- package/dts/chart/utils/context.d.ts +28 -7
- package/dts/chart/utils/context.d.ts.map +1 -1
- package/dts/chart/utils/gradient.d.ts +117 -0
- package/dts/chart/utils/gradient.d.ts.map +1 -0
- package/dts/chart/utils/index.d.ts +3 -0
- package/dts/chart/utils/index.d.ts.map +1 -1
- package/dts/chart/utils/path.d.ts +53 -0
- package/dts/chart/utils/path.d.ts.map +1 -1
- package/dts/chart/utils/point.d.ts +60 -1
- package/dts/chart/utils/point.d.ts.map +1 -1
- package/dts/chart/utils/scale.d.ts +91 -0
- package/dts/chart/utils/scale.d.ts.map +1 -1
- package/dts/chart/utils/scrubber.d.ts +39 -0
- package/dts/chart/utils/scrubber.d.ts.map +1 -0
- package/dts/chart/utils/transition.d.ts +140 -0
- package/dts/chart/utils/transition.d.ts.map +1 -0
- package/esm/chart/CartesianChart.js +164 -70
- package/esm/chart/ChartContextBridge.js +148 -0
- package/esm/chart/Path.js +196 -113
- package/esm/chart/PeriodSelector.js +1 -1
- package/esm/chart/__stories__/CartesianChart.stories.js +371 -129
- package/esm/chart/__stories__/Chart.stories.js +2 -4
- package/esm/chart/area/Area.js +25 -35
- package/esm/chart/area/AreaChart.js +17 -12
- package/esm/chart/area/DottedArea.js +61 -109
- package/esm/chart/area/GradientArea.js +35 -91
- package/esm/chart/area/SolidArea.js +22 -8
- package/esm/chart/area/__stories__/AreaChart.stories.js +1 -1
- package/esm/chart/axis/Axis.js +2 -0
- package/esm/chart/axis/DefaultAxisTickLabel.js +11 -0
- package/esm/chart/axis/XAxis.js +62 -56
- package/esm/chart/axis/YAxis.js +58 -52
- package/esm/chart/axis/__stories__/Axis.stories.js +0 -1
- package/esm/chart/axis/index.js +1 -0
- package/esm/chart/bar/Bar.js +3 -1
- package/esm/chart/bar/BarChart.js +15 -37
- package/esm/chart/bar/BarPlot.js +41 -35
- package/esm/chart/bar/BarStack.js +75 -38
- package/esm/chart/bar/BarStackGroup.js +6 -16
- package/esm/chart/bar/DefaultBar.js +26 -48
- package/esm/chart/bar/DefaultBarStack.js +23 -58
- package/esm/chart/bar/__stories__/BarChart.stories.js +463 -77
- package/esm/chart/gradient/Gradient.js +53 -0
- package/esm/chart/gradient/index.js +1 -0
- package/esm/chart/index.js +3 -1
- package/esm/chart/line/DefaultReferenceLineLabel.js +66 -0
- package/esm/chart/line/DottedLine.js +29 -14
- package/esm/chart/line/Line.js +106 -67
- package/esm/chart/line/LineChart.js +20 -14
- package/esm/chart/line/ReferenceLine.js +73 -62
- package/esm/chart/line/SolidLine.js +25 -10
- package/esm/chart/line/__stories__/LineChart.stories.js +2098 -1975
- package/esm/chart/line/__stories__/ReferenceLine.stories.js +83 -28
- package/esm/chart/line/index.js +1 -1
- package/esm/chart/point/DefaultPointLabel.js +39 -0
- package/esm/chart/point/Point.js +188 -0
- package/esm/chart/point/index.js +2 -0
- package/esm/chart/scrubber/DefaultScrubberBeacon.js +179 -0
- package/esm/chart/scrubber/DefaultScrubberBeaconLabel.js +43 -0
- package/esm/chart/scrubber/DefaultScrubberLabel.js +28 -0
- package/esm/chart/scrubber/Scrubber.js +130 -148
- package/esm/chart/scrubber/ScrubberBeaconGroup.js +161 -0
- package/esm/chart/scrubber/ScrubberBeaconLabelGroup.js +185 -0
- package/esm/chart/scrubber/ScrubberProvider.js +46 -54
- package/esm/chart/scrubber/index.js +3 -1
- package/esm/chart/text/ChartText.js +242 -174
- package/esm/chart/text/{SmartChartTextGroup.js → ChartTextGroup.js} +6 -5
- package/esm/chart/text/index.js +1 -1
- package/esm/chart/utils/chart.js +44 -3
- package/esm/chart/utils/gradient.js +305 -0
- package/esm/chart/utils/index.js +3 -0
- package/esm/chart/utils/path.js +76 -8
- package/esm/chart/utils/point.js +116 -5
- package/esm/chart/utils/scale.js +230 -1
- package/esm/chart/utils/scrubber.js +139 -0
- package/esm/chart/utils/transition.js +221 -0
- package/package.json +11 -9
- package/dts/chart/Point.d.ts +0 -103
- package/dts/chart/Point.d.ts.map +0 -1
- package/dts/chart/line/GradientLine.d.ts +0 -45
- package/dts/chart/line/GradientLine.d.ts.map +0 -1
- package/dts/chart/scrubber/ScrubberBeacon.d.ts +0 -75
- package/dts/chart/scrubber/ScrubberBeacon.d.ts.map +0 -1
- package/dts/chart/text/SmartChartTextGroup.d.ts.map +0 -1
- package/esm/chart/Point.js +0 -111
- package/esm/chart/line/GradientLine.js +0 -62
- package/esm/chart/scrubber/ScrubberBeacon.js +0 -199
|
@@ -1,10 +1,11 @@
|
|
|
1
|
-
import React, { useCallback, useMemo
|
|
1
|
+
import React, { useCallback, useMemo } from 'react';
|
|
2
2
|
import { Platform } from 'react-native';
|
|
3
3
|
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
|
|
4
|
-
import { runOnJS } from 'react-native-reanimated';
|
|
4
|
+
import { runOnJS, useAnimatedReaction, useSharedValue } from 'react-native-reanimated';
|
|
5
5
|
import { Haptics } from '@coinbase/cds-mobile/utils/haptics';
|
|
6
6
|
import { useCartesianChartContext } from '../ChartProvider';
|
|
7
|
-
import {
|
|
7
|
+
import { invertSerializableScale, ScrubberContext } from '../utils';
|
|
8
|
+
import { getPointOnSerializableScale } from '../utils/point';
|
|
8
9
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
9
10
|
/**
|
|
10
11
|
* A component which encapsulates the ScrubberContext.
|
|
@@ -22,25 +23,25 @@ export const ScrubberProvider = _ref => {
|
|
|
22
23
|
throw new Error('ScrubberProvider must be used within a ChartContext');
|
|
23
24
|
}
|
|
24
25
|
const {
|
|
25
|
-
|
|
26
|
-
getXAxis
|
|
27
|
-
series
|
|
26
|
+
getXSerializableScale,
|
|
27
|
+
getXAxis
|
|
28
28
|
} = chartContext;
|
|
29
|
-
const
|
|
29
|
+
const scrubberPosition = useSharedValue(undefined);
|
|
30
|
+
const xAxis = useMemo(() => getXAxis(), [getXAxis]);
|
|
31
|
+
const xScale = useMemo(() => getXSerializableScale(), [getXSerializableScale]);
|
|
30
32
|
const getDataIndexFromX = useCallback(touchX => {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
+
'worklet';
|
|
34
|
+
|
|
33
35
|
if (!xScale || !xAxis) return 0;
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
const bandwidth = (_xScale$bandwidth = xScale.bandwidth == null ? void 0 : xScale.bandwidth()) != null ? _xScale$bandwidth : 0;
|
|
36
|
+
if (xScale.type === 'band') {
|
|
37
|
+
const [domainMin, domainMax] = xScale.domain;
|
|
38
|
+
const categoryCount = domainMax - domainMin + 1;
|
|
38
39
|
let closestIndex = 0;
|
|
39
40
|
let closestDistance = Infinity;
|
|
40
|
-
for (let i = 0; i <
|
|
41
|
-
const xPos =
|
|
41
|
+
for (let i = 0; i < categoryCount; i++) {
|
|
42
|
+
const xPos = getPointOnSerializableScale(i, xScale);
|
|
42
43
|
if (xPos !== undefined) {
|
|
43
|
-
const distance = Math.abs(touchX -
|
|
44
|
+
const distance = Math.abs(touchX - xPos);
|
|
44
45
|
if (distance < closestDistance) {
|
|
45
46
|
closestDistance = distance;
|
|
46
47
|
closestIndex = i;
|
|
@@ -58,7 +59,7 @@ export const ScrubberProvider = _ref => {
|
|
|
58
59
|
let closestDistance = Infinity;
|
|
59
60
|
for (let i = 0; i < numericData.length; i++) {
|
|
60
61
|
const xValue = numericData[i];
|
|
61
|
-
const xPos =
|
|
62
|
+
const xPos = getPointOnSerializableScale(xValue, xScale);
|
|
62
63
|
if (xPos !== undefined) {
|
|
63
64
|
const distance = Math.abs(touchX - xPos);
|
|
64
65
|
if (distance < closestDistance) {
|
|
@@ -70,62 +71,53 @@ export const ScrubberProvider = _ref => {
|
|
|
70
71
|
return closestIndex;
|
|
71
72
|
} else {
|
|
72
73
|
var _domain$min, _domain$max;
|
|
73
|
-
const xValue =
|
|
74
|
+
const xValue = invertSerializableScale(touchX, xScale);
|
|
74
75
|
const dataIndex = Math.round(xValue);
|
|
75
76
|
const domain = xAxis.domain;
|
|
76
77
|
return Math.max((_domain$min = domain.min) != null ? _domain$min : 0, Math.min(dataIndex, (_domain$max = domain.max) != null ? _domain$max : 0));
|
|
77
78
|
}
|
|
78
79
|
}
|
|
79
|
-
}, [
|
|
80
|
-
const
|
|
81
|
-
if (!enableScrubbing || !series || series.length === 0) return;
|
|
82
|
-
const dataIndex = getDataIndexFromX(x);
|
|
83
|
-
if (dataIndex !== scrubberPosition) {
|
|
84
|
-
setScrubberPosition(dataIndex);
|
|
85
|
-
onScrubberPositionChange == null || onScrubberPositionChange(dataIndex);
|
|
86
|
-
}
|
|
87
|
-
}, [enableScrubbing, series, getDataIndexFromX, scrubberPosition, onScrubberPositionChange]);
|
|
88
|
-
const handleInteractionEnd = useCallback(() => {
|
|
89
|
-
if (!enableScrubbing) return;
|
|
90
|
-
setScrubberPosition(undefined);
|
|
91
|
-
onScrubberPositionChange == null || onScrubberPositionChange(undefined);
|
|
92
|
-
}, [enableScrubbing, onScrubberPositionChange]);
|
|
93
|
-
|
|
94
|
-
// Gesture handler callbacks
|
|
95
|
-
const handleOnStartJsThread = useCallback(() => {
|
|
80
|
+
}, [xAxis, xScale]);
|
|
81
|
+
const handleStartEndHaptics = useCallback(() => {
|
|
96
82
|
void Haptics.lightImpact();
|
|
97
|
-
// Could add onScrubStart callback here if needed
|
|
98
83
|
}, []);
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
void Haptics.lightImpact();
|
|
107
|
-
handleOnEndOrCancelledJsThread();
|
|
108
|
-
}, [handleOnEndOrCancelledJsThread]);
|
|
84
|
+
useAnimatedReaction(() => scrubberPosition.value, (currentValue, previousValue) => {
|
|
85
|
+
// Confirm changes here and inside of our gesture handler before calling JS thread
|
|
86
|
+
// To prevent any rerenders
|
|
87
|
+
if (onScrubberPositionChange !== undefined && currentValue !== previousValue) {
|
|
88
|
+
runOnJS(onScrubberPositionChange)(currentValue);
|
|
89
|
+
}
|
|
90
|
+
}, [onScrubberPositionChange]);
|
|
109
91
|
|
|
110
92
|
// Create the long press pan gesture
|
|
111
93
|
const longPressGesture = useMemo(() => Gesture.Pan().activateAfterLongPress(110).shouldCancelWhenOutside(!allowOverflowGestures).onStart(function onStart(event) {
|
|
112
|
-
runOnJS(
|
|
94
|
+
runOnJS(handleStartEndHaptics)();
|
|
113
95
|
|
|
114
96
|
// Android does not trigger onUpdate when the gesture starts. This achieves consistent behavior across both iOS and Android
|
|
115
97
|
if (Platform.OS === 'android') {
|
|
116
|
-
|
|
98
|
+
const newScrubberPosition = getDataIndexFromX(event.x);
|
|
99
|
+
if (newScrubberPosition !== scrubberPosition.value) {
|
|
100
|
+
scrubberPosition.value = newScrubberPosition;
|
|
101
|
+
}
|
|
117
102
|
}
|
|
118
103
|
}).onUpdate(function onUpdate(event) {
|
|
119
|
-
|
|
104
|
+
const newScrubberPosition = getDataIndexFromX(event.x);
|
|
105
|
+
if (newScrubberPosition !== scrubberPosition.value) {
|
|
106
|
+
scrubberPosition.value = newScrubberPosition;
|
|
107
|
+
}
|
|
120
108
|
}).onEnd(function onEnd() {
|
|
121
|
-
|
|
109
|
+
if (enableScrubbing) {
|
|
110
|
+
runOnJS(handleStartEndHaptics)();
|
|
111
|
+
scrubberPosition.value = undefined;
|
|
112
|
+
}
|
|
122
113
|
}).onTouchesCancelled(function onTouchesCancelled() {
|
|
123
|
-
|
|
124
|
-
|
|
114
|
+
if (enableScrubbing) {
|
|
115
|
+
scrubberPosition.value = undefined;
|
|
116
|
+
}
|
|
117
|
+
}), [allowOverflowGestures, handleStartEndHaptics, getDataIndexFromX, scrubberPosition, enableScrubbing]);
|
|
125
118
|
const contextValue = useMemo(() => ({
|
|
126
119
|
enableScrubbing: !!enableScrubbing,
|
|
127
|
-
scrubberPosition
|
|
128
|
-
onScrubberPositionChange: setScrubberPosition
|
|
120
|
+
scrubberPosition
|
|
129
121
|
}), [enableScrubbing, scrubberPosition]);
|
|
130
122
|
const content = /*#__PURE__*/_jsx(ScrubberContext.Provider, {
|
|
131
123
|
value: contextValue,
|
|
@@ -1,11 +1,28 @@
|
|
|
1
|
-
import
|
|
2
|
-
import {
|
|
1
|
+
import { memo, useMemo } from 'react';
|
|
2
|
+
import { runOnJS, useAnimatedReaction, useDerivedValue } from 'react-native-reanimated';
|
|
3
3
|
import { useTheme } from '@coinbase/cds-mobile/hooks/useTheme';
|
|
4
|
+
import { FontSlant, FontWeight, Group, Paint, Paragraph, RoundedRect, Shadow, Skia, TextAlign } from '@shopify/react-native-skia';
|
|
4
5
|
import { useCartesianChartContext } from '../ChartProvider';
|
|
5
|
-
import { getChartInset } from '../utils';
|
|
6
|
+
import { getChartInset, getColorWithOpacity, unwrapAnimatedValue } from '../utils';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Converts a fontWeight from Theme to a Skia FontWeight
|
|
10
|
+
* @note this only works when the fontWeight is a valid number (ie not 'bold')
|
|
11
|
+
* @param theme - The theme to use
|
|
12
|
+
* @param font - The font to use
|
|
13
|
+
* @returns The FontWeight or undefined if the fontWeight is not a valid number
|
|
14
|
+
*/
|
|
15
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
16
|
+
const getFontWeight = (theme, font) => {
|
|
17
|
+
const themeFontWeight = theme.fontWeight[font];
|
|
18
|
+
const numericWeight = typeof themeFontWeight === 'string' ? Number(themeFontWeight) : themeFontWeight;
|
|
19
|
+
const validFontWeights = Object.values(FontWeight).filter(value => typeof value === 'number');
|
|
20
|
+
return numericWeight !== undefined && validFontWeights.includes(numericWeight) ? numericWeight : undefined;
|
|
21
|
+
};
|
|
6
22
|
|
|
7
23
|
/**
|
|
8
24
|
* The supported content types for ChartText.
|
|
25
|
+
* Pass a string for simple text, or a SkParagraph for advanced rich text formatting.
|
|
9
26
|
*/
|
|
10
27
|
|
|
11
28
|
/**
|
|
@@ -15,137 +32,156 @@ import { getChartInset } from '../utils';
|
|
|
15
32
|
/**
|
|
16
33
|
* Vertical alignment options for chart text.
|
|
17
34
|
*/
|
|
18
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
19
|
-
/**
|
|
20
|
-
* Maps horizontal alignment to SVG textAnchor.
|
|
21
|
-
* This abstraction allows us to provide a consistent alignment API across web and mobile platforms,
|
|
22
|
-
* hiding the platform-specific SVG property differences.
|
|
23
|
-
*/
|
|
24
|
-
const getTextAnchor = alignment => {
|
|
25
|
-
switch (alignment) {
|
|
26
|
-
case 'left':
|
|
27
|
-
return 'start';
|
|
28
|
-
case 'center':
|
|
29
|
-
return 'middle';
|
|
30
|
-
case 'right':
|
|
31
|
-
return 'end';
|
|
32
|
-
}
|
|
33
|
-
};
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
* This abstraction allows us to provide a consistent alignment API across web and mobile platforms,
|
|
38
|
-
* hiding the platform-specific SVG property differences.
|
|
39
|
-
*/
|
|
40
|
-
const getAlignmentBaseline = alignment => {
|
|
41
|
-
switch (alignment) {
|
|
42
|
-
case 'top':
|
|
43
|
-
return 'hanging';
|
|
44
|
-
case 'middle':
|
|
45
|
-
return 'central';
|
|
46
|
-
case 'bottom':
|
|
47
|
-
return 'ideographic';
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const ChartTextVisible = /*#__PURE__*/memo(_ref => {
|
|
51
|
-
let {
|
|
52
|
-
children,
|
|
53
|
-
background,
|
|
54
|
-
textAnchor,
|
|
55
|
-
alignmentBaseline,
|
|
56
|
-
fontSize,
|
|
57
|
-
fontWeight,
|
|
58
|
-
fill,
|
|
59
|
-
borderRadius,
|
|
60
|
-
inset: insetInput,
|
|
61
|
-
textDimensions,
|
|
62
|
-
dx,
|
|
63
|
-
dy
|
|
64
|
-
} = _ref;
|
|
65
|
-
const theme = useTheme();
|
|
66
|
-
const inset = useMemo(() => getChartInset(insetInput), [insetInput]);
|
|
67
|
-
const rectHeight = useMemo(() => textDimensions.height + inset.top + inset.bottom, [textDimensions, inset]);
|
|
68
|
-
const rectWidth = useMemo(() => textDimensions.width + inset.left + inset.right, [textDimensions, inset]);
|
|
69
|
-
return /*#__PURE__*/_jsxs(G, {
|
|
70
|
-
children: [background !== 'transparent' && /*#__PURE__*/_jsx(SvgRect, {
|
|
71
|
-
fill: background,
|
|
72
|
-
height: rectHeight,
|
|
73
|
-
rx: borderRadius,
|
|
74
|
-
ry: borderRadius,
|
|
75
|
-
width: rectWidth,
|
|
76
|
-
x: textDimensions.x - inset.left,
|
|
77
|
-
y: textDimensions.y - inset.top
|
|
78
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
79
|
-
alignmentBaseline: alignmentBaseline,
|
|
80
|
-
dx: dx,
|
|
81
|
-
dy: dy,
|
|
82
|
-
fill: fill != null ? fill : theme.color.fgMuted,
|
|
83
|
-
fontSize: fontSize,
|
|
84
|
-
fontWeight: fontWeight,
|
|
85
|
-
textAnchor: textAnchor,
|
|
86
|
-
children: children
|
|
87
|
-
})]
|
|
88
|
-
});
|
|
89
|
-
});
|
|
90
|
-
export const ChartText = /*#__PURE__*/memo(_ref2 => {
|
|
36
|
+
export const ChartText = /*#__PURE__*/memo(_ref => {
|
|
37
|
+
var _elevationShadow$shad, _elevationShadow$shad2, _elevationShadow$shad3, _elevationShadow$shad4, _elevationShadow$shad5, _elevationShadow$shad6, _elevationShadow$shad7;
|
|
91
38
|
let {
|
|
92
39
|
children,
|
|
93
40
|
x,
|
|
94
41
|
y,
|
|
42
|
+
dx = 0,
|
|
43
|
+
dy = 0,
|
|
95
44
|
horizontalAlignment = 'center',
|
|
96
45
|
verticalAlignment = 'middle',
|
|
97
|
-
|
|
98
|
-
dy,
|
|
46
|
+
paragraphAlignment = TextAlign.Left,
|
|
99
47
|
disableRepositioning = false,
|
|
100
48
|
bounds,
|
|
101
|
-
testID,
|
|
102
|
-
fontSize = 12,
|
|
103
|
-
fontWeight,
|
|
104
49
|
color,
|
|
105
|
-
background
|
|
106
|
-
borderRadius,
|
|
50
|
+
background: backgroundProp,
|
|
51
|
+
borderRadius = 4,
|
|
107
52
|
inset: insetInput,
|
|
108
53
|
onDimensionsChange,
|
|
109
|
-
opacity = 1
|
|
110
|
-
|
|
54
|
+
opacity = 1,
|
|
55
|
+
fontFamilies,
|
|
56
|
+
font = 'label2',
|
|
57
|
+
fontSize,
|
|
58
|
+
fontWeight,
|
|
59
|
+
fontStyle: fontStyleProp = FontSlant.Upright,
|
|
60
|
+
elevated
|
|
61
|
+
} = _ref;
|
|
62
|
+
const theme = useTheme();
|
|
111
63
|
const {
|
|
112
64
|
width: chartWidth,
|
|
113
|
-
height: chartHeight
|
|
65
|
+
height: chartHeight,
|
|
66
|
+
fontFamilies: contextFontFamilies,
|
|
67
|
+
fontProvider
|
|
114
68
|
} = useCartesianChartContext();
|
|
115
|
-
const
|
|
116
|
-
const
|
|
117
|
-
const
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
69
|
+
const inset = useMemo(() => getChartInset(insetInput), [insetInput]);
|
|
70
|
+
const background = backgroundProp != null ? backgroundProp : elevated ? theme.color.bgElevation1 : 'transparent';
|
|
71
|
+
const defaultParagraphStyle = useMemo(() => {
|
|
72
|
+
var _ref2;
|
|
73
|
+
return {
|
|
74
|
+
fontFamilies: (_ref2 = fontFamilies != null ? fontFamilies : contextFontFamilies) != null ? _ref2 : [],
|
|
75
|
+
fontSize: fontSize != null ? fontSize : theme.fontSize[font],
|
|
76
|
+
fontStyle: {
|
|
77
|
+
weight: fontWeight != null ? fontWeight : getFontWeight(theme, font),
|
|
78
|
+
slant: fontStyleProp
|
|
79
|
+
},
|
|
80
|
+
color: Skia.Color(color != null ? color : theme.color.fgMuted)
|
|
81
|
+
};
|
|
82
|
+
}, [fontFamilies, contextFontFamilies, fontSize, theme, font, fontWeight, fontStyleProp, color]);
|
|
83
|
+
const paragraph = useDerivedValue(() => {
|
|
84
|
+
const childrenValue = unwrapAnimatedValue(children);
|
|
85
|
+
if (typeof childrenValue !== 'string') {
|
|
86
|
+
return childrenValue;
|
|
127
87
|
}
|
|
88
|
+
const builder = Skia.ParagraphBuilder.Make({
|
|
89
|
+
textAlign: TextAlign.Left
|
|
90
|
+
}, fontProvider);
|
|
91
|
+
builder.pushStyle(defaultParagraphStyle);
|
|
92
|
+
builder.addText(childrenValue);
|
|
93
|
+
builder.pop();
|
|
94
|
+
const para = builder.build();
|
|
95
|
+
para.layout(chartWidth);
|
|
96
|
+
return para;
|
|
97
|
+
}, [children, fontProvider, defaultParagraphStyle, chartWidth]);
|
|
98
|
+
const textDimensions = useDerivedValue(() => {
|
|
99
|
+
const unwrappedParagraph = paragraph.value;
|
|
100
|
+
if (!unwrappedParagraph) return {
|
|
101
|
+
width: 0,
|
|
102
|
+
height: 0
|
|
103
|
+
};
|
|
128
104
|
return {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
width: textSize.width,
|
|
132
|
-
height: textSize.height
|
|
105
|
+
width: unwrappedParagraph.getLongestLine(),
|
|
106
|
+
height: unwrappedParagraph.getHeight()
|
|
133
107
|
};
|
|
134
|
-
}, [
|
|
135
|
-
const
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
108
|
+
}, [paragraph]);
|
|
109
|
+
const backgroundRectSize = useDerivedValue(() => ({
|
|
110
|
+
width: textDimensions.value.width + inset.left + inset.right,
|
|
111
|
+
height: textDimensions.value.height + inset.top + inset.bottom
|
|
112
|
+
}), [textDimensions, inset]);
|
|
113
|
+
|
|
114
|
+
// Calculate background rect position based on alignment
|
|
115
|
+
const backgroundRect = useDerivedValue(() => {
|
|
116
|
+
const horAlignment = unwrapAnimatedValue(horizontalAlignment);
|
|
117
|
+
const verAlignment = unwrapAnimatedValue(verticalAlignment);
|
|
118
|
+
// By default the value is top left
|
|
119
|
+
let rectX = unwrapAnimatedValue(x);
|
|
120
|
+
let rectY = unwrapAnimatedValue(y);
|
|
121
|
+
const rectSize = backgroundRectSize.value;
|
|
122
|
+
|
|
123
|
+
// Adjust for horizontal alignment
|
|
124
|
+
switch (horAlignment) {
|
|
125
|
+
case 'center':
|
|
126
|
+
rectX = rectX - rectSize.width / 2;
|
|
127
|
+
break;
|
|
128
|
+
case 'right':
|
|
129
|
+
rectX = rectX - rectSize.width;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Adjust for vertical alignment
|
|
134
|
+
switch (verAlignment) {
|
|
135
|
+
case 'middle':
|
|
136
|
+
rectY = rectY - rectSize.height / 2;
|
|
137
|
+
break;
|
|
138
|
+
case 'bottom':
|
|
139
|
+
rectY = rectY - rectSize.height;
|
|
140
|
+
break;
|
|
139
141
|
}
|
|
140
|
-
const inset = getChartInset(insetInput);
|
|
141
142
|
return {
|
|
142
|
-
x:
|
|
143
|
-
y:
|
|
144
|
-
width:
|
|
145
|
-
height:
|
|
143
|
+
x: rectX,
|
|
144
|
+
y: rectY,
|
|
145
|
+
width: rectSize.width,
|
|
146
|
+
height: rectSize.height
|
|
146
147
|
};
|
|
147
|
-
}, [
|
|
148
|
-
|
|
148
|
+
}, [x, y, backgroundRectSize, horizontalAlignment, verticalAlignment]);
|
|
149
|
+
|
|
150
|
+
// Paragraph uses top-left positioning
|
|
151
|
+
const textPosition = useDerivedValue(() => {
|
|
152
|
+
const textDims = textDimensions.value;
|
|
153
|
+
|
|
154
|
+
// Calculate horizontal offset based on paragraph alignment
|
|
155
|
+
let horizontalOffset = 0;
|
|
156
|
+
switch (paragraphAlignment) {
|
|
157
|
+
case TextAlign.Center:
|
|
158
|
+
horizontalOffset = -textDims.width / 2;
|
|
159
|
+
break;
|
|
160
|
+
case TextAlign.Right:
|
|
161
|
+
case TextAlign.End:
|
|
162
|
+
horizontalOffset = -textDims.width;
|
|
163
|
+
break;
|
|
164
|
+
default:
|
|
165
|
+
// Left-aligned text needs no offset
|
|
166
|
+
horizontalOffset = 0;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
return {
|
|
170
|
+
x: backgroundRect.value.x + inset.left + horizontalOffset,
|
|
171
|
+
y: backgroundRect.value.y + inset.top,
|
|
172
|
+
width: textDims.width,
|
|
173
|
+
height: textDims.height
|
|
174
|
+
};
|
|
175
|
+
}, [backgroundRect, textDimensions, inset, paragraphAlignment]);
|
|
176
|
+
|
|
177
|
+
// Calculate overflow and repositioning
|
|
178
|
+
const fullChartBounds = useMemo(() => ({
|
|
179
|
+
x: 0,
|
|
180
|
+
y: 0,
|
|
181
|
+
width: chartWidth,
|
|
182
|
+
height: chartHeight
|
|
183
|
+
}), [chartWidth, chartHeight]);
|
|
184
|
+
const overflowAmount = useDerivedValue(() => {
|
|
149
185
|
if (disableRepositioning) {
|
|
150
186
|
return {
|
|
151
187
|
x: 0,
|
|
@@ -153,85 +189,117 @@ export const ChartText = /*#__PURE__*/memo(_ref2 => {
|
|
|
153
189
|
};
|
|
154
190
|
}
|
|
155
191
|
const parentBounds = bounds != null ? bounds : fullChartBounds;
|
|
156
|
-
if (!
|
|
192
|
+
if (!parentBounds || parentBounds.width <= 0 || parentBounds.height <= 0) {
|
|
157
193
|
return {
|
|
158
194
|
x: 0,
|
|
159
195
|
y: 0
|
|
160
196
|
};
|
|
161
197
|
}
|
|
162
|
-
let
|
|
163
|
-
let
|
|
198
|
+
let offsetX = 0;
|
|
199
|
+
let offsetY = 0;
|
|
164
200
|
|
|
165
201
|
// X-axis overflow
|
|
166
|
-
if (
|
|
167
|
-
|
|
168
|
-
} else if (
|
|
169
|
-
|
|
202
|
+
if (backgroundRect.value.x < parentBounds.x) {
|
|
203
|
+
offsetX = parentBounds.x - backgroundRect.value.x;
|
|
204
|
+
} else if (backgroundRect.value.x + backgroundRect.value.width > parentBounds.x + parentBounds.width) {
|
|
205
|
+
offsetX = parentBounds.x + parentBounds.width - (backgroundRect.value.x + backgroundRect.value.width);
|
|
170
206
|
}
|
|
171
207
|
|
|
172
208
|
// Y-axis overflow
|
|
173
|
-
if (
|
|
174
|
-
|
|
175
|
-
} else if (
|
|
176
|
-
|
|
209
|
+
if (backgroundRect.value.y < parentBounds.y) {
|
|
210
|
+
offsetY = parentBounds.y - backgroundRect.value.y;
|
|
211
|
+
} else if (backgroundRect.value.y + backgroundRect.value.height > parentBounds.y + parentBounds.height) {
|
|
212
|
+
offsetY = parentBounds.y + parentBounds.height - (backgroundRect.value.y + backgroundRect.value.height);
|
|
177
213
|
}
|
|
178
214
|
return {
|
|
179
|
-
x,
|
|
180
|
-
y
|
|
215
|
+
x: offsetX,
|
|
216
|
+
y: offsetY
|
|
181
217
|
};
|
|
182
|
-
}, [
|
|
183
|
-
|
|
184
|
-
|
|
218
|
+
}, [backgroundRect, fullChartBounds, bounds, disableRepositioning]);
|
|
219
|
+
|
|
220
|
+
// Final adjusted positions
|
|
221
|
+
const backgroundRectWithOffset = useDerivedValue(() => {
|
|
222
|
+
const offsetX = unwrapAnimatedValue(dx);
|
|
223
|
+
const offsetY = unwrapAnimatedValue(dy);
|
|
185
224
|
return {
|
|
186
|
-
x:
|
|
187
|
-
y:
|
|
188
|
-
width:
|
|
189
|
-
height:
|
|
225
|
+
x: backgroundRect.value.x + overflowAmount.value.x + offsetX,
|
|
226
|
+
y: backgroundRect.value.y + overflowAmount.value.y + offsetY,
|
|
227
|
+
width: backgroundRect.value.width,
|
|
228
|
+
height: backgroundRect.value.height
|
|
190
229
|
};
|
|
191
|
-
}, [
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
230
|
+
}, [backgroundRect, overflowAmount, dx, dy]);
|
|
231
|
+
const textWithOffsetX = useDerivedValue(() => textPosition.value.x + overflowAmount.value.x + unwrapAnimatedValue(dx), [textPosition, overflowAmount, dx]);
|
|
232
|
+
const textWithOffsetY = useDerivedValue(() => textPosition.value.y + overflowAmount.value.y + unwrapAnimatedValue(dy), [textPosition, overflowAmount, dy]);
|
|
233
|
+
useAnimatedReaction(() => backgroundRectWithOffset.value, (rect, previous) => {
|
|
234
|
+
if (onDimensionsChange && rect !== previous) {
|
|
235
|
+
runOnJS(onDimensionsChange)(rect);
|
|
195
236
|
}
|
|
196
|
-
}, [
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
237
|
+
}, [onDimensionsChange]);
|
|
238
|
+
|
|
239
|
+
// Show group if we are ready
|
|
240
|
+
const groupOpacity = useDerivedValue(() => {
|
|
241
|
+
const textSize = textDimensions.value;
|
|
242
|
+
const hasValidContent = paragraph.value && textSize.width > 0 && textSize.height > 0;
|
|
243
|
+
return hasValidContent ? unwrapAnimatedValue(opacity) : 0;
|
|
244
|
+
}, [paragraph, textDimensions, opacity]);
|
|
245
|
+
const backgroundRectHeight = useDerivedValue(() => backgroundRectWithOffset.value.height, [backgroundRectWithOffset]);
|
|
246
|
+
const backgroundRectWidth = useDerivedValue(() => backgroundRectWithOffset.value.width, [backgroundRectWithOffset]);
|
|
247
|
+
const backgroundRectX = useDerivedValue(() => backgroundRectWithOffset.value.x, [backgroundRectWithOffset]);
|
|
248
|
+
const backgroundRectY = useDerivedValue(() => backgroundRectWithOffset.value.y, [backgroundRectWithOffset]);
|
|
249
|
+
const elevationShadow = elevated ? theme.shadow.elevation1 : undefined;
|
|
250
|
+
|
|
251
|
+
// Calculate the paragraph's internal x offset from line metrics based on text alignment
|
|
252
|
+
const paragraphTransform = useDerivedValue(() => {
|
|
253
|
+
if (!paragraph.value || !paragraphAlignment) return [];
|
|
254
|
+
const rects = paragraph.value.getLineMetrics();
|
|
255
|
+
if (rects.length === 0) return [];
|
|
256
|
+
let minOffset;
|
|
257
|
+
switch (paragraphAlignment) {
|
|
258
|
+
case TextAlign.Center:
|
|
259
|
+
// For center-aligned text, account for half the width
|
|
260
|
+
minOffset = Math.min(...rects.map(rect => rect.x - rect.width / 2));
|
|
261
|
+
break;
|
|
262
|
+
case TextAlign.Right:
|
|
263
|
+
case TextAlign.End:
|
|
264
|
+
// For right-aligned text, account for the full width
|
|
265
|
+
minOffset = Math.min(...rects.map(rect => rect.x - rect.width));
|
|
266
|
+
break;
|
|
267
|
+
default:
|
|
268
|
+
// For left-aligned text, use the x position directly
|
|
269
|
+
minOffset = Math.min(...rects.map(rect => rect.x));
|
|
270
|
+
break;
|
|
200
271
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
272
|
+
return [{
|
|
273
|
+
translateX: -minOffset
|
|
274
|
+
}];
|
|
275
|
+
}, [paragraph, paragraphAlignment]);
|
|
276
|
+
|
|
277
|
+
// Opacity on a group doesn't impact the paragraph so we need to apply it to Group
|
|
278
|
+
return /*#__PURE__*/_jsxs(Group, {
|
|
279
|
+
layer: /*#__PURE__*/_jsx(Paint, {
|
|
280
|
+
opacity: groupOpacity
|
|
281
|
+
}),
|
|
282
|
+
children: [background !== 'transparent' && /*#__PURE__*/_jsx(RoundedRect, {
|
|
283
|
+
color: background,
|
|
284
|
+
height: backgroundRectHeight,
|
|
285
|
+
r: borderRadius,
|
|
286
|
+
width: backgroundRectWidth,
|
|
287
|
+
x: backgroundRectX,
|
|
288
|
+
y: backgroundRectY,
|
|
289
|
+
children: elevationShadow && /*#__PURE__*/_jsx(Shadow, {
|
|
290
|
+
blur: Number((_elevationShadow$shad = elevationShadow.shadowRadius) != null ? _elevationShadow$shad : 0),
|
|
291
|
+
color: getColorWithOpacity(String((_elevationShadow$shad2 = elevationShadow.shadowColor) != null ? _elevationShadow$shad2 : '#000000'), Number((_elevationShadow$shad3 = elevationShadow.shadowOpacity) != null ? _elevationShadow$shad3 : 1)),
|
|
292
|
+
dx: Number((_elevationShadow$shad4 = (_elevationShadow$shad5 = elevationShadow.shadowOffset) == null ? void 0 : _elevationShadow$shad5.width) != null ? _elevationShadow$shad4 : 0),
|
|
293
|
+
dy: Number((_elevationShadow$shad6 = (_elevationShadow$shad7 = elevationShadow.shadowOffset) == null ? void 0 : _elevationShadow$shad7.height) != null ? _elevationShadow$shad6 : 0)
|
|
294
|
+
})
|
|
295
|
+
}), /*#__PURE__*/_jsx(Group, {
|
|
296
|
+
transform: paragraphTransform,
|
|
297
|
+
children: /*#__PURE__*/_jsx(Paragraph, {
|
|
298
|
+
paragraph: paragraph,
|
|
299
|
+
width: chartWidth,
|
|
300
|
+
x: textWithOffsetX,
|
|
301
|
+
y: textWithOffsetY
|
|
223
302
|
})
|
|
224
|
-
}), /*#__PURE__*/_jsx(Text, {
|
|
225
|
-
alignmentBaseline: alignmentBaseline,
|
|
226
|
-
dx: dx,
|
|
227
|
-
dy: dy,
|
|
228
|
-
fill: "transparent",
|
|
229
|
-
fontSize: fontSize,
|
|
230
|
-
fontWeight: fontWeight,
|
|
231
|
-
onLayout: onLayout,
|
|
232
|
-
opacity: 0,
|
|
233
|
-
textAnchor: textAnchor,
|
|
234
|
-
children: children
|
|
235
303
|
})]
|
|
236
304
|
});
|
|
237
305
|
});
|