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' ;
8-
9- /**
10- * Arguments passed to the checkIsOver callback for hit-testing
11- */
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' ;
8+
9+ const INITIAL_INTERACTION_STATE = { x : 0 , y : { y : 0 } } ;
10+
1211type HitTestArgs = {
13- /** Current raw X position of the cursor */
1412 cursorX : number ;
15- /** Current raw Y position of the cursor */
1613 cursorY : number ;
17- /** Calculated X position of the matched data point */
1814 targetX : number ;
19- /** Calculated Y position of the matched data point */
2015 targetY : number ;
21- /** The bottom boundary of the chart area */
2216 chartBottom : number ;
2317} ;
2418
25- /**
26- * Configuration for the chart interactions hook
27- */
2819type UseChartInteractionsProps = {
29- /** Callback triggered when a valid data point is tapped/clicked */
3020 handlePress : ( index : number ) => void ;
31- /**
32- * Worklet function to determine if the cursor is technically "hovering"
33- * over a specific chart element (e.g., within a bar's width or a point's radius).
34- */
3521 checkIsOver : ( args : HitTestArgs ) => boolean ;
36- /** Optional shared value containing bar dimensions used for hit-testing in bar charts */
37- barGeometry ?: SharedValue < { barWidth : number ; chartBottom : number ; yZero : number } > ;
22+ barGeometry ?: SharedValue < { barWidth : number ; chartBottom : number ; yZero : number } > ;
3823} ;
3924
40- /**
41- * Type for Victory's actionsRef handle.
42- * Used to manually trigger Victory's internal touch handling logic.
43- */
4425type CartesianActionsHandle = {
4526 handleTouch : ( state : unknown , x : number , y : number ) => void ;
4627} ;
4728
48- /**
49- * Hook to manage complex chart interactions including hover gestures (web),
50- * tap gestures (mobile/web), hit-testing, and animated tooltip positioning.
51- *
52- * It synchronizes high-frequency interaction data from the UI thread to React state
53- * for metadata display (like tooltips) and navigation.
54- *
55- * @param props - Configuration including press handlers and hit-test logic.
56- * @returns An object containing refs, gestures, and state for the chart component.
57- *
58- * @example
59- * ```tsx
60- * const { actionsRef, customGestures, activeDataIndex, isTooltipActive, tooltipStyle } = useChartInteractions({
61- * handlePress: (index) => console.log("Pressed index:", index),
62- * checkIsOver: ({ cursorX, targetX, barWidth }) => {
63- * 'worklet';
64- * return Math.abs(cursorX - targetX) < barWidth / 2;
65- * },
66- * barGeometry: myBarSharedValue,
67- * });
68- *
69- * return (
70- * <View>
71- * <CartesianChart customGestures={customGestures} actionsRef={actionsRef} ... />
72- * {isTooltipActive && <Animated.View style={tooltipStyle}><Tooltip index={activeDataIndex} /></Animated.View>}
73- * </View>
74- * );
75- * ```
76- */
77- function useChartInteractions ( { handlePress, checkIsOver, barGeometry } : UseChartInteractionsProps ) {
78- const { state : chartInteractionState , isActive : isTooltipActiveState } = useChartInteractionState ( { x : 0 , y : { y : 0 } } ) ;
79- const actionsRef = useRef < CartesianActionsHandle > ( null ) ;
29+ function useChartInteractions ( { handlePress, checkIsOver, barGeometry} : UseChartInteractionsProps ) {
30+ const { state : chartInteractionState , isActive : isTooltipActiveState } = useChartInteractionState ( INITIAL_INTERACTION_STATE ) ;
8031
8132 const [ activeDataIndex , setActiveDataIndex ] = useState ( - 1 ) ;
8233 const [ isOverTarget , setIsOverTarget ] = useState ( false ) ;
8334
35+ const actionsRef = useRef < CartesianActionsHandle > ( null ) ;
36+
37+ const handleTouchWorklet = useDerivedValue ( ( ) => {
38+ return actionsRef . current ?. handleTouch ;
39+ } ) ;
40+
8441 const isCursorOverTarget = useDerivedValue ( ( ) => {
42+ 'worklet' ;
43+
8544 const cursorX = chartInteractionState . cursor . x . get ( ) ;
8645 const cursorY = chartInteractionState . cursor . y . get ( ) ;
8746 const targetX = chartInteractionState . x . position . get ( ) ;
@@ -111,22 +70,29 @@ function useChartInteractions({ handlePress, checkIsOver, barGeometry }: UseChar
11170 } ,
11271 ) ;
11372
114- // React Compiler automatycznie zmemoizuje te obiekty gestów
11573 const hoverGesture = Gesture . Hover ( )
11674 . onBegin ( ( e ) => {
11775 'worklet' ;
11876
11977 chartInteractionState . isActive . set ( true ) ;
12078 chartInteractionState . cursor . x . set ( e . x ) ;
12179 chartInteractionState . cursor . y . set ( e . y ) ;
122- actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
80+
81+ const touchFn = handleTouchWorklet . get ( ) ;
82+ if ( touchFn ) {
83+ touchFn ( chartInteractionState , e . x , e . y ) ;
84+ }
12385 } )
12486 . onUpdate ( ( e ) => {
12587 'worklet' ;
12688
12789 chartInteractionState . cursor . x . set ( e . x ) ;
12890 chartInteractionState . cursor . y . set ( e . y ) ;
129- actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
91+
92+ const touchFn = handleTouchWorklet . get ( ) ;
93+ if ( touchFn ) {
94+ touchFn ( chartInteractionState , e . x , e . y ) ;
95+ }
13096 } )
13197 . onEnd ( ( ) => {
13298 'worklet' ;
@@ -140,11 +106,13 @@ function useChartInteractions({ handlePress, checkIsOver, barGeometry }: UseChar
140106 chartInteractionState . cursor . x . set ( e . x ) ;
141107 chartInteractionState . cursor . y . set ( e . y ) ;
142108
143- actionsRef . current ?. handleTouch ( chartInteractionState , e . x , e . y ) ;
144- const matchedIndex = chartInteractionState . matchedIndex . get ( ) ;
145- const isOver = isCursorOverTarget . get ( ) ;
109+ const touchFn = handleTouchWorklet . get ( ) ;
110+ if ( touchFn ) {
111+ touchFn ( chartInteractionState , e . x , e . y ) ;
112+ }
146113
147- if ( matchedIndex >= 0 && isOver ) {
114+ const matchedIndex = chartInteractionState . matchedIndex . get ( ) ;
115+ if ( matchedIndex >= 0 && isCursorOverTarget . get ( ) ) {
148116 scheduleOnRN ( handlePress , matchedIndex ) ;
149117 }
150118 } ) ;
@@ -156,14 +124,13 @@ function useChartInteractions({ handlePress, checkIsOver, barGeometry }: UseChar
156124 const targetY = chartInteractionState . y . y . position . get ( ) ;
157125 const yZero = barGeometry ?. get ( ) . yZero ?? targetY ;
158126 const barTopY = Math . min ( targetY , yZero ) ;
159-
160127 const isVisible = chartInteractionState . isActive . get ( ) && isCursorOverTarget . get ( ) ;
161128
162129 return {
163130 position : 'absolute' ,
164131 left : posX ,
165132 top : barTopY - TOOLTIP_BAR_GAP ,
166- transform : [ { translateX : '-50%' } , { translateY : '-100%' } ] ,
133+ transform : [ { translateX : '-50%' } , { translateY : '-100%' } ] ,
167134 opacity : isVisible ? 1 : 0 ,
168135 } ;
169136 } ) ;
@@ -177,5 +144,5 @@ function useChartInteractions({ handlePress, checkIsOver, barGeometry }: UseChar
177144 } ;
178145}
179146
180- export { useChartInteractions } ;
181- export type { HitTestArgs } ;
147+ export { useChartInteractions } ;
148+ export type { HitTestArgs } ;
0 commit comments