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::{Part, PartType, ContentType};
6use crate::exc::PptxError;
7use crate::generator::charts::{Chart, generate_chart_part_xml};
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("No chart data available".to_string()))
85    }
86
87    fn from_xml(xml: &str) -> Result<Self, PptxError> {
88        // Basic parsing - store XML for now
89        Ok(ChartPart {
90            path: "ppt/charts/chart1.xml".to_string(),
91            chart_number: 1,
92            chart: None,
93            xml_content: Some(xml.to_string()),
94        })
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101    use crate::generator::charts::{ChartBuilder, ChartType, ChartSeries};
102
103    #[test]
104    fn test_chart_part_new() {
105        let part = ChartPart::new(1);
106        assert_eq!(part.chart_number(), 1);
107        assert_eq!(part.path(), "ppt/charts/chart1.xml");
108    }
109
110    #[test]
111    fn test_chart_part_from_chart() {
112        let chart = ChartBuilder::new("Test", ChartType::Bar)
113            .categories(vec!["A", "B"])
114            .add_series(ChartSeries::new("Data", vec![1.0, 2.0]))
115            .build();
116        
117        let part = ChartPart::from_chart(2, chart);
118        assert_eq!(part.chart_number(), 2);
119        assert!(part.chart().is_some());
120    }
121
122    #[test]
123    fn test_chart_part_to_xml() {
124        let chart = ChartBuilder::new("Sales", ChartType::Bar)
125            .categories(vec!["Q1", "Q2"])
126            .add_series(ChartSeries::new("2024", vec![100.0, 150.0]))
127            .build();
128        
129        let part = ChartPart::from_chart(1, chart);
130        let xml = part.to_xml().unwrap();
131        
132        assert!(xml.contains("c:chart"));
133    }
134
135    #[test]
136    fn test_chart_rel_target() {
137        let part = ChartPart::new(3);
138        assert_eq!(part.rel_target(), "../charts/chart3.xml");
139    }
140}