-
Notifications
You must be signed in to change notification settings - Fork 393
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Sync time axis for multiple TimeSeriesView #7931
Comments
I think this boils down to being able to connect blueprint properties of views with each other, so this might quickly turn into a more general infrastructure task. A more short-term solution that's worth experimenting with would be to add an optional view id to that property archetype specifically. I'm surprised we didn't have an issue for this already (couldn't fine any at least) |
Is there anything in particular in your use case that is preventing you from displaying the two timeseries in the same view? |
The y-scale of the data is very different. This makes it hard to show the data in the same TimerSeriesView. For this task, it would be possible to have to Y-axis plotted in one TimeSeriesView. However, this works only for two y-axis and doesn't scale for more Y-axis or separating out the data into multiple TimeSeriesViews (e.g. one TimeSeriesView for forces, one for centroidal position, one for torques of a robot etc). |
I wonder if we can have one global time-series view x-axis property that the individual time series can use or not. This would make the implementation easier as there is only one global property to sync and this doesn't need to be implemented generic enough for all properties. As this is a toggle-on / toggle-off property on the TimeSeriesView, this becomes a boolean flag, which might be easy to add to the TimeSeriesView. This leaves me with two questions about the rerun infra and implementation (for now):
|
no, not yet. One part of our vision in this area is that containers can have arbitrary view properties set that then propagate down the tree and are used whenever a view doesn't set anything. I.e. the view property accessors/queries would be aware of an inheritance tree.
Generally, all views operate in isolation and are very free in how interactions are implemented. In the case of the time series view this is for the most part just egui plot. There's already properties that the interaction writes out to the blueprint store e.g. here
|
Thanks for your comments @Wumpf . Over the weekend I looked at the code a bit. It seems like the egui_plot has a build in way to link axes of different plots together. See here the example from egui_plot: https://github.com/emilk/egui_plot/blob/main/demo/src/plot_demo.rs#L669 You can play with a demo of the linked axes here: https://emilk.github.io/egui_plot/ (click on the "Linked Axes" tab on the top). If we use this linked axes feature, the TimeSeriesViews could have a new property like "SharedAxesNames" (which is a string). The TimerSeriesViews with the same SharedAxesNames will then be synced. Do you think this is worth exploring further? |
it could be that we also need to use something like this to avoid frame delays, but generally I don't think that's a good direction to take as that would completely sidestep the state we have in the blueprint and thus what is controlled from api, shown in the ui and stored on disk |
Makes sense to want to keep things stored in the blueprint. If we would store a hash-map with |
yeah I guess something like that could work out for starters :) |
Another thing I didn't think of before is that you'll need some "new special place" to store and write that map though. Right now the structure of the blueprint store is fairly rigid and very very undocumented. But in a nutshell there's a definition of a container hierarchy with the leaf level, the views, having a bit or leeway to store arbitrary properties in sub-entities. |
I managed to implement a shared-x-axis. At the moment all TimeSeriesViews use the same shared x-axis. The shared x-axis is written to the blueprint. The code is quite hacked and I am not sure it will work well when the data is streaming in and the x-axis gets updated to the last x seconds. @Wumpf : Would you mind taking a link on the current changes and tell me what you think? I was wondering if there should be a My current code is here: https://github.com/jviereck/rerun/tree/issue-7931-sync-x-axis Video of the changes: Screen.Recording.2024-11-05.at.22.32.18-1080-h264.mov |
Hum, well yeah sure it's a hack as you say 😉.
Yeah I think the linking direction could make a lot of sense and aligns well with the vague plane of entity links we wanted to do in the future! That also sidesteps the issue we talked about previously here on where and how to store the shared axis.
... well as I said, this won't be easy since so much infrastructure is missing to do this kind of thing 😅. |
Hmm actually I guess for what you want you want to specifically link the |
This could look something like this: table TimeAxis (
"attr.rerun.scope": "blueprint",
"attr.rust.derive": "Default"
) {
/// The range of the axis.
///
/// If unset, the range well be automatically determined by the visible time range of the view.
range: rerun.components.Range1D ("attr.rerun.component_optional", nullable, order: 2100);
/// If enabled, the time axis range will remain locked to the specified range when zooming.
zoom_lock: rerun.blueprint.components.LockRangeDuringZoom ("attr.rerun.component_optional", nullable, order: 2200);
// ⬅️ Add a hack for linking to another view here.
} |
Thanks for your comments @Wumpf .
What I did in the implementation so far is creating a UUID from a string like this: let shared_id = SpaceViewId::hashed_from_str("TimeSeriesShared"); I was thinking about the Would that work? |
it could work with some more hacks and custom ui. But really I'd like that to be a configurable uuid of an existing view and not a made-up one. I'm not entirely sure about the repercussions of breaking the blueprint entity hierarchy in such a way, so please understand that I'd be very hesitant to land it like that. |
Thanks @Wumpf for your last reply. That makese sense. I will explore adding a linkage in the |
So my current idea is to add a new Looking at the generated code in here: https://github.com/rerun-io/rerun/blob/main/rerun_py/rerun_sdk/rerun/blueprint/views/time_series_view.py#L143 It looks like the fields on the Assuming the field Does it therefore make senes to implement a new |
Other idea might be to add a string |
I pondered this a lil bit more and synced with @jleibs to coordinate what solution we'd like to have medium term. Leading to the writeup of this related issue documenting the issues with the time axis in the absence of syncing With that context established, back to this issue and let's say we want to skip #8050 and jump ahead as quickly as possible :). Jumping a bit for didactic reasons:
Sort of! I think what we want is a new
That's not quite how the structure on the views works: The idea is is that each view has a series of property archetypes, each containing a bunch of components defining the property.
Yes. Let me clarify a bit further: (almost) everything that is defined in those fbs files is available from all SDK languages (it's just that some important scaffolding is missing so far to have blueprint be accessible from C++ and Rust). And vice versa every built-in type that Rerun stores in blueprint or the store comes from these. Hope that makes sense! |
Thanks for the write up. I am a bit confused what to do.
What do you mean by "global"? Can you maybe draw an outline where in the blueprint debug pannel these entries would live?
When constructing the Also, would the |
Global was a bit of a misnomer: The thing you did in your prototype I would have called global because it isn't tied to the entity of a specific view (whose path is derived from the view's id). But it would be ofc better to put it in a subpath of a view, so it's not really global but also doesn't quite follow the current system since there wouldn't be a formal definition (via fbs) where that data is (unless all of #8050 is implemented :)).
For decent ergonomics this requires some work on the Python API, but for a less fluid api this should be already possible I believe: the ids are created on the python side in
yeah my idea would be to treat |
Thanks for the explanations @Wumpf.
Would this data still be saved in a blueprint? If so, how can data be loaded and stored without a fbs? I am worried side stepping the fbs and other infrastructure will make solving this issue quite a hack. I am therefore leaning towards implementing the fbs from #8050 for this feature. What do you think? |
yes. Those fbs files are strictly just for the codegen that generates types (and their serialization!) for all sdk languages. But that doesn't prevent just making up stuff that isn't defined in those. Otherwise custom component/data types wouldn't work either. doing #8050 first (and separately) would definitely be preferable, yes :). I was just looking for ways to cutting corners to speed this up 🤷 |
Let's assume #8050 is implemented, what would be the way to link multiple TimeSeriesViews together (where linking together means to share the view range and query data range)? |
I am not planning to work on this. @Wumpf , do you have a timeline by when this will be implemented? |
I was really hoping to get to that sooner, but other things keep cropping up, so no timeline unfortunately |
FYI, I started working on an alternative approach: Instead of syncing multiple TimeSeriesViews, my version of the TimeSeriesView is capable to display multiple timeseries above each other. By adding a scrolling container, this makes it possible to scroll through a list of timeseries plots easily. For this to work, the interaction with the plot is very different compared to the current one. There are also some other features I am adding that makes determining what to plot easier when you have data with a lot of dimensions. Would having such a TimeSeriesView make sense to be integrated with rerun directly? I am willing to contribute my code and would be much easier for me if it could go into the main branch. |
definitely curious about this! I believe we considered containers with larger virtual area in the past, but never designed it out |
Yes, I'm a big proponent of going the "subplots-within-a-single-view" way instead of trying to sync axes across separate, possibly arbitrarily laid out views. We've discussed this at a workshop during our last offsite and I made a proposal to that effect, but this has yet to be fleshed out into a concrete design (or even an issue). Digging into the specifics since you mention a scrolling container: I would much like to explore alternatives to that. Plots are scrollable, and, in my experience, having scrollable things inside of a scrollable thing makes for a poor UX. Off the bat, my idea would be to always fit the full subplot grid to the available space, and maybe to collapse it to a single plot with a mini-map if space becomes tight. (Surely @gavrelina will have much better ideas.) That would obviously only work for a limited number of plots (say, less than 10ish). Do you have a use case that requires many more plots? If so, could you expend on it? |
Thank you for your feedback. I finished a first prototype. You can see a short demo in this video: https://youtu.be/5yKvZmUyPVo You can find the code here: https://github.com/jviereck/egui_plot/tree/timeseries_split_view Just call |
Thanks for your input @Wumpf and @abey79 . The proof of concept I have implemented is based on a previous timeseries plotter I wrote during my PhD. The code is here: https://github.com/KyberLabsAI/mim_data_utils/blob/main/index2.html. When working on robots (think humanoid robot), there are hundreds of data points coming at every timestep (where a timestep is in the range of 1000 Hz to 30,000 Hz). Doing the current approach from rerun with manually toggeling on and off individual data lines doesn't scale well. As the data is oven packed as on vector (e.g. joint positions as What I found helpful is to specify which data to display in which plot using a text input field. When the plots get too complicated, I used a script to generate the plot definition and then inserted it to the plotter (e.g. show all the forces, joint positions and joint velocities for the front-left leg of a quadruped as well as the IMU angular velocities in local and world frames). The string to specify the plot layouts support slicing in my implementation: Writing
In my concept, the y-axis is not scrollable. Instead, it auto-scales based on the current displayed y-data range. This works remarkable well when working on my real live applications so far.
I am using a fixed size of 300 px height at the moment. If there is enough space, the plots could be higher but if the plots become less than e.g. 300 px in height, I would start scrolling.
See the example above for plotting joint positions, joint velocities and IMU data. As each of these quantities has different data ranges, it is hard to plot them together. Still, you want to see correlations between the data (e.g. when did a joint stop moving and did the IMU pick it up?) by having the plot data aligned along the x axis with each other. |
Hi, is there any feedback on my design proposal / proof-of-concept? If this looks fine with you, I would start an implementation in rerun. |
Awesome work with the prototype! Here is some feedback. Layout
Although his is a very useful feature, I think it is too opinionated to be enforced. So IMO, the scrollable-thing-in-a-scrollable-thing issue remains.
This is also a bit too opinionated. Of course, you might offer a setting of that that, but that would become an alternative sizing mechanism to the existing one (aka the current view/container tile system of the viewport), something I'd rather avoid.
For sure, stacked plots are useful. My question was about huge stacks, eg. more than could reasonably fit on a single screen without scrolling. As is clear from the above, I am in general reluctant to introduce within views layout features which exist (or should exist) at the viewport level. In that sense, the mere idea of stacked plots within a single plot view is arguably "wrong" on the conceptual level. Since it is hard to tweak the current viewport paradigm to achieve axes alignment (we tried), the stacked plot view we're discussing is IMO the right way forward—despite its possible conceptual flaws. We should however strive to be minimalistic in the layout feature we introduce. Based on that framework, I believe the plot view should behave as follows:
UIYour DSL proposal is interesting. I see this being very useful to accelerate the building of a prototype. Because it "competes" (again 😀) with the existing entity path filter DSL (which will evolve in the future), I don't think we could keep it for the final feature. But I'm confident that with the help of @gavrelina we can figure out and implement a suitable configuration UI. |
Hi @abey79 , Thank you for you're feedback. This makes sense.
I like the idea to extend the existing path filter DSL (I have not thought about it much). I wonder if something like this could work
Where you can specify first a general match and then a per-plot match for data to display. Ideally, the match would understand the slicing syntax (like What do you think? |
It's an interesting idea, but that particular DSL is at the crossroads of many other things (both that exist and are to come), so it's rather tricky to design. I suggest not going in that territory for the present issue. My current idea for the configuration UI would be to have a sort of minimap in the selection panel, where each subplot is represented by a grey box. The number and arrangement of these box would depend on the chosen number of rows/column. By default, all entities would land in the top-left box, and could be dispatched to other boxes by drag-and-drop. |
I am worried that dran-and-drop will be very time consuming to setup the
subplots. Especially if every dimension of a vector is a independent drag
and drop. I would prefer a text bases selector pattern.
Can we have a per-plot text box to specify the data based on the global
entity path dsl? As these are two different textboxes, the syntax can be
slightly different.
…On Mon, Jan 20, 2025 at 10:56 Antoine Beyeler ***@***.***> wrote:
What do you think?
It's an interesting idea, but that particular DSL is at the crossroads of
many other things (both that exist and are to come), so it's rather tricky
to design. I suggest not going in that territory for the present issue.
My current idea for the configuration UI would be to have a sort of
minimap in the selection panel, where each subplot is represented by a grey
box. The number and arrangement of these box would depend on the chosen
number of rows/column. By default, all entities would land in the top-left
box, and could be dispatched to other boxes by drag-and-drop.
—
Reply to this email directly, view it on GitHub
<#7931 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AABOZZ3J5DW27Q2KYQHYJNL2LUMBBAVCNFSM6AAAAABQ2G2AAOVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDMMBSG43TCOBSGE>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Making this UI efficient is a challenge for @gavrelina. However, we are most likely not rolling out a super-advanced UI for "extreme" case at the expense of ease-of-use and learning curve for simpler (and likely more common) use case. IMO, the correct way to setup complex sub-plots involving lots of entities is to use the blueprint API (which, by the way, will have to be extended to nicely cover the sub-plot case). |
Okay, I leave the design for the property panel to @gavrelina then and focus on setting things up using the blueprint API only.
Makes sense. I can design some blueprint API and share it here. |
As for the API, the current API to specify a TimeSeriesView looks like this:
To make it work for subplots, I was thinking to add a rrb.TimeSeriesView(
origin='data/',
... # Other current fields.
row=3, # Optional - if not provided default value is 1.
columns=1 # Optional - if not provided default value is 1.
sub_plots=[
rrb.SubPlotData(row: 1, column: 1, filter: 'data/trig'),
rrb.SubPlotData(row: 2, column: 1, filter: 'data/pow[1]'),
rrb.SubPlotData(row: 3, column: 1, filter: 'data/trig[1]'),
rrb.SubPlotData(row: 3, column: 1, filter: 'data/pow[1]')
]
) The What do you think? |
Hey all, great work! I'm really looking forward to this feature! As an intermediate solution that potentially require less changes, would it be possible to use the range-selection feature in the time-line to update all TimeSeriesView's x-axes? Most, if not all of my usecases would be satisfied with that. |
I took another take at the I feel this is a much easier solution that trying to implement multi-plots-in-one-container approach. Let me know what you think. Screen.Recording.2025-01-25.at.11.10.54.mov |
Yes, these are a few of the many reasons I believe the "sub-plot" approach is easier than the "inter-view-synchronisation". Your blueprint API proposal look pretty good indeed. Most of the design complexity here lies in the per-sub-plot filter. It should probably have the same syntax as the view's entity path filter. But does it replace it? Or is it applied on top of the view filter? Neither feel particularly well integrated in the larger picture of how views generally work. I wonder what could be the alternatives. @Wumpf do you have a take on this specific point? |
@abey79 just checking - did you see my latest comment about progress on inter-view-synchronization? I ised this way to wync plots yesterday for some real world applications and it worked fine. |
I saw that, and yeah it's pretty nice, but I'm still unconvinced of the UX of this approach (what if views are moved around? how do you deal with multiple groups of views which should by independently synchronised?) and its architectural implications (as per your "//HACK" code comment 🙃). In other word, there is a great deal of work left to make that shippable. |
Thank you for your comments @abey79 !
The moved plots keep syncing with respect to each other. That's what I would expect to happen. Do you have other expectations?
My idea is to introduce a new field on
Assuming the hack part is okay, I feel there is not much left to do. I know the hack-solution doesn't look great, but it's working. I am worried that going with the "sub-plot" approach will be cleaner to implement but it will be much harder to figure out the UX: How do you assign the data to each subplot? How can you adjust the order of the subplots? Going with the "sub-plot" approach creates a lot of new UX patterns that are different from the existing ones. The UX for the inter-view-synchronization approach follows very much how things are done in rerun otherwise (you see a tree of which data is plotted on the left panel etc, you can drag views around between containers etc). So before all the UX for the "sub-plot" approach is figured out, I don't think this is the approach we should start to implement. |
In my setup I am displaying data in two TimeSeriesView. The TimeSeriesView are positioned in a vertical viewport above each other. To compare the data from multiple axis, I have to manually position the x-axis (time in this case) to align well. It would be great if there would be a way to keep the two time-/x-axis in sync between the two views.
I was wondering if there could be a flag on the vertical viewport to sync the x-axis for contained TimeSeriesViews.
If someone could provide me with some pointers on how to implement this feature, I am more than happy to work on a pull request.
The text was updated successfully, but these errors were encountered: