Skip to content

Commit

Permalink
fixup! feat: graph events
Browse files Browse the repository at this point in the history
  • Loading branch information
PeKne committed Jul 18, 2023
1 parent 6e077a5 commit 5ff9fd7
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 30 deletions.
13 changes: 7 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -190,18 +190,15 @@ See this [example `<SelectionDot />` component](./example/src/components/CustomS
### `EventComponent`
A component that is used to render an event.

See this [example `<GraphEvent/>` component](./example/src/components/GraphEvent.tsx).

### `EventTooltipComponent`
An additional event component that is rendered if the `SelectionDot` overlaps an `Event`.

See this [example `<GraphEventTooltip/>` component](./example/src/components/GraphEventTooltip.tsx).
### `onEventHover`
Callback called when an `Event` is hovered on.

> Events related props require `animated` and `enablePanGesture` to be `true`.
Example:

<img src="./img/events.png" align="right" height="200" />

```jsx
<LineGraph
points={priceHistory}
Expand All @@ -212,6 +209,10 @@ Example:
EventComponent={DefaultEventComponent}
/>
```
> Events related props require `animated` and `enablePanGesture` to be `true`.
<img src="./img/events.gif" align="right" height="250" />

## Sponsor

<img src="./img/pinkpanda.png" align="right" height="50">
Expand Down
Original file line number Diff line number Diff line change
@@ -1,36 +1,54 @@
import React, { useEffect } from 'react'
import {
runOnJS,
useDerivedValue,
useSharedValue,
withSpring,
withTiming,
} from 'react-native-reanimated'

import { Circle, Group } from '@shopify/react-native-skia'

import { EventComponentProps } from './LineGraphProps'
import {
Circle,
Group,
TwoPointConicalGradient,
vec,
} from '@shopify/react-native-skia'
import { EventComponentProps } from '../../../src/LineGraphProps'

const EVENT_SIZE = 6
const ACTIVE_EVENT_SIZE = 8
const ENTERING_ANIMATION_DURATION = 750

export function DefaultGraphEvent({
export function GraphEvent({
isGraphActive,
fingerX,
eventX,
eventY,
color,
index,
onEventHover,
}: EventComponentProps) {
const isEventActive = useDerivedValue(
() =>
const isEventActive = useDerivedValue(() => {
// If the finger is on X position of the event.
if (
isGraphActive.value &&
Math.abs(fingerX.value - eventX) < ACTIVE_EVENT_SIZE
)
) {
if (onEventHover) runOnJS(onEventHover)(index, true)

return true
}

if (onEventHover) runOnJS(onEventHover)(index, false)
return false
})

const dotRadius = useDerivedValue(() =>
withSpring(isEventActive.value ? ACTIVE_EVENT_SIZE : EVENT_SIZE)
)

const gradientEndRadius = useDerivedValue(() =>
withSpring(dotRadius.value / 2)
)
const animatedOpacity = useSharedValue(0)

useEffect(() => {
Expand All @@ -42,7 +60,15 @@ export function DefaultGraphEvent({

return (
<Group opacity={animatedOpacity}>
<Circle cx={eventX} cy={eventY} r={dotRadius} color={color} />
<Circle cx={eventX} cy={eventY} r={dotRadius} color={color}>
<TwoPointConicalGradient
start={vec(eventX, eventY)}
startR={0}
end={vec(eventX, eventY)}
endR={gradientEndRadius}
colors={['white', color]}
/>
</Circle>
</Group>
)
}
68 changes: 68 additions & 0 deletions example/src/components/GraphEventTooltip.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import React from 'react'
import Animated, { FadeIn, FadeOut } from 'react-native-reanimated'
import { Dimensions, Platform, StyleSheet, Text, View } from 'react-native'
import { EventTooltipComponentProps } from '../../../src/LineGraphProps'

export type TransactionEventTooltipProps = EventTooltipComponentProps<{}>

const SCREEN_WIDTH = Dimensions.get('screen').width
const ANIMATION_DURATION = 200
const TOOLTIP_LEFT_OFFSET = 25
const TOOLTIP_RIGHT_OFFSET = 145

export const GraphEventTooltip = ({
eventX,
eventY,
}: TransactionEventTooltipProps) => {
const tooltipPositionStyle = {
left:
eventX > SCREEN_WIDTH / 2
? eventX - TOOLTIP_RIGHT_OFFSET
: eventX + TOOLTIP_LEFT_OFFSET,
top: eventY,
}
return (
<Animated.View
style={[styles.tooltip, tooltipPositionStyle]}
entering={FadeIn.duration(ANIMATION_DURATION)}
exiting={FadeOut.duration(ANIMATION_DURATION)}
>
<View style={styles.content}>
<Text style={styles.textNote}>
Here you can display {'\n'}
any information you {'\n'}
want about the event.
</Text>
</View>
</Animated.View>
)
}

const styles = StyleSheet.create({
tooltip: {
position: 'absolute',
backgroundColor: 'white',
paddingHorizontal: 10,

borderRadius: 20,
// add shadow based on platform
...Platform.select({
ios: {
shadowColor: 'black',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.5,
shadowRadius: 3,
},
android: {
elevation: 3,
},
}),
},
content: {
paddingVertical: 12,
},
textNote: {
color: 'gray',
fontSize: 10,
},
})
10 changes: 10 additions & 0 deletions example/src/screens/GraphPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { LineGraph } from 'react-native-graph'
import StaticSafeAreaInsets from 'react-native-static-safe-area-insets'
import type { GraphRange } from '../../../src/LineGraphProps'
import { SelectionDot } from '../components/CustomSelectionDot'
import { GraphEvent } from '../components/GraphEvent'
import { GraphEventTooltip } from '../components/GraphEventTooltip'
import { Toggle } from '../components/Toggle'
import {
generateRandomGraphEvents,
Expand Down Expand Up @@ -34,6 +36,7 @@ export function GraphPage() {
const [enableIndicator, setEnableIndicator] = useState(false)
const [indicatorPulsating, setIndicatorPulsating] = useState(false)
const [enableEvents, setEnableEvents] = useState(false)
const [enableEventTooltip, setEnableEventTooltip] = useState(false)

const [points, setPoints] = useState(POINTS)
const [events, setEvents] = useState(EVENTS)
Expand Down Expand Up @@ -109,6 +112,8 @@ export function GraphPage() {
horizontalPadding={enableIndicator ? 15 : 0}
indicatorPulsating={indicatorPulsating}
events={enableEvents ? events : []}
EventComponent={enableEvents ? GraphEvent : null}
EventTooltipComponent={enableEventTooltip ? GraphEventTooltip : null}
/>

<Button title="Refresh" onPress={refreshData} />
Expand Down Expand Up @@ -162,6 +167,11 @@ export function GraphPage() {
isEnabled={enableEvents}
setIsEnabled={setEnableEvents}
/>
<Toggle
title="Enable event tooltip:"
isEnabled={enableEventTooltip}
setIsEnabled={setEnableEventTooltip}
/>
</ScrollView>

<View style={styles.spacer} />
Expand Down
Binary file added img/events.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file removed img/events.png
Binary file not shown.
5 changes: 2 additions & 3 deletions src/AnimatedLineGraph.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,6 @@ import { getSixDigitHex } from './utils/getSixDigitHex'
import { usePanGesture } from './hooks/usePanGesture'
import { getYForX } from './GetYForX'
import { hexToRgba } from './utils/hexToRgba'
import { DefaultGraphEvent } from './DefaultGraphEvent'
import { useEventTooltipProps } from './hooks/useEventTooltipProps'

const INDICATOR_RADIUS = 7
Expand Down Expand Up @@ -80,8 +79,8 @@ export function AnimatedLineGraph<TEventPayload extends object>({
TopAxisLabel,
BottomAxisLabel,
events,
EventComponent = DefaultGraphEvent,
EventTooltipComponent,
EventComponent = null,
EventTooltipComponent = null,
onEventHover,
...props
}: AnimatedLineGraphProps<TEventPayload>): React.ReactElement {
Expand Down
24 changes: 12 additions & 12 deletions src/hooks/useEventTooltipProps.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useState } from 'react'
import { useCallback, useState } from 'react'

import {
EventTooltipComponentProps,
Expand All @@ -13,18 +13,18 @@ export const useEventTooltipProps = <TEventPayload extends object>(
onEventHover?: () => void
) => {
const [activeEventIndex, setActiveEventIndex] = useState<number | null>(null)
const handleDisplayEventTooltip = (
eventIndex: number,
isDisplayed: boolean
) => {
if (activeEventIndex === eventIndex && !isDisplayed)
setActiveEventIndex(null)
const handleDisplayEventTooltip = useCallback(
(eventIndex: number, isDisplayed: boolean) => {
if (activeEventIndex === eventIndex && !isDisplayed)
setActiveEventIndex(null)

if (activeEventIndex === null && isDisplayed) {
onEventHover?.()
setActiveEventIndex(eventIndex)
}
}
if (activeEventIndex === null && isDisplayed) {
onEventHover?.()
setActiveEventIndex(eventIndex)
}
},
[activeEventIndex, onEventHover]
)
const activeEvent =
eventsWithCords && typeof activeEventIndex === 'number'
? eventsWithCords[activeEventIndex]
Expand Down

0 comments on commit 5ff9fd7

Please sign in to comment.