1use crate::color::{COLOR_HEX_BLUE_1, COLOR_HEX_BLUE_2};
2use crate::render::svg::*;
3use crate::shape::point::Point;
4use crate::{BandScale, 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
13const DEFAULT_LINE_STROKE_WIDTH: i32 = 2;
14
15#[derive(Clone)]
17pub struct LineView {
18 x_scale: BandScale,
19 y_scale: LinearScale,
20 stroke_color: String,
21 point_fill_color: String,
22 point_stroke_color: String,
23 points: Vec<Point>,
24 point_type: PointType,
25 point_visible: bool,
26 point_label_visible: bool,
27 point_label_position: PointLabelPosition,
28}
29
30impl LineView {
31 pub fn new(x_scale: BandScale, y_scale: LinearScale) -> Self {
33 Self {
34 x_scale,
35 y_scale,
36 stroke_color: COLOR_HEX_BLUE_1.to_string(),
37 point_fill_color: COLOR_HEX_BLUE_2.to_string(),
38 point_stroke_color: COLOR_HEX_BLUE_1.to_string(),
39 points: Vec::new(),
40 point_type: DEFAULT_POINT_TYPE,
41 point_visible: DEFAULT_POINT_VISIBLE,
42 point_label_visible: DEFAULT_LABEL_VISIBLE,
43 point_label_position: DEFAULT_LABEL_POSITION,
44 }
45 }
46
47 pub fn set_stroke_color(mut self, stroke_color: Color) -> Self {
49 self.stroke_color = stroke_color.to_string();
50 self
51 }
52
53 pub fn set_point_fill_color(mut self, point_fill_color: Color) -> Self {
55 self.point_fill_color = point_fill_color.to_string();
56 self
57 }
58
59 pub fn set_point_stroke_color(mut self, point_stroke_color: Color) -> Self {
61 self.point_stroke_color = point_stroke_color.to_string();
62 self
63 }
64
65 pub fn set_point_type(mut self, point_type: PointType) -> Self {
67 self.point_type = point_type;
68 self
69 }
70
71 pub fn set_point_visible(mut self, point_visible: bool) -> Self {
73 self.point_visible = point_visible;
74 self
75 }
76
77 pub fn set_point_label_visible(mut self, point_label_visible: bool) -> Self {
79 self.point_label_visible = point_label_visible;
80 self
81 }
82
83 pub fn set_point_label_position(mut self, point_label_position: PointLabelPosition) -> Self {
85 self.point_label_position = point_label_position;
86 self
87 }
88
89 pub fn set_data(mut self, data: &[f32]) -> Result<Self, Error> {
91 if data.is_empty() {
92 return Err(Error::DataIsEmpty);
93 }
94 if data.len() != self.x_scale.ticks().len() {
95 return Err(Error::CategoriesCountDoesntEqual);
96 }
97
98 let x_bandwidth_offset = {
100 if self.x_scale.is_range_reversed() {
101 -self.x_scale.tick_offset()
102 } else {
103 self.x_scale.tick_offset()
104 }
105 };
106 let y_bandwidth_offset = {
107 if self.y_scale.is_range_reversed() {
108 -self.y_scale.tick_offset()
109 } else {
110 self.y_scale.tick_offset()
111 }
112 };
113
114 let categories = self.x_scale.ticks();
115 let mut points = Vec::new();
116 for (idx, value) in data.iter().enumerate() {
117 let category = &categories[idx];
118 let scaled_x = &self.x_scale.scale(category);
119 let scaled_y = self.y_scale.scale(&value);
120
121 let point = Point::new(
122 scaled_x + x_bandwidth_offset,
123 scaled_y + y_bandwidth_offset,
124 self.point_type,
125 DEFAULT_POINT_SIZE,
126 &value.to_string(),
127 &self.point_fill_color.to_string(),
128 &self.point_stroke_color.to_string(),
129 )
130 .set_point_visible(self.point_visible)
131 .set_label_visible(self.point_label_visible)
132 .set_label_position(self.point_label_position);
133 points.push(point);
134 }
135 self.points = points;
136
137 Ok(self)
138 }
139}
140
141impl View for LineView {
142 fn to_svg(&self) -> svg::node::element::Group {
144 let mut res = svg::node::element::Group::new();
145 let mut data = svg::node::element::path::Data::new();
146
147 for (point_idx, point) in self.points.iter().enumerate() {
148 if point_idx == 0 {
149 data = data.move_to((point.x(), point.y()));
150 } else {
151 data = data.line_to((point.x(), point.y()));
152 }
153
154 res.append(point.to_svg());
155 }
156 let line = svg::node::element::Path::new()
157 .set(CLASS_ATTR, CLASS_LINE)
158 .set(FILL_ATTR, FILL_NONE)
159 .set(STROKE_ATTR, self.stroke_color.clone())
160 .set(STROKE_WIDTH_ATTR, DEFAULT_LINE_STROKE_WIDTH)
161 .set(D_ATTR, data);
162
163 res.append(line);
164
165 res
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172 use crate::Color;
173
174 #[test]
175 fn line_basic() {
176 let expected_svg_group = r##"<g>
177<g class="point" transform="translate(13.414631,99.01)">
178<g>
179<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
180<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
181</g>
182<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
18389.1
184</text>
185</g>
186<g class="point" transform="translate(37.804874,99.865555)">
187<g>
188<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
189<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
190</g>
191<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
19212.1
193</text>
194</g>
195<g class="point" transform="translate(62.195118,99.5)">
196<g>
197<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
198<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
199</g>
200<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
20145
202</text>
203</g>
204<g class="point" transform="translate(86.585365,99.76667)">
205<g>
206<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
207<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
208</g>
209<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
21021
211</text>
212</g>
213<path class="line" d="M13.414631,99.01 L37.804874,99.865555 L62.195118,99.5 L86.585365,99.76667" fill="none" stroke="#ff006c" stroke-width="2"/>
214</g>"##;
215
216 let x_scale = BandScale::new(
217 vec![
218 "a".to_string(),
219 "n1".to_string(),
220 "n2".to_string(),
221 "y".to_string(),
222 ],
223 0,
224 100,
225 );
226 let y_scale = LinearScale::new(0_f32, 9000_f32, 100, 0);
227 let data = vec![89.1_f32, 12.1_f32, 45_f32, 21_f32];
228 let line = LineView::new(x_scale, y_scale)
229 .set_point_label_position(PointLabelPosition::Bottom)
230 .set_stroke_color(Color::new_from_hex("#ff006c"))
231 .set_point_fill_color(Color::new_from_hex("#ffe5f5"))
232 .set_point_type(PointType::X)
233 .set_point_stroke_color(Color::new_from_hex("#ffffff"))
234 .set_data(&data)
235 .expect("unable to set data");
236 let line_svg = line.to_svg();
237 assert_eq!(line_svg.to_string(), expected_svg_group);
238 }
239}