1use sheetkit_xml::chart::{
7 Area3DChart, AreaChart, Bar3DChart, BarChart, BodyPr, BoolVal, BubbleChart, BubbleSeries,
8 CatAx, CategoryRef, Chart, ChartSpace, ChartTitle, DoughnutChart, IntVal, Layout, Legend,
9 Line3DChart, LineChart, NumRef, OfPieChart, Paragraph, Pie3DChart, PieChart, PlotArea,
10 RadarChart, RichText, Run, Scaling, ScatterChart, ScatterSeries, SerAx, SerLines, Series,
11 SeriesText, StockChart, StrRef, StringVal, Surface3DChart, SurfaceChart, TitleTx, UintVal,
12 ValAx, ValueRef, View3D,
13};
14use sheetkit_xml::drawing::{
15 AExt, CNvGraphicFramePr, CNvPr, ChartRef, ClientData, Graphic, GraphicData, GraphicFrame,
16 MarkerType, NvGraphicFramePr, Offset, TwoCellAnchor, WsDr, Xfrm,
17};
18use sheetkit_xml::namespaces;
19
20#[derive(Debug, Clone, PartialEq)]
22pub enum ChartType {
23 Col,
25 ColStacked,
27 ColPercentStacked,
29 Bar,
31 BarStacked,
33 BarPercentStacked,
35 Line,
37 Pie,
39 Area,
41 AreaStacked,
43 AreaPercentStacked,
45 Area3D,
47 Area3DStacked,
49 Area3DPercentStacked,
51 Col3D,
53 Col3DStacked,
55 Col3DPercentStacked,
57 Bar3D,
59 Bar3DStacked,
61 Bar3DPercentStacked,
63 LineStacked,
65 LinePercentStacked,
67 Line3D,
69 Pie3D,
71 Doughnut,
73 Scatter,
75 ScatterLine,
77 ScatterSmooth,
79 Radar,
81 RadarFilled,
83 RadarMarker,
85 StockHLC,
87 StockOHLC,
89 StockVHLC,
91 StockVOHLC,
93 Bubble,
95 Surface,
97 Surface3D,
99 SurfaceWireframe,
101 SurfaceWireframe3D,
103 ColLine,
105 ColLineStacked,
107 ColLinePercentStacked,
109 PieOfPie,
111 BarOfPie,
113 Col3DCone,
115 Col3DConeStacked,
117 Col3DConePercentStacked,
119 Col3DPyramid,
121 Col3DPyramidStacked,
123 Col3DPyramidPercentStacked,
125 Col3DCylinder,
127 Col3DCylinderStacked,
129 Col3DCylinderPercentStacked,
131 Contour,
133 WireframeContour,
135 Bubble3D,
137}
138
139#[derive(Debug, Clone, Default)]
141pub struct View3DConfig {
142 pub rot_x: Option<i32>,
144 pub rot_y: Option<i32>,
146 pub depth_percent: Option<u32>,
148 pub right_angle_axes: Option<bool>,
150 pub perspective: Option<u32>,
152}
153
154#[derive(Debug, Clone)]
156pub struct ChartConfig {
157 pub chart_type: ChartType,
159 pub title: Option<String>,
161 pub series: Vec<ChartSeries>,
163 pub show_legend: bool,
165 pub view_3d: Option<View3DConfig>,
167}
168
169#[derive(Debug, Clone)]
171pub struct ChartSeries {
172 pub name: String,
174 pub categories: String,
176 pub values: String,
178 pub x_values: Option<String>,
180 pub bubble_sizes: Option<String>,
182}
183
184pub fn build_chart_xml(config: &ChartConfig) -> ChartSpace {
186 let title = config.title.as_ref().map(|t| build_chart_title(t));
187 let legend = if config.show_legend {
188 Some(Legend {
189 legend_pos: StringVal {
190 val: "b".to_string(),
191 },
192 })
193 } else {
194 None
195 };
196 let view_3d = build_view_3d(config);
197 let plot_area = build_plot_area(config);
198
199 ChartSpace {
200 chart: Chart {
201 title,
202 view_3d,
203 plot_area,
204 legend,
205 plot_vis_only: Some(BoolVal { val: true }),
206 },
207 ..ChartSpace::default()
208 }
209}
210
211pub fn build_drawing_with_chart(chart_ref_id: &str, from: MarkerType, to: MarkerType) -> WsDr {
213 let graphic_frame = GraphicFrame {
214 nv_graphic_frame_pr: NvGraphicFramePr {
215 c_nv_pr: CNvPr {
216 id: 2,
217 name: "Chart 1".to_string(),
218 },
219 c_nv_graphic_frame_pr: CNvGraphicFramePr {},
220 },
221 xfrm: Xfrm {
222 off: Offset { x: 0, y: 0 },
223 ext: AExt { cx: 0, cy: 0 },
224 },
225 graphic: Graphic {
226 graphic_data: GraphicData {
227 uri: namespaces::DRAWING_ML_CHART.to_string(),
228 chart: ChartRef {
229 xmlns_c: namespaces::DRAWING_ML_CHART.to_string(),
230 r_id: chart_ref_id.to_string(),
231 },
232 },
233 },
234 };
235 let anchor = TwoCellAnchor {
236 from,
237 to,
238 graphic_frame: Some(graphic_frame),
239 pic: None,
240 shape: None,
241 client_data: ClientData {},
242 };
243 WsDr {
244 two_cell_anchors: vec![anchor],
245 ..WsDr::default()
246 }
247}
248
249fn is_no_axis_chart(ct: &ChartType) -> bool {
250 matches!(
251 ct,
252 ChartType::Pie
253 | ChartType::Pie3D
254 | ChartType::Doughnut
255 | ChartType::PieOfPie
256 | ChartType::BarOfPie
257 )
258}
259
260fn is_3d_chart(ct: &ChartType) -> bool {
261 matches!(
262 ct,
263 ChartType::Area3D
264 | ChartType::Area3DStacked
265 | ChartType::Area3DPercentStacked
266 | ChartType::Col3D
267 | ChartType::Col3DStacked
268 | ChartType::Col3DPercentStacked
269 | ChartType::Bar3D
270 | ChartType::Bar3DStacked
271 | ChartType::Bar3DPercentStacked
272 | ChartType::Line3D
273 | ChartType::Pie3D
274 | ChartType::Surface3D
275 | ChartType::SurfaceWireframe3D
276 | ChartType::Col3DCone
277 | ChartType::Col3DConeStacked
278 | ChartType::Col3DConePercentStacked
279 | ChartType::Col3DPyramid
280 | ChartType::Col3DPyramidStacked
281 | ChartType::Col3DPyramidPercentStacked
282 | ChartType::Col3DCylinder
283 | ChartType::Col3DCylinderStacked
284 | ChartType::Col3DCylinderPercentStacked
285 )
286}
287
288fn needs_ser_ax(ct: &ChartType) -> bool {
289 matches!(
290 ct,
291 ChartType::Surface
292 | ChartType::Surface3D
293 | ChartType::SurfaceWireframe
294 | ChartType::SurfaceWireframe3D
295 | ChartType::Contour
296 | ChartType::WireframeContour
297 )
298}
299
300fn build_view_3d(config: &ChartConfig) -> Option<View3D> {
301 if let Some(v) = &config.view_3d {
302 return Some(View3D {
303 rot_x: v.rot_x.map(|val| IntVal { val }),
304 rot_y: v.rot_y.map(|val| IntVal { val }),
305 depth_percent: v.depth_percent.map(|val| UintVal { val }),
306 r_ang_ax: v.right_angle_axes.map(|val| BoolVal { val }),
307 perspective: v.perspective.map(|val| UintVal { val }),
308 });
309 }
310 if is_3d_chart(&config.chart_type) {
311 Some(View3D {
312 rot_x: Some(IntVal { val: 15 }),
313 rot_y: Some(IntVal { val: 20 }),
314 depth_percent: None,
315 r_ang_ax: Some(BoolVal { val: true }),
316 perspective: Some(UintVal { val: 30 }),
317 })
318 } else {
319 None
320 }
321}
322
323fn build_series_text(series: &ChartSeries) -> Option<SeriesText> {
324 if series.name.is_empty() {
325 None
326 } else if series.name.contains('!') {
327 Some(SeriesText {
328 str_ref: Some(StrRef {
329 f: series.name.clone(),
330 }),
331 v: None,
332 })
333 } else {
334 Some(SeriesText {
335 str_ref: None,
336 v: Some(series.name.clone()),
337 })
338 }
339}
340
341fn build_series(index: u32, series: &ChartSeries) -> Series {
342 let tx = build_series_text(series);
343 let cat = if series.categories.is_empty() {
344 None
345 } else {
346 Some(CategoryRef {
347 str_ref: Some(StrRef {
348 f: series.categories.clone(),
349 }),
350 num_ref: None,
351 })
352 };
353 let val = if series.values.is_empty() {
354 None
355 } else {
356 Some(ValueRef {
357 num_ref: Some(NumRef {
358 f: series.values.clone(),
359 }),
360 })
361 };
362 Series {
363 idx: UintVal { val: index },
364 order: UintVal { val: index },
365 tx,
366 cat,
367 val,
368 }
369}
370
371fn build_scatter_series(index: u32, series: &ChartSeries) -> ScatterSeries {
372 let tx = build_series_text(series);
373 let x_val = series
374 .x_values
375 .as_ref()
376 .or(if series.categories.is_empty() {
377 None
378 } else {
379 Some(&series.categories)
380 })
381 .map(|ref_str| CategoryRef {
382 str_ref: None,
383 num_ref: Some(NumRef { f: ref_str.clone() }),
384 });
385 let y_val = if series.values.is_empty() {
386 None
387 } else {
388 Some(ValueRef {
389 num_ref: Some(NumRef {
390 f: series.values.clone(),
391 }),
392 })
393 };
394 ScatterSeries {
395 idx: UintVal { val: index },
396 order: UintVal { val: index },
397 tx,
398 x_val,
399 y_val,
400 }
401}
402
403fn build_bubble_series(index: u32, series: &ChartSeries, is_3d: bool) -> BubbleSeries {
404 let tx = build_series_text(series);
405 let x_val = series
406 .x_values
407 .as_ref()
408 .or(if series.categories.is_empty() {
409 None
410 } else {
411 Some(&series.categories)
412 })
413 .map(|ref_str| CategoryRef {
414 str_ref: None,
415 num_ref: Some(NumRef { f: ref_str.clone() }),
416 });
417 let y_val = if series.values.is_empty() {
418 None
419 } else {
420 Some(ValueRef {
421 num_ref: Some(NumRef {
422 f: series.values.clone(),
423 }),
424 })
425 };
426 let bubble_size = series.bubble_sizes.as_ref().map(|ref_str| ValueRef {
427 num_ref: Some(NumRef { f: ref_str.clone() }),
428 });
429 BubbleSeries {
430 idx: UintVal { val: index },
431 order: UintVal { val: index },
432 tx,
433 x_val,
434 y_val,
435 bubble_size,
436 bubble_3d: if is_3d {
437 Some(BoolVal { val: true })
438 } else {
439 None
440 },
441 }
442}
443
444fn build_chart_title(text: &str) -> ChartTitle {
445 ChartTitle {
446 tx: TitleTx {
447 rich: RichText {
448 body_pr: BodyPr {},
449 paragraphs: vec![Paragraph {
450 runs: vec![Run {
451 t: text.to_string(),
452 }],
453 }],
454 },
455 },
456 }
457}
458
459fn build_standard_axes() -> (Option<CatAx>, Option<ValAx>) {
460 (
461 Some(CatAx {
462 ax_id: UintVal { val: 1 },
463 scaling: Scaling {
464 orientation: StringVal {
465 val: "minMax".to_string(),
466 },
467 },
468 delete: BoolVal { val: false },
469 ax_pos: StringVal {
470 val: "b".to_string(),
471 },
472 cross_ax: UintVal { val: 2 },
473 }),
474 Some(ValAx {
475 ax_id: UintVal { val: 2 },
476 scaling: Scaling {
477 orientation: StringVal {
478 val: "minMax".to_string(),
479 },
480 },
481 delete: BoolVal { val: false },
482 ax_pos: StringVal {
483 val: "l".to_string(),
484 },
485 cross_ax: UintVal { val: 1 },
486 }),
487 )
488}
489
490fn build_ser_ax() -> SerAx {
491 SerAx {
492 ax_id: UintVal { val: 3 },
493 scaling: Scaling {
494 orientation: StringVal {
495 val: "minMax".to_string(),
496 },
497 },
498 delete: BoolVal { val: false },
499 ax_pos: StringVal {
500 val: "b".to_string(),
501 },
502 cross_ax: UintVal { val: 1 },
503 }
504}
505
506fn standard_ax_ids() -> Vec<UintVal> {
507 vec![UintVal { val: 1 }, UintVal { val: 2 }]
508}
509
510fn surface_ax_ids() -> Vec<UintVal> {
511 vec![UintVal { val: 1 }, UintVal { val: 2 }, UintVal { val: 3 }]
512}
513
514fn build_plot_area(config: &ChartConfig) -> PlotArea {
515 let ct = &config.chart_type;
516 let no_axes = is_no_axis_chart(ct);
517 let (cat_ax, val_ax) = if no_axes {
518 (None, None)
519 } else {
520 build_standard_axes()
521 };
522 let ser_ax = if needs_ser_ax(ct) {
523 Some(build_ser_ax())
524 } else {
525 None
526 };
527 let ax_ids = if no_axes {
528 vec![]
529 } else if needs_ser_ax(ct) {
530 surface_ax_ids()
531 } else {
532 standard_ax_ids()
533 };
534
535 let xml_series: Vec<Series> = config
536 .series
537 .iter()
538 .enumerate()
539 .map(|(i, s)| build_series(i as u32, s))
540 .collect();
541
542 let mut plot_area = PlotArea {
543 layout: Some(Layout {}),
544 bar_chart: None,
545 bar_3d_chart: None,
546 line_chart: None,
547 line_3d_chart: None,
548 pie_chart: None,
549 pie_3d_chart: None,
550 doughnut_chart: None,
551 area_chart: None,
552 area_3d_chart: None,
553 scatter_chart: None,
554 bubble_chart: None,
555 radar_chart: None,
556 stock_chart: None,
557 surface_chart: None,
558 surface_3d_chart: None,
559 of_pie_chart: None,
560 cat_ax,
561 val_ax,
562 ser_ax,
563 };
564
565 match ct {
566 ChartType::Col => {
567 plot_area.bar_chart = Some(BarChart {
568 bar_dir: StringVal { val: "col".into() },
569 grouping: StringVal {
570 val: "clustered".into(),
571 },
572 series: xml_series,
573 ax_ids,
574 });
575 }
576 ChartType::ColStacked => {
577 plot_area.bar_chart = Some(BarChart {
578 bar_dir: StringVal { val: "col".into() },
579 grouping: StringVal {
580 val: "stacked".into(),
581 },
582 series: xml_series,
583 ax_ids,
584 });
585 }
586 ChartType::ColPercentStacked => {
587 plot_area.bar_chart = Some(BarChart {
588 bar_dir: StringVal { val: "col".into() },
589 grouping: StringVal {
590 val: "percentStacked".into(),
591 },
592 series: xml_series,
593 ax_ids,
594 });
595 }
596 ChartType::Bar => {
597 plot_area.bar_chart = Some(BarChart {
598 bar_dir: StringVal { val: "bar".into() },
599 grouping: StringVal {
600 val: "clustered".into(),
601 },
602 series: xml_series,
603 ax_ids,
604 });
605 }
606 ChartType::BarStacked => {
607 plot_area.bar_chart = Some(BarChart {
608 bar_dir: StringVal { val: "bar".into() },
609 grouping: StringVal {
610 val: "stacked".into(),
611 },
612 series: xml_series,
613 ax_ids,
614 });
615 }
616 ChartType::BarPercentStacked => {
617 plot_area.bar_chart = Some(BarChart {
618 bar_dir: StringVal { val: "bar".into() },
619 grouping: StringVal {
620 val: "percentStacked".into(),
621 },
622 series: xml_series,
623 ax_ids,
624 });
625 }
626 ChartType::Line => {
627 plot_area.line_chart = Some(LineChart {
628 grouping: StringVal {
629 val: "standard".into(),
630 },
631 series: xml_series,
632 ax_ids,
633 });
634 }
635 ChartType::LineStacked => {
636 plot_area.line_chart = Some(LineChart {
637 grouping: StringVal {
638 val: "stacked".into(),
639 },
640 series: xml_series,
641 ax_ids,
642 });
643 }
644 ChartType::LinePercentStacked => {
645 plot_area.line_chart = Some(LineChart {
646 grouping: StringVal {
647 val: "percentStacked".into(),
648 },
649 series: xml_series,
650 ax_ids,
651 });
652 }
653 ChartType::Line3D => {
654 plot_area.line_3d_chart = Some(Line3DChart {
655 grouping: StringVal {
656 val: "standard".into(),
657 },
658 series: xml_series,
659 ax_ids,
660 });
661 }
662 ChartType::Pie => {
663 plot_area.pie_chart = Some(PieChart { series: xml_series });
664 }
665 ChartType::Pie3D => {
666 plot_area.pie_3d_chart = Some(Pie3DChart { series: xml_series });
667 }
668 ChartType::Doughnut => {
669 plot_area.doughnut_chart = Some(DoughnutChart {
670 series: xml_series,
671 hole_size: Some(UintVal { val: 50 }),
672 });
673 }
674 ChartType::Area => {
675 plot_area.area_chart = Some(AreaChart {
676 grouping: StringVal {
677 val: "standard".into(),
678 },
679 series: xml_series,
680 ax_ids,
681 });
682 }
683 ChartType::AreaStacked => {
684 plot_area.area_chart = Some(AreaChart {
685 grouping: StringVal {
686 val: "stacked".into(),
687 },
688 series: xml_series,
689 ax_ids,
690 });
691 }
692 ChartType::AreaPercentStacked => {
693 plot_area.area_chart = Some(AreaChart {
694 grouping: StringVal {
695 val: "percentStacked".into(),
696 },
697 series: xml_series,
698 ax_ids,
699 });
700 }
701 ChartType::Area3D => {
702 plot_area.area_3d_chart = Some(Area3DChart {
703 grouping: StringVal {
704 val: "standard".into(),
705 },
706 series: xml_series,
707 ax_ids,
708 });
709 }
710 ChartType::Area3DStacked => {
711 plot_area.area_3d_chart = Some(Area3DChart {
712 grouping: StringVal {
713 val: "stacked".into(),
714 },
715 series: xml_series,
716 ax_ids,
717 });
718 }
719 ChartType::Area3DPercentStacked => {
720 plot_area.area_3d_chart = Some(Area3DChart {
721 grouping: StringVal {
722 val: "percentStacked".into(),
723 },
724 series: xml_series,
725 ax_ids,
726 });
727 }
728 ChartType::Col3D => {
729 plot_area.bar_3d_chart = Some(Bar3DChart {
730 bar_dir: StringVal { val: "col".into() },
731 grouping: StringVal {
732 val: "clustered".into(),
733 },
734 series: xml_series,
735 shape: None,
736 ax_ids,
737 });
738 }
739 ChartType::Col3DStacked => {
740 plot_area.bar_3d_chart = Some(Bar3DChart {
741 bar_dir: StringVal { val: "col".into() },
742 grouping: StringVal {
743 val: "stacked".into(),
744 },
745 series: xml_series,
746 shape: None,
747 ax_ids,
748 });
749 }
750 ChartType::Col3DPercentStacked => {
751 plot_area.bar_3d_chart = Some(Bar3DChart {
752 bar_dir: StringVal { val: "col".into() },
753 grouping: StringVal {
754 val: "percentStacked".into(),
755 },
756 series: xml_series,
757 shape: None,
758 ax_ids,
759 });
760 }
761 ChartType::Bar3D => {
762 plot_area.bar_3d_chart = Some(Bar3DChart {
763 bar_dir: StringVal { val: "bar".into() },
764 grouping: StringVal {
765 val: "clustered".into(),
766 },
767 series: xml_series,
768 shape: None,
769 ax_ids,
770 });
771 }
772 ChartType::Bar3DStacked => {
773 plot_area.bar_3d_chart = Some(Bar3DChart {
774 bar_dir: StringVal { val: "bar".into() },
775 grouping: StringVal {
776 val: "stacked".into(),
777 },
778 series: xml_series,
779 shape: None,
780 ax_ids,
781 });
782 }
783 ChartType::Bar3DPercentStacked => {
784 plot_area.bar_3d_chart = Some(Bar3DChart {
785 bar_dir: StringVal { val: "bar".into() },
786 grouping: StringVal {
787 val: "percentStacked".into(),
788 },
789 series: xml_series,
790 shape: None,
791 ax_ids,
792 });
793 }
794 ChartType::Scatter => {
795 let ss: Vec<ScatterSeries> = config
796 .series
797 .iter()
798 .enumerate()
799 .map(|(i, s)| build_scatter_series(i as u32, s))
800 .collect();
801 plot_area.scatter_chart = Some(ScatterChart {
802 scatter_style: StringVal {
803 val: "lineMarker".into(),
804 },
805 series: ss,
806 ax_ids,
807 });
808 }
809 ChartType::ScatterLine => {
810 let ss: Vec<ScatterSeries> = config
811 .series
812 .iter()
813 .enumerate()
814 .map(|(i, s)| build_scatter_series(i as u32, s))
815 .collect();
816 plot_area.scatter_chart = Some(ScatterChart {
817 scatter_style: StringVal { val: "line".into() },
818 series: ss,
819 ax_ids,
820 });
821 }
822 ChartType::ScatterSmooth => {
823 let ss: Vec<ScatterSeries> = config
824 .series
825 .iter()
826 .enumerate()
827 .map(|(i, s)| build_scatter_series(i as u32, s))
828 .collect();
829 plot_area.scatter_chart = Some(ScatterChart {
830 scatter_style: StringVal {
831 val: "smoothMarker".into(),
832 },
833 series: ss,
834 ax_ids,
835 });
836 }
837 ChartType::Bubble => {
838 let bs: Vec<BubbleSeries> = config
839 .series
840 .iter()
841 .enumerate()
842 .map(|(i, s)| build_bubble_series(i as u32, s, false))
843 .collect();
844 plot_area.bubble_chart = Some(BubbleChart { series: bs, ax_ids });
845 }
846 ChartType::Bubble3D => {
847 let bs: Vec<BubbleSeries> = config
848 .series
849 .iter()
850 .enumerate()
851 .map(|(i, s)| build_bubble_series(i as u32, s, true))
852 .collect();
853 plot_area.bubble_chart = Some(BubbleChart { series: bs, ax_ids });
854 }
855 ChartType::Radar => {
856 plot_area.radar_chart = Some(RadarChart {
857 radar_style: StringVal {
858 val: "standard".into(),
859 },
860 series: xml_series,
861 ax_ids,
862 });
863 }
864 ChartType::RadarFilled => {
865 plot_area.radar_chart = Some(RadarChart {
866 radar_style: StringVal {
867 val: "filled".into(),
868 },
869 series: xml_series,
870 ax_ids,
871 });
872 }
873 ChartType::RadarMarker => {
874 plot_area.radar_chart = Some(RadarChart {
875 radar_style: StringVal {
876 val: "marker".into(),
877 },
878 series: xml_series,
879 ax_ids,
880 });
881 }
882 ChartType::StockHLC
883 | ChartType::StockOHLC
884 | ChartType::StockVHLC
885 | ChartType::StockVOHLC => {
886 plot_area.stock_chart = Some(StockChart {
887 series: xml_series,
888 ax_ids,
889 });
890 }
891 ChartType::Surface => {
892 plot_area.surface_chart = Some(SurfaceChart {
893 wireframe: None,
894 series: xml_series,
895 ax_ids,
896 });
897 }
898 ChartType::SurfaceWireframe => {
899 plot_area.surface_chart = Some(SurfaceChart {
900 wireframe: Some(BoolVal { val: true }),
901 series: xml_series,
902 ax_ids,
903 });
904 }
905 ChartType::Surface3D => {
906 plot_area.surface_3d_chart = Some(Surface3DChart {
907 wireframe: None,
908 series: xml_series,
909 ax_ids,
910 });
911 }
912 ChartType::SurfaceWireframe3D => {
913 plot_area.surface_3d_chart = Some(Surface3DChart {
914 wireframe: Some(BoolVal { val: true }),
915 series: xml_series,
916 ax_ids,
917 });
918 }
919 ChartType::PieOfPie => {
920 plot_area.of_pie_chart = Some(OfPieChart {
921 of_pie_type: StringVal { val: "pie".into() },
922 series: xml_series,
923 ser_lines: Some(SerLines {}),
924 });
925 }
926 ChartType::BarOfPie => {
927 plot_area.of_pie_chart = Some(OfPieChart {
928 of_pie_type: StringVal { val: "bar".into() },
929 series: xml_series,
930 ser_lines: Some(SerLines {}),
931 });
932 }
933 ChartType::Col3DCone => {
934 plot_area.bar_3d_chart = Some(Bar3DChart {
935 bar_dir: StringVal { val: "col".into() },
936 grouping: StringVal {
937 val: "clustered".into(),
938 },
939 series: xml_series,
940 shape: Some(StringVal { val: "cone".into() }),
941 ax_ids,
942 });
943 }
944 ChartType::Col3DConeStacked => {
945 plot_area.bar_3d_chart = Some(Bar3DChart {
946 bar_dir: StringVal { val: "col".into() },
947 grouping: StringVal {
948 val: "stacked".into(),
949 },
950 series: xml_series,
951 shape: Some(StringVal { val: "cone".into() }),
952 ax_ids,
953 });
954 }
955 ChartType::Col3DConePercentStacked => {
956 plot_area.bar_3d_chart = Some(Bar3DChart {
957 bar_dir: StringVal { val: "col".into() },
958 grouping: StringVal {
959 val: "percentStacked".into(),
960 },
961 series: xml_series,
962 shape: Some(StringVal { val: "cone".into() }),
963 ax_ids,
964 });
965 }
966 ChartType::Col3DPyramid => {
967 plot_area.bar_3d_chart = Some(Bar3DChart {
968 bar_dir: StringVal { val: "col".into() },
969 grouping: StringVal {
970 val: "clustered".into(),
971 },
972 series: xml_series,
973 shape: Some(StringVal {
974 val: "pyramid".into(),
975 }),
976 ax_ids,
977 });
978 }
979 ChartType::Col3DPyramidStacked => {
980 plot_area.bar_3d_chart = Some(Bar3DChart {
981 bar_dir: StringVal { val: "col".into() },
982 grouping: StringVal {
983 val: "stacked".into(),
984 },
985 series: xml_series,
986 shape: Some(StringVal {
987 val: "pyramid".into(),
988 }),
989 ax_ids,
990 });
991 }
992 ChartType::Col3DPyramidPercentStacked => {
993 plot_area.bar_3d_chart = Some(Bar3DChart {
994 bar_dir: StringVal { val: "col".into() },
995 grouping: StringVal {
996 val: "percentStacked".into(),
997 },
998 series: xml_series,
999 shape: Some(StringVal {
1000 val: "pyramid".into(),
1001 }),
1002 ax_ids,
1003 });
1004 }
1005 ChartType::Col3DCylinder => {
1006 plot_area.bar_3d_chart = Some(Bar3DChart {
1007 bar_dir: StringVal { val: "col".into() },
1008 grouping: StringVal {
1009 val: "clustered".into(),
1010 },
1011 series: xml_series,
1012 shape: Some(StringVal {
1013 val: "cylinder".into(),
1014 }),
1015 ax_ids,
1016 });
1017 }
1018 ChartType::Col3DCylinderStacked => {
1019 plot_area.bar_3d_chart = Some(Bar3DChart {
1020 bar_dir: StringVal { val: "col".into() },
1021 grouping: StringVal {
1022 val: "stacked".into(),
1023 },
1024 series: xml_series,
1025 shape: Some(StringVal {
1026 val: "cylinder".into(),
1027 }),
1028 ax_ids,
1029 });
1030 }
1031 ChartType::Col3DCylinderPercentStacked => {
1032 plot_area.bar_3d_chart = Some(Bar3DChart {
1033 bar_dir: StringVal { val: "col".into() },
1034 grouping: StringVal {
1035 val: "percentStacked".into(),
1036 },
1037 series: xml_series,
1038 shape: Some(StringVal {
1039 val: "cylinder".into(),
1040 }),
1041 ax_ids,
1042 });
1043 }
1044 ChartType::Contour => {
1045 plot_area.surface_chart = Some(SurfaceChart {
1046 wireframe: Some(BoolVal { val: false }),
1047 series: xml_series,
1048 ax_ids,
1049 });
1050 }
1051 ChartType::WireframeContour => {
1052 plot_area.surface_chart = Some(SurfaceChart {
1053 wireframe: Some(BoolVal { val: true }),
1054 series: xml_series,
1055 ax_ids,
1056 });
1057 }
1058 ChartType::ColLine | ChartType::ColLineStacked | ChartType::ColLinePercentStacked => {
1059 let grouping = match ct {
1060 ChartType::ColLineStacked => "stacked",
1061 ChartType::ColLinePercentStacked => "percentStacked",
1062 _ => "clustered",
1063 };
1064 let total = xml_series.len();
1065 let bar_count = total.div_ceil(2);
1066 let bar_series: Vec<Series> = xml_series.iter().take(bar_count).cloned().collect();
1067 let line_series: Vec<Series> = xml_series.iter().skip(bar_count).cloned().collect();
1068 plot_area.bar_chart = Some(BarChart {
1069 bar_dir: StringVal { val: "col".into() },
1070 grouping: StringVal {
1071 val: grouping.into(),
1072 },
1073 series: bar_series,
1074 ax_ids: ax_ids.clone(),
1075 });
1076 plot_area.line_chart = Some(LineChart {
1077 grouping: StringVal {
1078 val: "standard".into(),
1079 },
1080 series: line_series,
1081 ax_ids,
1082 });
1083 }
1084 }
1085
1086 plot_area
1087}
1088
1089#[cfg(test)]
1090mod tests {
1091 use super::*;
1092
1093 fn ss() -> Vec<ChartSeries> {
1094 vec![ChartSeries {
1095 name: "Revenue".into(),
1096 categories: "Sheet1!$A$2:$A$6".into(),
1097 values: "Sheet1!$B$2:$B$6".into(),
1098 x_values: None,
1099 bubble_sizes: None,
1100 }]
1101 }
1102
1103 fn mc(chart_type: ChartType) -> ChartConfig {
1104 ChartConfig {
1105 chart_type,
1106 title: None,
1107 series: ss(),
1108 show_legend: false,
1109 view_3d: None,
1110 }
1111 }
1112
1113 #[test]
1114 fn test_build_chart_xml_col() {
1115 let config = ChartConfig {
1116 chart_type: ChartType::Col,
1117 title: Some("Sales Chart".into()),
1118 series: ss(),
1119 show_legend: true,
1120 view_3d: None,
1121 };
1122 let cs = build_chart_xml(&config);
1123 assert!(cs.chart.title.is_some());
1124 assert!(cs.chart.legend.is_some());
1125 assert!(cs.chart.plot_area.bar_chart.is_some());
1126 assert!(cs.chart.plot_area.line_chart.is_none());
1127 assert!(cs.chart.plot_area.pie_chart.is_none());
1128 assert!(cs.chart.view_3d.is_none());
1129 let bar = cs.chart.plot_area.bar_chart.unwrap();
1130 assert_eq!(bar.bar_dir.val, "col");
1131 assert_eq!(bar.grouping.val, "clustered");
1132 assert_eq!(bar.series.len(), 1);
1133 assert_eq!(bar.ax_ids.len(), 2);
1134 }
1135
1136 #[test]
1137 fn test_build_chart_xml_bar() {
1138 let cs = build_chart_xml(&ChartConfig {
1139 chart_type: ChartType::Bar,
1140 title: None,
1141 series: vec![],
1142 show_legend: false,
1143 view_3d: None,
1144 });
1145 assert!(cs.chart.title.is_none());
1146 assert!(cs.chart.legend.is_none());
1147 let bar = cs.chart.plot_area.bar_chart.unwrap();
1148 assert_eq!(bar.bar_dir.val, "bar");
1149 assert_eq!(bar.grouping.val, "clustered");
1150 }
1151
1152 #[test]
1153 fn test_bar_stacked() {
1154 let cs = build_chart_xml(&mc(ChartType::BarStacked));
1155 let bar = cs.chart.plot_area.bar_chart.unwrap();
1156 assert_eq!(bar.bar_dir.val, "bar");
1157 assert_eq!(bar.grouping.val, "stacked");
1158 }
1159
1160 #[test]
1161 fn test_col_stacked() {
1162 let cs = build_chart_xml(&mc(ChartType::ColStacked));
1163 let bar = cs.chart.plot_area.bar_chart.unwrap();
1164 assert_eq!(bar.bar_dir.val, "col");
1165 assert_eq!(bar.grouping.val, "stacked");
1166 }
1167
1168 #[test]
1169 fn test_col_percent_stacked() {
1170 let cs = build_chart_xml(&mc(ChartType::ColPercentStacked));
1171 let bar = cs.chart.plot_area.bar_chart.unwrap();
1172 assert_eq!(bar.grouping.val, "percentStacked");
1173 }
1174
1175 #[test]
1176 fn test_bar_percent_stacked() {
1177 let cs = build_chart_xml(&mc(ChartType::BarPercentStacked));
1178 let bar = cs.chart.plot_area.bar_chart.unwrap();
1179 assert_eq!(bar.bar_dir.val, "bar");
1180 assert_eq!(bar.grouping.val, "percentStacked");
1181 }
1182
1183 #[test]
1184 fn test_line() {
1185 let cs = build_chart_xml(&ChartConfig {
1186 chart_type: ChartType::Line,
1187 title: Some("Trend".into()),
1188 series: vec![ChartSeries {
1189 name: "Sheet1!$A$1".into(),
1190 categories: "Sheet1!$A$2:$A$6".into(),
1191 values: "Sheet1!$B$2:$B$6".into(),
1192 x_values: None,
1193 bubble_sizes: None,
1194 }],
1195 show_legend: true,
1196 view_3d: None,
1197 });
1198 assert!(cs.chart.plot_area.line_chart.is_some());
1199 let line = cs.chart.plot_area.line_chart.unwrap();
1200 assert_eq!(line.grouping.val, "standard");
1201 assert_eq!(line.series.len(), 1);
1202 let tx = line.series[0].tx.as_ref().unwrap();
1203 assert!(tx.str_ref.is_some());
1204 assert!(tx.v.is_none());
1205 }
1206
1207 #[test]
1208 fn test_pie() {
1209 let cs = build_chart_xml(&ChartConfig {
1210 chart_type: ChartType::Pie,
1211 title: Some("Distribution".into()),
1212 series: vec![ChartSeries {
1213 name: "Data".into(),
1214 categories: "Sheet1!$A$2:$A$6".into(),
1215 values: "Sheet1!$B$2:$B$6".into(),
1216 x_values: None,
1217 bubble_sizes: None,
1218 }],
1219 show_legend: true,
1220 view_3d: None,
1221 });
1222 assert!(cs.chart.plot_area.pie_chart.is_some());
1223 assert!(cs.chart.plot_area.cat_ax.is_none());
1224 assert!(cs.chart.plot_area.val_ax.is_none());
1225 let pie = cs.chart.plot_area.pie_chart.unwrap();
1226 let tx = pie.series[0].tx.as_ref().unwrap();
1227 assert!(tx.str_ref.is_none());
1228 assert_eq!(tx.v.as_deref(), Some("Data"));
1229 }
1230
1231 #[test]
1232 fn test_no_legend() {
1233 let cs = build_chart_xml(&mc(ChartType::Col));
1234 assert!(cs.chart.legend.is_none());
1235 }
1236
1237 #[test]
1238 fn test_axes_present_for_non_pie() {
1239 let cs = build_chart_xml(&mc(ChartType::Line));
1240 assert!(cs.chart.plot_area.cat_ax.is_some());
1241 assert!(cs.chart.plot_area.val_ax.is_some());
1242 }
1243
1244 #[test]
1245 fn test_drawing_with_chart() {
1246 let from = MarkerType {
1247 col: 1,
1248 col_off: 0,
1249 row: 1,
1250 row_off: 0,
1251 };
1252 let to = MarkerType {
1253 col: 10,
1254 col_off: 0,
1255 row: 15,
1256 row_off: 0,
1257 };
1258 let dr = build_drawing_with_chart("rId1", from, to);
1259 assert_eq!(dr.two_cell_anchors.len(), 1);
1260 let anchor = &dr.two_cell_anchors[0];
1261 assert!(anchor.graphic_frame.is_some());
1262 assert_eq!(anchor.from.col, 1);
1263 assert_eq!(anchor.to.col, 10);
1264 let gf = anchor.graphic_frame.as_ref().unwrap();
1265 assert_eq!(gf.graphic.graphic_data.chart.r_id, "rId1");
1266 }
1267
1268 #[test]
1269 fn test_series_literal_name() {
1270 let s = ChartSeries {
1271 name: "MyName".into(),
1272 categories: "Sheet1!$A$2:$A$6".into(),
1273 values: "Sheet1!$B$2:$B$6".into(),
1274 x_values: None,
1275 bubble_sizes: None,
1276 };
1277 let xs = build_series(0, &s);
1278 let tx = xs.tx.as_ref().unwrap();
1279 assert!(tx.str_ref.is_none());
1280 assert_eq!(tx.v.as_deref(), Some("MyName"));
1281 }
1282
1283 #[test]
1284 fn test_series_cell_ref_name() {
1285 let s = ChartSeries {
1286 name: "Sheet1!$C$1".into(),
1287 categories: "".into(),
1288 values: "Sheet1!$B$2:$B$6".into(),
1289 x_values: None,
1290 bubble_sizes: None,
1291 };
1292 let xs = build_series(0, &s);
1293 let tx = xs.tx.as_ref().unwrap();
1294 assert!(tx.str_ref.is_some());
1295 assert!(tx.v.is_none());
1296 assert!(xs.cat.is_none());
1297 }
1298
1299 #[test]
1300 fn test_series_empty_name() {
1301 let s = ChartSeries {
1302 name: "".into(),
1303 categories: "Sheet1!$A$2:$A$6".into(),
1304 values: "Sheet1!$B$2:$B$6".into(),
1305 x_values: None,
1306 bubble_sizes: None,
1307 };
1308 let xs = build_series(0, &s);
1309 assert!(xs.tx.is_none());
1310 }
1311
1312 #[test]
1313 fn test_multiple_series() {
1314 let cs = build_chart_xml(&ChartConfig {
1315 chart_type: ChartType::Col,
1316 title: None,
1317 series: vec![
1318 ChartSeries {
1319 name: "A".into(),
1320 categories: "Sheet1!$A$2:$A$6".into(),
1321 values: "Sheet1!$B$2:$B$6".into(),
1322 x_values: None,
1323 bubble_sizes: None,
1324 },
1325 ChartSeries {
1326 name: "B".into(),
1327 categories: "Sheet1!$A$2:$A$6".into(),
1328 values: "Sheet1!$C$2:$C$6".into(),
1329 x_values: None,
1330 bubble_sizes: None,
1331 },
1332 ],
1333 show_legend: true,
1334 view_3d: None,
1335 });
1336 let bar = cs.chart.plot_area.bar_chart.unwrap();
1337 assert_eq!(bar.series.len(), 2);
1338 assert_eq!(bar.series[0].idx.val, 0);
1339 assert_eq!(bar.series[1].idx.val, 1);
1340 }
1341
1342 #[test]
1343 fn test_area_chart() {
1344 let cs = build_chart_xml(&mc(ChartType::Area));
1345 assert!(cs.chart.plot_area.area_chart.is_some());
1346 let a = cs.chart.plot_area.area_chart.unwrap();
1347 assert_eq!(a.grouping.val, "standard");
1348 assert_eq!(a.ax_ids.len(), 2);
1349 assert!(cs.chart.view_3d.is_none());
1350 }
1351
1352 #[test]
1353 fn test_area_stacked() {
1354 let cs = build_chart_xml(&mc(ChartType::AreaStacked));
1355 assert_eq!(
1356 cs.chart.plot_area.area_chart.unwrap().grouping.val,
1357 "stacked"
1358 );
1359 }
1360
1361 #[test]
1362 fn test_area_percent_stacked() {
1363 let cs = build_chart_xml(&mc(ChartType::AreaPercentStacked));
1364 assert_eq!(
1365 cs.chart.plot_area.area_chart.unwrap().grouping.val,
1366 "percentStacked"
1367 );
1368 }
1369
1370 #[test]
1371 fn test_area_3d() {
1372 let cs = build_chart_xml(&mc(ChartType::Area3D));
1373 assert!(cs.chart.view_3d.is_some());
1374 assert_eq!(
1375 cs.chart.plot_area.area_3d_chart.unwrap().grouping.val,
1376 "standard"
1377 );
1378 }
1379
1380 #[test]
1381 fn test_area_3d_stacked() {
1382 let cs = build_chart_xml(&mc(ChartType::Area3DStacked));
1383 assert!(cs.chart.view_3d.is_some());
1384 assert_eq!(
1385 cs.chart.plot_area.area_3d_chart.unwrap().grouping.val,
1386 "stacked"
1387 );
1388 }
1389
1390 #[test]
1391 fn test_area_3d_percent_stacked() {
1392 let cs = build_chart_xml(&mc(ChartType::Area3DPercentStacked));
1393 assert!(cs.chart.view_3d.is_some());
1394 assert_eq!(
1395 cs.chart.plot_area.area_3d_chart.unwrap().grouping.val,
1396 "percentStacked"
1397 );
1398 }
1399
1400 #[test]
1401 fn test_col_3d() {
1402 let cs = build_chart_xml(&mc(ChartType::Col3D));
1403 assert!(cs.chart.view_3d.is_some());
1404 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1405 assert_eq!(b.bar_dir.val, "col");
1406 assert_eq!(b.grouping.val, "clustered");
1407 }
1408
1409 #[test]
1410 fn test_col_3d_stacked() {
1411 let cs = build_chart_xml(&mc(ChartType::Col3DStacked));
1412 assert_eq!(
1413 cs.chart.plot_area.bar_3d_chart.unwrap().grouping.val,
1414 "stacked"
1415 );
1416 }
1417
1418 #[test]
1419 fn test_col_3d_percent_stacked() {
1420 let cs = build_chart_xml(&mc(ChartType::Col3DPercentStacked));
1421 assert_eq!(
1422 cs.chart.plot_area.bar_3d_chart.unwrap().grouping.val,
1423 "percentStacked"
1424 );
1425 }
1426
1427 #[test]
1428 fn test_bar_3d() {
1429 let cs = build_chart_xml(&mc(ChartType::Bar3D));
1430 assert!(cs.chart.view_3d.is_some());
1431 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1432 assert_eq!(b.bar_dir.val, "bar");
1433 assert_eq!(b.grouping.val, "clustered");
1434 }
1435
1436 #[test]
1437 fn test_bar_3d_stacked() {
1438 let cs = build_chart_xml(&mc(ChartType::Bar3DStacked));
1439 assert_eq!(
1440 cs.chart.plot_area.bar_3d_chart.unwrap().grouping.val,
1441 "stacked"
1442 );
1443 }
1444
1445 #[test]
1446 fn test_bar_3d_percent_stacked() {
1447 let cs = build_chart_xml(&mc(ChartType::Bar3DPercentStacked));
1448 assert_eq!(
1449 cs.chart.plot_area.bar_3d_chart.unwrap().grouping.val,
1450 "percentStacked"
1451 );
1452 }
1453
1454 #[test]
1455 fn test_line_stacked() {
1456 let cs = build_chart_xml(&mc(ChartType::LineStacked));
1457 assert_eq!(
1458 cs.chart.plot_area.line_chart.unwrap().grouping.val,
1459 "stacked"
1460 );
1461 }
1462
1463 #[test]
1464 fn test_line_percent_stacked() {
1465 let cs = build_chart_xml(&mc(ChartType::LinePercentStacked));
1466 assert_eq!(
1467 cs.chart.plot_area.line_chart.unwrap().grouping.val,
1468 "percentStacked"
1469 );
1470 }
1471
1472 #[test]
1473 fn test_line_3d() {
1474 let cs = build_chart_xml(&mc(ChartType::Line3D));
1475 assert!(cs.chart.view_3d.is_some());
1476 assert!(cs.chart.plot_area.line_3d_chart.is_some());
1477 }
1478
1479 #[test]
1480 fn test_pie_3d() {
1481 let cs = build_chart_xml(&mc(ChartType::Pie3D));
1482 assert!(cs.chart.view_3d.is_some());
1483 assert!(cs.chart.plot_area.pie_3d_chart.is_some());
1484 assert!(cs.chart.plot_area.cat_ax.is_none());
1485 }
1486
1487 #[test]
1488 fn test_doughnut() {
1489 let cs = build_chart_xml(&mc(ChartType::Doughnut));
1490 assert!(cs.chart.plot_area.doughnut_chart.is_some());
1491 assert!(cs.chart.plot_area.cat_ax.is_none());
1492 let d = cs.chart.plot_area.doughnut_chart.unwrap();
1493 assert_eq!(d.hole_size.as_ref().unwrap().val, 50);
1494 }
1495
1496 #[test]
1497 fn test_scatter() {
1498 let cs = build_chart_xml(&ChartConfig {
1499 chart_type: ChartType::Scatter,
1500 title: None,
1501 series: vec![ChartSeries {
1502 name: "XY".into(),
1503 categories: "Sheet1!$A$2:$A$6".into(),
1504 values: "Sheet1!$B$2:$B$6".into(),
1505 x_values: None,
1506 bubble_sizes: None,
1507 }],
1508 show_legend: false,
1509 view_3d: None,
1510 });
1511 let sc = cs.chart.plot_area.scatter_chart.unwrap();
1512 assert_eq!(sc.scatter_style.val, "lineMarker");
1513 assert_eq!(sc.series.len(), 1);
1514 let s = &sc.series[0];
1515 assert_eq!(
1516 s.x_val.as_ref().unwrap().num_ref.as_ref().unwrap().f,
1517 "Sheet1!$A$2:$A$6"
1518 );
1519 assert_eq!(
1520 s.y_val.as_ref().unwrap().num_ref.as_ref().unwrap().f,
1521 "Sheet1!$B$2:$B$6"
1522 );
1523 }
1524
1525 #[test]
1526 fn test_scatter_explicit_x() {
1527 let cs = build_chart_xml(&ChartConfig {
1528 chart_type: ChartType::Scatter,
1529 title: None,
1530 series: vec![ChartSeries {
1531 name: "".into(),
1532 categories: "Sheet1!$A$2:$A$6".into(),
1533 values: "Sheet1!$B$2:$B$6".into(),
1534 x_values: Some("Sheet1!$D$2:$D$6".into()),
1535 bubble_sizes: None,
1536 }],
1537 show_legend: false,
1538 view_3d: None,
1539 });
1540 let s = &cs.chart.plot_area.scatter_chart.unwrap().series[0];
1541 assert_eq!(
1542 s.x_val.as_ref().unwrap().num_ref.as_ref().unwrap().f,
1543 "Sheet1!$D$2:$D$6"
1544 );
1545 }
1546
1547 #[test]
1548 fn test_scatter_line() {
1549 let cs = build_chart_xml(&mc(ChartType::ScatterLine));
1550 assert_eq!(
1551 cs.chart.plot_area.scatter_chart.unwrap().scatter_style.val,
1552 "line"
1553 );
1554 }
1555
1556 #[test]
1557 fn test_scatter_smooth() {
1558 let cs = build_chart_xml(&mc(ChartType::ScatterSmooth));
1559 assert_eq!(
1560 cs.chart.plot_area.scatter_chart.unwrap().scatter_style.val,
1561 "smoothMarker"
1562 );
1563 }
1564
1565 #[test]
1566 fn test_bubble() {
1567 let cs = build_chart_xml(&ChartConfig {
1568 chart_type: ChartType::Bubble,
1569 title: None,
1570 series: vec![ChartSeries {
1571 name: "B".into(),
1572 categories: "Sheet1!$A$2:$A$6".into(),
1573 values: "Sheet1!$B$2:$B$6".into(),
1574 x_values: None,
1575 bubble_sizes: Some("Sheet1!$C$2:$C$6".into()),
1576 }],
1577 show_legend: false,
1578 view_3d: None,
1579 });
1580 let b = cs.chart.plot_area.bubble_chart.unwrap();
1581 assert_eq!(b.series.len(), 1);
1582 assert_eq!(
1583 b.series[0]
1584 .bubble_size
1585 .as_ref()
1586 .unwrap()
1587 .num_ref
1588 .as_ref()
1589 .unwrap()
1590 .f,
1591 "Sheet1!$C$2:$C$6"
1592 );
1593 }
1594
1595 #[test]
1596 fn test_radar() {
1597 let cs = build_chart_xml(&mc(ChartType::Radar));
1598 assert_eq!(
1599 cs.chart.plot_area.radar_chart.unwrap().radar_style.val,
1600 "standard"
1601 );
1602 }
1603
1604 #[test]
1605 fn test_radar_filled() {
1606 let cs = build_chart_xml(&mc(ChartType::RadarFilled));
1607 assert_eq!(
1608 cs.chart.plot_area.radar_chart.unwrap().radar_style.val,
1609 "filled"
1610 );
1611 }
1612
1613 #[test]
1614 fn test_radar_marker() {
1615 let cs = build_chart_xml(&mc(ChartType::RadarMarker));
1616 assert_eq!(
1617 cs.chart.plot_area.radar_chart.unwrap().radar_style.val,
1618 "marker"
1619 );
1620 }
1621
1622 #[test]
1623 fn test_stock_hlc() {
1624 let cs = build_chart_xml(&mc(ChartType::StockHLC));
1625 assert!(cs.chart.plot_area.stock_chart.is_some());
1626 }
1627
1628 #[test]
1629 fn test_stock_ohlc() {
1630 let cs = build_chart_xml(&mc(ChartType::StockOHLC));
1631 assert!(cs.chart.plot_area.stock_chart.is_some());
1632 }
1633
1634 #[test]
1635 fn test_stock_vhlc() {
1636 let cs = build_chart_xml(&mc(ChartType::StockVHLC));
1637 assert!(cs.chart.plot_area.stock_chart.is_some());
1638 }
1639
1640 #[test]
1641 fn test_stock_vohlc() {
1642 let cs = build_chart_xml(&mc(ChartType::StockVOHLC));
1643 assert!(cs.chart.plot_area.stock_chart.is_some());
1644 }
1645
1646 #[test]
1647 fn test_surface() {
1648 let cs = build_chart_xml(&mc(ChartType::Surface));
1649 assert!(cs.chart.plot_area.surface_chart.is_some());
1650 assert!(cs.chart.plot_area.ser_ax.is_some());
1651 let sf = cs.chart.plot_area.surface_chart.unwrap();
1652 assert!(sf.wireframe.is_none());
1653 assert_eq!(sf.ax_ids.len(), 3);
1654 }
1655
1656 #[test]
1657 fn test_surface_wireframe() {
1658 let cs = build_chart_xml(&mc(ChartType::SurfaceWireframe));
1659 let sf = cs.chart.plot_area.surface_chart.unwrap();
1660 assert!(sf.wireframe.as_ref().unwrap().val);
1661 assert_eq!(sf.ax_ids.len(), 3);
1662 }
1663
1664 #[test]
1665 fn test_surface_3d() {
1666 let cs = build_chart_xml(&mc(ChartType::Surface3D));
1667 assert!(cs.chart.view_3d.is_some());
1668 assert!(cs.chart.plot_area.surface_3d_chart.is_some());
1669 assert!(cs.chart.plot_area.ser_ax.is_some());
1670 }
1671
1672 #[test]
1673 fn test_surface_wireframe_3d() {
1674 let cs = build_chart_xml(&mc(ChartType::SurfaceWireframe3D));
1675 assert!(cs.chart.view_3d.is_some());
1676 let sf = cs.chart.plot_area.surface_3d_chart.unwrap();
1677 assert!(sf.wireframe.as_ref().unwrap().val);
1678 }
1679
1680 #[test]
1681 fn test_col_line_combo() {
1682 let cs = build_chart_xml(&ChartConfig {
1683 chart_type: ChartType::ColLine,
1684 title: None,
1685 series: vec![
1686 ChartSeries {
1687 name: "A".into(),
1688 categories: "Sheet1!$A$2:$A$6".into(),
1689 values: "Sheet1!$B$2:$B$6".into(),
1690 x_values: None,
1691 bubble_sizes: None,
1692 },
1693 ChartSeries {
1694 name: "B".into(),
1695 categories: "Sheet1!$A$2:$A$6".into(),
1696 values: "Sheet1!$C$2:$C$6".into(),
1697 x_values: None,
1698 bubble_sizes: None,
1699 },
1700 ],
1701 show_legend: true,
1702 view_3d: None,
1703 });
1704 assert!(cs.chart.plot_area.bar_chart.is_some());
1705 assert!(cs.chart.plot_area.line_chart.is_some());
1706 let bar = cs.chart.plot_area.bar_chart.unwrap();
1707 assert_eq!(bar.grouping.val, "clustered");
1708 assert_eq!(bar.series.len(), 1);
1709 assert_eq!(cs.chart.plot_area.line_chart.unwrap().series.len(), 1);
1710 }
1711
1712 #[test]
1713 fn test_col_line_stacked_combo() {
1714 let cs = build_chart_xml(&ChartConfig {
1715 chart_type: ChartType::ColLineStacked,
1716 title: None,
1717 series: vec![
1718 ChartSeries {
1719 name: "A".into(),
1720 categories: "".into(),
1721 values: "Sheet1!$B$2:$B$6".into(),
1722 x_values: None,
1723 bubble_sizes: None,
1724 },
1725 ChartSeries {
1726 name: "B".into(),
1727 categories: "".into(),
1728 values: "Sheet1!$C$2:$C$6".into(),
1729 x_values: None,
1730 bubble_sizes: None,
1731 },
1732 ChartSeries {
1733 name: "C".into(),
1734 categories: "".into(),
1735 values: "Sheet1!$D$2:$D$6".into(),
1736 x_values: None,
1737 bubble_sizes: None,
1738 },
1739 ],
1740 show_legend: false,
1741 view_3d: None,
1742 });
1743 let bar = cs.chart.plot_area.bar_chart.unwrap();
1744 assert_eq!(bar.grouping.val, "stacked");
1745 assert_eq!(bar.series.len(), 2);
1746 assert_eq!(cs.chart.plot_area.line_chart.unwrap().series.len(), 1);
1747 }
1748
1749 #[test]
1750 fn test_col_line_percent_stacked_combo() {
1751 let cs = build_chart_xml(&ChartConfig {
1752 chart_type: ChartType::ColLinePercentStacked,
1753 title: None,
1754 series: vec![ChartSeries {
1755 name: "A".into(),
1756 categories: "".into(),
1757 values: "Sheet1!$B$2:$B$6".into(),
1758 x_values: None,
1759 bubble_sizes: None,
1760 }],
1761 show_legend: false,
1762 view_3d: None,
1763 });
1764 let bar = cs.chart.plot_area.bar_chart.unwrap();
1765 assert_eq!(bar.grouping.val, "percentStacked");
1766 assert_eq!(bar.series.len(), 1);
1767 assert_eq!(cs.chart.plot_area.line_chart.unwrap().series.len(), 0);
1768 }
1769
1770 #[test]
1771 fn test_view_3d_explicit() {
1772 let cs = build_chart_xml(&ChartConfig {
1773 chart_type: ChartType::Col3D,
1774 title: None,
1775 series: vec![],
1776 show_legend: false,
1777 view_3d: Some(View3DConfig {
1778 rot_x: Some(30),
1779 rot_y: Some(40),
1780 depth_percent: Some(200),
1781 right_angle_axes: Some(false),
1782 perspective: Some(10),
1783 }),
1784 });
1785 let v = cs.chart.view_3d.unwrap();
1786 assert_eq!(v.rot_x.unwrap().val, 30);
1787 assert_eq!(v.rot_y.unwrap().val, 40);
1788 assert_eq!(v.depth_percent.unwrap().val, 200);
1789 assert!(!v.r_ang_ax.unwrap().val);
1790 assert_eq!(v.perspective.unwrap().val, 10);
1791 }
1792
1793 #[test]
1794 fn test_view_3d_auto_defaults() {
1795 let cs = build_chart_xml(&mc(ChartType::Col3D));
1796 let v = cs.chart.view_3d.unwrap();
1797 assert_eq!(v.rot_x.unwrap().val, 15);
1798 assert_eq!(v.rot_y.unwrap().val, 20);
1799 assert!(v.r_ang_ax.unwrap().val);
1800 assert_eq!(v.perspective.unwrap().val, 30);
1801 }
1802
1803 #[test]
1804 fn test_non_3d_no_view() {
1805 let cs = build_chart_xml(&mc(ChartType::Col));
1806 assert!(cs.chart.view_3d.is_none());
1807 }
1808
1809 #[test]
1810 fn test_chart_type_enum_coverage() {
1811 let types = [
1812 ChartType::Col,
1813 ChartType::ColStacked,
1814 ChartType::ColPercentStacked,
1815 ChartType::Bar,
1816 ChartType::BarStacked,
1817 ChartType::BarPercentStacked,
1818 ChartType::Line,
1819 ChartType::Pie,
1820 ChartType::Area,
1821 ChartType::AreaStacked,
1822 ChartType::AreaPercentStacked,
1823 ChartType::Area3D,
1824 ChartType::Area3DStacked,
1825 ChartType::Area3DPercentStacked,
1826 ChartType::Col3D,
1827 ChartType::Col3DStacked,
1828 ChartType::Col3DPercentStacked,
1829 ChartType::Bar3D,
1830 ChartType::Bar3DStacked,
1831 ChartType::Bar3DPercentStacked,
1832 ChartType::LineStacked,
1833 ChartType::LinePercentStacked,
1834 ChartType::Line3D,
1835 ChartType::Pie3D,
1836 ChartType::Doughnut,
1837 ChartType::Scatter,
1838 ChartType::ScatterLine,
1839 ChartType::ScatterSmooth,
1840 ChartType::Radar,
1841 ChartType::RadarFilled,
1842 ChartType::RadarMarker,
1843 ChartType::StockHLC,
1844 ChartType::StockOHLC,
1845 ChartType::StockVHLC,
1846 ChartType::StockVOHLC,
1847 ChartType::Bubble,
1848 ChartType::Surface,
1849 ChartType::Surface3D,
1850 ChartType::SurfaceWireframe,
1851 ChartType::SurfaceWireframe3D,
1852 ChartType::ColLine,
1853 ChartType::ColLineStacked,
1854 ChartType::ColLinePercentStacked,
1855 ChartType::PieOfPie,
1856 ChartType::BarOfPie,
1857 ChartType::Col3DCone,
1858 ChartType::Col3DConeStacked,
1859 ChartType::Col3DConePercentStacked,
1860 ChartType::Col3DPyramid,
1861 ChartType::Col3DPyramidStacked,
1862 ChartType::Col3DPyramidPercentStacked,
1863 ChartType::Col3DCylinder,
1864 ChartType::Col3DCylinderStacked,
1865 ChartType::Col3DCylinderPercentStacked,
1866 ChartType::Contour,
1867 ChartType::WireframeContour,
1868 ChartType::Bubble3D,
1869 ];
1870 for ct in &types {
1871 let _ = build_chart_xml(&ChartConfig {
1872 chart_type: ct.clone(),
1873 title: None,
1874 series: vec![],
1875 show_legend: false,
1876 view_3d: None,
1877 });
1878 }
1879 }
1880
1881 #[test]
1882 fn test_scatter_empty_categories() {
1883 let cs = build_chart_xml(&ChartConfig {
1884 chart_type: ChartType::Scatter,
1885 title: None,
1886 series: vec![ChartSeries {
1887 name: "".into(),
1888 categories: "".into(),
1889 values: "Sheet1!$B$2:$B$6".into(),
1890 x_values: None,
1891 bubble_sizes: None,
1892 }],
1893 show_legend: false,
1894 view_3d: None,
1895 });
1896 let s = &cs.chart.plot_area.scatter_chart.unwrap().series[0];
1897 assert!(s.x_val.is_none());
1898 assert!(s.y_val.is_some());
1899 }
1900
1901 #[test]
1902 fn test_bubble_no_sizes() {
1903 let cs = build_chart_xml(&ChartConfig {
1904 chart_type: ChartType::Bubble,
1905 title: None,
1906 series: vec![ChartSeries {
1907 name: "".into(),
1908 categories: "Sheet1!$A$2:$A$6".into(),
1909 values: "Sheet1!$B$2:$B$6".into(),
1910 x_values: None,
1911 bubble_sizes: None,
1912 }],
1913 show_legend: false,
1914 view_3d: None,
1915 });
1916 assert!(cs.chart.plot_area.bubble_chart.unwrap().series[0]
1917 .bubble_size
1918 .is_none());
1919 }
1920
1921 #[test]
1922 fn test_pie_of_pie() {
1923 let cs = build_chart_xml(&mc(ChartType::PieOfPie));
1924 assert!(cs.chart.plot_area.of_pie_chart.is_some());
1925 assert!(cs.chart.plot_area.cat_ax.is_none());
1926 assert!(cs.chart.plot_area.val_ax.is_none());
1927 let op = cs.chart.plot_area.of_pie_chart.unwrap();
1928 assert_eq!(op.of_pie_type.val, "pie");
1929 assert!(op.ser_lines.is_some());
1930 assert_eq!(op.series.len(), 1);
1931 }
1932
1933 #[test]
1934 fn test_bar_of_pie() {
1935 let cs = build_chart_xml(&mc(ChartType::BarOfPie));
1936 assert!(cs.chart.plot_area.of_pie_chart.is_some());
1937 assert!(cs.chart.plot_area.cat_ax.is_none());
1938 let op = cs.chart.plot_area.of_pie_chart.unwrap();
1939 assert_eq!(op.of_pie_type.val, "bar");
1940 assert!(op.ser_lines.is_some());
1941 }
1942
1943 #[test]
1944 fn test_col_3d_cone() {
1945 let cs = build_chart_xml(&mc(ChartType::Col3DCone));
1946 assert!(cs.chart.view_3d.is_some());
1947 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1948 assert_eq!(b.bar_dir.val, "col");
1949 assert_eq!(b.grouping.val, "clustered");
1950 assert_eq!(b.shape.as_ref().unwrap().val, "cone");
1951 }
1952
1953 #[test]
1954 fn test_col_3d_cone_stacked() {
1955 let cs = build_chart_xml(&mc(ChartType::Col3DConeStacked));
1956 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1957 assert_eq!(b.grouping.val, "stacked");
1958 assert_eq!(b.shape.as_ref().unwrap().val, "cone");
1959 }
1960
1961 #[test]
1962 fn test_col_3d_cone_percent_stacked() {
1963 let cs = build_chart_xml(&mc(ChartType::Col3DConePercentStacked));
1964 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1965 assert_eq!(b.grouping.val, "percentStacked");
1966 assert_eq!(b.shape.as_ref().unwrap().val, "cone");
1967 }
1968
1969 #[test]
1970 fn test_col_3d_pyramid() {
1971 let cs = build_chart_xml(&mc(ChartType::Col3DPyramid));
1972 assert!(cs.chart.view_3d.is_some());
1973 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1974 assert_eq!(b.bar_dir.val, "col");
1975 assert_eq!(b.grouping.val, "clustered");
1976 assert_eq!(b.shape.as_ref().unwrap().val, "pyramid");
1977 }
1978
1979 #[test]
1980 fn test_col_3d_pyramid_stacked() {
1981 let cs = build_chart_xml(&mc(ChartType::Col3DPyramidStacked));
1982 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1983 assert_eq!(b.grouping.val, "stacked");
1984 assert_eq!(b.shape.as_ref().unwrap().val, "pyramid");
1985 }
1986
1987 #[test]
1988 fn test_col_3d_pyramid_percent_stacked() {
1989 let cs = build_chart_xml(&mc(ChartType::Col3DPyramidPercentStacked));
1990 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1991 assert_eq!(b.grouping.val, "percentStacked");
1992 assert_eq!(b.shape.as_ref().unwrap().val, "pyramid");
1993 }
1994
1995 #[test]
1996 fn test_col_3d_cylinder() {
1997 let cs = build_chart_xml(&mc(ChartType::Col3DCylinder));
1998 assert!(cs.chart.view_3d.is_some());
1999 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
2000 assert_eq!(b.bar_dir.val, "col");
2001 assert_eq!(b.grouping.val, "clustered");
2002 assert_eq!(b.shape.as_ref().unwrap().val, "cylinder");
2003 }
2004
2005 #[test]
2006 fn test_col_3d_cylinder_stacked() {
2007 let cs = build_chart_xml(&mc(ChartType::Col3DCylinderStacked));
2008 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
2009 assert_eq!(b.grouping.val, "stacked");
2010 assert_eq!(b.shape.as_ref().unwrap().val, "cylinder");
2011 }
2012
2013 #[test]
2014 fn test_col_3d_cylinder_percent_stacked() {
2015 let cs = build_chart_xml(&mc(ChartType::Col3DCylinderPercentStacked));
2016 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
2017 assert_eq!(b.grouping.val, "percentStacked");
2018 assert_eq!(b.shape.as_ref().unwrap().val, "cylinder");
2019 }
2020
2021 #[test]
2022 fn test_contour() {
2023 let cs = build_chart_xml(&mc(ChartType::Contour));
2024 assert!(cs.chart.plot_area.surface_chart.is_some());
2025 assert!(cs.chart.plot_area.ser_ax.is_some());
2026 let sf = cs.chart.plot_area.surface_chart.unwrap();
2027 assert!(!sf.wireframe.as_ref().unwrap().val);
2028 assert_eq!(sf.ax_ids.len(), 3);
2029 }
2030
2031 #[test]
2032 fn test_wireframe_contour() {
2033 let cs = build_chart_xml(&mc(ChartType::WireframeContour));
2034 assert!(cs.chart.plot_area.surface_chart.is_some());
2035 assert!(cs.chart.plot_area.ser_ax.is_some());
2036 let sf = cs.chart.plot_area.surface_chart.unwrap();
2037 assert!(sf.wireframe.as_ref().unwrap().val);
2038 assert_eq!(sf.ax_ids.len(), 3);
2039 }
2040
2041 #[test]
2042 fn test_bubble_3d() {
2043 let cs = build_chart_xml(&ChartConfig {
2044 chart_type: ChartType::Bubble3D,
2045 title: None,
2046 series: vec![ChartSeries {
2047 name: "B3D".into(),
2048 categories: "Sheet1!$A$2:$A$6".into(),
2049 values: "Sheet1!$B$2:$B$6".into(),
2050 x_values: None,
2051 bubble_sizes: Some("Sheet1!$C$2:$C$6".into()),
2052 }],
2053 show_legend: false,
2054 view_3d: None,
2055 });
2056 let b = cs.chart.plot_area.bubble_chart.unwrap();
2057 assert_eq!(b.series.len(), 1);
2058 assert!(b.series[0].bubble_3d.as_ref().unwrap().val);
2059 }
2060
2061 #[test]
2062 fn test_bubble_no_3d_flag() {
2063 let cs = build_chart_xml(&mc(ChartType::Bubble));
2064 let b = cs.chart.plot_area.bubble_chart.unwrap();
2065 assert!(b.series[0].bubble_3d.is_none());
2066 }
2067
2068 #[test]
2069 fn test_col_3d_no_shape() {
2070 let cs = build_chart_xml(&mc(ChartType::Col3D));
2071 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
2072 assert!(b.shape.is_none());
2073 }
2074}