1- import { useMemo , useRef , useState } from 'react' ;
2- import { Gesture } from 'react-native-gesture-handler' ;
3- import type { SharedValue } from 'react-native-reanimated' ;
4- import { useAnimatedReaction , useAnimatedStyle , useDerivedValue } from 'react-native-reanimated' ;
5- import { scheduleOnRN } from 'react-native-worklets' ;
6- import { TOOLTIP_BAR_GAP } from '@components/Charts/constants' ;
7- import { useChartInteractionState } from './useChartInteractionState' ;
1+ import { useRef , useState } from 'react' ;
2+ import { Gesture } from 'react-native-gesture-handler' ;
3+ import type { SharedValue } from 'react-native-reanimated' ;
4+ import { useAnimatedReaction , useAnimatedStyle , useDerivedValue } from 'react-native-reanimated' ;
5+ import { scheduleOnRN } from 'react-native-worklets' ;
6+ import { TOOLTIP_BAR_GAP } from '@components/Charts/constants' ;
7+ import { useChartInteractionState } from './useChartInteractionState' ;
88
99/**
1010 * Arguments passed to the checkIsOver callback for hit-testing
@@ -34,7 +34,7 @@ type UseChartInteractionsProps = {
3434 */
3535 checkIsOver : ( args : HitTestArgs ) => boolean ;
3636 /** Optional shared value containing bar dimensions used for hit-testing in bar charts */
37- barGeometry ?: SharedValue < { barWidth : number ; chartBottom : number ; yZero : number } > ;
37+ barGeometry ?: SharedValue < { barWidth : number ; chartBottom : number ; yZero : number } > ;
3838} ;
3939
4040/**
@@ -74,29 +74,18 @@ type CartesianActionsHandle = {
7474 * );
7575 * ```
7676 */
77- function useChartInteractions ( { handlePress, checkIsOver, barGeometry} : UseChartInteractionsProps ) {
78- /** Interaction state compatible with Victory Native's internal logic */
79- const { state : chartInteractionState , isActive : isTooltipActiveState } = useChartInteractionState ( { x : 0 , y : { y : 0 } } ) ;
80-
81- /** Ref passed to CartesianChart to allow manual touch injection */
77+ function useChartInteractions ( { handlePress, checkIsOver, barGeometry } : UseChartInteractionsProps ) {
78+ const { state : chartInteractionState , isActive : isTooltipActiveState } = useChartInteractionState ( { x : 0 , y : { y : 0 } } ) ;
8279 const actionsRef = useRef < CartesianActionsHandle > ( null ) ;
8380
84- /** React state for the index of the point currently being interacted with */
8581 const [ activeDataIndex , setActiveDataIndex ] = useState ( - 1 ) ;
86-
87- /** React state indicating if the cursor is currently "hitting" a target based on checkIsOver */
8882 const [ isOverTarget , setIsOverTarget ] = useState ( false ) ;
8983
90- /**
91- * Derived value performing the hit-test on the UI thread.
92- * Runs whenever cursor position or matched data points change.
93- */
9484 const isCursorOverTarget = useDerivedValue ( ( ) => {
9585 const cursorX = chartInteractionState . cursor . x . get ( ) ;
9686 const cursorY = chartInteractionState . cursor . y . get ( ) ;
9787 const targetX = chartInteractionState . x . position . get ( ) ;
9888 const targetY = chartInteractionState . y . y . position . get ( ) ;
99-
10089 const chartBottom = barGeometry ?. get ( ) . chartBottom ?? 0 ;
10190
10291 return checkIsOver ( {
@@ -108,113 +97,85 @@ function useChartInteractions({handlePress, checkIsOver, barGeometry}: UseChartI
10897 } ) ;
10998 } ) ;
11099
111- /** Syncs the matched data index from the UI thread to React state */
112100 useAnimatedReaction (
113101 ( ) => chartInteractionState . matchedIndex . get ( ) ,
114102 ( currentIndex ) => {
115103 scheduleOnRN ( setActiveDataIndex , currentIndex ) ;
116104 } ,
117105 ) ;
118106
119- /** Syncs the hit-test result from the UI thread to React state */
120107 useAnimatedReaction (
121108 ( ) => isCursorOverTarget . get ( ) ,
122109 ( isOver ) => {
123110 scheduleOnRN ( setIsOverTarget , isOver ) ;
124111 } ,
125112 ) ;
126113
127- /**
128- * Hover gesture configuration.
129- * Primarily used for web/desktop to track mouse movement without clicking.
130- */
131- const hoverGesture = useMemo (
132- ( ) =>
133- Gesture . Hover ( )
134- . onBegin ( ( e ) => {
135- 'worklet' ;
136-
137- chartInteractionState . isActive . set ( true ) ;
138- chartInteractionState . cursor . x . set ( e . x ) ;
139- chartInteractionState . cursor . y . set ( e . y ) ;
140- actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
141- } )
142- . onUpdate ( ( e ) => {
143- 'worklet' ;
144-
145- chartInteractionState . cursor . x . set ( e . x ) ;
146- chartInteractionState . cursor . y . set ( e . y ) ;
147- actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
148- } )
149- . onEnd ( ( ) => {
150- 'worklet' ;
151-
152- chartInteractionState . isActive . set ( false ) ;
153- } ) ,
154- [ chartInteractionState ] ,
155- ) ;
114+ // React Compiler automatycznie zmemoizuje te obiekty gestów
115+ const hoverGesture = Gesture . Hover ( )
116+ . onBegin ( ( e ) => {
117+ 'worklet' ;
118+
119+ chartInteractionState . isActive . set ( true ) ;
120+ chartInteractionState . cursor . x . set ( e . x ) ;
121+ chartInteractionState . cursor . y . set ( e . y ) ;
122+ actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
123+ } )
124+ . onUpdate ( ( e ) => {
125+ 'worklet' ;
126+
127+ chartInteractionState . cursor . x . set ( e . x ) ;
128+ chartInteractionState . cursor . y . set ( e . y ) ;
129+ actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
130+ } )
131+ . onEnd ( ( ) => {
132+ 'worklet' ;
133+
134+ chartInteractionState . isActive . set ( false ) ;
135+ } ) ;
156136
157- /**
158- * Tap gesture configuration.
159- * Handles clicks/touches and triggers handlePress if Victory matched a data point.
160- */
161- const tapGesture = useMemo (
162- ( ) =>
163- Gesture . Tap ( ) . onEnd ( ( e ) => {
164- 'worklet' ;
165-
166- // Update cursor position
167- chartInteractionState . cursor . x . set ( e . x ) ;
168- chartInteractionState . cursor . y . set ( e . y ) ;
169-
170- // Let Victory calculate which data point was tapped
171- actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
172- const matchedIndex = chartInteractionState . matchedIndex . get ( ) ;
173-
174- // If Victory matched a valid data point, trigger the press handler
175- if ( matchedIndex >= 0 ) {
176- scheduleOnRN ( handlePress , matchedIndex ) ;
177- }
178- } ) ,
179- [ chartInteractionState , handlePress ] ,
180- ) ;
137+ const tapGesture = Gesture . Tap ( ) . onEnd ( ( e ) => {
138+ 'worklet' ;
181139
182- /** Combined gesture object to be passed to CartesianChart's customGestures prop */
183- const customGestures = useMemo ( ( ) => Gesture . Race ( hoverGesture , tapGesture ) , [ hoverGesture , tapGesture ] ) ;
140+ chartInteractionState . cursor . x . set ( e . x ) ;
141+ chartInteractionState . cursor . y . set ( e . y ) ;
142+
143+ actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
144+ const matchedIndex = chartInteractionState . matchedIndex . get ( ) ;
145+ const isOver = isCursorOverTarget . get ( ) ;
146+
147+ if ( matchedIndex >= 0 && isOver ) {
148+ scheduleOnRN ( handlePress , matchedIndex ) ;
149+ }
150+ } ) ;
151+
152+ const customGestures = Gesture . Race ( hoverGesture , tapGesture ) ;
184153
185- /**
186- * Animated style for positioning a tooltip relative to the matched data point.
187- * Automatically applies vertical offset and centering.
188- * For negative bars, positions tooltip at yZero (top of bar) instead of targetY (bottom of bar).
189- */
190154 const tooltipStyle = useAnimatedStyle ( ( ) => {
155+ const posX = chartInteractionState . x . position . get ( ) ;
191156 const targetY = chartInteractionState . y . y . position . get ( ) ;
192157 const yZero = barGeometry ?. get ( ) . yZero ?? targetY ;
193- // Position tooltip at the top of the bar (min of targetY and yZero)
194158 const barTopY = Math . min ( targetY , yZero ) ;
195159
160+ const isVisible = chartInteractionState . isActive . get ( ) && isCursorOverTarget . get ( ) ;
161+
196162 return {
197163 position : 'absolute' ,
198- left : chartInteractionState . x . position . get ( ) ,
164+ left : posX ,
199165 top : barTopY - TOOLTIP_BAR_GAP ,
200- transform : [ { translateX : '-50%' } , { translateY : '-100%' } ] ,
201- opacity : chartInteractionState . isActive . get ( ) ? 1 : 0 ,
166+ transform : [ { translateX : '-50%' } , { translateY : '-100%' } ] ,
167+ opacity : isVisible ? 1 : 0 ,
202168 } ;
203169 } ) ;
204170
205171 return {
206- /** Ref to be passed to CartesianChart */
207172 actionsRef,
208- /** Gestures to be passed to CartesianChart */
209173 customGestures,
210- /** The currently active data index (React state) */
211174 activeDataIndex,
212- /** Whether the tooltip should currently be rendered and visible */
213- isTooltipActive : isOverTarget && isTooltipActiveState ,
214- /** Animated styles for the tooltip container */
175+ isTooltipActive : isTooltipActiveState && isOverTarget ,
215176 tooltipStyle,
216177 } ;
217178}
218179
219- export { useChartInteractions } ;
220- export type { HitTestArgs } ;
180+ export { useChartInteractions } ;
181+ export type { HitTestArgs } ;
0 commit comments