Skip to content

Live Streaming ​

Draw samples as they arrive and keep the view on the latest data. Data updates and time navigation are independent: append to a source to change the signal, and advance the clock to follow it. Try the Live Stream example to see both together.

Press Start to append samples, pan away to inspect history, and use Follow live to return to the newest data. The demo starts with six seconds of a synthetic pulse signal and produces new samples at 100 Hz. The excerpts below come from this running example.

Append incoming samples ​

Use a 'point-aggregate' source for an ordered stream. Its append() method updates the chart automatically and supports decimation as the recording grows. Here pulse(time) generates a sample value; an application would receive those values from its data producer:

js
const sampleRate = 100;
const frameInterval = 1 / 60;
let index = 600;
let playbackTime = (index - 1) / sampleRate;
let disposed = false;
let appending = false;
const source = createDataSource({
  type: 'point-aggregate',
  data: Array.from({ length: index }, (_, i) => ({ time: i / sampleRate, value: pulse(i / sampleRate) })),
});

Each timer tick generates the next batch, awaits append(), then advances the playback clock. The timer skips ticks while a batch is in progress or the stream is paused:

js
const nextTime = playbackTime + frameInterval;
const nextIndex = Math.floor(nextTime * sampleRate) + 1;
const rows = Array.from({ length: nextIndex - index }, () => {
  const time = index++ / sampleRate;
  return { time, value: pulse(time) };
});
await source.append(rows);
if (disposed) return;
playbackTime = nextTime;
timescope.setPlaybackTime(playbackTime);
Chart configuration used in this example

The source is registered as signal. The drawing combines an average line with a min/max envelope, as in Styling. running is initially false, so the first view is paused at the latest recorded sample. target is the demo's host element.

js
const options = {
  target,
  time: running ? null : playbackTime,
  zoom: 6,
  sources: { signal: source },
  series: {
    signal: {
      tooltip: { round: 3 },
      data: {
        source: 'signal',
        name: 'Pulse',
        color: '#e11d48',
        domain: { range: [-0.35, 1.1], axis: 'left' },
        instantaneous: { resolution: 1 / sampleRate },
      },
      chart: {
        links: [
          { draw: 'area', using: ['value#min', 'value#max'], style: { fillColor: '#fda4af', fillOpacity: 0.5 } },
          { draw: 'line', using: 'value#avg', style: { lineColor: '#e11d48', lineWidth: 1.5 } },
        ],
      },
    },
  },
  tracks: { default: { timeAxis: { relative: true } } },
};
const timescope = new Timescope(options);
timescope.setPlaybackTime(playbackTime);

Supply points in time order, including across calls. Equal times are allowed, but a new point cannot precede an earlier one. Serialize incoming batches if your producer invokes an asynchronous receiver without awaiting it.

There is no need to call reload() after appending. Invalidation replaces the source's snapshot from its original input, so it would discard appended points absent from that input. Only 'point-aggregate' supports appending; see the append API for input transforms and completion semantics.

Follow live time or playback ​

time: null follows the wall clock by default. Use that for samples timestamped in Unix epoch seconds. For elapsed recording time or media playback, supply an application-driven clock with setPlaybackTime():

ts
timescope.setPlaybackTime(12.5);
timescope.setTime(null, false); // Follow the playback clock immediately.
timescope.setPlaybackTime(12.6); // Advance it on the next data or media tick.

The playback clock does not advance by itself. Update it in the same time units as your data. Appending samples does not move the view, and changing playback time does not append samples. A range-loaded source acquires data as needed when the followed view moves.

Users can pan away from the live position to inspect history. A "Follow live" control can call timescope.setTime(null, false) to resume following. Call timescope.setTime(30, false) to hold a selected time, or timescope.setPlaybackTime(null) to restore the wall clock.

Refresh changed data ​

For a remote history, use Chunk Loading and notify its DataSource when new or corrected samples are available. For example, if the chart uses a source named history:

ts
history.invalidate([120, 180]); // Corrected samples in this interval.
history.invalidate([180, undefined]); // New or corrected samples from time 180 onward.

Include late-arriving samples in the invalidated interval. Timescope reacquires affected data on demand, allowing the same range endpoint to serve both past and current data.

For a complete snapshot, call timescope.reload(['measurements']) after its URL, callback result, or input array has changed. This reloads the entire snapshot; range invalidation does not make a snapshot incremental. Omit the names to reload all sources.

Neither invalidate() nor await reload() waits for replacement data and drawing. When you need completed pixels, use a prepared view.

Stop a stream ​

When removing the view, stop the application-owned subscription or timer and dispose the Timescope instance. Framework components dispose the instance automatically, but your application still owns its data producer. See lifecycle handling.