Something went wrong

Thank you for being patient! We're working hard on resolving the issue

Data Series - Lona Docs Log in

Data Series

Data rows are the SDK's charting entry point. They let you read synced source data, extract series, inspect rendering presets, and, when needed, compute or query regression curves.

For engineering work, the important split is:

  • DataVariant on a data row gives you a DataIndex
  • DataSeriesVariant on a data-series child row gives you a RegressionIndex
  • kernelSmooth() and impute() are the low-level math utilities

Mental Model

Data rows usually appear with virtual children:

├── [data] "Garmin Stress" (:~garmin:stress)
│   ├── [data-canvas]
│   │   ├── [data-renderer]    ← points / curve / band / rules
│   ├── [data-series]          ← one named series
│   ├── [data-series]          ← another named series
│   └── [data-json]

What each piece does:

Row typePurpose
dataOwns the synced source cells and exposes DataIndex
data-seriesDeclares one named series and can expose RegressionIndex
data-canvasDeclares chart-level config such as axes and render layers
data-rendererDeclares visual layers such as points, rules, and regression curves
data-jsonRaw JSON/debug view

Registering Indexes

If you want chart data and regression data available through row variants, register the factories when creating the client:

import { DataIndex, LonaSdk, RegressionIndex } from "@tento-lona";

const client = new LonaSdk.Client({
  ...config,
  rowIndexFactories: [
    DataIndex.factory(),
    RegressionIndex.factory(),
  ],
});

DataIndex.factory() attaches to data rows. RegressionIndex.factory() attaches to data-series rows whose seriesType is "regression".

Accessing A Data Row

Start from the data row itself:

import { DataVariant } from "@tento-lona";

const dataRow = sheet.row({ type: "data" });
if (!dataRow) throw new Error("no data row");

const variant = dataRow.variant as DataVariant;
const dataIndex = variant.dataIndex;
if (!dataIndex) throw new Error("DataIndex not available");

If you started from a child row such as data-canvas or data-series, walk to its parent first:

const parent = childRow.parent();
if (!parent?.is("data")) throw new Error("expected a data row parent");

const dataIndex = (parent.variant as DataVariant).dataIndex;

Querying Series With DataIndex

DataIndex is the main read API for extracted chart series.

const series = dataIndex.getSeries("default");
if (!series) throw new Error("missing series");

console.log(series.key);           // "default"
console.log(series.type);          // "regression" | "default"
console.log(series.label);         // optional display label
console.log(series.points.length); // extracted [x, y] points
console.log(series.yRange);        // optional axis override
console.log(series.convHalfWidth); // optional smoothing hint
console.log(series.maxGap);        // optional segmentation hint
console.log(series.minPoints);     // optional regression threshold
console.log(series.minCoverage);   // optional regression threshold

Enumerate every series:

for (const [key, series] of dataIndex.getAllSeries()) {
  console.log(key, series.type, series.points.length);
}

Read only the points in an x-range:

const points = dataIndex.getPointsInRange("default", xStart, xEnd);

Building A DataFrame

For rendering a visible column window, build a frame from the index:

import { NaiveDate, TimezoneRegion } from "@tento-chrono";

const range = new NaiveDate.Range(
  NaiveDate.fromYmd1(2026, 3, 10).exp(),
  NaiveDate.fromYmd1(2026, 3, 17).exp(),
);

const frame = dataIndex.getDataFrame(range, TimezoneRegion.UTC);
if (!frame) throw new Error("no frame");

console.log(frame.xRange);        // { start, end }
console.log(frame.yAxis);         // optional { min, max, center }
console.log(frame.rendererSpecs); // declarative render layers

Each frame contains:

FieldTypeMeaning
xRange{ start, end }Numeric x-range for the requested date window
yAxisOption<{ min, max, center }>Y-axis config from the preset
seriesReadonlyMap<string, DataFrameSeries>Windowed series data
rendererSpecsRendererSpec[]Declarative render layers

Each DataFrameSeries contains:

FieldTypeMeaning
keystringSeries key
pointsreadonly [number, number][]Raw points in the requested window
yRangeOption<{ min, max, center }>Series-specific axis override

Important: DataFrameSeries does not include regression output. Regression curves come from RegressionIndex, described below.

Reading Regression Data

Regression data lives on data-series rows, not on DataFrameSeries.

import { DataSeriesVariant } from "@tento-lona";

const seriesRow = dataRow.children().find((row) => row.is("data-series"));
if (!seriesRow) throw new Error("no data-series child");

const seriesVariant = seriesRow.variant as DataSeriesVariant;
const regressionIndex = seriesVariant.regressionIndex;
if (!regressionIndex) throw new Error("RegressionIndex not available");

console.log(seriesVariant.seriesKey);
console.log(seriesVariant.seriesType);
console.log(seriesVariant.color);
console.log(regressionIndex.seriesKey);
console.log(regressionIndex.points.length);

Query regression output in a visible range:

const frame = dataIndex.getDataFrame(range, TimezoneRegion.UTC);
if (!frame) throw new Error("no frame");

const rawPoints = regressionIndex.getPointsInRange(
  frame.xRange.start,
  frame.xRange.end,
);

const curve = regressionIndex.getRegressionInRange(
  frame.xRange.start,
  frame.xRange.end,
);

for (const point of curve) {
  console.log(point.x, point.y, point.confidence, point.density, point.trust);
}

Each regression point contains:

FieldMeaning
xEvaluation coordinate
ySmoothed value
confidenceConfidence band width
densityData density within the kernel window
trustOverall support signal for rendering decisions

Renderer Specs

frame.rendererSpecs describes what the preset expects to be drawn. These are declarative layer descriptions, not rendered objects.

for (const spec of frame.rendererSpecs) {
  switch (spec.type) {
    case "points":
    case "regression-curve":
    case "regression-band":
    case "hrule":
    case "hrule-all":
    case "bar-chart-with-width":
    case "buckets":
      console.log(spec.id, spec.type, spec.key);
      break;
  }
}

Common spec fields:

FieldMeaning
idStable renderer identifier
typeLayer type
keyOptional series key
colorOptional color
radiusPoint radius
offset / offsetTypeRule placement
centerCenter value for rule families
barColor, topColor, bottomColorLayer-specific colors

Common renderer types in built-in presets:

TypeMeaning
pointsRaw data points
regression-curveSmoothed trend line
regression-bandConfidence band fill
hruleSingle horizontal rule
hrule-allMultiple reference rules
bar-chart-with-widthWidth-aware bars
bucketsBucketed aggregates

Low-Level Smoothing Utilities

If you want to run smoothing yourself, use the exported math helpers:

import { impute, kernelSmooth } from "@tento-lona";

const filled = impute(
  [
    [0, 70],
    [2, 74],
    [5, 73],
  ],
  1,
);

const result = kernelSmooth(filled, 2);

for (const point of result.curve) {
  console.log(point.x, point.y, point.confidence, point.density, point.trust);
}

Use impute() when you want to fill gaps before smoothing. Use kernelSmooth() when you already have sorted [x, y] points and want a smoothed curve with confidence metadata.

Coordinate Transforms With LinearYScaler

If you need a scaler for rendering, LinearYScaler is the public low-level utility for converting data values to canvas coordinates:

import { castData, LinearYScaler } from "@tento-lona/sheets";

if (!frame.yAxis) throw new Error("missing y-axis");

const yScaler = new LinearYScaler({
  range: frame.yAxis,
});

const canvasY = yScaler.d2c(castData(75), height, padding);
const dataY = yScaler.c2d(canvasY, height, padding);
const ticks = yScaler.getTicks();

See Also