After having a look at the code and the list of "non-features" (which is a rather opinionated list I might add) the "exceptionally fast" part primarily comes from it simply not doing as much as most charting libraries do. And especially when it comes to things like "No DOM measuring" you're essentially not doing something in the library that practically every implementation will have to do outside of it. It's your project so you're perfectly free to make the design choices you want but that puts the benchmarks resoundingly in apples vs oranges territory. "Lightest car on the market, engine and bodywork not included for weight optimisation reasons"
from a perf perspective it's more like excluding the mirrors and windshield wipers. the amount of measuring [of label text] that might be required for this bench is absolutely dwarfed by the time needed to render the data; it's a rounding error.
> 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.
And you should calm down a bit. I was referring to your benchmark hitting exactly the functional scope of your library while it's invoking more generic libraries that by extension have to do more work for the same functional result. Based on average feedback here that position is supported by other real world users of these sorts of libraries.
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.
> I was referring to your benchmark hitting exactly the functional scope of your library while it's invoking more generic libraries that by extension have to do more work for the same functional result
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.
> 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.
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.
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).
I don't disagree, but good vs bad error messages can be the different between a project that people love and contribute to and a project that people find frustrating and bombard with issues.
Keras is great about error message[1] and it's part of what makes Keras beloved (and heavily contributed to).
honestly, i already maintain too many open source projects (and quickly resolve bug reports) to be worried about people not using more of my free work.
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’m pretty sure the parent was just making an honest suggestion that wasn’t meant to attack or hurt you. And why post on HN if you aren’t willing to listen to others’ suggestions/criticisms of your work?
look, i get it. i'm not offended. but i want to keep lib size minimal and adding validation and error messages will bloat it. a lot.
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.
As an open-source maintainer, helpful error/debug messages is the key to reduce the number of GitHub issues, mails and all the other way people will track you down to ask for your help.
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?
this thing is nowhere near finished. once the api is stable i will write them and make more demos. if i did all of that now i would have to do 10x the work on every api change.
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.
i figured this was far enough along in the dev cycle now to get some feedback.
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...
if anything, it demonstrates how fast raw Canvas can be in the absence of extra baggage, like mem allocation and inefficient algorithms
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".
i considered using typed arrays for the input data, but most data is gonna come from JSON parsing, which will automatically allocate at least the size of a non-typed array, so i used that as the lowest common denominator for the demos.
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.
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.
Can you state what's the mechanism that makes it fast? Is this a breakthrough in rendering optimizations or does it introduce smarter data structures? Or is it an accumulation of small optimizations everywhere?
i think a better question to ask is why the others are not faster. honestly i cannot answer that question without digging into the source of each to find their bottlenecks.
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.
Awesome!! It's been disheartening to see flotjs (which was essentially unmaintained for 5 years) still beat out scores of other charting libraries in terms of raw speed.
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?
> Awesome!! It's been disheartening to see flotjs (which was essentially unmaintained for 5 years) still beat out scores of other charting libraries in terms of raw speed.
> 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
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.
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.
i tested Flot [1] locally and got 351ms/45MB heap vs 45ms/20MB heap for uPlot on this machine. (the numbers in the readme are from another machine that's maybe 20% slower). i'll update the bench table tomorrow when i run it there.
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.
I'd love a comparison with https://echarts.apache.org/ but I understand if you don't feel like it or don't have the time. I'd do it myself, but I don't have much spare time right now :(
So, feel free to ignore, but if you do, that would be awesome.
- 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.
Are there any (I speak as not-really-a-JS-person) frameworks on the opposite end of the spectrum, for when performance isn't (yet) as important as fully featured interactivity?
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'.
plot.ly JS [1][2] is probably the most "batteries included" charts framework I've used, which isn't mentioned on the comparison table on the bottom of that git repo.
if you look at the bechmarks table at the bottom of the readme, there are definitely charting libs that can do this (more-or-less). i think the major ones are commercial.
Have you tried running simplifying the lines before rendering? Rendering 150k points is cool, but there aren't enough pixels to see that
level of detail.
i had a prototype using https://github.com/mourner/simplify-js and it did a bit better in low quality/low precision mode, but worse in high quality mode. at some point the trade-off is probably worth it but i've noticed some not-so-great artifacts even in hq mode, so had to ditch it.
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.
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.
interesting. it looks less accurate tho. also, i cannot use this without supporting data gaps and sparse data. it could be worse when you get it to do everything it needs to without just having this case be a dedicated additional code path. once your neat uniform loops start branching, perf usually takes a nose dive.
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 :)
The column version looks like it has less detail along the top of the plot, but I think that's a rendering bug with the moveTo version: there aren't any data points above 1.
I added code for gaps. The tight loop isn't disrupted that much; the number of interruptions is bounded by the x resolution.
if you enable a bunch of unsafe options they can do some of them, but they tend to err on the safe side when it comes to native built-in methods like these on Date objects.
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.
Would it be possible to have 2 charts with synced crosshairs on this? I have a little open-source application that grabs stats from Elixir nodes and its plotting library is pretty slow. Lining up multiple crosshairs allows pinpointing a moment of time to an issue.
once the API is a bit more developed it will definitely have a way to hook into cursor onmove events and a method to set the cursor to specific x/y or data offsets. that's pretty much all you'll need for sync.
This was also my immediate thought - how lovely would it be to have synchronised charts built based on this to view system perf stats. Looking forward to the day this library gains that ability! Cheers on this project!!
> Zooming on my mobile (Chrome) works for dygraphs, but not for uPlot.js.
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 really like the packing implementation in data.json. Is this homegrown or more of a 'standard' method for saving having to send all the json mechanics?
i'll probably support that through allowing custom markers at the datapoints. beyond that i'm not sure how much will be baked into the lib itself, but it will be possible to draw your own candlesticks or rubber duckies at x/y coordinates for any given datapoint by a callback that accepts the necessary positions, data values and access to the canvas context.