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:catAx", skip_serializing_if = "Option::is_none")]
150    pub cat_ax: Option<CatAx>,
151
152    #[serde(rename = "c:valAx", skip_serializing_if = "Option::is_none")]
153    pub val_ax: Option<ValAx>,
154
155    #[serde(rename = "c:serAx", skip_serializing_if = "Option::is_none")]
156    pub ser_ax: Option<SerAx>,
157}
158
159/// Layout (empty, uses automatic layout).
160#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
161pub struct Layout {}
162
163/// Bar chart definition.
164#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
165pub struct BarChart {
166    #[serde(rename = "c:barDir")]
167    pub bar_dir: StringVal,
168
169    #[serde(rename = "c:grouping")]
170    pub grouping: StringVal,
171
172    #[serde(rename = "c:ser", default)]
173    pub series: Vec<Series>,
174
175    #[serde(rename = "c:axId", default)]
176    pub ax_ids: Vec<UintVal>,
177}
178
179/// 3D bar chart definition.
180#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
181pub struct Bar3DChart {
182    #[serde(rename = "c:barDir")]
183    pub bar_dir: StringVal,
184
185    #[serde(rename = "c:grouping")]
186    pub grouping: StringVal,
187
188    #[serde(rename = "c:ser", default)]
189    pub series: Vec<Series>,
190
191    #[serde(rename = "c:axId", default)]
192    pub ax_ids: Vec<UintVal>,
193}
194
195/// Line chart definition.
196#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
197pub struct LineChart {
198    #[serde(rename = "c:grouping")]
199    pub grouping: StringVal,
200
201    #[serde(rename = "c:ser", default)]
202    pub series: Vec<Series>,
203
204    #[serde(rename = "c:axId", default)]
205    pub ax_ids: Vec<UintVal>,
206}
207
208/// 3D line chart definition.
209#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
210pub struct Line3DChart {
211    #[serde(rename = "c:grouping")]
212    pub grouping: StringVal,
213
214    #[serde(rename = "c:ser", default)]
215    pub series: Vec<Series>,
216
217    #[serde(rename = "c:axId", default)]
218    pub ax_ids: Vec<UintVal>,
219}
220
221/// Pie chart definition.
222#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
223pub struct PieChart {
224    #[serde(rename = "c:ser", default)]
225    pub series: Vec<Series>,
226}
227
228/// 3D pie chart definition.
229#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
230pub struct Pie3DChart {
231    #[serde(rename = "c:ser", default)]
232    pub series: Vec<Series>,
233}
234
235/// Doughnut chart definition.
236#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
237pub struct DoughnutChart {
238    #[serde(rename = "c:ser", default)]
239    pub series: Vec<Series>,
240
241    #[serde(rename = "c:holeSize", skip_serializing_if = "Option::is_none")]
242    pub hole_size: Option<UintVal>,
243}
244
245/// Area chart definition.
246#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
247pub struct AreaChart {
248    #[serde(rename = "c:grouping")]
249    pub grouping: StringVal,
250
251    #[serde(rename = "c:ser", default)]
252    pub series: Vec<Series>,
253
254    #[serde(rename = "c:axId", default)]
255    pub ax_ids: Vec<UintVal>,
256}
257
258/// 3D area chart definition.
259#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
260pub struct Area3DChart {
261    #[serde(rename = "c:grouping")]
262    pub grouping: StringVal,
263
264    #[serde(rename = "c:ser", default)]
265    pub series: Vec<Series>,
266
267    #[serde(rename = "c:axId", default)]
268    pub ax_ids: Vec<UintVal>,
269}
270
271/// Scatter chart definition.
272#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
273pub struct ScatterChart {
274    #[serde(rename = "c:scatterStyle")]
275    pub scatter_style: StringVal,
276
277    #[serde(rename = "c:ser", default)]
278    pub series: Vec<ScatterSeries>,
279
280    #[serde(rename = "c:axId", default)]
281    pub ax_ids: Vec<UintVal>,
282}
283
284/// Scatter series (uses xVal/yVal instead of cat/val).
285#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
286pub struct ScatterSeries {
287    #[serde(rename = "c:idx")]
288    pub idx: UintVal,
289
290    #[serde(rename = "c:order")]
291    pub order: UintVal,
292
293    #[serde(rename = "c:tx", skip_serializing_if = "Option::is_none")]
294    pub tx: Option<SeriesText>,
295
296    #[serde(rename = "c:xVal", skip_serializing_if = "Option::is_none")]
297    pub x_val: Option<CategoryRef>,
298
299    #[serde(rename = "c:yVal", skip_serializing_if = "Option::is_none")]
300    pub y_val: Option<ValueRef>,
301}
302
303/// Bubble chart definition.
304#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
305pub struct BubbleChart {
306    #[serde(rename = "c:ser", default)]
307    pub series: Vec<BubbleSeries>,
308
309    #[serde(rename = "c:axId", default)]
310    pub ax_ids: Vec<UintVal>,
311}
312
313/// Bubble series (uses xVal/yVal/bubbleSize).
314#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
315pub struct BubbleSeries {
316    #[serde(rename = "c:idx")]
317    pub idx: UintVal,
318
319    #[serde(rename = "c:order")]
320    pub order: UintVal,
321
322    #[serde(rename = "c:tx", skip_serializing_if = "Option::is_none")]
323    pub tx: Option<SeriesText>,
324
325    #[serde(rename = "c:xVal", skip_serializing_if = "Option::is_none")]
326    pub x_val: Option<CategoryRef>,
327
328    #[serde(rename = "c:yVal", skip_serializing_if = "Option::is_none")]
329    pub y_val: Option<ValueRef>,
330
331    #[serde(rename = "c:bubbleSize", skip_serializing_if = "Option::is_none")]
332    pub bubble_size: Option<ValueRef>,
333}
334
335/// Radar chart definition.
336#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
337pub struct RadarChart {
338    #[serde(rename = "c:radarStyle")]
339    pub radar_style: StringVal,
340
341    #[serde(rename = "c:ser", default)]
342    pub series: Vec<Series>,
343
344    #[serde(rename = "c:axId", default)]
345    pub ax_ids: Vec<UintVal>,
346}
347
348/// Stock chart definition.
349#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
350pub struct StockChart {
351    #[serde(rename = "c:ser", default)]
352    pub series: Vec<Series>,
353
354    #[serde(rename = "c:axId", default)]
355    pub ax_ids: Vec<UintVal>,
356}
357
358/// Surface chart definition.
359#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
360pub struct SurfaceChart {
361    #[serde(rename = "c:wireframe", skip_serializing_if = "Option::is_none")]
362    pub wireframe: Option<BoolVal>,
363
364    #[serde(rename = "c:ser", default)]
365    pub series: Vec<Series>,
366
367    #[serde(rename = "c:axId", default)]
368    pub ax_ids: Vec<UintVal>,
369}
370
371/// 3D surface chart definition.
372#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
373pub struct Surface3DChart {
374    #[serde(rename = "c:wireframe", skip_serializing_if = "Option::is_none")]
375    pub wireframe: Option<BoolVal>,
376
377    #[serde(rename = "c:ser", default)]
378    pub series: Vec<Series>,
379
380    #[serde(rename = "c:axId", default)]
381    pub ax_ids: Vec<UintVal>,
382}
383
384/// A data series within a chart.
385#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
386pub struct Series {
387    #[serde(rename = "c:idx")]
388    pub idx: UintVal,
389
390    #[serde(rename = "c:order")]
391    pub order: UintVal,
392
393    #[serde(rename = "c:tx", skip_serializing_if = "Option::is_none")]
394    pub tx: Option<SeriesText>,
395
396    #[serde(rename = "c:cat", skip_serializing_if = "Option::is_none")]
397    pub cat: Option<CategoryRef>,
398
399    #[serde(rename = "c:val", skip_serializing_if = "Option::is_none")]
400    pub val: Option<ValueRef>,
401}
402
403/// Series text (name) reference.
404#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
405pub struct SeriesText {
406    #[serde(rename = "c:strRef", skip_serializing_if = "Option::is_none")]
407    pub str_ref: Option<StrRef>,
408
409    #[serde(rename = "c:v", skip_serializing_if = "Option::is_none")]
410    pub v: Option<String>,
411}
412
413/// String reference (a formula to a cell range).
414#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
415pub struct StrRef {
416    #[serde(rename = "c:f")]
417    pub f: String,
418}
419
420/// Category axis data reference.
421#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
422pub struct CategoryRef {
423    #[serde(rename = "c:strRef", skip_serializing_if = "Option::is_none")]
424    pub str_ref: Option<StrRef>,
425
426    #[serde(rename = "c:numRef", skip_serializing_if = "Option::is_none")]
427    pub num_ref: Option<NumRef>,
428}
429
430/// Value axis data reference.
431#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
432pub struct ValueRef {
433    #[serde(rename = "c:numRef", skip_serializing_if = "Option::is_none")]
434    pub num_ref: Option<NumRef>,
435}
436
437/// Numeric reference (a formula to a numeric cell range).
438#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
439pub struct NumRef {
440    #[serde(rename = "c:f")]
441    pub f: String,
442}
443
444/// Chart legend.
445#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
446pub struct Legend {
447    #[serde(rename = "c:legendPos")]
448    pub legend_pos: StringVal,
449}
450
451/// Category axis.
452#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
453pub struct CatAx {
454    #[serde(rename = "c:axId")]
455    pub ax_id: UintVal,
456
457    #[serde(rename = "c:scaling")]
458    pub scaling: Scaling,
459
460    #[serde(rename = "c:delete")]
461    pub delete: BoolVal,
462
463    #[serde(rename = "c:axPos")]
464    pub ax_pos: StringVal,
465
466    #[serde(rename = "c:crossAx")]
467    pub cross_ax: UintVal,
468}
469
470/// Value axis.
471#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
472pub struct ValAx {
473    #[serde(rename = "c:axId")]
474    pub ax_id: UintVal,
475
476    #[serde(rename = "c:scaling")]
477    pub scaling: Scaling,
478
479    #[serde(rename = "c:delete")]
480    pub delete: BoolVal,
481
482    #[serde(rename = "c:axPos")]
483    pub ax_pos: StringVal,
484
485    #[serde(rename = "c:crossAx")]
486    pub cross_ax: UintVal,
487}
488
489/// Series axis (used by surface and some 3D charts).
490#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
491pub struct SerAx {
492    #[serde(rename = "c:axId")]
493    pub ax_id: UintVal,
494
495    #[serde(rename = "c:scaling")]
496    pub scaling: Scaling,
497
498    #[serde(rename = "c:delete")]
499    pub delete: BoolVal,
500
501    #[serde(rename = "c:axPos")]
502    pub ax_pos: StringVal,
503
504    #[serde(rename = "c:crossAx")]
505    pub cross_ax: UintVal,
506}
507
508/// 3D view settings.
509#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
510pub struct View3D {
511    #[serde(rename = "c:rotX", skip_serializing_if = "Option::is_none")]
512    pub rot_x: Option<IntVal>,
513
514    #[serde(rename = "c:rotY", skip_serializing_if = "Option::is_none")]
515    pub rot_y: Option<IntVal>,
516
517    #[serde(rename = "c:depthPercent", skip_serializing_if = "Option::is_none")]
518    pub depth_percent: Option<UintVal>,
519
520    #[serde(rename = "c:rAngAx", skip_serializing_if = "Option::is_none")]
521    pub r_ang_ax: Option<BoolVal>,
522
523    #[serde(rename = "c:perspective", skip_serializing_if = "Option::is_none")]
524    pub perspective: Option<UintVal>,
525}
526
527/// Axis scaling (orientation).
528#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
529pub struct Scaling {
530    #[serde(rename = "c:orientation")]
531    pub orientation: StringVal,
532}
533
534/// A wrapper for a string `val` attribute.
535#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
536pub struct StringVal {
537    #[serde(rename = "@val")]
538    pub val: String,
539}
540
541/// A wrapper for an unsigned integer `val` attribute.
542#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
543pub struct UintVal {
544    #[serde(rename = "@val")]
545    pub val: u32,
546}
547
548/// A wrapper for a signed integer `val` attribute.
549#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
550pub struct IntVal {
551    #[serde(rename = "@val")]
552    pub val: i32,
553}
554
555/// A wrapper for a boolean `val` attribute.
556#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
557pub struct BoolVal {
558    #[serde(rename = "@val")]
559    pub val: bool,
560}
561
562#[cfg(test)]
563mod tests {
564    use super::*;
565
566    #[test]
567    fn test_chart_space_default() {
568        let cs = ChartSpace::default();
569        assert_eq!(cs.xmlns_c, namespaces::DRAWING_ML_CHART);
570        assert_eq!(cs.xmlns_a, namespaces::DRAWING_ML);
571        assert_eq!(cs.xmlns_r, namespaces::RELATIONSHIPS);
572    }
573
574    #[test]
575    fn test_string_val_serialize() {
576        let sv = StringVal {
577            val: "col".to_string(),
578        };
579        let xml = quick_xml::se::to_string(&sv).unwrap();
580        assert!(xml.contains("val=\"col\""));
581    }
582
583    #[test]
584    fn test_uint_val_serialize() {
585        let uv = UintVal { val: 42 };
586        let xml = quick_xml::se::to_string(&uv).unwrap();
587        assert!(xml.contains("val=\"42\""));
588    }
589
590    #[test]
591    fn test_int_val_serialize() {
592        let iv = IntVal { val: -15 };
593        let xml = quick_xml::se::to_string(&iv).unwrap();
594        assert!(xml.contains("val=\"-15\""));
595    }
596
597    #[test]
598    fn test_bool_val_serialize() {
599        let bv = BoolVal { val: true };
600        let xml = quick_xml::se::to_string(&bv).unwrap();
601        assert!(xml.contains("val=\"true\""));
602    }
603
604    #[test]
605    fn test_series_serialize() {
606        let series = Series {
607            idx: UintVal { val: 0 },
608            order: UintVal { val: 0 },
609            tx: Some(SeriesText {
610                str_ref: None,
611                v: Some("Sales".to_string()),
612            }),
613            cat: Some(CategoryRef {
614                str_ref: Some(StrRef {
615                    f: "Sheet1!$A$2:$A$6".to_string(),
616                }),
617                num_ref: None,
618            }),
619            val: Some(ValueRef {
620                num_ref: Some(NumRef {
621                    f: "Sheet1!$B$2:$B$6".to_string(),
622                }),
623            }),
624        };
625        let xml = quick_xml::se::to_string(&series).unwrap();
626        assert!(xml.contains("Sheet1!$A$2:$A$6"));
627        assert!(xml.contains("Sheet1!$B$2:$B$6"));
628    }
629
630    #[test]
631    fn test_bar_chart_serialize() {
632        let bar = BarChart {
633            bar_dir: StringVal {
634                val: "col".to_string(),
635            },
636            grouping: StringVal {
637                val: "clustered".to_string(),
638            },
639            series: vec![],
640            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
641        };
642        let xml = quick_xml::se::to_string(&bar).unwrap();
643        assert!(xml.contains("col"));
644        assert!(xml.contains("clustered"));
645    }
646
647    #[test]
648    fn test_bar_3d_chart_serialize() {
649        let bar = Bar3DChart {
650            bar_dir: StringVal {
651                val: "col".to_string(),
652            },
653            grouping: StringVal {
654                val: "clustered".to_string(),
655            },
656            series: vec![],
657            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
658        };
659        let xml = quick_xml::se::to_string(&bar).unwrap();
660        assert!(xml.contains("col"));
661        assert!(xml.contains("clustered"));
662    }
663
664    #[test]
665    fn test_area_chart_serialize() {
666        let area = AreaChart {
667            grouping: StringVal {
668                val: "standard".to_string(),
669            },
670            series: vec![],
671            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
672        };
673        let xml = quick_xml::se::to_string(&area).unwrap();
674        assert!(xml.contains("standard"));
675    }
676
677    #[test]
678    fn test_scatter_chart_serialize() {
679        let scatter = ScatterChart {
680            scatter_style: StringVal {
681                val: "lineMarker".to_string(),
682            },
683            series: vec![ScatterSeries {
684                idx: UintVal { val: 0 },
685                order: UintVal { val: 0 },
686                tx: None,
687                x_val: Some(CategoryRef {
688                    str_ref: None,
689                    num_ref: Some(NumRef {
690                        f: "Sheet1!$A$2:$A$6".to_string(),
691                    }),
692                }),
693                y_val: Some(ValueRef {
694                    num_ref: Some(NumRef {
695                        f: "Sheet1!$B$2:$B$6".to_string(),
696                    }),
697                }),
698            }],
699            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
700        };
701        let xml = quick_xml::se::to_string(&scatter).unwrap();
702        assert!(xml.contains("lineMarker"));
703        assert!(xml.contains("Sheet1!$A$2:$A$6"));
704        assert!(xml.contains("Sheet1!$B$2:$B$6"));
705    }
706
707    #[test]
708    fn test_bubble_chart_serialize() {
709        let bubble = BubbleChart {
710            series: vec![BubbleSeries {
711                idx: UintVal { val: 0 },
712                order: UintVal { val: 0 },
713                tx: None,
714                x_val: None,
715                y_val: None,
716                bubble_size: Some(ValueRef {
717                    num_ref: Some(NumRef {
718                        f: "Sheet1!$C$2:$C$6".to_string(),
719                    }),
720                }),
721            }],
722            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
723        };
724        let xml = quick_xml::se::to_string(&bubble).unwrap();
725        assert!(xml.contains("Sheet1!$C$2:$C$6"));
726    }
727
728    #[test]
729    fn test_radar_chart_serialize() {
730        let radar = RadarChart {
731            radar_style: StringVal {
732                val: "marker".to_string(),
733            },
734            series: vec![],
735            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }],
736        };
737        let xml = quick_xml::se::to_string(&radar).unwrap();
738        assert!(xml.contains("marker"));
739    }
740
741    #[test]
742    fn test_surface_chart_serialize() {
743        let surface = SurfaceChart {
744            wireframe: Some(BoolVal { val: true }),
745            series: vec![],
746            ax_ids: vec![UintVal { val: 1 }, UintVal { val: 2 }, UintVal { val: 3 }],
747        };
748        let xml = quick_xml::se::to_string(&surface).unwrap();
749        assert!(xml.contains("val=\"true\""));
750    }
751
752    #[test]
753    fn test_view_3d_serialize() {
754        let view = View3D {
755            rot_x: Some(IntVal { val: 15 }),
756            rot_y: Some(IntVal { val: 20 }),
757            depth_percent: Some(UintVal { val: 150 }),
758            r_ang_ax: Some(BoolVal { val: true }),
759            perspective: Some(UintVal { val: 30 }),
760        };
761        let xml = quick_xml::se::to_string(&view).unwrap();
762        assert!(xml.contains("val=\"15\""));
763        assert!(xml.contains("val=\"20\""));
764        assert!(xml.contains("val=\"150\""));
765    }
766
767    #[test]
768    fn test_ser_ax_serialize() {
769        let ser_ax = SerAx {
770            ax_id: UintVal { val: 3 },
771            scaling: Scaling {
772                orientation: StringVal {
773                    val: "minMax".to_string(),
774                },
775            },
776            delete: BoolVal { val: false },
777            ax_pos: StringVal {
778                val: "b".to_string(),
779            },
780            cross_ax: UintVal { val: 1 },
781        };
782        let xml = quick_xml::se::to_string(&ser_ax).unwrap();
783        assert!(xml.contains("val=\"3\""));
784        assert!(xml.contains("minMax"));
785    }
786
787    #[test]
788    fn test_legend_serialize() {
789        let legend = Legend {
790            legend_pos: StringVal {
791                val: "b".to_string(),
792            },
793        };
794        let xml = quick_xml::se::to_string(&legend).unwrap();
795        assert!(xml.contains("val=\"b\""));
796    }
797
798    #[test]
799    fn test_chart_title_serialize() {
800        let title = ChartTitle {
801            tx: TitleTx {
802                rich: RichText {
803                    body_pr: BodyPr {},
804                    paragraphs: vec![Paragraph {
805                        runs: vec![Run {
806                            t: "My Chart".to_string(),
807                        }],
808                    }],
809                },
810            },
811        };
812        let xml = quick_xml::se::to_string(&title).unwrap();
813        assert!(xml.contains("My Chart"));
814    }
815
816    #[test]
817    fn test_num_ref_serialize() {
818        let num_ref = NumRef {
819            f: "Sheet1!$B$1:$B$5".to_string(),
820        };
821        let xml = quick_xml::se::to_string(&num_ref).unwrap();
822        assert!(xml.contains("Sheet1!$B$1:$B$5"));
823    }
824
825    #[test]
826    fn test_str_ref_serialize() {
827        let str_ref = StrRef {
828            f: "Sheet1!$A$1".to_string(),
829        };
830        let xml = quick_xml::se::to_string(&str_ref).unwrap();
831        assert!(xml.contains("Sheet1!$A$1"));
832    }
833
834    #[test]
835    fn test_plot_area_default_all_none() {
836        let pa = PlotArea::default();
837        assert!(pa.layout.is_none());
838        assert!(pa.bar_chart.is_none());
839        assert!(pa.bar_3d_chart.is_none());
840        assert!(pa.line_chart.is_none());
841        assert!(pa.line_3d_chart.is_none());
842        assert!(pa.pie_chart.is_none());
843        assert!(pa.pie_3d_chart.is_none());
844        assert!(pa.doughnut_chart.is_none());
845        assert!(pa.area_chart.is_none());
846        assert!(pa.area_3d_chart.is_none());
847        assert!(pa.scatter_chart.is_none());
848        assert!(pa.bubble_chart.is_none());
849        assert!(pa.radar_chart.is_none());
850        assert!(pa.stock_chart.is_none());
851        assert!(pa.surface_chart.is_none());
852        assert!(pa.surface_3d_chart.is_none());
853        assert!(pa.cat_ax.is_none());
854        assert!(pa.val_ax.is_none());
855        assert!(pa.ser_ax.is_none());
856    }
857
858    #[test]
859    fn test_chart_with_view_3d() {
860        let chart = Chart {
861            title: None,
862            view_3d: Some(View3D {
863                rot_x: Some(IntVal { val: 15 }),
864                rot_y: Some(IntVal { val: 20 }),
865                depth_percent: None,
866                r_ang_ax: Some(BoolVal { val: true }),
867                perspective: None,
868            }),
869            plot_area: PlotArea::default(),
870            legend: None,
871            plot_vis_only: None,
872        };
873        let xml = quick_xml::se::to_string(&chart).unwrap();
874        assert!(xml.contains("val=\"15\""));
875        assert!(xml.contains("val=\"20\""));
876    }
877
878    #[test]
879    fn test_doughnut_chart_serialize() {
880        let doughnut = DoughnutChart {
881            series: vec![],
882            hole_size: Some(UintVal { val: 50 }),
883        };
884        let xml = quick_xml::se::to_string(&doughnut).unwrap();
885        assert!(xml.contains("val=\"50\""));
886    }
887}