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

Dataframe view update and blueprint API (part 2): UI for the query #7527

Open
wants to merge 10 commits into
base: antoine/dfv1-new-query-fbs
Choose a base branch
from
2 changes: 2 additions & 0 deletions crates/store/re_types_core/src/loggable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ impl ComponentName {
short_name
} else if let Some(short_name) = full_name.strip_prefix("rerun.components.") {
short_name
} else if let Some(short_name) = full_name.strip_prefix("rerun.controls.") {
short_name
} else if let Some(short_name) = full_name.strip_prefix("rerun.") {
short_name
} else {
Expand Down
1 change: 1 addition & 0 deletions crates/viewer/re_component_ui/src/timeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use re_types_core::LoggableBatch as _;
use re_viewer_context::external::re_log_types::TimelineName;
use re_viewer_context::{MaybeMutRef, ViewerContext};

//TODO(#7498): might be unneeded after the dataframe view update
pub(crate) fn edit_timeline_name(
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
Expand Down
1 change: 1 addition & 0 deletions crates/viewer/re_space_view_dataframe/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ mod expanded_rows;
mod query_kind;
mod space_view_class;
mod view_query;
mod view_query_v2;
mod visualizer_system;

pub use space_view_class::DataframeSpaceView;
16 changes: 14 additions & 2 deletions crates/viewer/re_space_view_dataframe/src/space_view_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ use re_chunk_store::{ColumnDescriptor, ColumnSelector};
use re_log_types::{EntityPath, EntityPathFilter, ResolvedTimeRange, TimelineName};
use re_types::blueprint::{archetypes, components};
use re_types_core::SpaceViewClassIdentifier;
use re_ui::modal::ModalHandler;
use re_ui::UiExt as _;
use re_viewer_context::{
SpaceViewClass, SpaceViewClassRegistryError, SpaceViewId, SpaceViewState, SpaceViewStateExt,
Expand All @@ -15,7 +16,7 @@ use re_viewport_blueprint::{SpaceViewContents, ViewProperty};
use crate::dataframe_ui::HideColumnAction;
use crate::{
dataframe_ui::dataframe_ui, expanded_rows::ExpandedRowsCache, query_kind::QueryKind,
visualizer_system::EmptySystem,
view_query_v2, visualizer_system::EmptySystem,
};

#[derive(Default)]
Expand Down Expand Up @@ -110,7 +111,18 @@ mode sets the default time range to _everything_. You can override this in the s
_space_origin: &EntityPath,
space_view_id: SpaceViewId,
) -> Result<(), SpaceViewSystemExecutionError> {
crate::view_query::query_ui(ctx, ui, state, space_view_id)
crate::view_query::query_ui(ctx, ui, state, space_view_id)?;

//TODO(ab): just display the UI for now, this has no effect on the view itself yet.
ui.separator();
let state = state.downcast_mut::<DataframeSpaceViewState>()?;
let view_query = view_query_v2::QueryV2::from_blueprint(ctx, space_view_id);
let Some(schema) = &state.schema else {
// Shouldn't happen, except maybe on the first frame, which is too early
// for the user to click the menu anyway.
return Ok(());
};
view_query.selection_panel_ui(ctx, ui, space_view_id, schema)
}

fn extra_title_bar_ui(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
use std::collections::HashSet;

use crate::dataframe_ui::HideColumnAction;
use crate::view_query_v2::{EventColumn, QueryV2};
use re_chunk_store::{ColumnDescriptor, ColumnSelector};
use re_log_types::{EntityPath, TimeInt, TimelineName};
use re_types::blueprint::{components, datatypes};
use re_types_core::ComponentName;
use re_viewer_context::{SpaceViewSystemExecutionError, ViewerContext};

// Accessors wrapping reads/writes to the blueprint store.
impl QueryV2 {
/// Get the query timeline.
///
/// This tries to read the timeline name from the blueprint. If missing or invalid, the current
/// timeline is used and saved back to the blueprint.
pub(crate) fn timeline(
&self,
ctx: &ViewerContext<'_>,
) -> Result<re_log_types::Timeline, SpaceViewSystemExecutionError> {
// read the timeline and make sure it actually exists
let timeline = self
.query_property
.component_or_empty::<components::TimelineName>()?
.and_then(|name| {
ctx.recording()
.timelines()
.find(|timeline| timeline.name() == &TimelineName::from(name.as_str()))
.copied()
});

// if the timeline is unset, we "freeze" it to the current time panel timeline
let save_timeline = timeline.is_none();
let timeline = timeline.unwrap_or_else(|| *ctx.rec_cfg.time_ctrl.read().timeline());
if save_timeline {
self.save_timeline_name(ctx, timeline.name());
}

Ok(timeline)
}

/// Save the timeline to the one specified.
///
/// Note: this resets the range filter timestamps to -inf/+inf as any other value might be
/// invalidated.
pub(super) fn save_timeline_name(&self, ctx: &ViewerContext<'_>, timeline_name: &TimelineName) {
self.query_property
.save_blueprint_component(ctx, &components::TimelineName::from(timeline_name.as_str()));

// clearing the range filter is equivalent to setting it to the default -inf/+inf
self.query_property
.clear_blueprint_component::<components::RangeFilter>(ctx);
}

pub(crate) fn range_filter(&self) -> Result<(TimeInt, TimeInt), SpaceViewSystemExecutionError> {
#[allow(clippy::map_unwrap_or)]
Ok(self
.query_property
.component_or_empty::<components::RangeFilter>()?
.map(|range_filter| (range_filter.start.into(), range_filter.end.into()))
.unwrap_or((TimeInt::MIN, TimeInt::MAX)))
}

pub(super) fn save_range_filter(&self, ctx: &ViewerContext<'_>, start: TimeInt, end: TimeInt) {
if (start, end) == (TimeInt::MIN, TimeInt::MAX) {
self.query_property
.clear_blueprint_component::<components::RangeFilter>(ctx);
} else {
self.query_property
.save_blueprint_component(ctx, &components::RangeFilter::new(start, end));
}
}

pub(crate) fn filter_by_event_active(&self) -> Result<bool, SpaceViewSystemExecutionError> {
Ok(self
.query_property
.component_or_empty::<components::FilterByEventActive>()?
.map_or(false, |comp| *comp.0))
}

pub(super) fn save_filter_by_event_active(&self, ctx: &ViewerContext<'_>, active: bool) {
self.query_property
.save_blueprint_component(ctx, &components::FilterByEventActive(active.into()));
}

pub(crate) fn filter_event_column(
&self,
) -> Result<Option<EventColumn>, SpaceViewSystemExecutionError> {
Ok(self
.query_property
.component_or_empty::<components::ComponentColumnSelector>()?
.map(|comp| {
let components::ComponentColumnSelector(datatypes::ComponentColumnSelector {
entity_path,
component,
}) = comp;

EventColumn {
entity_path: EntityPath::from(entity_path.as_str()),
component_name: ComponentName::from(component.as_str()),
}
}))
}

pub(super) fn save_filter_event_column(
&self,
ctx: &ViewerContext<'_>,
event_column: EventColumn,
) {
let EventColumn {
entity_path,
component_name,
} = event_column;

let component = components::ComponentColumnSelector::new(&entity_path, component_name);

self.query_property
.save_blueprint_component(ctx, &component);
}

pub(crate) fn latest_at_enabled(&self) -> Result<bool, SpaceViewSystemExecutionError> {
Ok(self
.query_property
.component_or_empty::<components::ApplyLatestAt>()?
.map_or(false, |comp| *comp.0))
}

pub(crate) fn save_latest_at_enabled(&self, ctx: &ViewerContext<'_>, enabled: bool) {
self.query_property
.save_blueprint_component(ctx, &components::ApplyLatestAt(enabled.into()));
}

pub(super) fn save_selected_columns(
&self,
ctx: &ViewerContext<'_>,
columns: impl IntoIterator<Item = ColumnSelector>,
) {
let mut selected_columns = datatypes::SelectedColumns::default();
for column in columns {
match column {
ColumnSelector::Control(_) => {}
ColumnSelector::Time(desc) => {
selected_columns
.time_columns
.push(desc.timeline.as_str().into());
}
ColumnSelector::Component(desc) => {
let blueprint_component_descriptor =
datatypes::ComponentColumnSelector::new(&desc.entity_path, desc.component);

selected_columns
.component_columns
.push(blueprint_component_descriptor);
}
}
}

self.query_property
.save_blueprint_component(ctx, &components::SelectedColumns(selected_columns));
}

pub(super) fn save_all_columns_selected(&self, ctx: &ViewerContext<'_>) {
self.query_property
.clear_blueprint_component::<components::SelectedColumns>(ctx);
}

pub(super) fn save_all_columns_unselected(&self, ctx: &ViewerContext<'_>) {
self.query_property
.save_blueprint_component(ctx, &components::SelectedColumns::default());
}

/// Given a schema, list the columns that should be visible, according to the blueprint.
pub(crate) fn apply_column_visibility_to_schema(
&self,
ctx: &ViewerContext<'_>,
schema: &[ColumnDescriptor],
) -> Result<Option<Vec<ColumnSelector>>, SpaceViewSystemExecutionError> {
let selected_columns = self
.query_property
.component_or_empty::<components::SelectedColumns>()?;

// no selected columns means all columns are visible
let Some(datatypes::SelectedColumns {
time_columns,
component_columns,
}) = selected_columns.as_deref()
else {
return Ok(None);
};

let selected_time_columns: HashSet<TimelineName> = time_columns
.iter()
.map(|timeline_name| timeline_name.as_str().into())
.collect();
let selected_component_columns = component_columns.iter().cloned().collect::<HashSet<_>>();

let query_timeline_name = *self.timeline(ctx)?.name();
let result = schema
.iter()
.filter(|column| match column {
ColumnDescriptor::Control(_) => true,
ColumnDescriptor::Time(desc) => {
// we always include the query timeline column because we need it for the dataframe ui
desc.timeline.name() == &query_timeline_name
|| selected_time_columns.contains(desc.timeline.name())
}
ColumnDescriptor::Component(desc) => {
let blueprint_component_descriptor = components::ComponentColumnSelector::new(
&desc.entity_path,
desc.component_name,
);

selected_component_columns.contains(&blueprint_component_descriptor)
}
})
.cloned()
.map(ColumnSelector::from)
.collect();

Ok(Some(result))
}

pub(crate) fn handle_hide_column_actions(
&self,
ctx: &ViewerContext<'_>,
schema: &[ColumnDescriptor],
actions: Vec<HideColumnAction>,
) -> Result<(), SpaceViewSystemExecutionError> {
if actions.is_empty() {
return Ok(());
}

let mut selected_columns: Vec<_> = self
.apply_column_visibility_to_schema(ctx, schema)?
.map(|columns| columns.into_iter().collect())
.unwrap_or_else(|| schema.iter().cloned().map(Into::into).collect());

for action in actions {
match action {
HideColumnAction::HideTimeColumn { timeline_name } => {
selected_columns.retain(|column| match column {
ColumnSelector::Time(desc) => desc.timeline != timeline_name,
_ => true,
});
}

HideColumnAction::HideComponentColumn {
entity_path,
component_name,
} => {
selected_columns.retain(|column| match column {
ColumnSelector::Component(desc) => {
desc.entity_path != entity_path || desc.component != component_name
}
_ => true,
});
}
}
}

self.save_selected_columns(ctx, selected_columns);

Ok(())
}
}
61 changes: 61 additions & 0 deletions crates/viewer/re_space_view_dataframe/src/view_query_v2/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
mod blueprint_io;
mod ui;

use re_chunk_store::ColumnDescriptor;
use re_log_types::EntityPath;
use re_types::blueprint::archetypes;
use re_types_core::ComponentName;
use re_viewer_context::{SpaceViewId, SpaceViewSystemExecutionError, ViewerContext};
use re_viewport_blueprint::ViewProperty;

/// Struct to hold the point-of-view column used for the filter by event.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub(crate) struct EventColumn {
pub(crate) entity_path: EntityPath,
pub(crate) component_name: ComponentName,
}

/// Wrapper over the `DataframeQueryV2` blueprint archetype that can also display some UI.
pub(crate) struct QueryV2 {
query_property: ViewProperty,
}

impl QueryV2 {
/// Create a query object from the blueprint store.
///
/// See the `blueprint_io` module for more related accessors.
pub(crate) fn from_blueprint(ctx: &ViewerContext<'_>, space_view_id: SpaceViewId) -> Self {
Self {
query_property: ViewProperty::from_archetype::<archetypes::DataframeQueryV2>(
ctx.blueprint_db(),
ctx.blueprint_query,
space_view_id,
),
}
}

/// Display the selection panel ui for this query.
///
/// Implementation is in the `ui` module.
pub(crate) fn selection_panel_ui(
&self,
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
space_view_id: SpaceViewId,
schema: &[ColumnDescriptor],
) -> Result<(), SpaceViewSystemExecutionError> {
let timeline = self.timeline(ctx)?;

self.timeline_ui(ctx, ui, &timeline)?;
ui.separator();
self.filter_range_ui(ctx, ui, &timeline)?;
ui.separator();
self.filter_event_ui(ctx, ui, &timeline, space_view_id)?;
ui.separator();
self.column_visibility_ui(ctx, ui, &timeline, schema)?;
ui.separator();
self.latest_at_ui(ctx, ui)?;

Ok(())
}
}
Loading
Loading