Skip to main content

Queries

Methods that interrogate a TimeSeries without changing it. None of these mutate; none of them return a new TimeSeries (except the subseries selectors at the bottom, which still leave the source untouched). Reach for these when you need to ask the series for something rather than transform it.

const cpu = TimeSeries.fromJSON({ name, schema, rows });
cpu.length; // 5
cpu.first()?.get('cpu'); // 0.31 — one row, via an Event
cpu.column('cpu').mean(); // 0.476 — a whole column, no per-row Events
cpu.timeRange(); // TimeRange — .begin() / .end() in epoch ms
cpu.tail('30s').reduce({ cpu: 'avg' }); // current state

Two ways to read: the row API (Events — first(), at(i), get('cpu'), iteration) for per-event logic, and the column API (column('cpu') — reductions and typed-array access) for throughput. The columnar backing (added in v0.18.0) made the column path first-class; the row API is unchanged. This page covers reading via rows; the column path is its own page (Columns), summarized under Reading columns below.

Counts and bounds

length

Number of events. Plain property.

series.length; // number

timeRange()

Overall extent as a TimeRange, or undefined for an empty series. It's a TimeRange instance — .begin() / .end() return the bounds in epoch milliseconds (not .start / .end fields):

const r = series.timeRange(); // TimeRange | undefined
r?.begin(); // number (epoch ms)
r?.end();

events

The rows as an array of Event objects — for when you need an Array.map / Array.find shape rather than the TimeSeries API:

series.events; // ReadonlyArray<EventForSchema<S>>

A TimeSeries is backed by typed columns, not Event objects, so events materializes one Event per row on first access (lazily, then memoized). That's fine for row-shaped logic, but for a scan or a reduction over a single field, read the column instead — see Reading columns.

Single events

at(index)

Event at a positional index, or undefined if out of bounds. Negative indices count from the end (-1 is the last event).

series.at(0); // first event, or undefined
series.at(-1); // last event, same as series.last()

first() / last()

series.first(); // EventForSchema<S> | undefined
series.last(); // EventForSchema<S> | undefined

find(predicate)

First event matching a predicate, like Array.prototype.find.

const spike = series.find((e) => (e.get('cpu') ?? 0) > 0.9);

Reading columns

To read values rather than events, go through a column instead of materializing an Event per row. series.column(name) returns a typed, read-only view of one field, narrowed by the schema:

const cpu = series.column('cpu'); // Float64Column (number kind)
cpu.mean(); // number | undefined — one validity-aware pass
cpu.minMax(); // [min, max] | undefined — the Y-extent, one scan
cpu.at(5); // number | undefined — the value at row 5
cpu.toFloat64Array(); // dense Float64Array for a canvas / WebGL loop

Numeric columns carry the scalar reductions (min / max / sum / mean / stdev / median / percentile / count / minMax); string columns have uniqueCount(), boolean columns all() / any() / none(). series.keyColumn() is the same idea for the time axis. The column API (added with the columnar backing in v0.18.0) is the throughput path — no per-row Event — while the row methods above stay the default for per-event logic.

This is the short version; the full column surface (missing-value handling, bin, scan, chunked columns) is on the Columns page.

Key-based lookups

For series with sorted keys (which is every TimeSeries), these are binary-search lookups in O(log N). Pass a Time, Interval, TimeRange, or raw timestamp.

includesKey(key)

Does any event match this key? true / false.

series.includesKey(new Time(0)); // boolean
series.includesKey(60_000); // also accepts a raw timestamp

bisect(key)

The index where this key would be inserted to keep order. Useful for "find the position of a timestamp without requiring an exact match":

const i = series.bisect(targetTimestamp);
// 0 ≤ i ≤ series.length

atOrBefore(key) / atOrAfter(key)

The nearest event that doesn't overshoot the key. Common pattern: "the most recent reading at or before t":

const previous = series.atOrBefore(Date.now());
const next = series.atOrAfter(Date.now());

nearest(key)

The single event closest to the key by begin() distance, either side (vs atOrBefore/atOrAfter, which pick a direction). undefined for an empty series:

series.nearest(Date.now()); // the closest reading to now

Predicates over the series

some(predicate) / every(predicate)

Array-style predicate scans:

series.some((e) => (e.get('cpu') ?? 0) > 0.9); // any spike?
series.every((e) => e.get('host') !== undefined); // all hosts known?

Subseries selection

These return a new TimeSeries<S> with the same schema and a subset of events. The source is untouched.

slice(begin?, end?)

Positional, like Array.slice. Negative indices supported.

const head = series.slice(0, 100); // first 100 events
const tail = series.slice(-10); // last 10 events

tail(duration)

Trailing time window measured from the last event's begin(). "The last 30 seconds":

series.tail('30s'); // TimeSeries<S>
series.tail('5m').reduce({ cpu: 'avg' }); // current state

tail() with no argument is the identity.

before(boundary) / after(boundary)

Events strictly before / after a temporal boundary (timestamp or event key):

series.before(Date.now() - 60_000); // events older than 1m
series.after(start); // events newer than `start`

overlapping(range) / containedBy(range)

MethodKeeps events…
overlapping(range)whose key intersects the range (any overlap)
containedBy(range)whose key is fully inside the range
within(range)same as containedBy (alias for ergonomics)
const overlapping = series.overlapping(yesterdayRange);
const fully = series.containedBy(yesterdayRange);

trim(range)

Like overlapping(range), but partially-overlapping events get their keys clipped to the range boundaries. Use this when you want a subseries that "fits" inside a window without leaking past either edge:

const clipped = series.trim(reportingWindow);

Column projection

Where the selectors above narrow the rows, these narrow the columns — both return a new TimeSeries (with a narrowed schema), zero-copy, source untouched.

select(...keys)

Keep the key column plus the named value columns; drop the rest. The result type narrows to just those fields:

series.select('cpu', 'healthy'); // TimeSeries with only cpu + healthy

rename(mapping)

Relabel columns:

series.rename({ cpu: 'load' }); // 'cpu' column becomes 'load'

Range predicates

These return a boolean or a TimeRange, not a series.

overlaps(other)

Does this series's timeRange() overlap another?

seriesA.overlaps(seriesB); // boolean
seriesA.overlaps(rangeB); // also accepts a TimeRange directly

contains(other)

Is the other range / series fully inside this one's timeRange()?

day.contains(meeting); // boolean

intersection(other)

The shared TimeRange, or undefined if no overlap:

day.intersection(meeting); // TimeRange | undefined

Iteration

TimeSeries is iterable. for...of walks events in order; spread yields the underlying array:

for (const event of series) {
console.log(event.begin(), event.get('cpu'));
}

const all = [...series]; // ReadonlyArray<EventForSchema<S>>

For array methods over the Event rows, reach for series.events. But for a reduction over a single column, the column API is the one-pass, validity-aware path — and skips materializing Events entirely:

series.events.reduce((s, e) => s + (e.get('cpu') ?? 0), 0); // row API
series.column('cpu').sum(); // same result, one column pass

Output forms

These are query-flavored too — they read the series and return data in a different shape, without producing a new TimeSeries.

MethodReturns
toJSON({ rowFormat })JSON-friendly payload accepted by TimeSeries.fromJSON(...)
toRows()Array of normalized row tuples — same shape that new TimeSeries({ rows }) takes
toObjects()Array of objects keyed by schema column name
toPoints()Wide { ts, ...values }[] rows for charting (ts is event.begin())
toArray()Shallow, mutable copy of the Event array (like events, but not readonly)

Every form here materializes row / Event objects — they're the row-API boundary. For a dense typed array of a single field (the canvas / WebGL path), stay columnar with series.column('cpu').toFloat64Array() — see Reading columns.

toPoints is the chart-bridge — see Charting. toJSON and friends are the serialization story — see Creating series.

See also

  • Transformations — once you've found the event(s) you want, the methods that change them.
  • ReshapinggroupBy, pivotByGroup, join for when "interrogate" turns into "combine sources".
  • Cleaning datafill, filter for normalizing what queries return.