Skip to content

Commit

Permalink
Move legend position & visibility to its own archetype (#5006)
Browse files Browse the repository at this point in the history
### What

* Part of #4818

Introduces new system of sub archetypes: Each space view has its own
small tree of properties.
Used this to move over the already existing plot options (no functional
change in this PR!), but all remaining plot properties can then be moved
to this new more versatile system.

Draft:
* ~~[ ] use component editors for visibility and legend position~~
  * Punting for future UI unification
* ~~[ ] make cloning of space views work (this now needs to copy the
entire blueprint subtree)~~
  * Will address in: #4977
* [x] test it out a bit more, self review

### 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):
* Using newly built examples:
[app.rerun.io](https://app.rerun.io/pr/5006/index.html)
* Using examples from latest `main` build:
[app.rerun.io](https://app.rerun.io/pr/5006/index.html?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[app.rerun.io](https://app.rerun.io/pr/5006/index.html?manifest_url=https://app.rerun.io/version/nightly/examples_manifest.json)
* [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/5006)
- [Docs
preview](https://rerun.io/preview/1bea7468aa745f6322c7e1873b15787ffc11a920/docs)
<!--DOCS-PREVIEW-->
- [Examples
preview](https://rerun.io/preview/1bea7468aa745f6322c7e1873b15787ffc11a920/examples)
<!--EXAMPLES-PREVIEW-->
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)

---------

Co-authored-by: Jeremy Leibs <jeremy@rerun.io>
  • Loading branch information
Wumpf and jleibs authored Feb 2, 2024
1 parent afca28b commit 55576c7
Show file tree
Hide file tree
Showing 43 changed files with 974 additions and 769 deletions.
223 changes: 129 additions & 94 deletions crates/re_space_view_time_series/src/space_view_class.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ use re_format::next_grid_tick_magnitude_ns;
use re_log_types::{EntityPath, EntityPathFilter, TimeZone};
use re_query::query_archetype;
use re_space_view::controls;
use re_types::blueprint::components::Corner2D;
use re_types::Archetype;
use re_viewer_context::external::re_entity_db::{
EditableAutoValue, EntityProperties, TimeSeriesAggregator,
EditableAutoValue, EntityProperties, EntityTree, TimeSeriesAggregator,
};
use re_viewer_context::{
IdentifiedViewSystem, IndicatedEntities, RecommendedSpaceView, SpaceViewClass,
Expand Down Expand Up @@ -115,99 +117,34 @@ impl SpaceViewClass for TimeSeriesSpaceView {
space_view_id: SpaceViewId,
root_entity_properties: &mut EntityProperties,
) {
let re_types::blueprint::archetypes::TimeSeries { legend } = query_archetype(
ctx.store_context.blueprint.store(),
ctx.blueprint_query,
&space_view_id.as_entity_path(),
)
.and_then(|arch| arch.to_archetype())
.unwrap_or_default();

ctx.re_ui
.selection_grid(ui, "time_series_selection_ui_aggregation")
.show(ui, |ui| {
ctx.re_ui
.grid_left_hand_label(ui, "Zoom Aggregation")
.on_hover_text("Configures the zoom-dependent scalar aggregation.\n
.selection_grid(ui, "time_series_selection_ui_aggregation")
.show(ui, |ui| {
ctx.re_ui
.grid_left_hand_label(ui, "Zoom Aggregation")
.on_hover_text("Configures the zoom-dependent scalar aggregation.\n
This is done only if steps on the X axis go below 1.0, i.e. a single pixel covers more than one tick worth of data.\n
It can greatly improve performance (and readability) in such situations as it prevents overdraw.");

let mut agg_mode = *root_entity_properties.time_series_aggregator.get();

egui::ComboBox::from_id_source("aggregation_mode")
.selected_text(agg_mode.to_string())
.show_ui(ui, |ui| {
ui.style_mut().wrap = Some(false);
ui.set_min_width(64.0);
let mut agg_mode = *root_entity_properties.time_series_aggregator.get();

for variant in TimeSeriesAggregator::variants() {
ui.selectable_value(&mut agg_mode, variant, variant.to_string())
.on_hover_text(variant.description());
}
});

root_entity_properties.time_series_aggregator =
EditableAutoValue::UserEdited(agg_mode);
});
egui::ComboBox::from_id_source("aggregation_mode")
.selected_text(agg_mode.to_string())
.show_ui(ui, |ui| {
ui.style_mut().wrap = Some(false);
ui.set_min_width(64.0);

ctx.re_ui
.selection_grid(ui, "time_series_selection_ui_legend")
.show(ui, |ui| {
ctx.re_ui.grid_left_hand_label(ui, "Legend");

ui.vertical(|ui| {
let mut edit_legend = legend.clone();

ctx.re_ui
.checkbox(ui, &mut edit_legend.0.visible, "Visible");

let mut corner = legend.corner().unwrap_or(DEFAULT_LEGEND_CORNER);

egui::ComboBox::from_id_source("legend_corner")
.selected_text(re_types::blueprint::components::Legend::to_str(corner))
.show_ui(ui, |ui| {
ui.style_mut().wrap = Some(false);
ui.set_min_width(64.0);

ui.selectable_value(
&mut corner,
egui_plot::Corner::LeftTop,
re_types::blueprint::components::Legend::to_str(
egui_plot::Corner::LeftTop,
),
);
ui.selectable_value(
&mut corner,
egui_plot::Corner::RightTop,
re_types::blueprint::components::Legend::to_str(
egui_plot::Corner::RightTop,
),
);
ui.selectable_value(
&mut corner,
egui_plot::Corner::LeftBottom,
re_types::blueprint::components::Legend::to_str(
egui_plot::Corner::LeftBottom,
),
);
ui.selectable_value(
&mut corner,
egui_plot::Corner::RightBottom,
re_types::blueprint::components::Legend::to_str(
egui_plot::Corner::RightBottom,
),
);
});

edit_legend.set_corner(corner);

if legend != edit_legend {
ctx.save_blueprint_component(&space_view_id.as_entity_path(), edit_legend);
for variant in TimeSeriesAggregator::variants() {
ui.selectable_value(&mut agg_mode, variant, variant.to_string())
.on_hover_text(variant.description());
}
});

ui.end_row();
});
root_entity_properties.time_series_aggregator =
EditableAutoValue::UserEdited(agg_mode);
});

legend_ui(ctx, space_view_id, ui);
}

fn spawn_heuristics(&self, ctx: &ViewerContext<'_>) -> SpaceViewSpawnHeuristics {
Expand Down Expand Up @@ -283,13 +220,13 @@ It can greatly improve performance (and readability) in such situations as it pr
) -> Result<(), SpaceViewSystemExecutionError> {
re_tracing::profile_function!();

let re_types::blueprint::archetypes::TimeSeries { legend } = query_archetype(
ctx.store_context.blueprint.store(),
ctx.blueprint_query,
&query.space_view_id.as_entity_path(),
)
.and_then(|arch| arch.to_archetype())
.unwrap_or_default();
let (
re_types::blueprint::archetypes::PlotLegend {
visible: legend_visible,
corner: legend_corner,
},
_,
) = query_space_view_sub_archetype(ctx, query.space_view_id);

let (current_time, time_type, timeline) = {
// Avoid holding the lock for long
Expand Down Expand Up @@ -377,9 +314,14 @@ It can greatly improve performance (and readability) in such situations as it pr
}
});

if legend.visible {
if legend_visible.unwrap_or(true.into()).0 {
plot = plot.legend(
Legend::default().position(legend.corner().unwrap_or(DEFAULT_LEGEND_CORNER)),
Legend::default().position(
legend_corner
.unwrap_or_default()
.try_into()
.unwrap_or(DEFAULT_LEGEND_CORNER),
),
);
}

Expand Down Expand Up @@ -514,6 +456,63 @@ It can greatly improve performance (and readability) in such situations as it pr
}
}

fn legend_ui(ctx: &ViewerContext<'_>, space_view_id: SpaceViewId, ui: &mut egui::Ui) {
// TODO(jleibs): use editors

let (re_types::blueprint::archetypes::PlotLegend { visible, corner }, blueprint_path) =
query_space_view_sub_archetype(ctx, space_view_id);

ctx.re_ui
.selection_grid(ui, "time_series_selection_ui_legend")
.show(ui, |ui| {
ctx.re_ui.grid_left_hand_label(ui, "Legend");

ui.vertical(|ui| {
let visible = visible.unwrap_or(true.into());
let mut edit_visibility = visible;
ctx.re_ui.checkbox(ui, &mut edit_visibility.0, "Visible");
if visible != edit_visibility {
ctx.save_blueprint_component(&blueprint_path, edit_visibility);
}

let corner = corner.unwrap_or(DEFAULT_LEGEND_CORNER.into());
let mut edit_corner = corner;
egui::ComboBox::from_id_source("legend_corner")
.selected_text(format!("{corner}"))
.show_ui(ui, |ui| {
ui.style_mut().wrap = Some(false);
ui.set_min_width(64.0);

ui.selectable_value(
&mut edit_corner,
egui_plot::Corner::LeftTop.into(),
format!("{}", Corner2D::from(egui_plot::Corner::LeftTop)),
);
ui.selectable_value(
&mut edit_corner,
egui_plot::Corner::RightTop.into(),
format!("{}", Corner2D::from(egui_plot::Corner::RightTop)),
);
ui.selectable_value(
&mut edit_corner,
egui_plot::Corner::LeftBottom.into(),
format!("{}", Corner2D::from(egui_plot::Corner::LeftBottom)),
);
ui.selectable_value(
&mut edit_corner,
egui_plot::Corner::RightBottom.into(),
format!("{}", Corner2D::from(egui_plot::Corner::RightBottom)),
);
});
if corner != edit_corner {
ctx.save_blueprint_component(&blueprint_path, edit_corner);
}
});

ui.end_row();
});
}

fn format_time(time_type: TimeType, time_int: i64, time_zone_for_timestamps: TimeZone) -> String {
if time_type == TimeType::Time {
let time = re_log_types::Time::from_ns_since_epoch(time_int);
Expand Down Expand Up @@ -573,3 +572,39 @@ fn round_ns_to_start_of_day(ns: i64) -> i64 {
let ns_per_day = 24 * 60 * 60 * 1_000_000_000;
(ns + ns_per_day / 2) / ns_per_day * ns_per_day
}

fn entity_path_for_space_view_sub_archetype<T: Archetype>(
space_view_id: SpaceViewId,
_blueprint_entity_tree: &EntityTree,
) -> EntityPath {
// TODO(andreas,jleibs):
// We want to search the subtree for occurrences of the property archetype here.
// Only if none is found we make up a new (standardized) path.
// There's some nuances to figure out what happens when we find the archetype several times.
// Also, we need to specify what it means to "find" the archetype (likely just matching the indicator?).
let space_view_blueprint_path = space_view_id.as_entity_path();

// Use short_name instead of full_name since full_name has dots and looks too much like an indicator component.
space_view_blueprint_path.join(&EntityPath::from_single_string(T::name().short_name()))
}

fn query_space_view_sub_archetype<T: Archetype + Default>(
ctx: &ViewerContext<'_>,
space_view_id: SpaceViewId,
) -> (T, EntityPath) {
let path = entity_path_for_space_view_sub_archetype::<T>(
space_view_id,
ctx.store_context.blueprint.tree(),
);

(
query_archetype(
ctx.store_context.blueprint.store(),
ctx.blueprint_query,
&path,
)
.and_then(|arch| arch.to_archetype())
.unwrap_or_default(),
path,
)
}
6 changes: 2 additions & 4 deletions crates/re_types/definitions/rerun/blueprint.fbs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,10 @@ include "./blueprint/components/space_view_maximized.fbs";
include "./blueprint/components/space_view_origin.fbs";
include "./blueprint/components/viewport_layout.fbs";
include "./blueprint/components/visible.fbs";

include "./blueprint/datatypes/legend.fbs";
include "./blueprint/components/legend.fbs";
include "./blueprint/components/corner_2d.fbs";

include "./blueprint/archetypes/container_blueprint.fbs";
include "./blueprint/archetypes/space_view_blueprint.fbs";
include "./blueprint/archetypes/viewport_blueprint.fbs";

include "./blueprint/archetypes/time_series.fbs";
include "./blueprint/archetypes/plot_legend.fbs";
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
include "arrow/attributes.fbs";
include "python/attributes.fbs";
include "rust/attributes.fbs";

include "rerun/datatypes.fbs";
include "rerun/attributes.fbs";

namespace rerun.blueprint.archetypes;


// ---

/// Configuration for the legend of a plot.
table PlotLegend (
"attr.docs.unreleased",
"attr.rerun.scope": "blueprint",
"attr.rust.derive": "Default"
) {
// --- Optional ---

/// To what corner the legend is aligned.
///
/// Defaults to the right bottom corner.
corner: rerun.blueprint.components.Corner2D ("attr.rerun.component_optional", nullable, order: 2100);

/// Whether the legend is shown at all.
///
/// True by default.
visible: rerun.blueprint.components.Visible ("attr.rerun.component_optional", nullable, order: 2200);
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ include "arrow/attributes.fbs";
include "python/attributes.fbs";
include "rust/attributes.fbs";

include "rerun/datatypes.fbs";
include "rerun/attributes.fbs";

namespace rerun.blueprint.datatypes;

namespace rerun.blueprint.components;

// TODO(#3384)
/*
Expand All @@ -18,22 +14,20 @@ enum LegendPosition: byte {
}
*/

// ---

/// Configuration for the legend of a plot.
table Legend (
/// One of four 2D corners, typically used to align objects.
table Corner2D (
"attr.docs.unreleased",
"attr.arrow.transparent",
"attr.rerun.scope": "blueprint",
"attr.rust.derive": "PartialEq, Eq, PartialOrd, Ord"
"attr.rust.derive": "Copy, PartialEq, Eq, PartialOrd, Ord",
"attr.rust.tuple_struct"
) {
/// Whether or not the legend should be displayed.
visible: bool (order: 100);

/// Where should the legend be located.
///
/// Allowed values:
/// - LeftTop = 1,
/// - RightTop = 2,
/// - LeftBottom = 3,
/// - RightBottom = 4
location: ubyte (order: 200, nullable);
location: ubyte (order: 200);
}
Loading

0 comments on commit 55576c7

Please sign in to comment.