Askr documentation
UI & Components

Channels and Transforms

Channels and Transforms: anatomy, keyboard behavior, state, and theming in Askr.

Example

import { createPlot, movingAverage } from '@askrjs/charts';

const Plot = createPlot<ProjectRow>();

<Plot.Root data={rows} rowKey="id" label="Smoothed request rate">
  <Plot.Line x="timestamp" y={movingAverage('requests', { window: 7 })} />
</Plot.Root>

Channels

A channel on any mark accepts a bare row-field name, an accessor (row, index) => value, or a channel expression — nothing else. Bare strings are always interpreted as field names, so a literal string value (a fixed color, a fixed label) has to be wrapped in constant("...") or it will be read as a field lookup instead. Accessors can derive values inline, as in y={(row) => row.value / 1_000}.

Aggregation

bin(input, options?) groups a numeric or Date channel by thresholds, interval, or an explicit domain, and pairs naturally with count(), sum(input), or mean(input) on the paired channel to build histograms and grouped summaries. group(input) declares the grouping key for a compound aggregate. stack(input, options?) and normalize(input) turn a numeric channel into stacked or proportional values, with offset (zero, diverging, expand) and order controlling how series accumulate.

Window transforms

movingWindow(input, { window, operation?, partial? }) computes a rolling sum, mean, min, or max over a fixed row count, and movingAverage(input, { window }) is the rolling-mean shorthand of the same helper. regression(input, options?) fits a linear trend either against row index or an explicit x channel. All three skip missing numeric values rather than treating them as zero, which keeps trend lines honest over sparse data.

Row lineage

Mark-level transform runs before layout and never mutates the source rows — filterRows(predicate), sortRows({ by, direction? }), and partition({ id, parentId?, children?, value, padding? }) each produce a fresh, frozen row set. That copy happens as soon as any transform is configured at all, not just when a transform actually changes something — a filterRows predicate that happens to keep every row still gets a newly allocated, frozen array back, not the original reference; only marks with no transform configured at all skip the copy and pass the original array through untouched. Because rendering is driven off this transformed output, stable row keys are what let selection, transitions, and live updates track a row across filtering, sorting, or re-partitioning.