lc_render/view/
scatter.rs1use 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#[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 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 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 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 pub fn set_point_type(mut self, point_type: PointType) -> Self {
57 self.point_type = point_type;
58 self
59 }
60
61 pub fn set_point_visible(mut self, point_visible: bool) -> Self {
63 self.point_visible = point_visible;
64 self
65 }
66
67 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 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 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 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 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}