Skip to content

Commit 9d66c44

Browse files
authored
feat: ability to customize SelectionDot component (#35)
* feat: ability to customize SelectionDot component * feat: ability to customize SelectionDot component * fix: pass props directly to view
1 parent eb29e4b commit 9d66c44

7 files changed

Lines changed: 153 additions & 43 deletions

File tree

README.md

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -122,14 +122,16 @@ Example:
122122

123123
---
124124

125-
### `selectionDotShadowColor`
125+
### `SelectionDot`
126126

127127
<img src="./img/selection-dot.jpeg" align="right" height="250" />
128128

129-
The color of the selection dot.
129+
Used to render the selection dot.
130130

131131
> Requires `animated` and `enablePanGesture` to be `true`.
132132
133+
If `SelectionDot` is missing or `undefined`, a default one is provided with an outer ring and light shadow.
134+
133135
Example:
134136

135137
```jsx
@@ -138,10 +140,12 @@ Example:
138140
animated={true}
139141
color="#4484B2"
140142
enablePanGesture={true}
141-
selectionDotShadowColor="#333333"
143+
SelectionDot={CustomSelectionDot}
142144
/>
143145
```
144146

147+
See this [example `<SelectionDot />` component](./example/src/components/CustomSelectionDot.tsx).
148+
145149
## Sponsor
146150

147151
<img src="./img/pinkpanda.png" align="right" height="50">
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
/**
2+
* An example for a custom SelectionDot component.
3+
*
4+
* Usage:
5+
*
6+
* ```jsx
7+
* <LineGraph
8+
* points={priceHistory}
9+
* animated={true}
10+
* enablePanGesture={true}
11+
* SelectionDot={CustomSelectionDot}
12+
* />
13+
* ```
14+
*
15+
* This example has removed the outer ring and light
16+
* shadow from the default one to make it more flat.
17+
*/
18+
import React, { useCallback } from 'react'
19+
import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'
20+
import { runSpring, useValue, Circle } from '@shopify/react-native-skia'
21+
import type { SelectionDotProps } from 'react-native-graph'
22+
23+
export function SelectionDot({
24+
isActive,
25+
color,
26+
circleX,
27+
circleY,
28+
}: SelectionDotProps): React.ReactElement {
29+
const circleRadius = useValue(0)
30+
31+
const setIsActive = useCallback(
32+
(active: boolean) => {
33+
runSpring(circleRadius, active ? 5 : 0, {
34+
mass: 1,
35+
stiffness: 1000,
36+
damping: 50,
37+
velocity: 0,
38+
})
39+
},
40+
[circleRadius]
41+
)
42+
43+
useAnimatedReaction(
44+
() => isActive.value,
45+
(active) => {
46+
runOnJS(setIsActive)(active)
47+
},
48+
[isActive, setIsActive]
49+
)
50+
51+
return <Circle cx={circleX} cy={circleY} r={circleRadius} color={color} />
52+
}

example/src/screens/GraphPage.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React, { useCallback, useState } from 'react'
22
import { View, StyleSheet, Text, Button } from 'react-native'
33
import { LineGraph } from 'react-native-graph'
44
import StaticSafeAreaInsets from 'react-native-static-safe-area-insets'
5+
import { SelectionDot } from '../components/CustomSelectionDot'
56
import { Toggle } from '../components/Toggle'
67
import {
78
generateRandomGraphData,
@@ -51,7 +52,7 @@ export function GraphPage() {
5152
enablePanGesture={enablePanGesture}
5253
enableFadeInMask={enableFadeInEffect}
5354
onGestureStart={() => hapticFeedback('impactLight')}
54-
selectionDotShadowColor={colors.foreground}
55+
SelectionDot={SelectionDot}
5556
/>
5657

5758
<Button title="Refresh" onPress={refreshData} />

src/AnimatedLineGraph.tsx

Lines changed: 11 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,11 @@ import {
1010
useValue,
1111
useComputedValue,
1212
vec,
13-
Circle,
1413
Group,
15-
Shadow,
1614
PathCommand,
1715
} from '@shopify/react-native-skia'
1816
import type { AnimatedLineGraphProps } from './LineGraphProps'
17+
import { SelectionDot as DefaultSelectionDot } from './SelectionDot'
1918
import { createGraphPath } from './CreateGraphPath'
2019
import Reanimated, {
2120
runOnJS,
@@ -38,9 +37,9 @@ export function AnimatedLineGraph({
3837
onPointSelected,
3938
onGestureStart,
4039
onGestureEnd,
40+
SelectionDot = DefaultSelectionDot,
4141
TopAxisLabel,
4242
BottomAxisLabel,
43-
selectionDotShadowColor,
4443
...props
4544
}: AnimatedLineGraphProps): React.ReactElement {
4645
const [width, setWidth] = useState(0)
@@ -163,11 +162,6 @@ export function AnimatedLineGraph({
163162
const circleX = useValue(0)
164163
const circleY = useValue(0)
165164
const pathEnd = useValue(0)
166-
const circleRadius = useValue(0)
167-
const circleStrokeRadius = useComputedValue(
168-
() => circleRadius.current * 6,
169-
[circleRadius]
170-
)
171165

172166
const setFingerX = useCallback(
173167
(fingerX: number) => {
@@ -188,18 +182,11 @@ export function AnimatedLineGraph({
188182
)
189183
const setIsActive = useCallback(
190184
(active: boolean) => {
191-
runSpring(circleRadius, active ? 5 : 0, {
192-
mass: 1,
193-
stiffness: 1000,
194-
damping: 50,
195-
velocity: 0,
196-
})
197185
if (!active) pathEnd.current = 1
198-
199186
if (active) onGestureStart?.()
200187
else onGestureEnd?.()
201188
},
202-
[circleRadius, onGestureEnd, onGestureStart, pathEnd]
189+
[onGestureEnd, onGestureStart, pathEnd]
203190
)
204191
useAnimatedReaction(
205192
() => x.value,
@@ -260,24 +247,14 @@ export function AnimatedLineGraph({
260247
</Path>
261248
</Group>
262249

263-
{enablePanGesture && (
264-
<Group>
265-
<Circle
266-
opacity={0.05}
267-
cx={circleX}
268-
cy={circleY}
269-
r={circleStrokeRadius}
270-
color={selectionDotShadowColor}
271-
/>
272-
<Circle
273-
cx={circleX}
274-
cy={circleY}
275-
r={circleRadius}
276-
color={color}
277-
>
278-
<Shadow dx={0} dy={0} color="rgba(0,0,0,0.5)" blur={4} />
279-
</Circle>
280-
</Group>
250+
{SelectionDot != null && (
251+
<SelectionDot
252+
isActive={isActive}
253+
color={color}
254+
lineThickness={lineThickness}
255+
circleX={circleX}
256+
circleY={circleY}
257+
/>
281258
)}
282259
</Canvas>
283260
</View>

src/LineGraphProps.ts

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,21 @@
11
import type React from 'react'
22
import type { ViewProps } from 'react-native'
3+
import type { SharedValue } from 'react-native-reanimated'
4+
import type { SkiaMutableValue } from '@shopify/react-native-skia'
35

46
export interface GraphPoint {
57
value: number
68
date: Date
79
}
810

11+
export interface SelectionDotProps {
12+
isActive: SharedValue<boolean>
13+
color: BaseLineGraphProps['color']
14+
lineThickness: BaseLineGraphProps['lineThickness']
15+
circleX: SkiaMutableValue<number>
16+
circleY: SkiaMutableValue<number>
17+
}
18+
919
interface BaseLineGraphProps extends ViewProps {
1020
/**
1121
* All points to be marked in the graph. Coordinate system will adjust to scale automatically.
@@ -36,11 +46,6 @@ export type AnimatedLineGraphProps = BaseLineGraphProps & {
3646
*/
3747
enablePanGesture?: boolean
3848

39-
/**
40-
* The color of the selection dot when the user is panning the graph.
41-
*/
42-
selectionDotShadowColor?: string
43-
4449
/**
4550
* Called for each point while the user is scrubbing/panning through the graph
4651
*/
@@ -54,10 +59,16 @@ export type AnimatedLineGraphProps = BaseLineGraphProps & {
5459
*/
5560
onGestureEnd?: () => void
5661

62+
/**
63+
* The element that renders the selection dot
64+
*/
65+
SelectionDot?: React.ComponentType<SelectionDotProps> | null
66+
5767
/**
5868
* The element that gets rendered above the Graph (usually the "max" point/value of the Graph)
5969
*/
6070
TopAxisLabel?: () => React.ReactElement | null
71+
6172
/**
6273
* The element that gets rendered below the Graph (usually the "min" point/value of the Graph)
6374
*/

src/SelectionDot.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React, { useCallback } from 'react'
2+
import { runOnJS, useAnimatedReaction } from 'react-native-reanimated'
3+
import {
4+
runSpring,
5+
useValue,
6+
useComputedValue,
7+
Circle,
8+
Group,
9+
Shadow,
10+
} from '@shopify/react-native-skia'
11+
import type { SelectionDotProps } from './LineGraphProps'
12+
13+
export function SelectionDot({
14+
isActive,
15+
color,
16+
circleX,
17+
circleY,
18+
}: SelectionDotProps): React.ReactElement {
19+
const circleRadius = useValue(0)
20+
const circleStrokeRadius = useComputedValue(
21+
() => circleRadius.current * 6,
22+
[circleRadius]
23+
)
24+
25+
const setIsActive = useCallback(
26+
(active: boolean) => {
27+
runSpring(circleRadius, active ? 5 : 0, {
28+
mass: 1,
29+
stiffness: 1000,
30+
damping: 50,
31+
velocity: 0,
32+
})
33+
},
34+
[circleRadius]
35+
)
36+
37+
useAnimatedReaction(
38+
() => isActive.value,
39+
(active) => {
40+
runOnJS(setIsActive)(active)
41+
},
42+
[isActive, setIsActive]
43+
)
44+
45+
return (
46+
<Group>
47+
<Circle
48+
opacity={0.05}
49+
cx={circleX}
50+
cy={circleY}
51+
r={circleStrokeRadius}
52+
color="#333333"
53+
/>
54+
<Circle cx={circleX} cy={circleY} r={circleRadius} color={color}>
55+
<Shadow dx={0} dy={0} color="rgba(0,0,0,0.5)" blur={4} />
56+
</Circle>
57+
</Group>
58+
)
59+
}

src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,7 @@
1+
export * from './SelectionDot'
12
export * from './LineGraph'
3+
export type {
4+
GraphPoint,
5+
LineGraphProps,
6+
SelectionDotProps,
7+
} from './LineGraphProps'

0 commit comments

Comments
 (0)