Skip to main content

esoc_chart/chart/
scatter.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2//! Scatter series.
3
4use esoc_gfx::canvas::Canvas;
5use esoc_gfx::color::Color;
6use esoc_gfx::element::{DrawElement, Element};
7use esoc_gfx::layer::Layer;
8use esoc_gfx::style::Fill;
9use esoc_gfx::transform::CoordinateTransform;
10
11use crate::series::{DataBounds, SeriesRenderer};
12use crate::theme::Theme;
13
14/// A scatter series rendering individual data points as circles.
15#[derive(Clone, Debug)]
16pub struct ScatterSeries {
17    /// X values.
18    pub x: Vec<f64>,
19    /// Y values.
20    pub y: Vec<f64>,
21    /// Optional series label.
22    pub label: Option<String>,
23    /// Override color.
24    pub color: Option<Color>,
25    /// Override point radius.
26    pub radius: Option<f64>,
27}
28
29impl ScatterSeries {
30    /// Create a new scatter series.
31    pub fn new(x: &[f64], y: &[f64]) -> Self {
32        Self {
33            x: x.to_vec(),
34            y: y.to_vec(),
35            label: None,
36            color: None,
37            radius: None,
38        }
39    }
40}
41
42impl SeriesRenderer for ScatterSeries {
43    fn data_bounds(&self) -> DataBounds {
44        let x_min = self.x.iter().copied().fold(f64::INFINITY, f64::min);
45        let x_max = self.x.iter().copied().fold(f64::NEG_INFINITY, f64::max);
46        let y_min = self.y.iter().copied().fold(f64::INFINITY, f64::min);
47        let y_max = self.y.iter().copied().fold(f64::NEG_INFINITY, f64::max);
48        DataBounds::new(x_min, x_max, y_min, y_max)
49    }
50
51    fn render(
52        &self,
53        canvas: &mut Canvas,
54        transform: &CoordinateTransform,
55        theme: &Theme,
56        series_index: usize,
57    ) {
58        let color = self
59            .color
60            .unwrap_or_else(|| theme.palette.get(series_index));
61        let radius = self.radius.unwrap_or(theme.point_radius);
62
63        for (&x, &y) in self.x.iter().zip(self.y.iter()) {
64            let p = transform.to_pixel(x, y);
65            canvas.add(DrawElement::new(
66                Element::circle(p.x, p.y, radius, Fill::Solid(color)),
67                Layer::Data,
68            ));
69        }
70    }
71
72    fn label(&self) -> Option<&str> {
73        self.label.as_deref()
74    }
75}