Core

The scales, ticks, curves and solvers on their own — importable with no Skia in the module graph.

import { closestIndex, invertValue, makeScale, scaleValue } from "@delacour/react-native-charts/core";

@delacour/react-native-charts/core is every number the package computes and nothing that draws. Nothing under it imports a runtime value from React, React Native, Skia, Reanimated or Gesture Handler, which is what lets a legend or a tooltip do scale arithmetic without mounting a canvas — and what lets the whole of it run under bun test with no simulator.

import { buildTicks, makeScale, scaleValue } from "@delacour/react-native-charts/core";

const y = makeScale({ kind: "linear", domain: [0, 240], range: [200, 0], nice: true });
const ticks = buildTicks(y, { count: 4 });
const zero = scaleValue(y, 0);

A scale is data

d3's scales are closures, and a closure cannot be read on the UI thread. makeScale lets d3 do the nicing and the time arithmetic, then throws the closure away and returns a ScaleDescriptor of plain numbers. Everything downstream — the marks, the ticks, the scrub — reads that.

type ScaleDescriptor =
  | { kind: "linear"; domain: [number, number]; range: [number, number] }
  | { kind: "time"; domain: [number, number]; range: [number, number] }
  | { kind: "log"; domain: [number, number]; range: [number, number]; base: number };

A degenerate input returns a finite number, never NaN: a zero-width domain maps to the range midpoint, a collapsed plot rect stays a rect, an unreadable log domain yields no ticks rather than -Infinity ones. One NaN written into a shared value freezes a chart permanently with nothing on screen to say why, so the guard is at the bottom rather than at every call site.

The flat worklets

Six functions carry a "worklet" directive and run on the UI thread every frame of a scrub: scaleValue, invertValue, closestIndex, getYForX, readAt and sliceIndexAt. Each calls nothing and closes over nothing but module-scope constants — a module-scope worklet that called another would bind at import time and hand the UI thread undefined is not a function the moment a finger landed. A worklet you build inside a hook is captured by ordinary closure and may call any of them, which is how the scrub gesture composes closestIndex and getYForX without duplicating either.

Scales and ticks

ExportWhat it does
makeScale({ kind, domain, range, nice?, base? })Builds a scale and returns its descriptor
scaleValue(scale, value)A domain value's position along the range. Flat worklet
invertValue(scale, position)The domain value at a position. Flat worklet
buildTicks(scale, { count?, values? })Tick values, each already positioned on the canvas
linearTicks, logTicks, timeTicksThe generators buildTicks dispatches to
downsampleTicks(values, count)At most count of values, evenly, both ends kept
normalizeTickCount(domain, count)Exactly count values across a domain, so two axes share gridlines
DEFAULT_TICK_COUNT5
ScaleDescriptor, ScaleType, ChartTickThe types

Curves and paths

Every builder returns an SVG path string. This module cannot import Skia's path builder; toSkPath in the React surface parses the string.

ExportWhat it does
CURVES, CURVE_TYPES, APPROXIMATING_CURVESThe d3-shape interpolators by name, the names, and the ones that do not pass through their data
buildLinePath(points, { curve?, connectMissingData? })A path through the points; a gap is a new subpath
buildAreaPath(points, { baseline, curve?, lower?, orientation? })The same, closed against a baseline or a lower edge
isDrawable(point)Both coordinates finite, and not a gap
rectPath(left, top, right, bottom, radii)A rectangle as M C L C L C L C Z — every corner a cubic
barRects, barsPathFromRects, buildBarsPath, resolveBaselinePoints or segments to bar rects, and one path for all of them
buildScatterPath(points, { radius, shape, baseline, orientation })A circle, square or star per point, on a fixed verb sequence
candlePoints, buildCandlePaths, candleSentiment, SENTIMENTSCandles zipped from four series; six paths, every candle in each
KAPPAThe cubic control-point distance that best approximates a quarter circle

Geometry

ExportWhat it does
resolveDomain({ values, domain?, padding?, absolutePadding?, includeZero? })The domain a scale should take
resolveDomainPadding(value)domainPadding decoded to one number per axis
resolveStep(xValues)The smallest gap between x values — what a bar's width is stated in
resolveBand, groupLayoutA datum's band on a scale with no band of its own, and where a group's bars sit inside it
stackSeries, collectStackedYValuesSeries stacked in data space, and the totals a stacked y domain needs
categoryDomainCovers(series, scale, step)Whether the domain holds the outermost bars — what ChartBar warns about
getChartBounds(canvas, padding?, gutters?), hasAreaThe plot rect, and whether it has room to draw in
planAxis, resolveChartFrame, axisRanges, pickAxisRoles, placeAxisRolesThe two layout passes, and the category/value roles onto the canvas axes
resolveXValues(data, xKey)The numeric-or-index decision, made once for the whole series
transformInputData({ data, yKeys, xValues, xScale, yScale, orientation })Rows to plotted points
gridSegments({ bounds, xTicks, yTicks, axis })One line segment per rule
resolveAxisGutters, anchorX, anchorY, labelHeight, barLabelAnchorMeasured label widths to the space an axis needs, and text placed against a point
defaultTickFormat, formatNumberTick, formatDateTick, categoricalTickFormatThe label formats the root falls back to

Interaction

ExportWhat it does
toCurvePath(svg)An SVG path string to flat cubic runs, for the UI thread. Every L becomes a cubic
closestIndex(xs, x)Nearest datum, by binary search. Flat worklet
getYForX(path, x)y on the drawn curve, by bisection. NaN inside a gap. Flat worklet
readAt(list, index)A list read that yields NaN for a gap or a missing index. Flat worklet
runSegmentCount, RUN_STRIDEHow a run is laid out

Animation

ExportWhat it does
chooseMorphStrategy(previousXs, nextXs)"none", "pad-end", "pad-start" or "resample" — which end to pad, and when to resample instead
matchPointCounts, resamplePointsTwo series brought to one length

Polar

ExportWhat it does
collectPieInput(data, valueKey, labelKey?)Rows to values and labels, nothing dropped
resolvePolarLayout({ canvas, padding?, size?, innerRadius? }), resolveInnerRadiusThe largest circle the padded canvas holds, and the hole inside it
resolveSlices({ values, labels, layout, startAngle, circleSweepDegrees })Values to angles; sweeps sum to the circle exactly
buildSlicePath(slice), buildSliceEdgePath(slice, edge)A slice as eleven verbs, and one of its edges
sliceLabelPosition(slice, radiusOffset)A point on the bisector, offset across the annulus
sliceIndexAt(x, y, cx, cy, innerRadius, radius, starts, sweeps)Which slice is under a point. Flat worklet
sliceOpacity(index, selectedIndex, opacity, dimUnselected)What one slice draws at while another is selected
polarToCartesian, normalizeDegreesDegrees clockwise from 12 o'clock to a canvas point
PieSliceData, PolarLayout, InnerRadius, PolarPointThe types

Utilities

ExportWhat it does
asNumber, isPlottableA field to a number, or null
clamp(value, lo, hi)
extent(values), unionExtentsMin and max, ignoring gaps
sidesOf(value), valueFromSidedNumberA SidedNumber — one number or four — decoded per side
ChartPoint, ChartSegment, ChartBounds, ChartSize, ChartRow, ChartOrientationThe shapes everything above shares

On this page