diff --git a/crates/re_space_view_time_series/src/space_view_class.rs b/crates/re_space_view_time_series/src/space_view_class.rs index 5137dc24ffcc..eeb1d7299e19 100644 --- a/crates/re_space_view_time_series/src/space_view_class.rs +++ b/crates/re_space_view_time_series/src/space_view_class.rs @@ -1,4 +1,5 @@ use egui::ahash::{HashMap, HashSet}; +use egui::NumExt as _; use egui_plot::{Legend, Line, Plot, PlotPoint, Points}; use re_data_store::TimeType; @@ -7,6 +8,7 @@ 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::components::Range1D; use re_types::Archetype; use re_viewer_context::external::re_entity_db::{ EditableAutoValue, EntityProperties, EntityTree, TimeSeriesAggregator, @@ -25,7 +27,7 @@ use crate::PlotSeriesKind; // --- -#[derive(Clone, Default)] +#[derive(Clone)] pub struct TimeSeriesSpaceViewState { /// Is the user dragging the cursor this frame? is_dragging_time_cursor: bool, @@ -35,6 +37,24 @@ pub struct TimeSeriesSpaceViewState { /// State of egui_plot's auto bounds before the user started dragging the time cursor. saved_auto_bounds: egui::Vec2b, + + /// State of egui_plot's bounds + saved_y_axis_range: [f64; 2], + + /// To track when the range has been edited + last_range: Option, +} + +impl Default for TimeSeriesSpaceViewState { + fn default() -> Self { + Self { + is_dragging_time_cursor: false, + was_dragging_time_cursor: false, + saved_auto_bounds: Default::default(), + saved_y_axis_range: [0.0, 1.0], + last_range: None, + } + } } impl SpaceViewState for TimeSeriesSpaceViewState { @@ -75,7 +95,7 @@ impl SpaceViewClass for TimeSeriesSpaceView { layout.add("Scroll + "); layout.add(controls::ASPECT_SCROLL_MODIFIER); - layout.add(" to change the aspect ratio.\n"); + layout.add(" to zoom only the temporal axis while holding the y-range fixed.\n"); layout.add("Drag "); layout.add(controls::SELECTION_RECT_ZOOM_BUTTON); @@ -113,7 +133,7 @@ impl SpaceViewClass for TimeSeriesSpaceView { &self, ctx: &ViewerContext<'_>, ui: &mut egui::Ui, - _state: &mut Self::State, + state: &mut Self::State, _space_origin: &EntityPath, space_view_id: SpaceViewId, root_entity_properties: &mut EntityProperties, @@ -146,6 +166,7 @@ It can greatly improve performance (and readability) in such situations as it pr }); legend_ui(ctx, space_view_id, ui); + axis_ui(ctx, space_view_id, ui, state); } fn spawn_heuristics(&self, ctx: &ViewerContext<'_>) -> SpaceViewSpawnHeuristics { @@ -280,6 +301,14 @@ It can greatly improve performance (and readability) in such situations as it pr _, ) = query_space_view_sub_archetype(ctx, query.space_view_id); + let ( + re_types::blueprint::archetypes::ScalarAxis { + range: y_range, + lock_range_during_zoom: y_lock_range_during_zoom, + }, + _, + ) = query_space_view_sub_archetype(ctx, query.space_view_id); + let (current_time, time_type, timeline) = { // Avoid holding the lock for long let time_ctrl = ctx.rec_cfg.time_ctrl.read(); @@ -332,12 +361,25 @@ It can greatly improve performance (and readability) in such situations as it pr // use timeline_name as part of id, so that egui stores different pan/zoom for different timelines let plot_id_src = ("plot", &timeline_name); - let zoom_both_axis = !ui.input(|i| i.modifiers.contains(controls::ASPECT_SCROLL_MODIFIER)); + let lock_y_during_zoom = y_lock_range_during_zoom.map_or(false, |v| v.0) + || ui.input(|i| i.modifiers.contains(controls::ASPECT_SCROLL_MODIFIER)); + + let auto_y = y_range.is_none(); + + // We don't want to allow vertical when y is locked or else the view "bounces" when we scroll and + // then reset to the locked range. + if lock_y_during_zoom { + ui.input_mut(|i| i.smooth_scroll_delta.y = 0.0); + } + + // TODO(jleibs): Would be nice to disable vertical drag instead of just resetting. + // TODO(#5075): Boxed-zoom should be fixed to accommodate the locked range. let time_zone_for_timestamps = ctx.app_options.time_zone_for_timestamps; let mut plot = Plot::new(plot_id_src) .id(crate::plot_id(query.space_view_id)) - .allow_zoom([true, zoom_both_axis]) + .auto_bounds([true, auto_y].into()) + .allow_zoom([true, !lock_y_during_zoom]) .x_axis_formatter(move |time, _, _| { format_time( time_type, @@ -400,6 +442,36 @@ It can greatly improve performance (and readability) in such situations as it pr time_ctrl_write.pause(); } + let range_was_edited = state.last_range != y_range; + state.last_range = y_range; + let is_resetting = plot_ui.response().double_clicked(); + let current_auto = plot_ui.auto_bounds(); + + if let Some(y_range) = y_range { + // If we have a y_range, there are a few cases where we want to adjust the bounds. + // - The range was just edited + // - The zoom behavior is in LockToRange + // - The user double-clicked + if range_was_edited || lock_y_during_zoom || is_resetting { + let current_bounds = plot_ui.plot_bounds(); + let mut min = current_bounds.min(); + let mut max = current_bounds.max(); + + // Pad the range by 5% on each side. + min[1] = y_range.0[0]; + max[1] = y_range.0[1]; + let new_bounds = egui_plot::PlotBounds::from_min_max(min, max); + plot_ui.set_plot_bounds(new_bounds); + // If we are resetting, we still want the X value to be auto for + // this frame. + plot_ui.set_auto_bounds([current_auto[0] || is_resetting, false].into()); + } + } else if lock_y_during_zoom || range_was_edited { + // If we are using auto range, but the range is locked, always + // force the y-bounds to be auto to prevent scrolling / zooming in y. + plot_ui.set_auto_bounds([current_auto[0] || is_resetting, true].into()); + } + for series in all_plot_series { let points = series .points @@ -444,6 +516,8 @@ It can greatly improve performance (and readability) in such situations as it pr } state.was_dragging_time_cursor = state.is_dragging_time_cursor; + let bounds = plot_ui.plot_bounds().range_y(); + state.saved_y_axis_range = [*bounds.start(), *bounds.end()]; }); // Decide if the time cursor should be displayed, and if so where: @@ -565,6 +639,101 @@ fn legend_ui(ctx: &ViewerContext<'_>, space_view_id: SpaceViewId, ui: &mut egui: }); } +fn axis_ui( + ctx: &ViewerContext<'_>, + space_view_id: SpaceViewId, + ui: &mut egui::Ui, + state: &TimeSeriesSpaceViewState, +) { + // TODO(jleibs): use editors + + let ( + re_types::blueprint::archetypes::ScalarAxis { + range: y_range, + lock_range_during_zoom: y_lock_range_during_zoom, + }, + blueprint_path, + ) = query_space_view_sub_archetype(ctx, space_view_id); + + ctx.re_ui.collapsing_header(ui, "Y Axis", true, |ui| { + ctx.re_ui + .selection_grid(ui, "time_series_selection_ui_y_axis_range") + .show(ui, |ui| { + ctx.re_ui.grid_left_hand_label(ui, "Range"); + + ui.vertical(|ui| { + let mut auto_range = y_range.is_none(); + + ui.horizontal(|ui| { + ctx.re_ui + .radio_value(ui, &mut auto_range, true, "Auto") + .on_hover_text("Automatically adjust the Y axis to fit the data."); + ctx.re_ui + .radio_value(ui, &mut auto_range, false, "Manual") + .on_hover_text("Manually specify a min and max Y value. This will define the range when resetting or locking the view range."); + }); + + if !auto_range { + let mut range_edit = y_range + .unwrap_or_else(|| y_range.unwrap_or(Range1D(state.saved_y_axis_range))); + + ui.horizontal(|ui| { + // Max < Min is not supported. + // Also, egui_plot doesn't handle min==max (it ends up picking a default range instead then) + let prev_min = crate::util::next_up_f64(range_edit.0[0]); + let prev_max = range_edit.0[1]; + // Scale the speed to the size of the range + let speed = ((prev_max - prev_min) * 0.01).at_least(0.001); + ui.label("Min"); + ui.add( + egui::DragValue::new(&mut range_edit.0[0]) + .speed(speed) + .clamp_range(std::f64::MIN..=prev_max), + ); + ui.label("Max"); + ui.add( + egui::DragValue::new(&mut range_edit.0[1]) + .speed(speed) + .clamp_range(prev_min..=std::f64::MAX), + ); + }); + + if y_range != Some(range_edit) { + ctx.save_blueprint_component(&blueprint_path, range_edit); + } + } else if y_range.is_some() { + ctx.save_empty_blueprint_component::(&blueprint_path); + } + }); + + ui.end_row(); + }); + + ctx.re_ui + .selection_grid(ui, "time_series_selection_ui_y_axis_zoom") + .show(ui, |ui| { + ctx.re_ui.grid_left_hand_label(ui, "Zoom Behavior"); + + ui.vertical(|ui| { + ui.horizontal(|ui| { + let y_lock_zoom = y_lock_range_during_zoom.unwrap_or(false.into()); + let mut edit_locked = y_lock_zoom; + ctx.re_ui + .checkbox(ui, &mut edit_locked.0, "Lock Range") + .on_hover_text( + "If set, when zooming, the Y axis range will remain locked to the specified range.", + ); + if y_lock_zoom != edit_locked { + ctx.save_blueprint_component(&blueprint_path, edit_locked); + } + }) + }); + + 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); diff --git a/crates/re_space_view_time_series/src/util.rs b/crates/re_space_view_time_series/src/util.rs index c03d53b4d4c1..ab86899ee565 100644 --- a/crates/re_space_view_time_series/src/util.rs +++ b/crates/re_space_view_time_series/src/util.rs @@ -264,3 +264,53 @@ fn add_series_runs( all_series.push(series); } } + +/// Returns the least number greater than `self`. +/// +/// Unstable feature in Rust. This is a copy of the implementation from the standard library. +/// +/// Let `TINY` be the smallest representable positive `f64`. Then, +/// - if `self.is_nan()`, this returns `self`; +/// - if `self` is [`NEG_INFINITY`], this returns [`MIN`]; +/// - if `self` is `-TINY`, this returns -0.0; +/// - if `self` is -0.0 or +0.0, this returns `TINY`; +/// - if `self` is [`MAX`] or [`INFINITY`], this returns [`INFINITY`]; +/// - otherwise the unique least value greater than `self` is returned. +/// +/// The identity `x.next_up() == -(-x).next_down()` holds for all non-NaN `x`. When `x` +/// is finite `x == x.next_up().next_down()` also holds. +/// +/// ```rust +/// #![feature(float_next_up_down)] +/// // f64::EPSILON is the difference between 1.0 and the next number up. +/// assert_eq!(1.0f64.next_up(), 1.0 + f64::EPSILON); +/// // But not for most numbers. +/// assert!(0.1f64.next_up() < 0.1 + f64::EPSILON); +/// assert_eq!(9007199254740992f64.next_up(), 9007199254740994.0); +/// ``` +/// +/// [`NEG_INFINITY`]: f64::NEG_INFINITY +/// [`INFINITY`]: f64::INFINITY +/// [`MIN`]: f64::MIN +/// [`MAX`]: f64::MAX +pub fn next_up_f64(this: f64) -> f64 { + // We must use strictly integer arithmetic to prevent denormals from + // flushing to zero after an arithmetic operation on some platforms. + const TINY_BITS: u64 = 0x1; // Smallest positive f64. + const CLEAR_SIGN_MASK: u64 = 0x7fff_ffff_ffff_ffff; + + let bits = this.to_bits(); + if this.is_nan() || bits == f64::INFINITY.to_bits() { + return this; + } + + let abs = bits & CLEAR_SIGN_MASK; + let next_bits = if abs == 0 { + TINY_BITS + } else if bits == abs { + bits + 1 + } else { + bits - 1 + }; + f64::from_bits(next_bits) +} diff --git a/crates/re_types/definitions/rerun/blueprint.fbs b/crates/re_types/definitions/rerun/blueprint.fbs index f5e4dae3a7c4..c69f03ab755c 100644 --- a/crates/re_types/definitions/rerun/blueprint.fbs +++ b/crates/re_types/definitions/rerun/blueprint.fbs @@ -19,9 +19,11 @@ include "./blueprint/components/space_view_origin.fbs"; include "./blueprint/components/viewport_layout.fbs"; include "./blueprint/components/visible.fbs"; include "./blueprint/components/corner_2d.fbs"; +include "./blueprint/components/lock_range_during_zoom.fbs"; include "./blueprint/archetypes/container_blueprint.fbs"; include "./blueprint/archetypes/space_view_blueprint.fbs"; include "./blueprint/archetypes/viewport_blueprint.fbs"; include "./blueprint/archetypes/plot_legend.fbs"; +include "./blueprint/archetypes/scalar_axis.fbs"; diff --git a/crates/re_types/definitions/rerun/blueprint/archetypes/scalar_axis.fbs b/crates/re_types/definitions/rerun/blueprint/archetypes/scalar_axis.fbs new file mode 100644 index 000000000000..0ed7fedfa4c2 --- /dev/null +++ b/crates/re_types/definitions/rerun/blueprint/archetypes/scalar_axis.fbs @@ -0,0 +1,28 @@ +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 scalar axis of a plot. +table ScalarAxis ( + "attr.docs.unreleased", + "attr.rerun.scope": "blueprint", + "attr.rust.derive": "Default" +) { + // --- Optional --- + + /// The range of the axis. + /// + /// If unset, the range well be automatically determined based on the queried data. + range: rerun.components.Range1D ("attr.rerun.component_optional", nullable, order: 2100); + + /// Whether to lock the range of the axis during zoom. + lock_range_during_zoom: rerun.blueprint.components.LockRangeDuringZoom ("attr.rerun.component_optional", nullable, order: 2200); +} diff --git a/crates/re_types/definitions/rerun/blueprint/components/lock_range_during_zoom.fbs b/crates/re_types/definitions/rerun/blueprint/components/lock_range_during_zoom.fbs new file mode 100644 index 000000000000..e2b5c2ee6cae --- /dev/null +++ b/crates/re_types/definitions/rerun/blueprint/components/lock_range_during_zoom.fbs @@ -0,0 +1,24 @@ +include "arrow/attributes.fbs"; +include "python/attributes.fbs"; +include "rust/attributes.fbs"; + +include "rerun/datatypes.fbs"; +include "rerun/attributes.fbs"; + +namespace rerun.blueprint.components; + +// --- + +/// Indicate whether the range should be locked when zooming in on the data. +/// +/// Default is `false`, i.e. zoom will change the visualized range. +struct LockRangeDuringZoom ( + "attr.docs.unreleased", + "attr.arrow.transparent", + "attr.rerun.scope": "blueprint", + "attr.rust.derive": "Copy, PartialEq, Eq", + "attr.rust.repr": "transparent", + "attr.rust.tuple_struct" +) { + lock_range: bool (order: 100); +} diff --git a/crates/re_types/definitions/rerun/components.fbs b/crates/re_types/definitions/rerun/components.fbs index f7b87ada1f26..3369e3782962 100644 --- a/crates/re_types/definitions/rerun/components.fbs +++ b/crates/re_types/definitions/rerun/components.fbs @@ -23,6 +23,7 @@ include "./components/pinhole_projection.fbs"; include "./components/position2d.fbs"; include "./components/position3d.fbs"; include "./components/radius.fbs"; +include "./components/range1d.fbs"; include "./components/resolution.fbs"; include "./components/rotation3d.fbs"; include "./components/scalar_scattering.fbs"; diff --git a/crates/re_types/definitions/rerun/components/range1d.fbs b/crates/re_types/definitions/rerun/components/range1d.fbs new file mode 100644 index 000000000000..d15ea0e7037f --- /dev/null +++ b/crates/re_types/definitions/rerun/components/range1d.fbs @@ -0,0 +1,19 @@ +include "arrow/attributes.fbs"; +include "python/attributes.fbs"; +include "rust/attributes.fbs"; + +include "rerun/datatypes.fbs"; +include "rerun/attributes.fbs"; + +namespace rerun.components; + +// --- + +/// A 1D range, specifying a lower and upper bound. +struct Range1D ( + "attr.docs.unreleased", + "attr.rust.derive": "Copy, PartialEq, PartialOrd, bytemuck::Pod, bytemuck::Zeroable", + "attr.rust.repr": "transparent" +) { + range: [double: 2] (order: 100); +} diff --git a/crates/re_types/src/blueprint/archetypes/.gitattributes b/crates/re_types/src/blueprint/archetypes/.gitattributes index 0deee8306592..31c9347bc154 100644 --- a/crates/re_types/src/blueprint/archetypes/.gitattributes +++ b/crates/re_types/src/blueprint/archetypes/.gitattributes @@ -3,4 +3,5 @@ .gitattributes linguist-generated=true mod.rs linguist-generated=true plot_legend.rs linguist-generated=true +scalar_axis.rs linguist-generated=true space_view_blueprint.rs linguist-generated=true diff --git a/crates/re_types/src/blueprint/archetypes/mod.rs b/crates/re_types/src/blueprint/archetypes/mod.rs index 7ea57923385d..dc0839ea794a 100644 --- a/crates/re_types/src/blueprint/archetypes/mod.rs +++ b/crates/re_types/src/blueprint/archetypes/mod.rs @@ -1,7 +1,9 @@ // DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/rust/api.rs mod plot_legend; +mod scalar_axis; mod space_view_blueprint; pub use self::plot_legend::PlotLegend; +pub use self::scalar_axis::ScalarAxis; pub use self::space_view_blueprint::SpaceViewBlueprint; diff --git a/crates/re_types/src/blueprint/archetypes/scalar_axis.rs b/crates/re_types/src/blueprint/archetypes/scalar_axis.rs new file mode 100644 index 000000000000..164883b08a7d --- /dev/null +++ b/crates/re_types/src/blueprint/archetypes/scalar_axis.rs @@ -0,0 +1,198 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/re_types/definitions/rerun/blueprint/archetypes/scalar_axis.fbs". + +#![allow(trivial_numeric_casts)] +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::iter_on_single_items)] +#![allow(clippy::map_flatten)] +#![allow(clippy::match_wildcard_for_single_variants)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] +#![allow(clippy::unnecessary_cast)] + +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 scalar axis of a plot. +#[derive(Clone, Debug, Default)] +pub struct ScalarAxis { + /// The range of the axis. + /// + /// If unset, the range well be automatically determined based on the queried data. + pub range: Option, + + /// Whether to lock the range of the axis during zoom. + pub lock_range_during_zoom: Option, +} + +impl ::re_types_core::SizeBytes for ScalarAxis { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.range.heap_size_bytes() + self.lock_range_during_zoom.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.ScalarAxisIndicator".into()]); + +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.blueprint.components.LockRangeDuringZoom".into(), + "rerun.components.InstanceKey".into(), + "rerun.components.Range1D".into(), + ] + }); + +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = + once_cell::sync::Lazy::new(|| { + [ + "rerun.blueprint.components.ScalarAxisIndicator".into(), + "rerun.blueprint.components.LockRangeDuringZoom".into(), + "rerun.components.InstanceKey".into(), + "rerun.components.Range1D".into(), + ] + }); + +impl ScalarAxis { + pub const NUM_COMPONENTS: usize = 4usize; +} + +/// Indicator component for the [`ScalarAxis`] [`::re_types_core::Archetype`] +pub type ScalarAxisIndicator = ::re_types_core::GenericIndicatorComponent; + +impl ::re_types_core::Archetype for ScalarAxis { + type Indicator = ScalarAxisIndicator; + + #[inline] + fn name() -> ::re_types_core::ArchetypeName { + "rerun.blueprint.archetypes.ScalarAxis".into() + } + + #[inline] + fn indicator() -> MaybeOwnedComponentBatch<'static> { + static INDICATOR: ScalarAxisIndicator = ScalarAxisIndicator::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 range = if let Some(array) = arrays_by_name.get("rerun.components.Range1D") { + ::from_arrow_opt(&**array) + .with_context("rerun.blueprint.archetypes.ScalarAxis#range")? + .into_iter() + .next() + .flatten() + } else { + None + }; + let lock_range_during_zoom = if let Some(array) = + arrays_by_name.get("rerun.blueprint.components.LockRangeDuringZoom") + { + ::from_arrow_opt(&**array) + .with_context("rerun.blueprint.archetypes.ScalarAxis#lock_range_during_zoom")? + .into_iter() + .next() + .flatten() + } else { + None + }; + Ok(Self { + range, + lock_range_during_zoom, + }) + } +} + +impl ::re_types_core::AsComponents for ScalarAxis { + fn as_component_batches(&self) -> Vec> { + re_tracing::profile_function!(); + use ::re_types_core::Archetype as _; + [ + Some(Self::indicator()), + self.range + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + self.lock_range_during_zoom + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), + ] + .into_iter() + .flatten() + .collect() + } + + #[inline] + fn num_instances(&self) -> usize { + 0 + } +} + +impl ScalarAxis { + pub fn new() -> Self { + Self { + range: None, + lock_range_during_zoom: None, + } + } + + #[inline] + pub fn with_range(mut self, range: impl Into) -> Self { + self.range = Some(range.into()); + self + } + + #[inline] + pub fn with_lock_range_during_zoom( + mut self, + lock_range_during_zoom: impl Into, + ) -> Self { + self.lock_range_during_zoom = Some(lock_range_during_zoom.into()); + self + } +} diff --git a/crates/re_types/src/blueprint/components/.gitattributes b/crates/re_types/src/blueprint/components/.gitattributes index d5d086bbc8be..58a10696238d 100644 --- a/crates/re_types/src/blueprint/components/.gitattributes +++ b/crates/re_types/src/blueprint/components/.gitattributes @@ -7,6 +7,7 @@ corner2d.rs linguist-generated=true entities_determined_by_user.rs linguist-generated=true included_contents.rs linguist-generated=true included_queries.rs linguist-generated=true +lock_range_during_zoom.rs linguist-generated=true mod.rs linguist-generated=true row_shares.rs linguist-generated=true space_view_class.rs linguist-generated=true diff --git a/crates/re_types/src/blueprint/components/lock_range_during_zoom.rs b/crates/re_types/src/blueprint/components/lock_range_during_zoom.rs new file mode 100644 index 000000000000..69ab0aed02a6 --- /dev/null +++ b/crates/re_types/src/blueprint/components/lock_range_during_zoom.rs @@ -0,0 +1,134 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/re_types/definitions/rerun/blueprint/components/lock_range_during_zoom.fbs". + +#![allow(trivial_numeric_casts)] +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::iter_on_single_items)] +#![allow(clippy::map_flatten)] +#![allow(clippy::match_wildcard_for_single_variants)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] +#![allow(clippy::unnecessary_cast)] + +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**: Indicate whether the range should be locked when zooming in on the data. +/// +/// Default is `false`, i.e. zoom will change the visualized range. +#[derive(Clone, Debug, Copy, PartialEq, Eq)] +#[repr(transparent)] +pub struct LockRangeDuringZoom(pub bool); + +impl ::re_types_core::SizeBytes for LockRangeDuringZoom { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl From for LockRangeDuringZoom { + #[inline] + fn from(lock_range: bool) -> Self { + Self(lock_range) + } +} + +impl From for bool { + #[inline] + fn from(value: LockRangeDuringZoom) -> Self { + value.0 + } +} + +::re_types_core::macros::impl_into_cow!(LockRangeDuringZoom); + +impl ::re_types_core::Loggable for LockRangeDuringZoom { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.blueprint.components.LockRangeDuringZoom".into() + } + + #[allow(clippy::wildcard_imports)] + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + use arrow2::datatypes::*; + DataType::Boolean + } + + #[allow(clippy::wildcard_imports)] + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, datatypes::*}; + Ok({ + let (somes, data0): (Vec<_>, Vec<_>) = data + .into_iter() + .map(|datum| { + let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into); + let datum = datum.map(|datum| { + let Self(data0) = datum.into_owned(); + data0 + }); + (datum.is_some(), datum) + }) + .unzip(); + let data0_bitmap: Option = { + let any_nones = somes.iter().any(|some| !*some); + any_nones.then(|| somes.into()) + }; + BooleanArray::new( + Self::arrow_datatype(), + data0.into_iter().map(|v| v.unwrap_or_default()).collect(), + data0_bitmap, + ) + .boxed() + }) + } + + #[allow(clippy::wildcard_imports)] + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, buffer::*, datatypes::*}; + Ok(arrow_data + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::Boolean, + arrow_data.data_type().clone(), + ) + }) + .with_context("rerun.blueprint.components.LockRangeDuringZoom#lock_range")? + .into_iter() + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .map(|res| res.map(|v| Some(Self(v)))) + .collect::>>>() + .with_context("rerun.blueprint.components.LockRangeDuringZoom#lock_range") + .with_context("rerun.blueprint.components.LockRangeDuringZoom")?) + } +} diff --git a/crates/re_types/src/blueprint/components/mod.rs b/crates/re_types/src/blueprint/components/mod.rs index cb2ca4d2d163..9ab4726d6e99 100644 --- a/crates/re_types/src/blueprint/components/mod.rs +++ b/crates/re_types/src/blueprint/components/mod.rs @@ -7,6 +7,7 @@ mod corner2d_ext; mod entities_determined_by_user; mod included_contents; mod included_queries; +mod lock_range_during_zoom; mod row_shares; mod space_view_class; mod space_view_class_ext; @@ -19,6 +20,7 @@ pub use self::corner2d::Corner2D; pub use self::entities_determined_by_user::EntitiesDeterminedByUser; pub use self::included_contents::IncludedContents; pub use self::included_queries::IncludedQueries; +pub use self::lock_range_during_zoom::LockRangeDuringZoom; pub use self::row_shares::RowShares; pub use self::space_view_class::SpaceViewClass; pub use self::space_view_origin::SpaceViewOrigin; diff --git a/crates/re_types/src/components/.gitattributes b/crates/re_types/src/components/.gitattributes index 87aca651f49c..503b4ae31ccd 100644 --- a/crates/re_types/src/components/.gitattributes +++ b/crates/re_types/src/components/.gitattributes @@ -25,6 +25,7 @@ pinhole_projection.rs linguist-generated=true position2d.rs linguist-generated=true position3d.rs linguist-generated=true radius.rs linguist-generated=true +range1d.rs linguist-generated=true resolution.rs linguist-generated=true rotation3d.rs linguist-generated=true scalar.rs linguist-generated=true diff --git a/crates/re_types/src/components/mod.rs b/crates/re_types/src/components/mod.rs index 00111f25b0fa..8cb128077a6f 100644 --- a/crates/re_types/src/components/mod.rs +++ b/crates/re_types/src/components/mod.rs @@ -43,6 +43,7 @@ mod position3d; mod position3d_ext; mod radius; mod radius_ext; +mod range1d; mod resolution; mod rotation3d; mod rotation3d_ext; @@ -89,6 +90,7 @@ pub use self::pinhole_projection::PinholeProjection; pub use self::position2d::Position2D; pub use self::position3d::Position3D; pub use self::radius::Radius; +pub use self::range1d::Range1D; pub use self::resolution::Resolution; pub use self::rotation3d::Rotation3D; pub use self::scalar::Scalar; diff --git a/crates/re_types/src/components/range1d.rs b/crates/re_types/src/components/range1d.rs new file mode 100644 index 000000000000..f650bcdbb55a --- /dev/null +++ b/crates/re_types/src/components/range1d.rs @@ -0,0 +1,282 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/rust/api.rs +// Based on "crates/re_types/definitions/rerun/components/range1d.fbs". + +#![allow(trivial_numeric_casts)] +#![allow(unused_imports)] +#![allow(unused_parens)] +#![allow(clippy::clone_on_copy)] +#![allow(clippy::iter_on_single_items)] +#![allow(clippy::map_flatten)] +#![allow(clippy::match_wildcard_for_single_variants)] +#![allow(clippy::needless_question_mark)] +#![allow(clippy::new_without_default)] +#![allow(clippy::redundant_closure)] +#![allow(clippy::too_many_arguments)] +#![allow(clippy::too_many_lines)] +#![allow(clippy::unnecessary_cast)] + +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**: A 1D range, specifying a lower and upper bound. +#[derive(Clone, Debug, Copy, PartialEq, PartialOrd, bytemuck::Pod, bytemuck::Zeroable)] +#[repr(transparent)] +pub struct Range1D(pub [f64; 2usize]); + +impl ::re_types_core::SizeBytes for Range1D { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + <[f64; 2usize]>::is_pod() + } +} + +impl From<[f64; 2usize]> for Range1D { + #[inline] + fn from(range: [f64; 2usize]) -> Self { + Self(range) + } +} + +impl From for [f64; 2usize] { + #[inline] + fn from(value: Range1D) -> Self { + value.0 + } +} + +::re_types_core::macros::impl_into_cow!(Range1D); + +impl ::re_types_core::Loggable for Range1D { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.Range1D".into() + } + + #[allow(clippy::wildcard_imports)] + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + use arrow2::datatypes::*; + DataType::FixedSizeList( + std::sync::Arc::new(Field { + name: "item".to_owned(), + data_type: DataType::Float64, + is_nullable: false, + metadata: [].into(), + }), + 2usize, + ) + } + + #[allow(clippy::wildcard_imports)] + fn to_arrow_opt<'a>( + data: impl IntoIterator>>>, + ) -> SerializationResult> + where + Self: Clone + 'a, + { + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, datatypes::*}; + Ok({ + let (somes, data0): (Vec<_>, Vec<_>) = data + .into_iter() + .map(|datum| { + let datum: Option<::std::borrow::Cow<'a, Self>> = datum.map(Into::into); + let datum = datum.map(|datum| { + let Self(data0) = datum.into_owned(); + data0 + }); + (datum.is_some(), datum) + }) + .unzip(); + let data0_bitmap: Option = { + let any_nones = somes.iter().any(|some| !*some); + any_nones.then(|| somes.into()) + }; + { + use arrow2::{buffer::Buffer, offset::OffsetsBuffer}; + let data0_inner_data: Vec<_> = data0 + .iter() + .flat_map(|v| match v { + Some(v) => itertools::Either::Left(v.iter().cloned()), + None => itertools::Either::Right( + std::iter::repeat(Default::default()).take(2usize), + ), + }) + .map(Some) + .collect(); + let data0_inner_bitmap: Option = + data0_bitmap.as_ref().map(|bitmap| { + bitmap + .iter() + .map(|i| std::iter::repeat(i).take(2usize)) + .flatten() + .collect::>() + .into() + }); + FixedSizeListArray::new( + Self::arrow_datatype(), + PrimitiveArray::new( + DataType::Float64, + data0_inner_data + .into_iter() + .map(|v| v.unwrap_or_default()) + .collect(), + data0_inner_bitmap, + ) + .boxed(), + data0_bitmap, + ) + .boxed() + } + }) + } + + #[allow(clippy::wildcard_imports)] + fn from_arrow_opt( + arrow_data: &dyn arrow2::array::Array, + ) -> DeserializationResult>> + where + Self: Sized, + { + 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(|| { + DeserializationError::datatype_mismatch( + DataType::FixedSizeList( + std::sync::Arc::new(Field { + name: "item".to_owned(), + data_type: DataType::Float64, + is_nullable: false, + metadata: [].into(), + }), + 2usize, + ), + arrow_data.data_type().clone(), + ) + }) + .with_context("rerun.components.Range1D#range")?; + if arrow_data.is_empty() { + Vec::new() + } else { + let offsets = (0..) + .step_by(2usize) + .zip((2usize..).step_by(2usize).take(arrow_data.len())); + let arrow_data_inner = { + let arrow_data_inner = &**arrow_data.values(); + arrow_data_inner + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::Float64, + arrow_data_inner.data_type().clone(), + ) + }) + .with_context("rerun.components.Range1D#range")? + .into_iter() + .map(|opt| opt.copied()) + .collect::>() + }; + arrow2::bitmap::utils::ZipValidity::new_with_validity( + offsets, + arrow_data.validity(), + ) + .map(|elem| { + elem.map(|(start, end)| { + debug_assert!(end - start == 2usize); + if end as usize > arrow_data_inner.len() { + return Err(DeserializationError::offset_slice_oob( + (start, end), + arrow_data_inner.len(), + )); + } + + #[allow(unsafe_code, clippy::undocumented_unsafe_blocks)] + let data = + unsafe { arrow_data_inner.get_unchecked(start as usize..end as usize) }; + let data = data.iter().cloned().map(Option::unwrap_or_default); + let arr = array_init::from_iter(data).unwrap(); + Ok(arr) + }) + .transpose() + }) + .collect::>>>()? + } + .into_iter() + } + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .map(|res| res.map(|v| Some(Self(v)))) + .collect::>>>() + .with_context("rerun.components.Range1D#range") + .with_context("rerun.components.Range1D")?) + } + + #[allow(clippy::wildcard_imports)] + #[inline] + fn from_arrow(arrow_data: &dyn arrow2::array::Array) -> DeserializationResult> + where + Self: Sized, + { + use ::re_types_core::{Loggable as _, ResultExt as _}; + use arrow2::{array::*, buffer::*, datatypes::*}; + if let Some(validity) = arrow_data.validity() { + if validity.unset_bits() != 0 { + return Err(DeserializationError::missing_data()); + } + } + Ok({ + let slice = { + let arrow_data = arrow_data + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::FixedSizeList( + std::sync::Arc::new(Field { + name: "item".to_owned(), + data_type: DataType::Float64, + is_nullable: false, + metadata: [].into(), + }), + 2usize, + ), + arrow_data.data_type().clone(), + ) + }) + .with_context("rerun.components.Range1D#range")?; + let arrow_data_inner = &**arrow_data.values(); + bytemuck::cast_slice::<_, [_; 2usize]>( + arrow_data_inner + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::Float64, + arrow_data_inner.data_type().clone(), + ) + }) + .with_context("rerun.components.Range1D#range")? + .values() + .as_slice(), + ) + }; + { + slice.iter().copied().map(|v| Self(v)).collect::>() + } + }) + } +} diff --git a/crates/re_viewer/src/blueprint/validation_gen/mod.rs b/crates/re_viewer/src/blueprint/validation_gen/mod.rs index 9fe08ae14804..cc2e87a9a9f0 100644 --- a/crates/re_viewer/src/blueprint/validation_gen/mod.rs +++ b/crates/re_viewer/src/blueprint/validation_gen/mod.rs @@ -10,6 +10,7 @@ pub use re_types::blueprint::components::Corner2D; pub use re_types::blueprint::components::EntitiesDeterminedByUser; pub use re_types::blueprint::components::IncludedContents; pub use re_types::blueprint::components::IncludedQueries; +pub use re_types::blueprint::components::LockRangeDuringZoom; pub use re_types::blueprint::components::RowShares; pub use re_types::blueprint::components::SpaceViewClass; pub use re_types::blueprint::components::SpaceViewOrigin; @@ -39,6 +40,7 @@ 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) diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index a64976a64c8c..f12d2fd1bfe6 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -32,6 +32,7 @@ Archetypes are bundles of components * [`Position2D`](components/position2d.md) * [`Position3D`](components/position3d.md) * [`Radius`](components/radius.md) +* [`Range1D`](components/range1d.md) * [`Resolution`](components/resolution.md) * [`Rotation3D`](components/rotation3d.md) * [`Scalar`](components/scalar.md) diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index 5711782a1a2e..18d845428ac4 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -26,6 +26,7 @@ pinhole_projection.md linguist-generated=true position2d.md linguist-generated=true position3d.md linguist-generated=true radius.md linguist-generated=true +range1d.md linguist-generated=true resolution.md linguist-generated=true rotation3d.md linguist-generated=true scalar.md linguist-generated=true diff --git a/docs/content/reference/types/components/range1d.md b/docs/content/reference/types/components/range1d.md new file mode 100644 index 000000000000..7de049e3be1f --- /dev/null +++ b/docs/content/reference/types/components/range1d.md @@ -0,0 +1,13 @@ +--- +title: "Range1D" +--- + +A 1D range, specifying a lower and upper bound. + + +## Links + * 🌊 [C++ API docs for `Range1D`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1Range1D.html?speculative-link) + * 🐍 [Python API docs for `Range1D`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.Range1D) + * 🦀 [Rust API docs for `Range1D`](https://docs.rs/rerun/latest/rerun/components/struct.Range1D.html?speculative-link) + + diff --git a/rerun_cpp/src/rerun/blueprint/archetypes.hpp b/rerun_cpp/src/rerun/blueprint/archetypes.hpp index 887ff45260c7..8c8237cbcb46 100644 --- a/rerun_cpp/src/rerun/blueprint/archetypes.hpp +++ b/rerun_cpp/src/rerun/blueprint/archetypes.hpp @@ -4,5 +4,6 @@ #include "blueprint/archetypes/container_blueprint.hpp" #include "blueprint/archetypes/plot_legend.hpp" +#include "blueprint/archetypes/scalar_axis.hpp" #include "blueprint/archetypes/space_view_blueprint.hpp" #include "blueprint/archetypes/viewport_blueprint.hpp" diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes b/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes index 219f203861cc..322e31cd7fff 100644 --- a/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes +++ b/rerun_cpp/src/rerun/blueprint/archetypes/.gitattributes @@ -5,6 +5,8 @@ container_blueprint.cpp linguist-generated=true container_blueprint.hpp linguist-generated=true plot_legend.cpp linguist-generated=true plot_legend.hpp linguist-generated=true +scalar_axis.cpp linguist-generated=true +scalar_axis.hpp linguist-generated=true space_view_blueprint.cpp linguist-generated=true space_view_blueprint.hpp linguist-generated=true viewport_blueprint.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/blueprint/archetypes/scalar_axis.cpp b/rerun_cpp/src/rerun/blueprint/archetypes/scalar_axis.cpp new file mode 100644 index 000000000000..3a7e2bc10bd6 --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/archetypes/scalar_axis.cpp @@ -0,0 +1,38 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/blueprint/archetypes/scalar_axis.fbs". + +#include "scalar_axis.hpp" + +#include "../../collection_adapter_builtins.hpp" + +namespace rerun::blueprint::archetypes {} + +namespace rerun { + + Result> AsComponents::serialize( + const blueprint::archetypes::ScalarAxis& archetype + ) { + using namespace blueprint::archetypes; + std::vector cells; + cells.reserve(3); + + if (archetype.range.has_value()) { + auto result = DataCell::from_loggable(archetype.range.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + if (archetype.lock_range_during_zoom.has_value()) { + auto result = DataCell::from_loggable(archetype.lock_range_during_zoom.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } + { + auto indicator = ScalarAxis::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/scalar_axis.hpp b/rerun_cpp/src/rerun/blueprint/archetypes/scalar_axis.hpp new file mode 100644 index 000000000000..ebe2cd90ef5a --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/archetypes/scalar_axis.hpp @@ -0,0 +1,80 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/blueprint/archetypes/scalar_axis.fbs". + +#pragma once + +#include "../../blueprint/components/lock_range_during_zoom.hpp" +#include "../../collection.hpp" +#include "../../compiler_utils.hpp" +#include "../../components/range1d.hpp" +#include "../../data_cell.hpp" +#include "../../indicator_component.hpp" +#include "../../result.hpp" + +#include +#include +#include +#include + +namespace rerun::blueprint::archetypes { + /// **Archetype**: Configuration for the scalar axis of a plot. + struct ScalarAxis { + /// The range of the axis. + /// + /// If unset, the range well be automatically determined based on the queried data. + std::optional range; + + /// Whether to lock the range of the axis during zoom. + std::optional lock_range_during_zoom; + + public: + static constexpr const char IndicatorComponentName[] = + "rerun.blueprint.components.ScalarAxisIndicator"; + + /// Indicator component, used to identify the archetype when converting to a list of components. + using IndicatorComponent = rerun::components::IndicatorComponent; + + public: + ScalarAxis() = default; + ScalarAxis(ScalarAxis&& other) = default; + + /// The range of the axis. + /// + /// If unset, the range well be automatically determined based on the queried data. + ScalarAxis with_range(rerun::components::Range1D _range) && { + range = std::move(_range); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Whether to lock the range of the axis during zoom. + ScalarAxis with_lock_range_during_zoom( + rerun::blueprint::components::LockRangeDuringZoom _lock_range_during_zoom + ) && { + lock_range_during_zoom = std::move(_lock_range_during_zoom); + // See: https://github.com/rerun-io/rerun/issues/4027 + RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) + } + + /// Returns the number of primary instances of this archetype. + size_t num_instances() const { + return 0; + } + }; + +} // 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::ScalarAxis& archetype + ); + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/blueprint/components.hpp b/rerun_cpp/src/rerun/blueprint/components.hpp index b66675901478..3cb9c70cdc95 100644 --- a/rerun_cpp/src/rerun/blueprint/components.hpp +++ b/rerun_cpp/src/rerun/blueprint/components.hpp @@ -14,6 +14,7 @@ #include "blueprint/components/included_contents.hpp" #include "blueprint/components/included_queries.hpp" #include "blueprint/components/included_space_views.hpp" +#include "blueprint/components/lock_range_during_zoom.hpp" #include "blueprint/components/panel_view.hpp" #include "blueprint/components/query_expressions.hpp" #include "blueprint/components/root_container.hpp" diff --git a/rerun_cpp/src/rerun/blueprint/components/.gitattributes b/rerun_cpp/src/rerun/blueprint/components/.gitattributes index 2ce17d034b19..4aae17f930f1 100644 --- a/rerun_cpp/src/rerun/blueprint/components/.gitattributes +++ b/rerun_cpp/src/rerun/blueprint/components/.gitattributes @@ -25,6 +25,8 @@ included_queries.cpp linguist-generated=true included_queries.hpp linguist-generated=true included_space_views.cpp linguist-generated=true included_space_views.hpp linguist-generated=true +lock_range_during_zoom.cpp linguist-generated=true +lock_range_during_zoom.hpp linguist-generated=true panel_view.cpp linguist-generated=true panel_view.hpp linguist-generated=true query_expressions.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/blueprint/components/lock_range_during_zoom.cpp b/rerun_cpp/src/rerun/blueprint/components/lock_range_during_zoom.cpp new file mode 100644 index 000000000000..83d7029f2a11 --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/components/lock_range_during_zoom.cpp @@ -0,0 +1,63 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/blueprint/components/lock_range_during_zoom.fbs". + +#include "lock_range_during_zoom.hpp" + +#include +#include + +namespace rerun::blueprint::components {} + +namespace rerun { + const std::shared_ptr& + Loggable::arrow_datatype() { + static const auto datatype = arrow::boolean(); + return datatype; + } + + rerun::Error Loggable::fill_arrow_array_builder( + arrow::BooleanBuilder* builder, const blueprint::components::LockRangeDuringZoom* 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." + ); + } + + static_assert(sizeof(*elements) == sizeof(elements->lock_range)); + ARROW_RETURN_NOT_OK(builder->AppendValues( + reinterpret_cast(&elements->lock_range), + static_cast(num_elements) + )); + + return Error::ok(); + } + + Result> + Loggable::to_arrow( + const blueprint::components::LockRangeDuringZoom* 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; + } +} // namespace rerun diff --git a/rerun_cpp/src/rerun/blueprint/components/lock_range_during_zoom.hpp b/rerun_cpp/src/rerun/blueprint/components/lock_range_during_zoom.hpp new file mode 100644 index 000000000000..42b598553f23 --- /dev/null +++ b/rerun_cpp/src/rerun/blueprint/components/lock_range_during_zoom.hpp @@ -0,0 +1,59 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/blueprint/components/lock_range_during_zoom.fbs". + +#pragma once + +#include "../../result.hpp" + +#include +#include + +namespace arrow { + class Array; + class BooleanBuilder; + class DataType; +} // namespace arrow + +namespace rerun::blueprint::components { + /// **Component**: Indicate whether the range should be locked when zooming in on the data. + /// + /// Default is `false`, i.e. zoom will change the visualized range. + struct LockRangeDuringZoom { + bool lock_range; + + public: + LockRangeDuringZoom() = default; + + LockRangeDuringZoom(bool lock_range_) : lock_range(lock_range_) {} + + LockRangeDuringZoom& operator=(bool lock_range_) { + lock_range = lock_range_; + return *this; + } + }; +} // namespace rerun::blueprint::components + +namespace rerun { + template + struct Loggable; + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.blueprint.components.LockRangeDuringZoom"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype(); + + /// Fills an arrow array builder with an array of this type. + static rerun::Error fill_arrow_array_builder( + arrow::BooleanBuilder* builder, + const blueprint::components::LockRangeDuringZoom* elements, size_t num_elements + ); + + /// Serializes an array of `rerun::blueprint:: components::LockRangeDuringZoom` into an arrow array. + static Result> to_arrow( + const blueprint::components::LockRangeDuringZoom* instances, size_t num_instances + ); + }; +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index 0c3fa26cfd6e..480b2095fb72 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -27,6 +27,7 @@ #include "components/position2d.hpp" #include "components/position3d.hpp" #include "components/radius.hpp" +#include "components/range1d.hpp" #include "components/resolution.hpp" #include "components/rotation3d.hpp" #include "components/scalar.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index ea279b36fe07..aa8ff1fc3a6a 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -51,6 +51,8 @@ position3d.cpp linguist-generated=true position3d.hpp linguist-generated=true radius.cpp linguist-generated=true radius.hpp linguist-generated=true +range1d.cpp linguist-generated=true +range1d.hpp linguist-generated=true resolution.cpp linguist-generated=true resolution.hpp linguist-generated=true rotation3d.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/range1d.cpp b/rerun_cpp/src/rerun/components/range1d.cpp new file mode 100644 index 000000000000..6f35a2a82715 --- /dev/null +++ b/rerun_cpp/src/rerun/components/range1d.cpp @@ -0,0 +1,64 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/components/range1d.fbs". + +#include "range1d.hpp" + +#include +#include + +namespace rerun::components {} + +namespace rerun { + const std::shared_ptr& Loggable::arrow_datatype() { + static const auto datatype = + arrow::fixed_size_list(arrow::field("item", arrow::float64(), false), 2); + return datatype; + } + + rerun::Error Loggable::fill_arrow_array_builder( + arrow::FixedSizeListBuilder* builder, const components::Range1D* 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." + ); + } + + auto value_builder = static_cast(builder->value_builder()); + + ARROW_RETURN_NOT_OK(builder->AppendValues(static_cast(num_elements))); + static_assert(sizeof(elements[0].range) == sizeof(elements[0])); + ARROW_RETURN_NOT_OK(value_builder->AppendValues( + elements[0].range.data(), + static_cast(num_elements * 2), + nullptr + )); + + return Error::ok(); + } + + Result> Loggable::to_arrow( + const components::Range1D* 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; + } +} // namespace rerun diff --git a/rerun_cpp/src/rerun/components/range1d.hpp b/rerun_cpp/src/rerun/components/range1d.hpp new file mode 100644 index 000000000000..17279a212530 --- /dev/null +++ b/rerun_cpp/src/rerun/components/range1d.hpp @@ -0,0 +1,58 @@ +// DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/cpp/mod.rs +// Based on "crates/re_types/definitions/rerun/components/range1d.fbs". + +#pragma once + +#include "../result.hpp" + +#include +#include +#include + +namespace arrow { + class Array; + class DataType; + class FixedSizeListBuilder; +} // namespace arrow + +namespace rerun::components { + /// **Component**: A 1D range, specifying a lower and upper bound. + struct Range1D { + std::array range; + + public: + Range1D() = default; + + Range1D(std::array range_) : range(range_) {} + + Range1D& operator=(std::array range_) { + range = range_; + return *this; + } + }; +} // namespace rerun::components + +namespace rerun { + template + struct Loggable; + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.Range1D"; + + /// Returns the arrow data type this type corresponds to. + static const std::shared_ptr& arrow_datatype(); + + /// Fills an arrow array builder with an array of this type. + static rerun::Error fill_arrow_array_builder( + arrow::FixedSizeListBuilder* builder, const components::Range1D* elements, + size_t num_elements + ); + + /// Serializes an array of `rerun::components::Range1D` into an arrow array. + static Result> to_arrow( + const components::Range1D* instances, size_t num_instances + ); + }; +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes index f711792bd60a..d7c6806ecae2 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/.gitattributes @@ -4,5 +4,6 @@ __init__.py linguist-generated=true container_blueprint.py linguist-generated=true plot_legend.py linguist-generated=true +scalar_axis.py linguist-generated=true space_view_blueprint.py linguist-generated=true viewport_blueprint.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 450186b7b0c0..efb810570f7f 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/__init__.py @@ -4,7 +4,8 @@ from .container_blueprint import ContainerBlueprint from .plot_legend import PlotLegend +from .scalar_axis import ScalarAxis from .space_view_blueprint import SpaceViewBlueprint from .viewport_blueprint import ViewportBlueprint -__all__ = ["ContainerBlueprint", "PlotLegend", "SpaceViewBlueprint", "ViewportBlueprint"] +__all__ = ["ContainerBlueprint", "PlotLegend", "ScalarAxis", "SpaceViewBlueprint", "ViewportBlueprint"] diff --git a/rerun_py/rerun_sdk/rerun/blueprint/archetypes/scalar_axis.py b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/scalar_axis.py new file mode 100644 index 000000000000..1cb8d5735730 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/blueprint/archetypes/scalar_axis.py @@ -0,0 +1,83 @@ +# DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/python.rs +# Based on "crates/re_types/definitions/rerun/blueprint/archetypes/scalar_axis.fbs". + +# You can extend this class by creating a "ScalarAxisExt" class in "scalar_axis_ext.py". + +from __future__ import annotations + +from typing import Any + +from attrs import define, field + +from ... import blueprint, components +from ..._baseclasses import Archetype +from ...error_utils import catch_and_log_exceptions + +__all__ = ["ScalarAxis"] + + +@define(str=False, repr=False, init=False) +class ScalarAxis(Archetype): + """**Archetype**: Configuration for the scalar axis of a plot.""" + + def __init__( + self: Any, + *, + range: components.Range1DLike | None = None, + lock_range_during_zoom: blueprint.components.LockRangeDuringZoomLike | None = None, + ): + """ + Create a new instance of the ScalarAxis archetype. + + Parameters + ---------- + range: + The range of the axis. + + If unset, the range well be automatically determined based on the queried data. + lock_range_during_zoom: + Whether to lock the range of the axis during zoom. + """ + + # You can define your own __init__ function as a member of ScalarAxisExt in scalar_axis_ext.py + with catch_and_log_exceptions(context=self.__class__.__name__): + self.__attrs_init__(range=range, lock_range_during_zoom=lock_range_during_zoom) + return + self.__attrs_clear__() + + def __attrs_clear__(self) -> None: + """Convenience method for calling `__attrs_init__` with all `None`s.""" + self.__attrs_init__( + range=None, # type: ignore[arg-type] + lock_range_during_zoom=None, # type: ignore[arg-type] + ) + + @classmethod + def _clear(cls) -> ScalarAxis: + """Produce an empty ScalarAxis, bypassing `__init__`.""" + inst = cls.__new__(cls) + inst.__attrs_clear__() + return inst + + range: components.Range1DBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.Range1DBatch._optional, # type: ignore[misc] + ) + # The range of the axis. + # + # If unset, the range well be automatically determined based on the queried data. + # + # (Docstring intentionally commented out to hide this field from the docs) + + lock_range_during_zoom: blueprint.components.LockRangeDuringZoomBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=blueprint.components.LockRangeDuringZoomBatch._optional, # type: ignore[misc] + ) + # Whether to lock the range of the axis during zoom. + # + # (Docstring intentionally commented out to hide this field from the docs) + + __str__ = Archetype.__str__ + __repr__ = Archetype.__repr__ diff --git a/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes b/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes index 85760acec02b..ae90fcffcf4c 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/blueprint/components/.gitattributes @@ -14,6 +14,7 @@ grid_columns.py linguist-generated=true included_contents.py linguist-generated=true included_queries.py linguist-generated=true included_space_views.py linguist-generated=true +lock_range_during_zoom.py linguist-generated=true panel_view.py linguist-generated=true query_expressions.py linguist-generated=true root_container.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 8e0c366496e7..07dfcb6e9cbe 100644 --- a/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/blueprint/components/__init__.py @@ -56,6 +56,13 @@ IncludedSpaceViewsLike, IncludedSpaceViewsType, ) +from .lock_range_during_zoom import ( + LockRangeDuringZoom, + LockRangeDuringZoomArrayLike, + LockRangeDuringZoomBatch, + LockRangeDuringZoomLike, + LockRangeDuringZoomType, +) from .panel_view import PanelView, PanelViewArrayLike, PanelViewBatch, PanelViewLike, PanelViewType from .query_expressions import ( QueryExpressions, @@ -143,6 +150,11 @@ "IncludedSpaceViewsBatch", "IncludedSpaceViewsLike", "IncludedSpaceViewsType", + "LockRangeDuringZoom", + "LockRangeDuringZoomArrayLike", + "LockRangeDuringZoomBatch", + "LockRangeDuringZoomLike", + "LockRangeDuringZoomType", "PanelView", "PanelViewArrayLike", "PanelViewBatch", diff --git a/rerun_py/rerun_sdk/rerun/blueprint/components/lock_range_during_zoom.py b/rerun_py/rerun_sdk/rerun/blueprint/components/lock_range_during_zoom.py new file mode 100644 index 000000000000..136e42d2e54c --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/blueprint/components/lock_range_during_zoom.py @@ -0,0 +1,60 @@ +# DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/python.rs +# Based on "crates/re_types/definitions/rerun/blueprint/components/lock_range_during_zoom.fbs". + +# You can extend this class by creating a "LockRangeDuringZoomExt" class in "lock_range_during_zoom_ext.py". + +from __future__ import annotations + +from typing import Any, Sequence, Union + +import pyarrow as pa +from attrs import define, field + +from ..._baseclasses import BaseBatch, BaseExtensionType, ComponentBatchMixin + +__all__ = [ + "LockRangeDuringZoom", + "LockRangeDuringZoomArrayLike", + "LockRangeDuringZoomBatch", + "LockRangeDuringZoomLike", + "LockRangeDuringZoomType", +] + + +@define(init=False) +class LockRangeDuringZoom: + """ + **Component**: Indicate whether the range should be locked when zooming in on the data. + + Default is `false`, i.e. zoom will change the visualized range. + """ + + def __init__(self: Any, lock_range: LockRangeDuringZoomLike): + """Create a new instance of the LockRangeDuringZoom component.""" + + # You can define your own __init__ function as a member of LockRangeDuringZoomExt in lock_range_during_zoom_ext.py + self.__attrs_init__(lock_range=lock_range) + + lock_range: bool = field(converter=bool) + + +LockRangeDuringZoomLike = LockRangeDuringZoom +LockRangeDuringZoomArrayLike = Union[ + LockRangeDuringZoom, + Sequence[LockRangeDuringZoomLike], +] + + +class LockRangeDuringZoomType(BaseExtensionType): + _TYPE_NAME: str = "rerun.blueprint.components.LockRangeDuringZoom" + + def __init__(self) -> None: + pa.ExtensionType.__init__(self, pa.bool_(), self._TYPE_NAME) + + +class LockRangeDuringZoomBatch(BaseBatch[LockRangeDuringZoomArrayLike], ComponentBatchMixin): + _ARROW_TYPE = LockRangeDuringZoomType() + + @staticmethod + def _native_to_pa_array(data: LockRangeDuringZoomArrayLike, data_type: pa.DataType) -> pa.Array: + raise NotImplementedError # You need to implement native_to_pa_array_override in lock_range_during_zoom_ext.py diff --git a/rerun_py/rerun_sdk/rerun/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index 5a77b3cb8dce..b7d5749f3dd7 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -27,6 +27,7 @@ pinhole_projection.py linguist-generated=true position2d.py linguist-generated=true position3d.py linguist-generated=true radius.py linguist-generated=true +range1d.py linguist-generated=true resolution.py linguist-generated=true rotation3d.py linguist-generated=true scalar.py linguist-generated=true diff --git a/rerun_py/rerun_sdk/rerun/components/__init__.py b/rerun_py/rerun_sdk/rerun/components/__init__.py index 40f61823102b..28641560b2e8 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -45,6 +45,7 @@ from .position2d import Position2D, Position2DBatch, Position2DType from .position3d import Position3D, Position3DBatch, Position3DType from .radius import Radius, RadiusArrayLike, RadiusBatch, RadiusLike, RadiusType +from .range1d import Range1D, Range1DArrayLike, Range1DBatch, Range1DLike, Range1DType from .resolution import Resolution, ResolutionBatch, ResolutionType from .rotation3d import Rotation3D, Rotation3DBatch, Rotation3DType from .scalar import Scalar, ScalarArrayLike, ScalarBatch, ScalarLike, ScalarType @@ -178,6 +179,11 @@ "RadiusBatch", "RadiusLike", "RadiusType", + "Range1D", + "Range1DArrayLike", + "Range1DBatch", + "Range1DLike", + "Range1DType", "Resolution", "ResolutionBatch", "ResolutionType", diff --git a/rerun_py/rerun_sdk/rerun/components/range1d.py b/rerun_py/rerun_sdk/rerun/components/range1d.py new file mode 100644 index 000000000000..41d91e6a6a1f --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/range1d.py @@ -0,0 +1,61 @@ +# DO NOT EDIT! This file was auto-generated by crates/re_types_builder/src/codegen/python.rs +# Based on "crates/re_types/definitions/rerun/components/range1d.fbs". + +# You can extend this class by creating a "Range1DExt" class in "range1d_ext.py". + +from __future__ import annotations + +from typing import Any, Sequence, Union + +import numpy as np +import numpy.typing as npt +import pyarrow as pa +from attrs import define, field + +from .._baseclasses import BaseBatch, BaseExtensionType, ComponentBatchMixin +from .._converters import ( + to_np_float64, +) + +__all__ = ["Range1D", "Range1DArrayLike", "Range1DBatch", "Range1DLike", "Range1DType"] + + +@define(init=False) +class Range1D: + """**Component**: A 1D range, specifying a lower and upper bound.""" + + def __init__(self: Any, range: Range1DLike): + """Create a new instance of the Range1D component.""" + + # You can define your own __init__ function as a member of Range1DExt in range1d_ext.py + self.__attrs_init__(range=range) + + range: npt.NDArray[np.float64] = field(converter=to_np_float64) + + def __array__(self, dtype: npt.DTypeLike = None) -> npt.NDArray[Any]: + # You can define your own __array__ function as a member of Range1DExt in range1d_ext.py + return np.asarray(self.range, dtype=dtype) + + +Range1DLike = Range1D +Range1DArrayLike = Union[ + Range1D, + Sequence[Range1DLike], +] + + +class Range1DType(BaseExtensionType): + _TYPE_NAME: str = "rerun.components.Range1D" + + def __init__(self) -> None: + pa.ExtensionType.__init__( + self, pa.list_(pa.field("item", pa.float64(), nullable=False, metadata={}), 2), self._TYPE_NAME + ) + + +class Range1DBatch(BaseBatch[Range1DArrayLike], ComponentBatchMixin): + _ARROW_TYPE = Range1DType() + + @staticmethod + def _native_to_pa_array(data: Range1DArrayLike, data_type: pa.DataType) -> pa.Array: + raise NotImplementedError # You need to implement native_to_pa_array_override in range1d_ext.py