Adds LiveChart for streaming data, moves every chart onto a UI-thread core with a parts-as-children API and built-in tooltips, adds the optional react-native-animateable-text peer for UI-thread tooltip text, and drops the d3 chart peers.
The thirteenth beta of HeroUI Native Pro rebuilds the charts on one chart core that runs on the UI thread. React renders a chart's parts once; after that, data changes, layout, animations, scrubbing, and axis labels update through Reanimated shared values without re-rendering. LineChart, AreaChart, BarChart, and ComposedChart move to a parts-as-children API, and PieChart, RadarChart, and RadialChart drop their render functions. Every chart now supports press selection, onSelect, and a Tooltip part, and tooltip text updates on the UI thread while you scrub. A new LiveChart streams samples with a continuously scrolling time window. This is a breaking release for every chart: read the Breaking Changes below before you upgrade.
Follow the installation guide to authenticate the private registry with your HEROUI_PERSONAL_TOKEN, install heroui-native-pro, and wire up HeroUINativeProvider.
Update to the latest version:
npm install heroui-native-pro@latestpnpm add heroui-native-pro@latestyarn add heroui-native-pro@latestbun add heroui-native-pro@latestCharts now need only the optional react-native-skia peer (>=3.0.0). d3-scale, d3-shape, d3-zoom, its-fine, and react-fast-compare are no longer peers, so you can remove them from your app if nothing else uses them.
For the best tooltip performance, also install the new optional peer react-native-animateable-text (>=0.19.3, New Architecture). Chart tooltips then write their text through a native Text on the UI thread; without it they fall back to a read-only TextInput. Rebuild the app after installing it, since the package ships a native view:
npm install react-native-animateable-textpnpm add react-native-animateable-textyarn add react-native-animateable-textbun add react-native-animateable-textnpx expo install react-native-animateable-textThis release introduces 1 new component in Charts:
LiveChart is built for data that arrives continuously, such as heart rate, prices, or sensor readings. Samples go into a buffer that lives on the UI thread, and one frame loop per chart scrolls the window, glides the line's end between samples, and eases the y-range. Pushing a sample never re-renders React, so use it instead of passing a new data array to LineChart on every sample.
Features:
LiveChart.Line, LiveChart.Grid, LiveChart.YAxis, LiveChart.ReferenceLine, LiveChart.Dot, LiveChart.Crosshair, LiveChart.Indicator, and LiveChart.TooltipuseLiveChartData creates the sample buffer; push and reset are worklets you can call from the JS or the UI thread, and latest / points are shared values for readouts outside the chartdelay ('auto' by default) keeps the right edge slightly behind real time so the line's end moves between known samples instead of jumpingyDomainselection={{ freeze: true }} to hold the window while the finger is downisActive={false} pauses the frame loop, LiveChart.Line animateIn draws the line in, and animation="disable-all" turns every animation offUsage:
import { LiveChart, useLiveChartData } from "heroui-native-pro";
import { useEffect } from "react";
export function HeartRate() {
const data = useLiveChartData({ capacity: 200 });
useEffect(() => {
let bpm = 120;
const id = setInterval(() => {
bpm += (Math.random() - 0.5) * 6;
data.push({ time: Date.now(), value: bpm });
}, 450);
return () => clearInterval(id);
}, [data]);
return (
<LiveChart data={data} window={30_000} className="h-[240px]">
<LiveChart.Grid />
<LiveChart.YAxis />
<LiveChart.ReferenceLine value={150} />
<LiveChart.Line colorClassName="accent-danger" />
<LiveChart.Dot colorClassName="accent-danger" />
<LiveChart.Crosshair />
<LiveChart.Tooltip
format={(value) => {
"worklet";
return `${Math.round(value)} bpm`;
}}
/>
</LiveChart>
);
}For complete documentation and examples, see the LiveChart component page.
All seven charts share one core. Layout, domains, ticks, paths, animations, and press selection are derived values on the UI thread, and the Skia parts render directly inside the canvas without a context bridge. Rows stay on the JS thread; only the plotted fields are copied to the UI thread as typed-array columns, and lines and areas are thinned per pixel column. On iOS, a 100,000-row line chart goes from about 5.7 s to about 0.27 s end to end.
LineChart, AreaChart, BarChart, and ComposedChart take parts as plain children. Each series names its own field with dataKey, and every part is optional: a chart with only a Line is a sparkline.
import { formatCurrency, LineChart } from "heroui-native-pro";
const formatRevenue = (value: number) => {
"worklet";
return formatCurrency(value, { compact: true });
};
export function RevenueChart() {
return (
<LineChart data={DATA} xKey="month" className="h-[220px]">
<LineChart.Grid />
<LineChart.XAxis />
<LineChart.YAxis count={4} format={formatRevenue} />
<LineChart.Line dataKey="revenue" curveType="monotoneX" />
<LineChart.Crosshair />
<LineChart.Indicator dataKey="revenue" />
<LineChart.Tooltip dataKey="revenue" format={formatRevenue} />
</LineChart>
);
}Features:
Grid, XAxis, YAxis, Crosshair, Indicator, and Tooltip; the y-axis reserves exactly the width its widest label needsLineChart.Line — curveType, dashed, dotRadius, and a draw-in animateIn with resetKey to replay itAreaChart.Area — gradient fill by default, stacked layers with a shared stack id, and AreaChart.Range for min–max bandsBarChart.Bar — grouped and stacked bars, orientation="horizontal", gradient colors, radius, barSize / barRatio, and grow-inComposedChart — mixes bar, line, area, and range series; axis="right" with YAxis side="right" adds a right axis whose round values sit on the left axis's grid linesanimate on the root glides from old data to new on the UI thread (timing, spring, or false)For complete documentation and examples, see the LineChart, AreaChart, BarChart, and ComposedChart pages.
Press-and-drag inspection is built into every chart. Add the Crosshair, Indicator, and Tooltip parts, or listen with onSelect, which fires on the JS thread only when the pressed row changes. PieChart, RadarChart, and RadialChart gain press-to-inspect too: the pressed slice, spoke, or ring is highlighted and their new Tooltip part opens away from the center.
Tooltip renders as a React Native view above the canvas. Build custom content from ChartTooltipLabel and ChartTooltipValue; the position follows on the UI thread.
import { ChartTooltipLabel, ChartTooltipValue } from "heroui-native-pro";
<ComposedChart.Tooltip className="gap-1 rounded-xl bg-overlay p-2.5">
<ChartTooltipLabel xKey="month" className="text-xs font-semibold" />
<ChartTooltipValue dataKey="revenue" format={formatThousands} />
<ChartTooltipValue dataKey="orders" />
</ComposedChart.Tooltip>;selection={{ activateAfter }} changes the press delay and selection={false} turns inspection off. For custom overlays, useChartSelection, useChartSelectedValue, and useChartSelectedLabel expose the press state as shared values.
When a tooltip's format is a worklet (or omitted), ChartTooltipValue, ChartTooltipLabel, and the default tooltips write their text from the UI thread, so scrubbing renders no React however many rows the finger crosses. This applies to every chart, including LiveChart.
react-native-animateable-text installed: the text is a native Text that sizes itself to every value. This is the recommended setup (see Installation).TextInput that keeps the width of the widest value it has shown, so the tooltip does not change width while scrubbing.With a plain (non-worklet) format, React renders the text and re-renders it at most once per JS frame, so a fast scrub never queues renders. See Tooltip text on the UI thread.
Axis and tooltip formatters of the cartesian charts run on the UI thread, so they must be worklets. The package now ships worklet formatters you can use directly or wrap:
formatCompact — 15KformatGroupedNumber — 1,234.5formatCurrency — $1,234.50, or $1.2K with { compact: true }formatPercent — 12.3%PieChart.Pie and its render function are gone; innerRadius, startAngle, and circleSweepDegrees move to the root. colorKey is optional (rows without a color take chart-1 … chart-5), animateIn sweeps slices in, and PieChart.Slice dims the other slices while one is pressed. (Documentation)colorClassName, animateIn / resetKey on Radar grow it from the center, and Radar shows an activeDotRadius dot on the pressed vertex. (Documentation)colorKey is optional, barSize and the sweep-in move to the root, and RadialChart.Bar gains dimOpacity. (Documentation)animation="disable-all" now also stops the tooltip fade and glide, and on RadarChart it stops Radar animations (#104).ScopedTheme uses the scoped theme (#104).'worklet' directives, so bundled formatters work on the UI thread (#104).react-native-skia now loads when the first chart renders instead of when the library is imported, so apps that use only non-chart components never run Skia's native setup (#104).onSelect and the useChartSelectedValue / useChartSelectedLabel hooks deliver at most once per JS frame and always end on the newest row, so a fast scrub no longer backs up the JS thread (#104).react-native-animateable-text (>=0.19.3) as an optional peer for UI-thread tooltip text.d3-scale, d3-shape, d3-zoom, its-fine, and react-fast-compare are removed from the optional peers. heroui-native stays at ^1.0.11.LineChart, AreaChart, BarChart, and ComposedChart no longer take yKeys or a render-prop child, and useChartPressState is removed.
Before:
const { state, isActive } = useChartPressState({
x: "" as string,
y: { revenue: 0 },
});
<LineChart
data={DATA}
xKey="month"
yKeys={["revenue"]}
chartPressState={state}
yAxis={[{ tickCount: 4, formatYLabel: formatRevenue }]}
wrapperClassName="h-[200px]"
>
{({ points, chartBounds }) => (
<>
<LineChart.Line points={points.revenue} curveType="monotoneX" />
{isActive ? (
<>
<ChartCrosshair
x={state.x.position}
top={chartBounds.top}
bottom={chartBounds.bottom}
/>
<ChartIndicator x={state.x.position} y={state.y.revenue.position} />
</>
) : null}
</>
)}
</LineChart>;After:
const formatRevenue = (value: number) => {
"worklet";
return `$${(value / 1000).toFixed(0)}k`;
};
<LineChart data={DATA} xKey="month" className="h-[200px]">
<LineChart.Grid />
<LineChart.XAxis />
<LineChart.YAxis count={4} format={formatRevenue} />
<LineChart.Line dataKey="revenue" curveType="monotoneX" />
<LineChart.Crosshair />
<LineChart.Indicator dataKey="revenue" />
<LineChart.Tooltip dataKey="revenue" />
</LineChart>;| Before | After |
|---|---|
yKeys | dataKey on each series |
children: ({ points, chartBounds }) => … | parts as children |
wrapperClassName | className |
domain={{ y: [min, max] }} | yDomain={{ min, max }} |
domainPadding={{ left, right, top, bottom }} | domainPadding (one number of pixels) |
xAxis / yAxis labels and tick count | <XAxis /> / <YAxis count format /> |
yAxis grid lines (lineWidth, linePathEffect) | <Grid strokeWidth dashed /> |
formatXLabel / formatYLabel | format on the axis part (a worklet) |
chartPressState, useChartPressState | Crosshair / Indicator / Tooltip parts, or onSelect |
chartPressConfig | selection={{ activateAfter }} or selection={false} |
onChartBoundsChange, renderOutside, ref actions | removed |
yAxis[i].axisSide: 'right' (ComposedChart) | axis="right" on the series and <YAxis side="right" /> |
yAxis[i].domain (right axis) | yDomainRight={{ min, max }} |
| Before | After |
|---|---|
<LineChart.Line points={points.x} /> | <LineChart.Line dataKey="x" /> |
<DashPathEffect /> child | dashed or dashed={[5, 5]} |
<Scatter points={…} radius={3} /> | dotRadius={3} |
LineChart.AnimatedLine / ComposedChart.AnimatedLine | Line animateIn={1200} resetKey={key} |
AreaChart.Area with y0 and a LinearGradient child | <AreaChart.Area dataKey="x" /> (gradient by default; opacity) |
AreaChart.StackedArea / ComposedChart.StackedArea | one Area per layer with the same stack id |
AreaChart.AreaRange upperPoints lowerPoints | <AreaChart.Range lowKey highKey /> |
BarChart.Bar points chartBounds barWidth | BarChart.Bar dataKey with barSize or barRatio |
roundedCorners | radius (value end only) |
BarChart.BarGroup + BarGroupItem | several BarChart.Bars without a stack |
BarChart.StackedBar | one BarChart.Bar per layer with the same stack id |
useBarPath + a Skia LinearGradient | BarChart.Bar colors={[...]} |
Axis and tooltip format functions on the cartesian charts run on the UI thread. Start them with the 'worklet' directive or use the bundled formatters.
data is a plain array. Pass a new array to update a chart; changes made to the same array in place are not detected.
Before:
<PieChart
data={DATA}
labelKey="name"
valueKey="value"
colorKey="color"
wrapperClassName="h-[220px]"
>
<PieChart.Pie innerRadius="68%">
{() => (
<>
<PieChart.Slice animate={{ type: "timing" }} />
<PieChart.SliceAngularInset
angularInset={{ angularStrokeWidth: 4, angularStrokeColor: bg }}
/>
</>
)}
</PieChart.Pie>
</PieChart>After:
<PieChart
data={DATA}
labelKey="name"
valueKey="value"
colorKey="color"
innerRadius="68%"
className="h-[220px]"
>
<PieChart.Slice />
<PieChart.SliceAngularInset strokeWidth={4} />
<PieChart.Label />
</PieChart>wrapperClassName becomes className on all three.animate moves from the parts to the root and is the data-change transition for every part; animateIn / resetKey replay the mount animation.Color props (Radar color, Grid strokeColor, axis color, Bar trackColor) take strings.headless, explicitSize, transformState, canvasProviders, containerStyle, and canvasStyle are removed, and barSize is set on the root only.ChartCrosshair, ChartIndicator, and ChartTooltip components are removed. Use each chart's Crosshair, Indicator, and Tooltip parts, with ChartTooltipLabel / ChartTooltipValue for custom tooltip content.Scatter, ScatterShape, ChartPressState, useChartPressState, ChartBounds, PointsArray, useBarPath, useBarGroupPaths, useStackedBarPaths, useSlicePath, useSliceAngularInsetPath, and PieSliceData are no longer exported.LineChartProps and AreaChartProps are renamed to LineChartRootProps and AreaChartRootProps; BarChartRootProps and ComposedChartRootProps have a new shape.PieChartPieProps, PieChartRenderFn, RadarChartColorFields, RadarChartRadarAnimateConfig, RadialChartBarAnimateConfig, RadialChartColorFields, and RadialChartInputFields (use RadialChartStringFields). New: PieChartLabelSlice, PieChartTooltipProps, RadarChartTooltipProps, and RadialChartTooltipProps.