A value-keyed series — the closed value-axis counterpart of
TimeSeries. Its key is a monotonic non-time axis (distance, cumulative
work, …). Two doors in: project a TimeSeries onto one of its monotonic
columns (TimeSeries.byValue(axis) — a track re-keyed by cumulative
distance), or construct directly from columnar arrays
(ValueSeries.fromColumns) when the data is natively value-keyed and
never had a meaningful time key per row — cross-sectional data such as an
options chain keyed by strike or a spectrum keyed by frequency.
ValueSeries carries the ordering-based operators (read the axis, read
value columns, nearest-by-value, slice-by-value) — the part of the series
algebra that was never really about time (RFC value-axis.md §5). The
calendar/clock operators (Sequence.every, tz formatting) are deliberately
absent: a value axis has no wall-clock semantics, and the disjoint
ValueSeriesSchema makes them type-impossible here.
Minimal by design (RFC §7: adopt the type early, grow the algebra as a second
value-axis consumer earns it). Wraps the columnar store directly — a value
row is an (axis, …values) tuple, not a Time-keyed Event, so it does not
go through the time-only SeriesStore / EventKey layer.
Properties
The axis (key) column's name — e.g. 'cumDist'.
Number of rows.
Static methods
fromColumnsstatic
fromColumns(input: { columns: Record<string, ReadonlyArray<number | null | undefined> | Float64Array>; name: string; schema: VS; sort?: boolean }): ValueSeries<VS>
ValueSeries.fromColumns({ name, schema, columns })The direct columnar door into value-land — for data that is natively
value-keyed and never had a meaningful per-row time key: an options chain
keyed by strike, a spectrum keyed by frequency, a profile keyed by depth.
(Data that starts life time-keyed projects in via TimeSeries.byValue
instead; before this door existed, cross-sectional callers had to launder
their axis through a fake time column just to reach
TimeSeries.fromColumns + byValue.)
The exact TimeSeries.fromColumns contract, with the axis in place of
time — the two doors share one ingest engine. schema[0] is the
'value'-kind axis column; each columns entry is one column's
values, keyed by schema column name and aligned by index. Values may be a
plain number[] or a Float64Array; a value cell is a gap (missing)
iff it's null/undefined or non-finite — identical rule for both input
types.
Float64Array inputs are adopted, not copied (zero-copy): the
resulting series' columns alias the caller's buffers; pass a fresh buffer
if that matters. (sort disables the adoption — a reorder needs its
own buffers.)
Ordering. The axis must be defined, finite, and non-decreasing —
it becomes the index (the same contract byValue enforces with
assertMonotonicAxis), so an out-of-order axis throws by default. Pass
sort: true to sort the rows by axis value before construction — the
stable sort every unordered snapshot wants (e.g. a keyed live feed that
delivers rows in update order, not axis order).
v1 scope: number value columns, matching TimeSeries.fromColumns.
Methods
axisAt
axisAt(i: number): number
The axis value at row i. Throws if out of range.
axisValues
axisValues(): Float64Array
The axis values (the x of every row), in axis order. Zero-copy — the
returned Float64Array is the live key buffer; treat it as read-only.
column
column(name: ValueSeriesColumnName<VS>): Column | undefined
A value column by name, for direct columnar reads (.read(i), .values()).
nearestIndex
nearestIndex(value: number): number
Index of the row whose axis value is closest to value — the
value-axis cursor primitive. The axis is non-decreasing, so this is a
binary search. Returns -1 for an empty series; clamps to the first / last
row when value is outside the axis extent.
sliceByValue
sliceByValue(lo: number, hi: number): ValueSeries<VS>
The contiguous sub-series whose axis value lies in [lo, hi) — the
value-axis cull (pan / zoom on a value x). Binary-searches the bounds and
zero-copy slices the store. lo >= hi (or a range outside the extent)
yields an empty series.