leeoniya / uPlot

📈 A small, fast chart for time series, lines, areas, ohlc & bars
MIT License
8.83k stars 386 forks source link

Strategies for showing a few unaligned / sparse data points among mostly aligned data #978

Open juliusv opened 4 months ago

juliusv commented 4 months ago

With reference to https://twitter.com/leeoniya/status/1816517816461459478, I'd be curious to learn any advice you may have around handling a situation where you have many series of mostly aligned data, along with a few unaligned + very sparse (single-point) series. In the old Prometheus UI (which I'm now rewriting with uPlot), we mostly show aligned time series, but we also allow optionally displaying sparse trace exemplar points in the same chart. These exemplar points are treated as regular series in the current Flot implementation, except that they have a custom point rendering. But they are still hoverable / focusable and show a tooltip like the regular series, and clicking on them opens a panel below the graph that shows more info about that exemplar:

image

My current workaround with uPlot's aligned data model would be to align them to the series timestamps and then fill everything up with null values. Except that each point you see above belongs to a different series with different labels etc., so it could be potentially a lot of series that contain mostly null values. Basically the scenario in which you should normally not use uPlot :) It would probably still work though, just not great. But if you have any even better recommendations for this situation, I'm all ears!

And thanks for uPlot btw., despite my difficulties implementing certain things around it, I'm having a lot of fun with it as well and love the small footprint and speed!

vildantursic commented 4 months ago

hey @juliusv I am having somewhat similar task, having over 2 million points spread across multiple series. So far it runs great but like you said it has to be filled with null values aligned with xAxis (which is a lot of "unnecessary" data for JS to handle 😄) but it works. For aligning data and filling with nulls I am using uPlot.join function which does great job and I don't have to do it on my own. I would also like to know if there is some approach that can avoid having this "workaround". 😄

vildantursic commented 4 months ago

Hey @juliusv I was playing with some data yesterday and I think I found one way to do it. There are still rough edges, not everything I want is working but at least I made points (highlights) on chart without filling it with null values. This is my chart

image

Those colored areas close to center are highlights drawn by uPlot hooks.

So this is data after using uPlot.join(), I believe that [[x]] array of arrays approach didn't merge placing nulls but left it as is.

image

So I used valToPos where I took timestamp from X0 and X1 and drawn rect at those positions.

What I didn't manage to do yet is:

Hope this helps

juliusv commented 4 months ago

@vildantursic Thanks, I wasn't aware of the uPlot.join() function yet, that definitely helps already! I'm not sure I have enough information to understand all of the rest you're saying, but I definitely want to keep the normal hover tooltip behavior that I have for normal series (plus an additional click handler). But that should work after joining - my only issue is that things become more expensive after the join due to the null fills. All that's expected with uPlot's documented restrictions of course, unless there's other tricks I'm missing.

leeoniya commented 4 months ago

sorry im a bit slammed today. i'll reply properly tonight or over weekend.

uPlot has a scatter mode called mode: 2 that doesnt require alignment [1], but i dont think thats the best path here since it has different tradeoffs for cursor behavior and will affect tooltip.

i will put together a demo of what i think is the best approach. maybe in the process i'll make changes to mode 2 that make it more feasible to use for this.

im starting to realize that i need to include a tooltip in the core that's basically a copy or reuse of the legend implementation, plus positioning logic and maybe sorting/filtering options. one reason i dont have it is that it bloats the lib size and is almost 100% redundant with live legend features, but the question comes up so often that i should just do it.

give me a few days...

[1] https://leeoniya.github.io/uPlot/demos/scatter.html

juliusv commented 4 months ago

All good, no hurry! :)

im starting to realize that i need to include a tooltip in the core that's basically a copy or reuse of the legend implementation, plus positioning logic and maybe sorting/filtering options.

Some input on that maybe: I like this kind of tooltip that just shows one series at a time, but with nicely formatted detail about that series: https://x.com/juliusvolz/status/1814772844640805080. But ultimately I guess that should be up to the user, and what the tooltip looks like and what info it contains should just be user-defined (unlike the current legend, where I think it'd also be great to be able to pass HTML content etc.). This is what the same kind of tooltip integration looks like in Flot (from the old UI in Prometheus): https://github.com/prometheus/prometheus/blob/d4f098ae80fb276153efc757e373c813163da0e8/web/ui/react-app/src/pages/graph/GraphHelpers.ts#L107-L147

vildantursic commented 4 months ago

Every day we learn more about uPlot 😄 That is why I asked here https://github.com/leeoniya/uPlot/issues/972 if @leeoniya need some help writing docs. Tooltips should be personal preference. Devs should be able to do anything with it with just provided data. So far I managed to make all that I need with tooltip, problem is that it just took me a while to find all the things that I need in code of uPlot 🥲 Might not be best implementation but it works

image
leeoniya commented 4 months ago

Tooltips should be personal preference

honestly, this is also true for legends. e.g. in Grafana we don't use uPlot's legend, but instead hook up a React one. and it's a big reason why i left tooltip out. the amount of customizable stuff people want in both is endless (including positioning, overflow/wrap strategies, etc).

where I think it'd also be great to be able to pass HTML content etc

i dont want to expose an innerHTML api because it has the worst possible performance when you're mousing around with 60hz updates. i would rather make something that is very fast and good enough for 85% of cases and let people diy the other 15%.

juliusv commented 4 months ago

honestly, this is also true for legends. e.g. in Grafana we doesnt use uPlot's legend, but instead hook up a React one.

Yeah, this is likely what I'll do with Prometheus + uPlot as well, just because I want to format each series with some syntax highlighting in the legend and potentially add some other functionality.

i dont want to expose an innerHTML api because it has the worst possible performance when you're mousing around with 60hz updates. i would rather make something that is very fast and good enough for 85% of cases and let people diy the other 15%.

In this case, I guess you're talking about the hover tooltip, not the legend? Wouldn't you have to set the innerHTML of the tooltip while hovering in any case (even if it's happening inside uPlot in some non-customizable way), or are you thinking of drawing it on the canvas, or just doing textContent setting like in the current tooltip demos?

In any case, my current uPlot tooltip implementation where I set the innerHTML seems to be super fast for me, so I'm happy with it for my use case, as long as that's still possible in the future :) Then again, all this is probably very off-topic for this issue by now...

YanWaite commented 3 months ago

hey gentlemen, I am a student, and I am learning the uPlot, I've got a problem and Hope to answer. my x-axis is a Unix timestamps (JavaScript), for example: [ 1724410913.000, 1724415751.000, 1724418170.000, 1724420589.000 ] in the uPlot, it is

企业微信截图_17247713415417

I don't want to show 6am / 12pm I hope show 06:30 / 12:45 I try reading https://github.com/leeoniya/uPlot/issues/60 But I not success, It still 6am / 12pm How do I need to configure it. (My English is not good, This is a Web translate, sorry...... )

YanWaite commented 3 months ago

I get some good news, I search for information at code and message in the opts: scales: { x: { time: true, auto: false }, }, and axes: [ //X { values: (self, splits, axisIdx, foundSpace, foundIncr) => { const transferSplits = [ transfer your wish array use splits ] //return array return transferSplits } }, //Y {} ] I do not know whether I have chosen the right, but it show success my expected x-axis XD ok, I got go study uPlot, good lucy, salute.