diff --git a/README.md b/README.md index 8a2f6f23..ca325729 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,7 @@ A sweet & simple chart library for React Native that will make us feel like **W* - [LineChart.useChart](#linechartusechart) - [LineChart.useDatetime](#linechartusedatetime) - [LineChart.usePrice](#linechartuseprice) + - [LineChart.useYAt](#linechartuseyat) - [CandlestickChart.useChart](#candlestickchartusechart) - [CandlestickChart.useCandleData](#candlestickchartusecandledata) - [CandlestickChart.useDatetime](#candlestickchartusedatetime) @@ -1055,6 +1056,31 @@ const { value, formatted } = LineChart.usePrice({ | `value` | `string` | | Price value | | `formatted` | `string` | | Formatted price value | +### LineChart.useYAt + +Get the y value on the chart for a given value or index. + +```jsx +const y = LineChart.useYAt({ + at, + offsetY, +}); +``` + +**Arguments** + +| Variable | Type | Default | Description | +|-----------|--------------------------------------------------------|---------|------------------------------------------------------------------------------------------------------------| +| `at` | `number` or `{ index: number }` or `{ value: number }` | `0` | Index of followed `data` item. You can alternatively pass `{ value: number }`, corresponding to a y value. | +| `offsetY` | `number` | `0` | An optional offset for the y value. | + +**Returns** + +| Variable | Type | Default | Description | +| ----------- | -------- | ------- | --------------------- | +| `value` | `string` | | Price value | +| `formatted` | `string` | | Formatted price value | + ### CandlestickChart.useChart ```jsx diff --git a/src/charts/line/HorizontalLine.tsx b/src/charts/line/HorizontalLine.tsx index 1e5d4bf4..86cba375 100644 --- a/src/charts/line/HorizontalLine.tsx +++ b/src/charts/line/HorizontalLine.tsx @@ -1,14 +1,9 @@ import React from 'react'; -import Animated, { - useAnimatedProps, - useDerivedValue, - withTiming, -} from 'react-native-reanimated'; +import Animated, { useAnimatedProps } from 'react-native-reanimated'; import { Line as SVGLine, LineProps } from 'react-native-svg'; -import { getYForX, parse } from 'react-native-redash'; - import { LineChartDimensionsContext } from './Chart'; -import { useLineChart } from './useLineChart'; +import type { AtPoint } from './types'; +import { useYAt } from './useYAt'; const AnimatedLine = Animated.createAnimatedComponent(SVGLine); @@ -33,16 +28,7 @@ type HorizontalLineProps = { * /> * ``` */ - at?: - | { - index: number; - value?: never; - } - | { - index?: never; - value: number; - } - | number; + at?: AtPoint; }; LineChartHorizontalLine.displayName = 'LineChartHorizontalLine'; @@ -53,41 +39,10 @@ export function LineChartHorizontalLine({ at = { index: 0 }, offsetY = 0, }: HorizontalLineProps) { - const { width, path, height, gutter } = React.useContext( - LineChartDimensionsContext - ); - const { data, yDomain } = useLineChart(); - - const parsedPath = React.useMemo(() => parse(path), [path]); - const pointWidth = React.useMemo( - () => width / data.length, - [data.length, width] - ); - - const y = useDerivedValue(() => { - if (typeof at === 'number' || at.index != null) { - const index = typeof at === 'number' ? at : at.index; - const yForX = getYForX(parsedPath!, pointWidth * index) || 0; - return withTiming(yForX + offsetY); - } - /** - * - * | ---------- | <- yDomain.max | - * | | | offsetTop - * | | <- value | - * | | - * | | <- yDomain.min - * - */ - - const offsetTop = yDomain.max - at.value; - const percentageOffsetTop = offsetTop / (yDomain.max - yDomain.min); - - const heightBetweenGutters = height - gutter * 2; - - const offsetTopPixels = gutter + percentageOffsetTop * heightBetweenGutters; - - return withTiming(offsetTopPixels + offsetY); + const { width } = React.useContext(LineChartDimensionsContext); + const y = useYAt({ + at, + offsetY, }); const lineAnimatedProps = useAnimatedProps(() => ({ diff --git a/src/charts/line/index.ts b/src/charts/line/index.ts index 145f1d2a..2132a3cb 100644 --- a/src/charts/line/index.ts +++ b/src/charts/line/index.ts @@ -16,6 +16,7 @@ import { useLineChartDatetime } from './useDatetime'; import { useLineChartPrice } from './usePrice'; import { useLineChart } from './useLineChart'; import { LineChartHoverTrap } from '../line/HoverTrap'; +import { useYAt } from './useYAt'; export * from './Chart'; export * from './ChartPath'; @@ -54,4 +55,5 @@ export const LineChart = Object.assign(_LineChart, { usePrice: useLineChartPrice, useChart: useLineChart, HoverTrap: LineChartHoverTrap, + useYAt: useYAt, }); diff --git a/src/charts/line/types.ts b/src/charts/line/types.ts index bfd90942..1db105bc 100644 --- a/src/charts/line/types.ts +++ b/src/charts/line/types.ts @@ -29,3 +29,14 @@ export type YDomain = { min: number; max: number; }; + +export type AtPoint = + | { + index: number; + value?: never; + } + | { + index?: never; + value: number; + } + | number; diff --git a/src/charts/line/useYAt.tsx b/src/charts/line/useYAt.tsx new file mode 100644 index 00000000..e492913c --- /dev/null +++ b/src/charts/line/useYAt.tsx @@ -0,0 +1,50 @@ +import React from 'react'; +import type { AtPoint } from './types'; +import { useLineChart } from './useLineChart'; +import { LineChartDimensionsContext } from './Chart'; +import { getYForX, parse } from 'react-native-redash'; +import { useDerivedValue, withTiming } from 'react-native-reanimated'; + +export type ViewAtProps = { + at: AtPoint; + offsetY?: number; +}; + +export function useYAt({ at, offsetY = 0 }: ViewAtProps) { + const { width, path, height, gutter } = React.useContext( + LineChartDimensionsContext + ); + const { data, yDomain } = useLineChart(); + + const parsedPath = React.useMemo(() => parse(path), [path]); + const pointWidth = React.useMemo( + () => width / data.length, + [data.length, width] + ); + + return useDerivedValue(() => { + if (typeof at === 'number' || at.index != null) { + const index = typeof at === 'number' ? at : at.index; + const yForX = getYForX(parsedPath!, pointWidth * index) || 0; + return withTiming(yForX + offsetY); + } + /** + * + * | ---------- | <- yDomain.max | + * | | | offsetTop + * | | <- value | + * | | + * | | <- yDomain.min + * + */ + + const offsetTop = yDomain.max - at.value; + const percentageOffsetTop = offsetTop / (yDomain.max - yDomain.min); + + const heightBetweenGutters = height - gutter * 2; + + const offsetTopPixels = gutter + percentageOffsetTop * heightBetweenGutters; + + return withTiming(offsetTopPixels + offsetY); + }, [at, parsedPath, pointWidth, yDomain, height, gutter, offsetY]); +}