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, Paragraph, Pie3DChart, PieChart, PlotArea, RadarChart,
10 RichText, Run, Scaling, ScatterChart, ScatterSeries, SerAx, Series, SeriesText, StockChart,
11 StrRef, StringVal, Surface3DChart, SurfaceChart, TitleTx, UintVal, ValAx, ValueRef, View3D,
12};
13use sheetkit_xml::drawing::{
14 AExt, CNvGraphicFramePr, CNvPr, ChartRef, ClientData, Graphic, GraphicData, GraphicFrame,
15 MarkerType, NvGraphicFramePr, Offset, TwoCellAnchor, WsDr, Xfrm,
16};
17use sheetkit_xml::namespaces;
18
19#[derive(Debug, Clone, PartialEq)]
21pub enum ChartType {
22 Col,
24 ColStacked,
26 ColPercentStacked,
28 Bar,
30 BarStacked,
32 BarPercentStacked,
34 Line,
36 Pie,
38 Area,
40 AreaStacked,
42 AreaPercentStacked,
44 Area3D,
46 Area3DStacked,
48 Area3DPercentStacked,
50 Col3D,
52 Col3DStacked,
54 Col3DPercentStacked,
56 Bar3D,
58 Bar3DStacked,
60 Bar3DPercentStacked,
62 LineStacked,
64 LinePercentStacked,
66 Line3D,
68 Pie3D,
70 Doughnut,
72 Scatter,
74 ScatterLine,
76 ScatterSmooth,
78 Radar,
80 RadarFilled,
82 RadarMarker,
84 StockHLC,
86 StockOHLC,
88 StockVHLC,
90 StockVOHLC,
92 Bubble,
94 Surface,
96 Surface3D,
98 SurfaceWireframe,
100 SurfaceWireframe3D,
102 ColLine,
104 ColLineStacked,
106 ColLinePercentStacked,
108}
109
110#[derive(Debug, Clone, Default)]
112pub struct View3DConfig {
113 pub rot_x: Option<i32>,
115 pub rot_y: Option<i32>,
117 pub depth_percent: Option<u32>,
119 pub right_angle_axes: Option<bool>,
121 pub perspective: Option<u32>,
123}
124
125#[derive(Debug, Clone)]
127pub struct ChartConfig {
128 pub chart_type: ChartType,
130 pub title: Option<String>,
132 pub series: Vec<ChartSeries>,
134 pub show_legend: bool,
136 pub view_3d: Option<View3DConfig>,
138}
139
140#[derive(Debug, Clone)]
142pub struct ChartSeries {
143 pub name: String,
145 pub categories: String,
147 pub values: String,
149 pub x_values: Option<String>,
151 pub bubble_sizes: Option<String>,
153}
154
155pub fn build_chart_xml(config: &ChartConfig) -> ChartSpace {
157 let title = config.title.as_ref().map(|t| build_chart_title(t));
158 let legend = if config.show_legend {
159 Some(Legend {
160 legend_pos: StringVal {
161 val: "b".to_string(),
162 },
163 })
164 } else {
165 None
166 };
167 let view_3d = build_view_3d(config);
168 let plot_area = build_plot_area(config);
169
170 ChartSpace {
171 chart: Chart {
172 title,
173 view_3d,
174 plot_area,
175 legend,
176 plot_vis_only: Some(BoolVal { val: true }),
177 },
178 ..ChartSpace::default()
179 }
180}
181
182pub fn build_drawing_with_chart(chart_ref_id: &str, from: MarkerType, to: MarkerType) -> WsDr {
184 let graphic_frame = GraphicFrame {
185 nv_graphic_frame_pr: NvGraphicFramePr {
186 c_nv_pr: CNvPr {
187 id: 2,
188 name: "Chart 1".to_string(),
189 },
190 c_nv_graphic_frame_pr: CNvGraphicFramePr {},
191 },
192 xfrm: Xfrm {
193 off: Offset { x: 0, y: 0 },
194 ext: AExt { cx: 0, cy: 0 },
195 },
196 graphic: Graphic {
197 graphic_data: GraphicData {
198 uri: namespaces::DRAWING_ML_CHART.to_string(),
199 chart: ChartRef {
200 xmlns_c: namespaces::DRAWING_ML_CHART.to_string(),
201 r_id: chart_ref_id.to_string(),
202 },
203 },
204 },
205 };
206 let anchor = TwoCellAnchor {
207 from,
208 to,
209 graphic_frame: Some(graphic_frame),
210 pic: None,
211 client_data: ClientData {},
212 };
213 WsDr {
214 two_cell_anchors: vec![anchor],
215 ..WsDr::default()
216 }
217}
218
219fn is_no_axis_chart(ct: &ChartType) -> bool {
220 matches!(ct, ChartType::Pie | ChartType::Pie3D | ChartType::Doughnut)
221}
222
223fn is_3d_chart(ct: &ChartType) -> bool {
224 matches!(
225 ct,
226 ChartType::Area3D
227 | ChartType::Area3DStacked
228 | ChartType::Area3DPercentStacked
229 | ChartType::Col3D
230 | ChartType::Col3DStacked
231 | ChartType::Col3DPercentStacked
232 | ChartType::Bar3D
233 | ChartType::Bar3DStacked
234 | ChartType::Bar3DPercentStacked
235 | ChartType::Line3D
236 | ChartType::Pie3D
237 | ChartType::Surface3D
238 | ChartType::SurfaceWireframe3D
239 )
240}
241
242fn needs_ser_ax(ct: &ChartType) -> bool {
243 matches!(
244 ct,
245 ChartType::Surface
246 | ChartType::Surface3D
247 | ChartType::SurfaceWireframe
248 | ChartType::SurfaceWireframe3D
249 )
250}
251
252fn build_view_3d(config: &ChartConfig) -> Option<View3D> {
253 if let Some(v) = &config.view_3d {
254 return Some(View3D {
255 rot_x: v.rot_x.map(|val| IntVal { val }),
256 rot_y: v.rot_y.map(|val| IntVal { val }),
257 depth_percent: v.depth_percent.map(|val| UintVal { val }),
258 r_ang_ax: v.right_angle_axes.map(|val| BoolVal { val }),
259 perspective: v.perspective.map(|val| UintVal { val }),
260 });
261 }
262 if is_3d_chart(&config.chart_type) {
263 Some(View3D {
264 rot_x: Some(IntVal { val: 15 }),
265 rot_y: Some(IntVal { val: 20 }),
266 depth_percent: None,
267 r_ang_ax: Some(BoolVal { val: true }),
268 perspective: Some(UintVal { val: 30 }),
269 })
270 } else {
271 None
272 }
273}
274
275fn build_series_text(series: &ChartSeries) -> Option<SeriesText> {
276 if series.name.is_empty() {
277 None
278 } else if series.name.contains('!') {
279 Some(SeriesText {
280 str_ref: Some(StrRef {
281 f: series.name.clone(),
282 }),
283 v: None,
284 })
285 } else {
286 Some(SeriesText {
287 str_ref: None,
288 v: Some(series.name.clone()),
289 })
290 }
291}
292
293fn build_series(index: u32, series: &ChartSeries) -> Series {
294 let tx = build_series_text(series);
295 let cat = if series.categories.is_empty() {
296 None
297 } else {
298 Some(CategoryRef {
299 str_ref: Some(StrRef {
300 f: series.categories.clone(),
301 }),
302 num_ref: None,
303 })
304 };
305 let val = if series.values.is_empty() {
306 None
307 } else {
308 Some(ValueRef {
309 num_ref: Some(NumRef {
310 f: series.values.clone(),
311 }),
312 })
313 };
314 Series {
315 idx: UintVal { val: index },
316 order: UintVal { val: index },
317 tx,
318 cat,
319 val,
320 }
321}
322
323fn build_scatter_series(index: u32, series: &ChartSeries) -> ScatterSeries {
324 let tx = build_series_text(series);
325 let x_val = series
326 .x_values
327 .as_ref()
328 .or(if series.categories.is_empty() {
329 None
330 } else {
331 Some(&series.categories)
332 })
333 .map(|ref_str| CategoryRef {
334 str_ref: None,
335 num_ref: Some(NumRef { f: ref_str.clone() }),
336 });
337 let y_val = if series.values.is_empty() {
338 None
339 } else {
340 Some(ValueRef {
341 num_ref: Some(NumRef {
342 f: series.values.clone(),
343 }),
344 })
345 };
346 ScatterSeries {
347 idx: UintVal { val: index },
348 order: UintVal { val: index },
349 tx,
350 x_val,
351 y_val,
352 }
353}
354
355fn build_bubble_series(index: u32, series: &ChartSeries) -> BubbleSeries {
356 let tx = build_series_text(series);
357 let x_val = series
358 .x_values
359 .as_ref()
360 .or(if series.categories.is_empty() {
361 None
362 } else {
363 Some(&series.categories)
364 })
365 .map(|ref_str| CategoryRef {
366 str_ref: None,
367 num_ref: Some(NumRef { f: ref_str.clone() }),
368 });
369 let y_val = if series.values.is_empty() {
370 None
371 } else {
372 Some(ValueRef {
373 num_ref: Some(NumRef {
374 f: series.values.clone(),
375 }),
376 })
377 };
378 let bubble_size = series.bubble_sizes.as_ref().map(|ref_str| ValueRef {
379 num_ref: Some(NumRef { f: ref_str.clone() }),
380 });
381 BubbleSeries {
382 idx: UintVal { val: index },
383 order: UintVal { val: index },
384 tx,
385 x_val,
386 y_val,
387 bubble_size,
388 }
389}
390
391fn build_chart_title(text: &str) -> ChartTitle {
392 ChartTitle {
393 tx: TitleTx {
394 rich: RichText {
395 body_pr: BodyPr {},
396 paragraphs: vec![Paragraph {
397 runs: vec![Run {
398 t: text.to_string(),
399 }],
400 }],
401 },
402 },
403 }
404}
405
406fn build_standard_axes() -> (Option<CatAx>, Option<ValAx>) {
407 (
408 Some(CatAx {
409 ax_id: UintVal { val: 1 },
410 scaling: Scaling {
411 orientation: StringVal {
412 val: "minMax".to_string(),
413 },
414 },
415 delete: BoolVal { val: false },
416 ax_pos: StringVal {
417 val: "b".to_string(),
418 },
419 cross_ax: UintVal { val: 2 },
420 }),
421 Some(ValAx {
422 ax_id: UintVal { val: 2 },
423 scaling: Scaling {
424 orientation: StringVal {
425 val: "minMax".to_string(),
426 },
427 },
428 delete: BoolVal { val: false },
429 ax_pos: StringVal {
430 val: "l".to_string(),
431 },
432 cross_ax: UintVal { val: 1 },
433 }),
434 )
435}
436
437fn build_ser_ax() -> SerAx {
438 SerAx {
439 ax_id: UintVal { val: 3 },
440 scaling: Scaling {
441 orientation: StringVal {
442 val: "minMax".to_string(),
443 },
444 },
445 delete: BoolVal { val: false },
446 ax_pos: StringVal {
447 val: "b".to_string(),
448 },
449 cross_ax: UintVal { val: 1 },
450 }
451}
452
453fn standard_ax_ids() -> Vec<UintVal> {
454 vec![UintVal { val: 1 }, UintVal { val: 2 }]
455}
456
457fn surface_ax_ids() -> Vec<UintVal> {
458 vec![UintVal { val: 1 }, UintVal { val: 2 }, UintVal { val: 3 }]
459}
460
461fn build_plot_area(config: &ChartConfig) -> PlotArea {
462 let ct = &config.chart_type;
463 let no_axes = is_no_axis_chart(ct);
464 let (cat_ax, val_ax) = if no_axes {
465 (None, None)
466 } else {
467 build_standard_axes()
468 };
469 let ser_ax = if needs_ser_ax(ct) {
470 Some(build_ser_ax())
471 } else {
472 None
473 };
474 let ax_ids = if no_axes {
475 vec![]
476 } else if needs_ser_ax(ct) {
477 surface_ax_ids()
478 } else {
479 standard_ax_ids()
480 };
481
482 let xml_series: Vec<Series> = config
483 .series
484 .iter()
485 .enumerate()
486 .map(|(i, s)| build_series(i as u32, s))
487 .collect();
488
489 let mut plot_area = PlotArea {
490 layout: Some(Layout {}),
491 bar_chart: None,
492 bar_3d_chart: None,
493 line_chart: None,
494 line_3d_chart: None,
495 pie_chart: None,
496 pie_3d_chart: None,
497 doughnut_chart: None,
498 area_chart: None,
499 area_3d_chart: None,
500 scatter_chart: None,
501 bubble_chart: None,
502 radar_chart: None,
503 stock_chart: None,
504 surface_chart: None,
505 surface_3d_chart: None,
506 cat_ax,
507 val_ax,
508 ser_ax,
509 };
510
511 match ct {
512 ChartType::Col => {
513 plot_area.bar_chart = Some(BarChart {
514 bar_dir: StringVal { val: "col".into() },
515 grouping: StringVal {
516 val: "clustered".into(),
517 },
518 series: xml_series,
519 ax_ids,
520 });
521 }
522 ChartType::ColStacked => {
523 plot_area.bar_chart = Some(BarChart {
524 bar_dir: StringVal { val: "col".into() },
525 grouping: StringVal {
526 val: "stacked".into(),
527 },
528 series: xml_series,
529 ax_ids,
530 });
531 }
532 ChartType::ColPercentStacked => {
533 plot_area.bar_chart = Some(BarChart {
534 bar_dir: StringVal { val: "col".into() },
535 grouping: StringVal {
536 val: "percentStacked".into(),
537 },
538 series: xml_series,
539 ax_ids,
540 });
541 }
542 ChartType::Bar => {
543 plot_area.bar_chart = Some(BarChart {
544 bar_dir: StringVal { val: "bar".into() },
545 grouping: StringVal {
546 val: "clustered".into(),
547 },
548 series: xml_series,
549 ax_ids,
550 });
551 }
552 ChartType::BarStacked => {
553 plot_area.bar_chart = Some(BarChart {
554 bar_dir: StringVal { val: "bar".into() },
555 grouping: StringVal {
556 val: "stacked".into(),
557 },
558 series: xml_series,
559 ax_ids,
560 });
561 }
562 ChartType::BarPercentStacked => {
563 plot_area.bar_chart = Some(BarChart {
564 bar_dir: StringVal { val: "bar".into() },
565 grouping: StringVal {
566 val: "percentStacked".into(),
567 },
568 series: xml_series,
569 ax_ids,
570 });
571 }
572 ChartType::Line => {
573 plot_area.line_chart = Some(LineChart {
574 grouping: StringVal {
575 val: "standard".into(),
576 },
577 series: xml_series,
578 ax_ids,
579 });
580 }
581 ChartType::LineStacked => {
582 plot_area.line_chart = Some(LineChart {
583 grouping: StringVal {
584 val: "stacked".into(),
585 },
586 series: xml_series,
587 ax_ids,
588 });
589 }
590 ChartType::LinePercentStacked => {
591 plot_area.line_chart = Some(LineChart {
592 grouping: StringVal {
593 val: "percentStacked".into(),
594 },
595 series: xml_series,
596 ax_ids,
597 });
598 }
599 ChartType::Line3D => {
600 plot_area.line_3d_chart = Some(Line3DChart {
601 grouping: StringVal {
602 val: "standard".into(),
603 },
604 series: xml_series,
605 ax_ids,
606 });
607 }
608 ChartType::Pie => {
609 plot_area.pie_chart = Some(PieChart { series: xml_series });
610 }
611 ChartType::Pie3D => {
612 plot_area.pie_3d_chart = Some(Pie3DChart { series: xml_series });
613 }
614 ChartType::Doughnut => {
615 plot_area.doughnut_chart = Some(DoughnutChart {
616 series: xml_series,
617 hole_size: Some(UintVal { val: 50 }),
618 });
619 }
620 ChartType::Area => {
621 plot_area.area_chart = Some(AreaChart {
622 grouping: StringVal {
623 val: "standard".into(),
624 },
625 series: xml_series,
626 ax_ids,
627 });
628 }
629 ChartType::AreaStacked => {
630 plot_area.area_chart = Some(AreaChart {
631 grouping: StringVal {
632 val: "stacked".into(),
633 },
634 series: xml_series,
635 ax_ids,
636 });
637 }
638 ChartType::AreaPercentStacked => {
639 plot_area.area_chart = Some(AreaChart {
640 grouping: StringVal {
641 val: "percentStacked".into(),
642 },
643 series: xml_series,
644 ax_ids,
645 });
646 }
647 ChartType::Area3D => {
648 plot_area.area_3d_chart = Some(Area3DChart {
649 grouping: StringVal {
650 val: "standard".into(),
651 },
652 series: xml_series,
653 ax_ids,
654 });
655 }
656 ChartType::Area3DStacked => {
657 plot_area.area_3d_chart = Some(Area3DChart {
658 grouping: StringVal {
659 val: "stacked".into(),
660 },
661 series: xml_series,
662 ax_ids,
663 });
664 }
665 ChartType::Area3DPercentStacked => {
666 plot_area.area_3d_chart = Some(Area3DChart {
667 grouping: StringVal {
668 val: "percentStacked".into(),
669 },
670 series: xml_series,
671 ax_ids,
672 });
673 }
674 ChartType::Col3D => {
675 plot_area.bar_3d_chart = Some(Bar3DChart {
676 bar_dir: StringVal { val: "col".into() },
677 grouping: StringVal {
678 val: "clustered".into(),
679 },
680 series: xml_series,
681 ax_ids,
682 });
683 }
684 ChartType::Col3DStacked => {
685 plot_area.bar_3d_chart = Some(Bar3DChart {
686 bar_dir: StringVal { val: "col".into() },
687 grouping: StringVal {
688 val: "stacked".into(),
689 },
690 series: xml_series,
691 ax_ids,
692 });
693 }
694 ChartType::Col3DPercentStacked => {
695 plot_area.bar_3d_chart = Some(Bar3DChart {
696 bar_dir: StringVal { val: "col".into() },
697 grouping: StringVal {
698 val: "percentStacked".into(),
699 },
700 series: xml_series,
701 ax_ids,
702 });
703 }
704 ChartType::Bar3D => {
705 plot_area.bar_3d_chart = Some(Bar3DChart {
706 bar_dir: StringVal { val: "bar".into() },
707 grouping: StringVal {
708 val: "clustered".into(),
709 },
710 series: xml_series,
711 ax_ids,
712 });
713 }
714 ChartType::Bar3DStacked => {
715 plot_area.bar_3d_chart = Some(Bar3DChart {
716 bar_dir: StringVal { val: "bar".into() },
717 grouping: StringVal {
718 val: "stacked".into(),
719 },
720 series: xml_series,
721 ax_ids,
722 });
723 }
724 ChartType::Bar3DPercentStacked => {
725 plot_area.bar_3d_chart = Some(Bar3DChart {
726 bar_dir: StringVal { val: "bar".into() },
727 grouping: StringVal {
728 val: "percentStacked".into(),
729 },
730 series: xml_series,
731 ax_ids,
732 });
733 }
734 ChartType::Scatter => {
735 let ss: Vec<ScatterSeries> = config
736 .series
737 .iter()
738 .enumerate()
739 .map(|(i, s)| build_scatter_series(i as u32, s))
740 .collect();
741 plot_area.scatter_chart = Some(ScatterChart {
742 scatter_style: StringVal {
743 val: "lineMarker".into(),
744 },
745 series: ss,
746 ax_ids,
747 });
748 }
749 ChartType::ScatterLine => {
750 let ss: Vec<ScatterSeries> = config
751 .series
752 .iter()
753 .enumerate()
754 .map(|(i, s)| build_scatter_series(i as u32, s))
755 .collect();
756 plot_area.scatter_chart = Some(ScatterChart {
757 scatter_style: StringVal { val: "line".into() },
758 series: ss,
759 ax_ids,
760 });
761 }
762 ChartType::ScatterSmooth => {
763 let ss: Vec<ScatterSeries> = config
764 .series
765 .iter()
766 .enumerate()
767 .map(|(i, s)| build_scatter_series(i as u32, s))
768 .collect();
769 plot_area.scatter_chart = Some(ScatterChart {
770 scatter_style: StringVal {
771 val: "smoothMarker".into(),
772 },
773 series: ss,
774 ax_ids,
775 });
776 }
777 ChartType::Bubble => {
778 let bs: Vec<BubbleSeries> = config
779 .series
780 .iter()
781 .enumerate()
782 .map(|(i, s)| build_bubble_series(i as u32, s))
783 .collect();
784 plot_area.bubble_chart = Some(BubbleChart { series: bs, ax_ids });
785 }
786 ChartType::Radar => {
787 plot_area.radar_chart = Some(RadarChart {
788 radar_style: StringVal {
789 val: "standard".into(),
790 },
791 series: xml_series,
792 ax_ids,
793 });
794 }
795 ChartType::RadarFilled => {
796 plot_area.radar_chart = Some(RadarChart {
797 radar_style: StringVal {
798 val: "filled".into(),
799 },
800 series: xml_series,
801 ax_ids,
802 });
803 }
804 ChartType::RadarMarker => {
805 plot_area.radar_chart = Some(RadarChart {
806 radar_style: StringVal {
807 val: "marker".into(),
808 },
809 series: xml_series,
810 ax_ids,
811 });
812 }
813 ChartType::StockHLC
814 | ChartType::StockOHLC
815 | ChartType::StockVHLC
816 | ChartType::StockVOHLC => {
817 plot_area.stock_chart = Some(StockChart {
818 series: xml_series,
819 ax_ids,
820 });
821 }
822 ChartType::Surface => {
823 plot_area.surface_chart = Some(SurfaceChart {
824 wireframe: None,
825 series: xml_series,
826 ax_ids,
827 });
828 }
829 ChartType::SurfaceWireframe => {
830 plot_area.surface_chart = Some(SurfaceChart {
831 wireframe: Some(BoolVal { val: true }),
832 series: xml_series,
833 ax_ids,
834 });
835 }
836 ChartType::Surface3D => {
837 plot_area.surface_3d_chart = Some(Surface3DChart {
838 wireframe: None,
839 series: xml_series,
840 ax_ids,
841 });
842 }
843 ChartType::SurfaceWireframe3D => {
844 plot_area.surface_3d_chart = Some(Surface3DChart {
845 wireframe: Some(BoolVal { val: true }),
846 series: xml_series,
847 ax_ids,
848 });
849 }
850 ChartType::ColLine | ChartType::ColLineStacked | ChartType::ColLinePercentStacked => {
851 let grouping = match ct {
852 ChartType::ColLineStacked => "stacked",
853 ChartType::ColLinePercentStacked => "percentStacked",
854 _ => "clustered",
855 };
856 let total = xml_series.len();
857 let bar_count = total.div_ceil(2);
858 let bar_series: Vec<Series> = xml_series.iter().take(bar_count).cloned().collect();
859 let line_series: Vec<Series> = xml_series.iter().skip(bar_count).cloned().collect();
860 plot_area.bar_chart = Some(BarChart {
861 bar_dir: StringVal { val: "col".into() },
862 grouping: StringVal {
863 val: grouping.into(),
864 },
865 series: bar_series,
866 ax_ids: ax_ids.clone(),
867 });
868 plot_area.line_chart = Some(LineChart {
869 grouping: StringVal {
870 val: "standard".into(),
871 },
872 series: line_series,
873 ax_ids,
874 });
875 }
876 }
877
878 plot_area
879}
880
881#[cfg(test)]
882mod tests {
883 use super::*;
884
885 fn ss() -> Vec<ChartSeries> {
886 vec![ChartSeries {
887 name: "Revenue".into(),
888 categories: "Sheet1!$A$2:$A$6".into(),
889 values: "Sheet1!$B$2:$B$6".into(),
890 x_values: None,
891 bubble_sizes: None,
892 }]
893 }
894
895 fn mc(chart_type: ChartType) -> ChartConfig {
896 ChartConfig {
897 chart_type,
898 title: None,
899 series: ss(),
900 show_legend: false,
901 view_3d: None,
902 }
903 }
904
905 #[test]
906 fn test_build_chart_xml_col() {
907 let config = ChartConfig {
908 chart_type: ChartType::Col,
909 title: Some("Sales Chart".into()),
910 series: ss(),
911 show_legend: true,
912 view_3d: None,
913 };
914 let cs = build_chart_xml(&config);
915 assert!(cs.chart.title.is_some());
916 assert!(cs.chart.legend.is_some());
917 assert!(cs.chart.plot_area.bar_chart.is_some());
918 assert!(cs.chart.plot_area.line_chart.is_none());
919 assert!(cs.chart.plot_area.pie_chart.is_none());
920 assert!(cs.chart.view_3d.is_none());
921 let bar = cs.chart.plot_area.bar_chart.unwrap();
922 assert_eq!(bar.bar_dir.val, "col");
923 assert_eq!(bar.grouping.val, "clustered");
924 assert_eq!(bar.series.len(), 1);
925 assert_eq!(bar.ax_ids.len(), 2);
926 }
927
928 #[test]
929 fn test_build_chart_xml_bar() {
930 let cs = build_chart_xml(&ChartConfig {
931 chart_type: ChartType::Bar,
932 title: None,
933 series: vec![],
934 show_legend: false,
935 view_3d: None,
936 });
937 assert!(cs.chart.title.is_none());
938 assert!(cs.chart.legend.is_none());
939 let bar = cs.chart.plot_area.bar_chart.unwrap();
940 assert_eq!(bar.bar_dir.val, "bar");
941 assert_eq!(bar.grouping.val, "clustered");
942 }
943
944 #[test]
945 fn test_bar_stacked() {
946 let cs = build_chart_xml(&mc(ChartType::BarStacked));
947 let bar = cs.chart.plot_area.bar_chart.unwrap();
948 assert_eq!(bar.bar_dir.val, "bar");
949 assert_eq!(bar.grouping.val, "stacked");
950 }
951
952 #[test]
953 fn test_col_stacked() {
954 let cs = build_chart_xml(&mc(ChartType::ColStacked));
955 let bar = cs.chart.plot_area.bar_chart.unwrap();
956 assert_eq!(bar.bar_dir.val, "col");
957 assert_eq!(bar.grouping.val, "stacked");
958 }
959
960 #[test]
961 fn test_col_percent_stacked() {
962 let cs = build_chart_xml(&mc(ChartType::ColPercentStacked));
963 let bar = cs.chart.plot_area.bar_chart.unwrap();
964 assert_eq!(bar.grouping.val, "percentStacked");
965 }
966
967 #[test]
968 fn test_bar_percent_stacked() {
969 let cs = build_chart_xml(&mc(ChartType::BarPercentStacked));
970 let bar = cs.chart.plot_area.bar_chart.unwrap();
971 assert_eq!(bar.bar_dir.val, "bar");
972 assert_eq!(bar.grouping.val, "percentStacked");
973 }
974
975 #[test]
976 fn test_line() {
977 let cs = build_chart_xml(&ChartConfig {
978 chart_type: ChartType::Line,
979 title: Some("Trend".into()),
980 series: vec![ChartSeries {
981 name: "Sheet1!$A$1".into(),
982 categories: "Sheet1!$A$2:$A$6".into(),
983 values: "Sheet1!$B$2:$B$6".into(),
984 x_values: None,
985 bubble_sizes: None,
986 }],
987 show_legend: true,
988 view_3d: None,
989 });
990 assert!(cs.chart.plot_area.line_chart.is_some());
991 let line = cs.chart.plot_area.line_chart.unwrap();
992 assert_eq!(line.grouping.val, "standard");
993 assert_eq!(line.series.len(), 1);
994 let tx = line.series[0].tx.as_ref().unwrap();
995 assert!(tx.str_ref.is_some());
996 assert!(tx.v.is_none());
997 }
998
999 #[test]
1000 fn test_pie() {
1001 let cs = build_chart_xml(&ChartConfig {
1002 chart_type: ChartType::Pie,
1003 title: Some("Distribution".into()),
1004 series: vec![ChartSeries {
1005 name: "Data".into(),
1006 categories: "Sheet1!$A$2:$A$6".into(),
1007 values: "Sheet1!$B$2:$B$6".into(),
1008 x_values: None,
1009 bubble_sizes: None,
1010 }],
1011 show_legend: true,
1012 view_3d: None,
1013 });
1014 assert!(cs.chart.plot_area.pie_chart.is_some());
1015 assert!(cs.chart.plot_area.cat_ax.is_none());
1016 assert!(cs.chart.plot_area.val_ax.is_none());
1017 let pie = cs.chart.plot_area.pie_chart.unwrap();
1018 let tx = pie.series[0].tx.as_ref().unwrap();
1019 assert!(tx.str_ref.is_none());
1020 assert_eq!(tx.v.as_deref(), Some("Data"));
1021 }
1022
1023 #[test]
1024 fn test_no_legend() {
1025 let cs = build_chart_xml(&mc(ChartType::Col));
1026 assert!(cs.chart.legend.is_none());
1027 }
1028
1029 #[test]
1030 fn test_axes_present_for_non_pie() {
1031 let cs = build_chart_xml(&mc(ChartType::Line));
1032 assert!(cs.chart.plot_area.cat_ax.is_some());
1033 assert!(cs.chart.plot_area.val_ax.is_some());
1034 }
1035
1036 #[test]
1037 fn test_drawing_with_chart() {
1038 let from = MarkerType {
1039 col: 1,
1040 col_off: 0,
1041 row: 1,
1042 row_off: 0,
1043 };
1044 let to = MarkerType {
1045 col: 10,
1046 col_off: 0,
1047 row: 15,
1048 row_off: 0,
1049 };
1050 let dr = build_drawing_with_chart("rId1", from, to);
1051 assert_eq!(dr.two_cell_anchors.len(), 1);
1052 let anchor = &dr.two_cell_anchors[0];
1053 assert!(anchor.graphic_frame.is_some());
1054 assert_eq!(anchor.from.col, 1);
1055 assert_eq!(anchor.to.col, 10);
1056 let gf = anchor.graphic_frame.as_ref().unwrap();
1057 assert_eq!(gf.graphic.graphic_data.chart.r_id, "rId1");
1058 }
1059
1060 #[test]
1061 fn test_series_literal_name() {
1062 let s = ChartSeries {
1063 name: "MyName".into(),
1064 categories: "Sheet1!$A$2:$A$6".into(),
1065 values: "Sheet1!$B$2:$B$6".into(),
1066 x_values: None,
1067 bubble_sizes: None,
1068 };
1069 let xs = build_series(0, &s);
1070 let tx = xs.tx.as_ref().unwrap();
1071 assert!(tx.str_ref.is_none());
1072 assert_eq!(tx.v.as_deref(), Some("MyName"));
1073 }
1074
1075 #[test]
1076 fn test_series_cell_ref_name() {
1077 let s = ChartSeries {
1078 name: "Sheet1!$C$1".into(),
1079 categories: "".into(),
1080 values: "Sheet1!$B$2:$B$6".into(),
1081 x_values: None,
1082 bubble_sizes: None,
1083 };
1084 let xs = build_series(0, &s);
1085 let tx = xs.tx.as_ref().unwrap();
1086 assert!(tx.str_ref.is_some());
1087 assert!(tx.v.is_none());
1088 assert!(xs.cat.is_none());
1089 }
1090
1091 #[test]
1092 fn test_series_empty_name() {
1093 let s = ChartSeries {
1094 name: "".into(),
1095 categories: "Sheet1!$A$2:$A$6".into(),
1096 values: "Sheet1!$B$2:$B$6".into(),
1097 x_values: None,
1098 bubble_sizes: None,
1099 };
1100 let xs = build_series(0, &s);
1101 assert!(xs.tx.is_none());
1102 }
1103
1104 #[test]
1105 fn test_multiple_series() {
1106 let cs = build_chart_xml(&ChartConfig {
1107 chart_type: ChartType::Col,
1108 title: None,
1109 series: vec![
1110 ChartSeries {
1111 name: "A".into(),
1112 categories: "Sheet1!$A$2:$A$6".into(),
1113 values: "Sheet1!$B$2:$B$6".into(),
1114 x_values: None,
1115 bubble_sizes: None,
1116 },
1117 ChartSeries {
1118 name: "B".into(),
1119 categories: "Sheet1!$A$2:$A$6".into(),
1120 values: "Sheet1!$C$2:$C$6".into(),
1121 x_values: None,
1122 bubble_sizes: None,
1123 },
1124 ],
1125 show_legend: true,
1126 view_3d: None,
1127 });
1128 let bar = cs.chart.plot_area.bar_chart.unwrap();
1129 assert_eq!(bar.series.len(), 2);
1130 assert_eq!(bar.series[0].idx.val, 0);
1131 assert_eq!(bar.series[1].idx.val, 1);
1132 }
1133
1134 #[test]
1135 fn test_area_chart() {
1136 let cs = build_chart_xml(&mc(ChartType::Area));
1137 assert!(cs.chart.plot_area.area_chart.is_some());
1138 let a = cs.chart.plot_area.area_chart.unwrap();
1139 assert_eq!(a.grouping.val, "standard");
1140 assert_eq!(a.ax_ids.len(), 2);
1141 assert!(cs.chart.view_3d.is_none());
1142 }
1143
1144 #[test]
1145 fn test_area_stacked() {
1146 let cs = build_chart_xml(&mc(ChartType::AreaStacked));
1147 assert_eq!(
1148 cs.chart.plot_area.area_chart.unwrap().grouping.val,
1149 "stacked"
1150 );
1151 }
1152
1153 #[test]
1154 fn test_area_percent_stacked() {
1155 let cs = build_chart_xml(&mc(ChartType::AreaPercentStacked));
1156 assert_eq!(
1157 cs.chart.plot_area.area_chart.unwrap().grouping.val,
1158 "percentStacked"
1159 );
1160 }
1161
1162 #[test]
1163 fn test_area_3d() {
1164 let cs = build_chart_xml(&mc(ChartType::Area3D));
1165 assert!(cs.chart.view_3d.is_some());
1166 assert_eq!(
1167 cs.chart.plot_area.area_3d_chart.unwrap().grouping.val,
1168 "standard"
1169 );
1170 }
1171
1172 #[test]
1173 fn test_area_3d_stacked() {
1174 let cs = build_chart_xml(&mc(ChartType::Area3DStacked));
1175 assert!(cs.chart.view_3d.is_some());
1176 assert_eq!(
1177 cs.chart.plot_area.area_3d_chart.unwrap().grouping.val,
1178 "stacked"
1179 );
1180 }
1181
1182 #[test]
1183 fn test_area_3d_percent_stacked() {
1184 let cs = build_chart_xml(&mc(ChartType::Area3DPercentStacked));
1185 assert!(cs.chart.view_3d.is_some());
1186 assert_eq!(
1187 cs.chart.plot_area.area_3d_chart.unwrap().grouping.val,
1188 "percentStacked"
1189 );
1190 }
1191
1192 #[test]
1193 fn test_col_3d() {
1194 let cs = build_chart_xml(&mc(ChartType::Col3D));
1195 assert!(cs.chart.view_3d.is_some());
1196 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1197 assert_eq!(b.bar_dir.val, "col");
1198 assert_eq!(b.grouping.val, "clustered");
1199 }
1200
1201 #[test]
1202 fn test_col_3d_stacked() {
1203 let cs = build_chart_xml(&mc(ChartType::Col3DStacked));
1204 assert_eq!(
1205 cs.chart.plot_area.bar_3d_chart.unwrap().grouping.val,
1206 "stacked"
1207 );
1208 }
1209
1210 #[test]
1211 fn test_col_3d_percent_stacked() {
1212 let cs = build_chart_xml(&mc(ChartType::Col3DPercentStacked));
1213 assert_eq!(
1214 cs.chart.plot_area.bar_3d_chart.unwrap().grouping.val,
1215 "percentStacked"
1216 );
1217 }
1218
1219 #[test]
1220 fn test_bar_3d() {
1221 let cs = build_chart_xml(&mc(ChartType::Bar3D));
1222 assert!(cs.chart.view_3d.is_some());
1223 let b = cs.chart.plot_area.bar_3d_chart.unwrap();
1224 assert_eq!(b.bar_dir.val, "bar");
1225 assert_eq!(b.grouping.val, "clustered");
1226 }
1227
1228 #[test]
1229 fn test_bar_3d_stacked() {
1230 let cs = build_chart_xml(&mc(ChartType::Bar3DStacked));
1231 assert_eq!(
1232 cs.chart.plot_area.bar_3d_chart.unwrap().grouping.val,
1233 "stacked"
1234 );
1235 }
1236
1237 #[test]
1238 fn test_bar_3d_percent_stacked() {
1239 let cs = build_chart_xml(&mc(ChartType::Bar3DPercentStacked));
1240 assert_eq!(
1241 cs.chart.plot_area.bar_3d_chart.unwrap().grouping.val,
1242 "percentStacked"
1243 );
1244 }
1245
1246 #[test]
1247 fn test_line_stacked() {
1248 let cs = build_chart_xml(&mc(ChartType::LineStacked));
1249 assert_eq!(
1250 cs.chart.plot_area.line_chart.unwrap().grouping.val,
1251 "stacked"
1252 );
1253 }
1254
1255 #[test]
1256 fn test_line_percent_stacked() {
1257 let cs = build_chart_xml(&mc(ChartType::LinePercentStacked));
1258 assert_eq!(
1259 cs.chart.plot_area.line_chart.unwrap().grouping.val,
1260 "percentStacked"
1261 );
1262 }
1263
1264 #[test]
1265 fn test_line_3d() {
1266 let cs = build_chart_xml(&mc(ChartType::Line3D));
1267 assert!(cs.chart.view_3d.is_some());
1268 assert!(cs.chart.plot_area.line_3d_chart.is_some());
1269 }
1270
1271 #[test]
1272 fn test_pie_3d() {
1273 let cs = build_chart_xml(&mc(ChartType::Pie3D));
1274 assert!(cs.chart.view_3d.is_some());
1275 assert!(cs.chart.plot_area.pie_3d_chart.is_some());
1276 assert!(cs.chart.plot_area.cat_ax.is_none());
1277 }
1278
1279 #[test]
1280 fn test_doughnut() {
1281 let cs = build_chart_xml(&mc(ChartType::Doughnut));
1282 assert!(cs.chart.plot_area.doughnut_chart.is_some());
1283 assert!(cs.chart.plot_area.cat_ax.is_none());
1284 let d = cs.chart.plot_area.doughnut_chart.unwrap();
1285 assert_eq!(d.hole_size.as_ref().unwrap().val, 50);
1286 }
1287
1288 #[test]
1289 fn test_scatter() {
1290 let cs = build_chart_xml(&ChartConfig {
1291 chart_type: ChartType::Scatter,
1292 title: None,
1293 series: vec![ChartSeries {
1294 name: "XY".into(),
1295 categories: "Sheet1!$A$2:$A$6".into(),
1296 values: "Sheet1!$B$2:$B$6".into(),
1297 x_values: None,
1298 bubble_sizes: None,
1299 }],
1300 show_legend: false,
1301 view_3d: None,
1302 });
1303 let sc = cs.chart.plot_area.scatter_chart.unwrap();
1304 assert_eq!(sc.scatter_style.val, "lineMarker");
1305 assert_eq!(sc.series.len(), 1);
1306 let s = &sc.series[0];
1307 assert_eq!(
1308 s.x_val.as_ref().unwrap().num_ref.as_ref().unwrap().f,
1309 "Sheet1!$A$2:$A$6"
1310 );
1311 assert_eq!(
1312 s.y_val.as_ref().unwrap().num_ref.as_ref().unwrap().f,
1313 "Sheet1!$B$2:$B$6"
1314 );
1315 }
1316
1317 #[test]
1318 fn test_scatter_explicit_x() {
1319 let cs = build_chart_xml(&ChartConfig {
1320 chart_type: ChartType::Scatter,
1321 title: None,
1322 series: vec![ChartSeries {
1323 name: "".into(),
1324 categories: "Sheet1!$A$2:$A$6".into(),
1325 values: "Sheet1!$B$2:$B$6".into(),
1326 x_values: Some("Sheet1!$D$2:$D$6".into()),
1327 bubble_sizes: None,
1328 }],
1329 show_legend: false,
1330 view_3d: None,
1331 });
1332 let s = &cs.chart.plot_area.scatter_chart.unwrap().series[0];
1333 assert_eq!(
1334 s.x_val.as_ref().unwrap().num_ref.as_ref().unwrap().f,
1335 "Sheet1!$D$2:$D$6"
1336 );
1337 }
1338
1339 #[test]
1340 fn test_scatter_line() {
1341 let cs = build_chart_xml(&mc(ChartType::ScatterLine));
1342 assert_eq!(
1343 cs.chart.plot_area.scatter_chart.unwrap().scatter_style.val,
1344 "line"
1345 );
1346 }
1347
1348 #[test]
1349 fn test_scatter_smooth() {
1350 let cs = build_chart_xml(&mc(ChartType::ScatterSmooth));
1351 assert_eq!(
1352 cs.chart.plot_area.scatter_chart.unwrap().scatter_style.val,
1353 "smoothMarker"
1354 );
1355 }
1356
1357 #[test]
1358 fn test_bubble() {
1359 let cs = build_chart_xml(&ChartConfig {
1360 chart_type: ChartType::Bubble,
1361 title: None,
1362 series: vec![ChartSeries {
1363 name: "B".into(),
1364 categories: "Sheet1!$A$2:$A$6".into(),
1365 values: "Sheet1!$B$2:$B$6".into(),
1366 x_values: None,
1367 bubble_sizes: Some("Sheet1!$C$2:$C$6".into()),
1368 }],
1369 show_legend: false,
1370 view_3d: None,
1371 });
1372 let b = cs.chart.plot_area.bubble_chart.unwrap();
1373 assert_eq!(b.series.len(), 1);
1374 assert_eq!(
1375 b.series[0]
1376 .bubble_size
1377 .as_ref()
1378 .unwrap()
1379 .num_ref
1380 .as_ref()
1381 .unwrap()
1382 .f,
1383 "Sheet1!$C$2:$C$6"
1384 );
1385 }
1386
1387 #[test]
1388 fn test_radar() {
1389 let cs = build_chart_xml(&mc(ChartType::Radar));
1390 assert_eq!(
1391 cs.chart.plot_area.radar_chart.unwrap().radar_style.val,
1392 "standard"
1393 );
1394 }
1395
1396 #[test]
1397 fn test_radar_filled() {
1398 let cs = build_chart_xml(&mc(ChartType::RadarFilled));
1399 assert_eq!(
1400 cs.chart.plot_area.radar_chart.unwrap().radar_style.val,
1401 "filled"
1402 );
1403 }
1404
1405 #[test]
1406 fn test_radar_marker() {
1407 let cs = build_chart_xml(&mc(ChartType::RadarMarker));
1408 assert_eq!(
1409 cs.chart.plot_area.radar_chart.unwrap().radar_style.val,
1410 "marker"
1411 );
1412 }
1413
1414 #[test]
1415 fn test_stock_hlc() {
1416 let cs = build_chart_xml(&mc(ChartType::StockHLC));
1417 assert!(cs.chart.plot_area.stock_chart.is_some());
1418 }
1419
1420 #[test]
1421 fn test_stock_ohlc() {
1422 let cs = build_chart_xml(&mc(ChartType::StockOHLC));
1423 assert!(cs.chart.plot_area.stock_chart.is_some());
1424 }
1425
1426 #[test]
1427 fn test_stock_vhlc() {
1428 let cs = build_chart_xml(&mc(ChartType::StockVHLC));
1429 assert!(cs.chart.plot_area.stock_chart.is_some());
1430 }
1431
1432 #[test]
1433 fn test_stock_vohlc() {
1434 let cs = build_chart_xml(&mc(ChartType::StockVOHLC));
1435 assert!(cs.chart.plot_area.stock_chart.is_some());
1436 }
1437
1438 #[test]
1439 fn test_surface() {
1440 let cs = build_chart_xml(&mc(ChartType::Surface));
1441 assert!(cs.chart.plot_area.surface_chart.is_some());
1442 assert!(cs.chart.plot_area.ser_ax.is_some());
1443 let sf = cs.chart.plot_area.surface_chart.unwrap();
1444 assert!(sf.wireframe.is_none());
1445 assert_eq!(sf.ax_ids.len(), 3);
1446 }
1447
1448 #[test]
1449 fn test_surface_wireframe() {
1450 let cs = build_chart_xml(&mc(ChartType::SurfaceWireframe));
1451 let sf = cs.chart.plot_area.surface_chart.unwrap();
1452 assert!(sf.wireframe.as_ref().unwrap().val);
1453 assert_eq!(sf.ax_ids.len(), 3);
1454 }
1455
1456 #[test]
1457 fn test_surface_3d() {
1458 let cs = build_chart_xml(&mc(ChartType::Surface3D));
1459 assert!(cs.chart.view_3d.is_some());
1460 assert!(cs.chart.plot_area.surface_3d_chart.is_some());
1461 assert!(cs.chart.plot_area.ser_ax.is_some());
1462 }
1463
1464 #[test]
1465 fn test_surface_wireframe_3d() {
1466 let cs = build_chart_xml(&mc(ChartType::SurfaceWireframe3D));
1467 assert!(cs.chart.view_3d.is_some());
1468 let sf = cs.chart.plot_area.surface_3d_chart.unwrap();
1469 assert!(sf.wireframe.as_ref().unwrap().val);
1470 }
1471
1472 #[test]
1473 fn test_col_line_combo() {
1474 let cs = build_chart_xml(&ChartConfig {
1475 chart_type: ChartType::ColLine,
1476 title: None,
1477 series: vec![
1478 ChartSeries {
1479 name: "A".into(),
1480 categories: "Sheet1!$A$2:$A$6".into(),
1481 values: "Sheet1!$B$2:$B$6".into(),
1482 x_values: None,
1483 bubble_sizes: None,
1484 },
1485 ChartSeries {
1486 name: "B".into(),
1487 categories: "Sheet1!$A$2:$A$6".into(),
1488 values: "Sheet1!$C$2:$C$6".into(),
1489 x_values: None,
1490 bubble_sizes: None,
1491 },
1492 ],
1493 show_legend: true,
1494 view_3d: None,
1495 });
1496 assert!(cs.chart.plot_area.bar_chart.is_some());
1497 assert!(cs.chart.plot_area.line_chart.is_some());
1498 let bar = cs.chart.plot_area.bar_chart.unwrap();
1499 assert_eq!(bar.grouping.val, "clustered");
1500 assert_eq!(bar.series.len(), 1);
1501 assert_eq!(cs.chart.plot_area.line_chart.unwrap().series.len(), 1);
1502 }
1503
1504 #[test]
1505 fn test_col_line_stacked_combo() {
1506 let cs = build_chart_xml(&ChartConfig {
1507 chart_type: ChartType::ColLineStacked,
1508 title: None,
1509 series: vec![
1510 ChartSeries {
1511 name: "A".into(),
1512 categories: "".into(),
1513 values: "Sheet1!$B$2:$B$6".into(),
1514 x_values: None,
1515 bubble_sizes: None,
1516 },
1517 ChartSeries {
1518 name: "B".into(),
1519 categories: "".into(),
1520 values: "Sheet1!$C$2:$C$6".into(),
1521 x_values: None,
1522 bubble_sizes: None,
1523 },
1524 ChartSeries {
1525 name: "C".into(),
1526 categories: "".into(),
1527 values: "Sheet1!$D$2:$D$6".into(),
1528 x_values: None,
1529 bubble_sizes: None,
1530 },
1531 ],
1532 show_legend: false,
1533 view_3d: None,
1534 });
1535 let bar = cs.chart.plot_area.bar_chart.unwrap();
1536 assert_eq!(bar.grouping.val, "stacked");
1537 assert_eq!(bar.series.len(), 2);
1538 assert_eq!(cs.chart.plot_area.line_chart.unwrap().series.len(), 1);
1539 }
1540
1541 #[test]
1542 fn test_col_line_percent_stacked_combo() {
1543 let cs = build_chart_xml(&ChartConfig {
1544 chart_type: ChartType::ColLinePercentStacked,
1545 title: None,
1546 series: vec![ChartSeries {
1547 name: "A".into(),
1548 categories: "".into(),
1549 values: "Sheet1!$B$2:$B$6".into(),
1550 x_values: None,
1551 bubble_sizes: None,
1552 }],
1553 show_legend: false,
1554 view_3d: None,
1555 });
1556 let bar = cs.chart.plot_area.bar_chart.unwrap();
1557 assert_eq!(bar.grouping.val, "percentStacked");
1558 assert_eq!(bar.series.len(), 1);
1559 assert_eq!(cs.chart.plot_area.line_chart.unwrap().series.len(), 0);
1560 }
1561
1562 #[test]
1563 fn test_view_3d_explicit() {
1564 let cs = build_chart_xml(&ChartConfig {
1565 chart_type: ChartType::Col3D,
1566 title: None,
1567 series: vec![],
1568 show_legend: false,
1569 view_3d: Some(View3DConfig {
1570 rot_x: Some(30),
1571 rot_y: Some(40),
1572 depth_percent: Some(200),
1573 right_angle_axes: Some(false),
1574 perspective: Some(10),
1575 }),
1576 });
1577 let v = cs.chart.view_3d.unwrap();
1578 assert_eq!(v.rot_x.unwrap().val, 30);
1579 assert_eq!(v.rot_y.unwrap().val, 40);
1580 assert_eq!(v.depth_percent.unwrap().val, 200);
1581 assert!(!v.r_ang_ax.unwrap().val);
1582 assert_eq!(v.perspective.unwrap().val, 10);
1583 }
1584
1585 #[test]
1586 fn test_view_3d_auto_defaults() {
1587 let cs = build_chart_xml(&mc(ChartType::Col3D));
1588 let v = cs.chart.view_3d.unwrap();
1589 assert_eq!(v.rot_x.unwrap().val, 15);
1590 assert_eq!(v.rot_y.unwrap().val, 20);
1591 assert!(v.r_ang_ax.unwrap().val);
1592 assert_eq!(v.perspective.unwrap().val, 30);
1593 }
1594
1595 #[test]
1596 fn test_non_3d_no_view() {
1597 let cs = build_chart_xml(&mc(ChartType::Col));
1598 assert!(cs.chart.view_3d.is_none());
1599 }
1600
1601 #[test]
1602 fn test_chart_type_enum_coverage() {
1603 let types = [
1604 ChartType::Col,
1605 ChartType::ColStacked,
1606 ChartType::ColPercentStacked,
1607 ChartType::Bar,
1608 ChartType::BarStacked,
1609 ChartType::BarPercentStacked,
1610 ChartType::Line,
1611 ChartType::Pie,
1612 ChartType::Area,
1613 ChartType::AreaStacked,
1614 ChartType::AreaPercentStacked,
1615 ChartType::Area3D,
1616 ChartType::Area3DStacked,
1617 ChartType::Area3DPercentStacked,
1618 ChartType::Col3D,
1619 ChartType::Col3DStacked,
1620 ChartType::Col3DPercentStacked,
1621 ChartType::Bar3D,
1622 ChartType::Bar3DStacked,
1623 ChartType::Bar3DPercentStacked,
1624 ChartType::LineStacked,
1625 ChartType::LinePercentStacked,
1626 ChartType::Line3D,
1627 ChartType::Pie3D,
1628 ChartType::Doughnut,
1629 ChartType::Scatter,
1630 ChartType::ScatterLine,
1631 ChartType::ScatterSmooth,
1632 ChartType::Radar,
1633 ChartType::RadarFilled,
1634 ChartType::RadarMarker,
1635 ChartType::StockHLC,
1636 ChartType::StockOHLC,
1637 ChartType::StockVHLC,
1638 ChartType::StockVOHLC,
1639 ChartType::Bubble,
1640 ChartType::Surface,
1641 ChartType::Surface3D,
1642 ChartType::SurfaceWireframe,
1643 ChartType::SurfaceWireframe3D,
1644 ChartType::ColLine,
1645 ChartType::ColLineStacked,
1646 ChartType::ColLinePercentStacked,
1647 ];
1648 for ct in &types {
1649 let _ = build_chart_xml(&ChartConfig {
1650 chart_type: ct.clone(),
1651 title: None,
1652 series: vec![],
1653 show_legend: false,
1654 view_3d: None,
1655 });
1656 }
1657 }
1658
1659 #[test]
1660 fn test_scatter_empty_categories() {
1661 let cs = build_chart_xml(&ChartConfig {
1662 chart_type: ChartType::Scatter,
1663 title: None,
1664 series: vec![ChartSeries {
1665 name: "".into(),
1666 categories: "".into(),
1667 values: "Sheet1!$B$2:$B$6".into(),
1668 x_values: None,
1669 bubble_sizes: None,
1670 }],
1671 show_legend: false,
1672 view_3d: None,
1673 });
1674 let s = &cs.chart.plot_area.scatter_chart.unwrap().series[0];
1675 assert!(s.x_val.is_none());
1676 assert!(s.y_val.is_some());
1677 }
1678
1679 #[test]
1680 fn test_bubble_no_sizes() {
1681 let cs = build_chart_xml(&ChartConfig {
1682 chart_type: ChartType::Bubble,
1683 title: None,
1684 series: vec![ChartSeries {
1685 name: "".into(),
1686 categories: "Sheet1!$A$2:$A$6".into(),
1687 values: "Sheet1!$B$2:$B$6".into(),
1688 x_values: None,
1689 bubble_sizes: None,
1690 }],
1691 show_legend: false,
1692 view_3d: None,
1693 });
1694 assert!(cs.chart.plot_area.bubble_chart.unwrap().series[0]
1695 .bubble_size
1696 .is_none());
1697 }
1698}