-
Notifications
You must be signed in to change notification settings - Fork 378
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add experimental Dataframe Space View (#4468)
### What Add a new kind of space view—called Dataframe Space View—which display the raw data of the entities added to it. This first iteration display data with the "latest at" semantics, with each rows corresponding to an entity instance. This features is _experimental_ and has plenty of usability issues (whose growing list is tracked in #4466). As a result, it is disabled by default and can be enabled in the options. For the purpose of this feature flag, this PR adds support for de-registering a space view from the registry. <img width="1918" alt="image" src="https://github.com/rerun-io/rerun/assets/49431240/bf197724-eb64-41bf-996f-8276f6b17336"> <img width="557" alt="image" src="https://github.com/rerun-io/rerun/assets/49431240/bd9296a8-b201-4e4d-9880-e1534399e4b3"> ### Checklist * [x] I have read and agree to [Contributor Guide](https://github.com/rerun-io/rerun/blob/main/CONTRIBUTING.md) and the [Code of Conduct](https://github.com/rerun-io/rerun/blob/main/CODE_OF_CONDUCT.md) * [x] I've included a screenshot or gif (if applicable) * [x] I have tested the web demo (if applicable): * Full build: [app.rerun.io](https://app.rerun.io/pr/4468/index.html) * Partial build: [app.rerun.io](https://app.rerun.io/pr/4468/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json) - Useful for quick testing when changes do not affect examples in any way * [x] The PR title and labels are set such as to maximize their usefulness for the next release's CHANGELOG - [PR Build Summary](https://build.rerun.io/pr/4468) - [Docs preview](https://rerun.io/preview/91ab16c95fb7e061a8f327ad0364ac433de543d5/docs) <!--DOCS-PREVIEW--> - [Examples preview](https://rerun.io/preview/91ab16c95fb7e061a8f327ad0364ac433de543d5/examples) <!--EXAMPLES-PREVIEW--> - [Recent benchmark results](https://build.rerun.io/graphs/crates.html) - [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)
- Loading branch information
Showing
16 changed files
with
477 additions
and
27 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | ||
|
||
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)) | ||
} |
Oops, something went wrong.