lc_render/view/
area.rs

1use crate::color::{COLOR_HEX_GREEN_1, COLOR_HEX_GREEN_4, COLOR_HEX_GREEN_5};
2use crate::render::svg::*;
3use crate::shape::area::Area;
4use crate::shape::point::Point;
5use crate::{BandScale, Color, Error, LinearScale, PointLabelPosition, PointType, Scale, View};
6use svg::Node;
7
8const DEFAULT_LABEL_VISIBLE: bool = true;
9const DEFAULT_LABEL_POSITION: PointLabelPosition = PointLabelPosition::Top;
10
11const DEFAULT_POINT_TYPE: PointType = PointType::Circle;
12const DEFAULT_POINT_VISIBLE: bool = true;
13
14/// View that represents area.
15#[derive(Clone)]
16pub struct AreaView {
17    x_scale: BandScale,
18    y_scale: LinearScale,
19    area: Area,
20    fill_color: String,
21    stroke_color: String,
22    point_fill_color: String,
23    point_stroke_color: String,
24    point_type: PointType,
25    point_visible: bool,
26    point_label_visible: bool,
27    point_label_position: PointLabelPosition,
28}
29
30impl AreaView {
31    /// Create a new Area.
32    pub fn new(x_scale: BandScale, y_scale: LinearScale) -> Self {
33        Self {
34            x_scale,
35            y_scale,
36            fill_color: COLOR_HEX_GREEN_5.to_string(),
37            stroke_color: COLOR_HEX_GREEN_1.to_string(),
38            point_fill_color: COLOR_HEX_GREEN_4.to_string(),
39            point_stroke_color: COLOR_HEX_GREEN_1.to_string(),
40            area: Area::default(),
41            point_type: DEFAULT_POINT_TYPE,
42            point_visible: DEFAULT_POINT_VISIBLE,
43            point_label_visible: DEFAULT_LABEL_VISIBLE,
44            point_label_position: DEFAULT_LABEL_POSITION,
45        }
46    }
47
48    /// Set area fill color.
49    pub fn set_fill_color(mut self, fill_color: Color) -> Self {
50        self.fill_color = fill_color.to_string();
51        self
52    }
53    /// Set area stroke color.
54    pub fn set_stroke_color(mut self, stroke_color: Color) -> Self {
55        self.stroke_color = stroke_color.to_string();
56        self
57    }
58
59    /// Set area point fill color.
60    pub fn set_point_fill_color(mut self, point_fill_color: Color) -> Self {
61        self.point_fill_color = point_fill_color.to_string();
62        self
63    }
64
65    /// Set area point stroke color.
66    pub fn set_point_stroke_color(mut self, point_stroke_color: Color) -> Self {
67        self.point_stroke_color = point_stroke_color.to_string();
68        self
69    }
70
71    /// Set area point type color.
72    pub fn set_point_type(mut self, point_type: PointType) -> Self {
73        self.point_type = point_type;
74        self
75    }
76
77    /// Set area point visibility.
78    pub fn set_point_visible(mut self, point_visible: bool) -> Self {
79        self.point_visible = point_visible;
80        self
81    }
82
83    /// Set area point label visibility.
84    pub fn set_point_label_visible(mut self, point_label_visible: bool) -> Self {
85        self.point_label_visible = point_label_visible;
86        self
87    }
88
89    /// Set area point label position.
90    pub fn set_point_label_position(mut self, point_label_position: PointLabelPosition) -> Self {
91        self.point_label_position = point_label_position;
92        self
93    }
94
95    /// Set area data.
96    pub fn set_data(mut self, data: &[f32]) -> Result<Self, Error> {
97        if data.is_empty() {
98            return Err(Error::DataIsEmpty);
99        }
100        if data.len() != self.x_scale.ticks().len() {
101            return Err(Error::CategoriesCountDoesntEqual);
102        }
103
104        // Compute offsets in case there is a non-zero bandwidth.
105        let x_bandwidth_offset = {
106            if self.x_scale.is_range_reversed() {
107                -self.x_scale.tick_offset()
108            } else {
109                self.x_scale.tick_offset()
110            }
111        };
112        let y_bandwidth_offset = {
113            if self.y_scale.is_range_reversed() {
114                -self.y_scale.tick_offset()
115            } else {
116                self.y_scale.tick_offset()
117            }
118        };
119
120        let categories = self.x_scale.ticks();
121        let mut points = Vec::new();
122        for (idx, value) in data.iter().enumerate() {
123            let category = &categories[idx];
124            let scaled_x = &self.x_scale.scale(category);
125            let scaled_y = self.y_scale.scale(&value);
126
127            let point = Point::new(
128                scaled_x + x_bandwidth_offset,
129                scaled_y + y_bandwidth_offset,
130                self.point_type,
131                DEFAULT_POINT_SIZE,
132                &value.to_string(),
133                &self.point_fill_color.to_string(),
134                &self.point_stroke_color.to_string(),
135            )
136            .set_point_visible(self.point_visible)
137            .set_label_visible(self.point_label_visible)
138            .set_label_position(self.point_label_position);
139            points.push(point);
140        }
141
142        let y_origin = {
143            if self.y_scale.is_range_reversed() {
144                self.y_scale.range_start()
145            } else {
146                self.y_scale.range_end()
147            }
148        };
149
150        let last_point = Point::new(
151            self.x_scale
152                .scale(&categories[self.x_scale.ticks().len() - 1])
153                + x_bandwidth_offset,
154            y_origin as f32,
155            self.point_type,
156            DEFAULT_POINT_SIZE,
157            &data[0].to_string(),
158            &self.point_fill_color,
159            &self.point_stroke_color,
160        )
161        .set_point_visible(false)
162        .set_label_visible(false);
163        points.push(last_point);
164
165        let first_point = Point::new(
166            self.x_scale.scale(&categories[0]) + x_bandwidth_offset,
167            y_origin as f32,
168            self.point_type,
169            DEFAULT_POINT_SIZE,
170            &data[0].to_string(),
171            &self.point_fill_color,
172            &self.point_stroke_color,
173        )
174        .set_point_visible(false)
175        .set_label_visible(false);
176        points.push(first_point);
177
178        self.area = Area::new(points, &self.fill_color, &self.stroke_color);
179
180        Ok(self)
181    }
182}
183
184impl View for AreaView {
185    /// Get area SVG representation.
186    fn to_svg(&self) -> svg::node::element::Group {
187        let mut res = svg::node::element::Group::new();
188        res.append(self.area.to_svg());
189
190        res
191    }
192}
193
194#[cfg(test)]
195mod tests {
196    use super::*;
197    use crate::Color;
198
199    #[test]
200    fn area_basic() {
201        let expected_svg_group = r##"<g>
202<g class="area">
203<g class="point" transform="translate(13.414631,99.01)">
204<g>
205<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
206<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
207</g>
208<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
20989.1
210</text>
211</g>
212<g class="point" transform="translate(37.804874,99.865555)">
213<g>
214<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
215<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
216</g>
217<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
21812.1
219</text>
220</g>
221<g class="point" transform="translate(62.195118,99.5)">
222<g>
223<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
224<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
225</g>
226<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
22745
228</text>
229</g>
230<g class="point" transform="translate(86.585365,99.76667)">
231<g>
232<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
233<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
234</g>
235<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
23621
237</text>
238</g>
239<g class="point" transform="translate(86.585365,100)"/>
240<g class="point" transform="translate(13.414631,100)"/>
241<path d="M13.414631,99.01 L37.804874,99.865555 L62.195118,99.5 L86.585365,99.76667 L86.585365,100 L13.414631,100 z" fill="#038d05" stroke="#ff006c"/>
242<g class="point" transform="translate(13.414631,99.01)">
243<g>
244<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
245<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
246</g>
247<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
24889.1
249</text>
250</g>
251<g class="point" transform="translate(37.804874,99.865555)">
252<g>
253<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
254<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
255</g>
256<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
25712.1
258</text>
259</g>
260<g class="point" transform="translate(62.195118,99.5)">
261<g>
262<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
263<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
264</g>
265<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
26645
267</text>
268</g>
269<g class="point" transform="translate(86.585365,99.76667)">
270<g>
271<line stroke="#ffffff" stroke-width="2px" x1="-5" x2="5" y1="-5" y2="5"/>
272<line stroke="#ffffff" stroke-width="2px" x1="5" x2="-5" y1="-5" y2="5"/>
273</g>
274<text dy=".35em" fill="#080808" font-family="sans-serif" font-size="14px" text-anchor="middle" x="0" y="17">
27521
276</text>
277</g>
278<g class="point" transform="translate(86.585365,100)"/>
279<g class="point" transform="translate(13.414631,100)"/>
280</g>
281</g>"##;
282
283        let x_scale = BandScale::new(
284            vec![
285                "a".to_string(),
286                "n1".to_string(),
287                "n2".to_string(),
288                "y".to_string(),
289            ],
290            0,
291            100,
292        );
293        let y_scale = LinearScale::new(0_f32, 9000_f32, 100, 0);
294        let data = vec![89.1_f32, 12.1_f32, 45_f32, 21_f32];
295        let area = AreaView::new(x_scale, y_scale)
296            .set_point_label_position(PointLabelPosition::Bottom)
297            .set_stroke_color(Color::new_from_hex("#ff006c"))
298            .set_point_fill_color(Color::new_from_hex("#ffe5f5"))
299            .set_point_type(PointType::X)
300            .set_point_stroke_color(Color::new_from_hex("#ffffff"))
301            .set_data(&data)
302            .expect("unable to set data");
303        let area_svg = area.to_svg();
304        assert_eq!(area_svg.to_string(), expected_svg_group);
305    }
306}