Visualize Time Series Data With μPlot Library

Category: Chart & Graph , Javascript , Recommended | January 30, 2024
Views Total:148 views
Official Page:Go to website
Last Update:January 30, 2024


Visualize Time Series Data With μPlot Library


μPlot is a lightweight, interactive, scalable, high-performance chart library to visualize any time series data (a series of data points indexed in time order) using Canvas API.

More features:

  • Crosshair cursor.
  • Configurable x and y-axis.
  • Legend with live values.
  • Dynamic data fetching.

How to use it:

1. Download and import the μPlot library.

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

2. Prepare the time series data.

const data = [
      // x-values (Unix timestamps)
      [1566453600, 1566457260, 1566460860, 1566464460], 
      // data series 1  
      [0.54, 0.15, 3.27, 7.51],
      // data series 2
      [12.85, 13.21, 13.65, 14.01],
      // data series 3
      [0.52, 1.25, 0.75, 3.62]

3. The example JS to initialize the μPlot Library.

let myChart = new uPlot({
    // width/height in pixels
    width: 960,
    height: 400,
    // chart title
    title: "My Chart",
    // unique chart ID
    id: "chart1",
    // additional CSS class(es)
    class: "my-chart",
    // determines how to present your data
    series: [
        // initial toggled state (optional)
        show: true,
        spanGaps: false,
        // in-legend display
        label: "RAM",
        value: (self, rawValue) => "$" + rawValue.toFixed(2),
        // series style
        stroke: "red",
        width: 1,
        fill: "rgba(255, 0, 0, 0.3)",
        dash: [10, 5],
    // customize the x, y axis
    axes: {
      y: [
          scale: '%',
          values: (vals, space) => => +v.toFixed(1) + "%"),
          side: 3,
          scale: 'mb',
          values: (vals, space) => => +v.toFixed(2) + "MB"),
          grid: null,
}, data, document.body);

4. Default x-axis options.

scale: 'x',
space: 40,
height: 30,
side: 0,
class: "x-time",
incrs: timeIncrs,
values: (vals, space) => {
  let incr = vals[1] - vals[0];
  let stamp = (
    incr >= d ? fmtDate('{M}/{D}') :
    // {M}/{DD}/{YY} should only be prepended at 12a?   // {YY} only at year boundaries?
    incr >= h ? fmtDate('{M}/{DD}\n{h}{aa}') :
    incr >= m ? fmtDate('{M}/{DD}\n{h}:{mm}{aa}') :
  return => stamp(new Date(val * 1e3)));
grid: {
  color: "#eee",
  width: 2

6. Default y-axis options.

scale: 'y',
space: 30,
width: 50,
side: 1,
class: "y-vals",
incrs: numIncrs,
values: (vals, space) => vals,
grid: {
  color: "#eee",
  width: 2

7. Default options for the time series data on the x-axis.

label: "Time",
scale: "x",
value: v => stamp(new Date(v * 1e3))

8. Default options for the time series data on the y-axis.

shown: true,
label: "Value",
scale: "y",
value: v => v


1.6.30 (01/30/2024)

  • bugfixes

1.6.29 (01/29/2024)

  • bugfixes

1.6.28 (12/30/2023)

  • update

1.6.27 (10/28/2023)

  • update

1.6.26 (09/21/2023)

  • disable pixel snapping for bars with gap sizes < 5px

1.6.25 (08/10/2023)

  • Bugfixes

1.6.24 (01/27/2023)

  • Implements the ability to set custom legend value(s) when the plot is not hovered. It will now call any user-supplied series.value or series.values legend callbacks with value=null and dataIdx=null whenever the mouse cursor leaves the plotting area. previously, uPlot did not invoke these callbacks during mouseleave and simply put — into the legend values. Therefore, if you neglected to handle null values in these user-supplied callbacks, your code may need to be updated.

1.6.23 (12/01/2022)

  • Bugfix

1.6.22 (07/08/2022)

  • update

1.6.21 (06/06/2022)

  • update

1.6.19 (02/08/2022)

  • bugfix

1.6.18 (12/17/2021)

  • bugfix

1.6.17 (11/14/2021)

  • update

1.6.16 (09/16/2021)

  • accept arbitrary scale keys in y facets

1.6.15 (08/13/2021)

  • Bugfixes

1.6.14 (07/22/2021)

  • update

1.6.13 (06/17/2021)

  • detect & sync devicePixelRatio changes (e.g. browser zoom). this makes for an SVG-like experience on desktop but not mobile, where pinch zooming does not trigger dppx MediaQueryList change events
  • accept null values in Range.MinMax tuple to indicate auto-ranging
  • add series.points.filter() for custom per-point show/hide logic & pass in gaps array from series.paths()
  • add cursor.idxs which holds indices returned by cursor.dataIdx() (for read-back)
  • fix uPlot.rangeNum() in softMode: 2 auto-ranging flat/all-0 values to -100,100
  • improve axis auto-size logic in demo to actually use measureText() and add in the required axis.ticks.size and

1.6.12 (06/05/2021)

  • bars pathBuilder now uses the minimum x distance between all adjacent points in dataset to determine available space for a bar. previously only the first two points were used, which may create too-wide bars when dataset is not evenly spaced.
  • auto point visibility now uses x distance between first and last in-view points to determine avg point density. previously it always used points-in-view and full width of chart, which was inaccurate if dataset does not actually span full chart (x scale is wider than dataset).

1.6.11 (06/03/2021)

  • fix stroke being wrongly clipped (along with fill) when using bands with non-bars pathbuilders

1.6.10 (05/27/2021)

  • [BREAKING] legend marker options (width, stroke, fill, dash) have moved from legend inwards to the new legend.markers. these are not commonly used opts, so it felt okay to break them mechanically as part of introducing another marker-specific option (below)
  • can now be set to false to disable markers and render the labels with the stroke or fill color
  • for bars pathBuilder
  • formal band manip methods: .addBand(), .setBand(), .delBand()
  • new syncRect hook for notifying when .getBoundingClientRect() is called after scroll or resize
  • expose .u-over and .u-under as instance props to reduce need for u.root.querySelector(“.u-over”), reduces plugins boilerplate and improves perf
  • ensure axis auto-sizing converges after 3 cycles (fixes infinite loops & ui lockups if externally-provided axis.size() function fails to properly converge)
  • fix join() to not expand undefined values with nulls
  • fix cursor sync setSelect using own scale keys for lookups instead of emitter’s
  • fix regression: cannot read ‘scale’ of undefined error when initializing uPlot with no y series
  • fix y-scale auto-ranging: treat deltas < 1e-9 as flat. fixes min/max of e.g. 89.69999999999999, 89.7
  • add undefined to AlignedData type series value arrays
  • bars pathBuilder improvements

1.6.8 (04/10/2021)

  • new .setLegend({idx}) API and matching hook that fires on legend updates
  • opts.pxAlign and series.pxAlign can now to define the pixel rounding increment to reduce micro-shifting in streaming cases
  • axis.values can now be a static array
  • optimized handling of clientRect invalidation during resize and scrolling. also fixes bug that failed to invalidate clientRect of plots when scrolling was of an ancestor container (not window)
  • fix for float math precision issues in ms date handling
  • 50% fewer lineTo() calls when only 2 datapoints per pixel

1.6.7 (03/12/2021)

  • fixes a perf issue with cursor.focus which caused the chart to redraw on every focus change even with alpha: 1 where no visible change happened. this caused a significant perf impact when mousing over a lot of densely-packed series.

1.6.6 (03/09/2021)

  • ascDesc opt for stepped pathbuilder to control whether to draw ascenders/descenders at null/gap boundaries. now defaults to false
  • selective filtering for synced cursors. e.g. can sync position without syncing zoom
  • perf improvements, mostly for streaming cases
  • typings refinements
  • fix for initially-defined min/max for scales

1.6.5 (02/15/2021)

  • Inverse hyperbolic sine scale distribution – like log scales, but are linear below a certain threshold and can be <= 0
  • expose internal pubsub creator as uPlot.sync(key). this allows things like runtime toggling of cursor sync
  • typings are now under a namespace
  • inverted log scales demo (using 2 direction-reflected charts)

1.6.4 (02/02/2021)

  • it’s now possible to redraw axes without invalidating scales, e.g. to change the tick value formatting or precision
  • series._focus prop to help check if a series is focused by cursor
  • opts.pxAlign & series.pxAlign opts to opt out of canvas translation during path & point drawing
  • perf improvements to linear path builder when qty of datapoints <= width pixels
  • various tweaks to uPlot.join()

1.6.3 (01/22/2021)

  • fix for bands not filling below 0

1.6.2 (01/21/2021)

  • series.isGap is gone and uPlot.join() now returns the data array directly, with undefined occupying the value locations of alignment artifacts. as a result, .join() internals have been optimized/simplified for a 2-3x speedup.
  • callback signatures for axis.strokeaxis.grid.strokeaxis.ticks.stroke
  • no more defaulting to black when series.stroke is absent. this helps fill-only series.
  • some more robustness to early-exit before trying to draw will empty stroke, empty fill or empty paths.

1.6.1 (01/16/2021)

  • tweak/fix some y scale auto-ranging behavior

1.6.0 (01/13/2021)

  • handle scale orientation for selection, drag & sync

1.5.2 (12/16/2020)

  • improved hover-point alignment

1.5.1 (12/12/2020)

  • join(): skipGaps bools -> nullModes flags

1.5.0 (12/11/2020)

  • extract addGap() & clipGaps() from closure (expose as util fns). rename uPlot.alignData() -> uPlot.join()

1.4.7 (12/04/2020)

  • update

1.4.6 (11/28/2020)

  • fix tooltip-closest chart size for small screens

1.4.4 (11/21/2020)

  • add {data, isGap} signature to setData() & constructor

1.4.2 (11/19/2020)

  • update

1.4.0 (11/19/2020)

  • dynamic gutter sizing

v1.3.0 (11/06/2020)

  • Bugfixes

v1.2.2 (09/29/2020)

  • fix axis splitting for year+ incrs to account for leap years.
  • improve empty chart creation. init default x/y scales without relying on x/y series presence.
  • add raw xValue arg to cursor.dataIdx() to simplify adjustment of the hovered points.

v1.2.1 (09/18/2020)

  • fix ESM build
  • decouple axis splits filtering from splits formatting (add axis.filter(), axis.grid.filter(), axis.ticks.filter())
  • fix series.fill for log scales trying to fill to non-existent 0
  • log base 2 support as axis.log: 10
  • fix rounding errors in rangeLog() which threw an infinite loop for values < 1e-3
  • some internal cleanup and optimizations

v1.2.0 (09/16/2020)

  • [BREAKING] expansion and improvement of the temporal axis.values config array
  • fix a long-standing bug where series hover points were cut off at plot edges
  • {select: {over: true}} ability to choose whether .u-select is placed inside .u-over as previously, or .u-under. this is now relevant since .u-over is no longer overflow: hidden and it’s possible for a programatically-set selection inside .u-over to visually overflow the plot bounds
  • false can be used to exclude a series’ data from being considered when auto-ranging its y scale
  • a bunch of fixes for plots with no datapoints, or single datapoint
  • much-improved line-smoothing demo using Centripetal Catmull-Rom Spline algo

v1.1.2 (08/25/2020)

  • fix: DST rollover periods were dropping axis splits when zoomed in to <= 1hr resolutions.
  • fix: more accurate floating point math when generating axis splits with decimals.
  • fix: regression caused by precision increase and add more guards against infinite loops.
  • fix-demo: tooltips-closest failed to toggle tooltip display on plot re-entry.
  • fix-demo: tooltips-closest was drawing interpolated markers outside plotting area when zoomed in.

v1.1.1 (08/22/2020)

  • new: series.fillTo() API to adjust the area-fill baseline (instead of always 0)
  • new: cursor.move callback to refine or snap cursor position
  • new: false option to disable live cursor values (static legend)
  • fix: force update legend for setData() calls, even when cursor.idx is unchanged
  • fix: also defocus hovered points when defocusing inactive series
  • fix: typings & demos

v1.1.0 (08/03/2020)

  • new: logarithmic scales (via scale.distr: 3)
  • new: uPlot.rangeLog() and uPlot.fmtNum() util funcs
  • new: cursor.dataIdx can now transform the hovered idx to a different data idx used for legend display and cursor hover points
  • chg: all CSS classes are now prefixed with u-. also, some legend classes got better names
  • chg: axis functions now receive axisIdx as an argument
  • chg: axis.split option renamed to axis.splits
  • fix: saner & more consistent scale padding (ranging) behavior

v1.0.11 (06/16/2020)

  • fix for data gaps (null values) at start and end of datasets when spanGaps: true
  • fix for plots with leading/trailing gaps and only single data point
  • fix for initially empty-data plots improperly retaining initial y-scale min/max after first setData() call
  • fix for deleting a series when false

v1.0.10 (06/08/2020)

  • fix path filling oddities when spanGaps was used with leading or trailing null data
  • fix an init bug by setting default y scales
  • perf optimizations for mousemove & legend display

v1.0.7 (05/04/2020)

  • ensure chart position cache is always in sync
  • ensure scale.range can get current scale min/max
  • TS defs fixups
  • various fixes for charts without data

v1.0.7 (04/23/2020)

  • fix legend showing junk in legend when cursor is enabled without data in chart
  • .redraw() will now force-rebuild the paths, unless false is passed in to opt out
  • axis.rotate for user-defined x-axis label rotation

v1.0.6 (04/12/2020)

  • fixed scale ranging for a single data points (where dataMin == dataMax)

v1.0.5 (04/05/2020)

  • fix esm build regression caused by recent refactor.

v1.0.4 (04/05/2020)

  • opts.fmtDate & new “names” arg for uPlot.fmtDate() for redefining month and weekday names.
  • .syncRect() for handling plot position changes unrelated to window scroll & resize.
  • new post-init series apis: “.addSeries(opts, idx)” and “.delSeries(idx)”

v1.0.3 (03/28/2020)

  • TypeScript declarations for API
  • better Webpack compat by avoiding a lone use of this
  • perf optimization to point rendering
  • minor api tweaks (u.idxs -> u.series[i].idxs)

v1.0.2 (03/13/2020)

  • for the purpose of auto-adding axis gutters, treat axis.size: 0 the same as false. this avoids creating superfluous gutters which would otherwise require explicit disablement via e.g. gutters: {x: 0, y: 0}

v1.0.1 (03/13/2020)

  • You can create a custom build that disables any of these features to cut down on lib size and unused code.
  • .setSelect() now requires explicit sizing and no longer relies on cursor.drag.x or cursor.drag.y to auto-size the selection size in the drag-perpendicular dimension. the effect is that both width and height must be explicitly set. you can see the effect in this diff: a1f720d. this should have no impact on drag selection performed with a cursor, where cursor.drag is taken into account to infer the perpendicular select size.
  • the alpha option of cursor.focus was moved to a new top-level opt focus.alpha. this ensures that .setSeries(2, {focus: true}) can continue to work without relying on cursor opts. cursor.focus.prox remains in place. cursor.focus.alpha should continue to work as a side-effect of the internal option merging, but should not be relied upon going forward.


  • v1.0


  • improve pixel snapping


  • Fix high/low band series toggling

You Might Be Interested In:

Leave a Reply