diff --git a/crates/re_data_ui/src/editors.rs b/crates/re_data_ui/src/editors.rs index 2f42b02f6e1f..818a8ddea94d 100644 --- a/crates/re_data_ui/src/editors.rs +++ b/crates/re_data_ui/src/editors.rs @@ -5,11 +5,13 @@ use re_data_store::{DataStore, LatestAtQuery}; use re_log_types::EntityPath; use re_query::ComponentWithInstances; use re_types::{ - components::{Color, Radius, ScalarScattering, Text}, + components::{Color, MarkerShape, Radius, ScalarScattering, Text}, Component, Loggable, }; use re_viewer_context::{UiVerbosity, ViewerContext}; +// ---- + #[allow(clippy::too_many_arguments)] fn edit_color_ui( ctx: &ViewerContext<'_>, @@ -55,6 +57,8 @@ fn default_color( Color::from_rgb(255, 255, 255) } +// ---- + #[allow(clippy::too_many_arguments)] fn edit_text_ui( ctx: &ViewerContext<'_>, @@ -94,6 +98,8 @@ fn default_text( Text::from(entity_path.to_string()) } +// ---- + #[allow(clippy::too_many_arguments)] fn edit_scatter_ui( ctx: &ViewerContext<'_>, @@ -141,6 +147,8 @@ fn default_scatter( ScalarScattering::from(false) } +// ---- + #[allow(clippy::too_many_arguments)] fn edit_radius_ui( ctx: &ViewerContext<'_>, @@ -186,6 +194,55 @@ fn default_radius( Radius::from(1.0) } +// ---- + +#[allow(clippy::too_many_arguments)] +fn edit_marker_shape_ui( + ctx: &ViewerContext<'_>, + ui: &mut egui::Ui, + _verbosity: UiVerbosity, + query: &LatestAtQuery, + store: &DataStore, + entity_path: &EntityPath, + override_path: &EntityPath, + component: &ComponentWithInstances, + instance_key: &re_types::components::InstanceKey, +) { + let current_marker = component + .lookup::(instance_key) + .ok() + .unwrap_or_else(|| default_marker_shape(ctx, query, store, entity_path)); + + let mut edit_marker = current_marker; + + let marker_text = edit_marker.as_str(); + + egui::ComboBox::from_id_source("marker_shape") + .selected_text(marker_text) + .show_ui(ui, |ui| { + ui.style_mut().wrap = Some(false); + for marker in MarkerShape::all_markers() { + ui.selectable_value(&mut edit_marker, marker, marker.as_str()); + } + }); + + if edit_marker != current_marker { + ctx.save_blueprint_component(override_path, edit_marker); + } +} + +#[inline] +fn default_marker_shape( + _ctx: &ViewerContext<'_>, + _query: &LatestAtQuery, + _store: &DataStore, + _entity_path: &EntityPath, +) -> MarkerShape { + MarkerShape::default() +} + +// ---- + fn register_editor<'a, C: Component + Loggable + 'static>( registry: &mut re_viewer_context::ComponentUiRegistry, default: fn(&ViewerContext<'_>, &LatestAtQuery, &DataStore, &EntityPath) -> C, @@ -218,4 +275,5 @@ pub fn register_editors(registry: &mut re_viewer_context::ComponentUiRegistry) { register_editor::(registry, default_text, edit_text_ui); register_editor::(registry, default_scatter, edit_scatter_ui); register_editor::(registry, default_radius, edit_radius_ui); + register_editor::(registry, default_marker_shape, edit_marker_shape_ui); } diff --git a/crates/re_space_view_time_series/src/lib.rs b/crates/re_space_view_time_series/src/lib.rs index f8cf87331de3..de3ba4e89f0b 100644 --- a/crates/re_space_view_time_series/src/lib.rs +++ b/crates/re_space_view_time_series/src/lib.rs @@ -11,6 +11,7 @@ mod util; mod visualizer_system; use re_log_types::EntityPath; +use re_types::components::MarkerShape; use re_viewer_context::external::re_entity_db::TimeSeriesAggregator; pub use space_view_class::TimeSeriesSpaceView; @@ -36,6 +37,11 @@ pub struct PlotPointAttrs { pub kind: PlotSeriesKind, } +#[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] +pub struct ScatterAttrs { + pub marker: MarkerShape, +} + impl PartialEq for PlotPointAttrs { fn eq(&self, rhs: &Self) -> bool { let Self { @@ -63,7 +69,7 @@ struct PlotPoint { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum PlotSeriesKind { Continuous, - Scatter, + Scatter(ScatterAttrs), Clear, } diff --git a/crates/re_space_view_time_series/src/point_visualizer_system.rs b/crates/re_space_view_time_series/src/point_visualizer_system.rs index 6dd6bbb0be2c..15024fb920ee 100644 --- a/crates/re_space_view_time_series/src/point_visualizer_system.rs +++ b/crates/re_space_view_time_series/src/point_visualizer_system.rs @@ -1,5 +1,6 @@ use re_query_cache::{MaybeCachedComponentData, QueryError}; use re_types::archetypes; +use re_types::components::MarkerShape; use re_types::{ archetypes::SeriesPoint, components::{Color, Radius, Scalar, Text}, @@ -14,6 +15,7 @@ use crate::overrides::initial_override_color; use crate::util::{ determine_plot_bounds_and_time_per_pixel, determine_time_range, points_to_series, }; +use crate::ScatterAttrs; use crate::{overrides::lookup_override, PlotPoint, PlotPointAttrs, PlotSeries, PlotSeriesKind}; /// The system for rendering [`SeriesPoint`] archetypes. @@ -29,6 +31,10 @@ impl IdentifiedViewSystem for SeriesPointSystem { } } +// We use a larger default radius for scatter plots so the marker is +// visible. +const DEFAULT_RADIUS: f32 = 3.0; + impl VisualizerSystem for SeriesPointSystem { fn visualizer_query_info(&self) -> VisualizerQueryInfo { let mut query_info = VisualizerQueryInfo::from_archetype::(); @@ -37,6 +43,8 @@ impl VisualizerSystem for SeriesPointSystem { .map(ToOwned::to_owned) .collect::(); query_info.queried.append(&mut series_point_queried); + // TODO(jleibs): Use StrokeWidth instead + query_info.queried.insert(Radius::name()); query_info } @@ -76,6 +84,8 @@ impl VisualizerSystem for SeriesPointSystem { ) -> Option { if *component == Color::name() { Some([initial_override_color(entity_path)].into()) + } else if *component == Radius::name() { + Some([Radius(DEFAULT_RADIUS)].into()) } else { None } @@ -125,16 +135,18 @@ impl SeriesPointSystem { let override_radius = lookup_override::(data_result, ctx).map(|r| r.0); + let override_marker = lookup_override::(data_result, ctx); + let query = re_data_store::RangeQuery::new(query.timeline, time_range); // TODO(jleibs): need to do a "joined" archetype query query_caches - .query_archetype_pov1_comp2::( + .query_archetype_pov1_comp3::( ctx.app_options.experimental_primary_caching_range, store, &query.clone().into(), &data_result.entity_path, - |((time, _row_id), _, scalars, colors, labels)| { + |((time, _row_id), _, scalars, colors, markers, labels)| { let Some(time) = time else { return; }; // scalars cannot be timeless @@ -154,12 +166,16 @@ impl SeriesPointSystem { return; } - for (scalar, color, label) in itertools::izip!( + for (scalar, color, marker, label) in itertools::izip!( scalars.iter(), MaybeCachedComponentData::iter_or_repeat_opt( &colors, scalars.len() ), + MaybeCachedComponentData::iter_or_repeat_opt( + &markers, + scalars.len() + ), //MaybeCachedComponentData::iter_or_repeat_opt(&radii, scalars.len()), MaybeCachedComponentData::iter_or_repeat_opt( &labels, @@ -178,7 +194,9 @@ impl SeriesPointSystem { let radius = override_radius .unwrap_or_else(|| radius.map_or(DEFAULT_RADIUS, |r| r.0)); - const DEFAULT_RADIUS: f32 = 0.75; + let marker = override_marker.unwrap_or(marker.unwrap_or_default()); + + points.push(PlotPoint { time: time.as_i64(), @@ -187,7 +205,7 @@ impl SeriesPointSystem { label, color, radius, - kind: PlotSeriesKind::Scatter, + kind: PlotSeriesKind::Scatter(ScatterAttrs{marker}), }, }); } 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 23d38b8d9cfe..64a09ca38f89 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 @@ -406,30 +406,31 @@ It can greatly improve performance (and readability) in such situations as it pr time_ctrl_write.pause(); } - for line in all_plot_series { - let points = line + for series in all_plot_series { + let points = series .points .iter() .map(|p| [(p.0 - time_offset) as _, p.1]) .collect::>(); - let color = line.color; - let id = egui::Id::new(line.entity_path.hash()); - plot_item_id_to_entity_path.insert(id, line.entity_path.clone()); + let color = series.color; + let id = egui::Id::new(series.entity_path.hash()); + plot_item_id_to_entity_path.insert(id, series.entity_path.clone()); - match line.kind { + match series.kind { PlotSeriesKind::Continuous => plot_ui.line( Line::new(points) - .name(&line.label) + .name(&series.label) .color(color) - .width(line.width) + .width(series.width) .id(id), ), - PlotSeriesKind::Scatter => plot_ui.points( + PlotSeriesKind::Scatter(scatter_attrs) => plot_ui.points( Points::new(points) - .name(&line.label) + .name(&series.label) .color(color) - .radius(line.width) + .radius(series.width) + .shape(scatter_attrs.marker.into()) .id(id), ), // Break up the chart. At some point we might want something fancier. diff --git a/crates/re_space_view_time_series/src/util.rs b/crates/re_space_view_time_series/src/util.rs index d61eed33dd7c..d6b710a2cb1c 100644 --- a/crates/re_space_view_time_series/src/util.rs +++ b/crates/re_space_view_time_series/src/util.rs @@ -3,7 +3,7 @@ use re_viewer_context::{external::re_entity_db::TimeSeriesAggregator, ViewQuery, use crate::{ aggregation::{AverageAggregator, MinMaxAggregator}, - PlotPoint, PlotSeries, PlotSeriesKind, + PlotPoint, PlotSeries, PlotSeriesKind, ScatterAttrs, }; /// Find the plot bounds and the per-ui-point delta from egui. @@ -101,7 +101,7 @@ pub fn points_to_series( // Can't draw a single point as a continuous line, so fall back on scatter let mut kind = points[0].attrs.kind; if kind == PlotSeriesKind::Continuous { - kind = PlotSeriesKind::Scatter; + kind = PlotSeriesKind::Scatter(ScatterAttrs::default()); } all_series.push(PlotSeries { diff --git a/crates/re_space_view_time_series/src/visualizer_system.rs b/crates/re_space_view_time_series/src/visualizer_system.rs index c8db567ff928..9c37ee183895 100644 --- a/crates/re_space_view_time_series/src/visualizer_system.rs +++ b/crates/re_space_view_time_series/src/visualizer_system.rs @@ -12,7 +12,7 @@ use re_viewer_context::{ use crate::{ overrides::{initial_override_color, lookup_override}, util::{determine_plot_bounds_and_time_per_pixel, determine_time_range, points_to_series}, - PlotPoint, PlotPointAttrs, PlotSeries, PlotSeriesKind, + PlotPoint, PlotPointAttrs, PlotSeries, PlotSeriesKind, ScatterAttrs, }; /// The legacy system for rendering [`TimeSeriesScalar`] archetypes. @@ -174,8 +174,8 @@ impl LegacyTimeSeriesSystem { let radius = override_radius .unwrap_or_else(|| radius.map_or(DEFAULT_RADIUS, |r| r.0)); - let kind = if scattered { - PlotSeriesKind::Scatter + let kind= if scattered { + PlotSeriesKind::Scatter(ScatterAttrs::default()) } else { PlotSeriesKind::Continuous }; diff --git a/crates/re_types/definitions/rerun/archetypes/series_point.fbs b/crates/re_types/definitions/rerun/archetypes/series_point.fbs index 0d826f6332ec..92da08e59bd9 100644 --- a/crates/re_types/definitions/rerun/archetypes/series_point.fbs +++ b/crates/re_types/definitions/rerun/archetypes/series_point.fbs @@ -18,4 +18,7 @@ table SeriesPoint ( /// Color for the corresponding series. // TODO(jleibs): This should be batch if we make a batch Scalars loggable. color: rerun.components.Color ("attr.rerun.component_optional", nullable, order: 1000); + + /// What shape to use to represent the point + marker: rerun.components.MarkerShape ("attr.rerun.component_optional", nullable, order: 2000); } diff --git a/crates/re_types/definitions/rerun/components.fbs b/crates/re_types/definitions/rerun/components.fbs index a9f3e3386633..e9646b7b6813 100644 --- a/crates/re_types/definitions/rerun/components.fbs +++ b/crates/re_types/definitions/rerun/components.fbs @@ -12,6 +12,7 @@ include "./components/instance_key.fbs"; include "./components/keypoint_id.fbs"; include "./components/line_strip2d.fbs"; include "./components/line_strip3d.fbs"; +include "./components/marker_shape.fbs"; include "./components/material.fbs"; include "./components/media_type.fbs"; include "./components/mesh_properties.fbs"; diff --git a/crates/re_types/definitions/rerun/components/marker_shape.fbs b/crates/re_types/definitions/rerun/components/marker_shape.fbs new file mode 100644 index 000000000000..a68a2dbb1128 --- /dev/null +++ b/crates/re_types/definitions/rerun/components/marker_shape.fbs @@ -0,0 +1,33 @@ +include "arrow/attributes.fbs"; +include "python/attributes.fbs"; +include "rust/attributes.fbs"; + +include "rerun/datatypes.fbs"; +include "rerun/attributes.fbs"; + +namespace rerun.components; + +// TODO(#3384) +/* +enum MarkerShape: byte { + Circle = 1, + Diamond = 2, + Square = 3, + Cross = 4, + Plus = 5, + Up = 6, + Down = 7, + Left = 8, + Right = 9, + Asterisk = 10, +} +*/ + +/// Shape of a marker. +struct MarkerShape ( + "attr.docs.unreleased", + "attr.rust.derive": "PartialEq, Eq, PartialOrd, Copy", + "attr.rust.repr": "transparent" +) { + shape: ubyte (order: 100); +} diff --git a/crates/re_types/src/archetypes/series_point.rs b/crates/re_types/src/archetypes/series_point.rs index a9421d851f2d..f95212857dd2 100644 --- a/crates/re_types/src/archetypes/series_point.rs +++ b/crates/re_types/src/archetypes/series_point.rs @@ -26,17 +26,21 @@ use ::re_types_core::{DeserializationError, DeserializationResult}; pub struct SeriesPoint { /// Color for the corresponding series. pub color: Option, + + /// What shape to use to represent the point + pub marker: Option, } impl ::re_types_core::SizeBytes for SeriesPoint { #[inline] fn heap_size_bytes(&self) -> u64 { - self.color.heap_size_bytes() + self.color.heap_size_bytes() + self.marker.heap_size_bytes() } #[inline] fn is_pod() -> bool { >::is_pod() + && >::is_pod() } } @@ -46,25 +50,27 @@ static REQUIRED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 0usize]> = static RECOMMENDED_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 1usize]> = once_cell::sync::Lazy::new(|| ["rerun.components.SeriesPointIndicator".into()]); -static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 2usize]> = +static OPTIONAL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.Color".into(), "rerun.components.InstanceKey".into(), + "rerun.components.MarkerShape".into(), ] }); -static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 3usize]> = +static ALL_COMPONENTS: once_cell::sync::Lazy<[ComponentName; 4usize]> = once_cell::sync::Lazy::new(|| { [ "rerun.components.SeriesPointIndicator".into(), "rerun.components.Color".into(), "rerun.components.InstanceKey".into(), + "rerun.components.MarkerShape".into(), ] }); impl SeriesPoint { - pub const NUM_COMPONENTS: usize = 3usize; + pub const NUM_COMPONENTS: usize = 4usize; } /// Indicator component for the [`SeriesPoint`] [`::re_types_core::Archetype`] @@ -123,7 +129,16 @@ impl ::re_types_core::Archetype for SeriesPoint { } else { None }; - Ok(Self { color }) + let marker = if let Some(array) = arrays_by_name.get("rerun.components.MarkerShape") { + ::from_arrow_opt(&**array) + .with_context("rerun.archetypes.SeriesPoint#marker")? + .into_iter() + .next() + .flatten() + } else { + None + }; + Ok(Self { color, marker }) } } @@ -136,6 +151,9 @@ impl ::re_types_core::AsComponents for SeriesPoint { self.color .as_ref() .map(|comp| (comp as &dyn ComponentBatch).into()), + self.marker + .as_ref() + .map(|comp| (comp as &dyn ComponentBatch).into()), ] .into_iter() .flatten() @@ -150,7 +168,10 @@ impl ::re_types_core::AsComponents for SeriesPoint { impl SeriesPoint { pub fn new() -> Self { - Self { color: None } + Self { + color: None, + marker: None, + } } #[inline] @@ -158,4 +179,10 @@ impl SeriesPoint { self.color = Some(color.into()); self } + + #[inline] + pub fn with_marker(mut self, marker: impl Into) -> Self { + self.marker = Some(marker.into()); + self + } } diff --git a/crates/re_types/src/components/.gitattributes b/crates/re_types/src/components/.gitattributes index 03fe0fe129f6..c46f65b82413 100644 --- a/crates/re_types/src/components/.gitattributes +++ b/crates/re_types/src/components/.gitattributes @@ -13,6 +13,7 @@ half_sizes3d.rs linguist-generated=true keypoint_id.rs linguist-generated=true line_strip2d.rs linguist-generated=true line_strip3d.rs linguist-generated=true +marker_shape.rs linguist-generated=true material.rs linguist-generated=true media_type.rs linguist-generated=true mesh_properties.rs linguist-generated=true diff --git a/crates/re_types/src/components/marker_shape.rs b/crates/re_types/src/components/marker_shape.rs new file mode 100644 index 000000000000..a29a13777821 --- /dev/null +++ b/crates/re_types/src/components/marker_shape.rs @@ -0,0 +1,165 @@ +// 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/marker_shape.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**: Shape of a marker. +#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Copy)] +#[repr(transparent)] +pub struct MarkerShape(pub u8); + +impl ::re_types_core::SizeBytes for MarkerShape { + #[inline] + fn heap_size_bytes(&self) -> u64 { + self.0.heap_size_bytes() + } + + #[inline] + fn is_pod() -> bool { + ::is_pod() + } +} + +impl From for MarkerShape { + #[inline] + fn from(shape: u8) -> Self { + Self(shape) + } +} + +impl From for u8 { + #[inline] + fn from(value: MarkerShape) -> Self { + value.0 + } +} + +::re_types_core::macros::impl_into_cow!(MarkerShape); + +impl ::re_types_core::Loggable for MarkerShape { + type Name = ::re_types_core::ComponentName; + + #[inline] + fn name() -> Self::Name { + "rerun.components.MarkerShape".into() + } + + #[allow(clippy::wildcard_imports)] + #[inline] + fn arrow_datatype() -> arrow2::datatypes::DataType { + use arrow2::datatypes::*; + DataType::UInt8 + } + + #[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()) + }; + PrimitiveArray::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::UInt8, + arrow_data.data_type().clone(), + ) + }) + .with_context("rerun.components.MarkerShape#shape")? + .into_iter() + .map(|opt| opt.copied()) + .map(|v| v.ok_or_else(DeserializationError::missing_data)) + .map(|res| res.map(|v| Some(Self(v)))) + .collect::>>>() + .with_context("rerun.components.MarkerShape#shape") + .with_context("rerun.components.MarkerShape")?) + } + + #[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 = arrow_data + .as_any() + .downcast_ref::() + .ok_or_else(|| { + DeserializationError::datatype_mismatch( + DataType::UInt8, + arrow_data.data_type().clone(), + ) + }) + .with_context("rerun.components.MarkerShape#shape")? + .values() + .as_slice(); + { + slice.iter().copied().map(|v| Self(v)).collect::>() + } + }) + } +} diff --git a/crates/re_types/src/components/marker_shape_ext.rs b/crates/re_types/src/components/marker_shape_ext.rs new file mode 100644 index 000000000000..33959c8b22ed --- /dev/null +++ b/crates/re_types/src/components/marker_shape_ext.rs @@ -0,0 +1,83 @@ +use super::MarkerShape; + +#[cfg(feature = "egui_plot")] +fn egui_to_u8(marker: egui_plot::MarkerShape) -> u8 { + match marker { + egui_plot::MarkerShape::Circle => 1, + egui_plot::MarkerShape::Diamond => 2, + egui_plot::MarkerShape::Square => 3, + egui_plot::MarkerShape::Cross => 4, + egui_plot::MarkerShape::Plus => 5, + egui_plot::MarkerShape::Up => 6, + egui_plot::MarkerShape::Down => 7, + egui_plot::MarkerShape::Left => 8, + egui_plot::MarkerShape::Right => 9, + egui_plot::MarkerShape::Asterisk => 10, + } +} + +#[cfg(feature = "egui_plot")] +fn u8_to_egui(marker: u8) -> egui_plot::MarkerShape { + match marker { + 1 => egui_plot::MarkerShape::Circle, + 2 => egui_plot::MarkerShape::Diamond, + 3 => egui_plot::MarkerShape::Square, + 4 => egui_plot::MarkerShape::Cross, + 5 => egui_plot::MarkerShape::Plus, + 6 => egui_plot::MarkerShape::Up, + 7 => egui_plot::MarkerShape::Down, + 8 => egui_plot::MarkerShape::Left, + 9 => egui_plot::MarkerShape::Right, + 10 => egui_plot::MarkerShape::Asterisk, + _ => { + re_log::error_once!("Could not interpret {marker} as egui_plot::MarkerShape."); + // Fall back on Circle + egui_plot::MarkerShape::Circle + } + } +} + +#[cfg(feature = "egui_plot")] +impl From for MarkerShape { + #[inline] + fn from(shape: egui_plot::MarkerShape) -> Self { + Self(egui_to_u8(shape)) + } +} + +#[cfg(feature = "egui_plot")] +impl From for egui_plot::MarkerShape { + #[inline] + fn from(value: MarkerShape) -> Self { + u8_to_egui(value.0) + } +} + +impl Default for MarkerShape { + #[inline] + fn default() -> Self { + Self(1) + } +} + +impl MarkerShape { + pub fn as_str(&self) -> &'static str { + match self.0 { + 1 => "Circle", + 2 => "Diamond", + 3 => "Square", + 4 => "Cross", + 5 => "Plus", + 6 => "Up", + 7 => "Down", + 8 => "Left", + 9 => "Right", + 10 => "Asterisk", + _ => "Unknown", + } + } + + pub fn all_markers() -> Vec { + (1..=10).map(MarkerShape).collect() + } +} diff --git a/crates/re_types/src/components/mod.rs b/crates/re_types/src/components/mod.rs index 3ca7004b8a4a..f4bdcd1ab97a 100644 --- a/crates/re_types/src/components/mod.rs +++ b/crates/re_types/src/components/mod.rs @@ -23,6 +23,8 @@ mod line_strip2d; mod line_strip2d_ext; mod line_strip3d; mod line_strip3d_ext; +mod marker_shape; +mod marker_shape_ext; mod material; mod material_ext; mod media_type; @@ -72,6 +74,7 @@ pub use self::half_sizes3d::HalfSizes3D; pub use self::keypoint_id::KeypointId; pub use self::line_strip2d::LineStrip2D; pub use self::line_strip3d::LineStrip3D; +pub use self::marker_shape::MarkerShape; pub use self::material::Material; pub use self::media_type::MediaType; pub use self::mesh_properties::MeshProperties; diff --git a/docs/content/reference/types/archetypes/series_point.md b/docs/content/reference/types/archetypes/series_point.md index f9f1f94774e8..81235c9cd707 100644 --- a/docs/content/reference/types/archetypes/series_point.md +++ b/docs/content/reference/types/archetypes/series_point.md @@ -6,7 +6,7 @@ Define the style properties for a point series in a chart. ## Components -**Optional**: [`Color`](../components/color.md) +**Optional**: [`Color`](../components/color.md), [`MarkerShape`](../components/marker_shape.md) ## Links * 🌊 [C++ API docs for `SeriesPoint`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1archetypes_1_1SeriesPoint.html?speculative-link) diff --git a/docs/content/reference/types/components.md b/docs/content/reference/types/components.md index 97341c6914c2..c1a61b405f5f 100644 --- a/docs/content/reference/types/components.md +++ b/docs/content/reference/types/components.md @@ -21,6 +21,7 @@ Archetypes are bundles of components * [`KeypointId`](components/keypoint_id.md) * [`LineStrip2D`](components/line_strip2d.md) * [`LineStrip3D`](components/line_strip3d.md) +* [`MarkerShape`](components/marker_shape.md) * [`Material`](components/material.md) * [`MediaType`](components/media_type.md) * [`MeshProperties`](components/mesh_properties.md) diff --git a/docs/content/reference/types/components/.gitattributes b/docs/content/reference/types/components/.gitattributes index f0ab345e4f2d..71b8a1e42436 100644 --- a/docs/content/reference/types/components/.gitattributes +++ b/docs/content/reference/types/components/.gitattributes @@ -15,6 +15,7 @@ instance_key.md linguist-generated=true keypoint_id.md linguist-generated=true line_strip2d.md linguist-generated=true line_strip3d.md linguist-generated=true +marker_shape.md linguist-generated=true material.md linguist-generated=true media_type.md linguist-generated=true mesh_properties.md linguist-generated=true diff --git a/docs/content/reference/types/components/marker_shape.md b/docs/content/reference/types/components/marker_shape.md new file mode 100644 index 000000000000..25811ae1fc0d --- /dev/null +++ b/docs/content/reference/types/components/marker_shape.md @@ -0,0 +1,16 @@ +--- +title: "MarkerShape" +--- + +Shape of a marker. + + +## Links + * 🌊 [C++ API docs for `MarkerShape`](https://ref.rerun.io/docs/cpp/stable/structrerun_1_1components_1_1MarkerShape.html?speculative-link) + * 🐍 [Python API docs for `MarkerShape`](https://ref.rerun.io/docs/python/stable/common/components?speculative-link#rerun.components.MarkerShape) + * 🦀 [Rust API docs for `MarkerShape`](https://docs.rs/rerun/latest/rerun/components/struct.MarkerShape.html?speculative-link) + + +## Used by + +* [`SeriesPoint`](../archetypes/series_point.md?speculative-link) diff --git a/rerun_cpp/src/rerun/archetypes/series_point.cpp b/rerun_cpp/src/rerun/archetypes/series_point.cpp index bad39f7f2e52..488dd35d08e3 100644 --- a/rerun_cpp/src/rerun/archetypes/series_point.cpp +++ b/rerun_cpp/src/rerun/archetypes/series_point.cpp @@ -14,13 +14,18 @@ namespace rerun { ) { using namespace archetypes; std::vector cells; - cells.reserve(2); + cells.reserve(3); if (archetype.color.has_value()) { auto result = DataCell::from_loggable(archetype.color.value()); RR_RETURN_NOT_OK(result.error); cells.push_back(std::move(result.value)); } + if (archetype.marker.has_value()) { + auto result = DataCell::from_loggable(archetype.marker.value()); + RR_RETURN_NOT_OK(result.error); + cells.push_back(std::move(result.value)); + } { auto indicator = SeriesPoint::IndicatorComponent(); auto result = DataCell::from_loggable(indicator); diff --git a/rerun_cpp/src/rerun/archetypes/series_point.hpp b/rerun_cpp/src/rerun/archetypes/series_point.hpp index 9fa309ac0b27..f08ec5de30c0 100644 --- a/rerun_cpp/src/rerun/archetypes/series_point.hpp +++ b/rerun_cpp/src/rerun/archetypes/series_point.hpp @@ -6,6 +6,7 @@ #include "../collection.hpp" #include "../compiler_utils.hpp" #include "../components/color.hpp" +#include "../components/marker_shape.hpp" #include "../data_cell.hpp" #include "../indicator_component.hpp" #include "../result.hpp" @@ -21,6 +22,9 @@ namespace rerun::archetypes { /// Color for the corresponding series. std::optional color; + /// What shape to use to represent the point + std::optional marker; + public: static constexpr const char IndicatorComponentName[] = "rerun.components.SeriesPointIndicator"; @@ -39,6 +43,13 @@ namespace rerun::archetypes { RR_WITH_MAYBE_UNINITIALIZED_DISABLED(return std::move(*this);) } + /// What shape to use to represent the point + SeriesPoint with_marker(rerun::components::MarkerShape _marker) && { + marker = std::move(_marker); + // 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; diff --git a/rerun_cpp/src/rerun/components.hpp b/rerun_cpp/src/rerun/components.hpp index c2a372bfc2dd..1e8194fa65ac 100644 --- a/rerun_cpp/src/rerun/components.hpp +++ b/rerun_cpp/src/rerun/components.hpp @@ -16,6 +16,7 @@ #include "components/keypoint_id.hpp" #include "components/line_strip2d.hpp" #include "components/line_strip3d.hpp" +#include "components/marker_shape.hpp" #include "components/material.hpp" #include "components/media_type.hpp" #include "components/mesh_properties.hpp" diff --git a/rerun_cpp/src/rerun/components/.gitattributes b/rerun_cpp/src/rerun/components/.gitattributes index d2cbdcb6772f..61aebda8d8f6 100644 --- a/rerun_cpp/src/rerun/components/.gitattributes +++ b/rerun_cpp/src/rerun/components/.gitattributes @@ -29,6 +29,8 @@ line_strip2d.cpp linguist-generated=true line_strip2d.hpp linguist-generated=true line_strip3d.cpp linguist-generated=true line_strip3d.hpp linguist-generated=true +marker_shape.cpp linguist-generated=true +marker_shape.hpp linguist-generated=true material.cpp linguist-generated=true material.hpp linguist-generated=true media_type.cpp linguist-generated=true diff --git a/rerun_cpp/src/rerun/components/marker_shape.cpp b/rerun_cpp/src/rerun/components/marker_shape.cpp new file mode 100644 index 000000000000..4f7e9fadb546 --- /dev/null +++ b/rerun_cpp/src/rerun/components/marker_shape.cpp @@ -0,0 +1,57 @@ +// 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/marker_shape.fbs". + +#include "marker_shape.hpp" + +#include +#include + +namespace rerun::components {} + +namespace rerun { + const std::shared_ptr& Loggable::arrow_datatype() { + static const auto datatype = arrow::uint8(); + return datatype; + } + + rerun::Error Loggable::fill_arrow_array_builder( + arrow::UInt8Builder* builder, const components::MarkerShape* 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->shape)); + ARROW_RETURN_NOT_OK( + builder->AppendValues(&elements->shape, static_cast(num_elements)) + ); + + return Error::ok(); + } + + Result> Loggable::to_arrow( + const components::MarkerShape* 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/marker_shape.hpp b/rerun_cpp/src/rerun/components/marker_shape.hpp new file mode 100644 index 000000000000..bf1fcfb28032 --- /dev/null +++ b/rerun_cpp/src/rerun/components/marker_shape.hpp @@ -0,0 +1,62 @@ +// 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/marker_shape.fbs". + +#pragma once + +#include "../result.hpp" + +#include +#include + +namespace arrow { + /// \private + template + class NumericBuilder; + + class Array; + class DataType; + class UInt8Type; + using UInt8Builder = NumericBuilder; +} // namespace arrow + +namespace rerun::components { + /// **Component**: Shape of a marker. + struct MarkerShape { + uint8_t shape; + + public: + MarkerShape() = default; + + MarkerShape(uint8_t shape_) : shape(shape_) {} + + MarkerShape& operator=(uint8_t shape_) { + shape = shape_; + return *this; + } + }; +} // namespace rerun::components + +namespace rerun { + template + struct Loggable; + + /// \private + template <> + struct Loggable { + static constexpr const char Name[] = "rerun.components.MarkerShape"; + + /// 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::UInt8Builder* builder, const components::MarkerShape* elements, + size_t num_elements + ); + + /// Serializes an array of `rerun::components::MarkerShape` into an arrow array. + static Result> to_arrow( + const components::MarkerShape* instances, size_t num_instances + ); + }; +} // namespace rerun diff --git a/rerun_py/rerun_sdk/rerun/archetypes/series_point.py b/rerun_py/rerun_sdk/rerun/archetypes/series_point.py index 7f68ad092b61..2ad566415381 100644 --- a/rerun_py/rerun_sdk/rerun/archetypes/series_point.py +++ b/rerun_py/rerun_sdk/rerun/archetypes/series_point.py @@ -20,7 +20,9 @@ class SeriesPoint(Archetype): """**Archetype**: Define the style properties for a point series in a chart.""" - def __init__(self: Any, *, color: datatypes.Rgba32Like | None = None): + def __init__( + self: Any, *, color: datatypes.Rgba32Like | None = None, marker: components.MarkerShapeLike | None = None + ): """ Create a new instance of the SeriesPoint archetype. @@ -28,11 +30,13 @@ def __init__(self: Any, *, color: datatypes.Rgba32Like | None = None): ---------- color: Color for the corresponding series. + marker: + What shape to use to represent the point """ # You can define your own __init__ function as a member of SeriesPointExt in series_point_ext.py with catch_and_log_exceptions(context=self.__class__.__name__): - self.__attrs_init__(color=color) + self.__attrs_init__(color=color, marker=marker) return self.__attrs_clear__() @@ -40,6 +44,7 @@ def __attrs_clear__(self) -> None: """Convenience method for calling `__attrs_init__` with all `None`s.""" self.__attrs_init__( color=None, # type: ignore[arg-type] + marker=None, # type: ignore[arg-type] ) @classmethod @@ -58,5 +63,14 @@ def _clear(cls) -> SeriesPoint: # # (Docstring intentionally commented out to hide this field from the docs) + marker: components.MarkerShapeBatch | None = field( + metadata={"component": "optional"}, + default=None, + converter=components.MarkerShapeBatch._optional, # type: ignore[misc] + ) + # What shape to use to represent the point + # + # (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/components/.gitattributes b/rerun_py/rerun_sdk/rerun/components/.gitattributes index 19bbc880f34e..bf559f4cfd39 100644 --- a/rerun_py/rerun_sdk/rerun/components/.gitattributes +++ b/rerun_py/rerun_sdk/rerun/components/.gitattributes @@ -16,6 +16,7 @@ instance_key.py linguist-generated=true keypoint_id.py linguist-generated=true line_strip2d.py linguist-generated=true line_strip3d.py linguist-generated=true +marker_shape.py linguist-generated=true material.py linguist-generated=true media_type.py linguist-generated=true mesh_properties.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 38b99b68adea..c381da880ea6 100644 --- a/rerun_py/rerun_sdk/rerun/components/__init__.py +++ b/rerun_py/rerun_sdk/rerun/components/__init__.py @@ -34,6 +34,7 @@ from .keypoint_id import KeypointId, KeypointIdBatch, KeypointIdType from .line_strip2d import LineStrip2D, LineStrip2DArrayLike, LineStrip2DBatch, LineStrip2DLike, LineStrip2DType from .line_strip3d import LineStrip3D, LineStrip3DArrayLike, LineStrip3DBatch, LineStrip3DLike, LineStrip3DType +from .marker_shape import MarkerShape, MarkerShapeArrayLike, MarkerShapeBatch, MarkerShapeLike, MarkerShapeType from .material import Material, MaterialBatch, MaterialType from .media_type import MediaType, MediaTypeBatch, MediaTypeType from .mesh_properties import MeshProperties, MeshPropertiesBatch, MeshPropertiesType @@ -135,6 +136,11 @@ "LineStrip3DBatch", "LineStrip3DLike", "LineStrip3DType", + "MarkerShape", + "MarkerShapeArrayLike", + "MarkerShapeBatch", + "MarkerShapeLike", + "MarkerShapeType", "Material", "MaterialBatch", "MaterialType", diff --git a/rerun_py/rerun_sdk/rerun/components/marker_shape.py b/rerun_py/rerun_sdk/rerun/components/marker_shape.py new file mode 100644 index 000000000000..5d21453cbc72 --- /dev/null +++ b/rerun_py/rerun_sdk/rerun/components/marker_shape.py @@ -0,0 +1,59 @@ +# 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/marker_shape.fbs". + +# You can extend this class by creating a "MarkerShapeExt" class in "marker_shape_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 + +__all__ = ["MarkerShape", "MarkerShapeArrayLike", "MarkerShapeBatch", "MarkerShapeLike", "MarkerShapeType"] + + +@define(init=False) +class MarkerShape: + """**Component**: Shape of a marker.""" + + def __init__(self: Any, shape: MarkerShapeLike): + """Create a new instance of the MarkerShape component.""" + + # You can define your own __init__ function as a member of MarkerShapeExt in marker_shape_ext.py + self.__attrs_init__(shape=shape) + + shape: int = field(converter=int) + + def __array__(self, dtype: npt.DTypeLike = None) -> npt.NDArray[Any]: + # You can define your own __array__ function as a member of MarkerShapeExt in marker_shape_ext.py + return np.asarray(self.shape, dtype=dtype) + + def __int__(self) -> int: + return int(self.shape) + + +MarkerShapeLike = MarkerShape +MarkerShapeArrayLike = Union[ + MarkerShape, + Sequence[MarkerShapeLike], +] + + +class MarkerShapeType(BaseExtensionType): + _TYPE_NAME: str = "rerun.components.MarkerShape" + + def __init__(self) -> None: + pa.ExtensionType.__init__(self, pa.uint8(), self._TYPE_NAME) + + +class MarkerShapeBatch(BaseBatch[MarkerShapeArrayLike], ComponentBatchMixin): + _ARROW_TYPE = MarkerShapeType() + + @staticmethod + def _native_to_pa_array(data: MarkerShapeArrayLike, data_type: pa.DataType) -> pa.Array: + raise NotImplementedError # You need to implement native_to_pa_array_override in marker_shape_ext.py