react-native-wagmi-charts
Version:
A sweet candlestick chart for React Native
132 lines (126 loc) • 4.37 kB
JavaScript
import React from 'react';
import Animated, { runOnJS } from 'react-native-reanimated';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import { LineChartDimensionsContext } from './Chart';
import { StyleSheet } from 'react-native';
import { bisectCenter } from 'd3-array';
import { scaleLinear } from 'd3-scale';
import { useLineChart } from './useLineChart';
import { useEffect } from 'react';
export const CursorContext = /*#__PURE__*/React.createContext({
type: ''
});
LineChartCursor.displayName = 'LineChartCursor';
export function LineChartCursor({
children,
snapToPoint,
type,
at,
shouldCancelWhenOutside = false,
persistOnEnd = false,
minDurationMs = 0,
onActivated,
onEnded
}) {
const {
pathWidth: width,
parsedPath
} = React.useContext(LineChartDimensionsContext);
const {
currentX,
currentIndex,
isActive,
data,
xDomain
} = useLineChart();
const xValues = React.useMemo(() => (data ?? []).map(({
timestamp
}, i) => xDomain ? timestamp : i), [data, xDomain]);
// Same scale as in /src/charts/line/utils/getPath.ts
const scaleX = React.useMemo(() => {
const domainArray = xDomain ?? [0, xValues.length];
return scaleLinear().domain(domainArray).range([0, width]);
}, [width, xDomain, xValues.length]);
const linearScalePositionAndIndex = ({
xPosition
}) => {
if (!parsedPath) {
return;
}
// Calculate a scaled timestamp for the current touch position
const xRelative = scaleX.invert(xPosition);
const closestIndex = bisectCenter(xValues, xRelative);
const pathDataDelta = Math.abs(parsedPath.curves.length - xValues.length); // sometimes there is a difference between data length and number of path curves.
const closestPathCurve = Math.max(Math.min(closestIndex, parsedPath.curves.length + 1) - pathDataDelta, 0);
const curveSegment = closestIndex > 0 && parsedPath.curves[closestPathCurve] ? parsedPath.curves[closestPathCurve] : null;
const newXPosition = (curveSegment ? curveSegment.to : parsedPath.move).x;
// Update values
currentIndex.value = closestIndex;
currentX.value = newXPosition;
};
useEffect(() => {
if (at !== undefined) {
const xPosition = scaleX(at);
runOnJS(linearScalePositionAndIndex)({
xPosition
});
isActive.value = true;
}
}, [at, scaleX]);
const updatePosition = xPosition => {
'worklet';
if (parsedPath) {
// on Web, we could drag the cursor to be negative, breaking it
// so we clamp the index at 0 to fix it
// https://github.com/coinjar/react-native-wagmi-charts/issues/24
const minIndex = 0;
const boundedIndex = Math.max(minIndex, Math.round(xPosition / width / (1 / (data ? data.length - 1 : 1))));
if (snapToPoint) {
// We have to run this on the JS thread unfortunately as the scaleLinear functions won't work on UI thread
runOnJS(linearScalePositionAndIndex)({
xPosition
});
} else if (!snapToPoint) {
currentX.value = xPosition;
currentIndex.value = boundedIndex;
}
}
};
const longPressGesture = Gesture.LongPress().minDuration(minDurationMs ?? 0).maxDistance(999999).shouldCancelWhenOutside(shouldCancelWhenOutside).onStart(event => {
'worklet';
if (parsedPath) {
const xPosition = Math.max(0, event.x <= width ? event.x : width);
isActive.value = true;
updatePosition(xPosition);
if (onActivated) {
runOnJS(onActivated)();
}
}
}).onTouchesMove(event => {
'worklet';
if (parsedPath && isActive.value && event.allTouches.length > 0 && event.allTouches[0]) {
const touchX = event.allTouches[0].x;
const xPosition = Math.max(0, touchX <= width ? touchX : width);
updatePosition(xPosition);
}
}).onEnd(() => {
'worklet';
if (!persistOnEnd) {
isActive.value = false;
currentIndex.value = -1;
}
if (onEnded) {
runOnJS(onEnded)();
}
});
return /*#__PURE__*/React.createElement(CursorContext.Provider, {
value: {
type
}
}, /*#__PURE__*/React.createElement(GestureDetector, {
gesture: longPressGesture
}, /*#__PURE__*/React.createElement(Animated.View, {
style: StyleSheet.absoluteFill
}, children)));
}
//# sourceMappingURL=Cursor.js.map