Live example:
app/demo/axes-and-grid.tsx
in the example app.Grid lines, axis spacing, and edge labels
Grid & axes
yAxis={false} or xAxis={false} to hide an axis. gridStyle.intervals of [] renders
solid grid lines.
xAxis.minGap sets the minimum pixel spacing between time labels (default 60).
Changing it updates the label set immediately on both chart components, including
paused charts and views parked by scrolling or zooming. Labels can be spaced
farther apart when their text would otherwise overlap.
The right gutter is measured with your formatValue and font once a finite,
nonzero live value arrives. A 0 loading placeholder does not lock it to a short
label. It measures again after the value moves by a factor of ten in either
direction from the last sample, keeping ordinary ticks and small changes around
a power of ten from moving the plot’s edge. LiveChartSeries samples the largest
finite absolute value across its series, including negative values alongside
zero placeholders. An explicit insets.right overrides automatic measurement.
Vertical grid lines
xAxis.gridLines adds a vertical line under each time label, so the grid runs in
both directions. The lines draw behind the series, in the same layer as the
horizontal grid, and fade in and out with their labels at the plot edges.
gridLines: true styles the lines with gridStyle. A GridStyleConfig overrides
only the fields it sets; the rest still come from gridStyle. The lines follow
the label spacing (xAxis.minGap), run down through a candle chart’s volume
band to the axis line, and hide with the axis (xAxis={false}) and under
LiveChart’s axisAutoHide. They are off by default, on both LiveChart and
LiveChartSeries. Strokes are clipped to the plot width, so a fading tick cannot
draw a grid line into the reserved Y-axis gutters.
Left-side Y-axis labels
BothLiveChart and LiveChartSeries accept yAxis.side; it defaults to "right".
Reserve enough room for your widest formatted label with insets.left:
float, labelRightMargin, gridEndGap) do not position
left labels. The live badge and multi-series value labels remain on their existing
side. Left labels render after the left-edge fade so the gutter stays readable.
Fixed Y-axis price count
By default the Y axis picks a dynamic number of “nice”-value grid lines fromminGap. Pass
yAxis={{ count }} to instead show a fixed number of prices, spaced evenly down the plot —
top label = current high, bottom = current low. The count stays constant as data streams in;
the values track the live range each frame (they aren’t rounded to nice numbers).
minGap still acts as a floor: if count labels won’t fit at least minGap px apart, the count
drops to what fits. The count is clamped to at most 15.
Nice intervals in display units
UseintervalScale when stored values and displayed values differ by a constant multiplier.
The chart keeps every point in its original units, but chooses dynamic grid intervals after
applying the multiplier. Apply the same multiplier in formatValue:
$9,876,543 and $10,864,198 into round $10,000,000 and
$11,000,000 intervals. The chart geometry, markers, reference lines, and scrub values stay in
their original units. The multiplier must be positive and finite; invalid values behave as 1.
If applying it would overflow or underflow the current live range, interval selection safely falls
back to source units. Fixed-count mode ignores it because { count } tracks the exact visible range
instead of choosing nice intervals.
Minimum Y range
On a short window the data can move less than one display step, so every grid label formats to the same text. PassminRange (in data units) to keep the fitted span at least that tall; flatter data
widens around its midpoint instead of zooming in:
minRange fits as before. On a nonNegative chart or under
maxValue, the span slides off the bound instead of shrinking, including when a bound clips
the ordinary fit’s margin. If both hard bounds leave less room than the requested floor,
the available span wins. Use positive finite values; invalid or overflowing floors are ignored.
Manual yRangeScale zoom still applies on top and can go below the floor.
LiveChartSeries accepts it too. The Minimum Y range demo compares
rounded labels, both engines, bounds, static re-settling, and manual zoom.
Right-anchored price label column
SetlabelRightMargin to place every Y-axis label at the same left X in a column whose right edge
is measured from the canvas edge. Add gridEndGap to keep a fixed gap between the grid-line end
and that column:
insets.right takes precedence.
Without labelRightMargin, labels keep their default centered-gutter placement and gridEndGap
has no effect. When combined with yAxis.float, labelRightMargin overrides the floating axis’s
default edge margin.
Plain horizontal referenceLines stop at the same X as the grid, keeping the gap consistent.
fullWidth reference lines and value/time bands keep their existing extents.
Badge value in the column
By default the live badge centers its value in the pill. To line it up with the label column, setbadge={{ textAlign: "yAxisColumn" }}: the value starts at the labels’ left X, and a value wider
than every axis label ends at the column’s right edge. Two trade-offs make this opt-in:
- The pill keeps its shape and the value stays within its
padXpadding, so the room on each side comes from the right inset and the badge metrics. Next to the tail, whose taper already keeps the value off the outline, a pill too narrow for both paddings gives up the left one first, then the right one, so the value moves continuously as the pill narrows. With an inset ofdotGap, the tail (tailLengthplus half the pill’s height), the value,padXandmarginEdge, the value endspadXfrom the pill’s end (the column’s right edge whenmarginEdge + padXequalslabelRightMargin). The value’s width is Skia’smeasureTextbox, which can be wider than the glyph advances. A value wider than a tailed pill’s body, or one that doesn’t fit both paddings of a pill without a tail, is centered. The automatic right inset reserves room for one more digit, which ends up left of the value. For even padding, sizeinsets.rightfor your widest price and setmetrics.badgesomarginEdge + padXequalslabelRightMargin. - The value moves sideways whenever the column’s width changes: a wider label entering or leaving
the axis (
99.95→100.00), or digits of different widths in a proportional font.
yAxis.side: "left", while the axis floats (yAxis.float; with timeScroll, only while scrolled
back), and with badge={{ position: "left" }}, the value stays centered. badge.offsetX moves the
value with the pill. To place a pill relative to the column yourself, turn the badge off and anchor
your own pill to the column in renderOverlay, as the
Phantom showcase
does for its target pill.
For an interactive comparison of centered and column-aligned values, including
the transition across $100 and padding/font fallbacks, open the
Badge alignment demo.
Axis edge labels
topLabel and bottomLabel float a value at the top / bottom edge of the plot — the
Robinhood-style high/low readout. Pass true for the batteries-included label: the chart shows
its current top (topLabel) and bottom (bottomLabel) Y-axis bound, formatted with the chart’s
formatValue and updated each frame on the UI thread.
AxisLabelConfig to tune the
built-in label — format, color, position ("left" | "right", default "right"), and the
text fontSize / fontWeight / fontFamily:
render escape hatch floats any element at the edge (it overrides the
built-in value):
To pin the label to the actual high / low data point instead of the edge —
position: "extrema"
/ "extrema-edge" — see Extrema labels.Spacing
Adjust the drawing area withinsets (top, right, bottom, left) and soften the left edge
with leftEdgeFade.