use std::fmt::Formatter;
#[cfg(feature = "serde")]
use re_log_types::EntityPath;
#[cfg(feature = "serde")]
use crate::EditableAutoValue;
#[cfg(feature = "serde")]
#[derive(Clone, Default, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub struct EntityPropertyMap {
props: nohash_hasher::IntMap<EntityPath, EntityProperties>,
}
#[cfg(feature = "serde")]
impl EntityPropertyMap {
#[inline]
pub fn get(&self, entity_path: &EntityPath) -> EntityProperties {
self.props.get(entity_path).cloned().unwrap_or_default()
}
#[inline]
pub fn get_opt(&self, entity_path: &EntityPath) -> Option<&EntityProperties> {
self.props.get(entity_path)
}
#[inline]
pub fn update(&mut self, entity_path: EntityPath, prop: EntityProperties) {
if prop == EntityProperties::default() {
self.props.remove(&entity_path); } else if self.props.contains_key(&entity_path) {
let merged = self
.props
.get(&entity_path)
.cloned()
.unwrap_or_default()
.merge_with(&prop);
self.props.insert(entity_path, merged);
} else {
self.props.insert(entity_path, prop);
}
}
pub fn overwrite_properties(&mut self, entity_path: EntityPath, prop: EntityProperties) {
if prop == EntityProperties::default() {
self.props.remove(&entity_path); } else {
self.props.insert(entity_path, prop);
}
}
#[inline]
pub fn iter(&self) -> impl Iterator<Item = (&EntityPath, &EntityProperties)> {
self.props.iter()
}
pub fn has_edits(&self, other: &Self) -> bool {
self.props.len() != other.props.len()
|| self.props.iter().any(|(key, val)| {
other
.props
.get(key)
.map_or(true, |other_val| val.has_edits(other_val))
})
}
}
#[cfg(feature = "serde")]
impl FromIterator<(EntityPath, EntityProperties)> for EntityPropertyMap {
fn from_iter<T: IntoIterator<Item = (EntityPath, EntityProperties)>>(iter: T) -> Self {
Self {
props: iter.into_iter().collect(),
}
}
}
#[cfg(feature = "serde")]
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
#[cfg_attr(feature = "serde", serde(default))]
pub struct EntityProperties {
pub interactive: bool, pub color_mapper: EditableAutoValue<ColorMapper>, pub pinhole_image_plane_distance: EditableAutoValue<f32>, pub backproject_depth: EditableAutoValue<bool>, pub depth_from_world_scale: EditableAutoValue<f32>, pub backproject_radius_scale: EditableAutoValue<f32>, pub transform_3d_visible: EditableAutoValue<bool>,
pub transform_3d_size: EditableAutoValue<f32>, pub show_legend: EditableAutoValue<bool>, pub legend_location: Option<LegendCorner>, pub time_series_aggregator: EditableAutoValue<TimeSeriesAggregator>, }
#[cfg(feature = "serde")]
impl Default for EntityProperties {
fn default() -> Self {
Self {
interactive: true,
color_mapper: EditableAutoValue::default(),
pinhole_image_plane_distance: EditableAutoValue::Auto(1.0),
backproject_depth: EditableAutoValue::Auto(true),
depth_from_world_scale: EditableAutoValue::Auto(1.0),
backproject_radius_scale: EditableAutoValue::Auto(1.0),
transform_3d_visible: EditableAutoValue::Auto(false),
transform_3d_size: EditableAutoValue::Auto(1.0),
show_legend: EditableAutoValue::Auto(true),
legend_location: None,
time_series_aggregator: EditableAutoValue::Auto(TimeSeriesAggregator::default()),
}
}
}
#[cfg(feature = "serde")]
impl EntityProperties {
pub fn with_child(&self, child: &Self) -> Self {
Self {
interactive: self.interactive && child.interactive,
color_mapper: self.color_mapper.or(&child.color_mapper).clone(),
pinhole_image_plane_distance: self
.pinhole_image_plane_distance
.or(&child.pinhole_image_plane_distance)
.clone(),
backproject_depth: self.backproject_depth.or(&child.backproject_depth).clone(),
depth_from_world_scale: self
.depth_from_world_scale
.or(&child.depth_from_world_scale)
.clone(),
backproject_radius_scale: self
.backproject_radius_scale
.or(&child.backproject_radius_scale)
.clone(),
transform_3d_visible: self
.transform_3d_visible
.or(&child.transform_3d_visible)
.clone(),
transform_3d_size: self.transform_3d_size.or(&child.transform_3d_size).clone(),
show_legend: self.show_legend.or(&child.show_legend).clone(),
legend_location: self.legend_location.or(child.legend_location),
time_series_aggregator: self
.time_series_aggregator
.or(&child.time_series_aggregator)
.clone(),
}
}
pub fn merge_with(&self, other: &Self) -> Self {
Self {
interactive: other.interactive,
color_mapper: other.color_mapper.or(&self.color_mapper).clone(),
pinhole_image_plane_distance: other
.pinhole_image_plane_distance
.or(&self.pinhole_image_plane_distance)
.clone(),
backproject_depth: other.backproject_depth.or(&self.backproject_depth).clone(),
depth_from_world_scale: other
.depth_from_world_scale
.or(&self.depth_from_world_scale)
.clone(),
backproject_radius_scale: other
.backproject_radius_scale
.or(&self.backproject_radius_scale)
.clone(),
transform_3d_visible: other
.transform_3d_visible
.or(&self.transform_3d_visible)
.clone(),
transform_3d_size: self.transform_3d_size.or(&other.transform_3d_size).clone(),
show_legend: other.show_legend.or(&self.show_legend).clone(),
legend_location: other.legend_location.or(self.legend_location),
time_series_aggregator: other
.time_series_aggregator
.or(&self.time_series_aggregator)
.clone(),
}
}
pub fn has_edits(&self, other: &Self) -> bool {
let Self {
interactive,
color_mapper,
pinhole_image_plane_distance,
backproject_depth,
depth_from_world_scale,
backproject_radius_scale,
transform_3d_visible,
transform_3d_size,
show_legend,
legend_location,
time_series_aggregator,
} = self;
interactive != &other.interactive
|| color_mapper.has_edits(&other.color_mapper)
|| pinhole_image_plane_distance.has_edits(&other.pinhole_image_plane_distance)
|| backproject_depth.has_edits(&other.backproject_depth)
|| depth_from_world_scale.has_edits(&other.depth_from_world_scale)
|| backproject_radius_scale.has_edits(&other.backproject_radius_scale)
|| transform_3d_visible.has_edits(&other.transform_3d_visible)
|| transform_3d_size.has_edits(&other.transform_3d_size)
|| show_legend.has_edits(&other.show_legend)
|| *legend_location != other.legend_location
|| time_series_aggregator.has_edits(&other.time_series_aggregator)
}
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum Colormap {
Grayscale,
Inferno,
Magma,
Plasma,
#[default]
Turbo,
Viridis,
}
impl std::fmt::Display for Colormap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Colormap::Grayscale => "Grayscale",
Colormap::Inferno => "Inferno",
Colormap::Magma => "Magma",
Colormap::Plasma => "Plasma",
Colormap::Turbo => "Turbo",
Colormap::Viridis => "Viridis",
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum ColorMapper {
Colormap(Colormap),
}
impl std::fmt::Display for ColorMapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ColorMapper::Colormap(colormap) => colormap.fmt(f),
}
}
}
impl Default for ColorMapper {
#[inline]
fn default() -> Self {
Self::Colormap(Colormap::default())
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum LegendCorner {
LeftTop,
RightTop,
LeftBottom,
RightBottom,
}
impl std::fmt::Display for LegendCorner {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
LegendCorner::LeftTop => write!(f, "Top Left"),
LegendCorner::RightTop => write!(f, "Top Right"),
LegendCorner::LeftBottom => write!(f, "Bottom Left"),
LegendCorner::RightBottom => write!(f, "Bottom Right"),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Deserialize, serde::Serialize))]
pub enum TimeSeriesAggregator {
Off,
Average,
Max,
Min,
#[default]
MinMax,
MinMaxAverage,
}
impl std::fmt::Display for TimeSeriesAggregator {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
TimeSeriesAggregator::Off => write!(f, "Off"),
TimeSeriesAggregator::Average => write!(f, "Average"),
TimeSeriesAggregator::Max => write!(f, "Max"),
TimeSeriesAggregator::Min => write!(f, "Min"),
TimeSeriesAggregator::MinMax => write!(f, "MinMax"),
TimeSeriesAggregator::MinMaxAverage => write!(f, "MinMaxAverage"),
}
}
}
impl TimeSeriesAggregator {
#[inline]
pub fn variants() -> [TimeSeriesAggregator; 6] {
#[allow(clippy::match_same_arms)]
match Self::default() {
TimeSeriesAggregator::Off => {}
TimeSeriesAggregator::Average => {}
TimeSeriesAggregator::Max => {}
TimeSeriesAggregator::Min => {}
TimeSeriesAggregator::MinMax => {}
TimeSeriesAggregator::MinMaxAverage => {}
}
[
TimeSeriesAggregator::Off,
TimeSeriesAggregator::Average,
TimeSeriesAggregator::Max,
TimeSeriesAggregator::Min,
TimeSeriesAggregator::MinMax,
TimeSeriesAggregator::MinMaxAverage,
]
}
#[inline]
pub fn description(&self) -> &'static str {
match self {
TimeSeriesAggregator::Off => "No aggregation.",
TimeSeriesAggregator::Average => "Average all points in the range together.",
TimeSeriesAggregator::Max => "Keep only the maximum values in the range.",
TimeSeriesAggregator::Min => "Keep only the minimum values in the range.",
TimeSeriesAggregator::MinMax => "Keep both the minimum and maximum values in the range.\nThis will yield two aggregated points instead of one, effectively creating a vertical line.",
TimeSeriesAggregator::MinMaxAverage => "Find both the minimum and maximum values in the range, then use the average of those",
}
}
}