charts_rs/charts/
canvas.rs

1// Licensed under the Apache License, Version 2.0 (the "License");
2// you may not use this file except in compliance with the License.
3// You may obtain a copy of the License at
4//
5//     http://www.apache.org/licenses/LICENSE-2.0
6//
7// Unless required by applicable law or agreed to in writing, software
8// distributed under the License is distributed on an "AS IS" BASIS,
9// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
10// See the License for the specific language governing permissions and
11// limitations under the License.
12
13use super::component::{
14    generate_svg, Arrow, Axis, Bubble, Circle, Component, Grid, Legend, Line, Pie, Polygon,
15    Polyline, Rect, SmoothLine, SmoothLineFill, StraightLine, StraightLineFill, Text, LEGEND_WIDTH,
16};
17
18use super::{measure_text_width_family, util::*};
19use snafu::{ResultExt, Snafu};
20use std::cell::RefCell;
21use std::rc::Rc;
22
23#[derive(Debug, Snafu)]
24pub enum Error {
25    #[snafu(display("Error to svg: {source}"))]
26    ToSVG { source: super::component::Error },
27    #[snafu(display("Params is invalid: {message}"))]
28    Params { message: String },
29    #[snafu(display("Json is invalid: {source}"))]
30    Json { source: serde_json::Error },
31    #[snafu(display("Font is invalid: {source}"))]
32    Font { source: super::FontError },
33}
34
35impl From<serde_json::Error> for Error {
36    fn from(value: serde_json::Error) -> Self {
37        Error::Json { source: value }
38    }
39}
40
41impl From<super::FontError> for Error {
42    fn from(value: super::FontError) -> Self {
43        Error::Font { source: value }
44    }
45}
46
47pub type Result<T, E = Error> = std::result::Result<T, E>;
48
49#[derive(Clone)]
50pub struct Canvas {
51    pub width: f32,
52    pub height: f32,
53    pub x: f32,
54    pub y: f32,
55    pub components: Rc<RefCell<Vec<Component>>>,
56    pub margin: Box,
57}
58
59impl Canvas {
60    /// Creates a specified size canvas.
61    pub fn new(width: f32, height: f32) -> Self {
62        Canvas::new_width_xy(width, height, 0.0, 0.0)
63    }
64    /// Creates  a specified size canvas and set the x and y value.
65    pub fn new_width_xy(width: f32, height: f32, x: f32, y: f32) -> Self {
66        Canvas {
67            width,
68            height,
69            x,
70            y,
71            components: Rc::new(RefCell::new(vec![])),
72            margin: Box::default(),
73        }
74    }
75    /// Gets the width of canvas.
76    pub fn width(&self) -> f32 {
77        self.width - self.margin.left - self.margin.right
78    }
79    /// Gets the height of canvas.
80    pub fn height(&self) -> f32 {
81        self.height - self.margin.top - self.margin.bottom
82    }
83    /// Creates a child canvas and sets the left top value.
84    pub fn child_left_top(&self, margin: Box) -> Self {
85        let mut m = margin;
86        m.left += self.margin.left;
87        m.top += self.margin.top;
88        Canvas {
89            width: self.width,
90            height: self.height,
91            components: Rc::clone(&self.components),
92            margin: m,
93            x: self.x,
94            y: self.y,
95        }
96    }
97    /// Creates a child canvas.
98    pub fn child(&self, margin: Box) -> Self {
99        let mut m = margin;
100        m.left += self.margin.left;
101        m.top += self.margin.top;
102        m.right += self.margin.right;
103        m.bottom += self.margin.bottom;
104        Canvas {
105            width: self.width,
106            height: self.height,
107            components: Rc::clone(&self.components),
108            margin: m,
109            x: self.x,
110            y: self.y,
111        }
112    }
113    /// Appends arrow widget to canvas.
114    pub fn arrow(&mut self, arrow: Arrow) -> Box {
115        let mut c = arrow;
116        c.x += self.margin.left;
117        c.y += self.margin.top;
118        self.append(Component::Arrow(c));
119        let mut b = self.margin.clone();
120        b.right = b.left + 10.0;
121        b.bottom = b.top;
122        b
123    }
124    /// Appends line widget to canvas.
125    pub fn line(&mut self, line: Line) -> Box {
126        let mut c = line;
127        c.left += self.margin.left;
128        c.right += self.margin.left;
129        c.top += self.margin.top;
130        c.bottom += self.margin.top;
131        let b = Box {
132            left: c.left,
133            top: c.top,
134            right: c.right,
135            bottom: c.bottom,
136        };
137        self.append(Component::Line(c));
138        b
139    }
140    /// Appends rect widget to canvas.
141    pub fn rect(&mut self, rect: Rect) -> Box {
142        let mut c = rect;
143        c.left += self.margin.left;
144        c.top += self.margin.top;
145        let b = Box {
146            left: c.left,
147            top: c.top,
148            right: c.left + c.width,
149            bottom: c.top + c.height,
150        };
151        self.append(Component::Rect(c));
152        b
153    }
154    /// Appends polyline widget to canvas.
155    pub fn polyline(&mut self, polyline: Polyline) -> Box {
156        let mut c = polyline;
157        for p in c.points.iter_mut() {
158            p.x += self.margin.left;
159            p.y += self.margin.top;
160        }
161        let b = get_box_of_points(&c.points);
162
163        self.append(Component::Polyline(c));
164        b
165    }
166    /// Appends circle widget to canvas.
167    pub fn circle(&mut self, circle: Circle) -> Box {
168        let mut c = circle;
169        c.cx += self.margin.left;
170        c.cy += self.margin.top;
171        let b = Box {
172            left: c.cx - c.r,
173            top: c.cy - c.r,
174            right: c.cx + c.r,
175            bottom: c.cy + c.r,
176        };
177        self.append(Component::Circle(c));
178        b
179    }
180    /// Appends polygon widget to canvas.
181    pub fn polygon(&mut self, polygon: Polygon) -> Box {
182        let mut c = polygon;
183        for p in c.points.iter_mut() {
184            p.x += self.margin.left;
185            p.y += self.margin.top
186        }
187        let b = get_box_of_points(&c.points);
188        self.append(Component::Polygon(c));
189        b
190    }
191    /// Appends text widget to canvas.
192    pub fn text(&mut self, text: Text) -> Box {
193        let font_family = text.font_family.clone().unwrap_or_default();
194        let font_size = text.font_size.unwrap_or_default();
195        let mut c = text;
196
197        if let Some(x) = c.x {
198            c.x = Some(x + self.margin.left);
199        } else {
200            c.x = Some(self.margin.left);
201        }
202        if let Some(y) = c.y {
203            c.y = Some(y + self.margin.top);
204        } else {
205            c.y = Some(self.margin.top);
206        }
207        let mut b = Box {
208            left: c.x.unwrap_or_default(),
209            top: c.y.unwrap_or_default(),
210            ..Default::default()
211        };
212        if !font_family.is_empty() && font_size > 0.0 {
213            if let Ok(result) = measure_text_width_family(&font_family, font_size, &c.text) {
214                b.right = b.left + result.width();
215                b.bottom = b.top + result.height();
216            }
217            let line_height = c.line_height.unwrap_or_default();
218            // 设置了行高
219            if line_height > font_size {
220                c.dy = Some(c.dy.unwrap_or_default() + line_height / 2.0);
221                c.dominant_baseline = Some("middle".to_string());
222                b.bottom = b.top + line_height;
223            }
224        }
225
226        self.append(Component::Text(c));
227        b
228    }
229    /// Appends pie widget to canvas.
230    pub fn pie(&mut self, pie: Pie) -> Box {
231        let mut c = pie;
232        c.cx += self.margin.left;
233        c.cy += self.margin.top;
234        let b = Box {
235            left: self.margin.left,
236            top: self.margin.top,
237            right: self.margin.left + c.r * 2.0,
238            bottom: self.margin.top + c.r * 2.0,
239        };
240
241        self.append(Component::Pie(c));
242        b
243    }
244    /// Appends smooth line points widget to canvas.
245    pub fn smooth_line(&mut self, line: SmoothLine) -> Box {
246        let mut c = line;
247        for p in c.points.iter_mut() {
248            p.x += self.margin.left;
249            p.y += self.margin.top
250        }
251        let b = get_box_of_points(&c.points);
252        self.append(Component::SmoothLine(c));
253        b
254    }
255    /// Appends straight line points widget to canvas.
256    pub fn straight_line(&mut self, line: StraightLine) -> Box {
257        let mut c = line;
258        for p in c.points.iter_mut() {
259            p.x += self.margin.left;
260            p.y += self.margin.top
261        }
262        let b = get_box_of_points(&c.points);
263        self.append(Component::StraightLine(c));
264        b
265    }
266    /// Appends smooth line points with fill color widget to canvas.
267    pub fn smooth_line_fill(&mut self, fill: SmoothLineFill) -> Box {
268        let mut c = fill;
269        for p in c.points.iter_mut() {
270            p.x += self.margin.left;
271            p.y += self.margin.top
272        }
273        c.bottom += self.margin.top;
274        let mut b = get_box_of_points(&c.points);
275        b.bottom = c.bottom;
276        self.append(Component::SmoothLineFill(c));
277        b
278    }
279    /// Appends straight line points with fill color widget to canvas.
280    pub fn straight_line_fill(&mut self, fill: StraightLineFill) -> Box {
281        let mut c = fill;
282        for p in c.points.iter_mut() {
283            p.x += self.margin.left;
284            p.y += self.margin.top
285        }
286        c.bottom += self.margin.top;
287        let mut b = get_box_of_points(&c.points);
288        b.bottom = c.bottom;
289        self.append(Component::StraightLineFill(c));
290        b
291    }
292    // Appends grid widget to canvas.
293    pub fn grid(&mut self, grip: Grid) -> Box {
294        let mut c = grip;
295        c.left += self.margin.left;
296        c.right += self.margin.left;
297        c.top += self.margin.top;
298        c.bottom += self.margin.top;
299        let b = Box {
300            left: c.left,
301            top: c.top,
302            right: c.right,
303            bottom: c.bottom,
304        };
305        self.append(Component::Grid(c));
306        b
307    }
308    // Appends axis widget to canvas.
309    pub fn axis(&mut self, axis: Axis) -> Box {
310        let mut c = axis;
311        c.left += self.margin.left;
312        c.top += self.margin.top;
313        let b = Box {
314            left: c.left,
315            top: c.top,
316            right: c.left + c.width,
317            bottom: c.top + c.height,
318        };
319        self.append(Component::Axis(c));
320        b
321    }
322    /// Appends lenged widget to canvas.
323    pub fn legend(&mut self, legend: Legend) -> Box {
324        let mut c = legend;
325        c.left += self.margin.left;
326        c.top += self.margin.top;
327        let measurement =
328            measure_text_width_family(&c.font_family, c.font_size, &c.text).unwrap_or_default();
329        let b = Box {
330            left: c.left,
331            top: c.top,
332            right: c.left + measurement.width() + LEGEND_WIDTH,
333            bottom: c.top + measurement.height(),
334        };
335        self.append(Component::Legend(c));
336        b
337    }
338    /// Appends bubble widget to canvas.
339    pub fn bubble(&mut self, bubble: Bubble) -> Box {
340        let mut c = bubble;
341        c.x += self.margin.left;
342        c.y += self.margin.top;
343        let b = Box {
344            left: c.x - c.r,
345            top: c.y - c.r,
346            right: c.x + c.r,
347            bottom: c.y + c.r,
348        };
349        self.append(Component::Bubble(c));
350        b
351    }
352    pub fn append(&mut self, component: Component) {
353        let mut components = self.components.borrow_mut();
354        components.push(component);
355    }
356    /// Generates the svg of canvas.
357    pub fn svg(&self) -> Result<String> {
358        let mut data = vec![];
359        for c in self.components.borrow().iter() {
360            let value = match c {
361                Component::Line(c) => c.svg(),
362                Component::Rect(c) => c.svg(),
363                Component::Arrow(c) => c.svg(),
364                Component::Bubble(c) => c.svg(),
365                Component::Polyline(c) => c.svg(),
366                Component::Circle(c) => c.svg(),
367                Component::Polygon(c) => c.svg(),
368                Component::Text(c) => c.svg(),
369                Component::SmoothLine(c) => c.svg(),
370                Component::StraightLine(c) => c.svg(),
371                Component::SmoothLineFill(c) => c.svg(),
372                Component::StraightLineFill(c) => c.svg(),
373                Component::Grid(c) => c.svg(),
374                Component::Axis(c) => c.svg().context(ToSVGSnafu)?,
375                Component::Legend(c) => c.svg(),
376                Component::Pie(c) => c.svg(),
377            };
378            data.push(value);
379        }
380        Ok(generate_svg(
381            self.width,
382            self.height,
383            self.x,
384            self.y,
385            data.join("\n"),
386        ))
387    }
388}
389
390#[cfg(test)]
391mod tests {
392    use super::Canvas;
393    use crate::{
394        convert_to_points, Align, Axis, Grid, Legend, LegendCategory, Line, Polyline, Rect,
395        SmoothLine, SmoothLineFill, StraightLine, StraightLineFill, Symbol, Text,
396        DEFAULT_FONT_FAMILY,
397    };
398    use pretty_assertions::assert_eq;
399    #[test]
400    fn canvas_width_height() {
401        let mut c = Canvas::new(400.0, 300.0);
402
403        assert_eq!("(0,0,0,0)", c.margin.to_string());
404        assert_eq!(400.0, c.width());
405        assert_eq!(300.0, c.height());
406        c = c.child((5.0, 10.0, 15.0, 20.0).into());
407        assert_eq!("(5,10,15,20)", c.margin.to_string());
408        assert_eq!(380.0, c.width());
409        assert_eq!(270.0, c.height());
410    }
411    #[test]
412    fn canvas_line() {
413        let mut c = Canvas::new(400.0, 300.0);
414        let b = c.line(Line {
415            color: Some((0, 0, 0).into()),
416            left: 5.0,
417            top: 5.0,
418            right: 50.0,
419            bottom: 20.0,
420            ..Default::default()
421        });
422        assert_eq!("(5,5,50,20)", b.to_string());
423        assert_eq!(
424            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
425<line stroke-width="1" x1="5" y1="5" x2="50" y2="20" stroke="#000000"/>
426</svg>"###,
427            c.svg().unwrap()
428        );
429    }
430    #[test]
431    fn canvas_rect() {
432        let mut c = Canvas::new(400.0, 300.0);
433        let b = c.rect(Rect {
434            color: Some((0, 0, 0).into()),
435            fill: Some((0, 255, 0).into()),
436            left: 10.0,
437            top: 10.0,
438            width: 100.0,
439            height: 30.0,
440            rx: Some(3.0),
441            ry: Some(5.0),
442        });
443        assert_eq!("(10,10,110,40)", b.to_string());
444        assert_eq!(
445            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
446<rect x="10" y="10" width="100" height="30" rx="3" ry="5" stroke="#000000" fill="#00FF00"/>
447</svg>"###,
448            c.svg().unwrap()
449        );
450    }
451    #[test]
452    fn canvas_polyline() {
453        let mut c = Canvas::new(400.0, 300.0);
454        let b = c.polyline(Polyline {
455            color: Some((0, 0, 0).into()),
456            stroke_width: 1.0,
457            points: convert_to_points(&[(1.0, 5.0), (30.0, 60.0), (50.0, 10.0), (70.0, 40.0)]),
458        });
459        assert_eq!("(1,5,70,60)", b.to_string());
460        assert_eq!(
461            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
462<polyline fill="none" stroke-width="1" points="1,5 30,60 50,10 70,40" stroke="#000000"/>
463</svg>"###,
464            c.svg().unwrap()
465        );
466    }
467    #[test]
468    fn canvas_text() {
469        let mut c = Canvas::new(400.0, 300.0);
470        let b = c.text(Text {
471            text: "Hello World!".to_string(),
472            font_family: Some(DEFAULT_FONT_FAMILY.to_string()),
473            font_size: Some(14.0),
474            font_color: Some((0, 0, 0).into()),
475            font_weight: Some("bold".to_string()),
476            line_height: Some(30.0),
477            ..Default::default()
478        });
479        assert_eq!("(0,0,79,30)", b.to_string());
480        assert_eq!(
481            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
482<text font-size="14" x="0" y="0" dy="15" font-weight="bold" dominant-baseline="middle" font-family="Roboto" fill="#000000">
483Hello World!
484</text>
485</svg>"###,
486            c.svg().unwrap()
487        );
488    }
489    #[test]
490    fn canvas_smooth_line() {
491        let mut c = Canvas::new(400.0, 300.0);
492        let b = c.smooth_line(SmoothLine {
493            color: Some((0, 0, 0).into()),
494            points: convert_to_points(&[
495                (10.0, 10.0),
496                (30.0, 50.0),
497                (50.0, 80.0),
498                (70.0, 20.0),
499                (90.0, 40.0),
500            ]),
501            stroke_width: 1.0,
502            symbol: Some(Symbol::Circle(3.0, Some((0, 255, 0).into()))),
503            ..Default::default()
504        });
505        assert_eq!("(10,10,90,80)", b.to_string());
506        assert_eq!(
507            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
508<g>
509<path d="M10,10 C15 20, 24.5 40.3, 30 50 C34.5 57.8, 46.4 82.7, 50 80 C56.4 75.2, 63.1 26.9, 70 20 C73.1 16.9, 85 35, 90 40" stroke-width="1" fill="none" stroke="#000000"/>
510<circle cx="10" cy="10" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
511<circle cx="30" cy="50" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
512<circle cx="50" cy="80" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
513<circle cx="70" cy="20" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
514<circle cx="90" cy="40" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
515</g>
516</svg>"###,
517            c.svg().unwrap()
518        );
519    }
520    #[test]
521    fn canvas_straight_line() {
522        let mut c = Canvas::new(400.0, 300.0);
523        let b = c.straight_line(StraightLine {
524            color: Some((0, 0, 0).into()),
525            points: convert_to_points(&[
526                (10.0, 10.0),
527                (30.0, 50.0),
528                (50.0, 80.0),
529                (70.0, 20.0),
530                (90.0, 40.0),
531            ]),
532            stroke_width: 1.0,
533            symbol: Some(Symbol::Circle(3.0, Some((0, 255, 0).into()))),
534            ..Default::default()
535        });
536        assert_eq!("(10,10,90,80)", b.to_string());
537        assert_eq!(
538            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
539<g>
540<path d="M 10 10 L 30 50 L 50 80 L 70 20 L 90 40" stroke-width="1" fill="none" stroke="#000000"/>
541<circle cx="10" cy="10" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
542<circle cx="30" cy="50" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
543<circle cx="50" cy="80" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
544<circle cx="70" cy="20" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
545<circle cx="90" cy="40" r="3" stroke-width="1" stroke="#000000" fill="#00FF00"/>
546</g>
547</svg>"###,
548            c.svg().unwrap()
549        );
550    }
551
552    #[test]
553    fn canvas_smooth_line_fill() {
554        let mut c = Canvas::new(400.0, 300.0);
555        let b = c.smooth_line_fill(SmoothLineFill {
556            fill: (0, 0, 0).into(),
557            points: convert_to_points(&[
558                (10.0, 10.0),
559                (30.0, 50.0),
560                (50.0, 80.0),
561                (70.0, 20.0),
562                (90.0, 40.0),
563            ]),
564            bottom: 150.0,
565        });
566        assert_eq!("(10,10,90,150)", b.to_string());
567        assert_eq!(
568            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
569<path d="M10,10 C15 20, 24.5 40.3, 30 50 C34.5 57.8, 46.4 82.7, 50 80 C56.4 75.2, 63.1 26.9, 70 20 C73.1 16.9, 85 35, 90 40M 90 40 L 90 150 L 10 150 L 10 10" fill="#000000"/>
570</svg>"###,
571            c.svg().unwrap()
572        );
573    }
574    #[test]
575    fn canvas_straight_line_fill() {
576        let mut c = Canvas::new(400.0, 300.0);
577        let b = c.straight_line_fill(StraightLineFill {
578            fill: (0, 0, 0).into(),
579            points: convert_to_points(&[
580                (10.0, 10.0),
581                (30.0, 50.0),
582                (50.0, 80.0),
583                (70.0, 20.0),
584                (90.0, 40.0),
585            ]),
586            bottom: 150.0,
587            ..Default::default()
588        });
589        assert_eq!("(10,10,90,150)", b.to_string());
590        assert_eq!(
591            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
592<path d="M 10 10 L 30 50 L 50 80 L 70 20 L 90 40 L 90 150 L 10 150 L 10 10" fill="#000000"/>
593</svg>"###,
594            c.svg().unwrap()
595        );
596    }
597    #[test]
598    fn canvas_grid() {
599        let mut c = Canvas::new(400.0, 300.0);
600        let b = c.grid(Grid {
601            left: 10.0,
602            top: 10.0,
603            right: 390.0,
604            bottom: 290.0,
605            color: Some((0, 0, 0).into()),
606            stroke_width: 1.0,
607            verticals: 5,
608            hidden_verticals: vec![0],
609            horizontals: 6,
610            hidden_horizontals: vec![6],
611        });
612        assert_eq!("(10,10,390,290)", b.to_string());
613        assert_eq!(
614            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
615<g stroke="#000000">
616<line stroke-width="1" x1="86" y1="10" x2="86" y2="290"/><line stroke-width="1" x1="162" y1="10" x2="162" y2="290"/><line stroke-width="1" x1="238" y1="10" x2="238" y2="290"/><line stroke-width="1" x1="314" y1="10" x2="314" y2="290"/><line stroke-width="1" x1="390" y1="10" x2="390" y2="290"/><line stroke-width="1" x1="10" y1="10" x2="390" y2="10"/><line stroke-width="1" x1="10" y1="56.7" x2="390" y2="56.7"/><line stroke-width="1" x1="10" y1="103.3" x2="390" y2="103.3"/><line stroke-width="1" x1="10" y1="150" x2="390" y2="150"/><line stroke-width="1" x1="10" y1="196.7" x2="390" y2="196.7"/><line stroke-width="1" x1="10" y1="243.3" x2="390" y2="243.3"/>
617</g>
618</svg>"###,
619            c.svg().unwrap()
620        );
621    }
622    #[test]
623    fn canvas_axis() {
624        let mut c = Canvas::new(400.0, 300.0);
625        let b = c.axis(Axis {
626            data: vec![
627                "Mon".to_string(),
628                "Tue".to_string(),
629                "Wed".to_string(),
630                "Thu".to_string(),
631                "Fri".to_string(),
632                "Sat".to_string(),
633                "Sun".to_string(),
634            ],
635            left: 5.0,
636            top: 5.0,
637            width: 390.0,
638            stroke_color: Some((0, 0, 0).into()),
639            ..Default::default()
640        });
641        assert_eq!("(5,5,395,5)", b.to_string());
642        assert_eq!(
643            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
644<g>
645<g stroke="#000000">
646<line stroke-width="1" x1="5" y1="5" x2="395" y2="5"/>
647<line stroke-width="1" x1="5" y1="5" x2="5" y2="10"/>
648<line stroke-width="1" x1="60.7" y1="5" x2="60.7" y2="10"/>
649<line stroke-width="1" x1="116.4" y1="5" x2="116.4" y2="10"/>
650<line stroke-width="1" x1="172.1" y1="5" x2="172.1" y2="10"/>
651<line stroke-width="1" x1="227.9" y1="5" x2="227.9" y2="10"/>
652<line stroke-width="1" x1="283.6" y1="5" x2="283.6" y2="10"/>
653<line stroke-width="1" x1="339.3" y1="5" x2="339.3" y2="10"/>
654<line stroke-width="1" x1="395" y1="5" x2="395" y2="10"/>
655</g>
656<text font-size="14" x="18.9" y="24" font-family="Roboto">
657Mon
658</text>
659<text font-size="14" x="76.6" y="24" font-family="Roboto">
660Tue
661</text>
662<text font-size="14" x="130.3" y="24" font-family="Roboto">
663Wed
664</text>
665<text font-size="14" x="188" y="24" font-family="Roboto">
666Thu
667</text>
668<text font-size="14" x="247.7" y="24" font-family="Roboto">
669Fri
670</text>
671<text font-size="14" x="300.4" y="24" font-family="Roboto">
672Sat
673</text>
674<text font-size="14" x="355.1" y="24" font-family="Roboto">
675Sun
676</text>
677</g>
678</svg>"###,
679            c.svg().unwrap()
680        );
681
682        // split number
683        let mut c = Canvas::new(400.0, 300.0);
684        let b = c.axis(Axis {
685            data: vec![
686                "Mon".to_string(),
687                "Tue".to_string(),
688                "Wed".to_string(),
689                "Thu".to_string(),
690                "Fri".to_string(),
691                "Sat".to_string(),
692            ],
693            left: 5.0,
694            top: 5.0,
695            width: 390.0,
696            split_number: 3,
697            stroke_color: Some((0, 0, 0).into()),
698            ..Default::default()
699        });
700        assert_eq!("(5,5,395,5)", b.to_string());
701        assert_eq!(
702            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
703<g>
704<g stroke="#000000">
705<line stroke-width="1" x1="5" y1="5" x2="395" y2="5"/>
706<line stroke-width="1" x1="5" y1="5" x2="5" y2="10"/>
707<line stroke-width="1" x1="135" y1="5" x2="135" y2="10"/>
708<line stroke-width="1" x1="265" y1="5" x2="265" y2="10"/>
709<line stroke-width="1" x1="395" y1="5" x2="395" y2="10"/>
710</g>
711<text font-size="14" x="23.5" y="24" font-family="Roboto">
712Mon
713</text>
714<text font-size="14" x="90.5" y="24" font-family="Roboto">
715Tue
716</text>
717<text font-size="14" x="153.5" y="24" font-family="Roboto">
718Wed
719</text>
720<text font-size="14" x="220.5" y="24" font-family="Roboto">
721Thu
722</text>
723<text font-size="14" x="289.5" y="24" font-family="Roboto">
724Fri
725</text>
726<text font-size="14" x="351.5" y="24" font-family="Roboto">
727Sat
728</text>
729</g>
730</svg>"###,
731            c.svg().unwrap()
732        );
733
734        // set tick interval
735        let mut c = Canvas::new(400.0, 300.0);
736        let b = c.axis(Axis {
737            data: vec![
738                "Mon".to_string(),
739                "Tue".to_string(),
740                "Wed".to_string(),
741                "Thu".to_string(),
742                "Fri".to_string(),
743                "Sat".to_string(),
744                "Sun".to_string(),
745            ],
746            left: 5.0,
747            top: 5.0,
748            width: 390.0,
749            tick_interval: 2,
750            stroke_color: Some((0, 0, 0).into()),
751            ..Default::default()
752        });
753        assert_eq!("(5,5,395,5)", b.to_string());
754        assert_eq!(
755            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
756<g>
757<g stroke="#000000">
758<line stroke-width="1" x1="5" y1="5" x2="395" y2="5"/>
759<line stroke-width="1" x1="5" y1="5" x2="5" y2="10"/>
760<line stroke-width="1" x1="116.4" y1="5" x2="116.4" y2="10"/>
761<line stroke-width="1" x1="227.9" y1="5" x2="227.9" y2="10"/>
762<line stroke-width="1" x1="339.3" y1="5" x2="339.3" y2="10"/>
763</g>
764<text font-size="14" x="18.9" y="24" font-family="Roboto">
765Mon
766</text>
767<text font-size="14" x="76.6" y="24" font-family="Roboto">
768Tue
769</text>
770<text font-size="14" x="130.3" y="24" font-family="Roboto">
771Wed
772</text>
773<text font-size="14" x="188" y="24" font-family="Roboto">
774Thu
775</text>
776<text font-size="14" x="247.7" y="24" font-family="Roboto">
777Fri
778</text>
779<text font-size="14" x="300.4" y="24" font-family="Roboto">
780Sat
781</text>
782<text font-size="14" x="355.1" y="24" font-family="Roboto">
783Sun
784</text>
785</g>
786</svg>"###,
787            c.svg().unwrap()
788        );
789
790        // name align left
791        let mut c = Canvas::new(400.0, 300.0);
792        let b = c.axis(Axis {
793            data: vec![
794                "Mon".to_string(),
795                "Tue".to_string(),
796                "Wed".to_string(),
797                "Thu".to_string(),
798                "Fri".to_string(),
799                "Sat".to_string(),
800                "Sun".to_string(),
801            ],
802            left: 20.0,
803            top: 5.0,
804            width: 360.0,
805            stroke_color: Some((0, 0, 0).into()),
806            name_align: Align::Left,
807            ..Default::default()
808        });
809        assert_eq!("(20,5,380,5)", b.to_string());
810        assert_eq!(
811            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
812<g>
813<g stroke="#000000">
814<line stroke-width="1" x1="20" y1="5" x2="380" y2="5"/>
815<line stroke-width="1" x1="20" y1="5" x2="20" y2="10"/>
816<line stroke-width="1" x1="71.4" y1="5" x2="71.4" y2="10"/>
817<line stroke-width="1" x1="122.9" y1="5" x2="122.9" y2="10"/>
818<line stroke-width="1" x1="174.3" y1="5" x2="174.3" y2="10"/>
819<line stroke-width="1" x1="225.7" y1="5" x2="225.7" y2="10"/>
820<line stroke-width="1" x1="277.1" y1="5" x2="277.1" y2="10"/>
821<line stroke-width="1" x1="328.6" y1="5" x2="328.6" y2="10"/>
822<line stroke-width="1" x1="380" y1="5" x2="380" y2="10"/>
823</g>
824<text font-size="14" x="6" y="24" font-family="Roboto">
825Mon
826</text>
827<text font-size="14" x="68" y="24" font-family="Roboto">
828Tue
829</text>
830<text font-size="14" x="126" y="24" font-family="Roboto">
831Wed
832</text>
833<text font-size="14" x="188" y="24" font-family="Roboto">
834Thu
835</text>
836<text font-size="14" x="252" y="24" font-family="Roboto">
837Fri
838</text>
839<text font-size="14" x="309" y="24" font-family="Roboto">
840Sat
841</text>
842<text font-size="14" x="368" y="24" font-family="Roboto">
843Sun
844</text>
845</g>
846</svg>"###,
847            c.svg().unwrap()
848        );
849    }
850
851    #[test]
852    fn canvas_legend() {
853        let mut c = Canvas::new(400.0, 300.0);
854        let b = c.legend(Legend {
855            text: "Email".to_string(),
856            font_size: 14.0,
857            font_family: DEFAULT_FONT_FAMILY.to_string(),
858            font_color: Some((0, 0, 0).into()),
859            stroke_color: Some((0, 0, 0).into()),
860            fill: Some((0, 0, 0).into()),
861            left: 10.0,
862            top: 10.0,
863            category: LegendCategory::Normal,
864            ..Default::default()
865        });
866        assert_eq!("(10,10,71,24)", b.to_string());
867        assert_eq!(
868            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
869<g>
870<line stroke-width="2" x1="10" y1="20" x2="35" y2="20" stroke="#000000"/>
871<circle cx="22.5" cy="20" r="5.5" stroke-width="2" stroke="#000000" fill="#000000"/>
872<text font-size="14" x="38" y="24" font-family="Roboto" fill="#000000">
873Email
874</text>
875</g>
876</svg>"###,
877            c.svg().unwrap()
878        );
879
880        let mut c = Canvas::new(400.0, 300.0);
881        let b = c.legend(Legend {
882            text: "Email".to_string(),
883            font_size: 14.0,
884            font_family: DEFAULT_FONT_FAMILY.to_string(),
885            font_color: Some((0, 0, 0).into()),
886            stroke_color: Some((0, 0, 0).into()),
887            fill: Some((0, 0, 0).into()),
888            left: 10.0,
889            top: 10.0,
890            category: LegendCategory::Rect,
891            ..Default::default()
892        });
893        assert_eq!("(10,10,71,24)", b.to_string());
894        assert_eq!(
895            r###"<svg width="400" height="300" viewBox="0 0 400 300" xmlns="http://www.w3.org/2000/svg">
896<g>
897<rect x="10" y="15" width="25" height="10" stroke="#000000" fill="#000000"/>
898<text font-size="14" x="38" y="24" font-family="Roboto" fill="#000000">
899Email
900</text>
901</g>
902</svg>"###,
903            c.svg().unwrap()
904        );
905    }
906}