react-native-wagmi-charts
Version:
A sweet candlestick chart for React Native
196 lines (186 loc) • 6.47 kB
JavaScript
function _extends() { return _extends = Object.assign ? Object.assign.bind() : function (n) { for (var e = 1; e < arguments.length; e++) { var t = arguments[e]; for (var r in t) ({}).hasOwnProperty.call(t, r) && (n[r] = t[r]); } return n; }, _extends.apply(null, arguments); }
import React from 'react';
import Animated, { useAnimatedStyle, useDerivedValue, useSharedValue, withTiming } from 'react-native-reanimated';
import { StyleSheet } from 'react-native';
import { LineChartPriceText } from './PriceText';
import { CursorContext } from './Cursor';
import { LineChartDimensionsContext } from './Chart';
import { getXPositionForCurve } from './utils/getXPositionForCurve';
import { getYForX } from 'react-native-redash';
import { useLineChart } from './useLineChart';
import { useMemo } from 'react';
LineChartTooltip.displayName = 'LineChartTooltip';
export function LineChartTooltip({
children,
format,
xGutter = 8,
yGutter = 8,
cursorGutter = 48,
position = 'top',
withHorizontalFloating = false,
textProps,
textStyle,
at,
...props
}) {
const {
width,
height,
parsedPath
} = React.useContext(LineChartDimensionsContext);
const {
type
} = React.useContext(CursorContext);
const {
currentX,
currentY,
isActive
} = useLineChart();
const elementWidth = useSharedValue(0);
const elementHeight = useSharedValue(0);
const handleLayout = React.useCallback(event => {
elementWidth.value = event.nativeEvent.layout.width;
elementHeight.value = event.nativeEvent.layout.height;
}, [elementHeight, elementWidth]);
// When the user set a `at` index, get the index's y & x positions
const atXPosition = useMemo(() => at !== null && at !== undefined ? getXPositionForCurve(parsedPath, at) : undefined, [at, parsedPath]);
const atYPosition = useDerivedValue(() => {
return atXPosition == null ? undefined : getYForX(parsedPath, atXPosition) ?? 0;
}, [atXPosition]);
const getInitialTranslateXOffset = React.useCallback(elementWidth => {
'worklet';
if (position === 'right') return elementWidth + cursorGutter;
if (position === 'left') return -cursorGutter;
return elementWidth / 2;
}, [cursorGutter, position]);
/**
* Helper function to calculate the X translation offset based on position
* and boundary constraints
*/
const calculateXTranslateOffset = React.useCallback(params => {
'worklet';
const {
position,
x,
elementWidth,
width,
xGutter,
cursorGutter,
withHorizontalFloating
} = params;
let translateXOffset = getInitialTranslateXOffset(elementWidth);
const elementFullWidth = elementWidth + xGutter + cursorGutter;
if (position === 'right') {
if (x < elementFullWidth) {
translateXOffset = withHorizontalFloating ? -cursorGutter : translateXOffset - elementFullWidth + x;
}
} else if (position === 'left') {
if (x > width - elementFullWidth) {
translateXOffset = withHorizontalFloating ? elementWidth + cursorGutter : translateXOffset + (x - (width - elementFullWidth));
}
} else {
// Center position
if (x < elementWidth / 2 + xGutter) {
translateXOffset -= elementWidth / 2 + xGutter - x;
}
if (x > width - elementWidth / 2 - xGutter) {
translateXOffset += x - (width - elementWidth / 2 - xGutter);
}
}
return translateXOffset;
}, [getInitialTranslateXOffset]);
/**
* Helper function to calculate the Y translation offset based on position and
* boundary constraints
*/
const calculateYTranslateOffset = React.useCallback(params => {
'worklet';
const {
position,
y,
elementHeight,
height,
yGutter,
cursorGutter
} = params;
let translateYOffset = 0;
if (position === 'top') {
translateYOffset = elementHeight / 2 + cursorGutter;
if (y - translateYOffset < yGutter) {
translateYOffset = y - yGutter;
}
} else if (position === 'bottom') {
translateYOffset = -(elementHeight / 2) - cursorGutter / 2;
if (y - translateYOffset + elementHeight > height - yGutter) {
translateYOffset = y - (height - yGutter) + elementHeight;
}
} else if (position === 'right' || position === 'left') {
translateYOffset = elementHeight / 2;
}
return translateYOffset;
}, []);
const animatedCursorStyle = useAnimatedStyle(() => {
// the tooltip is considered static when the user specified an `at` prop
const isStatic = atYPosition.value != null;
// Calculate X position:
const x = atXPosition ?? currentX.value;
const translateXOffset = calculateXTranslateOffset({
position,
x,
elementWidth: elementWidth.value,
width,
xGutter,
cursorGutter,
withHorizontalFloating
});
const translateX = x - translateXOffset;
// Calculate Y position:
const y = atYPosition.value ?? currentY.value;
const translateYOffset = calculateYTranslateOffset({
position,
y,
elementHeight: elementHeight.value,
height,
yGutter,
cursorGutter
});
// Determine final translateY value
let translateY;
if (type === 'crosshair' || isStatic) {
translateY = y - translateYOffset;
} else {
translateY = position === 'top' ? yGutter : height - elementHeight.value - yGutter;
}
// Calculate opacity
let opacity = isActive.value ? 1 : 0;
if (isStatic) {
// Only show static when there is no active cursor
opacity = withTiming(isActive.value ? 0 : 1);
}
return {
transform: [{
translateX
}, {
translateY
}],
opacity: opacity
};
}, [atXPosition, atYPosition, calculateXTranslateOffset, calculateYTranslateOffset, currentX, currentY, cursorGutter, elementHeight, elementWidth, height, isActive, position, type, width, withHorizontalFloating, xGutter, yGutter]);
return /*#__PURE__*/React.createElement(Animated.View, _extends({
onLayout: handleLayout
}, props, {
style: [styles.tooltip, animatedCursorStyle, props.style]
}), children || /*#__PURE__*/React.createElement(LineChartPriceText, _extends({
format: format,
index: at,
style: [textStyle]
}, textProps)));
}
const styles = StyleSheet.create({
tooltip: {
position: 'absolute',
padding: 4,
alignSelf: 'flex-start'
}
});
//# sourceMappingURL=Tooltip.js.map