` (`uplot.root`).
- `width` and `height` are required dimensions in *logical* [CSS] pixels of the plotting area & axes, but **excluding** `title` or `legend` dimensions (which can be variable based on user CSS).
- `spanGaps` can be set to `true` to connect `null` data points.
- For a series to be rendered, it **must** be specified in the opts; simply having it in the data is insufficient.
- All series' options are optional; `label` will default to "Value" and `color` will default to "black".
- Series' line `width` is specified in *physical* [device] pixels (e.g. on high-DPI displays with a pixel ratio = 2, `width: 1` will draw a line with an effective width of 0.5 logical [CSS] pixels).
- `color`, `width`, `fill`, and `dash` map directly to Canvas API's [ctx.strokeStyle](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/strokeStyle), [ctx.lineWidth](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/lineWidth), [ctx.fillStyle](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fillStyle), and [ctx.setLineDash](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/setLineDash).
---
#### High/Low Bands
High/Low bands are defined as two adjacent series in the `data` (in high,low order) and setting `series.band = true` in the opts.
```js
const opts = {
series: {
y: [
{
label: "Low",
fill: "rgba(0, 255, 0, .2)",
band: true,
},
{
label: "High",
fill: "rgba(0, 255, 0, .2)",
band: true,
},
],
},
};
```
---
#### Scales, Axes, Grid
uPlot's API strives for brevity, uniformity and logical consistency.
Understanding the roles and processing order of `data`, `series`, `scales`, and `axes` will help with the remaining topics. The high-level rendering flow is this:
1. `data` is the first input into the system.
0. `series` holds the config of each dataset, such as visibility, styling, labels & value display in the legend, and the `scale` key along which they should be drawn. Implicit scale keys are `x` for the `data[0]` series and `y` for `data[1..N]`.
0. `scales` reflect the min/max ranges visible within the view. All view range adjustments such as zooming and pagination are done here. If not explicitly set via opts, `scales` are automatically initialized using the `series` config and auto-ranged using the provided `data`.
0. `axes` render the ticks, values, labels and grid along their `scale`. Tick & grid spacing, value granularity & formatting, timezone & DST handling is done here.
---
#### Multiple Scales & Axes
Series with differing units can be plotted along additional scales and display corresponding y-axes.
1. Use the same `series.scale` key.
2. Optionally, specify an additional `axis` with the `scale` key.
```js
let opts = {
series: {
y: [
{
label: "CPU",
color: "red",
scale: '%',
value: rawValue => rawValue.toFixed(1) + "%",
}
{
label: "RAM",
color: "blue",
scale: '%',
value: rawValue => rawValue.toFixed(1) + "%",
},
{
label: "TCP",
color: "green",
scale: 'mb',
value: rawValue => rawValue.toFixed(2) + "MB",
},
]
},
axes: {
y: [
{
scale: '%',
values: ticks => ticks.map(rawValue => rawValue.toFixed(1) + "%"),
},
{
scale: 'mb',
values: ticks => ticks.map(rawValue => rawValue.toFixed(2) + "MB"),
side: 3,
grid: {show: false},
},
]
},
};
```
- `side` is the where to place the axis (0: bottom, 1: left, 2: top, 3: right).
#### Axes for Alternate Units
Sometimes it's useful to provide an additional axis to display alternate units, e.g. °F / °C. This is done using derived scales.
```js
let opts = {
series: {
y: [
{
label: "Temp",
color: "red",
scale: 'F',
},
]
},
axes: {
y: [
{
scale: 'F',
values: ticks => ticks.map(rawValue => rawValue.map(v => v + '° F'),
},
{
scale: 'C',
values: ticks => ticks.map(rawValue => rawValue.map(v => v + '° C'),
side: 3,
grid: {show: false},
}
],
},
scales: {
'C': {
base: 'F',
range: (baseMin, baseMax) => [
(baseMin - 32) * 5/9,
(baseMax - 32) * 5/9,
],
}
},
```
- `base` specifies the key of the scale from which another is derived.
- `range` converts the base scale's min/max into the new scale's min/max.
---
#### Scale Opts
If a scale does not need auto-ranging from the visible data, you can provide static min/max values.
This is also a performance optimization, since the data does not need to be scanned on every view change.
```js
let opts = {
scales: {
'%': {
auto: false,
range: [0, 100],
}
},
}
```
The default x scale is temporal, but can be switched to plain numbers. This can be used to plot functions.
```js
let opts = {
scales: {
'x': {
time: false,
}
},
}
```
A scale's default distribution is linear `type: 1`, but can be switched to indexed/evenly-spaced.
This is useful when you'd like to squash periods with no data, such as weekends.
Keep in mind that this will prevent logical temporal tick baselines such as start of day or start of month.
```js
let opts = {
scales: {
'x': {
type: 2,
}
},
}
```
---
#### Axis & Grid Opts
Most options are self-explanatory:
```js
let opts = {
axes: {
y: [
{
show: true,
label: "Population",
width: 50,
class: 'my-y',
color: 'red',
grid: {
show: true,
color: "#eee",
width: 2,
dash: [],
}
}
]
},
}
```
Customizing the tick/grid spacing, value formatting and granularity is somewhat more involved:
```js
let opts = {
axes: {
x: {
space: 40,
incrs: [
// minute divisors (# of secs)
1,
5,
10,
15,
30,
// hour divisors
60,
60 * 5,
60 * 10,
60 * 15,
60 * 30,
// day divisors
3600,
// ...
],
values: [
[3600 * 24 * 365, "{YYYY}", 7 "{YYYY}" ],
[3600 * 24 * 28, "{MMM}", 7, "{MMM}\n{YYYY}" ],
[3600 * 24, "{M}/{D}", 7, "{M}/{D}\n{YYYY}" ],
[3600, "{h}{aa}", 4, "{h}{aa}\n{M}/{D}" ],
[60, "{h}:{mm}{aa}", 4, "{h}:{mm}{aa}\n{M}/{D}" ],
[1, "{h}:{mm}:{ss}{aa}", 4, "{h}:{mm}:{ss}{aa}\n{M}/{D}"],
];
// ticks:
}
},
}
```
- `space` is the minumum space between adjacent ticks; a smaller number will result in smaller selected divisors.
- `incrs` are divisors available for segmenting the axis to produce ticks.
- `values` can be an array of tick formatters with breakpoints. more format details can be found in the source: https://github.com/leeoniya/uPlot/blob/master/src/opts.js#L110
---
### Performance
Benchmarks done on a ThinkPad T480S:
- Windows 10 x64, Chrome 78.0.3904.70
- Core i5-8350U @ 1.70GHz, 8GB RAM
- Intel HD 620 GPU, 2560x1440 res
| Bench Demo |
Size (min) |
Render (167k) |
Total |
Mem (peak) |
Mem (retained) |
Interact (10s) |
| uPlot |
15 KB |
39 ms |
71 ms |
19.6 MB |
3.7 MB |
154 ms |
| Flot |
172 KB |
130 ms |
190 ms |
42.7 MB |
17.3 MB |
-- |
| dygraphs |
121 KB |
168 ms |
251 ms |
113 MB |
66.0 MB |
2569 ms |
| CanvasJS |
448 KB |
295 ms |
414 ms |
49.2 MB |
39.1 MB |
2401 ms |
| LightningChart |
883 KB |
-- |
500 ms |
43.5 MB |
21.5 MB |
9446 ms |
| jqChart |
270 KB |
450 ms |
577 ms |
142 MB |
99.9 MB |
600 ms |
| Highcharts |
270 KB |
-- |
717 ms |
71.7 MB |
40.7 MB |
1122 ms |
| ECharts |
734 KB |
513 ms |
765 ms |
179 MB |
118.8 MB |
2194 ms |
| Chart.js |
239 KB |
653 ms |
741 ms |
117 MB |
78.9 MB |
5408 ms |
| ApexCharts |
430 KB |
1269 ms |
2441 ms |
142 MB |
157.9 MB |
7559 ms |
| ZingChart |
682 KB |
2324 ms |
2518 ms |
220 MB |
175.7 MB |
-- |
| amCharts |
1,034 KB |
6514 ms |
6730 ms |
397 MB |
430.0 MB |
7539 ms |
| Chartist.js |
-- |
-- |
-- |
-- |
-- |
-- |
| C3.js (d3-based) |
-- |
-- |
-- |
-- |
-- |
-- |
| dc.js (d3-based) |
-- |
-- |
-- |
-- |
-- |
-- |
| Plotly (d3-based) |
-- |
-- |
-- |
-- |
-- |
-- |
| MetricsGraphics (d3-based) |
-- |
-- |
-- |
-- |
-- |
-- |
| rickshaw (d3-based) |
-- |
-- |
-- |
-- |
-- |
-- |
```
Chrome 78.0.3904.108 (2019-11-23)
rend js sys size heap max heap ret
----------------------------------------------------------------------
uPlot 40 ms 72 ms 71 ms 15 KB 19.7 MB 3.8 MB
dygraphs 180 ms 241 ms 183 ms 123 KB 113.0 MB 64.1 MB
Flot 338 ms 182 ms 290 ms 172 KB 43.5 MB 17.2 MB
CanvasJS 327 ms 374 ms 60 ms 448 KB 48.5 MB 38.2 MB
LightningChart --- ms 490 ms 78 ms 883 KB 42.4 MB 21.1 MB
jqChart 506 ms 574 ms 96 ms 269 KB 134.0 MB 100.3 MB
Highcharts --- ms 707 ms 62 ms 272 KB 80.4 MB 77.2 MB
Chart.js 650 ms 700 ms 178 ms 239 KB 141.0 MB 122.2 MB
ECharts 515 ms 769 ms 1043 ms 734 KB 182.0 MB 125.0 MB
ApexCharts 1255 ms 2360 ms 67 ms 435 KB 159.4 MB 159.4 MB
ZingChart 5820 ms 5957 ms 72 ms 690 KB 182.0 MB 162.8 MB
amCharts 6732 ms 6697 ms 88 ms 1024 KB 405.8 MB 405.8 MB
```
---
### Acknowledgements
- Dan Vanderkam's [dygraphs](https://github.com/danvk/dygraphs) was a big inspiration; in fact, my stale [pull request #948](https://github.com/danvk/dygraphs/pull/948) was a primary motivator for μPlot's inception.
- Adam Pearce for [#15 - remove redundant lineTo commands](https://github.com/leeoniya/uPlot/issues/15).