use crate::{
ui::{annotations::AnnotationMap, DefaultColor, SceneQuery},
ViewerContext,
};
use re_arrow_store::TimeRange;
use re_log_types::{
component_types::{self, InstanceKey},
msg_bundle::Component,
};
use re_query::{range_entity_with_primary, QueryError};
#[derive(Clone, Debug)]
pub struct PlotPointAttrs {
pub label: Option<String>,
pub color: egui::Color32,
pub radius: f32,
pub scattered: bool,
}
impl PartialEq for PlotPointAttrs {
fn eq(&self, rhs: &Self) -> bool {
let Self {
label,
color,
radius,
scattered,
} = self;
label.eq(&rhs.label)
&& color.eq(&rhs.color)
&& radius.total_cmp(&rhs.radius).is_eq()
&& scattered.eq(&rhs.scattered)
}
}
impl Eq for PlotPointAttrs {}
#[derive(Clone, Debug)]
struct PlotPoint {
time: i64,
value: f64,
attrs: PlotPointAttrs,
}
#[derive(Clone, Copy, Debug)]
pub enum PlotSeriesKind {
Continuous,
Scatter,
}
#[derive(Clone, Debug)]
pub struct PlotSeries {
pub label: String,
pub color: egui::Color32,
pub width: f32,
pub kind: PlotSeriesKind,
pub points: Vec<(i64, f64)>,
}
#[derive(Default, Debug)]
pub struct SceneTimeSeries {
pub annotation_map: AnnotationMap,
pub lines: Vec<PlotSeries>,
}
impl SceneTimeSeries {
pub(crate) fn load(&mut self, ctx: &mut ViewerContext<'_>, query: &SceneQuery<'_>) {
crate::profile_function!();
self.annotation_map.load(ctx, query);
self.load_scalars(ctx, query);
}
fn load_scalars(&mut self, ctx: &mut ViewerContext<'_>, query: &SceneQuery<'_>) {
crate::profile_function!();
let store = &ctx.log_db.entity_db.data_store;
for entity_path in query.entity_paths {
let ent_path = entity_path;
let mut points = Vec::new();
let annotations = self.annotation_map.find(ent_path);
let annotation_info = annotations.class_description(None).annotation_info();
let default_color = DefaultColor::EntityPath(ent_path);
let query = re_arrow_store::RangeQuery::new(
query.timeline,
TimeRange::new(i64::MIN.into(), i64::MAX.into()),
);
let components = [
InstanceKey::name(),
component_types::Scalar::name(),
component_types::ScalarPlotProps::name(),
component_types::ColorRGBA::name(),
component_types::Radius::name(),
component_types::Label::name(),
];
let ent_views = range_entity_with_primary::<component_types::Scalar, 6>(
store, &query, ent_path, components,
);
for (time, ent_view) in ent_views {
match ent_view.visit5(
|_instance,
scalar: component_types::Scalar,
props: Option<component_types::ScalarPlotProps>,
color: Option<component_types::ColorRGBA>,
radius: Option<component_types::Radius>,
label: Option<component_types::Label>| {
let color = annotation_info
.color(color.map(|c| c.to_array()).as_ref(), default_color);
let label = annotation_info.label(label.map(|l| l.into()).as_ref());
const DEFAULT_RADIUS: f32 = 0.75;
points.push(PlotPoint {
time: time.unwrap().as_i64(), value: scalar.into(),
attrs: PlotPointAttrs {
label,
color,
radius: radius.map_or(DEFAULT_RADIUS, |r| r.0),
scattered: props.map_or(false, |props| props.scattered),
},
});
},
) {
Ok(_) | Err(QueryError::PrimaryNotFound) => {}
Err(err) => {
re_log::error_once!("Unexpected error querying {ent_path:?}: {err}");
}
}
}
points.sort_by_key(|s| s.time);
if points.is_empty() {
continue;
}
let same_label = |points: &[PlotPoint]| {
let label = points[0].attrs.label.as_ref();
(label.is_some() && points.iter().all(|p| p.attrs.label.as_ref() == label))
.then(|| label.cloned().unwrap())
};
let line_label = same_label(&points).unwrap_or_else(|| entity_path.to_string());
self.add_line_segments(&line_label, points);
}
}
fn add_line_segments(&mut self, line_label: &str, points: Vec<PlotPoint>) {
crate::profile_function!();
let num_points = points.len();
let mut attrs = points[0].attrs.clone();
let mut line: PlotSeries = PlotSeries {
label: line_label.to_owned(),
color: attrs.color,
width: 2.0 * attrs.radius,
kind: if attrs.scattered {
PlotSeriesKind::Scatter
} else {
PlotSeriesKind::Continuous
},
points: Vec::with_capacity(num_points),
};
for (i, p) in points.into_iter().enumerate() {
if p.attrs == attrs {
line.points.push((p.time, p.value));
} else {
attrs = p.attrs.clone();
let kind = if attrs.scattered {
PlotSeriesKind::Scatter
} else {
PlotSeriesKind::Continuous
};
let prev_line = std::mem::replace(
&mut line,
PlotSeries {
label: line_label.to_owned(),
color: attrs.color,
width: 2.0 * attrs.radius,
kind,
points: Vec::with_capacity(num_points - i),
},
);
let prev_point = *prev_line.points.last().unwrap();
self.lines.push(prev_line);
let cur_continuous = matches!(kind, PlotSeriesKind::Continuous);
let prev_continuous = matches!(kind, PlotSeriesKind::Continuous);
if cur_continuous && prev_continuous {
line.points.push(prev_point);
}
line.points.push((p.time, p.value));
}
}
if !line.points.is_empty() {
self.lines.push(line);
}
}
}