Skip to main content

sheetkit_xml/
chart.rs

1//! Chart XML schema structures.
2//!
3//! Represents `xl/charts/chart{N}.xml` in the OOXML package.
4
5use serde::{Deserialize, Serialize};
6
7use crate::namespaces;
8
9/// Root element for a chart part.
10#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
11#[serde(rename = "chartSpace")]
12pub struct ChartSpace {
13    #[serde(rename = "@xmlns:c")]
14    pub xmlns_c: String,
15
16    #[serde(rename = "@xmlns:a")]
17    pub xmlns_a: String,
18
19    #[serde(rename = "@xmlns:r")]
20    pub xmlns_r: String,
21
22    #[serde(rename = "c:chart")]
23    pub chart: Chart,
24}
25
26impl Default for ChartSpace {
27    fn default() -> Self {
28        Self {
29            xmlns_c: namespaces::DRAWING_ML_CHART.to_string(),
30            xmlns_a: namespaces::DRAWING_ML.to_string(),
31            xmlns_r: namespaces::RELATIONSHIPS.to_string(),
32            chart: Chart::default(),
33        }
34    }
35}
36
37/// The chart element containing plot area, legend, and title.
38#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
39pub struct Chart {
40    #[serde(rename = "c:title", skip_serializing_if = "Option::is_none")]
41    pub title: Option<ChartTitle>,
42
43    #[serde(rename = "c:view3D", skip_serializing_if = "Option::is_none")]
44    pub view_3d: Option<View3D>,
45
46    #[serde(rename = "c:plotArea")]
47    pub plot_area: PlotArea,
48
49    #[serde(rename = "c:legend", skip_serializing_if = "Option::is_none")]
50    pub legend: Option<Legend>,
51
52    #[serde(rename = "c:plotVisOnly", skip_serializing_if = "Option::is_none")]
53    pub plot_vis_only: Option<BoolVal>,
54}
55
56/// Chart title.
57#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
58pub struct ChartTitle {
59    #[serde(rename = "c:tx")]
60    pub tx: TitleTx,
61}
62
63/// Title text body.
64#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
65pub struct TitleTx {
66    #[serde(rename = "c:rich")]
67    pub rich: RichText,
68}
69
70/// Rich text body for chart titles.
71#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
72pub struct RichText {
73    #[serde(rename = "a:bodyPr")]
74    pub body_pr: BodyPr,
75
76    #[serde(rename = "a:p")]
77    pub paragraphs: Vec<Paragraph>,
78}
79
80/// Body properties (empty marker for chart titles).
81#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
82pub struct BodyPr {}
83
84/// A paragraph in rich text.
85#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
86pub struct Paragraph {
87    #[serde(rename = "a:r", default)]
88    pub runs: Vec<Run>,
89}
90
91/// A text run within a paragraph.
92#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
93pub struct Run {
94    #[serde(rename = "a:t")]
95    pub t: String,
96}
97
98/// Plot area containing chart type definitions and axes.
99#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Default)]
100pub struct PlotArea {
101    #[serde(rename = "c:layout", skip_serializing_if = "Option::is_none")]
102    pub layout: Option<Layout>,
103
104    #[serde(rename = "c:barChart", skip_serializing_if = "Option::is_none")]
105    pub bar_chart: Option<BarChart>,
106
107    #[serde(rename = "c:bar3DChart", skip_serializing_if = "Option::is_none")]
108    pub bar_3d_chart: Option<Bar3DChart>,
109
110    #[serde(rename = "c:lineChart", skip_serializing_if = "Option::is_none")]
111    pub line_chart: Option<LineChart>,
112
113    #[serde(rename = "c:line3DChart", skip_serializing_if = "Option::is_none")]
114    pub line_3d_chart: Option<Line3DChart>,
115
116    #[serde(rename = "c:pieChart", skip_serializing_if = "Option::is_none")]
117    pub pie_chart: Option<PieChart>,
118
119    #[serde(rename = "c:pie3DChart", skip_serializing_if = "Option::is_none")]
120    pub pie_3d_chart: Option<Pie3DChart>,
121
122    #[serde(rename = "c:doughnutChart", skip_serializing_if = "Option::is_none")]
123    pub doughnut_chart: Option<DoughnutChart>,
124
125    #[serde(rename = "c:areaChart", skip_serializing_if = "Option::is_none")]
126    pub area_chart: Option<AreaChart>,
127
128    #[serde(rename = "c:area3DChart", skip_serializing_if = "Option::is_none")]
129    pub area_3d_chart: Option<Area3DChart>,
130
131    #[serde(rename = "c:scatterChart", skip_serializing_if = "Option::is_none")]
132    pub scatter_chart: Option<ScatterChart>,
133
134    #[serde(rename = "c:bubbleChart", skip_serializing_if = "Option::is_none")]
135    pub bubble_chart: Option<BubbleChart>,
136
137    #[serde(rename = "c:radarChart", skip_serializing_if = "Option::is_none")]
138    pub radar_chart: Option<RadarChart>,
139
140    #[serde(rename = "c:stockChart", skip_serializing_if = "Option::is_none")]
141    pub stock_chart: Option<StockChart>,
142
143    #[serde(rename = "c:surfaceChart", skip_serializing_if = "Option::is_none")]
144    pub surface_chart: Option<SurfaceChart>,
145
146    #[serde(rename = "c:surface3DChart", skip_serializing_if = "Option::is_none")]
147    pub surface_3d_chart: Option<Surface3DChart>,
148
149    #[serde(rename = "c:ofPieChart", skip_serializing_if = "Option::is_none")]
150    pub of_pie_chart: Option<OfPieChart>,
151
152    #[serde(rename = "c:catAx", skip_serializing_if = "Option::is_none")]
153    pub cat_ax: Option<CatAx>,
154
155    #[serde(rename = "c:valAx", skip_serializing_if = "Option::is_none")]
156    pub val_ax: Option<ValAx>,
157
158    #[serde(rename = "c:serAx", skip_serializing_if = "Option::is_none")]
159    pub ser_ax: Option<SerAx>,
160}
161
162/// Layout (empty, uses automatic layout).
163#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
164pub struct Layout {}
165
166/// Bar chart definition.
167#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
168pub struct BarChart {
169    #[serde(rename = "c:barDir")]
170    pub bar_dir: StringVal,
171
172    #[serde(rename = "c:grouping")]
173    pub grouping: StringVal,
174
175    #[serde(rename = "c:ser", default)]
176    pub series: Vec<Series>,
177
178    #[serde(rename = "c:axId", default)]
179    pub ax_ids: Vec<UintVal>,
180}
181
182/// 3D bar chart definition.
183#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
184pub struct Bar3DChart {
185    #[serde(rename = "c:barDir")]
186    pub bar_dir: StringVal,
187
188    #[serde(rename = "c:grouping")]
189    pub grouping: StringVal,
190
191    #[serde(rename = "c:ser", default)]
192    pub series: Vec<Series>,
193
194    #[serde(rename = "c:shape", skip_serializing_if = "Option::is_none")]
195    pub shape: Option<StringVal>,
196
197    #[serde(rename = "c:axId", default)]
198    pub ax_ids: Vec<UintVal>,
199}
200
201/// Line chart definition.
202#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
203pub struct LineChart {
204    #[serde(rename = "c:grouping")]
205    pub grouping: StringVal,
206
207    #[serde(rename = "c:ser", default)]
208    pub series: Vec<Series>,
209
210    #[serde(rename = "c:axId", default)]
211    pub ax_ids: Vec<UintVal>,
212}
213
214/// 3D line chart definition.
215#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
216pub struct Line3DChart {
217    #[serde(rename = "c:grouping")]
218    pub grouping: StringVal,
219
220    #[serde(rename = "c:ser", default)]
221    pub series: Vec<Series>,
222
223    #[serde(rename = "c:axId", default)]
224    pub ax_ids: Vec<UintVal>,
225}
226
227/// Pie chart definition.
228#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
229pub struct PieChart {
230    #[serde(rename = "c:ser", default)]
231    pub series: Vec<Series>,
232}
233
234/// 3D pie chart definition.
235#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
236pub struct Pie3DChart {
237    #[serde(rename = "c:ser", default)]
238    pub series: Vec<Series>,
239}
240
241/// Doughnut chart definition.
242#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
243pub struct DoughnutChart {
244    #[serde(rename = "c:ser", default)]
245    pub series: Vec<Series>,
246
247    #[serde(rename = "c:holeSize", skip_serializing_if = "Option::is_none")]
248    pub hole_size: Option<UintVal>,
249}
250
251/// Area chart definition.
252#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
253pub struct AreaChart {
254    #[serde(rename = "c:grouping")]
255    pub grouping: StringVal,
256
257    #[serde(rename = "c:ser", default)]
258    pub series: Vec<Series>,
259
260    #[serde(rename = "c:axId", default)]
261    pub ax_ids: Vec<UintVal>,
262}
263
264/// 3D area chart definition.
265#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
266pub struct Area3DChart {
267    #[serde(rename = "c:grouping")]
268    pub grouping: StringVal,
269
270    #[serde(rename = "c:ser", default)]
271    pub series: Vec<Series>,
272
273    #[serde(rename = "c:axId", default)]
274    pub ax_ids: Vec<UintVal>,
275}
276
277/// Scatter chart definition.
278#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
279pub struct ScatterChart {
280    #[serde(rename = "c:scatterStyle")]
281    pub scatter_style: StringVal,
282
283    #[serde(rename = "c:ser", default)]
284    pub series: Vec<ScatterSeries>,
285
286    #[serde(rename = "c:axId", default)]
287    pub ax_ids: Vec<UintVal>,
288}
289
290/// Scatter series (uses xVal/yVal instead of cat/val).
291#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
292pub struct ScatterSeries {
293    #[serde(rename = "c:idx")]
294    pub idx: UintVal,
295
296    #[serde(rename = "c:order")]
297    pub order: UintVal,
298
299    #[serde(rename = "c:tx", skip_serializing_if = "Option::is_none")]
300    pub tx: Option<SeriesText>,
301
302    #[serde(rename = "c:xVal", skip_serializing_if = "Option::is_none")]
303    pub x_val: Option<CategoryRef>,
304
305    #[serde(rename = "c:yVal", skip_serializing_if = "Option::is_none")]
306    pub y_val: Option<ValueRef>,
307}
308
309/// Bubble chart definition.
310#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
311pub struct BubbleChart {
312    #[serde(rename = "c:ser", default)]
313    pub series: Vec<BubbleSeries>,
314
315    #[serde(rename = "c:axId", default)]
316    pub ax_ids: Vec<UintVal>,
317}
318
319/// Bubble series (uses xVal/yVal/bubbleSize).
320#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
321pub struct BubbleSeries {
322    #[serde(rename = "c:idx")]
323    pub idx: UintVal,
324
325    #[serde(rename = "c:order")]
326    pub order: UintVal,
327
328    #[serde(rename = "c:tx", skip_serializing_if = "Option::is_none")]
329    pub tx: Option<SeriesText>,
330
331    #[serde(rename = "c:xVal", skip_serializing_if = "Option::is_none")]
332    pub x_val: Option<CategoryRef>,
333
334    #[serde(rename = "c:yVal", skip_serializing_if = "Option::is_none")]
335    pub y_val: Option<ValueRef>,
336
337    #[serde(rename = "c:bubbleSize", skip_serializing_if = "Option::is_none")]
338    pub bubble_size: Option<ValueRef>,
339
340    #[serde(rename = "c:bubble3D", skip_serializing_if = "Option::is_none")]
341    pub bubble_3d: Option<BoolVal>,
342}
343
344/// Radar chart definition.
345#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
346pub struct RadarChart {
347    #[serde(rename = "c:radarStyle")]
348    pub radar_style: StringVal,
349
350    #[serde(rename = "c:ser", default)]
351    pub series: Vec<Series>,
352
353    #[serde(rename = "c:axId", default)]
354    pub ax_ids: Vec<UintVal>,
355}
356
357/// Stock chart definition.
358#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
359pub struct StockChart {
360    #[serde(rename = "c:ser", default)]
361    pub series: Vec<Series>,
362
363    #[serde(rename = "c:axId", default)]
364    pub ax_ids: Vec<UintVal>,
365}
366
367/// Surface chart definition.
368#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
369pub struct SurfaceChart {
370    #[serde(rename = "c:wireframe", skip_serializing_if = "Option::is_none")]
371    pub wireframe: Option<BoolVal>,
372
373    #[serde(rename = "c:ser", default)]
374    pub series: Vec<Series>,
375
376    #[serde(rename = "c:axId", default)]
377    pub ax_ids: Vec<UintVal>,
378}
379
380/// 3D surface chart definition.
381#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
382pub struct Surface3DChart {
383    #[serde(rename = "c:wireframe", skip_serializing_if = "Option::is_none")]
384    pub wireframe: Option<BoolVal>,
385
386    #[serde(rename = "c:ser", default)]
387    pub series: Vec<Series>,
388
389    #[serde(rename = "c:axId", default)]
390    pub ax_ids: Vec<UintVal>,
391}
392
393/// Of-pie chart definition (pie-of-pie or bar-of-pie).
394#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
395pub struct OfPieChart {
396    #[serde(rename = "c:ofPieType")]
397    pub of_pie_type: StringVal,
398
399    #[serde(rename = "c:ser", default)]
400    pub series: Vec<Series>,
401
402    #[serde(rename = "c:serLines", skip_serializing_if = "Option::is_none")]
403    pub ser_lines: Option<SerLines>,
404}
405
406/// Series lines marker element.
407#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
408pub struct SerLines {}
409
410/// A data series within a chart.
411#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
412pub struct Series {
413    #[serde(rename = "c:idx")]
414    pub idx: UintVal,
415
416    #[serde(rename = "c:order")]
417    pub order: UintVal,
418
419    #[serde(rename = "c:tx", skip_serializing_if = "Option::is_none")]
420    pub tx: Option<SeriesText>,
421
422    #[serde(rename = "c:cat", skip_serializing_if = "Option::is_none")]
423    pub cat: Option<CategoryRef>,
424
425    #[serde(rename = "c:val", skip_serializing_if = "Option::is_none")]
426    pub val: Option<ValueRef>,
427}
428
429/// Series text (name) reference.
430#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
431pub struct SeriesText {
432    #[serde(rename = "c:strRef", skip_serializing_if = "Option::is_none")]
433    pub str_ref: Option<StrRef>,
434
435    #[serde(rename = "c:v", skip_serializing_if = "Option::is_none")]
436    pub v: Option<String>,
437}
438
439/// String reference (a formula to a cell range).
440#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
441pub struct StrRef {
442    #[serde(rename = "c:f")]
443    pub f: String,
444}
445
446/// Category axis data reference.
447#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
448pub struct CategoryRef {
449    #[serde(rename = "c:strRef", skip_serializing_if = "Option::is_none")]
450    pub str_ref: Option<StrRef>,
451
452    #[serde(rename = "c:numRef", skip_serializing_if = "Option::is_none")]
453    pub num_ref: Option<NumRef>,
454}
455
456/// Value axis data reference.
457#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
458pub struct ValueRef {
459    #[serde(rename = "c:numRef", skip_serializing_if = "Option::is_none")]
460    pub num_ref: Option<NumRef>,
461}
462
463/// Numeric reference (a formula to a numeric cell range).
464#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
465pub struct NumRef {
466    #[serde(rename = "c:f")]
467    pub f: String,
468}
469
470/// Chart legend.
471#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
472pub struct Legend {
473    #[serde(rename = "c:legendPos")]
474    pub legend_pos: StringVal,
475}
476
477/// Category axis.
478#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
479pub struct CatAx {
480    #[serde(rename = "c:axId")]
481    pub ax_id: UintVal,
482
483    #[serde(rename = "c:scaling")]
484    pub scaling: Scaling,
485
486    #[serde(rename = "c:delete")]
487    pub delete: BoolVal,
488
489    #[serde(rename = "c:axPos")]
490    pub ax_pos: StringVal,
491
492    #[serde(rename = "c:crossAx")]
493    pub cross_ax: UintVal,
494}
495
496/// Value axis.
497#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
498pub struct ValAx {
499    #[serde(rename = "c:axId")]
500    pub ax_id: UintVal,
501
502    #[serde(rename = "c:scaling")]
503    pub scaling: Scaling,
504
505    #[serde(rename = "c:delete")]
506    pub delete: BoolVal,
507
508    #[serde(rename = "c:axPos")]
509    pub ax_pos: StringVal,
510
511    #[serde(rename = "c:crossAx")]
512    pub cross_ax: UintVal,
513}
514
515/// Series axis (used by surface and some 3D charts).
516#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
517pub struct SerAx {
518    #[serde(rename = "c:axId")]
519    pub ax_id: UintVal,
520
521    #[serde(rename = "c:scaling")]
522    pub scaling: Scaling,
523
524    #[serde(rename = "c:delete")]
525    pub delete: BoolVal,
526
527    #[serde(rename = "c:axPos")]
528    pub ax_pos: StringVal,
529
530    #[serde(rename = "c:crossAx")]
531    pub cross_ax: UintVal,
532}
533
534/// 3D view settings.
535#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
536pub struct View3D {
537    #[serde(rename = "c:rotX", skip_serializing_if = "Option::is_none")]
538    pub rot_x: Option<IntVal>,
539
540    #[serde(rename = "c:rotY", skip_serializing_if = "Option::is_none")]
541    pub rot_y: Option<IntVal>,
542
543    #[serde(rename = "c:depthPercent", skip_serializing_if = "Option::is_none")]
544    pub depth_percent: Option<UintVal>,
545
546    #[serde(rename = "c:rAngAx", skip_serializing_if = "Option::is_none")]
547    pub r_ang_ax: Option<BoolVal>,
548
549    #[serde(rename = "c:perspective", skip_serializing_if = "Option::is_none")]
550    pub perspective: Option<UintVal>,
551}
552
553/// Axis scaling (orientation).
554#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
555pub struct Scaling {
556    #[serde(rename = "c:orientation")]
557    pub orientation: StringVal,
558}
559
560/// A wrapper for a string `val` attribute.
561#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
562pub struct StringVal {
563    #[serde(rename = "@val")]
564    pub val: String,
565}
566
567/// A wrapper for an unsigned integer `val` attribute.
568#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
569pub struct UintVal {
570    #[serde(rename = "@val")]
571    pub val: u32,
572}
573
574/// A wrapper for a signed integer `val` attribute.
575#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
576pub struct IntVal {
577    #[serde(rename = "@val")]
578    pub val: i32,
579}
580
581/// A wrapper for a boolean `val` attribute.
582#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
583pub struct BoolVal {
584    #[serde(rename = "@val")]
585    pub val: bool,
586}
587
588#[cfg(test)]
589mod tests {
590    use super::*;
591
592    #[test]
593    fn test_chart_space_default() {
594        let cs = ChartSpace::default();
595        assert_eq!(cs.xmlns_c, namespaces::DRAWING_ML_CHART);
596        assert_eq!(cs.xmlns_a, namespaces::DRAWING_ML);
597        assert_eq!(cs.xmlns_r, namespaces::RELATIONSHIPS);
598    }
599
600    #[test]
601    fn test_string_val_serialize() {
602        let sv = StringVal {
603            val: "col".to_string(),
604        };
605        let xml = quick_xml::se::to_string(&sv).unwrap();
606        assert!(xml.contains("val=\"col\""));
607    }
608
609    #[test]
610    fn test_uint_val_serialize() {
611        let uv = UintVal { val: 42 };
612        let xml = quick_xml::se::to_string(&uv).unwrap();
613        assert!(xml.contains("val=\"42\""));
614    }
615
616    #[test]
617    fn test_int_val_serialize() {
618        let iv = IntVal { val: -15 };
619        let xml = quick_xml::se::to_string(&iv).unwrap();
620        assert!(xml.contains("val=\"-15\""));
621    }
622
623    #[test]
624    fn test_bool_val_serialize() {
625        let bv = BoolVal { val: true };
626        let xml = quick_xml::se::to_string(&bv).unwrap();
627        assert!(xml.contains("val=\"true\""));
628    }
629
630    #[test]
631    fn test_series_serialize() {
632        let series = Series {
633            idx: UintVal { val: 0 },
634            order: UintVal { val: 0 },
635            tx: Some(SeriesText {
636                str_ref: None,
637                v: Some("Sales".to_string()),
638            }),
639            cat: Some(CategoryRef {
640                str_ref: Some(StrRef {
641                    f: "Sheet1!$A$2:$A$6".to_string(),
642                }),
643                num_ref: None,
644            }),
645            val: Some(ValueRef {
646                num_ref: Some(NumRef {
647                    f: "Sheet1!$B$2:$B$6".to_string(),
648                }),
649            }),
650        };
651        let xml = quick_xml::se::to_string(&series).unwrap();
652        assert!(xml.contains("Sheet1!$A$2:$A$6"));
653        assert!(xml.contains("Sheet1!$B$2:$B$6"));
654    }
655
656    #[test]
657    fn test_bar_chart_serialize() {
658        let bar = BarChart {
659            bar_dir: StringVal {
660                val: "col".to_string(),
661            },
662            grouping: StringVal {
663                val: "clustered".to_string(),
664            },
665            series: vec![],
666            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
667        };
668        let xml = quick_xml::se::to_string(&bar).unwrap();
669        assert!(xml.contains("col"));
670        assert!(xml.contains("clustered"));
671    }
672
673    #[test]
674    fn test_bar_3d_chart_serialize() {
675        let bar = Bar3DChart {
676            bar_dir: StringVal {
677                val: "col".to_string(),
678            },
679            grouping: StringVal {
680                val: "clustered".to_string(),
681            },
682            series: vec![],
683            shape: None,
684            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
685        };
686        let xml = quick_xml::se::to_string(&bar).unwrap();
687        assert!(xml.contains("col"));
688        assert!(xml.contains("clustered"));
689    }
690
691    #[test]
692    fn test_area_chart_serialize() {
693        let area = AreaChart {
694            grouping: StringVal {
695                val: "standard".to_string(),
696            },
697            series: vec![],
698            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
699        };
700        let xml = quick_xml::se::to_string(&area).unwrap();
701        assert!(xml.contains("standard"));
702    }
703
704    #[test]
705    fn test_scatter_chart_serialize() {
706        let scatter = ScatterChart {
707            scatter_style: StringVal {
708                val: "lineMarker".to_string(),
709            },
710            series: vec![ScatterSeries {
711                idx: UintVal { val: 0 },
712                order: UintVal { val: 0 },
713                tx: None,
714                x_val: Some(CategoryRef {
715                    str_ref: None,
716                    num_ref: Some(NumRef {
717                        f: "Sheet1!$A$2:$A$6".to_string(),
718                    }),
719                }),
720                y_val: Some(ValueRef {
721                    num_ref: Some(NumRef {
722                        f: "Sheet1!$B$2:$B$6".to_string(),
723                    }),
724                }),
725            }],
726            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
727        };
728        let xml = quick_xml::se::to_string(&scatter).unwrap();
729        assert!(xml.contains("lineMarker"));
730        assert!(xml.contains("Sheet1!$A$2:$A$6"));
731        assert!(xml.contains("Sheet1!$B$2:$B$6"));
732    }
733
734    #[test]
735    fn test_bubble_chart_serialize() {
736        let bubble = BubbleChart {
737            series: vec![BubbleSeries {
738                idx: UintVal { val: 0 },
739                order: UintVal { val: 0 },
740                tx: None,
741                x_val: None,
742                y_val: None,
743                bubble_size: Some(ValueRef {
744                    num_ref: Some(NumRef {
745                        f: "Sheet1!$C$2:$C$6".to_string(),
746                    }),
747                }),
748                bubble_3d: None,
749            }],
750            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
751        };
752        let xml = quick_xml::se::to_string(&bubble).unwrap();
753        assert!(xml.contains("Sheet1!$C$2:$C$6"));
754    }
755
756    #[test]
757    fn test_radar_chart_serialize() {
758        let radar = RadarChart {
759            radar_style: StringVal {
760                val: "marker".to_string(),
761            },
762            series: vec![],
763            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
764        };
765        let xml = quick_xml::se::to_string(&radar).unwrap();
766        assert!(xml.contains("marker"));
767    }
768
769    #[test]
770    fn test_surface_chart_serialize() {
771        let surface = SurfaceChart {
772            wireframe: Some(BoolVal { val: true }),
773            series: vec![],
774            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }, UintVal { val: 3 }],
775        };
776        let xml = quick_xml::se::to_string(&surface).unwrap();
777        assert!(xml.contains("val=\"true\""));
778    }
779
780    #[test]
781    fn test_view_3d_serialize() {
782        let view = View3D {
783            rot_x: Some(IntVal { val: 15 }),
784            rot_y: Some(IntVal { val: 20 }),
785            depth_percent: Some(UintVal { val: 150 }),
786            r_ang_ax: Some(BoolVal { val: true }),
787            perspective: Some(UintVal { val: 30 }),
788        };
789        let xml = quick_xml::se::to_string(&view).unwrap();
790        assert!(xml.contains("val=\"15\""));
791        assert!(xml.contains("val=\"20\""));
792        assert!(xml.contains("val=\"150\""));
793    }
794
795    #[test]
796    fn test_ser_ax_serialize() {
797        let ser_ax = SerAx {
798            ax_id: UintVal { val: 3 },
799            scaling: Scaling {
800                orientation: StringVal {
801                    val: "minMax".to_string(),
802                },
803            },
804            delete: BoolVal { val: false },
805            ax_pos: StringVal {
806                val: "b".to_string(),
807            },
808            cross_ax: UintVal { val: 1 },
809        };
810        let xml = quick_xml::se::to_string(&ser_ax).unwrap();
811        assert!(xml.contains("val=\"3\""));
812        assert!(xml.contains("minMax"));
813    }
814
815    #[test]
816    fn test_legend_serialize() {
817        let legend = Legend {
818            legend_pos: StringVal {
819                val: "b".to_string(),
820            },
821        };
822        let xml = quick_xml::se::to_string(&legend).unwrap();
823        assert!(xml.contains("val=\"b\""));
824    }
825
826    #[test]
827    fn test_chart_title_serialize() {
828        let title = ChartTitle {
829            tx: TitleTx {
830                rich: RichText {
831                    body_pr: BodyPr {},
832                    paragraphs: vec![Paragraph {
833                        runs: vec![Run {
834                            t: "My Chart".to_string(),
835                        }],
836                    }],
837                },
838            },
839        };
840        let xml = quick_xml::se::to_string(&title).unwrap();
841        assert!(xml.contains("My Chart"));
842    }
843
844    #[test]
845    fn test_num_ref_serialize() {
846        let num_ref = NumRef {
847            f: "Sheet1!$B$1:$B$5".to_string(),
848        };
849        let xml = quick_xml::se::to_string(&num_ref).unwrap();
850        assert!(xml.contains("Sheet1!$B$1:$B$5"));
851    }
852
853    #[test]
854    fn test_str_ref_serialize() {
855        let str_ref = StrRef {
856            f: "Sheet1!$A$1".to_string(),
857        };
858        let xml = quick_xml::se::to_string(&str_ref).unwrap();
859        assert!(xml.contains("Sheet1!$A$1"));
860    }
861
862    #[test]
863    fn test_plot_area_default_all_none() {
864        let pa = PlotArea::default();
865        assert!(pa.layout.is_none());
866        assert!(pa.bar_chart.is_none());
867        assert!(pa.bar_3d_chart.is_none());
868        assert!(pa.line_chart.is_none());
869        assert!(pa.line_3d_chart.is_none());
870        assert!(pa.pie_chart.is_none());
871        assert!(pa.pie_3d_chart.is_none());
872        assert!(pa.doughnut_chart.is_none());
873        assert!(pa.area_chart.is_none());
874        assert!(pa.area_3d_chart.is_none());
875        assert!(pa.scatter_chart.is_none());
876        assert!(pa.bubble_chart.is_none());
877        assert!(pa.radar_chart.is_none());
878        assert!(pa.stock_chart.is_none());
879        assert!(pa.surface_chart.is_none());
880        assert!(pa.surface_3d_chart.is_none());
881        assert!(pa.of_pie_chart.is_none());
882        assert!(pa.cat_ax.is_none());
883        assert!(pa.val_ax.is_none());
884        assert!(pa.ser_ax.is_none());
885    }
886
887    #[test]
888    fn test_chart_with_view_3d() {
889        let chart = Chart {
890            title: None,
891            view_3d: Some(View3D {
892                rot_x: Some(IntVal { val: 15 }),
893                rot_y: Some(IntVal { val: 20 }),
894                depth_percent: None,
895                r_ang_ax: Some(BoolVal { val: true }),
896                perspective: None,
897            }),
898            plot_area: PlotArea::default(),
899            legend: None,
900            plot_vis_only: None,
901        };
902        let xml = quick_xml::se::to_string(&chart).unwrap();
903        assert!(xml.contains("val=\"15\""));
904        assert!(xml.contains("val=\"20\""));
905    }
906
907    #[test]
908    fn test_doughnut_chart_serialize() {
909        let doughnut = DoughnutChart {
910            series: vec![],
911            hole_size: Some(UintVal { val: 50 }),
912        };
913        let xml = quick_xml::se::to_string(&doughnut).unwrap();
914        assert!(xml.contains("val=\"50\""));
915    }
916
917    #[test]
918    fn test_bar_3d_chart_with_shape() {
919        let bar = Bar3DChart {
920            bar_dir: StringVal {
921                val: "col".to_string(),
922            },
923            grouping: StringVal {
924                val: "clustered".to_string(),
925            },
926            series: vec![],
927            shape: Some(StringVal {
928                val: "cone".to_string(),
929            }),
930            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
931        };
932        let xml = quick_xml::se::to_string(&bar).unwrap();
933        assert!(xml.contains("cone"));
934    }
935
936    #[test]
937    fn test_of_pie_chart_serialize() {
938        let of_pie = OfPieChart {
939            of_pie_type: StringVal {
940                val: "pie".to_string(),
941            },
942            series: vec![Series {
943                idx: UintVal { val: 0 },
944                order: UintVal { val: 0 },
945                tx: None,
946                cat: None,
947                val: None,
948            }],
949            ser_lines: Some(SerLines {}),
950        };
951        let xml = quick_xml::se::to_string(&of_pie).unwrap();
952        assert!(xml.contains("val=\"pie\""));
953        assert!(xml.contains("serLines"));
954    }
955
956    #[test]
957    fn test_bubble_series_with_bubble_3d() {
958        let bs = BubbleSeries {
959            idx: UintVal { val: 0 },
960            order: UintVal { val: 0 },
961            tx: None,
962            x_val: None,
963            y_val: None,
964            bubble_size: None,
965            bubble_3d: Some(BoolVal { val: true }),
966        };
967        let xml = quick_xml::se::to_string(&bs).unwrap();
968        assert!(xml.contains("bubble3D"));
969        assert!(xml.contains("val=\"true\""));
970    }
971}