1use super::canvas;
14use super::component::generate_svg;
15use super::component::Rect;
16use super::params::{get_color_from_value, get_f32_from_value, get_margin_from_value};
17use super::{
18 BarChart, CandlestickChart, CanvasResult, HorizontalBarChart, LineChart, PieChart, RadarChart,
19 ScatterChart, TableChart,
20};
21use super::{Box, Color};
22use substring::Substring;
23
24pub enum ChildChart {
25 Bar(BarChart, Option<(f32, f32)>),
26 Candlestick(CandlestickChart, Option<(f32, f32)>),
27 HorizontalBar(HorizontalBarChart, Option<(f32, f32)>),
28 Line(LineChart, Option<(f32, f32)>),
29 Pie(PieChart, Option<(f32, f32)>),
30 Radar(RadarChart, Option<(f32, f32)>),
31 Scatter(ScatterChart, Option<(f32, f32)>),
32 Table(TableChart, Option<(f32, f32)>),
33}
34#[derive(Default)]
35pub struct MultiChart {
36 pub charts: Vec<ChildChart>,
37 pub gap: f32,
38 pub margin: Box,
39 pub background_color: Option<Color>,
40}
41struct ChildChartResult {
42 svg: String,
43 right: f32,
44 bottom: f32,
45}
46
47impl MultiChart {
48 pub fn from_json(data: &str) -> canvas::Result<MultiChart> {
50 let value: serde_json::Value = serde_json::from_str(data)?;
51 let mut theme = "".to_string();
52 if let Some(value) = value.get("theme") {
53 theme = value.to_string();
54 }
55 let mut multi_chart = MultiChart::new();
56 if let Some(margin) = get_margin_from_value(&value, "margin") {
57 multi_chart.margin = margin;
58 }
59 if let Some(gap) = get_f32_from_value(&value, "gap") {
60 multi_chart.gap = gap;
61 }
62 if let Some(background_color) = get_color_from_value(&value, "background_color") {
63 multi_chart.background_color = Some(background_color);
64 }
65 if let Some(child_charts) = value.get("child_charts") {
66 if let Some(values) = child_charts.as_array() {
67 for item in values.iter() {
68 let chart_type = if let Some(value) = item.get("type") {
69 value.as_str().unwrap_or_default()
70 } else {
71 ""
72 };
73 let mut x = 0.0;
74 let mut y = 0.0;
75 let mut exists_position = false;
76 if let Some(v) = get_f32_from_value(item, "x") {
77 x = v;
78 exists_position = true;
79 }
80 if let Some(v) = get_f32_from_value(item, "y") {
81 y = v;
82 exists_position = true;
83 }
84 let mut position = None;
85 if exists_position {
86 position = Some((x, y));
87 }
88
89 let mut str = serde_json::to_string(item).unwrap();
91 if item.get("theme").is_none() {
92 str = format!(
93 r###"{},"theme":{theme}}}"###,
94 str.substring(0, str.len() - 1)
95 );
96 }
97 match chart_type {
98 "line" => {
99 let chart = LineChart::from_json(&str)?;
100 multi_chart.add(ChildChart::Line(chart, position));
101 }
102 "horizontal_bar" => {
103 let chart = HorizontalBarChart::from_json(&str)?;
104 multi_chart.add(ChildChart::HorizontalBar(chart, position));
105 }
106 "pie" => {
107 let chart = PieChart::from_json(&str)?;
108 multi_chart.add(ChildChart::Pie(chart, position));
109 }
110 "radar" => {
111 let chart = RadarChart::from_json(&str)?;
112 multi_chart.add(ChildChart::Radar(chart, position));
113 }
114 "table" => {
115 let chart = TableChart::from_json(&str)?;
116 multi_chart.add(ChildChart::Table(chart, position));
117 }
118 "scatter" => {
119 let chart = ScatterChart::from_json(&str)?;
120 multi_chart.add(ChildChart::Scatter(chart, position));
121 }
122 "candlestick" => {
123 let chart = CandlestickChart::from_json(&str)?;
124 multi_chart.add(ChildChart::Candlestick(chart, position));
125 }
126 _ => {
127 let chart = BarChart::from_json(&str)?;
128 multi_chart.add(ChildChart::Bar(chart, position));
129 }
130 };
131 }
132 }
133 }
134 Ok(multi_chart)
135 }
136 pub fn new() -> MultiChart {
138 MultiChart {
139 charts: vec![],
140 gap: 10.0,
141 margin: (10.0).into(),
142 ..Default::default()
143 }
144 }
145 pub fn add(&mut self, c: ChildChart) {
147 self.charts.push(c);
148 }
149 pub fn svg(&mut self) -> CanvasResult<String> {
151 let mut arr = vec![];
152 let mut y = 0.0;
153 let mut x = 0.0;
154 let margin_top = self.margin.top;
155 let margin_left = self.margin.left;
156 for item in self.charts.iter_mut() {
157 let result = match item {
158 ChildChart::Bar(c, position) => {
159 c.y = y;
160 if let Some((x, y)) = position {
162 y.clone_into(&mut c.y);
163 x.clone_into(&mut c.x);
164 } else if y == 0.0 {
165 c.y = margin_top;
166 } else {
167 y += self.gap;
169 c.y = y;
170 }
171 if position.is_none() {
172 c.x = c.x.max(margin_left);
173 }
174
175 ChildChartResult {
176 svg: c.svg()?,
177 right: c.x + c.width,
178 bottom: c.y + c.height,
179 }
180 }
181 ChildChart::Candlestick(c, position) => {
182 c.y = y;
183 if let Some((x, y)) = position {
184 y.clone_into(&mut c.y);
185 x.clone_into(&mut c.x);
186 } else if y == 0.0 {
187 c.y = margin_top;
188 } else {
189 y += self.gap;
191 c.y = y;
192 }
193 if position.is_none() {
194 c.x = c.x.max(margin_left);
195 }
196
197 ChildChartResult {
198 svg: c.svg()?,
199 right: c.x + c.width,
200 bottom: c.y + c.height,
201 }
202 }
203 ChildChart::HorizontalBar(c, position) => {
204 c.y = y;
205 if let Some((x, y)) = position {
206 y.clone_into(&mut c.y);
207 x.clone_into(&mut c.x);
208 } else if y == 0.0 {
209 c.y = margin_top;
210 } else {
211 y += self.gap;
212 c.y = y;
213 }
214 if position.is_none() {
215 c.x = c.x.max(margin_left);
216 }
217
218 ChildChartResult {
219 svg: c.svg()?,
220 right: c.x + c.width,
221 bottom: c.y + c.height,
222 }
223 }
224 ChildChart::Line(c, position) => {
225 c.y = y;
226 if let Some((x, y)) = position {
227 y.clone_into(&mut c.y);
228 x.clone_into(&mut c.x);
229 } else if y == 0.0 {
230 c.y = margin_top;
231 } else {
232 y += self.gap;
233 c.y = y;
234 }
235 if position.is_none() {
236 c.x = c.x.max(margin_left);
237 }
238
239 ChildChartResult {
240 svg: c.svg()?,
241 right: c.x + c.width,
242 bottom: c.y + c.height,
243 }
244 }
245 ChildChart::Pie(c, position) => {
246 c.y = y;
247 if let Some((x, y)) = position {
248 y.clone_into(&mut c.y);
249 x.clone_into(&mut c.x);
250 } else if y == 0.0 {
251 c.y = margin_top;
252 } else {
253 y += self.gap;
254 c.y = y;
255 }
256 if position.is_none() {
257 c.x = c.x.max(margin_left);
258 }
259
260 ChildChartResult {
261 svg: c.svg()?,
262 right: c.x + c.width,
263 bottom: c.y + c.height,
264 }
265 }
266 ChildChart::Radar(c, position) => {
267 c.y = y;
268 if let Some((x, y)) = position {
269 y.clone_into(&mut c.y);
270 x.clone_into(&mut c.x);
271 } else if y == 0.0 {
272 c.y = margin_top;
273 } else {
274 y += self.gap;
275 c.y = y;
276 }
277 if position.is_none() {
278 c.x = c.x.max(margin_left);
279 }
280
281 ChildChartResult {
282 svg: c.svg()?,
283 right: c.x + c.width,
284 bottom: c.y + c.height,
285 }
286 }
287 ChildChart::Scatter(c, position) => {
288 c.y = y;
289 if let Some((x, y)) = position {
290 y.clone_into(&mut c.y);
291 x.clone_into(&mut c.x);
292 } else if y == 0.0 {
293 c.y = margin_top;
294 } else {
295 y += self.gap;
296 c.y = y;
297 }
298 if position.is_none() {
299 c.x = c.x.max(margin_left);
300 }
301
302 ChildChartResult {
303 svg: c.svg()?,
304 right: c.x + c.width,
305 bottom: c.y + c.height,
306 }
307 }
308 ChildChart::Table(c, position) => {
309 c.y = y;
310 if let Some((x, y)) = position {
311 y.clone_into(&mut c.y);
312 x.clone_into(&mut c.x);
313 } else if y == 0.0 {
314 c.y = margin_top;
315 } else {
316 y += self.gap;
317 c.y = y;
318 }
319 if position.is_none() {
320 c.x = c.x.max(margin_left);
321 }
322 let svg = c.svg()?;
324 ChildChartResult {
325 svg,
326 right: c.x + c.width,
327 bottom: c.y + c.height,
328 }
329 }
330 };
331 if result.bottom > y {
332 y = result.bottom;
333 }
334 if result.right > x {
335 x = result.right;
336 }
337 arr.push(result.svg);
338 }
339 x += self.margin.right;
340 y += self.margin.bottom;
341
342 if let Some(background_color) = self.background_color {
343 arr.insert(
344 0,
345 Rect {
346 fill: Some(background_color),
347 left: 0.0,
348 top: 0.0,
349 width: x,
350 height: y,
351 ..Default::default()
352 }
353 .svg(),
354 );
355 }
356
357 Ok(generate_svg(x, y, 0.0, 0.0, arr.join("\n")))
358 }
359}
360
361#[cfg(test)]
362mod tests {
363 use super::{ChildChart, MultiChart};
364 use crate::{
365 BarChart, CandlestickChart, HorizontalBarChart, LineChart, PieChart, RadarChart,
366 ScatterChart, TableChart,
367 };
368 use pretty_assertions::assert_eq;
369 #[test]
370 fn multi_chart() {
371 let mut charts = MultiChart::new();
372 charts.margin = (10.0).into();
373 charts.background_color = Some((31, 29, 29, 150).into());
374
375 let bar_chart = BarChart::new(
376 vec![
377 (
378 "Email",
379 vec![120.0, 132.0, 101.0, 134.0, 90.0, 230.0, 210.0],
380 )
381 .into(),
382 (
383 "Union Ads",
384 vec![220.0, 182.0, 191.0, 234.0, 290.0, 330.0, 310.0],
385 )
386 .into(),
387 (
388 "Direct",
389 vec![320.0, 332.0, 301.0, 334.0, 390.0, 330.0, 320.0],
390 )
391 .into(),
392 (
393 "Search Engine",
394 vec![820.0, 932.0, 901.0, 934.0, 1290.0, 1330.0, 1320.0],
395 )
396 .into(),
397 ],
398 vec![
399 "Mon".to_string(),
400 "Tue".to_string(),
401 "Wed".to_string(),
402 "Thu".to_string(),
403 "Fri".to_string(),
404 "Sat".to_string(),
405 "Sun".to_string(),
406 ],
407 );
408 charts.add(ChildChart::Bar(bar_chart, None));
409
410 let candlestick_chart = CandlestickChart::new(
411 vec![(
412 "",
413 vec![
414 20.0, 34.0, 10.0, 38.0, 40.0, 35.0, 30.0, 50.0, 31.0, 38.0, 33.0, 44.0, 38.0,
415 15.0, 5.0, 42.0,
416 ],
417 )
418 .into()],
419 vec![
420 "2017-10-24".to_string(),
421 "2017-10-25".to_string(),
422 "2017-10-26".to_string(),
423 "2017-10-27".to_string(),
424 ],
425 );
426 charts.add(ChildChart::Candlestick(candlestick_chart, None));
427
428 let horizontal_bar_chart = HorizontalBarChart::new(
429 vec![
430 (
431 "2011",
432 vec![18203.0, 23489.0, 29034.0, 104970.0, 131744.0, 630230.0],
433 )
434 .into(),
435 (
436 "2012",
437 vec![19325.0, 23438.0, 31000.0, 121594.0, 134141.0, 681807.0],
438 )
439 .into(),
440 ],
441 vec![
442 "Brazil".to_string(),
443 "Indonesia".to_string(),
444 "USA".to_string(),
445 "India".to_string(),
446 "China".to_string(),
447 "World".to_string(),
448 ],
449 );
450 charts.add(ChildChart::HorizontalBar(horizontal_bar_chart, None));
451
452 let line_chart = LineChart::new(
453 vec![
454 (
455 "Email",
456 vec![120.0, 132.0, 101.0, 134.0, 90.0, 230.0, 210.0],
457 )
458 .into(),
459 (
460 "Union Ads",
461 vec![220.0, 182.0, 191.0, 234.0, 290.0, 330.0, 310.0],
462 )
463 .into(),
464 (
465 "Direct",
466 vec![320.0, 332.0, 301.0, 334.0, 390.0, 330.0, 320.0],
467 )
468 .into(),
469 (
470 "Search Engine",
471 vec![820.0, 932.0, 901.0, 934.0, 1290.0, 1330.0, 1320.0],
472 )
473 .into(),
474 ],
475 vec![
476 "Mon".to_string(),
477 "Tue".to_string(),
478 "Wed".to_string(),
479 "Thu".to_string(),
480 "Fri".to_string(),
481 "Sat".to_string(),
482 "Sun".to_string(),
483 ],
484 );
485 charts.add(ChildChart::Line(line_chart, None));
486
487 let pie_chart = PieChart::new(vec![
488 ("rose 1", vec![40.0]).into(),
489 ("rose 2", vec![38.0]).into(),
490 ("rose 3", vec![32.0]).into(),
491 ("rose 4", vec![30.0]).into(),
492 ("rose 5", vec![28.0]).into(),
493 ("rose 6", vec![26.0]).into(),
494 ("rose 7", vec![22.0]).into(),
495 ("rose 8", vec![18.0]).into(),
496 ]);
497
498 charts.add(ChildChart::Pie(pie_chart, None));
499
500 let radar_chart = RadarChart::new(
501 vec![
502 (
503 "Allocated Budget",
504 vec![4200.0, 3000.0, 20000.0, 35000.0, 50000.0, 18000.0],
505 )
506 .into(),
507 (
508 "Actual Spending",
509 vec![5000.0, 14000.0, 28000.0, 26000.0, 42000.0, 21000.0],
510 )
511 .into(),
512 ],
513 vec![
514 ("Sales", 6500.0).into(),
515 ("Administration", 16000.0).into(),
516 ("Information Technology", 30000.0).into(),
517 ("Customer Support", 38000.0).into(),
518 ("Development", 52000.0).into(),
519 ("Marketing", 25000.0).into(),
520 ],
521 );
522 charts.add(ChildChart::Radar(radar_chart, None));
523
524 let scatter_chart = ScatterChart::new(vec![
525 (
526 "Female",
527 vec![
528 161.2, 51.6, 167.5, 59.0, 159.5, 49.2, 157.0, 63.0, 155.8, 53.6, 170.0, 59.0,
529 159.1, 47.6, 166.0, 69.8, 176.2, 66.8, 160.2, 75.2, 172.5, 55.2, 170.9, 54.2,
530 172.9, 62.5, 153.4, 42.0, 160.0, 50.0, 147.2, 49.8, 168.2, 49.2, 175.0, 73.2,
531 157.0, 47.8, 167.6, 68.8, 159.5, 50.6, 175.0, 82.5, 166.8, 57.2, 176.5, 87.8,
532 170.2, 72.8,
533 ],
534 )
535 .into(),
536 (
537 "Male",
538 vec![
539 174.0, 65.6, 175.3, 71.8, 193.5, 80.7, 186.5, 72.6, 187.2, 78.8, 181.5, 74.8,
540 184.0, 86.4, 184.5, 78.4, 175.0, 62.0, 184.0, 81.6, 180.0, 76.6, 177.8, 83.6,
541 192.0, 90.0, 176.0, 74.6, 174.0, 71.0, 184.0, 79.6, 192.7, 93.8, 171.5, 70.0,
542 173.0, 72.4, 176.0, 85.9, 176.0, 78.8, 180.5, 77.8, 172.7, 66.2, 176.0, 86.4,
543 173.5, 81.8,
544 ],
545 )
546 .into(),
547 ]);
548 charts.add(ChildChart::Scatter(scatter_chart, None));
549
550 let table_chart = TableChart::new(vec![
551 vec![
552 "Name".to_string(),
553 "Price".to_string(),
554 "Change".to_string(),
555 ],
556 vec![
557 "Datadog Inc".to_string(),
558 "97.32".to_string(),
559 "-7.49%".to_string(),
560 ],
561 vec![
562 "Hashicorp Inc".to_string(),
563 "28.66".to_string(),
564 "-9.25%".to_string(),
565 ],
566 vec![
567 "Gitlab Inc".to_string(),
568 "51.63".to_string(),
569 "+4.32%".to_string(),
570 ],
571 ]);
572 charts.add(ChildChart::Table(table_chart, None));
573
574 assert_eq!(
575 include_str!("../../asset/multi_chart/basic.svg"),
576 charts.svg().unwrap()
577 );
578 }
579
580 #[test]
581 fn multi_chart_override() {
582 let mut charts = MultiChart::new();
583 let bar_chart = BarChart::new(
584 vec![
585 (
586 "Email",
587 vec![120.0, 132.0, 101.0, 134.0, 90.0, 230.0, 210.0],
588 )
589 .into(),
590 (
591 "Union Ads",
592 vec![220.0, 182.0, 191.0, 234.0, 290.0, 330.0, 310.0],
593 )
594 .into(),
595 (
596 "Direct",
597 vec![320.0, 332.0, 301.0, 334.0, 390.0, 330.0, 320.0],
598 )
599 .into(),
600 (
601 "Search Engine",
602 vec![820.0, 932.0, 901.0, 934.0, 1290.0, 1330.0, 1320.0],
603 )
604 .into(),
605 ],
606 vec![
607 "Mon".to_string(),
608 "Tue".to_string(),
609 "Wed".to_string(),
610 "Thu".to_string(),
611 "Fri".to_string(),
612 "Sat".to_string(),
613 "Sun".to_string(),
614 ],
615 );
616 charts.add(ChildChart::Bar(bar_chart, None));
617
618 let mut pie_chart = PieChart::new(vec![
619 ("rose 1", vec![40.0]).into(),
620 ("rose 2", vec![38.0]).into(),
621 ("rose 3", vec![32.0]).into(),
622 ("rose 4", vec![30.0]).into(),
623 ("rose 5", vec![28.0]).into(),
624 ("rose 6", vec![26.0]).into(),
625 ("rose 7", vec![22.0]).into(),
626 ("rose 8", vec![18.0]).into(),
627 ]);
628 pie_chart.width = 400.0;
629 pie_chart.height = 200.0;
630 pie_chart.background_color = (0, 0, 0, 0).into();
631
632 charts.add(ChildChart::Pie(pie_chart, Some((200.0, 0.0))));
633
634 assert_eq!(
635 include_str!("../../asset/multi_chart/override.svg"),
636 charts.svg().unwrap()
637 );
638 }
639}