Skip to content
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

Add experimental Dataframe Space View #4468

Merged
merged 14 commits into from
Dec 13, 2023
11 changes: 6 additions & 5 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,11 +88,11 @@ Of course, this will only take us so far. In the future we plan on caching queri
Here is an overview of the crates included in the project:

<picture>
<img src="https://static.rerun.io/crates/eaea8b78fd7efbefd76c0d6a09086ef9cd742c8b/full.png" alt="">
<source media="(max-width: 480px)" srcset="https://static.rerun.io/crates/eaea8b78fd7efbefd76c0d6a09086ef9cd742c8b/480w.png">
<source media="(max-width: 768px)" srcset="https://static.rerun.io/crates/eaea8b78fd7efbefd76c0d6a09086ef9cd742c8b/768w.png">
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/crates/eaea8b78fd7efbefd76c0d6a09086ef9cd742c8b/1024w.png">
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/crates/eaea8b78fd7efbefd76c0d6a09086ef9cd742c8b/1200w.png">
<img src="https://static.rerun.io/crates/21c577a05570720e96b850e8da21b5aa1fcd6c93/full.png" alt="">
<source media="(max-width: 480px)" srcset="https://static.rerun.io/crates/21c577a05570720e96b850e8da21b5aa1fcd6c93/480w.png">
<source media="(max-width: 768px)" srcset="https://static.rerun.io/crates/21c577a05570720e96b850e8da21b5aa1fcd6c93/768w.png">
<source media="(max-width: 1024px)" srcset="https://static.rerun.io/crates/21c577a05570720e96b850e8da21b5aa1fcd6c93/1024w.png">
<source media="(max-width: 1200px)" srcset="https://static.rerun.io/crates/21c577a05570720e96b850e8da21b5aa1fcd6c93/1200w.png">
</picture>

<!-- !!! IMPORTANT!!!
Expand Down Expand Up @@ -134,6 +134,7 @@ Update instructions:
| re_renderer | A wgpu-based renderer for all your visualization needs. |
| re_space_view | Types & utilities for defining Space View classes and communicating with the Viewport. |
| re_space_view_bar_chart | A Space View that shows a single bar chart. |
| re_space_view_dataframe | A Space View that shows the data contained in entities in a table. |
| re_space_view_spatial | Space Views that show entities in a 2D or 3D spatial relationship. |
| re_space_view_tensor | A Space View dedicated to visualizing tensors with arbitrary dimensionality. |
| re_space_view_text_document | A simple Space View that shows a single text box. |
Expand Down
21 changes: 21 additions & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ re_sdk_comms = { path = "crates/re_sdk_comms", version = "=0.12.0-alpha.1", defa
re_smart_channel = { path = "crates/re_smart_channel", version = "=0.12.0-alpha.1", default-features = false }
re_space_view = { path = "crates/re_space_view", version = "=0.12.0-alpha.1", default-features = false }
re_space_view_bar_chart = { path = "crates/re_space_view_bar_chart", version = "=0.12.0-alpha.1", default-features = false }
re_space_view_dataframe = { path = "crates/re_space_view_dataframe", version = "=0.12.0-alpha.1", default-features = false }
re_space_view_spatial = { path = "crates/re_space_view_spatial", version = "=0.12.0-alpha.1", default-features = false }
re_space_view_tensor = { path = "crates/re_space_view_tensor", version = "=0.12.0-alpha.1", default-features = false }
re_space_view_text_log = { path = "crates/re_space_view_text_log", version = "=0.12.0-alpha.1", default-features = false }
Expand Down
33 changes: 33 additions & 0 deletions crates/re_space_view_dataframe/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
[package]
authors.workspace = true
description = "A Space View that shows the data contained in entities in a table."
edition.workspace = true
homepage.workspace = true
license.workspace = true
name = "re_space_view_dataframe"
publish = true
readme = "README.md"
repository.workspace = true
rust-version.workspace = true
version.workspace = true
include = ["../../LICENSE-APACHE", "../../LICENSE-MIT", "**/*.rs", "Cargo.toml"]

[package.metadata.docs.rs]
all-features = true

[dependencies]
re_arrow_store.workspace = true
re_data_store.workspace = true
re_data_ui.workspace = true
re_log.workspace = true
re_log_types.workspace = true
re_query.workspace = true
re_renderer.workspace = true
re_tracing.workspace = true
re_types.workspace = true
re_ui.workspace = true
re_viewer_context.workspace = true

egui_extras.workspace = true
egui.workspace = true
itertools.workspace = true
10 changes: 10 additions & 0 deletions crates/re_space_view_dataframe/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# re_space_view_dataframe

Part of the [`rerun`](https://github.com/rerun-io/rerun) family of crates.

[![Latest version](https://img.shields.io/crates/v/re_space_view_dataframe.svg)](https://crates.io/crates/re_space_view_dataframe?speculative-link)
[![Documentation](https://docs.rs/re_space_view_dataframe/badge.svg)](https://docs.rs/re_space_view_dataframe?speculative-link)
![MIT](https://img.shields.io/badge/license-MIT-blue.svg)
![Apache](https://img.shields.io/badge/license-Apache-blue.svg)

A Space View that shows the data contained in entities in a table.
8 changes: 8 additions & 0 deletions crates/re_space_view_dataframe/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
//! Rerun `Data` Space View
//!
//! A Space View that shows the data contained in entities in a table.

mod space_view_class;
mod view_part_system;

pub use space_view_class::DataframeSpaceView;
253 changes: 253 additions & 0 deletions crates/re_space_view_dataframe/src/space_view_class.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
use std::collections::BTreeSet;

use egui_extras::Column;

use re_arrow_store::{DataStore, LatestAtQuery};
use re_data_store::{EntityProperties, InstancePath};
use re_data_ui::item_ui::instance_path_button;
use re_log_types::{EntityPath, Timeline};
use re_query::get_component_with_instances;
use re_viewer_context::{
AutoSpawnHeuristic, PerSystemEntities, SpaceViewClass, SpaceViewClassRegistryError,
SpaceViewId, SpaceViewSystemExecutionError, SystemExecutionOutput, UiVerbosity, ViewQuery,
ViewerContext,
};

use crate::view_part_system::EmptySystem;

#[derive(Default)]
pub struct DataframeSpaceView;

impl SpaceViewClass for DataframeSpaceView {
type State = ();

const IDENTIFIER: &'static str = "Dataframe";
const DISPLAY_NAME: &'static str = "Dataframe";

fn icon(&self) -> &'static re_ui::Icon {
//TODO(ab): fix that icon
&re_ui::icons::SPACE_VIEW_TEXTBOX
}

fn help_text(&self, _re_ui: &re_ui::ReUi) -> egui::WidgetText {
"Show the data contained in entities in a table.\n\n\
Each entity is represented by as many rows as it has instances. This includes out-of-bound \
instances—instances from secondary components that cannot be joined to the primary \
component—that are typically not represented in other space views. Also, splats are merged \
into the entity's instance."
.into()
}

fn on_register(
&self,
system_registry: &mut re_viewer_context::SpaceViewSystemRegistry,
) -> Result<(), SpaceViewClassRegistryError> {
system_registry.register_part_system::<EmptySystem>()
}

fn preferred_tile_aspect_ratio(&self, _state: &Self::State) -> Option<f32> {
None
}

fn layout_priority(&self) -> re_viewer_context::SpaceViewClassLayoutPriority {
re_viewer_context::SpaceViewClassLayoutPriority::Low
}

fn auto_spawn_heuristic(
&self,
_ctx: &ViewerContext<'_>,
_space_origin: &EntityPath,
_ent_paths: &PerSystemEntities,
) -> re_viewer_context::AutoSpawnHeuristic {
AutoSpawnHeuristic::NeverSpawn
}

fn selection_ui(
&self,
_ctx: &ViewerContext<'_>,
_ui: &mut egui::Ui,
_state: &mut Self::State,
_space_origin: &EntityPath,
_space_view_id: SpaceViewId,
_root_entity_properties: &mut EntityProperties,
) {
}

fn ui(
&self,
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
_state: &mut Self::State,
_root_entity_properties: &EntityProperties,
query: &ViewQuery<'_>,
_system_output: SystemExecutionOutput,
) -> Result<(), SpaceViewSystemExecutionError> {
re_tracing::profile_function!();

// These are the entity paths whose content we must display.
let sorted_entity_paths: BTreeSet<_> = query
.iter_all_data_results()
.filter(|data_result| data_result.resolved_properties.visible)
.map(|data_result| &data_result.entity_path)
.cloned()
.collect();
emilk marked this conversation as resolved.
Show resolved Hide resolved

let store = ctx.store_db.store();
let latest_at_query = query.latest_at_query();

let sorted_instance_paths: Vec<_>;
let sorted_components: BTreeSet<_>;
{
re_tracing::profile_scope!("query");

// Produce a sorted list of each entity with all their instance keys. This will be the rows
// of the table.
//
// Important: our semantics here differs from other built-in space views. "Out-of-bound"
// instance keys (aka instance keys from a secondary component that cannot be joined with a
// primary component) are not filtered out. Reasons:
// - Primary/secondary component distinction only makes sense with archetypes, which we
// ignore. TODO(#4466): make archetypes more explicit?
// - This space view is about showing all user data anyways.
//
// Note: this must be a `Vec<_>` because we need random access for `body.rows()`.
sorted_instance_paths = sorted_entity_paths
.iter()
.flat_map(|entity_path| {
sorted_instance_paths_for(entity_path, store, &query.timeline, &latest_at_query)
})
.collect();

// Produce a sorted list of all components that are present in one or more entities. This
// will be the columns of the table.
sorted_components = sorted_entity_paths
.iter()
.flat_map(|entity_path| {
store
.all_components(&query.timeline, entity_path)
.unwrap_or_default()
})
// TODO(#4466): make showing/hiding indicators components an explicit optional
.filter(|comp| !comp.is_indicator_component())
.collect();
}

// Draw the header row.
let header_ui = |mut row: egui_extras::TableRow<'_, '_>| {
row.col(|ui| {
ui.strong("Entity");
});

for comp in &sorted_components {
row.col(|ui| {
ui.strong(comp.short_name());
});
}
};

// Draw a single line of the table. This is called for each _visible_ row, so it's ok to
// duplicate some of the querying.
let row_ui = |idx: usize, mut row: egui_extras::TableRow<'_, '_>| {
let instance = &sorted_instance_paths[idx];

// TODO(#4466): make it explicit if that instance key is "out
// of bounds" (aka cannot be joined to a primary component).

row.col(|ui| {
instance_path_button(ctx, ui, None, instance);
});

for comp in &sorted_components {
row.col(|ui| {
// TODO(#4466): make it explicit if that value results
// from a splat joint.

if let Some((_, comp_inst)) =
// This is a duplicate of the one above, but this ok since this codes runs
// *only* for visible rows.
get_component_with_instances(
store,
&latest_at_query,
&instance.entity_path,
*comp,
)
{
ctx.component_ui_registry.ui(
ctx,
ui,
UiVerbosity::Small,
&latest_at_query,
&instance.entity_path,
&comp_inst,
&instance.instance_key,
);
} else {
ui.weak("-");
}
});
}
};

{
re_tracing::profile_scope!("table UI");

egui::ScrollArea::both()
.auto_shrink([false, false])
.show(ui, |ui| {
ui.style_mut().wrap = Some(false);

egui::Frame {
inner_margin: egui::Margin::same(5.0),
..Default::default()
}
.show(ui, |ui| {
egui_extras::TableBuilder::new(ui)
.columns(
Column::auto_with_initial_suggestion(200.0).clip(true),
1 + sorted_components.len(),
)
.resizable(true)
.vscroll(false)
.auto_shrink([false, true])
.striped(true)
.header(re_ui::ReUi::table_line_height(), header_ui)
.body(|body| {
body.rows(
re_ui::ReUi::table_line_height(),
sorted_instance_paths.len(),
row_ui,
);
});
});
});
}

Ok(())
}
}

/// Returns a sorted, deduplicated iterator of all instance paths for a given entity.
///
/// This includes _any_ instance key in all components logged under this entity path, excluding
/// splats.
fn sorted_instance_paths_for<'a>(
entity_path: &'a EntityPath,
store: &'a DataStore,
timeline: &'a Timeline,
latest_at_query: &'a LatestAtQuery,
) -> impl Iterator<Item = InstancePath> + 'a {
store
.all_components(timeline, entity_path)
.unwrap_or_default()
.into_iter()
.filter(|comp| !comp.is_indicator_component())
.flat_map(|comp| {
get_component_with_instances(store, latest_at_query, entity_path, comp)
.map(|(_, comp_inst)| comp_inst.instance_keys())
.unwrap_or_default()
})
.filter(|instance_key| !instance_key.is_splat())
.collect::<BTreeSet<_>>() // dedup and sort
.into_iter()
.map(|instance_key| InstancePath::instance(entity_path.clone(), instance_key))
}
Loading
Loading