Show HN: uPlot.js – An exceptionally fast, tiny time series chart
github.com
github.com
> After having a look at the code and the list of "non-features"
it's quite obvious that you didn't understand the code you looked at, because you would have noticed that the bench code does not use any of the "non-features" (in any lib).
you should present stronger evidence that the benchmarks are BS.
As for label rendering/measuring; choose. Either it's a rounding error in perf in which case don't claim it's a performance hog. Or claim it's a performance hog and don't call it a rounding error to support an argument.
ok that's fair but that overhead is immense, and you end up paying it regardless of whether it's needed or not. obviously uPlot is opinionated and is fast because of this. i'm not here to pry anyone's favorite charting lib from them. all i'm showing is what's possible if you dont need to be generic and cater to everyone.
i dont think i ever said label measuring/collision detection is a performance hog. compared to the work of doing the plot itself, it's insignificant. avoiding that work is more of a code size and complexity reduction for uPlot, rather than a perf opt.
how else could i possibly present a benchmark of uPlot without limiting the alternatives to its own functional scope? that would be like comparing 0-60 on a formula 1 vs a mini-van and claiming the results to be invalid because the minivan can fit 7 people but the formula 1 cannot. that fact is made abundantly clear upfront.
Okay, fair enough
> how else could i possibly present a benchmark of my lib without limiting the alternatives to its own functional scope?
Can't think of a good way to do that. That's why I don't think your benchmark is BS or in bad faith. All I was trying to say (but clearly didn't communicate effectively) is that the value of the benchmark is limited as comparison data for me and I assume others because it's not really comparing the same end-to-end functionality if in the vast majority of cases the implementor would have to manually add some things that it doesn't do out of the box (thereby burning the same CPU cycles you're avoiding).
Anyway, let's close this thread. The above footnote aside it looks like a super clean library. Well done.
To be fair, we're not that many steps from marketing cars in that way¹. Pesky fatso transmission fluid ;)
> No validation of inputs or helpful error messages - study the examples, read the docs.
I agree that validation is probably redundant, but why would you promote not having helpful error messages? Seems a bit elitist...
i might make a devmode version that has some of this in the far future. but it's not a priority before API stabilization, perf optimization, code golfing, tests, docs, examples and v1.
if anyone wants to contribute this, i would certainly entertain a PR, but no one is paying me to write extra code (or any code, really).
Fair enough, I understand the mentality, it's not easy developing and maintaining packages in your spare time...
But it's important to remember that thousands of hours of frustrated google searches:
'error: something not found'
Could be prevented with a single helpful error message for the user...And don't forget about support requests posted to the "issues" in the repo itself.
Ultimately spending an hour writing helpful errors can save you hundred of hours in support.
Of course you can just ignore everybody asking for help, but then if people aren't going to be able to use your project why make it open-source in the first place?
I love it! Having fun with your project is key :)
Keras is great about error message[1] and it's part of what makes Keras beloved (and heavily contributed to).
[1] https://blog.keras.io/user-experience-design-for-apis.html
i write open source code primarily for myself. doing anything i dont consider critical takes the joy out of doing it.
if people want features, they can fork and contribute code and then maintain that code, too...forever. of course, once you shift that burden to them, suddenly no one wants to get involved.
sorry for putting it this bluntly but it's the truth. i am one human, with a 9-5 and a life outside of coding. people seem to feel entitled to opinions on free open-source projects having contributed little-to-nothing.
i typically add them (or update the docs) once users encounter problems and open Github issues (reactively). doing it proactively is a waste of time (for me).
i accept the criticism. i just don't have the necessary free cycles to address it.
I would read them if they were linked from the readme :)
the purpose of posting it here is to hopefully get some alpha testing and feedback for more use-cases so it can be polished outside of my isolated purposes.
On the feedback side, a reset zoom button on the demos would be handy.
it's behind a double-click ;)
most charting libs try hard to do as much as possible; uPlot tries hard to do as little as necessary. if anything, it demonstrates how fast raw Canvas can be in the absence of extra baggage, like mem allocation and inefficient algorithms.
i'm still fleshing out the programmatic API and also considering adding bar chart/category functionality, since it captures the other type of useful chart which cannot be represented as a trendline. eg: https://doc-0c-2g-docs.googleusercontent.com/docs/securesc/h...
https://morepypy.blogspot.com/2019/10/pypys-new-json-parser....
Obviously using an efficient algorithm is important, but your point about memory allocation is more interesting. The closest you can get to malloc in a browser is a fixed length typed array, and if you're working with a canvas that's probably Uint8ClampedArray(xSize * ySize * 4) for 24bit color. They're really fast. Why wouldn't you use that? It's definitely not "baggage".
my point was more that many charting libs use record-based datasets like [[a,b],[c,d]] which would need to allocate possibly hundreds of thousands of arrays or objects. uPlot uses something closer to a column store for its data format, which saves a lot of mem, in addition to not duplicating the same timestamps for every series.
if you need svg, there are plenty of great charting libs that have svg backends.
don't get me wrong, i love svg, but the performance is much worse.
With many points the performance of SVG is simply not acceptable. On the other hand, for canvas they are just pixels (once they are drawn) - no difference in performance if they are white or green.
Note that I love SVG and still use it for the axes and the layers above the chart, but it has too much overhead to be used for drawing the chart values.
allocating a ton of small objects or arrays is a very common source of slowness.
i took a raw canvas and a raw data structure of a single array per series, plus one for timestamps and made a loop to draw lines on a canvas. it turned out to be very fast. the mousemove interaction has rAF throttling applied and does a binary search over the timestamps plus some basic arithmetic. there's no "secret sauce" that makes it go fast. maybe the way it calculates scales is more efficient than the others. i honestly don't know without looking into what the others do, nor do i care enough to look into it.
Over the last few years we've tried a lot of different techniques, eventually settling on using Vega with our own interaction layer ontop- but I'd like to give this a try. I'd love if this library handled the sizing of all the elements as vega would, but I understand the desire to keep this small... I browsed the entirety of the source code on my phone!
One of our use cases is streaming high frequency data- do you still see uPlot performing better than the alternatives in that scenario?
i should add it to the benchmarks :) probably based on https://www.flotcharts.org/flot/examples/axes-time/index.htm...
> Over the last few years we've tried a lot of different techniques, eventually settling on using Vega with our own interaction layer ontop- but I'd like to give this a try. I'd love if this library handled the sizing of all the elements as vega would
i'll add Vega, too based on https://vega.github.io/editor/#/examples/vega-lite/line
as far as label/element sizing goes, right now uPlot's labels are just absolutely positioned divs; vega's are canvas text. in addition, uPlot simply blows away and re-creates all labels on each zoom/unzoom action - far from ideal, but more than fast enough for manual ranging/zooming. i could potentially move to canvas text as well as a starting point, but there are many trade-offs to consider or whether it's necessary at all. i would like to see what type of label sizing you guys take advantage of with Vega to understand how extensive the implementation would have to be to offer something similar.
> One of our use cases is streaming high frequency data- do you still see uPlot performing better than the alternatives in that scenario?
that's a loaded question. and the answer is that it really depends on the frequency and the amount of data. if you can give me a function that simulates the type of data feed & rate you're dealing with, along with how much data you expect to be shown at any one time, then i can give you a better answer. right now, if you open the benchmark in the repo and toggle the series on/off while recording perf in devtools, you'll see that it takes ~12ms to redraw the 170k point chart, which includes auto-scaling. this is enough to do 60fps, but does not leave much frame budget for much else. in a real streaming case, i would expect the actual numbers to be much lower than 60hz data updates and much fewer than 170k concurrently displayed points. in addition, i would probably turn off auto-scaling, since it would be very distracting to have the chart rescale constantly.
if you'd be interested in providing a data stream simulation, then we can work through an example. feel free to open an issue if you're interested in fleshing this out.
for Vega, i could use your help to add it to the list. learning exactly how its schemas work does not sound like a good time to me.
[1] https://github.com/leeoniya/uPlot/blob/master/bench/Flot.htm...
So, feel free to ignore, but if you do, that would be awesome.
I don't think you should really add Vega as a comparison. It's more about describing charts than rendering them (Vega-lite comes with a renderer baked in). In fact it would be nice if uPlot could be a renderer for Vega.
...but probably wait for 1.0
On the example page https://leeoniya.github.io/uPlot/bench/uPlot.html
- Right click on the plot, click on any context menu item
- The zoom selection area highlight appears, a second click highlights an area but does not zoom. That highlight stays visible through zooming out by double clicking and only goes away with a regular zoom in action.
I'm imagining a sort of library of pre-made chart styles for different purposes with different sorts of interactivity or sub charts already built-in, such that using it is as simple as `fxSecurityWithCandlesticks(mydata)` or `cmpRegionTrend(region1data, region2data)` etc. for when I just want a working chart and I'm happy to mangle my data to fit the prescribed API, not to build it up to fit my data.
Everything I've seen has varying levels of simplicity, and may make it quite easy to add bundles of options for candlesticks or a second line or another chart underneath allowing the range to be easily selected, or whatever, but even if there's examples of different usages, it's still 'copy and paste' rather than 'use this function'.
https://github.com/leeoniya/uPlot#performance
Personally I use highcharts a lot, it has many chart types out of the box and allows for enough customization to fit my needs.
[1] https://plot.ly/javascript/ [2] https://github.com/plotly/plotly.js/
another major reason for ditching it is that all the series must be x-value-coalesced, so it's impossible to remove/merge datapoints along any single x without incorrectly removing/merging them in an unrelated y.
since the path is drawn directly from the data without any intermediate data->path conversions & allocations, i dont think there will be a reasonable point at which general path simplification would provide a net positive.
i'm pretty sure dygraphs does some form of simplification which seems to render well, but overall it ends up slower (not necessarily due to this, but did not check). it was also written at a time when Canvas was not as fast as it is today, so maybe it made more sense back then.
One really simple idea: group points by their x pixel, connecting the max and min y values for each with a vertical line.
feel free to experiment, but i suspect that any workable solution is not going to be cheap enough and is likely to not be simple and high quality.
you must be able to simplify each data series as a stream (during the path drawing loop itself) rather than allocating another set of 3 x 50,000-element pixel offset arrays, which will eat up all the benefit very quickly.
im on a phone, but will look into it later, thanks!
if you want to open an issue in the repo to work on porting this to the lib for actual apples-to-apples comparison, that would be cool, too :)
I added code for gaps. The tight loop isn't disrupted that much; the number of interruptions is bounded by the x resolution.
added a placeholder issue https://github.com/leeoniya/uPlot/issues/15
export const getFullYear = 'getFullYear';
export const getMonth = 'getMonth';
export const getDate = 'getDate';
export const getDay = 'getDay';
export const getHours = 'getHours';
export const getMinutes = 'getMinutes';
export const getSeconds = 'getSeconds';
export const getMilliseconds = 'getMilliseconds';
To be seen here:https://github.com/leeoniya/uPlot/blob/master/src/fmtDate.js
d.getHours();d.getHours(); cannot be compressed but d[getHours]();d[getHours](); can compress to d[a]();d[a]().
do this enough times and you've saved 1kb.
you should see the kinds of compression hacks the Preact team does ;)
also, some minifiers optimize for gzip size where the effect is not as drastic, but the browser still has to parse the un-gzipped source afterwards. it depends on the weighted costs built into each minifier.
I suppose it's possible the js runtime might also benefit for some of these modifications?
I tried the benchmark example and it doesn't zoom in for me.
The other ,,example and API'' demo doesn't work at all.
Anyways, thanks for making something fast, it's rare these days that people care about speed!
i haven't added touch events yet. right now zoom reset works on dblclick event which i get for free with a mouse, but there's no free doubletap event, so it's not terribly trivial to just add it quickly and with little code. but i'll have to figure out what to do eventually :)
There is a tiny bug, if you want to call it that: When you drag to select a period and go over the edge of the chart itself and go back, you can't finish selecting the period and have to start over by clicking a few times.
i should bind the mouseup event at the document level instead of chart level and clamp the min/max ranges.
I'd like to use this elsewhere.
Very impressive in a world of over engineering and over designed charts!
https://bl.ocks.org/d3netxer/10a28b7aee406f4e7fce
https://bl.ocks.org/robyngit/89327a78e22d138cff19c6de7288c1c...
if you know d3 well enough to faithfully match what the benchmarks do, then please submit a PR. or at least point me to a minimal demo.
maybe i can do one using c3.js, which is d3-based: https://c3js.org/samples/timeseries.html
this demo uses SVG, so it will very likely be poor. i don't know if there's an option to use a d3 canvas-based backend?
it does not appear so: https://github.com/c3js/c3/pull/1436#issuecomment-302930456