Skip to content

Drawing a Chart ​

Draw snapshot data with chart presets, arrange multiple Series, and choose how values are scaled. Each section shows a configuration for a particular result; use the ones your chart needs. Core Concepts explains how the parts fit together.

Basic chart ​

A line chart needs a target, a DataSource, and a Series that draws it. Usually, you will give the target an explicit height to make the data easier to see:

html
<div id="timescope" style="height: 240px"></div>

The examples below use this import and target. Each live chart runs the code shown alongside it:

js
import { Timescope } from 'timescope';
const target = '#timescope';
js
const samples = [12, 24, 18, 42, 35, 48, 20, 32, 26].map((value, index) => ({
  time: index * 7.5,
  value,
}));

const timescope = new Timescope({
  target,
  fit: [0, 60],
  sources: { samples },
  series: {
    signal: {
      data: { source: 'samples', color: '#0d9488' },
      chart: 'lines',
    },
  },
});
  • sources names the data inputs. samples is how a Series refers to this array, not a field in each row.
  • series names the Series. Each one chooses a source and a chart preset; without chart, it does not draw a Chart.
  • fit sets the initial visible interval. Numeric times are seconds by default, so this example spans 60 seconds from the Unix epoch.

With tracks and data.domain omitted, the chart uses the implicit default Track and an independent, automatically scaled linear Domain. No value axis is shown unless requested. A larger height is a presentation choice: a compact chart can also fit in 36px with a symmetric: true Track.

Basic Chart example · Edit in Playground

Choose a chart preset ​

Set chart on a Series to select its presentation. Choose a string below to redraw the same samples, starting with bars:filled. The data and value scale stay fixed so you can compare the presets:

To draw…Use…
Straight connections'lines'
Smooth connections'curves'
Individual samples'points'
Lines with sample markers'linespoints'
Step changes'steps'
Bars from the baseline'bars'
A filled area under a curve'curves:filled'

The selector passes its chosen string to this function:

js
function selectPreset(chart) {
  timescope.updateOptions({
    series: { signal: { chart } },
  });
}

updateOptions() merges the change, retaining the Series' data and other settings. You can also put the preset directly in the constructor. Connections change the drawing, not the source samples or the instantaneous value shown at the cursor.

All presets · Curve example · Custom Marks & Links

Use snapshot data ​

A snapshot supplies the complete dataset, whether it comes from an array, a URL, or a callback. The basic chart uses an array. To load the same row format from JSON, replace its source input:

ts
sources: {
  samples: { url: '/samples.json' },
}

For application-specific acquisition, use a callback with chunked: false:

ts
sources: {
  samples: {
    chunked: false,
    loader: async () => {
      const response = await fetch('/samples.json');
      if (!response.ok) throw new Error(`Samples: ${response.status}`);
      return response.json();
    },
  },
}

Use decoder or mappings if the response has a different structure. Snapshots remain loaded until invalidated; call timescope.reload(['samples']) when the input has changed. For a history too large to load at once, use Chunk Loading.

Plot dates or elapsed time ​

Rows can use date strings as well as numeric times:

ts
const samples = [
  { time: '2026-01-15T10:00:00Z', value: 18 },
  { time: '2026-01-15T11:00:00Z', value: 21 },
];

Date strings and Date inputs represent calendar instants. Numbers use the data's time units, seconds by default. Time strings are parsed as dates, not numeric strings; use a Decimal when a numeric timestamp needs more precision than a JavaScript number can retain.

For a recording whose times start at zero, display elapsed time on the Track:

ts
tracks: { default: { timeAxis: { relative: true } } }

For calendar labels, use timeAxis: { timeZone: 'utc' } or an IANA zone such as 'Asia/Tokyo'. Time-axis styling covers custom labels.

Overlay multiple Series ​

Use multiple Series on the same Track to compare signals. Give them the same named Domain when equal vertical positions should mean equal values:

js
const timescope = new Timescope({
  target,
  fit: [0, 30],
  sources: {
    indoor: [
      { time: 0, value: 18 },
      { time: 15, value: 21 },
      { time: 30, value: 19 },
    ],
    outdoor: [
      { time: 0, value: 12 },
      { time: 15, value: 16 },
      { time: 30, value: 14 },
    ],
  },
  domains: { temperature: { axis: 'left', unit: '°C' } },
  series: {
    indoor: {
      data: { source: 'indoor', domain: 'temperature', color: '#0d9488' },
      chart: 'linespoints',
    },
    outdoor: {
      data: { source: 'outdoor', domain: 'temperature', color: '#d97706' },
      chart: 'linespoints',
    },
  },
  tracks: { default: { timeAxis: { relative: true } } },
});

The shared Domain scales to both Series. Without it, each Series auto-scales independently, even when they share a Track. Series can also share a DataSource; Styling shows how to select different fields from the same rows.

Explore a larger comparison in the Multiple Series example or Playground.

Arrange separate Tracks ​

Put signals with different units in separate drawing regions while keeping their time axes aligned. Select a Track with the Series' track field:

js
const timescope = new Timescope({
  target,
  fit: [0, 30],
  sources: {
    temperature: [
      { time: 0, value: 18 },
      { time: 15, value: 21 },
      { time: 30, value: 19 },
    ],
    latency: [
      { time: 0, value: 8 },
      { time: 15, value: 42 },
      { time: 30, value: 12 },
    ],
  },
  series: {
    temperature: {
      track: 'temperature',
      data: { source: 'temperature', domain: { axis: 'left', unit: '°C' } },
      chart: 'lines',
    },
    latency: {
      track: 'latency',
      data: { source: 'latency', domain: { axis: 'left', unit: 'ms' } },
      chart: 'curves',
    },
  },
  tracks: {
    temperature: { timeAxis: false },
    latency: { timeAxis: { relative: true } },
  },
});

Tracks without an explicit height share the available height. This example gives its target height: 400px to leave room for both Tracks. All Tracks pan and zoom together; Domains determine value scales independently of that layout.

More multi-Track examples · Explore in Playground

Choose a value scale ​

Use a logarithmic Domain when values span several orders of magnitude. This configuration plots response times with a left value axis; the small bump near 44 seconds remains visible alongside the much larger initial values:

Choose scale to switch between logarithmic and linear scaling. The curvespoints preset shows the original samples alongside the smooth curve; the small bump is much easier to see on the logarithmic scale.

js
const timescope = new Timescope({
  target,
  fit: [0, 60],
  sources: {
    response: Array.from({ length: 16 }, (_, index) => {
      const time = index * 4;
      const decay = 10 ** (4 - time / 12);
      const bump = 1 + 6 * Math.exp(-(((time - 44) / 2.8) ** 2));
      return { time, value: decay * bump };
    }),
  },
  series: {
    response: {
      data: { source: 'response', domain: { scale: 'log', axis: 'left', unit: 'ms' } },
      chart: 'curvespoints',
    },
  },
  tracks: { default: { timeAxis: { relative: true } } },
});

Equal vertical distances now represent equal ratios. Logarithmic Domains draw positive values only; any specified bounds must also be positive. Leave scale unspecified for a linear scale.

Move the time cursor to inspect the response value, then choose a tooltip.round setting. Numbers 0–5 select decimal places: round: 3 displays three decimal places, while 0 rounds to a whole number. undefined leaves the tooltip value unrounded. This changes the label, not the data or the scale. For axis labels and exponential notation, see Number formatting.

The controls update these settings independently (scale and round are Vue refs in this example):

js
function updateScale() {
  timescope.updateOptions({
    series: { response: { data: { domain: { scale: scale.value } } } },
  });
}

function updateRound() {
  timescope.updateOptions({
    series: { response: { tooltip: { round: round.value } } },
  });
}

Bounds normally follow visible data. Use Domain range to set bounds and expand / shrink to control their adjustment; see auto scaling. Label formatting is independent of the scale: Styling covers decimal and exponential labels.

Compare the linear Curve example with the Log Scale example, or try different scales in Playground.

Decimation ​

For a dense recording, use a 'point-aggregate' source to summarize samples at the requested data resolution. This example contains 262,144 samples: 64 seconds at 4096 Hz, combining a slow, 16-second wave with fine sawtooth detail. The overview shows a smooth trend; zoom in repeatedly around any part of the wave to reveal the jagged detail and individual sample points.

js
const sampleRate = 4096;
const duration = 64;
const timescope = new Timescope({
  target,
  fit: [0, duration],
  sources: {
    recording: {
      type: 'point-aggregate',
      data: Array.from({ length: sampleRate * duration }, (_, index) => {
        const time = index / sampleRate;
        const wave = Math.sin((2 * Math.PI * time) / 16);
        const detail = ((index % 17) - 8) / 40;
        return { time, value: wave + detail };
      }),
    },
  },
  series: {
    signal: { data: { source: 'recording' }, chart: 'linespoints' },
  },
  tracks: { default: { timeAxis: { relative: true } } },
});

The preset draws the average of each bucket, not a selection of the original samples. When zoomed out, the circles in linespoints represent those averages too. Fine variations and brief peaks can disappear in the average; zooming in requests finer buckets until individual samples become visible.

The source also exposes minimum and maximum values, so you can preserve the signal's envelope with custom Marks & Links. The Decimation example combines an envelope with the average line.

Use 'point-percentile' for percentile summaries. Both summary source types accept snapshot points, not interval rows or range loaders. See source types and output fields for the available summaries.

Explore further ​

  • Styling — change the appearance and build your own Charts with Marks and Links.
  • Chunk Loading — load large histories at the visible range and resolution.
  • Live Streaming — draw arriving samples and follow the latest time.
  • Gallery — explore more combinations and open their configuration in Playground.