Skip to content
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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, defined in x-axis units and defaults to 1. Should be a 1-dimensional tensor (i.e. a vector) in same length as values.
widths: [rerun.components.Length] ("attr.rerun.component_optional", nullable, order: 4000);
}
57 changes: 53 additions & 4 deletions crates/store/re_types/src/archetypes/bar_chart.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions crates/store/re_types/src/reflection/mod.rs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ pub fn archetype_field_fallbacks(registry: &mut FallbackProviderRegistry) {
components::TensorData(tensor_data)
},
);
registry.register_component_fallback_provider(
archetypes::BarChart::descriptor_widths().component,
|_| components::Length::from(1.0),
);

// GraphNodes
registry.register_component_fallback_provider(
Expand Down
107 changes: 26 additions & 81 deletions crates/viewer/re_view_bar_chart/src/view_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ impl ViewClass for BarChartView {
fn create_bar_chart<N: Into<f64>>(
ent_path: &EntityPath,
indexes: impl Iterator<Item = f64>,
widths: impl Iterator<Item = f32>,
values: impl Iterator<Item = N>,
color: &re_types::components::Color,
background_color: egui::Color32,
Expand All @@ -240,10 +241,11 @@ impl ViewClass for BarChartView {
"bar_chart",
values
.zip(indexes)
.zip(widths)
.enumerate()
.map(|(i, (value, index))| {
Bar::new(index + 0.5, value.into())
.width(1.0) // No gaps
.map(|(i, ((value, index), width))| {
Bar::new(index + (0.5 * width as f64), value.into())
.width(width as f64)
.name(format!("{ent_path} #{i}"))
.fill(fill)
.stroke((1.0, stroke_color))
Expand All @@ -260,6 +262,7 @@ impl ViewClass for BarChartView {
abscissa,
values: tensor,
color,
widths,
},
) in charts
{
Expand All @@ -277,85 +280,27 @@ impl ViewClass for BarChartView {
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! :)

TensorBuffer::U8(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied(),
color,
background_color,
),
TensorBuffer::U16(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied(),
color,
background_color,
),
TensorBuffer::U32(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied(),
color,
background_color,
),
TensorBuffer::U64(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied().map(|v| v as f64),
color,
background_color,
),
TensorBuffer::I8(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied(),
color,
background_color,
),
TensorBuffer::I16(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied(),
color,
background_color,
),
TensorBuffer::I32(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied(),
color,
background_color,
),
TensorBuffer::I64(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied().map(|v| v as f64),
color,
background_color,
),
TensorBuffer::F16(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().map(|f| f.to_f32()),
color,
background_color,
),
TensorBuffer::F32(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied(),
color,
background_color,
),
TensorBuffer::F64(data) => create_bar_chart(
ent_path,
arg.iter().copied(),
data.iter().copied(),
color,
background_color,
),
let data: ::arrow::buffer::ScalarBuffer<f64> = match &tensor.buffer {
TensorBuffer::U8(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::U16(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::U32(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::U64(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::I8(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::I16(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::I32(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::I64(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::F16(data) => data.iter().map(|v| f64::from(*v)).collect(),
TensorBuffer::F32(data) => data.iter().map(|v| *v as f64).collect(),
TensorBuffer::F64(data) => data.iter().copied().collect(),
};
let chart = create_bar_chart(
ent_path,
arg.iter().copied(),
widths.iter().copied(),
data.iter().copied(),
color,
background_color,
);

let id = egui::Id::new(ent_path.hash());
plot_item_id_to_entity_path.insert(id, ent_path.clone());
Expand Down
28 changes: 25 additions & 3 deletions crates/viewer/re_view_bar_chart/src/visualizer_system.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,19 @@ use re_chunk_store::LatestAtQuery;
use re_entity_db::EntityPath;
use re_types::{
archetypes::BarChart,
components::{self},
components::{self, Length},
datatypes,
};
use re_view::DataResultQuery as _;
use re_view::{DataResultQuery as _, RangeResultsExt as _};
use re_viewer_context::{
IdentifiedViewSystem, ViewContext, ViewContextCollection, ViewQuery, ViewSystemExecutionError,
VisualizerQueryInfo, VisualizerSystem,
VisualizerQueryInfo, VisualizerSystem, typed_fallback_for,
};

#[derive(Default)]
pub struct BarChartData {
pub abscissa: datatypes::TensorData,
pub widths: Vec<f32>,
pub values: datatypes::TensorData,
pub color: components::Color,
}
Expand Down Expand Up @@ -56,15 +57,36 @@ impl VisualizerSystem for BarChartVisualizerSystem {
};

if tensor.is_vector() {
let length: u64 = tensor.shape().iter().product();

let abscissa: components::TensorData =
results.get_mono_with_fallback(BarChart::descriptor_abscissa().component);
let color = results.get_mono_with_fallback(BarChart::descriptor_color().component);
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]
};
Comment on lines +65 to +82
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
Author

Choose a reason for hiding this comment

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

Will take a look after work. Yea, coming from bevy the data model is not quite as intuitive to an outsider 😄 But thank you for the quick reviews, I think I have some better understanding now.

self.charts.insert(
data_result.entity_path.clone(),
BarChartData {
abscissa: abscissa.0.clone(),
values: tensor.0.clone(),
color,
widths,
},
);
}
Expand Down
Loading