Skip to content

Repository files navigation

react-native-pure-chart

Charts built from nothing but <View> and <Text>. Zero dependencies. No native modules. No SVG, no Skia.

npm npm downloads license types dependencies

react-native-pure-chart draws every axis, line, bar and pie slice with plain React Native core components — rotated Views with transformOrigin, circular clipping, and the Animated API. Nothing to link, nothing to compile.

Line chart with area fill Grouped bar chart Donut chart with center label

Why pure?

  • npm install and you're done. No pod install, no config plugin, no native linking — works in Expo Go, bare RN, and react-native-web.
  • Survives every React Native upgrade. Only core components are used, so there is no native module to break on the next RN release.
  • New Architecture native. Built for RN 0.76+ / Fabric; animations run on the native driver (transform/opacity only).

Quick start

import { LineChart } from 'react-native-pure-chart';

<LineChart data={[30, 200, 170, 250, 10]} />;

That's a full chart: width fills the parent, nice-number ticks, compact 1.2K-style labels, and a 500 ms entrance animation — all defaults.

Installation

npm install react-native-pure-chart
# or
yarn add react-native-pure-chart

Requires React Native 0.73+ (for transformOrigin). No other setup.


Gallery

Every image below is a real screenshot from example/ on an iOS simulator — the code under each one is what produced it.

Line charts

Multi-series line chart

Multi-series + legend

<LineChart data={revenue} height={200} />
Monotone line chart with area fill

Smooth curve + area fill

<LineChart data={traffic} curve="monotone" area />
Step line chart

Step curve, thick, no dots

<LineChart
  data={traffic}
  curve="step"
  strokeWidth={3}
  showDataPoints={false}
/>
Line chart with missing values

null values break the line

<LineChart data={[50, null, null, 90, 40, 70, 20, 60]} />
Line chart with custom value labels

Custom value labels

<LineChart
  data={traffic}
  curve="monotone"
  renderValueLabel={(e) => <Pill value={e.value} />}
/>
Line chart with tooltip

Built-in tooltip + guide line

// tap, or drive it yourself
<LineChart data={revenue} selectedIndex={3} />

Bar charts

Grouped bar chart

Grouped

<BarChart data={platforms} barRadius={5} />
Stacked bar chart

Stacked

<BarChart data={platforms} stacked barRadius={5} />
Horizontal bar chart

Horizontal

<BarChart data={monthly} horizontal barRadius={5} />
Bar chart with negative values

Negative values

<BarChart data={[12, -8, 25, -15, 30, 5]} barRadius={5} />
Bar chart with a selected category

Selection dims the rest

<BarChart data={platforms} selectedIndex={2} />
Horizontal stacked bar chart, dark theme

Stacked + horizontal + dark

<BarChart data={platforms} stacked horizontal theme="dark" />

Pie & donut

Pie chart

Pie + legend

<PieChart data={budget} size={200} />
Donut chart with center label

Donut + center label

<PieChart
  data={budget}
  innerRadius="60%"
  renderCenterLabel={() => <Total />}
/>
Pie chart with percentage labels

On-slice labels + padAngle

<PieChart data={budget} padAngle={2} sliceLabel="percentage" />
Donut chart, dark theme

Dark donut

<PieChart data={budget} innerRadius="60%" theme="dark" />

Dark mode

One prop. Pass useColorScheme() straight through and every axis, grid line, label and tooltip flips with it.

import { useColorScheme } from 'react-native';

const scheme = useColorScheme();

<LineChart data={revenue} theme={scheme === 'dark' ? 'dark' : 'light'} />;
Line chart, dark theme Bar chart, dark theme

API

Runtime exports: LineChart, BarChart, PieChart, DEFAULT_PALETTE, formatCompact. Every prop and payload type is exported too (LineChartProps, PressEvent, ChartTheme, …).

Data

type ChartData =
  | readonly (number | null | DataPoint)[] // one series
  | readonly Series[]; // many

interface DataPoint {
  value: number | null; // null = missing
  label?: string; // x-axis label
  color?: string; // per-point override
  extra?: unknown; // returned untouched in press events
}

interface Series {
  data: readonly (number | null | DataPoint)[];
  name?: string; // legend + tooltip
  color?: string; // defaults to palette[seriesIndex]
}

Numbers, nulls and DataPoint objects can be mixed freely in one array. Pie charts take PieSliceDatum[]{ value, label?, color?, extra? }.

Colors

Nothing is hard-coded. Colors come from four levels, most specific first:

// 1. the whole chart — replaces the auto-assigned palette
<BarChart data={data} palette={['#FF6B6B', '#4ECDC4', '#FFD93D']} />

// 2. one series / one pie slice
<LineChart data={[{ name: 'Sales', data: [30, 90, 50], color: '#FF6B6B' }]} />
<PieChart data={[{ value: 50, label: 'Marketing', color: '#FF6B6B' }]} />

// 3. one bar — highlight a single value
<BarChart data={[30, { value: 90, color: '#FF6B6B' }, 50]} />

// 4. line dots, independently of the line
<LineChart data={data} showDataPoints={{ color: '#111827', radius: 5 }} />

The palette cycles when there are more series than colors, so it never runs out. Extend the built-in one instead of retyping it:

import { DEFAULT_PALETTE } from 'react-native-pure-chart';

<BarChart data={data} palette={['#FF6B6B', ...DEFAULT_PALETTE]} />;

Two limits worth knowing: a per-point color applies to grouped and single-series bars only — stacked segments take the series color — and line segments always use the series color, so a single point can't recolor the line it sits on. Axis, grid, label and tooltip colors are not part of the palette; they come from theme.

LineChart

Prop Type Default Description
data ChartData Numbers, {value, label} objects, or an array of series
strokeWidth number 2 Line thickness (no upper limit)
curve 'linear' | 'step' | 'monotone' 'linear' 'monotone' draws a smooth curve that never overshoots the data
area boolean | {opacity} false Fill between the line and the zero baseline (default opacity 0.15)
showDataPoints boolean | {radius, color} true Point markers. Default radius max(3, strokeWidth * 1.5)
missingValues 'break' | 'interpolate' | 'zero' 'break' How null values are treated
spacing number auto Exact px between points. Auto mode fills the width, but never squeezes below 40px — the chart grows and scrolls instead
scrollable boolean true Horizontal scroll when content overflows
initialScroll 'start' | 'end' 'start' Initial scroll position
legend boolean | LegendOptions auto Shown when there are 2+ series and at least one is named
tooltip boolean | TooltipOptions true Built-in tooltip + selection guide
onPointPress (e: PressEvent) => void Tap on the chart (nearest point wins)
selectedIndex / onSelectionChange number | null Controlled selection
renderDataPoint (e: PressEvent) => ReactNode Replace point markers
renderValueLabel (e: PressEvent) => ReactNode Always-visible label above points
xAxis / yAxis see Axes Ticks, grid, formatters

BarChart

Prop Type Default Description
data ChartData Same shapes as LineChart
barWidth number auto Bar width. Auto mode fills the width, but grows and scrolls instead of rendering sliver bars for many categories
barRadius number 4 Corner radius on the value end
barGap number 2 Gap between bars in a group
groupGap number auto Gap between categories
stacked boolean false Stack multi-series values (positives up, negatives down)
horizontal boolean false Horizontal bars (yAxis still configures the value axis)
legend boolean | LegendOptions auto Same rule as LineChart
scrollable / initialScroll true / 'start' Vertical bars only — horizontal bars always fit their height
tooltip boolean | TooltipOptions true Selection also dims the other categories
onPointPress / selectedIndex / onSelectionChange Same as LineChart
renderValueLabel (e: PressEvent) => ReactNode Label above each bar
xAxis / yAxis see Axes yAxis is always the value axis

Negative values are supported — bars grow down from the zero baseline, and stacks split at it.

PieChart

Prop Type Default Description
data PieSliceDatum[] {value, label?, color?, extra?} per slice
size number 200 Diameter in px
innerRadius number | '60%' 0 Donut hole — px, or a % of the radius
holeColor string theme background Hole fill (opaque — see Limitations)
startAngle number 0 Degrees clockwise from 12 o'clock
padAngle number 0 Gap between slices in degrees
sliceLabel 'none' | 'label' | 'value' | 'percentage' | (e) => string 'none' On-slice labels
legend boolean | LegendOptions true Lists every labeled slice
focusOnPress boolean true Tapped slice slides outward
onSlicePress (e: SlicePressEvent) => void Tap on a slice
selectedIndex / onSelectionChange number | null Controlled selection
renderCenterLabel (selected: SlicePressEvent | null) => ReactNode Donut center content

PieChart sizes itself with size — it does not take width, height or padding.

Common props

Every chart accepts:

Prop Type Default Description
width number parent width Measured with onLayout when omitted
height number 220 Plot height (pies use size)
padding number | {top, right, bottom, left} {top: 12, right: 12, bottom: 0, left: 0} Inner padding of the plot area
palette string[] built-in 10 colors Auto-assigned series/slice colors — exported as DEFAULT_PALETTE
theme 'light' | 'dark' | ChartTheme 'light' Pass useColorScheme() output directly
animate boolean | AnimationConfig true {duration, delay, easing, type: 'grow' | 'fade' | 'none'}
style, testID, accessibilityLabel Standard RN props (a11y label auto-generated)

The default entrance (type: 'grow') is chart-aware: lines draw left to right, bars grow from the baseline with a stagger, pies sweep clockwise. One Animated.Value drives the whole chart on the native driver.

Axes

yAxis configures the value axis (including on horizontal bars), xAxis the category axis.

YAxisOptions Type Default
show boolean true
tickCount number 5
min / max number nice-number bounds from the data
formatLabel (value, index) => string formatCompact
showGridLines boolean true
gridLineStyle 'solid' | 'dashed' 'dashed'
showAxisLine boolean false
position 'left' | 'right' 'left' (ignored by horizontal bars)
labelStyle TextStyle
XAxisOptions Type Default
show boolean true (auto-hidden when no point has a label)
interval number | 'auto' 'auto' — thins labels to fit the width
formatLabel (label, index) => string identity
showAxisLine boolean true
showGridLines boolean false
labelStyle TextStyle

Theme, legend, tooltip, events

interface ChartTheme {
  gridColor?: string;
  axisColor?: string;
  labelColor?: string;
  tooltipBackgroundColor?: string;
  tooltipTextColor?: string;
  /** Only used as the donut hole fill — charts never paint their background. */
  backgroundColor?: string;
}

interface LegendOptions {
  position?: 'top' | 'bottom'; // default 'bottom'
  labelStyle?: TextStyle;
}

interface TooltipOptions {
  render?: (e: PressEvent) => ReactNode; // replace it entirely
  formatValue?: (value: number) => string; // default formatCompact
  dismissOnTapOutside?: boolean; // default true
}

interface PressEvent {
  point: DataPoint;
  value: number | null;
  label?: string;
  index: number; // index within the series
  seriesIndex: number;
  seriesName?: string;
  position: { x: number; y: number }; // px, relative to the chart
}

interface SlicePressEvent {
  slice: PieSliceDatum;
  index: number;
  value: number;
  percentage: number; // 0..1
}

Guides

Handling missing data

null values are first-class citizens in line charts:

  • 'break' (default) — the line is interrupted, like recharts' connectNulls={false}. An isolated point still gets its dot.
  • 'interpolate' — gaps are bridged linearly; synthetic points get no markers
  • 'zero' — nulls are treated as 0

Bars always render null as an empty slot (the category label remains).

Controlled selection

Omit selectedIndex and the chart manages selection itself. Pass it and you own it — useful for syncing a chart with an external list:

const [selected, setSelected] = useState<number | null>(null);

<BarChart
  data={data}
  selectedIndex={selected}
  onSelectionChange={setSelected}
  onPointPress={(e) => console.log(e.seriesName, e.label, e.value)}
/>;

Custom tooltips

<BarChart
  data={data}
  tooltip={{
    render: (e) => (
      <View style={styles.tip}>
        <Text>
          {e.seriesName}: {e.value}
        </Text>
      </View>
    ),
  }}
/>

Axis formatting

formatCompact (exported) is the default y-axis formatter: 1234 → '1.2K', 2500000 → '2.5M'. Compose it for currencies or percentages:

import { formatCompact } from 'react-native-pure-chart';

<LineChart
  data={revenue}
  yAxis={{ tickCount: 4, formatLabel: (v) => `$${formatCompact(v)}` }}
  xAxis={{ interval: 2 }}
/>;

Many points

Auto sizing never crams points closer than 40px (or bars thinner than 12px). Past that the chart grows wider than its viewport and scrolls horizontally, with the y-axis labels staying put. Set scrollable={false} to squeeze everything into the width instead, or initialScroll="end" to open on the most recent data.

Limitations

Honesty section — the price of the zero-dependency concept:

  • Smooth curves cost Views. curve="monotone" approximates the curve with short line segments (budgeted at ~400 per series). For very dense series prefer 'linear'.
  • Donut holes are opaque. The hole is a colored circle overlay, so donuts don't work on top of images/gradients. Set holeColor to match your background.
  • Large datasets: each point/segment is a View. A few hundred points are fine; for thousands of points use an SVG- or Skia-based library instead.

Migrating from 0.x

Version 2 is a complete rewrite with no backward compatibility. The old <PureChart type="..." /> component is gone — import charts directly:

0.x 2.x
<PureChart type="line" /> <LineChart />
<PureChart type="bar" /> <BarChart />
<PureChart type="pie" /> <PieChart />
data={[{x, y}]} data={[{value, label}]}
data={[{seriesName, data, color}]} data={[{name, data, color}]}
gap spacing
lineThickness (max 10) strokeWidth (unlimited)
numberOfYAxisGuideLine yAxis={{ tickCount }}
yAxisSymbol yAxis={{ formatLabel }}
showXAxisLabel / showYAxisLabel xAxis={{ show }} / yAxis={{ show }}
showEvenNumberXaxisLabel xAxis={{ interval }}
initialScrollPosition + initialScrollTimeOut initialScroll: 'start' | 'end'
onPress(index) onPointPress(event) — full event object
customValueRenderer renderValueLabel
defaultColumnWidth / defaultColumnMargin barWidth / groupGap
primaryColor / colors Series.color / palette

Development

npm install          # library dev deps
npm test             # unit tests for the core + component tests
npm run typecheck

cd example && npm install
npm start            # gallery app against the local ../src (live editing)
npm run start:npm    # gallery app against the published npm package

The example/ app is a gallery of every chart variant, including artifact stress tests (1° pie slices, high-contrast neighbors, missing data). It runs in two modes: npm start resolves the library from ../src for live development, while npm run start:npm resolves the version installed from npm — use it to smoke-test a release exactly as consumers get it. example/screens/ShotScreen.tsx is the reproducible source of every image in docs/ — flip SHOT_MODE in example/App.tsx, pick a group, screenshot.

License

MIT

About

react-native chart library that not using svg or ART but only using react native pure components.

Topics

Resources

Stars

296 stars

Watchers

10 watching

Forks

Releases

Packages

Used by

Contributors

Languages