Skip to content

Commit c0738e9

Browse files
committed
feat(linechart): add line chart
1 parent e9554f6 commit c0738e9

File tree

7 files changed

+606
-21
lines changed

7 files changed

+606
-21
lines changed

example/storybook-nativewind/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
"@react-native-community/slider": "4.2.4",
5252
"@react-stately/collections": "^3.6.0",
5353
"@react-stately/tree": "^3.5.0",
54+
"@shopify/react-native-skia": "^1.3.8",
5455
"expo": "^47.0.0",
5556
"expo-linear-gradient": "^12.3.0",
5657
"expo-status-bar": "~1.4.2",
@@ -64,7 +65,7 @@
6465
"react-dom": "^18.2.0",
6566
"react-native": "0.72.4",
6667
"react-native-gesture-handler": "~2.14.0",
67-
"react-native-reanimated": "~3.6.2",
68+
"react-native-reanimated": "^3.14.0",
6869
"react-native-safe-area-context": "^4.4.1",
6970
"react-native-svg": "13.4.0",
7071
"react-native-vector-icons": "^10.0.0",
@@ -74,7 +75,8 @@
7475
"tailwind-variants": "^0.1.20",
7576
"tailwindcss": "^3.4.1",
7677
"ts-jest": "^29.1.0",
77-
"uuidv4": "^6.2.13"
78+
"uuidv4": "^6.2.13",
79+
"victory-native": "^41.0.1"
7880
},
7981
"devDependencies": {
8082
"@babel/core": "^7.19.3",
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
import type { ComponentMeta } from '@storybook/react-native';
2+
import LineChart from './LineChart';
3+
4+
const AccordionMeta: ComponentMeta<typeof LineChart> = {
5+
title: 'stories/Line Chart',
6+
component: LineChart,
7+
// metaInfo is required for figma generation
8+
// @ts-ignore
9+
metaInfo: {
10+
componentDescription: `The Line Chart component displays data compatable with a two-dimensional cartesian plane`,
11+
},
12+
argTypes: {},
13+
};
14+
15+
export default AccordionMeta;
16+
17+
export { LineChart };
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import React, { useState } from 'react';
2+
3+
import { LineChart } from '@/components/ui/line-chart';
4+
5+
import { Button, View, StyleSheet, useColorScheme } from 'react-native';
6+
7+
const DATA = Array.from({ length: 31 }, (_, i) => ({
8+
x: i,
9+
y: 40 + 30 * Math.random(),
10+
}));
11+
const DATA2 = Array.from({ length: 31 }, (_, i) => ({
12+
x: i,
13+
y: 40 + 30 * Math.random(),
14+
}));
15+
16+
const LineChartBasic = () => {
17+
const [data, setData] = useState(DATA);
18+
const colorMode = useColorScheme();
19+
20+
const labelColor = colorMode === 'dark' ? 'white' : 'black';
21+
const lineColor = colorMode === 'dark' ? 'lightgrey' : 'black';
22+
23+
return (
24+
<View style={style.container}>
25+
<LineChart
26+
width={300}
27+
height={300}
28+
data={data}
29+
outlineColor="lightgreen"
30+
gradientColors={['green', '#90ee9050']}
31+
labelColor={labelColor}
32+
lineColor={lineColor}
33+
topLabelPrefix="$"
34+
/>
35+
<Button
36+
title="Change to Data 1"
37+
onPress={() => {
38+
setData(DATA);
39+
}}
40+
/>
41+
<Button
42+
title="Change to Data 2"
43+
onPress={() => {
44+
setData(DATA2);
45+
}}
46+
/>
47+
</View>
48+
);
49+
};
50+
51+
LineChartBasic.description =
52+
'This is a basic Line Chart example. The Line Chart is a component that lets you display data on a two dimensional cartesian plane';
53+
54+
export default LineChartBasic;
55+
56+
const style = StyleSheet.create({
57+
container: {
58+
height: '100%',
59+
width: '100%',
60+
justifyContent: 'center',
61+
alignItems: 'center',
62+
},
63+
});
Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
import React from 'react';
2+
3+
import {
4+
Circle,
5+
LinearGradient,
6+
matchFont,
7+
vec,
8+
} from '@shopify/react-native-skia';
9+
import { Platform } from 'react-native';
10+
import { useDerivedValue, type SharedValue } from 'react-native-reanimated';
11+
import { Area, CartesianChart, Line, useChartPressState } from 'victory-native';
12+
13+
import { Text as SKText } from '@shopify/react-native-skia';
14+
import { Box } from '@gluestack-ui/themed';
15+
16+
const fontFamily = Platform.select({ ios: 'Helvetica', default: 'sans-serif' });
17+
18+
const legendFontStyle = {
19+
fontFamily,
20+
fontSize: 14,
21+
fontWeight: 'bold',
22+
};
23+
24+
const indicatorFontStyle = {
25+
fontFamily,
26+
fontSize: 25,
27+
fontWeight: 'bold',
28+
};
29+
30+
// @ts-ignore
31+
const legendFont = matchFont(legendFontStyle);
32+
// @ts-ignore
33+
const indicatorFont = matchFont(indicatorFontStyle);
34+
35+
interface ChartData {
36+
[x: string]: number;
37+
}
38+
39+
interface Props {
40+
height: number;
41+
width: number;
42+
data: ChartData[];
43+
outlineColor: string;
44+
gradientColors: string[];
45+
labelColor: string;
46+
lineColor: string;
47+
topLabelPrefix?: string;
48+
topLabelSuffix?: string;
49+
}
50+
51+
export const LineChart = ({
52+
height,
53+
width,
54+
data,
55+
outlineColor,
56+
gradientColors,
57+
labelColor,
58+
lineColor,
59+
topLabelPrefix = '',
60+
topLabelSuffix = '',
61+
}: Props) => {
62+
const { state, isActive } = useChartPressState({ x: 0, y: { y: 0 } });
63+
64+
const value = useDerivedValue(() => {
65+
return topLabelPrefix + state.y.y.value.value.toFixed(2) + topLabelSuffix;
66+
}, [state]);
67+
68+
return (
69+
<Box height={height} width={width}>
70+
<CartesianChart
71+
data={data}
72+
xKey="x"
73+
yKeys={['y']}
74+
domainPadding={{ top: 30 }}
75+
axisOptions={{
76+
font: legendFont,
77+
labelColor,
78+
lineColor,
79+
}}
80+
chartPressState={state}
81+
>
82+
{({ points, chartBounds }) => (
83+
<>
84+
<SKText
85+
x={chartBounds.left + 10}
86+
y={chartBounds.top + indicatorFont.measureText('0').height + 5}
87+
font={indicatorFont}
88+
text={value}
89+
color={labelColor}
90+
style={'fill'}
91+
/>
92+
<Line
93+
points={points.y}
94+
color={outlineColor}
95+
strokeWidth={3}
96+
animate={{ type: 'timing', duration: 500 }}
97+
/>
98+
<Area
99+
points={points.y}
100+
y0={chartBounds.bottom}
101+
animate={{ type: 'timing', duration: 500 }}
102+
>
103+
<LinearGradient
104+
start={vec(chartBounds.bottom, 200)}
105+
end={vec(chartBounds.bottom, chartBounds.bottom)}
106+
colors={gradientColors}
107+
/>
108+
</Area>
109+
110+
{isActive ? (
111+
<ToolTip x={state.x.position} y={state.y.y.position} />
112+
) : null}
113+
</>
114+
)}
115+
</CartesianChart>
116+
</Box>
117+
);
118+
};
119+
120+
function ToolTip({ x, y }: { x: SharedValue<number>; y: SharedValue<number> }) {
121+
return <Circle cx={x} cy={y} r={8} color={'grey'} opacity={0.8} />;
122+
}

example/storybook-v7/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"@react-native-async-storage/async-storage": "1.23.1",
2828
"@react-native-community/datetimepicker": "8.0.1",
2929
"@react-native-community/slider": "4.5.2",
30+
"@shopify/react-native-skia": "1.2.3",
3031
"autoprefixer": "^10.4.19",
3132
"eas-cli": "^9.0.7",
3233
"expo": "^51.0.8",
@@ -40,12 +41,13 @@
4041
"react": "18.2.0",
4142
"react-dom": "18.2.0",
4243
"react-native": "0.74.1",
43-
"react-native-gesture-handler": "~2.14.0",
44+
"react-native-gesture-handler": "~2.16.1",
4445
"react-native-reanimated": "~3.10.1",
4546
"react-native-safe-area-context": "4.10.1",
4647
"react-native-svg": "15.2.0",
4748
"react-native-web": "~0.19.10",
48-
"tailwind-variants": "^0.2.1"
49+
"tailwind-variants": "^0.2.1",
50+
"victory-native": "^41.0.1"
4951
},
5052
"devDependencies": {
5153
"@babel/core": "^7.19.3",

0 commit comments

Comments
 (0)