From b580da42d5afec6094fe59865820f8d1dcb6a6d0 Mon Sep 17 00:00:00 2001
From: Antoine Beyeler <49431240+abey79@users.noreply.github.com>
Date: Mon, 15 Jul 2024 15:23:55 +0200
Subject: [PATCH] Add support for visible time range to the dataframe view
(#6869)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
### What
- Part of #4466
- Soft-blocked by #6878
This adds support for visible time range to the dataframe. For now
(likely to be iterated on soon), this mode is enabled when _any_ of the
view entities have visible time range enabled (see note below). In that
mode, rows are indexed by (entity, time, row_id) and can be sorted with
either of the first two (asc or desc) using two new view properties.
The dataframe feature is—and remains—behind an opt-in feature flag.
#### Note on the current latest at vs. range switch
Currently A single view entity with visible time range force the entire
view into this mode. In particular, it force-opt-in *all* view entities
to visible time range, setting it to `Rel(0)-Rel(0)` when not explicitly
set. (It's as if the view's default visible time range switched to
`Rel(0)-Rel(0)` although that's not how it's implemented.)
This implicit behaviour is not ideal, and we probably should design a
better way to go about it, see #4466.
### Checklist
* [x] update view help text
* [x] split in multiple files
* [x] clean Chunk stuff
* [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 examples from latest `main` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/6869?manifest_url=https://app.rerun.io/version/main/examples_manifest.json)
* Using full set of examples from `nightly` build:
[rerun.io/viewer](https://rerun.io/viewer/pr/6869?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
* [x] If applicable, add a new check to the [release
checklist](https://github.com/rerun-io/rerun/blob/main/tests/python/release_checklist)!
* [x] If have noted any breaking changes to the log API in
`CHANGELOG.md` and the migration guide
- [PR Build Summary](https://build.rerun.io/pr/6869)
- [Recent benchmark results](https://build.rerun.io/graphs/crates.html)
- [Wasm size tracking](https://build.rerun.io/graphs/sizes.html)
To run all checks from `main`, comment on the PR with `@rerun-bot
full-check`.
---------
Co-authored-by: Clement Rey
---
Cargo.lock | 4 +
.../re_types/definitions/rerun/blueprint.fbs | 3 +
.../archetypes/range_table_order.fbs | 23 ++
.../rerun/blueprint/components/sort_key.fbs | 20 ++
.../rerun/blueprint/components/sort_order.fbs | 20 ++
.../src/blueprint/archetypes/.gitattributes | 1 +
.../re_types/src/blueprint/archetypes/mod.rs | 2 +
.../blueprint/archetypes/range_table_order.rs | 201 +++++++++++++
.../src/blueprint/components/.gitattributes | 2 +
.../re_types/src/blueprint/components/mod.rs | 4 +
.../src/blueprint/components/sort_key.rs | 163 +++++++++++
.../src/blueprint/components/sort_order.rs | 163 +++++++++++
crates/viewer/re_edit_ui/src/lib.rs | 5 +-
crates/viewer/re_selection_panel/Cargo.toml | 1 +
.../src/visible_time_range_ui.rs | 2 +
.../viewer/re_space_view_dataframe/Cargo.toml | 3 +
.../src/latest_at_table.rs | 136 +++++++++
.../viewer/re_space_view_dataframe/src/lib.rs | 4 +
.../src/space_view_class.rs | 261 ++++++-----------
.../re_space_view_dataframe/src/table_ui.rs | 59 ++++
.../src/time_range_table.rs | 276 ++++++++++++++++++
.../re_space_view_dataframe/src/utils.rs | 53 ++++
.../src/blueprint/validation_gen/mod.rs | 4 +
crates/viewer/re_viewer/src/reflection/mod.rs | 29 ++
.../re_viewer_context/src/query_range.rs | 12 +
rerun_cpp/src/rerun/blueprint/archetypes.hpp | 1 +
.../rerun/blueprint/archetypes/.gitattributes | 2 +
.../archetypes/range_table_order.cpp | 38 +++
.../archetypes/range_table_order.hpp | 69 +++++
rerun_cpp/src/rerun/blueprint/components.hpp | 2 +
.../rerun/blueprint/components/.gitattributes | 4 +
.../rerun/blueprint/components/sort_key.cpp | 62 ++++
.../rerun/blueprint/components/sort_key.hpp | 52 ++++
.../rerun/blueprint/components/sort_order.cpp | 62 ++++
.../rerun/blueprint/components/sort_order.hpp | 52 ++++
.../rerun/blueprint/archetypes/.gitattributes | 1 +
.../rerun/blueprint/archetypes/__init__.py | 2 +
.../blueprint/archetypes/range_table_order.py | 82 ++++++
.../rerun/blueprint/components/.gitattributes | 2 +
.../rerun/blueprint/components/__init__.py | 12 +
.../rerun/blueprint/components/sort_key.py | 93 ++++++
.../rerun/blueprint/components/sort_order.py | 93 ++++++
42 files changed, 1899 insertions(+), 181 deletions(-)
create mode 100644 crates/store/re_types/definitions/rerun/blueprint/archetypes/range_table_order.fbs
create mode 100644 crates/store/re_types/definitions/rerun/blueprint/components/sort_key.fbs
create mode 100644 crates/store/re_types/definitions/rerun/blueprint/components/sort_order.fbs
create mode 100644 crates/store/re_types/src/blueprint/archetypes/range_table_order.rs
create mode 100644 crates/store/re_types/src/blueprint/components/sort_key.rs
create mode 100644 crates/store/re_types/src/blueprint/components/sort_order.rs
create mode 100644 crates/viewer/re_space_view_dataframe/src/latest_at_table.rs
create mode 100644 crates/viewer/re_space_view_dataframe/src/table_ui.rs
create mode 100644 crates/viewer/re_space_view_dataframe/src/time_range_table.rs
create mode 100644 crates/viewer/re_space_view_dataframe/src/utils.rs
create mode 100644 rerun_cpp/src/rerun/blueprint/archetypes/range_table_order.cpp
create mode 100644 rerun_cpp/src/rerun/blueprint/archetypes/range_table_order.hpp
create mode 100644 rerun_cpp/src/rerun/blueprint/components/sort_key.cpp
create mode 100644 rerun_cpp/src/rerun/blueprint/components/sort_key.hpp
create mode 100644 rerun_cpp/src/rerun/blueprint/components/sort_order.cpp
create mode 100644 rerun_cpp/src/rerun/blueprint/components/sort_order.hpp
create mode 100644 rerun_py/rerun_sdk/rerun/blueprint/archetypes/range_table_order.py
create mode 100644 rerun_py/rerun_sdk/rerun/blueprint/components/sort_key.py
create mode 100644 rerun_py/rerun_sdk/rerun/blueprint/components/sort_order.py
diff --git a/Cargo.lock b/Cargo.lock
index cebbe37fd232..91cda6c5b686 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4854,6 +4854,7 @@ dependencies = [
"re_log_types",
"re_query",
"re_space_view",
+ "re_space_view_dataframe",
"re_space_view_spatial",
"re_space_view_time_series",
"re_tracing",
@@ -4927,10 +4928,13 @@ dependencies = [
"re_entity_db",
"re_log_types",
"re_renderer",
+ "re_space_view",
"re_tracing",
+ "re_types",
"re_types_core",
"re_ui",
"re_viewer_context",
+ "re_viewport_blueprint",
]
[[package]]
diff --git a/crates/store/re_types/definitions/rerun/blueprint.fbs b/crates/store/re_types/definitions/rerun/blueprint.fbs
index d6ddcb840da7..9a9ef17b3e15 100644
--- a/crates/store/re_types/definitions/rerun/blueprint.fbs
+++ b/crates/store/re_types/definitions/rerun/blueprint.fbs
@@ -17,6 +17,8 @@ include "./blueprint/components/panel_state.fbs";
include "./blueprint/components/query_expression.fbs";
include "./blueprint/components/root_container.fbs";
include "./blueprint/components/row_share.fbs";
+include "./blueprint/components/sort_key.fbs";
+include "./blueprint/components/sort_order.fbs";
include "./blueprint/components/space_view_class.fbs";
include "./blueprint/components/space_view_maximized.fbs";
include "./blueprint/components/space_view_origin.fbs";
@@ -41,6 +43,7 @@ include "./blueprint/archetypes/visible_time_ranges.fbs";
include "./blueprint/archetypes/visual_bounds2d.fbs";
include "./blueprint/archetypes/plot_legend.fbs";
+include "./blueprint/archetypes/range_table_order.fbs";
include "./blueprint/archetypes/scalar_axis.fbs";
include "./blueprint/views/bar_chart.fbs";
diff --git a/crates/store/re_types/definitions/rerun/blueprint/archetypes/range_table_order.fbs b/crates/store/re_types/definitions/rerun/blueprint/archetypes/range_table_order.fbs
new file mode 100644
index 000000000000..7cdc8f0f377d
--- /dev/null
+++ b/crates/store/re_types/definitions/rerun/blueprint/archetypes/range_table_order.fbs
@@ -0,0 +1,23 @@
+include "arrow/attributes.fbs";
+include "python/attributes.fbs";
+include "rust/attributes.fbs";
+
+include "rerun/attributes.fbs";
+
+namespace rerun.blueprint.archetypes;
+
+
+/// Configuration for the sorting of the rows of a time range table.
+table RangeTableOrder (
+ "attr.rerun.scope": "blueprint",
+ "attr.rust.derive": "Copy",
+ "attr.rust.generate_field_info"
+) {
+ // --- Optional ---
+
+ /// The primary sort key.
+ sort_key: rerun.blueprint.components.SortKey ("attr.rerun.component_optional", nullable, order: 1000);
+
+ /// The sort order.
+ sort_order: rerun.blueprint.components.SortOrder ("attr.rerun.component_optional", nullable, order: 2000);
+}
diff --git a/crates/store/re_types/definitions/rerun/blueprint/components/sort_key.fbs b/crates/store/re_types/definitions/rerun/blueprint/components/sort_key.fbs
new file mode 100644
index 000000000000..7c55b5de44bf
--- /dev/null
+++ b/crates/store/re_types/definitions/rerun/blueprint/components/sort_key.fbs
@@ -0,0 +1,20 @@
+include "arrow/attributes.fbs";
+include "python/attributes.fbs";
+include "rust/attributes.fbs";
+
+include "rerun/datatypes.fbs";
+include "rerun/attributes.fbs";
+
+namespace rerun.blueprint.components;
+
+
+/// Primary element by which to group by in a temporal data table.
+enum SortKey: byte (
+ "attr.rerun.scope": "blueprint"
+) {
+ /// Group by entity.
+ Entity (default),
+
+ /// Group by instance.
+ Time,
+}
diff --git a/crates/store/re_types/definitions/rerun/blueprint/components/sort_order.fbs b/crates/store/re_types/definitions/rerun/blueprint/components/sort_order.fbs
new file mode 100644
index 000000000000..94e2310f79cc
--- /dev/null
+++ b/crates/store/re_types/definitions/rerun/blueprint/components/sort_order.fbs
@@ -0,0 +1,20 @@
+include "arrow/attributes.fbs";
+include "python/attributes.fbs";
+include "rust/attributes.fbs";
+
+include "rerun/datatypes.fbs";
+include "rerun/attributes.fbs";
+
+namespace rerun.blueprint.components;
+
+
+/// Sort order for data table.
+enum SortOrder: byte (
+ "attr.rerun.scope": "blueprint"
+) {
+ /// Ascending
+ Ascending (default),
+
+ /// Descending
+ Descending,
+}
diff --git a/crates/store/re_types/src/blueprint/archetypes/.gitattributes b/crates/store/re_types/src/blueprint/archetypes/.gitattributes
index 6501c56053ed..ba0fa4bab5b8 100644
--- a/crates/store/re_types/src/blueprint/archetypes/.gitattributes
+++ b/crates/store/re_types/src/blueprint/archetypes/.gitattributes
@@ -4,6 +4,7 @@
background.rs linguist-generated=true
mod.rs linguist-generated=true
plot_legend.rs linguist-generated=true
+range_table_order.rs linguist-generated=true
scalar_axis.rs linguist-generated=true
space_view_blueprint.rs linguist-generated=true
space_view_contents.rs linguist-generated=true
diff --git a/crates/store/re_types/src/blueprint/archetypes/mod.rs b/crates/store/re_types/src/blueprint/archetypes/mod.rs
index c1ae16766e61..dd60566e9a65 100644
--- a/crates/store/re_types/src/blueprint/archetypes/mod.rs
+++ b/crates/store/re_types/src/blueprint/archetypes/mod.rs
@@ -2,6 +2,7 @@
mod background;
mod plot_legend;
+mod range_table_order;
mod scalar_axis;
mod space_view_blueprint;
mod space_view_contents;
@@ -14,6 +15,7 @@ mod visual_bounds2d;
pub use self::background::Background;
pub use self::plot_legend::PlotLegend;
+pub use self::range_table_order::RangeTableOrder;
pub use self::scalar_axis::ScalarAxis;
pub use self::space_view_blueprint::SpaceViewBlueprint;
pub use self::space_view_contents::SpaceViewContents;
diff --git a/crates/store/re_types/src/blueprint/archetypes/range_table_order.rs b/crates/store/re_types/src/blueprint/archetypes/range_table_order.rs
new file mode 100644
index 000000000000..f153d095270e
--- /dev/null
+++ b/crates/store/re_types/src/blueprint/archetypes/range_table_order.rs
@@ -0,0 +1,201 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/archetypes/range_table_order.fbs".
+
+#![allow(unused_imports)]
+#![allow(unused_parens)]
+#![allow(clippy::clone_on_copy)]
+#![allow(clippy::cloned_instead_of_copied)]
+#![allow(clippy::map_flatten)]
+#![allow(clippy::needless_question_mark)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::too_many_arguments)]
+#![allow(clippy::too_many_lines)]
+
+use ::re_types_core::external::arrow2;
+use ::re_types_core::ComponentName;
+use ::re_types_core::SerializationResult;
+use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch};
+use ::re_types_core::{DeserializationError, DeserializationResult};
+
+/// **Archetype**: Configuration for the sorting of the rows of a time range table.
+#[derive(Clone, Debug, Copy)]
+pub struct RangeTableOrder {
+ /// The primary sort key.
+ pub sort_key: Option,
+
+ /// The sort order.
+ pub sort_order: Option,
+}
+
+impl ::re_types_core::SizeBytes for RangeTableOrder {
+ #[inline]
+ fn heap_size_bytes(&self) -> u64 {
+ self.sort_key.heap_size_bytes() + self.sort_order.heap_size_bytes()
+ }
+
+ #[inline]
+ fn is_pod() -> bool {
+ >::is_pod()
+ && >::is_pod()
+ }
+}
+
+static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 0usize]> =
+ once_cell::sync::Lazy::new(|| []);
+
+static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> =
+ once_cell::sync::Lazy::new(|| ["rerun.blueprint.components.RangeTableOrderIndicator".into()]);
+
+static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 2usize]> =
+ once_cell::sync::Lazy::new(|| {
+ [
+ "rerun.blueprint.components.SortKey".into(),
+ "rerun.blueprint.components.SortOrder".into(),
+ ]
+ });
+
+static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> =
+ once_cell::sync::Lazy::new(|| {
+ [
+ "rerun.blueprint.components.RangeTableOrderIndicator".into(),
+ "rerun.blueprint.components.SortKey".into(),
+ "rerun.blueprint.components.SortOrder".into(),
+ ]
+ });
+
+impl RangeTableOrder {
+ /// The total number of components in the archetype: 0 required, 1 recommended, 2 optional
+ pub const NUM_COMPONENTS: usize = 3usize;
+}
+
+/// Indicator component for the [`RangeTableOrder`] [`::re_types_core::Archetype`]
+pub type RangeTableOrderIndicator = ::re_types_core::GenericIndicatorComponent;
+
+impl ::re_types_core::Archetype for RangeTableOrder {
+ type Indicator = RangeTableOrderIndicator;
+
+ #[inline]
+ fn name() -> ::re_types_core::ArchetypeName {
+ "rerun.blueprint.archetypes.RangeTableOrder".into()
+ }
+
+ #[inline]
+ fn display_name() -> &'static str {
+ "Range table order"
+ }
+
+ #[inline]
+ fn indicator() -> MaybeOwnedComponentBatch<'static> {
+ static INDICATOR: RangeTableOrderIndicator = RangeTableOrderIndicator::DEFAULT;
+ MaybeOwnedComponentBatch::Ref(&INDICATOR)
+ }
+
+ #[inline]
+ fn required_components() -> ::std::borrow::Cow<'static, [ComponentName]> {
+ REQUIRED_COMPONENTS.as_slice().into()
+ }
+
+ #[inline]
+ fn recommended_components() -> ::std::borrow::Cow<'static, [ComponentName]> {
+ RECOMMENDED_COMPONENTS.as_slice().into()
+ }
+
+ #[inline]
+ fn optional_components() -> ::std::borrow::Cow<'static, [ComponentName]> {
+ OPTIONAL_COMPONENTS.as_slice().into()
+ }
+
+ #[inline]
+ fn all_components() -> ::std::borrow::Cow<'static, [ComponentName]> {
+ ALL_COMPONENTS.as_slice().into()
+ }
+
+ #[inline]
+ fn from_arrow_components(
+ arrow_data: impl IntoIterator- )>,
+ ) -> DeserializationResult
{
+ re_tracing::profile_function!();
+ use ::re_types_core::{Loggable as _, ResultExt as _};
+ let arrays_by_name: ::std::collections::HashMap<_, _> = arrow_data
+ .into_iter()
+ .map(|(name, array)| (name.full_name(), array))
+ .collect();
+ let sort_key = if let Some(array) = arrays_by_name.get("rerun.blueprint.components.SortKey")
+ {
+ ::from_arrow_opt(&**array)
+ .with_context("rerun.blueprint.archetypes.RangeTableOrder#sort_key")?
+ .into_iter()
+ .next()
+ .flatten()
+ } else {
+ None
+ };
+ let sort_order =
+ if let Some(array) = arrays_by_name.get("rerun.blueprint.components.SortOrder") {
+ ::from_arrow_opt(&**array)
+ .with_context("rerun.blueprint.archetypes.RangeTableOrder#sort_order")?
+ .into_iter()
+ .next()
+ .flatten()
+ } else {
+ None
+ };
+ Ok(Self {
+ sort_key,
+ sort_order,
+ })
+ }
+}
+
+impl ::re_types_core::AsComponents for RangeTableOrder {
+ fn as_component_batches(&self) -> Vec> {
+ re_tracing::profile_function!();
+ use ::re_types_core::Archetype as _;
+ [
+ Some(Self::indicator()),
+ self.sort_key
+ .as_ref()
+ .map(|comp| (comp as &dyn ComponentBatch).into()),
+ self.sort_order
+ .as_ref()
+ .map(|comp| (comp as &dyn ComponentBatch).into()),
+ ]
+ .into_iter()
+ .flatten()
+ .collect()
+ }
+}
+
+impl ::re_types_core::ArchetypeReflectionMarker for RangeTableOrder {}
+
+impl RangeTableOrder {
+ /// Create a new `RangeTableOrder`.
+ #[inline]
+ pub fn new() -> Self {
+ Self {
+ sort_key: None,
+ sort_order: None,
+ }
+ }
+
+ /// The primary sort key.
+ #[inline]
+ pub fn with_sort_key(
+ mut self,
+ sort_key: impl Into,
+ ) -> Self {
+ self.sort_key = Some(sort_key.into());
+ self
+ }
+
+ /// The sort order.
+ #[inline]
+ pub fn with_sort_order(
+ mut self,
+ sort_order: impl Into,
+ ) -> Self {
+ self.sort_order = Some(sort_order.into());
+ self
+ }
+}
diff --git a/crates/store/re_types/src/blueprint/components/.gitattributes b/crates/store/re_types/src/blueprint/components/.gitattributes
index 6a9874206ded..653e7df988ad 100644
--- a/crates/store/re_types/src/blueprint/components/.gitattributes
+++ b/crates/store/re_types/src/blueprint/components/.gitattributes
@@ -12,6 +12,8 @@ mod.rs linguist-generated=true
panel_state.rs linguist-generated=true
query_expression.rs linguist-generated=true
row_share.rs linguist-generated=true
+sort_key.rs linguist-generated=true
+sort_order.rs linguist-generated=true
space_view_class.rs linguist-generated=true
space_view_origin.rs linguist-generated=true
tensor_dimension_index_slider.rs linguist-generated=true
diff --git a/crates/store/re_types/src/blueprint/components/mod.rs b/crates/store/re_types/src/blueprint/components/mod.rs
index 3389f6f39b39..000478b2b5f0 100644
--- a/crates/store/re_types/src/blueprint/components/mod.rs
+++ b/crates/store/re_types/src/blueprint/components/mod.rs
@@ -13,6 +13,8 @@ mod panel_state;
mod panel_state_ext;
mod query_expression;
mod row_share;
+mod sort_key;
+mod sort_order;
mod space_view_class;
mod space_view_class_ext;
mod space_view_origin;
@@ -38,6 +40,8 @@ pub use self::lock_range_during_zoom::LockRangeDuringZoom;
pub use self::panel_state::PanelState;
pub use self::query_expression::QueryExpression;
pub use self::row_share::RowShare;
+pub use self::sort_key::SortKey;
+pub use self::sort_order::SortOrder;
pub use self::space_view_class::SpaceViewClass;
pub use self::space_view_origin::SpaceViewOrigin;
pub use self::tensor_dimension_index_slider::TensorDimensionIndexSlider;
diff --git a/crates/store/re_types/src/blueprint/components/sort_key.rs b/crates/store/re_types/src/blueprint/components/sort_key.rs
new file mode 100644
index 000000000000..4e809b109d55
--- /dev/null
+++ b/crates/store/re_types/src/blueprint/components/sort_key.rs
@@ -0,0 +1,163 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/components/sort_key.fbs".
+
+#![allow(unused_imports)]
+#![allow(unused_parens)]
+#![allow(clippy::clone_on_copy)]
+#![allow(clippy::cloned_instead_of_copied)]
+#![allow(clippy::map_flatten)]
+#![allow(clippy::needless_question_mark)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::too_many_arguments)]
+#![allow(clippy::too_many_lines)]
+
+use ::re_types_core::external::arrow2;
+use ::re_types_core::ComponentName;
+use ::re_types_core::SerializationResult;
+use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch};
+use ::re_types_core::{DeserializationError, DeserializationResult};
+
+/// **Component**: Primary element by which to group by in a temporal data table.
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
+pub enum SortKey {
+ /// Group by entity.
+ #[default]
+ Entity = 1,
+
+ /// Group by instance.
+ Time = 2,
+}
+
+impl ::re_types_core::reflection::Enum for SortKey {
+ #[inline]
+ fn variants() -> &'static [Self] {
+ &[Self::Entity, Self::Time]
+ }
+
+ #[inline]
+ fn docstring_md(self) -> &'static str {
+ match self {
+ Self::Entity => "Group by entity.",
+ Self::Time => "Group by instance.",
+ }
+ }
+}
+
+impl ::re_types_core::SizeBytes for SortKey {
+ #[inline]
+ fn heap_size_bytes(&self) -> u64 {
+ 0
+ }
+
+ #[inline]
+ fn is_pod() -> bool {
+ true
+ }
+}
+
+impl std::fmt::Display for SortKey {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Entity => write!(f, "Entity"),
+ Self::Time => write!(f, "Time"),
+ }
+ }
+}
+
+::re_types_core::macros::impl_into_cow!(SortKey);
+
+impl ::re_types_core::Loggable for SortKey {
+ type Name = ::re_types_core::ComponentName;
+
+ #[inline]
+ fn name() -> Self::Name {
+ "rerun.blueprint.components.SortKey".into()
+ }
+
+ #[inline]
+ fn arrow_datatype() -> arrow2::datatypes::DataType {
+ #![allow(clippy::wildcard_imports)]
+ use arrow2::datatypes::*;
+ DataType::Union(
+ std::sync::Arc::new(vec![
+ Field::new("_null_markers", DataType::Null, true),
+ Field::new("Entity", DataType::Null, true),
+ Field::new("Time", DataType::Null, true),
+ ]),
+ Some(std::sync::Arc::new(vec![0i32, 1i32, 2i32])),
+ UnionMode::Sparse,
+ )
+ }
+
+ fn to_arrow_opt<'a>(
+ data: impl IntoIterator- >>>,
+ ) -> SerializationResult
>
+ where
+ Self: Clone + 'a,
+ {
+ #![allow(clippy::wildcard_imports)]
+ use ::re_types_core::{Loggable as _, ResultExt as _};
+ use arrow2::{array::*, datatypes::*};
+ Ok({
+ // Sparse Arrow union
+ let data: Vec<_> = data
+ .into_iter()
+ .map(|datum| {
+ let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into);
+ datum
+ })
+ .collect();
+ let num_variants = 2usize;
+ let types = data
+ .iter()
+ .map(|a| match a.as_deref() {
+ None => 0,
+ Some(value) => *value as i8,
+ })
+ .collect();
+ let fields: Vec<_> =
+ std::iter::repeat(NullArray::new(DataType::Null, data.len()).boxed())
+ .take(1 + num_variants)
+ .collect();
+ UnionArray::new(Self::arrow_datatype(), types, fields, None).boxed()
+ })
+ }
+
+ fn from_arrow_opt(
+ arrow_data: &dyn arrow2::array::Array,
+ ) -> DeserializationResult>>
+ where
+ Self: Sized,
+ {
+ #![allow(clippy::wildcard_imports)]
+ use ::re_types_core::{Loggable as _, ResultExt as _};
+ use arrow2::{array::*, buffer::*, datatypes::*};
+ Ok({
+ let arrow_data = arrow_data
+ .as_any()
+ .downcast_ref::()
+ .ok_or_else(|| {
+ let expected = Self::arrow_datatype();
+ let actual = arrow_data.data_type().clone();
+ DeserializationError::datatype_mismatch(expected, actual)
+ })
+ .with_context("rerun.blueprint.components.SortKey")?;
+ let arrow_data_types = arrow_data.types();
+ arrow_data_types
+ .iter()
+ .map(|typ| match typ {
+ 0 => Ok(None),
+ 1 => Ok(Some(Self::Entity)),
+ 2 => Ok(Some(Self::Time)),
+ _ => Err(DeserializationError::missing_union_arm(
+ Self::arrow_datatype(),
+ "",
+ *typ as _,
+ )),
+ })
+ .collect::>>()
+ .with_context("rerun.blueprint.components.SortKey")?
+ })
+ }
+}
diff --git a/crates/store/re_types/src/blueprint/components/sort_order.rs b/crates/store/re_types/src/blueprint/components/sort_order.rs
new file mode 100644
index 000000000000..b81f37a2dc76
--- /dev/null
+++ b/crates/store/re_types/src/blueprint/components/sort_order.rs
@@ -0,0 +1,163 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/rust/api.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/components/sort_order.fbs".
+
+#![allow(unused_imports)]
+#![allow(unused_parens)]
+#![allow(clippy::clone_on_copy)]
+#![allow(clippy::cloned_instead_of_copied)]
+#![allow(clippy::map_flatten)]
+#![allow(clippy::needless_question_mark)]
+#![allow(clippy::new_without_default)]
+#![allow(clippy::redundant_closure)]
+#![allow(clippy::too_many_arguments)]
+#![allow(clippy::too_many_lines)]
+
+use ::re_types_core::external::arrow2;
+use ::re_types_core::ComponentName;
+use ::re_types_core::SerializationResult;
+use ::re_types_core::{ComponentBatch, MaybeOwnedComponentBatch};
+use ::re_types_core::{DeserializationError, DeserializationResult};
+
+/// **Component**: Sort order for data table.
+#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq, Default)]
+pub enum SortOrder {
+ /// Ascending
+ #[default]
+ Ascending = 1,
+
+ /// Descending
+ Descending = 2,
+}
+
+impl ::re_types_core::reflection::Enum for SortOrder {
+ #[inline]
+ fn variants() -> &'static [Self] {
+ &[Self::Ascending, Self::Descending]
+ }
+
+ #[inline]
+ fn docstring_md(self) -> &'static str {
+ match self {
+ Self::Ascending => "Ascending",
+ Self::Descending => "Descending",
+ }
+ }
+}
+
+impl ::re_types_core::SizeBytes for SortOrder {
+ #[inline]
+ fn heap_size_bytes(&self) -> u64 {
+ 0
+ }
+
+ #[inline]
+ fn is_pod() -> bool {
+ true
+ }
+}
+
+impl std::fmt::Display for SortOrder {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Ascending => write!(f, "Ascending"),
+ Self::Descending => write!(f, "Descending"),
+ }
+ }
+}
+
+::re_types_core::macros::impl_into_cow!(SortOrder);
+
+impl ::re_types_core::Loggable for SortOrder {
+ type Name = ::re_types_core::ComponentName;
+
+ #[inline]
+ fn name() -> Self::Name {
+ "rerun.blueprint.components.SortOrder".into()
+ }
+
+ #[inline]
+ fn arrow_datatype() -> arrow2::datatypes::DataType {
+ #![allow(clippy::wildcard_imports)]
+ use arrow2::datatypes::*;
+ DataType::Union(
+ std::sync::Arc::new(vec![
+ Field::new("_null_markers", DataType::Null, true),
+ Field::new("Ascending", DataType::Null, true),
+ Field::new("Descending", DataType::Null, true),
+ ]),
+ Some(std::sync::Arc::new(vec![0i32, 1i32, 2i32])),
+ UnionMode::Sparse,
+ )
+ }
+
+ fn to_arrow_opt<'a>(
+ data: impl IntoIterator- >>>,
+ ) -> SerializationResult
>
+ where
+ Self: Clone + 'a,
+ {
+ #![allow(clippy::wildcard_imports)]
+ use ::re_types_core::{Loggable as _, ResultExt as _};
+ use arrow2::{array::*, datatypes::*};
+ Ok({
+ // Sparse Arrow union
+ let data: Vec<_> = data
+ .into_iter()
+ .map(|datum| {
+ let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into);
+ datum
+ })
+ .collect();
+ let num_variants = 2usize;
+ let types = data
+ .iter()
+ .map(|a| match a.as_deref() {
+ None => 0,
+ Some(value) => *value as i8,
+ })
+ .collect();
+ let fields: Vec<_> =
+ std::iter::repeat(NullArray::new(DataType::Null, data.len()).boxed())
+ .take(1 + num_variants)
+ .collect();
+ UnionArray::new(Self::arrow_datatype(), types, fields, None).boxed()
+ })
+ }
+
+ fn from_arrow_opt(
+ arrow_data: &dyn arrow2::array::Array,
+ ) -> DeserializationResult>>
+ where
+ Self: Sized,
+ {
+ #![allow(clippy::wildcard_imports)]
+ use ::re_types_core::{Loggable as _, ResultExt as _};
+ use arrow2::{array::*, buffer::*, datatypes::*};
+ Ok({
+ let arrow_data = arrow_data
+ .as_any()
+ .downcast_ref::()
+ .ok_or_else(|| {
+ let expected = Self::arrow_datatype();
+ let actual = arrow_data.data_type().clone();
+ DeserializationError::datatype_mismatch(expected, actual)
+ })
+ .with_context("rerun.blueprint.components.SortOrder")?;
+ let arrow_data_types = arrow_data.types();
+ arrow_data_types
+ .iter()
+ .map(|typ| match typ {
+ 0 => Ok(None),
+ 1 => Ok(Some(Self::Ascending)),
+ 2 => Ok(Some(Self::Descending)),
+ _ => Err(DeserializationError::missing_union_arm(
+ Self::arrow_datatype(),
+ "",
+ *typ as _,
+ )),
+ })
+ .collect::>>()
+ .with_context("rerun.blueprint.components.SortOrder")?
+ })
+ }
+}
diff --git a/crates/viewer/re_edit_ui/src/lib.rs b/crates/viewer/re_edit_ui/src/lib.rs
index af4efaf8b46e..12c33a7b27d9 100644
--- a/crates/viewer/re_edit_ui/src/lib.rs
+++ b/crates/viewer/re_edit_ui/src/lib.rs
@@ -15,6 +15,7 @@ use datatype_editors::{
display_name_ui, display_text_ui, edit_bool, edit_f32_min_to_max_float, edit_f32_zero_to_max,
edit_f32_zero_to_one, edit_multiline_string, edit_singleline_string, edit_view_enum,
};
+use re_types::blueprint::components::{SortKey, SortOrder};
use re_types::{
blueprint::components::{BackgroundKind, Corner2D, LockRangeDuringZoom, ViewFit, Visible},
components::{
@@ -68,8 +69,6 @@ pub fn register_editors(registry: &mut re_viewer_context::ComponentUiRegistry) {
colormap_edit_or_view_ui(ctx.render_ctx, ui, value)
});
registry.add_singleline_edit_or_view(|_ctx, ui, value| edit_view_enum::(ui, value));
- registry
- .add_singleline_edit_or_view(|_ctx, ui, value| edit_view_enum::(ui, value));
registry.add_singleline_edit_or_view(|_ctx, ui, value| edit_view_enum::(ui, value));
registry.add_singleline_edit_or_view(|_ctx, ui, value| edit_view_enum::(ui, value));
registry.add_singleline_edit_or_view(|_ctx, ui, value| {
@@ -79,6 +78,8 @@ pub fn register_editors(registry: &mut re_viewer_context::ComponentUiRegistry) {
edit_view_enum::(ui, value)
});
registry.add_singleline_edit_or_view(|_ctx, ui, value| edit_view_enum::(ui, value));
+ registry.add_singleline_edit_or_view(|_ctx, ui, value| edit_view_enum::(ui, value));
+ registry.add_singleline_edit_or_view(|_ctx, ui, value| edit_view_enum::(ui, value));
registry.add_multiline_edit_or_view(visual_bounds2d::multiline_edit_visual_bounds2d);
registry.add_singleline_edit_or_view(visual_bounds2d::singleline_edit_visual_bounds2d);
diff --git a/crates/viewer/re_selection_panel/Cargo.toml b/crates/viewer/re_selection_panel/Cargo.toml
index 4c6ddfba6474..2e7d11d249d4 100644
--- a/crates/viewer/re_selection_panel/Cargo.toml
+++ b/crates/viewer/re_selection_panel/Cargo.toml
@@ -27,6 +27,7 @@ re_entity_db.workspace = true
re_log_types.workspace = true
re_log.workspace = true
re_query.workspace = true
+re_space_view_dataframe.workspace = true
re_space_view_spatial.workspace = true
re_space_view_time_series.workspace = true
re_space_view.workspace = true
diff --git a/crates/viewer/re_selection_panel/src/visible_time_range_ui.rs b/crates/viewer/re_selection_panel/src/visible_time_range_ui.rs
index c95d42bb7ab4..86b701cd01f1 100644
--- a/crates/viewer/re_selection_panel/src/visible_time_range_ui.rs
+++ b/crates/viewer/re_selection_panel/src/visible_time_range_ui.rs
@@ -5,6 +5,7 @@ use egui::{NumExt as _, Response, Ui};
use re_entity_db::TimeHistogram;
use re_log_types::{EntityPath, ResolvedTimeRange, TimeType, TimeZone, TimelineName};
+use re_space_view_dataframe::DataframeSpaceView;
use re_space_view_spatial::{SpatialSpaceView2D, SpatialSpaceView3D};
use re_space_view_time_series::TimeSeriesSpaceView;
use re_types::{
@@ -24,6 +25,7 @@ static VISIBLE_HISTORY_SUPPORTED_SPACE_VIEWS: once_cell::sync::Lazy<
SpatialSpaceView3D::identifier(),
SpatialSpaceView2D::identifier(),
TimeSeriesSpaceView::identifier(),
+ DataframeSpaceView::identifier(),
]
.map(Into::into)
.into()
diff --git a/crates/viewer/re_space_view_dataframe/Cargo.toml b/crates/viewer/re_space_view_dataframe/Cargo.toml
index 483c6804cf68..a6ff38a428fb 100644
--- a/crates/viewer/re_space_view_dataframe/Cargo.toml
+++ b/crates/viewer/re_space_view_dataframe/Cargo.toml
@@ -24,10 +24,13 @@ re_data_ui.workspace = true
re_entity_db.workspace = true
re_log_types.workspace = true
re_renderer.workspace = true
+re_space_view.workspace = true
re_tracing.workspace = true
+re_types.workspace = true
re_types_core.workspace = true
re_ui.workspace = true
re_viewer_context.workspace = true
+re_viewport_blueprint.workspace = true
egui_extras.workspace = true
egui.workspace = true
diff --git a/crates/viewer/re_space_view_dataframe/src/latest_at_table.rs b/crates/viewer/re_space_view_dataframe/src/latest_at_table.rs
new file mode 100644
index 000000000000..9090d9250418
--- /dev/null
+++ b/crates/viewer/re_space_view_dataframe/src/latest_at_table.rs
@@ -0,0 +1,136 @@
+use std::collections::BTreeSet;
+
+use re_data_ui::item_ui::instance_path_button;
+use re_viewer_context::{UiLayout, ViewQuery, ViewerContext};
+
+use crate::{
+ table_ui::table_ui,
+ utils::{sorted_instance_paths_for, sorted_visible_entity_path},
+};
+
+/// Display a "latest at" table.
+///
+/// This table has entity instances as rows and components as column. That data is the result of a
+/// "latest at" query based on the current timeline and time.
+pub(crate) fn latest_at_table_ui(
+ ctx: &ViewerContext<'_>,
+ ui: &mut egui::Ui,
+ query: &ViewQuery<'_>,
+) {
+ re_tracing::profile_function!();
+
+ // These are the entity paths whose content we must display.
+ let sorted_entity_paths = sorted_visible_entity_path(ctx, query);
+ 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,
+ ctx.recording_store(),
+ &query.timeline,
+ &latest_at_query,
+ )
+ })
+ .collect();
+
+ // Produce a sorted list of all components that are present in one or more entities. These
+ // will be the columns of the table.
+ sorted_components = sorted_entity_paths
+ .iter()
+ .flat_map(|entity_path| {
+ ctx.recording_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 = |mut row: egui_extras::TableRow<'_, '_>| {
+ let instance = &sorted_instance_paths[row.index()];
+
+ row.col(|ui| {
+ instance_path_button(
+ ctx,
+ &latest_at_query,
+ ctx.recording(),
+ ui,
+ Some(query.space_view_id),
+ instance,
+ );
+ });
+
+ for component_name in &sorted_components {
+ row.col(|ui| {
+ let results = ctx.recording().query_caches().latest_at(
+ ctx.recording_store(),
+ &latest_at_query,
+ &instance.entity_path,
+ [*component_name],
+ );
+
+ if let Some(results) =
+ // This is a duplicate of the one above, but this is ok since this codes runs
+ // *only* for visible rows.
+ results.components.get(component_name)
+ {
+ ctx.component_ui_registry.ui(
+ ctx,
+ ui,
+ UiLayout::List,
+ &latest_at_query,
+ ctx.recording(),
+ &instance.entity_path,
+ results,
+ &instance.instance,
+ );
+ } else {
+ ui.weak("-");
+ }
+ });
+ }
+ };
+
+ table_ui(
+ ui,
+ &sorted_components,
+ 1, // entity column
+ header_ui,
+ sorted_instance_paths.len(),
+ row_ui,
+ );
+}
diff --git a/crates/viewer/re_space_view_dataframe/src/lib.rs b/crates/viewer/re_space_view_dataframe/src/lib.rs
index 5798c5b6e90b..b3d8a9436846 100644
--- a/crates/viewer/re_space_view_dataframe/src/lib.rs
+++ b/crates/viewer/re_space_view_dataframe/src/lib.rs
@@ -2,7 +2,11 @@
//!
//! A Space View that shows the data contained in entities in a table.
+mod latest_at_table;
mod space_view_class;
+mod table_ui;
+mod time_range_table;
+mod utils;
mod visualizer_system;
pub use space_view_class::DataframeSpaceView;
diff --git a/crates/viewer/re_space_view_dataframe/src/space_view_class.rs b/crates/viewer/re_space_view_dataframe/src/space_view_class.rs
index d9cacbcd6b55..f7cdd171e237 100644
--- a/crates/viewer/re_space_view_dataframe/src/space_view_class.rs
+++ b/crates/viewer/re_space_view_dataframe/src/space_view_class.rs
@@ -1,18 +1,23 @@
-use std::collections::BTreeSet;
+use egui::Ui;
-use egui_extras::Column;
-
-use re_chunk_store::{ChunkStore, LatestAtQuery};
-use re_data_ui::item_ui::instance_path_button;
-use re_entity_db::InstancePath;
-use re_log_types::{EntityPath, Instance, Timeline};
+use re_log_types::EntityPath;
+use re_space_view::view_property_ui;
+use re_types::blueprint::{
+ archetypes::RangeTableOrder,
+ components::{SortKey, SortOrder},
+};
use re_types_core::SpaceViewClassIdentifier;
+use re_ui::list_item;
use re_viewer_context::{
- SpaceViewClass, SpaceViewClassRegistryError, SpaceViewState, SpaceViewSystemExecutionError,
- SystemExecutionOutput, UiLayout, ViewQuery, ViewerContext,
+ SpaceViewClass, SpaceViewClassRegistryError, SpaceViewId, SpaceViewState,
+ SpaceViewSystemExecutionError, SystemExecutionOutput, ViewQuery, ViewerContext,
};
+use re_viewport_blueprint::ViewProperty;
-use crate::visualizer_system::EmptySystem;
+use crate::{
+ latest_at_table::latest_at_table_ui, time_range_table::time_range_table_ui,
+ visualizer_system::EmptySystem,
+};
#[derive(Default)]
pub struct DataframeSpaceView;
@@ -27,14 +32,29 @@ impl SpaceViewClass for DataframeSpaceView {
}
fn icon(&self) -> &'static re_ui::Icon {
- //TODO(ab): fix that icon
&re_ui::icons::SPACE_VIEW_DATAFRAME
}
fn help_markdown(&self, _egui_ctx: &egui::Context) -> String {
"# Dataframe view
-Show the data contained in entities in a table. Each entity is represented by as many rows as it has instances."
+This view displays the content of the entities it contains in tabular form. Click on the view and
+use the _Entity path filter_ to control which entities are displayed.
+
+## View types
+
+The Dataframe view operates in two modes: the _latest at_ mode and the _time range_ mode.
+
+In the _latest at_ mode, the view displays the latest data for the timeline and time set in the time
+panel. A row is shown for each entity instance.
+
+In the _time range_ mode, the view displays all the data logged within the time range set for each
+view entity. In this mode, each row corresponds to an entity and time pair. Rows are further split
+if multiple `rr.log()` calls were made for the same entity/time. Static data is also displayed.
+
+The view switches to _time range_ mode as soon as a single one of its entities has its visible time
+range set to _Override_. Each entity may have its own time range setting. (To set the same time range
+for all entities, it is preferable to override the view-level visible time range.)"
.to_owned()
}
@@ -65,188 +85,71 @@ Show the data contained in entities in a table. Each entity is represented by as
Default::default()
}
+ fn selection_ui(
+ &self,
+ ctx: &ViewerContext<'_>,
+ ui: &mut Ui,
+ state: &mut dyn SpaceViewState,
+ _space_origin: &EntityPath,
+ space_view_id: SpaceViewId,
+ ) -> Result<(), SpaceViewSystemExecutionError> {
+ list_item::list_item_scope(ui, "dataframe_view_selection_ui", |ui| {
+ view_property_ui::(ctx, ui, space_view_id, self, state);
+ });
+
+ Ok(())
+ }
+
fn ui(
&self,
ctx: &ViewerContext<'_>,
ui: &mut egui::Ui,
- _state: &mut dyn SpaceViewState,
-
+ state: &mut dyn SpaceViewState,
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.is_visible(ctx))
- .map(|data_result| &data_result.entity_path)
- .cloned()
- .collect();
-
- 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,
- ctx.recording_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| {
- ctx.recording_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();
- }
+ let row_order = ViewProperty::from_archetype::(
+ ctx.blueprint_db(),
+ ctx.blueprint_query,
+ query.space_view_id,
+ );
+ let sort_key = row_order.component_or_fallback::(ctx, self, state)?;
+ let sort_order = row_order.component_or_fallback::(ctx, self, state)?;
- // 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());
- });
- }
- };
+ let mode = self.table_mode(query);
- // 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 = |mut row: egui_extras::TableRow<'_, '_>| {
- let instance = &sorted_instance_paths[row.index()];
-
- row.col(|ui| {
- instance_path_button(ctx, &latest_at_query, ctx.recording(), ui, None, instance);
- });
-
- for component_name in &sorted_components {
- row.col(|ui| {
- let results = ctx.recording().query_caches().latest_at(
- ctx.recording_store(),
- &latest_at_query,
- &instance.entity_path,
- [*component_name],
- );
-
- if let Some(results) =
- // This is a duplicate of the one above, but this ok since this codes runs
- // *only* for visible rows.
- results.components.get(component_name)
- {
- ctx.component_ui_registry.ui(
- ctx,
- ui,
- UiLayout::List,
- &latest_at_query,
- ctx.recording(),
- &instance.entity_path,
- results,
- &instance.instance,
- );
- } else {
- ui.weak("-");
- }
- });
- }
+ match mode {
+ TableMode::LatestAtTable => latest_at_table_ui(ctx, ui, query),
+ TableMode::TimeRangeTable => time_range_table_ui(ctx, ui, query, sort_key, sort_order),
};
- {
- re_tracing::profile_scope!("table UI");
-
- egui::ScrollArea::both()
- .auto_shrink([false, false])
- .show(ui, |ui| {
- ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
-
- 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::DesignTokens::table_line_height(), header_ui)
- .body(|body| {
- body.rows(
- re_ui::DesignTokens::table_line_height(),
- sorted_instance_paths.len(),
- row_ui,
- );
- });
- });
- });
- }
-
Ok(())
}
}
-/// Returns a sorted, deduplicated iterator of all instance paths for a given entity.
-fn sorted_instance_paths_for<'a>(
- entity_path: &'a EntityPath,
- store: &'a ChunkStore,
- timeline: &'a Timeline,
- latest_at_query: &'a LatestAtQuery,
-) -> impl Iterator- + 'a {
- store
- .all_components(timeline, entity_path)
- .unwrap_or_default()
- .into_iter()
- .filter(|component_name| !component_name.is_indicator_component())
- .flat_map(|component_name| {
- let num_instances = store
- .latest_at_relevant_chunks(latest_at_query, entity_path, component_name)
- .into_iter()
- .filter_map(|chunk| {
- let (data_time, row_id, batch) = chunk
- .latest_at(latest_at_query, component_name)
- .iter_rows(timeline, &component_name)
- .next()?;
- batch.map(|batch| (data_time, row_id, batch))
- })
- .max_by_key(|(data_time, row_id, _)| (*data_time, *row_id))
- .map_or(0, |(_, _, batch)| batch.len());
- (0..num_instances).map(|i| Instance::from(i as u64))
- })
- .collect::
>() // dedup and sort
- .into_iter()
- .map(|instance| InstancePath::instance(entity_path.clone(), instance))
+re_viewer_context::impl_component_fallback_provider!(DataframeSpaceView => []);
+
+/// The two modes of the dataframe view.
+enum TableMode {
+ LatestAtTable,
+ TimeRangeTable,
+}
+
+impl DataframeSpaceView {
+ /// Determine which [`TableMode`] is currently active.
+ // TODO(ab): we probably want a less "implicit" way to switch from temporal vs. latest at tables.
+ #[allow(clippy::unused_self)]
+ fn table_mode(&self, query: &ViewQuery<'_>) -> TableMode {
+ let is_range_query = query
+ .iter_all_data_results()
+ .any(|data_result| data_result.property_overrides.query_range.is_time_range());
+
+ if is_range_query {
+ TableMode::TimeRangeTable
+ } else {
+ TableMode::LatestAtTable
+ }
+ }
}
diff --git a/crates/viewer/re_space_view_dataframe/src/table_ui.rs b/crates/viewer/re_space_view_dataframe/src/table_ui.rs
new file mode 100644
index 000000000000..9735c0fa0509
--- /dev/null
+++ b/crates/viewer/re_space_view_dataframe/src/table_ui.rs
@@ -0,0 +1,59 @@
+use std::collections::BTreeSet;
+
+use egui_extras::{Column, TableRow};
+
+use re_chunk_store::RowId;
+use re_types_core::ComponentName;
+
+/// Display a nicely configured table with the provided header ui, row ui, and row count.
+///
+/// The `extra_columns` are hom many more columns there are in addition to the components.
+pub(crate) fn table_ui(
+ ui: &mut egui::Ui,
+ sorted_components: &BTreeSet,
+ extra_columns: usize,
+ header_ui: impl FnOnce(egui_extras::TableRow<'_, '_>),
+ row_count: usize,
+ row_ui: impl FnMut(TableRow<'_, '_>),
+) {
+ re_tracing::profile_function!();
+
+ egui::ScrollArea::horizontal()
+ .auto_shrink([false, false])
+ .show(ui, |ui| {
+ ui.style_mut().wrap_mode = Some(egui::TextWrapMode::Extend);
+
+ 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),
+ extra_columns + sorted_components.len(),
+ )
+ .resizable(true)
+ .vscroll(true)
+ //TODO(ab): remove when https://github.com/emilk/egui/pull/4817 is merged/released
+ .max_scroll_height(f32::INFINITY)
+ .auto_shrink([false, false])
+ .striped(true)
+ .header(re_ui::DesignTokens::table_line_height(), header_ui)
+ .body(|body| {
+ body.rows(re_ui::DesignTokens::table_line_height(), row_count, row_ui);
+ });
+ });
+ });
+}
+
+pub(crate) fn row_id_ui(ui: &mut egui::Ui, row_id: &RowId) {
+ let s = row_id.to_string();
+ let split_pos = s.char_indices().nth_back(5);
+
+ ui.label(match split_pos {
+ Some((pos, _)) => &s[pos..],
+ None => &s,
+ })
+ .on_hover_text(s);
+}
diff --git a/crates/viewer/re_space_view_dataframe/src/time_range_table.rs b/crates/viewer/re_space_view_dataframe/src/time_range_table.rs
new file mode 100644
index 000000000000..501cfb1316fe
--- /dev/null
+++ b/crates/viewer/re_space_view_dataframe/src/time_range_table.rs
@@ -0,0 +1,276 @@
+use std::collections::{BTreeMap, BTreeSet};
+use std::sync::Arc;
+
+use re_chunk_store::{Chunk, RangeQuery, RowId};
+use re_data_ui::item_ui::entity_path_button;
+use re_log_types::{EntityPath, ResolvedTimeRange, TimeInt, Timeline};
+use re_types::blueprint::components::{SortKey, SortOrder};
+use re_types_core::datatypes::TimeRange;
+use re_types_core::ComponentName;
+use re_viewer_context::{QueryRange, UiLayout, ViewQuery, ViewerContext};
+
+use crate::table_ui::{row_id_ui, table_ui};
+
+/// Show a table with entities and time as rows, and components as columns.
+///
+/// Here, a "row" is a tuple of `(entity_path, time, row_id)`. This means that both "over logging"
+/// (i.e. logging multiple times the same entity/component at the same timestamp) and "split
+/// logging" (i.e. multiple log calls on the same [entity, time] but with different components)
+/// lead to multiple rows. In other words, no joining is done here.
+///
+/// Also:
+/// - View entities have their query range "forced" to a range query. If set to "latest at", they
+/// are converted to Rel(0)-Rel(0).
+/// - Only the data logged in the query range is displayed. There is no implicit "latest at" like
+/// it's done for regular views.
+/// - Static data is always shown.
+/// - When both static and non-static data exist for the same entity/component, the non-static data
+/// is never shown (as per our data model).
+pub(crate) fn time_range_table_ui(
+ ctx: &ViewerContext<'_>,
+ ui: &mut egui::Ui,
+ query: &ViewQuery<'_>,
+ sort_key: SortKey,
+ sort_order: SortOrder,
+) {
+ re_tracing::profile_function!();
+
+ //
+ // Produce a sorted list of all components we are interested id.
+ //
+
+ // TODO(ab): add support for filtering components more narrowly.
+ let sorted_components: BTreeSet<_> = {
+ re_tracing::profile_scope!("query components");
+
+ // Produce a sorted list of all components that are present in one or more entities.
+ // These will be the columns of the table.
+ query
+ .iter_all_data_results()
+ .filter(|data_result| data_result.is_visible(ctx))
+ .flat_map(|data_result| {
+ ctx.recording_store()
+ .all_components(&query.timeline, &data_result.entity_path)
+ .unwrap_or_default()
+ })
+ // TODO(#4466): make showing/hiding indicators components an explicit optional
+ .filter(|comp| !comp.is_indicator_component())
+ .collect()
+ };
+
+ //
+ // Build the full list of rows, along with the chunk where the data is. Rows are keyed by an
+ // (entity, time, row_id) tuple (see function docstring). These keys are mapped to the
+ // corresponding chunk that contains the actual data.
+ //
+ // We build a big, monolithic iterator for all the rows. The following code builds that by
+ // breaking it into several functions for sanity purpose, from innermost to outermost.
+ //
+
+ type RowKey = (EntityPath, TimeInt, RowId);
+
+ #[inline]
+ fn map_chunk_indices_to_key_value_iter<'a>(
+ indices_iter: impl Iterator- + 'a,
+ chunk: Arc
,
+ entity_path: EntityPath,
+ resolved_time_range: ResolvedTimeRange,
+ ) -> impl Iterator- )> + 'a {
+ indices_iter
+ .filter(move |(time, _)| time.is_static() || resolved_time_range.contains(*time))
+ .map(move |(time, row_id)| {
+ let chunk = chunk.clone();
+ ((entity_path.clone(), time, row_id), chunk)
+ })
+ }
+
+ #[inline]
+ fn entity_components_to_key_value_iter<'a>(
+ ctx: &ViewerContext<'_>,
+ entity_path: &'a EntityPath,
+ component: &'a ComponentName,
+ timeline: Timeline,
+ resolved_time_range: ResolvedTimeRange,
+ ) -> impl Iterator
- )> + 'a {
+ let range_query = RangeQuery::new(timeline, resolved_time_range);
+
+ ctx.recording_store()
+ .range_relevant_chunks(&range_query, entity_path, *component)
+ .into_iter()
+ // This does two things:
+ // 1) Filter out instances where `chunk.iter_indices()` returns `None`.
+ // 2) Exploit the fact that the returned iterator (if any) is *not* bound to the
+ // lifetime of the chunk (it has an internal Arc).
+ .filter_map(move |chunk| {
+ //TODO(ab, cmc): remove this line when a range-aware, iter_indices API is available.
+ let chunk = Arc::new(chunk.range(&range_query, *component));
+
+ chunk
+ .clone()
+ .iter_indices(&timeline)
+ .map(|iter_indices| (iter_indices, chunk))
+ })
+ .flat_map(move |(indices_iter, chunk)| {
+ map_chunk_indices_to_key_value_iter(
+ indices_iter,
+ chunk,
+ entity_path.clone(),
+ resolved_time_range,
+ )
+ })
+ }
+
+ // all the rows!
+ let rows_to_chunk = query
+ .iter_all_data_results()
+ .filter(|data_result| data_result.is_visible(ctx))
+ .flat_map(|data_result| {
+ let time_range = match &data_result.property_overrides.query_range {
+ QueryRange::TimeRange(time_range) => time_range.clone(),
+ QueryRange::LatestAt => TimeRange::AT_CURSOR,
+ };
+
+ let resolved_time_range =
+ ResolvedTimeRange::from_relative_time_range(&time_range, ctx.current_query().at());
+
+ sorted_components.iter().flat_map(move |component| {
+ entity_components_to_key_value_iter(
+ ctx,
+ &data_result.entity_path,
+ component,
+ query.timeline,
+ resolved_time_range,
+ )
+ })
+ })
+ .collect::
>();
+
+ //
+ // Row sorting based on view properties.
+ //
+
+ let mut rows = rows_to_chunk.keys().collect::>();
+
+ // apply sort key
+ match sort_key {
+ SortKey::Entity => {} // already correctly sorted
+ SortKey::Time => rows.sort_by_key(|(entity_path, time, _)| (*time, entity_path)),
+ };
+ if sort_order == SortOrder::Descending {
+ rows.reverse();
+ }
+
+ //
+ // Drawing code.
+ //
+
+ let entity_header = |ui: &mut egui::Ui| {
+ ui.strong("Entity");
+ };
+ let time_header = |ui: &mut egui::Ui| {
+ ui.strong("Time");
+ };
+
+ // Draw the header row.
+ let header_ui = |mut row: egui_extras::TableRow<'_, '_>| {
+ match sort_key {
+ SortKey::Entity => {
+ row.col(entity_header);
+ row.col(time_header);
+ }
+ SortKey::Time => {
+ row.col(time_header);
+ row.col(entity_header);
+ }
+ }
+
+ row.col(|ui| {
+ ui.strong("Row ID");
+ });
+
+ for comp in &sorted_components {
+ row.col(|ui| {
+ ui.strong(comp.short_name());
+ });
+ }
+ };
+
+ let latest_at_query = query.latest_at_query();
+ let entity_ui = |ui: &mut egui::Ui, entity_path: &EntityPath| {
+ entity_path_button(
+ ctx,
+ &latest_at_query,
+ ctx.recording(),
+ ui,
+ Some(query.space_view_id),
+ entity_path,
+ );
+ };
+
+ let time_ui = |ui: &mut egui::Ui, time: &TimeInt| {
+ if ui
+ .button(
+ query
+ .timeline
+ .typ()
+ .format(*time, ctx.app_options.time_zone),
+ )
+ .clicked()
+ {
+ ctx.rec_cfg.time_ctrl.write().set_time(*time);
+ }
+ };
+
+ // Draw a single line of the table. This is called for each _visible_ row.
+ let latest_at_query = query.latest_at_query();
+ let row_ui = |mut row: egui_extras::TableRow<'_, '_>| {
+ let row_key = rows[row.index()];
+ let row_chunk = &rows_to_chunk[row_key];
+ let (entity_path, time, row_id) = row_key;
+
+ match sort_key {
+ SortKey::Entity => {
+ row.col(|ui| entity_ui(ui, entity_path));
+ row.col(|ui| time_ui(ui, time));
+ }
+ SortKey::Time => {
+ row.col(|ui| time_ui(ui, time));
+ row.col(|ui| entity_ui(ui, entity_path));
+ }
+ };
+
+ row.col(|ui| {
+ row_id_ui(ui, row_id);
+ });
+
+ for component_name in &sorted_components {
+ row.col(|ui| {
+ let content = row_chunk.cell(*row_id, component_name);
+
+ if let Some(content) = content {
+ ctx.component_ui_registry.ui_raw(
+ ctx,
+ ui,
+ UiLayout::List,
+ &latest_at_query,
+ ctx.recording(),
+ entity_path,
+ *component_name,
+ &*content,
+ );
+ } else {
+ ui.weak("-");
+ }
+ });
+ }
+ };
+
+ table_ui(
+ ui,
+ &sorted_components,
+ 3, // time, entity, row id
+ header_ui,
+ rows.len(),
+ row_ui,
+ );
+}
diff --git a/crates/viewer/re_space_view_dataframe/src/utils.rs b/crates/viewer/re_space_view_dataframe/src/utils.rs
new file mode 100644
index 000000000000..afcb2358cee2
--- /dev/null
+++ b/crates/viewer/re_space_view_dataframe/src/utils.rs
@@ -0,0 +1,53 @@
+use std::collections::BTreeSet;
+
+use re_chunk_store::{ChunkStore, LatestAtQuery};
+use re_entity_db::InstancePath;
+use re_log_types::{EntityPath, Instance, Timeline};
+use re_viewer_context::{ViewQuery, ViewerContext};
+
+/// Returns a sorted list of all entities that are visible in the view.
+pub(crate) fn sorted_visible_entity_path(
+ ctx: &ViewerContext<'_>,
+ query: &ViewQuery<'_>,
+) -> BTreeSet {
+ query
+ .iter_all_data_results()
+ .filter(|data_result| data_result.is_visible(ctx))
+ .map(|data_result| &data_result.entity_path)
+ .cloned()
+ .collect()
+}
+
+/// Returns a sorted, deduplicated iterator of all instance paths for a given entity.
+pub(crate) fn sorted_instance_paths_for<'a>(
+ entity_path: &'a EntityPath,
+ store: &'a ChunkStore,
+ timeline: &'a Timeline,
+ latest_at_query: &'a LatestAtQuery,
+) -> impl Iterator- + 'a {
+ re_tracing::profile_function!();
+
+ store
+ .all_components(timeline, entity_path)
+ .unwrap_or_default()
+ .into_iter()
+ .filter(|component_name| !component_name.is_indicator_component())
+ .flat_map(|component_name| {
+ let num_instances = store
+ .latest_at_relevant_chunks(latest_at_query, entity_path, component_name)
+ .into_iter()
+ .filter_map(|chunk| {
+ let (data_time, row_id, batch) = chunk
+ .latest_at(latest_at_query, component_name)
+ .iter_rows(timeline, &component_name)
+ .next()?;
+ batch.map(|batch| (data_time, row_id, batch))
+ })
+ .max_by_key(|(data_time, row_id, _)| (*data_time, *row_id))
+ .map_or(0, |(_, _, batch)| batch.len());
+ (0..num_instances).map(|i| Instance::from(i as u64))
+ })
+ .collect::
>() // dedup and sort
+ .into_iter()
+ .map(|instance| InstancePath::instance(entity_path.clone(), instance))
+}
diff --git a/crates/viewer/re_viewer/src/blueprint/validation_gen/mod.rs b/crates/viewer/re_viewer/src/blueprint/validation_gen/mod.rs
index f7eb9218a025..81d265eac828 100644
--- a/crates/viewer/re_viewer/src/blueprint/validation_gen/mod.rs
+++ b/crates/viewer/re_viewer/src/blueprint/validation_gen/mod.rs
@@ -11,6 +11,8 @@ pub use re_types::blueprint::components::LockRangeDuringZoom;
pub use re_types::blueprint::components::PanelState;
pub use re_types::blueprint::components::QueryExpression;
pub use re_types::blueprint::components::RowShare;
+pub use re_types::blueprint::components::SortKey;
+pub use re_types::blueprint::components::SortOrder;
pub use re_types::blueprint::components::SpaceViewClass;
pub use re_types::blueprint::components::SpaceViewOrigin;
pub use re_types::blueprint::components::TensorDimensionIndexSlider;
@@ -48,6 +50,8 @@ pub fn is_valid_blueprint(blueprint: &EntityDb) -> bool {
&& validate_component::(blueprint)
&& validate_component::(blueprint)
&& validate_component::(blueprint)
+ && validate_component::(blueprint)
+ && validate_component::(blueprint)
&& validate_component::(blueprint)
&& validate_component::(blueprint)
&& validate_component::(blueprint)
diff --git a/crates/viewer/re_viewer/src/reflection/mod.rs b/crates/viewer/re_viewer/src/reflection/mod.rs
index c5c287d71e93..c4b0c3f61e50 100644
--- a/crates/viewer/re_viewer/src/reflection/mod.rs
+++ b/crates/viewer/re_viewer/src/reflection/mod.rs
@@ -146,6 +146,20 @@ fn generate_component_reflection() -> Result::name(),
+ ComponentReflection {
+ docstring_md: "Primary element by which to group by in a temporal data table.",
+ placeholder: Some(SortKey::default().to_arrow()?),
+ },
+ ),
+ (
+ ::name(),
+ ComponentReflection {
+ docstring_md: "Sort order for data table.",
+ placeholder: Some(SortOrder::default().to_arrow()?),
+ },
+ ),
(
::name(),
ComponentReflection {
@@ -595,6 +609,21 @@ fn generate_archetype_reflection() -> ArchetypeReflectionMap {
],
},
),
+ (
+ ArchetypeName::new("rerun.blueprint.archetypes.RangeTableOrder"),
+ ArchetypeReflection {
+ display_name: "Range table order",
+ docstring_md: "Configuration for the sorting of the rows of a time range table.",
+ fields: vec![
+ ArchetypeFieldReflection { component_name :
+ "rerun.blueprint.components.SortKey".into(), display_name :
+ "Sort key", docstring_md : "The primary sort key.", },
+ ArchetypeFieldReflection { component_name :
+ "rerun.blueprint.components.SortOrder".into(), display_name :
+ "Sort order", docstring_md : "The sort order.", },
+ ],
+ },
+ ),
(
ArchetypeName::new("rerun.blueprint.archetypes.ScalarAxis"),
ArchetypeReflection {
diff --git a/crates/viewer/re_viewer_context/src/query_range.rs b/crates/viewer/re_viewer_context/src/query_range.rs
index 2fcc1c89b7d3..1dd49ac69102 100644
--- a/crates/viewer/re_viewer_context/src/query_range.rs
+++ b/crates/viewer/re_viewer_context/src/query_range.rs
@@ -8,3 +8,15 @@ pub enum QueryRange {
#[default]
LatestAt,
}
+
+impl QueryRange {
+ #[inline]
+ pub fn is_latest_at(&self) -> bool {
+ matches!(self, Self::LatestAt)
+ }
+
+ #[inline]
+ pub fn is_time_range(&self) -> bool {
+ matches!(self, Self::TimeRange(_))
+ }
+}
diff --git a/rerun_cpp/src/rerun/blueprint/archetypes.hpp b/rerun_cpp/src/rerun/blueprint/archetypes.hpp
index 8dfd7175e565..fec6bca89b58 100644
--- a/rerun_cpp/src/rerun/blueprint/archetypes.hpp
+++ b/rerun_cpp/src/rerun/blueprint/archetypes.hpp
@@ -6,6 +6,7 @@
#include "blueprint/archetypes/container_blueprint.hpp"
#include "blueprint/archetypes/panel_blueprint.hpp"
#include "blueprint/archetypes/plot_legend.hpp"
+#include "blueprint/archetypes/range_table_order.hpp"
#include "blueprint/archetypes/scalar_axis.hpp"
#include "blueprint/archetypes/space_view_blueprint.hpp"
#include "blueprint/archetypes/space_view_contents.hpp"
diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes b/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes
index c941889168aa..9e6c38e48b63 100644
--- a/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes
+++ b/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes
@@ -9,6 +9,8 @@ panel_blueprint.cpp linguist-generated=true
panel_blueprint.hpp linguist-generated=true
plot_legend.cpp linguist-generated=true
plot_legend.hpp linguist-generated=true
+range_table_order.cpp linguist-generated=true
+range_table_order.hpp linguist-generated=true
scalar_axis.cpp linguist-generated=true
scalar_axis.hpp linguist-generated=true
space_view_blueprint.cpp linguist-generated=true
diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/range_table_order.cpp b/rerun_cpp/src/rerun/blueprint/archetypes/range_table_order.cpp
new file mode 100644
index 000000000000..ab6d81ed94cd
--- /dev/null
+++ b/rerun_cpp/src/rerun/blueprint/archetypes/range_table_order.cpp
@@ -0,0 +1,38 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/archetypes/range_table_order.fbs".
+
+#include "range_table_order.hpp"
+
+#include "../../collection_adapter_builtins.hpp"
+
+namespace rerun::blueprint::archetypes {}
+
+namespace rerun {
+
+ Result> AsComponents::serialize(
+ const blueprint::archetypes::RangeTableOrder& archetype
+ ) {
+ using namespace blueprint::archetypes;
+ std::vector cells;
+ cells.reserve(3);
+
+ if (archetype.sort_key.has_value()) {
+ auto result = DataCell::from_loggable(archetype.sort_key.value());
+ RR_RETURN_NOT_OK(result.error);
+ cells.push_back(std::move(result.value));
+ }
+ if (archetype.sort_order.has_value()) {
+ auto result = DataCell::from_loggable(archetype.sort_order.value());
+ RR_RETURN_NOT_OK(result.error);
+ cells.push_back(std::move(result.value));
+ }
+ {
+ auto indicator = RangeTableOrder::IndicatorComponent();
+ auto result = DataCell::from_loggable(indicator);
+ RR_RETURN_NOT_OK(result.error);
+ cells.emplace_back(std::move(result.value));
+ }
+
+ return cells;
+ }
+} // namespace rerun
diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/range_table_order.hpp b/rerun_cpp/src/rerun/blueprint/archetypes/range_table_order.hpp
new file mode 100644
index 000000000000..6df4f71f285b
--- /dev/null
+++ b/rerun_cpp/src/rerun/blueprint/archetypes/range_table_order.hpp
@@ -0,0 +1,69 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/archetypes/range_table_order.fbs".
+
+#pragma once
+
+#include "../../blueprint/components/sort_key.hpp"
+#include "../../blueprint/components/sort_order.hpp"
+#include "../../collection.hpp"
+#include "../../compiler_utils.hpp"
+#include "../../data_cell.hpp"
+#include "../../indicator_component.hpp"
+#include "../../result.hpp"
+
+#include
+#include
+#include
+#include
+
+namespace rerun::blueprint::archetypes {
+ /// **Archetype**: Configuration for the sorting of the rows of a time range table.
+ struct RangeTableOrder {
+ /// The primary sort key.
+ std::optional sort_key;
+
+ /// The sort order.
+ std::optional sort_order;
+
+ public:
+ static constexpr const char IndicatorComponentName[] =
+ "rerun.blueprint.components.RangeTableOrderIndicator";
+
+ /// Indicator component, used to identify the archetype when converting to a list of components.
+ using IndicatorComponent = rerun::components::IndicatorComponent;
+
+ public:
+ RangeTableOrder() = default;
+ RangeTableOrder(RangeTableOrder&& other) = default;
+
+ /// The primary sort key.
+ RangeTableOrder with_sort_key(rerun::blueprint::components::SortKey _sort_key) && {
+ sort_key = std::move(_sort_key);
+ // See: https://github.com/rerun-io/rerun/issues/4027
+ RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);)
+ }
+
+ /// The sort order.
+ RangeTableOrder with_sort_order(rerun::blueprint::components::SortOrder _sort_order) && {
+ sort_order = std::move(_sort_order);
+ // See: https://github.com/rerun-io/rerun/issues/4027
+ RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);)
+ }
+ };
+
+} // namespace rerun::blueprint::archetypes
+
+namespace rerun {
+ /// \private
+ template
+ struct AsComponents;
+
+ /// \private
+ template <>
+ struct AsComponents {
+ /// Serialize all set component batches.
+ static Result> serialize(
+ const blueprint::archetypes::RangeTableOrder& archetype
+ );
+ };
+} // namespace rerun
diff --git a/rerun_cpp/src/rerun/blueprint/components.hpp b/rerun_cpp/src/rerun/blueprint/components.hpp
index 45e6beaf7803..bcbb0d44b2ff 100644
--- a/rerun_cpp/src/rerun/blueprint/components.hpp
+++ b/rerun_cpp/src/rerun/blueprint/components.hpp
@@ -18,6 +18,8 @@
#include "blueprint/components/query_expression.hpp"
#include "blueprint/components/root_container.hpp"
#include "blueprint/components/row_share.hpp"
+#include "blueprint/components/sort_key.hpp"
+#include "blueprint/components/sort_order.hpp"
#include "blueprint/components/space_view_class.hpp"
#include "blueprint/components/space_view_maximized.hpp"
#include "blueprint/components/space_view_origin.hpp"
diff --git a/rerun_cpp/src/rerun/blueprint/components/.gitattributes b/rerun_cpp/src/rerun/blueprint/components/.gitattributes
index 11d00c3d1875..189ad84f442b 100644
--- a/rerun_cpp/src/rerun/blueprint/components/.gitattributes
+++ b/rerun_cpp/src/rerun/blueprint/components/.gitattributes
@@ -21,6 +21,10 @@ panel_state.hpp linguist-generated=true
query_expression.hpp linguist-generated=true
root_container.hpp linguist-generated=true
row_share.hpp linguist-generated=true
+sort_key.cpp linguist-generated=true
+sort_key.hpp linguist-generated=true
+sort_order.cpp linguist-generated=true
+sort_order.hpp linguist-generated=true
space_view_class.hpp linguist-generated=true
space_view_maximized.hpp linguist-generated=true
space_view_origin.hpp linguist-generated=true
diff --git a/rerun_cpp/src/rerun/blueprint/components/sort_key.cpp b/rerun_cpp/src/rerun/blueprint/components/sort_key.cpp
new file mode 100644
index 000000000000..f488500c1803
--- /dev/null
+++ b/rerun_cpp/src/rerun/blueprint/components/sort_key.cpp
@@ -0,0 +1,62 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/components/sort_key.fbs".
+
+#include "sort_key.hpp"
+
+#include
+#include
+
+namespace rerun {
+ const std::shared_ptr&
+ Loggable::arrow_datatype() {
+ static const auto datatype = arrow::sparse_union({
+ arrow::field("_null_markers", arrow::null(), true, nullptr),
+ arrow::field("Entity", arrow::null(), true),
+ arrow::field("Time", arrow::null(), true),
+ });
+ return datatype;
+ }
+
+ Result> Loggable::to_arrow(
+ const blueprint::components::SortKey* instances, size_t num_instances
+ ) {
+ // TODO(andreas): Allow configuring the memory pool.
+ arrow::MemoryPool* pool = arrow::default_memory_pool();
+ auto datatype = arrow_datatype();
+
+ ARROW_ASSIGN_OR_RAISE(auto builder, arrow::MakeBuilder(datatype, pool))
+ if (instances && num_instances > 0) {
+ RR_RETURN_NOT_OK(Loggable::fill_arrow_array_builder(
+ static_cast(builder.get()),
+ instances,
+ num_instances
+ ));
+ }
+ std::shared_ptr array;
+ ARROW_RETURN_NOT_OK(builder->Finish(&array));
+ return array;
+ }
+
+ rerun::Error Loggable::fill_arrow_array_builder(
+ arrow::SparseUnionBuilder* builder, const blueprint::components::SortKey* elements,
+ size_t num_elements
+ ) {
+ if (builder == nullptr) {
+ return rerun::Error(ErrorCode::UnexpectedNullArgument, "Passed array builder is null.");
+ }
+ if (elements == nullptr) {
+ return rerun::Error(
+ ErrorCode::UnexpectedNullArgument,
+ "Cannot serialize null pointer to arrow array."
+ );
+ }
+
+ ARROW_RETURN_NOT_OK(builder->Reserve(static_cast(num_elements)));
+ for (size_t elem_idx = 0; elem_idx < num_elements; elem_idx += 1) {
+ const auto variant = elements[elem_idx];
+ ARROW_RETURN_NOT_OK(builder->Append(static_cast(variant)));
+ }
+
+ return Error::ok();
+ }
+} // namespace rerun
diff --git a/rerun_cpp/src/rerun/blueprint/components/sort_key.hpp b/rerun_cpp/src/rerun/blueprint/components/sort_key.hpp
new file mode 100644
index 000000000000..4437765fddf0
--- /dev/null
+++ b/rerun_cpp/src/rerun/blueprint/components/sort_key.hpp
@@ -0,0 +1,52 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/components/sort_key.fbs".
+
+#pragma once
+
+#include "../../result.hpp"
+
+#include
+#include
+
+namespace arrow {
+ class Array;
+ class DataType;
+ class SparseUnionBuilder;
+} // namespace arrow
+
+namespace rerun::blueprint::components {
+ /// **Component**: Primary element by which to group by in a temporal data table.
+ enum class SortKey : uint8_t {
+
+ /// Group by entity.
+ Entity = 1,
+
+ /// Group by instance.
+ Time = 2,
+ };
+} // namespace rerun::blueprint::components
+
+namespace rerun {
+ template
+ struct Loggable;
+
+ /// \private
+ template <>
+ struct Loggable {
+ static constexpr const char Name[] = "rerun.blueprint.components.SortKey";
+
+ /// Returns the arrow data type this type corresponds to.
+ static const std::shared_ptr& arrow_datatype();
+
+ /// Serializes an array of `rerun::blueprint:: components::SortKey` into an arrow array.
+ static Result> to_arrow(
+ const blueprint::components::SortKey* instances, size_t num_instances
+ );
+
+ /// Fills an arrow array builder with an array of this type.
+ static rerun::Error fill_arrow_array_builder(
+ arrow::SparseUnionBuilder* builder, const blueprint::components::SortKey* elements,
+ size_t num_elements
+ );
+ };
+} // namespace rerun
diff --git a/rerun_cpp/src/rerun/blueprint/components/sort_order.cpp b/rerun_cpp/src/rerun/blueprint/components/sort_order.cpp
new file mode 100644
index 000000000000..b1a4ec0e5814
--- /dev/null
+++ b/rerun_cpp/src/rerun/blueprint/components/sort_order.cpp
@@ -0,0 +1,62 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/components/sort_order.fbs".
+
+#include "sort_order.hpp"
+
+#include
+#include
+
+namespace rerun {
+ const std::shared_ptr&
+ Loggable::arrow_datatype() {
+ static const auto datatype = arrow::sparse_union({
+ arrow::field("_null_markers", arrow::null(), true, nullptr),
+ arrow::field("Ascending", arrow::null(), true),
+ arrow::field("Descending", arrow::null(), true),
+ });
+ return datatype;
+ }
+
+ Result> Loggable::to_arrow(
+ const blueprint::components::SortOrder* instances, size_t num_instances
+ ) {
+ // TODO(andreas): Allow configuring the memory pool.
+ arrow::MemoryPool* pool = arrow::default_memory_pool();
+ auto datatype = arrow_datatype();
+
+ ARROW_ASSIGN_OR_RAISE(auto builder, arrow::MakeBuilder(datatype, pool))
+ if (instances && num_instances > 0) {
+ RR_RETURN_NOT_OK(Loggable::fill_arrow_array_builder(
+ static_cast(builder.get()),
+ instances,
+ num_instances
+ ));
+ }
+ std::shared_ptr array;
+ ARROW_RETURN_NOT_OK(builder->Finish(&array));
+ return array;
+ }
+
+ rerun::Error Loggable::fill_arrow_array_builder(
+ arrow::SparseUnionBuilder* builder, const blueprint::components::SortOrder* elements,
+ size_t num_elements
+ ) {
+ if (builder == nullptr) {
+ return rerun::Error(ErrorCode::UnexpectedNullArgument, "Passed array builder is null.");
+ }
+ if (elements == nullptr) {
+ return rerun::Error(
+ ErrorCode::UnexpectedNullArgument,
+ "Cannot serialize null pointer to arrow array."
+ );
+ }
+
+ ARROW_RETURN_NOT_OK(builder->Reserve(static_cast(num_elements)));
+ for (size_t elem_idx = 0; elem_idx < num_elements; elem_idx += 1) {
+ const auto variant = elements[elem_idx];
+ ARROW_RETURN_NOT_OK(builder->Append(static_cast(variant)));
+ }
+
+ return Error::ok();
+ }
+} // namespace rerun
diff --git a/rerun_cpp/src/rerun/blueprint/components/sort_order.hpp b/rerun_cpp/src/rerun/blueprint/components/sort_order.hpp
new file mode 100644
index 000000000000..a292ab9bd19a
--- /dev/null
+++ b/rerun_cpp/src/rerun/blueprint/components/sort_order.hpp
@@ -0,0 +1,52 @@
+// DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/cpp/mod.rs
+// Based on "crates/store/re_types/definitions/rerun/blueprint/components/sort_order.fbs".
+
+#pragma once
+
+#include "../../result.hpp"
+
+#include
+#include
+
+namespace arrow {
+ class Array;
+ class DataType;
+ class SparseUnionBuilder;
+} // namespace arrow
+
+namespace rerun::blueprint::components {
+ /// **Component**: Sort order for data table.
+ enum class SortOrder : uint8_t {
+
+ /// Ascending
+ Ascending = 1,
+
+ /// Descending
+ Descending = 2,
+ };
+} // namespace rerun::blueprint::components
+
+namespace rerun {
+ template
+ struct Loggable;
+
+ /// \private
+ template <>
+ struct Loggable {
+ static constexpr const char Name[] = "rerun.blueprint.components.SortOrder";
+
+ /// Returns the arrow data type this type corresponds to.
+ static const std::shared_ptr& arrow_datatype();
+
+ /// Serializes an array of `rerun::blueprint:: components::SortOrder` into an arrow array.
+ static Result> to_arrow(
+ const blueprint::components::SortOrder* instances, size_t num_instances
+ );
+
+ /// Fills an arrow array builder with an array of this type.
+ static rerun::Error fill_arrow_array_builder(
+ arrow::SparseUnionBuilder* builder, const blueprint::components::SortOrder* elements,
+ size_t num_elements
+ );
+ };
+} // namespace rerun
diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes
index ac20f8caae1f..1ca2967b2015 100644
--- a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes
+++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes
@@ -6,6 +6,7 @@ background.py linguist-generated=true
container_blueprint.py linguist-generated=true
panel_blueprint.py linguist-generated=true
plot_legend.py linguist-generated=true
+range_table_order.py linguist-generated=true
scalar_axis.py linguist-generated=true
space_view_blueprint.py linguist-generated=true
space_view_contents.py linguist-generated=true
diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py
index 3af18008eb45..1af293c74ee6 100644
--- a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py
+++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py
@@ -6,6 +6,7 @@
from .container_blueprint import ContainerBlueprint
from .panel_blueprint import PanelBlueprint
from .plot_legend import PlotLegend
+from .range_table_order import RangeTableOrder
from .scalar_axis import ScalarAxis
from .space_view_blueprint import SpaceViewBlueprint
from .space_view_contents import SpaceViewContents
@@ -21,6 +22,7 @@
"ContainerBlueprint",
"PanelBlueprint",
"PlotLegend",
+ "RangeTableOrder",
"ScalarAxis",
"SpaceViewBlueprint",
"SpaceViewContents",
diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/range_table_order.py b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/range_table_order.py
new file mode 100644
index 000000000000..a1eb712e951a
--- /dev/null
+++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/range_table_order.py
@@ -0,0 +1,82 @@
+# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs
+# Based on "crates/store/re_types/definitions/rerun/blueprint/archetypes/range_table_order.fbs".
+
+# You can extend this class by creating a "RangeTableOrderExt" class in "range_table_order_ext.py".
+
+from __future__ import annotations
+
+from typing import Any
+
+from attrs import define, field
+
+from ..._baseclasses import (
+ Archetype,
+)
+from ...blueprint import components as blueprint_components
+from ...error_utils import catch_and_log_exceptions
+
+__all__ = ["RangeTableOrder"]
+
+
+@define(str=False, repr=False, init=False)
+class RangeTableOrder(Archetype):
+ """**Archetype**: Configuration for the sorting of the rows of a time range table."""
+
+ def __init__(
+ self: Any,
+ *,
+ sort_key: blueprint_components.SortKeyLike | None = None,
+ sort_order: blueprint_components.SortOrderLike | None = None,
+ ):
+ """
+ Create a new instance of the RangeTableOrder archetype.
+
+ Parameters
+ ----------
+ sort_key:
+ The primary sort key.
+ sort_order:
+ The sort order.
+
+ """
+
+ # You can define your own __init__ function as a member of RangeTableOrderExt in range_table_order_ext.py
+ with catch_and_log_exceptions(context=self.__class__.__name__):
+ self.__attrs_init__(sort_key=sort_key, sort_order=sort_order)
+ return
+ self.__attrs_clear__()
+
+ def __attrs_clear__(self) -> None:
+ """Convenience method for calling `__attrs_init__` with all `None`s."""
+ self.__attrs_init__(
+ sort_key=None, # type: ignore[arg-type]
+ sort_order=None, # type: ignore[arg-type]
+ )
+
+ @classmethod
+ def _clear(cls) -> RangeTableOrder:
+ """Produce an empty RangeTableOrder, bypassing `__init__`."""
+ inst = cls.__new__(cls)
+ inst.__attrs_clear__()
+ return inst
+
+ sort_key: blueprint_components.SortKeyBatch | None = field(
+ metadata={"component": "optional"},
+ default=None,
+ converter=blueprint_components.SortKeyBatch._optional, # type: ignore[misc]
+ )
+ # The primary sort key.
+ #
+ # (Docstring intentionally commented out to hide this field from the docs)
+
+ sort_order: blueprint_components.SortOrderBatch | None = field(
+ metadata={"component": "optional"},
+ default=None,
+ converter=blueprint_components.SortOrderBatch._optional, # type: ignore[misc]
+ )
+ # The sort order.
+ #
+ # (Docstring intentionally commented out to hide this field from the docs)
+
+ __str__ = Archetype.__str__
+ __repr__ = Archetype.__repr__ # type: ignore[assignment]
diff --git a/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes b/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes
index e232a7f04c35..ca7cc8880260 100644
--- a/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes
+++ b/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes
@@ -18,6 +18,8 @@ panel_state.py linguist-generated=true
query_expression.py linguist-generated=true
root_container.py linguist-generated=true
row_share.py linguist-generated=true
+sort_key.py linguist-generated=true
+sort_order.py linguist-generated=true
space_view_class.py linguist-generated=true
space_view_maximized.py linguist-generated=true
space_view_origin.py linguist-generated=true
diff --git a/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py b/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py
index 10f98717719d..ac06fd41efa7 100644
--- a/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py
+++ b/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py
@@ -30,6 +30,8 @@
from .query_expression import QueryExpression, QueryExpressionBatch, QueryExpressionType
from .root_container import RootContainer, RootContainerBatch, RootContainerType
from .row_share import RowShare, RowShareBatch, RowShareType
+from .sort_key import SortKey, SortKeyArrayLike, SortKeyBatch, SortKeyLike, SortKeyType
+from .sort_order import SortOrder, SortOrderArrayLike, SortOrderBatch, SortOrderLike, SortOrderType
from .space_view_class import SpaceViewClass, SpaceViewClassBatch, SpaceViewClassType
from .space_view_maximized import SpaceViewMaximized, SpaceViewMaximizedBatch, SpaceViewMaximizedType
from .space_view_origin import SpaceViewOrigin, SpaceViewOriginBatch, SpaceViewOriginType
@@ -106,6 +108,16 @@
"RowShare",
"RowShareBatch",
"RowShareType",
+ "SortKey",
+ "SortKeyArrayLike",
+ "SortKeyBatch",
+ "SortKeyLike",
+ "SortKeyType",
+ "SortOrder",
+ "SortOrderArrayLike",
+ "SortOrderBatch",
+ "SortOrderLike",
+ "SortOrderType",
"SpaceViewClass",
"SpaceViewClassBatch",
"SpaceViewClassType",
diff --git a/rerun_py/rerun_sdk/rerun/blueprint/components/sort_key.py b/rerun_py/rerun_sdk/rerun/blueprint/components/sort_key.py
new file mode 100644
index 000000000000..b4da9b031608
--- /dev/null
+++ b/rerun_py/rerun_sdk/rerun/blueprint/components/sort_key.py
@@ -0,0 +1,93 @@
+# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs
+# Based on "crates/store/re_types/definitions/rerun/blueprint/components/sort_key.fbs".
+
+# You can extend this class by creating a "SortKeyExt" class in "sort_key_ext.py".
+
+from __future__ import annotations
+
+from typing import Literal, Sequence, Union
+
+import pyarrow as pa
+
+from ..._baseclasses import (
+ BaseBatch,
+ BaseExtensionType,
+ ComponentBatchMixin,
+)
+
+__all__ = ["SortKey", "SortKeyArrayLike", "SortKeyBatch", "SortKeyLike", "SortKeyType"]
+
+
+from enum import Enum
+
+
+class SortKey(Enum):
+ """**Component**: Primary element by which to group by in a temporal data table."""
+
+ Entity = 1
+ """Group by entity."""
+
+ Time = 2
+ """Group by instance."""
+
+
+SortKeyLike = Union[SortKey, Literal["entity", "time"]]
+SortKeyArrayLike = Union[SortKeyLike, Sequence[SortKeyLike]]
+
+
+class SortKeyType(BaseExtensionType):
+ _TYPE_NAME: str = "rerun.blueprint.components.SortKey"
+
+ def __init__(self) -> None:
+ pa.ExtensionType.__init__(
+ self,
+ pa.sparse_union([
+ pa.field("_null_markers", pa.null(), nullable=True, metadata={}),
+ pa.field("Entity", pa.null(), nullable=True, metadata={}),
+ pa.field("Time", pa.null(), nullable=True, metadata={}),
+ ]),
+ self._TYPE_NAME,
+ )
+
+
+class SortKeyBatch(BaseBatch[SortKeyArrayLike], ComponentBatchMixin):
+ _ARROW_TYPE = SortKeyType()
+
+ @staticmethod
+ def _native_to_pa_array(data: SortKeyArrayLike, data_type: pa.DataType) -> pa.Array:
+ if isinstance(data, (SortKey, int, str)):
+ data = [data]
+
+ types: list[int] = []
+
+ for value in data:
+ if value is None:
+ types.append(0)
+ elif isinstance(value, SortKey):
+ types.append(value.value) # Actual enum value
+ elif isinstance(value, int):
+ types.append(value) # By number
+ elif isinstance(value, str):
+ if hasattr(SortKey, value):
+ types.append(SortKey[value].value) # fast path
+ elif value.lower() == "entity":
+ types.append(SortKey.Entity.value)
+ elif value.lower() == "time":
+ types.append(SortKey.Time.value)
+ else:
+ raise ValueError(f"Unknown SortKey kind: {value}")
+ else:
+ raise ValueError(f"Unknown SortKey kind: {value}")
+
+ buffers = [
+ None,
+ pa.array(types, type=pa.int8()).buffers()[1],
+ ]
+ children = (1 + 2) * [pa.nulls(len(data))]
+
+ return pa.UnionArray.from_buffers(
+ type=data_type,
+ length=len(data),
+ buffers=buffers,
+ children=children,
+ )
diff --git a/rerun_py/rerun_sdk/rerun/blueprint/components/sort_order.py b/rerun_py/rerun_sdk/rerun/blueprint/components/sort_order.py
new file mode 100644
index 000000000000..70c932379614
--- /dev/null
+++ b/rerun_py/rerun_sdk/rerun/blueprint/components/sort_order.py
@@ -0,0 +1,93 @@
+# DO NOT EDIT! This file was auto-generated by crates/build/re_types_builder/src/codegen/python/mod.rs
+# Based on "crates/store/re_types/definitions/rerun/blueprint/components/sort_order.fbs".
+
+# You can extend this class by creating a "SortOrderExt" class in "sort_order_ext.py".
+
+from __future__ import annotations
+
+from typing import Literal, Sequence, Union
+
+import pyarrow as pa
+
+from ..._baseclasses import (
+ BaseBatch,
+ BaseExtensionType,
+ ComponentBatchMixin,
+)
+
+__all__ = ["SortOrder", "SortOrderArrayLike", "SortOrderBatch", "SortOrderLike", "SortOrderType"]
+
+
+from enum import Enum
+
+
+class SortOrder(Enum):
+ """**Component**: Sort order for data table."""
+
+ Ascending = 1
+ """Ascending"""
+
+ Descending = 2
+ """Descending"""
+
+
+SortOrderLike = Union[SortOrder, Literal["ascending", "descending"]]
+SortOrderArrayLike = Union[SortOrderLike, Sequence[SortOrderLike]]
+
+
+class SortOrderType(BaseExtensionType):
+ _TYPE_NAME: str = "rerun.blueprint.components.SortOrder"
+
+ def __init__(self) -> None:
+ pa.ExtensionType.__init__(
+ self,
+ pa.sparse_union([
+ pa.field("_null_markers", pa.null(), nullable=True, metadata={}),
+ pa.field("Ascending", pa.null(), nullable=True, metadata={}),
+ pa.field("Descending", pa.null(), nullable=True, metadata={}),
+ ]),
+ self._TYPE_NAME,
+ )
+
+
+class SortOrderBatch(BaseBatch[SortOrderArrayLike], ComponentBatchMixin):
+ _ARROW_TYPE = SortOrderType()
+
+ @staticmethod
+ def _native_to_pa_array(data: SortOrderArrayLike, data_type: pa.DataType) -> pa.Array:
+ if isinstance(data, (SortOrder, int, str)):
+ data = [data]
+
+ types: list[int] = []
+
+ for value in data:
+ if value is None:
+ types.append(0)
+ elif isinstance(value, SortOrder):
+ types.append(value.value) # Actual enum value
+ elif isinstance(value, int):
+ types.append(value) # By number
+ elif isinstance(value, str):
+ if hasattr(SortOrder, value):
+ types.append(SortOrder[value].value) # fast path
+ elif value.lower() == "ascending":
+ types.append(SortOrder.Ascending.value)
+ elif value.lower() == "descending":
+ types.append(SortOrder.Descending.value)
+ else:
+ raise ValueError(f"Unknown SortOrder kind: {value}")
+ else:
+ raise ValueError(f"Unknown SortOrder kind: {value}")
+
+ buffers = [
+ None,
+ pa.array(types, type=pa.int8()).buffers()[1],
+ ]
+ children = (1 + 2) * [pa.nulls(len(data))]
+
+ return pa.UnionArray.from_buffers(
+ type=data_type,
+ length=len(data),
+ buffers=buffers,
+ children=children,
+ )