Skip to main content

ppt_rs/parts/
chart.rs

1//! Chart part
2//!
3//! Represents a chart embedded in the presentation.
4
5use super::base::{ContentType, Part, PartType};
6use crate::exc::PptxError;
7use crate::generator::charts::{generate_chart_part_xml, Chart};
8
9/// Chart part (ppt/charts/chartN.xml)
10#[derive(Debug, Clone)]
11pub struct ChartPart {
12    path: String,
13    chart_number: usize,
14    chart: Option<Chart>,
15    xml_content: Option<String>,
16}
17
18impl ChartPart {
19    /// Create a new chart part
20    pub fn new(chart_number: usize) -> Self {
21        ChartPart {
22            path: format!("ppt/charts/chart{}.xml", chart_number),
23            chart_number,
24            chart: None,
25            xml_content: None,
26        }
27    }
28
29    /// Create from Chart
30    pub fn from_chart(chart_number: usize, chart: Chart) -> Self {
31        ChartPart {
32            path: format!("ppt/charts/chart{}.xml", chart_number),
33            chart_number,
34            chart: Some(chart),
35            xml_content: None,
36        }
37    }
38
39    /// Get chart number
40    pub fn chart_number(&self) -> usize {
41        self.chart_number
42    }
43
44    /// Get chart if available
45    pub fn chart(&self) -> Option<&Chart> {
46        self.chart.as_ref()
47    }
48
49    /// Set chart
50    pub fn set_chart(&mut self, chart: Chart) {
51        self.chart = Some(chart);
52        self.xml_content = None;
53    }
54
55    /// Get relative path for relationships
56    pub fn rel_target(&self) -> String {
57        format!("../charts/chart{}.xml", self.chart_number)
58    }
59}
60
61impl Part for ChartPart {
62    fn path(&self) -> &str {
63        &self.path
64    }
65
66    fn part_type(&self) -> PartType {
67        PartType::Chart
68    }
69
70    fn content_type(&self) -> ContentType {
71        ContentType::Chart
72    }
73
74    fn to_xml(&self) -> Result<String, PptxError> {
75        if let Some(ref xml) = self.xml_content {
76            return Ok(xml.clone());
77        }
78
79        if let Some(ref chart) = self.chart {
80            return Ok(generate_chart_part_xml(chart));
81        }
82
83        // Return minimal chart XML
84        Err(PptxError::InvalidOperation(
85            "No chart data available".to_string(),
86        ))
87    }
88
89    fn from_xml(xml: &str) -> Result<Self, PptxError> {
90        // Basic parsing - store XML for now
91        Ok(ChartPart {
92            path: "ppt/charts/chart1.xml".to_string(),
93            chart_number: 1,
94            chart: None,
95            xml_content: Some(xml.to_string()),
96        })
97    }
98}
99
100#[cfg(test)]
101mod tests {
102    use super::*;
103    use crate::generator::charts::{ChartBuilder, ChartSeries, ChartType};
104
105    #[test]
106    fn test_chart_part_new() {
107        let part = ChartPart::new(1);
108        assert_eq!(part.chart_number(), 1);
109        assert_eq!(part.path(), "ppt/charts/chart1.xml");
110    }
111
112    #[test]
113    fn test_chart_part_from_chart() {
114        let chart = ChartBuilder::new("Test", ChartType::Bar)
115            .categories(vec!["A", "B"])
116            .add_series(ChartSeries::new("Data", vec![1.0, 2.0]))
117            .build();
118
119        let part = ChartPart::from_chart(2, chart);
120        assert_eq!(part.chart_number(), 2);
121        assert!(part.chart().is_some());
122    }
123
124    #[test]
125    fn test_chart_part_to_xml() {
126        let chart = ChartBuilder::new("Sales", ChartType::Bar)
127            .categories(vec!["Q1", "Q2"])
128            .add_series(ChartSeries::new("2024", vec![100.0, 150.0]))
129            .build();
130
131        let part = ChartPart::from_chart(1, chart);
132        let xml = part.to_xml().unwrap();
133
134        assert!(xml.contains("c:chart"));
135    }
136
137    #[test]
138    fn test_chart_rel_target() {
139        let part = ChartPart::new(3);
140        assert_eq!(part.rel_target(), "../charts/chart3.xml");
141    }
142}