Skip to content

Commit fe9506a

Browse files
authored
Merge pull request #13 from ricale/feature/c04
[C04] 라인차트 특정 아이템 선택 기능 구현
2 parents afbdaa0 + eff29c1 commit fe9506a

File tree

25 files changed

+1679
-1
lines changed

25 files changed

+1679
-1
lines changed

src/navigation/RootNavigation.tsx

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ import { createNativeStackNavigator } from '@react-navigation/native-stack';
33

44
import C01LineChartScreen from 'screens/C01LineChartScreen';
55
import C02LineChartWithOptionsScreen from 'screens/C02LineChartWithOptionsScreen';
6+
import C03LineChartWithLegendScreen from 'screens/C03LineChartWithLegendScreen';
7+
import C04SelectableLineChartScreen from 'screens/C04SelectableLineChartScreen';
68
import HomeScreen from 'screens/HomeScreen';
79

810
import { RootStackParamsList } from './types';
9-
import C03LineChartWithLegendScreen from 'screens/C03LineChartWithLegendScreen';
1011

1112
const Stack = createNativeStackNavigator<RootStackParamsList>();
1213

@@ -30,6 +31,11 @@ function RootNavigation() {
3031
component={C03LineChartWithLegendScreen}
3132
options={{ animation: 'none' }}
3233
/>
34+
<Stack.Screen
35+
name="C04SelectableLineChart"
36+
component={C04SelectableLineChartScreen}
37+
options={{ animation: 'none' }}
38+
/>
3339
</Stack.Navigator>
3440
</NavigationContainer>
3541
);

src/navigation/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ export type RootStackParamsList = {
22
C01LineChart: undefined;
33
C02LineChartWithOptions: undefined;
44
C03LineChartWithLegend: undefined;
5+
C04SelectableLineChart: undefined;
56
Home: undefined;
67
};
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { ViewProps } from 'react-native';
2+
import { LegendOptions } from '../types';
3+
4+
const getContainerStyle = ({
5+
position,
6+
direction,
7+
align,
8+
width,
9+
height,
10+
}: LegendOptions): ViewProps['style'] => {
11+
if (position === 'right' || position === 'left') {
12+
return {
13+
flexDirection: 'column',
14+
justifyContent: 'center',
15+
alignItems: align ?? 'flex-start',
16+
width,
17+
};
18+
}
19+
20+
if (direction === 'column') {
21+
return {
22+
flexDirection: 'column',
23+
justifyContent: height ? 'flex-start' : 'center',
24+
alignItems: align ?? 'center',
25+
flexWrap: height ? 'wrap' : undefined,
26+
width: width ?? '100%',
27+
height,
28+
};
29+
}
30+
31+
return {
32+
flexDirection: 'row',
33+
justifyContent: align ?? 'center',
34+
alignItems: 'center',
35+
flexWrap: 'wrap',
36+
width: width ?? '100%',
37+
height,
38+
};
39+
};
40+
41+
export default getContainerStyle;
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
import { StyleSheet, View } from 'react-native';
2+
3+
import Text from 'components/Text';
4+
5+
import Pressable from '../../Pressable';
6+
import { LegendOptions, LinesOptions, TimeSeries } from '../types';
7+
import { DEFAULT_COLORS } from '../constants';
8+
import getContainerStyle from './getContainerStyle';
9+
10+
const NOT_VISIBLE_COLOR = 'gainsboro';
11+
12+
type LegendProps = LegendOptions & {
13+
series: TimeSeries[];
14+
linesOptions?: LinesOptions;
15+
onPressItem?: (sr: TimeSeries, idx: number) => void;
16+
};
17+
function Legend({
18+
series,
19+
linesOptions,
20+
onPressItem,
21+
22+
enabled = true,
23+
24+
position,
25+
direction = 'row',
26+
align,
27+
28+
width,
29+
height,
30+
31+
itemGap = 4,
32+
itemNotVisibleColor = NOT_VISIBLE_COLOR,
33+
34+
itemPadding,
35+
itemPaddingTop,
36+
itemPaddingLeft,
37+
itemPaddingRight,
38+
itemPaddingBottom,
39+
40+
itemRectWidth = 12,
41+
itemRectHeight = 12,
42+
itemRectBorderRadius = 2,
43+
44+
itemLabelSize,
45+
itemLabelFont,
46+
itemLabelWeight,
47+
itemLabelColor,
48+
itemLabelFormatter,
49+
}: LegendProps) {
50+
const colors = linesOptions?.colors ?? DEFAULT_COLORS;
51+
52+
if (!enabled) {
53+
return null;
54+
}
55+
return (
56+
<View
57+
style={getContainerStyle({ position, direction, align, width, height })}
58+
>
59+
{series.map((sr, i) => (
60+
<Pressable
61+
key={i}
62+
style={[
63+
styles.item,
64+
{
65+
paddingTop: itemPaddingTop ?? itemPadding ?? 2,
66+
paddingLeft: itemPaddingLeft ?? itemPadding ?? 8,
67+
paddingRight: itemPaddingRight ?? itemPadding ?? 8,
68+
paddingBottom: itemPaddingBottom ?? itemPadding ?? 2,
69+
},
70+
]}
71+
onPress={() => onPressItem?.(sr, i)}
72+
>
73+
<View
74+
style={{
75+
width: itemRectWidth,
76+
height: itemRectHeight,
77+
marginRight: itemGap,
78+
borderRadius: itemRectBorderRadius,
79+
backgroundColor: !sr.visible
80+
? itemNotVisibleColor
81+
: sr.color ?? colors[i % colors.length],
82+
}}
83+
/>
84+
<Text
85+
style={{
86+
fontSize: itemLabelSize,
87+
fontFamily: itemLabelFont,
88+
fontWeight: itemLabelWeight,
89+
...(!sr.visible
90+
? { color: itemNotVisibleColor }
91+
: itemLabelColor
92+
? { color: itemLabelColor }
93+
: {}),
94+
}}
95+
>
96+
{itemLabelFormatter?.(sr, i) ?? sr.name ?? `시리즈 ${i + 1}`}
97+
</Text>
98+
</Pressable>
99+
))}
100+
</View>
101+
);
102+
}
103+
104+
const styles = StyleSheet.create({
105+
item: {
106+
flexDirection: 'row',
107+
alignItems: 'center',
108+
},
109+
});
110+
111+
export default Legend;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Line as D3Line } from 'd3';
2+
import { G, Path } from 'react-native-svg';
3+
4+
import { LinesOptions, TimeSeries } from '../types';
5+
import { DEFAULT_COLORS } from '../constants';
6+
7+
type LinesProps = LinesOptions & {
8+
series: TimeSeries[];
9+
lineFunc: D3Line<TimeSeries['data'][0]>;
10+
};
11+
function Lines({
12+
series,
13+
lineFunc,
14+
colors = DEFAULT_COLORS,
15+
lineWidth = 1,
16+
}: LinesProps) {
17+
return (
18+
<G>
19+
{series.map((sr, i) =>
20+
!sr.visible ? null : (
21+
<Path
22+
key={i}
23+
d={lineFunc(sr.data) ?? undefined}
24+
stroke={sr.color ?? colors[i % colors.length]}
25+
strokeLinecap="round"
26+
fill="transparent"
27+
strokeWidth={sr.lineWidth ?? lineWidth}
28+
/>
29+
)
30+
)}
31+
</G>
32+
);
33+
}
34+
35+
export default Lines;
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Circle, G } from 'react-native-svg';
2+
import { ScaleLinear, ScaleTime } from 'd3';
3+
import getColorWithAlpha from 'utils/getColorWithAlpha';
4+
import { SelectedItem, SelectionOptions } from '../../types';
5+
6+
type DotsProps = SelectionOptions['dot'] & {
7+
items: SelectedItem[];
8+
xScale: ScaleTime<number, number, never>;
9+
yScale: ScaleLinear<number, number, never>;
10+
colors: string[];
11+
};
12+
function Dots({
13+
items,
14+
xScale,
15+
yScale,
16+
colors,
17+
18+
enabled = true,
19+
color = seriesColor => seriesColor,
20+
radius = 2,
21+
borderColor = seriesColor => getColorWithAlpha(seriesColor, 0.5),
22+
borderWidth = 3,
23+
}: DotsProps) {
24+
if (!enabled) {
25+
return null;
26+
}
27+
return (
28+
<G>
29+
{items.map(item => (
30+
<Circle
31+
key={item.seriesIndex}
32+
x={xScale(item.date)}
33+
y={yScale(item.value)}
34+
r={radius}
35+
fill={
36+
typeof color === 'function'
37+
? color(colors[item.seriesIndex % colors.length])
38+
: color
39+
}
40+
stroke={
41+
typeof borderColor === 'function'
42+
? borderColor(colors[item.seriesIndex % colors.length])
43+
: borderColor
44+
}
45+
strokeWidth={borderWidth}
46+
/>
47+
))}
48+
</G>
49+
);
50+
}
51+
52+
export default Dots;

0 commit comments

Comments
 (0)