Skip to content
Β 
Β 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

215 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

πŸ“ˆ ΞΌPlot

An exceptionally fast, tiny (< 15 KB min) time series & line chart (MIT Licensed)


Introduction

ΞΌPlot is a fast, memory-efficient time series & line chart based on Canvas 2D; from a cold start it can create an interactive chart containing 150,000 data points in 40ms, scaling linearly at ~4,000 pts/ms. In addition to fast initial render, the zooming and cursor performance is by far the best of any similar charting lib; at < 15 KB, it's likely the smallest and fastest time series plotter that doesn't make use of WebGL shaders or WASM, both of which have much higher startup cost and code size.


uPlot Chart


🚧 UNDER CONSTRUCTION 🚧

2019-10-24: ΞΌPlot is now mostly feature-complete and its declarative opts API is in pretty good, future-accommodating shape. Its imperative API, docs and additional examples are still in progress.

v1.0 and API stabilization are loosely targetted for sometime before 2020-01-01. Until then, feedback, feature suggestions and real use-cases can be submitted to the issue tracker for consideration & further discussion.


Features


Non-Features

In order to stay lean, fast and focused the following features will not be added:

  • No data parsing, aggregation, summation or statistical processing - just do it in advance. e.g. https://simplestatistics.org/, https://www.papaparse.com/
  • No transitions or animations - they're always pure distractions.
  • No DOM measuring; uPlot does not know how much space your dynamic labels & values will occupy, so requires explicit sizing and/or some CSS authoring.
  • No stacked series or line smoothing. See links for how these are each terrible at actually communicating information.
  • Probably no drag scrolling/panning. Maintaining good perf with huge datasets would require a lot of extra code & multiple <canvas> elements to avoid continuous redraw and rescaling on each dragged pixel. However, since uPlot's performance allows rendering of very wide canvases, they can be scrolled naturally with CSS's overflow-x: auto applied to a narrower containing element. Pagination of data also works well.

Usage & API

Example: https://jsfiddle.net/4o0ge9wx/

<link rel="stylesheet" href="src/uPlot.css">
<script src="dist/uPlot.iife.min.js"></script>
<script>
    const data = [
        [1566453600, 1566457260, 1566460860, 1566464460],   // Unix timestamps
        [0.54,       0.15,       3.27,       7.51      ],   // CPU
        [12.85,      13.21,      13.65,      14.01     ],   // RAM
        [0.52,       1.25,       0.75,       3.62      ],   // TCP Out
    ];

    const opts = {
        width: 800,
        height: 400,
        series: {
            y: [
                {
                    label: "CPU",
                    scale: "%",
                    value: v => v.toFixed(1) + "%",
                    color: "red",
                    width: 2,
                    dash: [10, 5],
                },
                {
                    label: "RAM",
                    scale: "%",
                    value: v => v.toFixed(1) + "%",
                    color: "blue",
                },
                {
                    label: "TCP Out",
                    scale: "mb",
                    value: v => v.toFixed(2) + "MB",
                    color: "green",
                }
            ],
        },
        axes: {
            y: [
                {
                    scale: '%',
                    values: (vals, space) => vals.map(v => +v.toFixed(1) + "%"),
                },
                {
                    side: 3,
                    scale: 'mb',
                    values: (vals, space) => vals.map(v => +v.toFixed(2) + "MB"),
                    grid: null,
                },
            ],
        },
    };

    let uplot = new uPlot.Line(opts, data);

    document.body.appendChild(uplot.root);
</script>

Documentation


Installation

<link rel="stylesheet" href="src/uPlot.css">
<script src="dist/uPlot.iife.min.js"></script>

Data Format

let data = [
  [1546300800, 1546387200],    // x-values (timestamps)
  [        35,         71],    // y-values (series 1)
  [        90,         15],    // y-values (series 2)
];

uPlot expects a columnar data format as shown above.

  • x-values must be numbers, unique, and in ascending order.
  • y-values must be numbers (or nulls for missing data).
  • x-values and y-values arrays must be of equal lengths.

By default, x-values are assumed to be unix timestamps but can be treated as plain numbers via scales.x.time = false.

This format has implications that can make uPlot an awkward choice for multi-series datasets which cannot be easily aligned along their x-values. If one series is data-dense and the other is sparse, then the latter will need to be filled in with mostly null y-values. If each series has data at arbitrary x-values, then the x-values array must be augmented with all x-values, and all y-values arrays must be augmented with nulls, potentially leading to exponential growth in dataset size, and a structure consisting of mostly nulls.

This does not mean that all series must have identical x-values - just that they are alignable. For instance, it is possible to plot series that express different time periods, because the data is equally spaced.

Before choosing uPlot, ensure your data can conform to these requirements.


Basics

let opts = {
  title: "My Chart",
  id: "chart1",
  class: "my-chart",
  width: 800,
  height: 600,
  spanGaps: false,
  series: {
    y: [
      {
        // in-legend display
        label: "RAM",
        value: rawValue => "$" + rawValue.toFixed(2),

        // series style
        color: "red",
        width: 1,
        fill: "rgba(255, 0, 0, 0.3)",
        dash: [10, 5],
      }
    ]
  }
};

let uplot = new uPlot.Line(opts, data);

document.body.appendChild(uplot.root);
  • id and class are optional HTML attributes to set on the chart's container <div> (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, ctx.lineWidth, ctx.fillStyle, and ctx.setLineDash.

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.
  2. series hold 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].
  3. 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.
  4. axes render the ticks, values, labels and grid along their scale. Tick & grid spacing, value granularity & formatting, timezone & DST handling is done here.

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

About

πŸ“ˆ An exceptionally fast, tiny time series & line chart

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages