Skip to main content

Multi Press

In this guide, we'll take the end-result of the Getting Started guide and add multi-press interaction to it.

The final code of the Getting Started guide is shown below:

import * as React from "react";
import { View } from "react-native";
import { CartesianChart, Line, useChartPressState } from "victory-native";
import { Circle, useFont } from "@shopify/react-native-skia";
import type { SharedValue } from "react-native-reanimated";
import inter from "../../assets/inter-medium.ttf"; // Wherever your font actually lives

function MyChart() {
const font = useFont(inter, 12);
const { state, isActive } = useChartPressState({ x: 0, y: { highTmp: 0 } });

return (
<View style={{ height: 300 }}>
<CartesianChart
data={DATA}
xKey="day"
yKeys={["highTmp"]}
axisOptions={{ font }}
chartPressState={state}
>
{({ points }) => (
<>
<Line points={points.highTmp} color="red" strokeWidth={3} />
{isActive && (
<ToolTip x={state.x.position} y={state.y.highTmp.position} />
)}
</>
)}
</CartesianChart>
</View>
);
}

function ToolTip({ x, y }: { x: SharedValue<number>; y: SharedValue<number> }) {
return <Circle cx={x} cy={y} r={8} color="black" />;
}

const DATA = Array.from({ length: 31 }, (_, i) => ({
day: i,
highTmp: 40 + 30 * Math.random(),
}));

Tracking multiple presses

To track multiple press gestures on our chart, we create multiple ChartPressState values using the useChartPressState hook and pass the state values into the chartPressState prop of the CartesianChart. Let's do that now.

// ... imports

const INIT_STATE = { x: 0, y: { highTmp: 0 } } as const;

function MyChart() {
// ...
// 👇 Create two "chart press state" values
const { state: firstPress, isActive: isFirstPressActive } =
useChartPressState(INIT_STATE);
const { state: secondPress, isActive: isSecondPressActive } =
useChartPressState(INIT_STATE);

return (
<View style={{ height: 300 }}>
<CartesianChart
// ...
chartPressState={[firstPress, secondPress]} // 👈 Pass state values
>
{({ points }) => (
<>
<Line points={points.highTmp} color="red" strokeWidth={3} />
{/* 👇 Update our variable names here. */}
{isFirstPressActive && (
<ToolTip
x={firstPress.x.position}
y={firstPress.y.highTmp.position}
/>
)}
</>
)}
</CartesianChart>
</View>
);
}

With this in place, the original ToolTip element should continue behaving as it did before. Let's add a second tooltip for when a second press gesture is active on the chart.

Adding a second Tooltip

We'll use the same ToolTip component, but pass it values from our secondPress state value. This will make the second tooltip show up when the second press gesture is active.

// ... imports

function MyChart() {
// ...
const { state: firstPress, isActive: isFirstPressActive } =
useChartPressState(INIT_STATE);
const { state: secondPress, isActive: isSecondPressActive } =
useChartPressState(INIT_STATE);

return (
<View style={{ height: 300 }}>
<CartesianChart
// ...
chartPressState={[firstPress, secondPress]}
>
{({ points }) => (
<>
<Line points={points.highTmp} color="red" strokeWidth={3} />
{isFirstPressActive && (/* ... */)}
{/* 👇 Add a second tooltip */}
{isSecondPressActive && (
<ToolTip
x={secondPress.x.position}
y={secondPress.y.highTmp.position}
/>
)}
</>
)}
</CartesianChart>
</View>
);
}

Full example

Putting this all together, our code looks something like the following:

import * as React from "react";
import { View } from "react-native";
import { CartesianChart, Line, useChartPressState } from "victory-native";
import { Circle, useFont } from "@shopify/react-native-skia";
import type { SharedValue } from "react-native-reanimated";
import inter from "../../assets/inter-medium.ttf";

const INIT_STATE = { x: 0, y: { highTmp: 0 } } as const;

function MyChart() {
const font = useFont(inter, 12);
const { state: firstPress, isActive: isFirstPressActive } =
useChartPressState(INIT_STATE);
const { state: secondPress, isActive: isSecondPressActive } =
useChartPressState(INIT_STATE);

return (
<View style={{ height: 300 }}>
<CartesianChart
data={DATA}
xKey="day"
yKeys={["highTmp"]}
axisOptions={{ font }}
chartPressState={[firstPress, secondPress]}
>
{({ points }) => (
<>
<Line points={points.highTmp} color="red" strokeWidth={3} />
{isFirstPressActive && (
<ToolTip
x={firstPress.x.position}
y={firstPress.y.highTmp.position}
/>
)}
{isSecondPressActive && (
<ToolTip
x={secondPress.x.position}
y={secondPress.y.highTmp.position}
/>
)}
</>
)}
</CartesianChart>
</View>
);
}

function ToolTip({ x, y }: { x: SharedValue<number>; y: SharedValue<number> }) {
return <Circle cx={x} cy={y} r={8} color="black" />;
}

const DATA = Array.from({ length: 31 }, (_, i) => ({
day: i,
highTmp: 40 + 30 * Math.random(),
}));