Installation
The package, its native peers, and the one thing your app has to provide.
bun add @delacour/react-native-charts@alphabunx expo install @shopify/react-native-skia react-native-gesture-handler react-native-reanimated react-native-workletsThe package ships raw .ts and .tsx, and every native module it draws with is a peer. Two
copies of Skia or Reanimated register twice and fail at runtime, which is why they are not
dependencies.
Do not install native modules with your package manager
bun add react-native-reanimated fetches the newest release, which on any older SDK is a package
that fails at the linker rather than at install time. expo install asks the SDK what it supports.
Peer dependencies
| Package | Range | Needed for |
|---|---|---|
@shopify/react-native-skia | >=2.6 | the canvas, paths and text |
react-native-reanimated | >=4 | path morphing and the scrub's shared values |
react-native-gesture-handler | >=2.28 | the scrub's pan and the pie's tap |
react-native-worklets | >=0.5 | the UI-thread functions Reanimated 4 runs |
react | >=19 | |
react-native | >=0.81 |
d3-scale and d3-shape are the package's only runtime dependencies. They are pure JavaScript
and arrive on their own.
The gesture root
Your app needs a GestureHandlerRootView around everything that takes touches. The package does
not render one — a nested root is dead weight, and every React Native app that handles touch
already has one. If you also use @delacour/react-native-ui, its DelacourProvider is that root.
import { GestureHandlerRootView } from "react-native-gesture-handler";
export default function RootLayout() {
return <GestureHandlerRootView style={{ flex: 1 }}>{children}</GestureHandlerRootView>;
}Without the gesture root a scrub never activates and a pie never reports a tap. Silently — no error, no warning.
The worklets plugin
Reanimated 4 compiles its UI-thread functions with the react-native-worklets/plugin Babel
plugin. babel-preset-expo adds it for you once react-native-worklets is installed; a bare React
Native app lists it last in babel.config.js.
module.exports = {
presets: ["module:@react-native/babel-preset"],
plugins: ["react-native-worklets/plugin"],
};Verify
A chart needs a height. The root fills whatever view it is placed in and measures itself with
onLayout, so give it a box.
import { CartesianChart, ChartLine } from "@delacour/react-native-charts";
import { View } from "react-native";
const rows = [
{ day: "Mon", revenue: 120 },
{ day: "Tue", revenue: 180 },
{ day: "Wed", revenue: 150 },
{ day: "Thu", revenue: 210 },
{ day: "Fri", revenue: 240 },
];
<View style={{ height: 240 }}>
<CartesianChart data={rows} xKey="day" yKeys={["revenue"]}>
<ChartLine color="#0A84FF" curve="monotone" yKey="revenue" />
</CartesianChart>
</View>;A chart in a zero-height view draws nothing and says nothing: the plot rect has no area, and
ready on the context is false.
Fonts
No font ships with the package. useSystemFont resolves one from the families the operating
system already has, so an axis is legible with no expo-font and no config plugin. Call it
above the chart and pass the result in — Composition explains why
it cannot be called inside.
import { CartesianChart, ChartXAxis, useSystemFont } from "@delacour/react-native-charts";
const font = useSystemFont(undefined, 12);
<CartesianChart data={rows} font={font} xKey="day" yKeys={["revenue"]}>
<ChartXAxis color="#8E8E93" />
</CartesianChart>;iOS's system font is not Android's, so the same labels measure differently and the plot rect differs by a few points across platforms. An app that needs identical layout on both loads its own font and passes that instead.