use crate::*; /// Provides methods to interact with a plot while building it. It is the single argument of the closure /// provided to [`Plot::show`]. See [`Plot`] for an example of how to use it. pub struct PlotUi { pub(crate) ctx: Context, pub(crate) items: Vec>, pub(crate) next_auto_color_idx: usize, pub(crate) last_plot_transform: PlotTransform, pub(crate) last_auto_bounds: Vec2b, pub(crate) response: Response, pub(crate) bounds_modifications: Vec, } impl PlotUi { fn auto_color(&mut self) -> Color32 { let i = self.next_auto_color_idx; self.next_auto_color_idx += 1; let golden_ratio = (5.0_f32.sqrt() - 1.0) / 2.0; // 0.61803398875 let h = i as f32 * golden_ratio; Hsva::new(h, 0.85, 0.5, 1.0).into() // TODO(emilk): OkLab or some other perspective color space } pub fn ctx(&self) -> &Context { &self.ctx } /// The plot bounds as they were in the last frame. If called on the first frame and the bounds were not /// further specified in the plot builder, this will return bounds centered on the origin. The bounds do /// not change until the plot is drawn. pub fn plot_bounds(&self) -> PlotBounds { *self.last_plot_transform.bounds() } /// Set the plot bounds. Can be useful for implementing alternative plot navigation methods. pub fn set_plot_bounds(&mut self, plot_bounds: PlotBounds) { self.bounds_modifications .push(BoundsModification::Set(plot_bounds)); } /// Move the plot bounds. Can be useful for implementing alternative plot navigation methods. pub fn translate_bounds(&mut self, delta_pos: Vec2) { self.bounds_modifications .push(BoundsModification::Translate(delta_pos)); } /// Whether the plot axes were in auto-bounds mode in the last frame. If called on the first /// frame, this is the [`Plot`]'s default auto-bounds mode. pub fn auto_bounds(&self) -> Vec2b { self.last_auto_bounds } /// Set the auto-bounds mode for the plot axes. pub fn set_auto_bounds(&mut self, auto_bounds: Vec2b) { self.bounds_modifications .push(BoundsModification::AutoBounds(auto_bounds)); } /// Can be used to check if the plot was hovered or clicked. pub fn response(&self) -> &Response { &self.response } /// Scale the plot bounds around a position in screen coordinates. /// /// Can be useful for implementing alternative plot navigation methods. /// /// The plot bounds are divided by `zoom_factor`, therefore: /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. pub fn zoom_bounds(&mut self, zoom_factor: Vec2, center: PlotPoint) { self.bounds_modifications .push(BoundsModification::Zoom(zoom_factor, center)); } /// Scale the plot bounds around the hovered position, if any. /// /// Can be useful for implementing alternative plot navigation methods. /// /// The plot bounds are divided by `zoom_factor`, therefore: /// - `zoom_factor < 1.0` zooms out, i.e., increases the visible range to show more data. /// - `zoom_factor > 1.0` zooms in, i.e., reduces the visible range to show more detail. pub fn zoom_bounds_around_hovered(&mut self, zoom_factor: Vec2) { if let Some(hover_pos) = self.pointer_coordinate() { self.zoom_bounds(zoom_factor, hover_pos); } } /// The pointer position in plot coordinates. Independent of whether the pointer is in the plot area. pub fn pointer_coordinate(&self) -> Option { // We need to subtract the drag delta to keep in sync with the frame-delayed screen transform: let last_pos = self.ctx().input(|i| i.pointer.latest_pos())? - self.response.drag_delta(); let value = self.plot_from_screen(last_pos); Some(value) } /// The pointer drag delta in plot coordinates. pub fn pointer_coordinate_drag_delta(&self) -> Vec2 { let delta = self.response.drag_delta(); let dp_dv = self.last_plot_transform.dpos_dvalue(); Vec2::new(delta.x / dp_dv[0] as f32, delta.y / dp_dv[1] as f32) } /// Read the transform between plot coordinates and screen coordinates. pub fn transform(&self) -> &PlotTransform { &self.last_plot_transform } /// Transform the plot coordinates to screen coordinates. pub fn screen_from_plot(&self, position: PlotPoint) -> Pos2 { self.last_plot_transform.position_from_point(&position) } /// Transform the screen coordinates to plot coordinates. pub fn plot_from_screen(&self, position: Pos2) -> PlotPoint { self.last_plot_transform.value_from_position(position) } /// Add a data line. pub fn line(&mut self, mut line: Line) { if line.series.is_empty() { return; }; // Give the stroke an automatic color if no color has been assigned. if line.stroke.color == Color32::TRANSPARENT { line.stroke.color = self.auto_color(); } self.items.push(Box::new(line)); } /// Add a polygon. The polygon has to be convex. pub fn polygon(&mut self, mut polygon: Polygon) { if polygon.series.is_empty() { return; }; // Give the stroke an automatic color if no color has been assigned. if polygon.stroke.color == Color32::TRANSPARENT { polygon.stroke.color = self.auto_color(); } self.items.push(Box::new(polygon)); } /// Add a text. pub fn text(&mut self, text: Text) { if text.text.is_empty() { return; }; self.items.push(Box::new(text)); } /// Add data points. pub fn points(&mut self, mut points: Points) { if points.series.is_empty() { return; }; // Give the points an automatic color if no color has been assigned. if points.color == Color32::TRANSPARENT { points.color = self.auto_color(); } self.items.push(Box::new(points)); } /// Add arrows. pub fn arrows(&mut self, mut arrows: Arrows) { if arrows.origins.is_empty() || arrows.tips.is_empty() { return; }; // Give the arrows an automatic color if no color has been assigned. if arrows.color == Color32::TRANSPARENT { arrows.color = self.auto_color(); } self.items.push(Box::new(arrows)); } /// Add an image. pub fn image(&mut self, image: PlotImage) { self.items.push(Box::new(image)); } /// Add a horizontal line. /// Can be useful e.g. to show min/max bounds or similar. /// Always fills the full width of the plot. pub fn hline(&mut self, mut hline: HLine) { if hline.stroke.color == Color32::TRANSPARENT { hline.stroke.color = self.auto_color(); } self.items.push(Box::new(hline)); } /// Add a vertical line. /// Can be useful e.g. to show min/max bounds or similar. /// Always fills the full height of the plot. pub fn vline(&mut self, mut vline: VLine) { if vline.stroke.color == Color32::TRANSPARENT { vline.stroke.color = self.auto_color(); } self.items.push(Box::new(vline)); } /// Add a box plot diagram. pub fn box_plot(&mut self, mut box_plot: BoxPlot) { if box_plot.boxes.is_empty() { return; } // Give the elements an automatic color if no color has been assigned. if box_plot.default_color == Color32::TRANSPARENT { box_plot = box_plot.color(self.auto_color()); } self.items.push(Box::new(box_plot)); } /// Add a bar chart. pub fn bar_chart(&mut self, mut chart: BarChart) { if chart.bars.is_empty() { return; } // Give the elements an automatic color if no color has been assigned. if chart.default_color == Color32::TRANSPARENT { chart = chart.color(self.auto_color()); } self.items.push(Box::new(chart)); } }