lc_render/view/
scatter.rs

1use crate::color::{COLOR_HEX_BLUE_3, COLOR_HEX_BLUE_4};
2use crate::render::svg::*;
3use crate::shape::point::Point;
4use crate::{Color, Error, LinearScale, PointLabelPosition, PointType, Scale, View};
5use svg::Node;
6
7const DEFAULT_LABEL_VISIBLE: bool = true;
8const DEFAULT_LABEL_POSITION: PointLabelPosition = PointLabelPosition::Top;
9
10const DEFAULT_POINT_TYPE: PointType = PointType::Circle;
11const DEFAULT_POINT_VISIBLE: bool = true;
12
13/// ScatterView represents separated points view.
14#[derive(Clone)]
15pub struct ScatterView {
16    x_scale: LinearScale,
17    y_scale: LinearScale,
18    point_fill_color: String,
19    point_stroke_color: String,
20    points: Vec<Point>,
21    point_type: PointType,
22    point_visible: bool,
23    point_label_visible: bool,
24    point_label_position: PointLabelPosition,
25}
26
27impl ScatterView {
28    /// Create a new ScatterView.
29    pub fn new(x_scale: LinearScale, y_scale: LinearScale) -> Self {
30        Self {
31            x_scale,
32            y_scale,
33            point_fill_color: COLOR_HEX_BLUE_4.to_string(),
34            point_stroke_color: COLOR_HEX_BLUE_3.to_string(),
35            points: Vec::new(),
36            point_type: DEFAULT_POINT_TYPE,
37            point_visible: DEFAULT_POINT_VISIBLE,
38            point_label_visible: DEFAULT_LABEL_VISIBLE,
39            point_label_position: DEFAULT_LABEL_POSITION,
40        }
41    }
42
43    /// Set scatter points fill color.
44    pub fn set_point_fill_color(mut self, point_fill_color: Color) -> Self {
45        self.point_fill_color = point_fill_color.to_string();
46        self
47    }
48
49    /// Set scatter points stroke color.
50    pub fn set_point_stroke_color(mut self, point_stroke_color: Color) -> Self {
51        self.point_stroke_color = point_stroke_color.to_string();
52        self
53    }
54
55    /// Set scatter points type.
56    pub fn set_point_type(mut self, point_type: PointType) -> Self {
57        self.point_type = point_type;
58        self
59    }
60
61    /// Set scatter points visibility.
62    pub fn set_point_visible(mut self, point_visible: bool) -> Self {
63        self.point_visible = point_visible;
64        self
65    }
66
67    /// Set scatter points label visibility.
68    pub fn set_point_label_visible(mut self, point_label_visible: bool) -> Self {
69        self.point_label_visible = point_label_visible;
70        self
71    }
72
73    /// Set scatter points label position.
74    pub fn set_point_label_position(mut self, point_label_position: PointLabelPosition) -> Self {
75        self.point_label_position = point_label_position;
76        self
77    }
78
79    /// Set values for scatter view.
80    pub fn set_data(mut self, data: &[(f32, f32)]) -> Result<Self, Error> {
81        if data.is_empty() {
82            return Err(Error::DataIsEmpty);
83        }
84
85        // Compute offsets in case there is a non-zero bandwidth.
86        let x_bandwidth_offset = {
87            if self.x_scale.is_range_reversed() {
88                -self.x_scale.tick_offset()
89            } else {
90                self.x_scale.tick_offset()
91            }
92        };
93        let y_bandwidth_offset = {
94            if self.y_scale.is_range_reversed() {
95                -self.y_scale.tick_offset()
96            } else {
97                self.y_scale.tick_offset()
98            }
99        };
100
101        let mut points = Vec::new();
102        for values in data.iter() {
103            let scaled_x = &self.x_scale.scale(&values.0);
104            let scaled_y = self.y_scale.scale(&values.1);
105
106            let point = Point::new(
107                scaled_x + x_bandwidth_offset,
108                scaled_y + y_bandwidth_offset,
109                self.point_type,
110                DEFAULT_POINT_SIZE,
111                &values.1.to_string(),
112                &self.point_fill_color.to_string(),
113                &self.point_stroke_color.to_string(),
114            )
115            .set_point_visible(self.point_visible)
116            .set_x_label(&values.0.to_string())
117            .set_label_visible(self.point_label_visible)
118            .set_label_position(self.point_label_position);
119            points.push(point);
120        }
121        self.points = points;
122
123        Ok(self)
124    }
125}
126
127impl View for ScatterView {
128    /// Get scatter view SVG representation.
129    fn to_svg(&self) -> svg::node::element::Group {
130        let mut res = svg::node::element::Group::new();
131        for point in self.points.iter() {
132            res.append(point.to_svg());
133        }
134
135        res
136    }
137}
138
139#[cfg(test)]
140mod tests {
141    use super::*;
142
143    #[test]
144    fn scatter_basic() {
145        let expected_svg_group = r##"<g>
146<g class="point" transform="translate(5.125,4.7249985)">
147<circle cx="0" cy="0" fill="#5095e5" r="5" stroke="#3a88e2"/>
148<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="-17">
149(20.5,90.55)
150</text>
151</g>
152<g class="point" transform="translate(23.9,29.67)">
153<circle cx="0" cy="0" fill="#5095e5" r="5" stroke="#3a88e2"/>
154<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="-17">
155(95.6,40.66)
156</text>
157</g>
158</g>"##;
159
160        let x_scale = LinearScale::new(0.0, 200.0, 0, 50);
161        let y_scale = LinearScale::new(0.0, 100.0, 50, 0);
162        let data = vec![(20.5, 90.55), (95.6, 40.66)];
163        let scatter = ScatterView::new(x_scale, y_scale)
164            .set_data(&data)
165            .expect("unable to set data");
166        let scatter_svg = scatter.to_svg();
167        assert_eq!(scatter_svg.to_string(), expected_svg_group);
168    }
169}