1use 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 pub fn new(width: f32, height: f32) -> Self {
62 Canvas::new_width_xy(width, height, 0.0, 0.0)
63 }
64 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 pub fn width(&self) -> f32 {
77 self.width - self.margin.left - self.margin.right
78 }
79 pub fn height(&self) -> f32 {
81 self.height - self.margin.top - self.margin.bottom
82 }
83 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}