@bashem/rn-charts 0.0.3 → 0.0.5
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/README.md +1 -1
- package/lib/module/skia/AreaChart/AreaChart.js +60 -51
- package/lib/module/skia/AreaChart/AreaChart.js.map +1 -1
- package/lib/module/skia/AreaChart/useAreaChart.js +18 -14
- package/lib/module/skia/AreaChart/useAreaChart.js.map +1 -1
- package/lib/module/skia/BarChart/BarChart.js +156 -69
- package/lib/module/skia/BarChart/BarChart.js.map +1 -1
- package/lib/module/skia/BarChart/useBarChart.js +53 -41
- package/lib/module/skia/BarChart/useBarChart.js.map +1 -1
- package/lib/module/skia/Common/HorizontalLabelView.js +121 -0
- package/lib/module/skia/Common/HorizontalLabelView.js.map +1 -0
- package/lib/module/skia/Common/VerticalLabelView.js +100 -0
- package/lib/module/skia/Common/VerticalLabelView.js.map +1 -0
- package/lib/module/skia/Common/useComponentLayout.js +13 -0
- package/lib/module/skia/Common/useComponentLayout.js.map +1 -0
- package/lib/module/skia/HeatMap/DateHeatMap.js +472 -0
- package/lib/module/skia/HeatMap/DateHeatMap.js.map +1 -0
- package/lib/module/skia/HeatMap/HeatMap.js +121 -33
- package/lib/module/skia/HeatMap/HeatMap.js.map +1 -1
- package/lib/module/skia/HeatMap/useHeatMap.js +132 -48
- package/lib/module/skia/HeatMap/useHeatMap.js.map +1 -1
- package/lib/module/skia/PieChart/PieChart.js +21 -17
- package/lib/module/skia/PieChart/PieChart.js.map +1 -1
- package/lib/module/skia/PieChart/usePieChart.js +241 -30
- package/lib/module/skia/PieChart/usePieChart.js.map +1 -1
- package/lib/module/skia/Popup.js +7 -5
- package/lib/module/skia/Popup.js.map +1 -1
- package/lib/module/skia/common.js +4 -41
- package/lib/module/skia/common.js.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/lib/typescript/src/skia/AreaChart/AreaChart.d.ts +15 -3
- package/lib/typescript/src/skia/AreaChart/AreaChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/AreaChart/useAreaChart.d.ts +13 -14
- package/lib/typescript/src/skia/AreaChart/useAreaChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/BarChart/BarChart.d.ts +16 -2
- package/lib/typescript/src/skia/BarChart/BarChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/BarChart/useBarChart.d.ts +17 -5
- package/lib/typescript/src/skia/BarChart/useBarChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/Common/HorizontalLabelView.d.ts +33 -0
- package/lib/typescript/src/skia/Common/HorizontalLabelView.d.ts.map +1 -0
- package/lib/typescript/src/skia/Common/VerticalLabelView.d.ts +24 -0
- package/lib/typescript/src/skia/Common/VerticalLabelView.d.ts.map +1 -0
- package/lib/typescript/src/skia/Common/useComponentLayout.d.ts +4 -0
- package/lib/typescript/src/skia/Common/useComponentLayout.d.ts.map +1 -0
- package/lib/typescript/src/skia/HeatMap/DateHeatMap.d.ts +1 -0
- package/lib/typescript/src/skia/HeatMap/DateHeatMap.d.ts.map +1 -0
- package/lib/typescript/src/skia/HeatMap/HeatMap.d.ts +28 -12
- package/lib/typescript/src/skia/HeatMap/HeatMap.d.ts.map +1 -1
- package/lib/typescript/src/skia/HeatMap/useHeatMap.d.ts +15 -4
- package/lib/typescript/src/skia/HeatMap/useHeatMap.d.ts.map +1 -1
- package/lib/typescript/src/skia/PieChart/PieChart.d.ts +5 -1
- package/lib/typescript/src/skia/PieChart/PieChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/PieChart/usePieChart.d.ts +6 -1
- package/lib/typescript/src/skia/PieChart/usePieChart.d.ts.map +1 -1
- package/lib/typescript/src/skia/Popup.d.ts.map +1 -1
- package/lib/typescript/src/skia/common.d.ts +3 -10
- package/lib/typescript/src/skia/common.d.ts.map +1 -1
- package/package.json +11 -8
- package/src/index.tsx +6 -4
- package/src/skia/AreaChart/AreaChart.tsx +85 -62
- package/src/skia/AreaChart/useAreaChart.ts +25 -26
- package/src/skia/BarChart/BarChart.tsx +163 -95
- package/src/skia/BarChart/useBarChart.ts +55 -44
- package/src/skia/Common/HorizontalLabelView.tsx +153 -0
- package/src/skia/Common/VerticalLabelView.tsx +113 -0
- package/src/skia/Common/useComponentLayout.ts +14 -0
- package/src/skia/HeatMap/DateHeatMap.tsx +470 -0
- package/src/skia/HeatMap/HeatMap.tsx +168 -54
- package/src/skia/HeatMap/useHeatMap.ts +139 -65
- package/src/skia/PieChart/PieChart.tsx +16 -11
- package/src/skia/PieChart/usePieChart.ts +316 -66
- package/src/skia/Popup.tsx +38 -36
- package/src/skia/common.ts +8 -46
- package/lib/module/skia/Common/VerticalLabel.js +0 -73
- package/lib/module/skia/Common/VerticalLabel.js.map +0 -1
- package/lib/typescript/src/skia/Common/VerticalLabel.d.ts +0 -17
- package/lib/typescript/src/skia/Common/VerticalLabel.d.ts.map +0 -1
- package/src/skia/Common/VerticalLabel.tsx +0 -91
|
@@ -1,16 +1,20 @@
|
|
|
1
1
|
import { useMemo, useState } from "react";
|
|
2
|
-
import { rect } from "@shopify/react-native-skia";
|
|
2
|
+
import { rect, type SkHostRect } from "@shopify/react-native-skia";
|
|
3
3
|
import { arrayFrom, isDefined } from "../../util/util";
|
|
4
|
-
import type {
|
|
4
|
+
import type { StackValue, BarChartProps } from "./BarChart";
|
|
5
5
|
import { useWindowDimensions } from "react-native";
|
|
6
|
-
import {
|
|
6
|
+
import { getPaddings } from "../common";
|
|
7
|
+
import { useSharedValue } from "react-native-reanimated";
|
|
8
|
+
import { scheduleOnRN } from "react-native-worklets";
|
|
7
9
|
|
|
8
10
|
export default function useBarChart(
|
|
9
11
|
{
|
|
10
12
|
data,
|
|
11
13
|
style,
|
|
12
14
|
maxValue,
|
|
13
|
-
minValue
|
|
15
|
+
minValue,
|
|
16
|
+
yLabels,
|
|
17
|
+
overscanRatio
|
|
14
18
|
}: BarChartProps
|
|
15
19
|
) {
|
|
16
20
|
const { maxValueCalculated, minValueCalculated } = useMemo(() => {
|
|
@@ -47,7 +51,7 @@ export default function useBarChart(
|
|
|
47
51
|
}, [data, maxValue]);
|
|
48
52
|
|
|
49
53
|
const steps = useMemo(() => arrayFrom(1, 0.2), []);
|
|
50
|
-
const [tooltip, setTooltip] = useState<{ centerX: number, centerY: number, data: StackValue; } | undefined>(undefined);
|
|
54
|
+
const [tooltip, setTooltip] = useState<{ centerX: number, centerY: number, rect: SkHostRect, data: StackValue, xLabel?: string; } | undefined>(undefined);
|
|
51
55
|
const [startX, setStartX] = useState<number>(0);
|
|
52
56
|
|
|
53
57
|
const {
|
|
@@ -59,25 +63,26 @@ export default function useBarChart(
|
|
|
59
63
|
|
|
60
64
|
const chartBarWidth = style?.barWidth ?? 100;
|
|
61
65
|
const chartBarSpacing = style?.barSpacing ?? 0;
|
|
62
|
-
|
|
66
|
+
|
|
67
|
+
const [verticalLabelWidth, setVerticalLabelWidth] = useState(style?.verticalLabelStyle?.width ?? 0);
|
|
63
68
|
const chartHeight = style?.height ?? 200;
|
|
64
|
-
const
|
|
65
|
-
const
|
|
66
|
-
const
|
|
69
|
+
const verticalStrokeWidth = style?.verticalLabelStyle?.strokeWidth ?? 0;
|
|
70
|
+
const horizontalStrokeWidth = style?.horizontalLabelStyle?.strokeWidth ?? 0;
|
|
71
|
+
const [bottomLabelHeight, setBottomLabelHeight] = useState(20);
|
|
67
72
|
const { width: windowWidth } = useWindowDimensions();
|
|
68
73
|
const totalWidth = style?.width ?? windowWidth;
|
|
69
74
|
const totalHeight = chartHeight;
|
|
70
|
-
|
|
75
|
+
|
|
71
76
|
const initialSpacing = style?.firstBarLeadingSpacing ?? 0;
|
|
72
77
|
const endSpacing = style?.lastBarTrailingSpacing ?? chartBarSpacing;
|
|
73
78
|
|
|
74
79
|
const scrollAreaWidth = initialSpacing + data.length * chartBarWidth + (Math.max(0, data.length - 1) * chartBarSpacing) + endSpacing;
|
|
75
80
|
const canvasWidth = Math.min(scrollAreaWidth, totalWidth - verticalLabelWidth - paddingRight - paddingLeft);
|
|
76
|
-
const
|
|
81
|
+
const overscanArea = totalWidth * (overscanRatio ?? 0.5);
|
|
82
|
+
const leftBoundary = startX - overscanArea;
|
|
77
83
|
|
|
78
84
|
const rectangles = useMemo(() => {
|
|
79
|
-
let
|
|
80
|
-
let rightBoundary = startX + totalWidth;
|
|
85
|
+
let rightBoundary = startX + totalWidth + overscanArea;
|
|
81
86
|
|
|
82
87
|
let startArrayIndex = Math.floor(Math.max(leftBoundary - initialSpacing, 0) / (chartBarWidth + chartBarSpacing));
|
|
83
88
|
let endArrayIndex = Math.min(Math.ceil(rightBoundary / (chartBarWidth + chartBarSpacing)), data.length);
|
|
@@ -85,19 +90,19 @@ export default function useBarChart(
|
|
|
85
90
|
return data.slice(startArrayIndex, endArrayIndex)
|
|
86
91
|
.map((bar, xIndex) => {
|
|
87
92
|
let previousHeight = 0;
|
|
88
|
-
const x = initialSpacing + (xIndex + startArrayIndex) * (chartBarWidth + chartBarSpacing)
|
|
93
|
+
const x = initialSpacing + (xIndex + startArrayIndex) * (chartBarWidth + chartBarSpacing);
|
|
89
94
|
return {
|
|
90
95
|
bars: bar.values.map((item, yIndex) => {
|
|
91
96
|
const barHeight =
|
|
92
97
|
((item.value - minValueCalculated) /
|
|
93
|
-
(maxValueCalculated - minValueCalculated)) *
|
|
98
|
+
Math.max(maxValueCalculated - minValueCalculated, 1)) *
|
|
94
99
|
chartHeight;
|
|
95
100
|
|
|
96
101
|
const y =
|
|
97
|
-
chartHeight - barHeight - previousHeight
|
|
102
|
+
chartHeight - barHeight - previousHeight;
|
|
98
103
|
|
|
99
104
|
previousHeight += barHeight;
|
|
100
|
-
return rect(x, y, chartBarWidth, barHeight);
|
|
105
|
+
return { rect: rect(x, y, chartBarWidth, barHeight), stackValue: item };
|
|
101
106
|
}),
|
|
102
107
|
label: bar.label,
|
|
103
108
|
dataIndex: xIndex + startArrayIndex,
|
|
@@ -110,7 +115,6 @@ export default function useBarChart(
|
|
|
110
115
|
chartBarSpacing,
|
|
111
116
|
maxValueCalculated,
|
|
112
117
|
minValueCalculated,
|
|
113
|
-
strokeWidth,
|
|
114
118
|
startX
|
|
115
119
|
]);
|
|
116
120
|
|
|
@@ -120,19 +124,18 @@ export default function useBarChart(
|
|
|
120
124
|
return;
|
|
121
125
|
}
|
|
122
126
|
|
|
123
|
-
let
|
|
124
|
-
let
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
startingXIndex = Math.max(0, rectangles[0]!.x);
|
|
129
|
-
} else if (touchedX >= rectangles[0]!.x) {
|
|
130
|
-
xIndex = Math.floor((touchedX - (rectangles[0]!.x + chartBarWidth) - chartBarSpacing) / (chartBarWidth + chartBarSpacing)) + 1;
|
|
131
|
-
startingXIndex = rectangles[xIndex]!.x;
|
|
127
|
+
let firstX = rectangles[0]!.x - startX
|
|
128
|
+
let spaceBetween = touchedX - firstX
|
|
129
|
+
if(spaceBetween < 0) {
|
|
130
|
+
setTooltip(undefined);
|
|
131
|
+
return;
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
let xIndex = Math.floor(spaceBetween / (chartBarWidth + chartBarSpacing));
|
|
135
|
+
let startingXIndex = rectangles[xIndex]!.x - startX;
|
|
136
|
+
|
|
137
|
+
if (xIndex === -1 || (touchedX < rectangles[xIndex]!.x - startX|| touchedX > rectangles[xIndex]!.x - startX + chartBarWidth)) {
|
|
138
|
+
console.log('Touch is outside the bar width, ignoring.', xIndex);
|
|
136
139
|
setTooltip(undefined);
|
|
137
140
|
return;
|
|
138
141
|
}
|
|
@@ -149,7 +152,7 @@ export default function useBarChart(
|
|
|
149
152
|
) {
|
|
150
153
|
const barHeight =
|
|
151
154
|
((categoryData[yIndex]!.value - minValueCalculated) /
|
|
152
|
-
(maxValueCalculated - minValueCalculated)) *
|
|
155
|
+
Math.max(maxValueCalculated - minValueCalculated, 1)) *
|
|
153
156
|
chartHeight;
|
|
154
157
|
yPassed += barHeight;
|
|
155
158
|
lastBarHeight = barHeight;
|
|
@@ -163,28 +166,32 @@ export default function useBarChart(
|
|
|
163
166
|
}
|
|
164
167
|
|
|
165
168
|
setTooltip({
|
|
166
|
-
centerX: startingXIndex + chartBarWidth / 2,
|
|
169
|
+
centerX: startingXIndex + verticalLabelWidth + paddingLeft + chartBarWidth / 2,
|
|
167
170
|
centerY:
|
|
168
|
-
chartHeight - yPassed
|
|
171
|
+
chartHeight - yPassed + paddingTop + lastBarHeight / 2,
|
|
172
|
+
rect: rect(startingXIndex, chartHeight - yPassed, chartBarWidth, lastBarHeight),
|
|
169
173
|
data: categoryData[yIndex - 1]!,
|
|
174
|
+
xLabel: data[xIndex]?.label
|
|
170
175
|
});
|
|
171
176
|
};
|
|
172
177
|
|
|
178
|
+
const offset = useSharedValue(0);
|
|
179
|
+
|
|
173
180
|
function onScroll(translateX: number) {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
181
|
+
'worklet';
|
|
182
|
+
let prev = offset.value;
|
|
183
|
+
let newX = Math.max(0, prev + translateX);
|
|
184
|
+
if (newX + canvasWidth > scrollAreaWidth)
|
|
185
|
+
newX = Math.max(0, scrollAreaWidth - canvasWidth);
|
|
186
|
+
offset.set(newX);
|
|
187
|
+
scheduleOnRN(setTooltip, undefined);
|
|
188
|
+
scheduleOnRN(setStartX, newX);
|
|
182
189
|
}
|
|
183
190
|
|
|
184
191
|
return {
|
|
192
|
+
offset,
|
|
185
193
|
maxValueCalculated,
|
|
186
194
|
minValueCalculated,
|
|
187
|
-
canvasHeight,
|
|
188
195
|
canvasWidth,
|
|
189
196
|
steps,
|
|
190
197
|
scrollAreaWidth,
|
|
@@ -194,17 +201,21 @@ export default function useBarChart(
|
|
|
194
201
|
paddingLeft,
|
|
195
202
|
paddingRight,
|
|
196
203
|
verticalLabelWidth,
|
|
204
|
+
setVerticalLabelWidth,
|
|
197
205
|
chartBarWidth,
|
|
198
206
|
chartBarSpacing,
|
|
199
|
-
strokeWidth,
|
|
200
207
|
rectangles,
|
|
201
208
|
tooltip,
|
|
202
209
|
bottomLabelHeight,
|
|
203
|
-
|
|
210
|
+
setBottomLabelHeight,
|
|
204
211
|
setTooltip,
|
|
205
212
|
touchHandler,
|
|
206
213
|
onScroll,
|
|
214
|
+
startX,
|
|
207
215
|
totalHeight,
|
|
208
|
-
totalWidth
|
|
216
|
+
totalWidth,
|
|
217
|
+
yLabels,
|
|
218
|
+
verticalStrokeWidth,
|
|
219
|
+
horizontalStrokeWidth
|
|
209
220
|
};
|
|
210
221
|
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { View, type LayoutChangeEvent } from "react-native";
|
|
2
|
+
import { getPaddings, type CommonStyle } from '../common';
|
|
3
|
+
import React, { useEffect, useState } from "react";
|
|
4
|
+
import { Canvas, Group, Line, type AnimatedProp, type Transforms3d } from "@shopify/react-native-skia";
|
|
5
|
+
import Animated, { useAnimatedStyle, type SharedValue } from "react-native-reanimated";
|
|
6
|
+
|
|
7
|
+
export interface HorizontalLabelStyle {
|
|
8
|
+
height?: number;
|
|
9
|
+
backgroundColor?: string;
|
|
10
|
+
strokeWidth?: number;
|
|
11
|
+
strokeColor?: string;
|
|
12
|
+
strokePosition?: "top" | "bottom";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface HorizontalLabelStyleExtended extends HorizontalLabelStyle {
|
|
16
|
+
viewPosition?: "top" | "bottom";
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface HorizontalLabelViewStyle extends CommonStyle {
|
|
20
|
+
width: number;
|
|
21
|
+
left?: number;
|
|
22
|
+
horizontalLabelStyle?: HorizontalLabelStyleExtended;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
interface HorizontalLabelProps<T> {
|
|
26
|
+
labels: T[];
|
|
27
|
+
positions: number[];
|
|
28
|
+
style: HorizontalLabelViewStyle;
|
|
29
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
30
|
+
children?: (index: number, data: T) => React.JSX.Element | undefined;
|
|
31
|
+
labelSkiaView?: (
|
|
32
|
+
yPosition: number,
|
|
33
|
+
index: number,
|
|
34
|
+
data: T,
|
|
35
|
+
) => React.JSX.Element | undefined;
|
|
36
|
+
transform?: AnimatedProp<Transforms3d>;
|
|
37
|
+
xOffset?: SharedValue<number>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function HorizontalLabelView<T>({
|
|
41
|
+
labels,
|
|
42
|
+
positions,
|
|
43
|
+
style,
|
|
44
|
+
onLayout,
|
|
45
|
+
children,
|
|
46
|
+
labelSkiaView,
|
|
47
|
+
transform,
|
|
48
|
+
xOffset
|
|
49
|
+
}: HorizontalLabelProps<T>) {
|
|
50
|
+
const {
|
|
51
|
+
width,
|
|
52
|
+
backgroundColor,
|
|
53
|
+
left,
|
|
54
|
+
horizontalLabelStyle = {}
|
|
55
|
+
} = style;
|
|
56
|
+
|
|
57
|
+
const {
|
|
58
|
+
height,
|
|
59
|
+
strokeColor = "white",
|
|
60
|
+
strokePosition = "top",
|
|
61
|
+
strokeWidth = 0,
|
|
62
|
+
} = horizontalLabelStyle;
|
|
63
|
+
const { paddingLeft = 0 } = getPaddings(style);
|
|
64
|
+
|
|
65
|
+
const [maxHeight, setMaxHeight] = useState(height);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
setMaxHeight(height);
|
|
68
|
+
}, [height]);
|
|
69
|
+
|
|
70
|
+
return (
|
|
71
|
+
<View
|
|
72
|
+
style={{
|
|
73
|
+
left,
|
|
74
|
+
width: width,
|
|
75
|
+
height: (maxHeight ?? 0),
|
|
76
|
+
backgroundColor,
|
|
77
|
+
flexDirection: "row-reverse",
|
|
78
|
+
justifyContent: "flex-end",
|
|
79
|
+
overflow: "hidden"
|
|
80
|
+
}}
|
|
81
|
+
onLayout={(event) => {
|
|
82
|
+
onLayout?.(event);
|
|
83
|
+
}}
|
|
84
|
+
>
|
|
85
|
+
<View style={{ position: "relative", height: (maxHeight ?? 0), top: strokeWidth, paddingVertical: 0 }}>
|
|
86
|
+
{labels.map((label, index) => {
|
|
87
|
+
return <LabelViewWrapper
|
|
88
|
+
key={index}
|
|
89
|
+
leftPosition={positions[index]}
|
|
90
|
+
xOffset={xOffset}
|
|
91
|
+
onLayout={(event) => {
|
|
92
|
+
let height = event.nativeEvent.layout.height;
|
|
93
|
+
if (horizontalLabelStyle.height !== undefined)
|
|
94
|
+
setMaxHeight(horizontalLabelStyle.height);
|
|
95
|
+
else {
|
|
96
|
+
if (index === 0) {
|
|
97
|
+
setMaxHeight(height + strokeWidth);
|
|
98
|
+
} else {
|
|
99
|
+
setMaxHeight(prevHeight => Math.max(height + strokeWidth, prevHeight ?? 0));
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}}
|
|
103
|
+
>
|
|
104
|
+
{children?.(index, label)}
|
|
105
|
+
</LabelViewWrapper>;
|
|
106
|
+
})}
|
|
107
|
+
</View>
|
|
108
|
+
{(labelSkiaView || strokeWidth > 0) &&
|
|
109
|
+
<Canvas style={{ position: "absolute", width, height: labelSkiaView ? (maxHeight ?? 0) : strokeWidth, }}>
|
|
110
|
+
<Group transform={transform}>
|
|
111
|
+
{labelSkiaView && labels.map((label, index) => {
|
|
112
|
+
let yPosition = positions[index];
|
|
113
|
+
if (yPosition === undefined) return null;
|
|
114
|
+
return <Group key={index}>{labelSkiaView(yPosition, index, label)}</Group>;
|
|
115
|
+
})}
|
|
116
|
+
{strokeWidth > 0 &&
|
|
117
|
+
<Line
|
|
118
|
+
p1={{ x: paddingLeft, y: strokePosition === "top" ? strokeWidth / 2 : (maxHeight ?? height ?? 0) - strokeWidth / 2 }}
|
|
119
|
+
p2={{ x: width, y: strokePosition === "top" ? strokeWidth / 2 : (maxHeight ?? height ?? 0) - strokeWidth / 2 }}
|
|
120
|
+
color={strokeColor}
|
|
121
|
+
strokeWidth={strokeWidth}
|
|
122
|
+
/>
|
|
123
|
+
}
|
|
124
|
+
</Group>
|
|
125
|
+
</Canvas>
|
|
126
|
+
}
|
|
127
|
+
</View >
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
interface LabelViewWrapperProps {
|
|
132
|
+
leftPosition?: number;
|
|
133
|
+
xOffset?: SharedValue<number>;
|
|
134
|
+
onLayout: ((event: LayoutChangeEvent) => void) | undefined;
|
|
135
|
+
children?: React.JSX.Element | undefined;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
function LabelViewWrapper(props: LabelViewWrapperProps) {
|
|
139
|
+
const labelContainerStyle = useAnimatedStyle(() => {
|
|
140
|
+
return {
|
|
141
|
+
position: "absolute", left: props.leftPosition ? props.leftPosition - (props.xOffset?.get() ?? 0) : undefined,
|
|
142
|
+
};
|
|
143
|
+
}, [props.leftPosition]);
|
|
144
|
+
|
|
145
|
+
return <Animated.View
|
|
146
|
+
style={labelContainerStyle}
|
|
147
|
+
onLayout={(event) => props.onLayout?.(event)}
|
|
148
|
+
>
|
|
149
|
+
{props.children}
|
|
150
|
+
</Animated.View >;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export default HorizontalLabelView;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { View, type LayoutChangeEvent } from "react-native";
|
|
2
|
+
import { Canvas, Group, Line } from '@shopify/react-native-skia';
|
|
3
|
+
import { getPaddings, type CommonStyle } from '../common';
|
|
4
|
+
import React, { useMemo, useState } from "react";
|
|
5
|
+
|
|
6
|
+
export interface VerticalLabelStyle {
|
|
7
|
+
width?: number;
|
|
8
|
+
backgroundColor?: string;
|
|
9
|
+
strokeWidth?: number;
|
|
10
|
+
strokeColor?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
interface VerticalLabelViewStyles extends CommonStyle {
|
|
14
|
+
height: number;
|
|
15
|
+
top?: number;
|
|
16
|
+
verticalLabelStyle?: VerticalLabelStyle;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
interface VerticalLabelProps {
|
|
20
|
+
labelPercentages: number[];
|
|
21
|
+
styles: VerticalLabelViewStyles;
|
|
22
|
+
onLayout?: (event: LayoutChangeEvent) => void;
|
|
23
|
+
children: (percentage: number, index: number) => React.JSX.Element | undefined;
|
|
24
|
+
labelSkiaView?: (
|
|
25
|
+
percentage: number,
|
|
26
|
+
yPosition: number,
|
|
27
|
+
index: number
|
|
28
|
+
) => React.JSX.Element | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function VerticalLabelView({
|
|
32
|
+
labelSkiaView,
|
|
33
|
+
labelPercentages,
|
|
34
|
+
styles,
|
|
35
|
+
onLayout,
|
|
36
|
+
children
|
|
37
|
+
}: VerticalLabelProps) {
|
|
38
|
+
const {
|
|
39
|
+
top,
|
|
40
|
+
height,
|
|
41
|
+
backgroundColor,
|
|
42
|
+
verticalLabelStyle = {}
|
|
43
|
+
} = styles;
|
|
44
|
+
const {
|
|
45
|
+
width,
|
|
46
|
+
strokeWidth = 0,
|
|
47
|
+
strokeColor = 'white',
|
|
48
|
+
} = verticalLabelStyle;
|
|
49
|
+
|
|
50
|
+
const { paddingTop = 0, paddingBottom = 0 } = getPaddings(styles);
|
|
51
|
+
const [maxWidth, setMaxWidth] = useState(width);
|
|
52
|
+
useMemo(() => {
|
|
53
|
+
setMaxWidth(width);
|
|
54
|
+
}, [width]);
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<View
|
|
58
|
+
style={{
|
|
59
|
+
width: (maxWidth ?? 0),
|
|
60
|
+
top,
|
|
61
|
+
height,
|
|
62
|
+
backgroundColor,
|
|
63
|
+
flexDirection: "row-reverse",
|
|
64
|
+
justifyContent: "flex-end"
|
|
65
|
+
}}
|
|
66
|
+
onLayout={(event) => {
|
|
67
|
+
onLayout?.(event);
|
|
68
|
+
}}
|
|
69
|
+
>
|
|
70
|
+
<View style={{ position: "relative", width: (maxWidth ?? 0) - strokeWidth, paddingVertical: 0, height: height }}>
|
|
71
|
+
{labelPercentages.map((percentage, index) => {
|
|
72
|
+
return <View
|
|
73
|
+
key={percentage}
|
|
74
|
+
style={{ position: "absolute", top: (1 - percentage) * height, backgroundColor: "purple" }}
|
|
75
|
+
onLayout={(event) => {
|
|
76
|
+
let width = event.nativeEvent.layout.width + strokeWidth;
|
|
77
|
+
if (verticalLabelStyle.width !== undefined)
|
|
78
|
+
setMaxWidth(verticalLabelStyle.width);
|
|
79
|
+
else {
|
|
80
|
+
if (index === 0) {
|
|
81
|
+
setMaxWidth(width + strokeWidth);
|
|
82
|
+
} else {
|
|
83
|
+
setMaxWidth(prevWidth => Math.max(width + strokeWidth, prevWidth ?? 0));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}}
|
|
87
|
+
>
|
|
88
|
+
{children(percentage, index)}
|
|
89
|
+
</View>;
|
|
90
|
+
})}
|
|
91
|
+
</View>
|
|
92
|
+
|
|
93
|
+
{(labelSkiaView || strokeWidth > 0) &&
|
|
94
|
+
<Canvas style={{ position: "absolute", left: 0, width, height, }}>
|
|
95
|
+
{labelSkiaView && labelPercentages.map((percentage, index) => {
|
|
96
|
+
return <Group key={percentage}>{labelSkiaView(percentage, (1 - percentage) * height, index)}</Group>;
|
|
97
|
+
})}
|
|
98
|
+
|
|
99
|
+
{strokeWidth > 0 &&
|
|
100
|
+
<Line
|
|
101
|
+
p1={{ x: (maxWidth ?? width ?? 0) - strokeWidth / 2, y: paddingTop }}
|
|
102
|
+
p2={{ x: (maxWidth ?? width ?? 0) - strokeWidth / 2, y: height - paddingBottom }}
|
|
103
|
+
color={strokeColor}
|
|
104
|
+
strokeWidth={strokeWidth}
|
|
105
|
+
/>
|
|
106
|
+
}
|
|
107
|
+
</Canvas>
|
|
108
|
+
}
|
|
109
|
+
</View>
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export default VerticalLabelView;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { useCallback, useState } from "react";
|
|
2
|
+
import type { LayoutChangeEvent, LayoutRectangle } from "react-native";
|
|
3
|
+
|
|
4
|
+
function useComponentLayout(): [LayoutRectangle | undefined, (event: LayoutChangeEvent) => void] {
|
|
5
|
+
const [size, setSize] = useState<LayoutRectangle | undefined>(undefined);
|
|
6
|
+
|
|
7
|
+
const onLayout = useCallback((event: LayoutChangeEvent) => {
|
|
8
|
+
setSize(event.nativeEvent.layout);
|
|
9
|
+
}, []);
|
|
10
|
+
|
|
11
|
+
return [size, onLayout];
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export default useComponentLayout;
|