Skip to content

Conversation

@bilsen
Copy link

@bilsen bilsen commented Dec 3, 2025

Related

What

Adds an optional widths component to the BarChart archetype. Uses this when rendering the BarChart in the viewer.

This together with abscissa should allow logging arbitrary bar charts, to visualize distributions.
From bar_chart snippet:

image

Copy link

@github-actions github-actions bot left a 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.

@bilsen bilsen marked this pull request as ready for review December 3, 2025 19:08
@Wumpf Wumpf self-requested a review December 4, 2025 09:08
@Wumpf Wumpf added enhancement New feature or request 📺 re_viewer affects re_viewer itself include in changelog feat-plots Plots, charts, graphs, timeseries, … labels Dec 4, 2025
Copy link
Member

@Wumpf Wumpf left a 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);
Copy link
Member

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

Copy link
Author

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.

Copy link
Author

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

Copy link
Member

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.

Copy link
Author

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

Copy link
Member

@Wumpf Wumpf Dec 7, 2025

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.

@Wumpf
Copy link
Member

Wumpf commented Dec 4, 2025

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 😳

@bilsen
Copy link
Author

bilsen commented Dec 4, 2025

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

@Wumpf
Copy link
Member

Wumpf commented Dec 5, 2025

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

@Wumpf
Copy link
Member

Wumpf commented Dec 5, 2025

Thanks for adding examples & screenshot!

@bilsen
Copy link
Author

bilsen commented Dec 5, 2025

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

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.webm

It'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?

@Wumpf
Copy link
Member

Wumpf commented Dec 5, 2025

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

@bilsen
Copy link
Author

bilsen commented Dec 6, 2025

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 {
Copy link
Author

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.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to, appreciated! :)

Copy link
Member

@Wumpf Wumpf left a 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 🙇

Copy link
Member

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

Comment on lines +65 to +82
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]
};
Copy link
Member

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.

Copy link
Member

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request feat-plots Plots, charts, graphs, timeseries, … include in changelog 📺 re_viewer affects re_viewer itself

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow specifying horizontal range of barchart Allow to specify boundaries for BarChart

2 participants