-
Notifications
You must be signed in to change notification settings - Fork 588
Allow specifying widths of BarChart bars
#12090
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
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi! Thanks for opening this pull request.
Because this is your first time contributing to this repository, make sure you've read our Contributor Guide and Code of Conduct.
Wumpf
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for contributing this feature! Looks good in principle, but I'd like to discuss whether a simpler component type would be appropriate, see comment. 🙂
Otherwise, could you please add an example for this to https://github.com/rerun-io/rerun/blob/main/docs/snippets/all/archetypes/bar_chart.py (/cpp/rs) and add a screenshot the outcome to the PR description?
Changing those snippets will unfortunately also require updating https://github.com/rerun-io/rerun/blob/main/tests/assets/rrd/snippets/archetypes/bar_chart.rrd which we use for backwards compatibility tests so that compare_snippet_output.py runs through. This bit can be a bit tedious, so happy to help with that if needed.
| @@ -25,4 +25,7 @@ table BarChart ( | |||
|
|
|||
| /// The abscissa corresponding to each value. Should be a 1-dimensional tensor (i.e. a vector) in same length as values. | |||
| abscissa: rerun.components.TensorData ("attr.rerun.component_optional", nullable, order: 3000); | |||
|
|
|||
| /// The width of the bins. Should be a 1-dimensional tensor (i.e. a vector) in same length as values. | |||
| widths: rerun.components.TensorData ("attr.rerun.component_optional", nullable, order: 4000); | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason we ended up with TensorData on abscissa is it was really important to the person contributing that this has the same datatype flexibility as the values. Which makes sense since this is again still core data.
So, now we're not super happy with today's TensorData for a bunch of reasons: as you noted there's a lot of bloaty conversion code. But also, it uses arrow unions under the hood and they cause a lot of trouble when querying data back out in general.
All of which makes me wonder whether we could use a much simpler component type here that also describes the semantics better - in a way we don't have "raw numerical data" here, but rather a visual width.
We already have a Length component to define a length in a local coordinate system (which is opposed to StrokeWidth which is a width in UI coordinates).
The big drawback here is though that it is limited to float values. Just going by your own usecase, is that too limiting or just fine?
Independent of the above, the docstring here should better describe how these widths are specified (not in UI points as many other things but rather in x axis units just like values) and that they default to 1
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the quick review 😄
will fix the doc
I would be fine with only allowing float values, although I would also feel its weird to have a different datatype than abscissa and values.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some additional thought:
As a user, the ideal workflow would be to just log some table of floats (with named columns, perhaps) and to decide when using the viewer how to visualize the data (maybe using a python script 😶). Right now it feels like rerun has some (really great) default ways to visualize my data, but the data is pretty difficult to interact with/show in non-supported ways after it gets converted into a .rrd file.
In the case of BarCharts, what i would really like, is to just log the entire set of points which represents samples from my distribution, and to look at the distribution evolved over time in the viewer.
Im not familiar enough with the project to make any concrete suggestions though
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔
Re datatypes:
We generally want to get more flexible in regards to datatypes. It's been long the vision that components types are a semantic layer that allow generically a number of different datatypes. We made some steps towards that but aren't quite there yet.
Agreed that in this concrete case mixing types might be a bit unintuitive, but from a semantics pov it makes sense. So if floats on widths aren't limiting for you here I'd like to go through with it
Re visualization configuration:
Already today you can specify all these properties in blueprint overrides as well - it often makes sense to only put what you consider "data" to be in the rrd and everything else be a blueprint override, including stuff like bar widths. That said, the ux of practically loading an rrd and sending different blueprints is a bit lacking, but it's possible and iirc demonstrated in the docs.
Also, UI for blueprint overrides is lacking for more complex stuff like arrays, making it impossible to configure these things adhoc in a meaningful way without code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alright, will change the datatype. I am just a bit mystified regarding what the concept of a component is, since it seems like there's also something like a 'list of components' that can also be part of an archetype?
and the syntax to query these components seem to differ to regular components aswell. Think i sort of got it working now using [Length] as a datatype, will push some changes shortly
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, will have a look soon!
since it seems like there's also something like a 'list of components' that can also be part of an archetype?
Internally we always store arrays for each components and the notation (whether array or not) in the fbs files is more or less just a hint for the api codegen. TensorData is a very weird exception many ways right now. It is an array internally and I think our last remaining arrow union type, so having that as a starting point makes everything else confusing 😄
Generally the idea is that there's a bunch of raw data types that are then wrapped by component types which wrap them into some loose semantic.
|
If I understand correctly, #6265 is actually not closed by this since we'd need a property of the graph itself to be wider than the bars that show up in it. That should be a view property of bar chart view then. However, it also looks like I incorrectly closed #4764 when the abscissa PR came around which doesn't fully address that ticket 😳 |
I actually think that #6265 should already be closed, since the bar chart viewer currently has the x and y axis specified through blueprint. Maybe it was fixed here? #6514 |
|
What's imho missing for #6265 is to set the plot window to a certain width, similar to what the time axis width setting on time series view does |
|
Thanks for adding examples & screenshot! |
Hmm I'm not familiar with the time series view, but this is how it looks on my project (without the widths changes) when zoomed out and scrubbing the timeline: Screencast.from.2025-12-05.10-42-47.webmIt's maybe not optimal, (might be a better default to specify the x-axis width to cover the entire timeline instead of dynamically changing the limits) but I think this is what was asked for in the issue? |
|
The issue author of #6265 specifically asks for bins moving within a given range as on the supplied gif, so I understood it as having to configure the range of the plots. Well, either way that's a feature disparity there with the time series, so if that ticket doesn't cover that we need a new one 😄 On the time series view you can set x and y axis range from blueprint code, which on the bar chart you can only do via manual interaction which is also then not persisted over sessions |
|
I also changed the component fallback, trying to match what I saw done with 'radii' and other similar 'list-components' |
| TensorBuffer::F64(data) => data.iter().copied().collect(), | ||
| }; | ||
|
|
||
| let chart = match &tensor.buffer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Was thinking this should be a pretty uncontroversial refactor. Can split into another PR if necessary however.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to, appreciated! :)
Wumpf
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks for bearing with me here so far 🙇
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this file needs to be updated rather than deleted
| let widths = if let Some(chunked_widths) = | ||
| results.get_required_chunks(BarChart::descriptor_widths().component) | ||
| { | ||
| let mut widths = Vec::with_capacity(length as usize); | ||
| for slice in chunked_widths | ||
| .iter() | ||
| .flat_map(|chunk| chunk.iter_slices::<f32>()) | ||
| { | ||
| widths.extend_from_slice(slice); | ||
| } | ||
| widths | ||
| } else { | ||
| let fallback_width: Length = typed_fallback_for( | ||
| &ctx.query_context(data_result, &view_query.latest_at_query()), | ||
| BarChart::descriptor_widths().component, | ||
| ); | ||
| vec![fallback_width.0.into(); length as usize] | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This goes into the right direction, but something we usually do is to repeat the last element often enough to ensure that we have enough of them. This has two advantages:
- if there's less widths than values we won't just loose values
- a user can just specify a single value that is then splatted out to everything
- that's then consistent with the fallback you put in which is just returning a single value :)
Unfortunately that's not handled by any query later and is done in-place most of everywhere.
My suggestion would be to use the fact that we always have a LatestAtResults where we know that the we get back a single row worth of data. This allows us also to set the semantics straight:
let widths: &[f32] = results
.iter_as(view_query.timeline, BarChart::descriptor_widths().component)
.slice::<f32>()
.next() // It's a latest-at result, therefore this is a single row, therefore we there can't be more than 1 chunk involved.
.map_or(&[], |((_time, _row), slice)| slice);
let fallback_width = typed_fallback_for::<Length>(
&ctx.query_context(data_result, &view_query.latest_at_query()),
BarChart::descriptor_widths().component,
)
.0;
let widths = clamped_vec_or(fallback_width, length as usize, fallback_width);(there's some other subtle differences in how this plays out, but it has mostly the same effect.
clamped_vec_or is a utility method that lives over in re_view_spatial::entity_iterator.rs but I think it should be moved to re_view (can go to lib.rs for now, where there's already a related similar utility, clamped_or_nothing).
Guess a clamped_vec_or_else would be nice to not evaluate the fallback if not needed here 🤔 . But I've burdened you already enough with refactor asks
I realize that some of this stuff goes pretty deep into Rerun data model arcana and our query utilities are really poorly documented and a bit all over the place. Plus since the tensor view has a lot of "special" queries it has no good examples yet (unlike most views it only deals with latest-at and it has to deal with the relatively strange TensorData query).
I'm bit embarrassed we left things in this state; I think the underlying mechanics do work out, but it needs a thorough cleanup & documentation pass.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this file needs updating rather than deleting. The most reliable (albeit very slow the first time around) way to do so is pixi run -e py python ./docs/snippets/compare_snippet_output.py --write-missing-backward-assets archetypes/bar_chart
Related
What
Adds an optional
widthscomponent to theBarChartarchetype. Uses this when rendering theBarChartin the viewer.This together with abscissa should allow logging arbitrary bar charts, to visualize distributions.
From
bar_chartsnippet: