use crate::oxml::chart::{
Chart as OxmlChart, ChartData, ChartSeries, ChartType, DataLabels, SeriesFormat,
};
use crate::oxml::shape::{Graphic as OxmlGraphic, GraphicFrame as OxmlFrame};
use crate::shape::base::Shape;
use crate::units::Emu;
#[derive(Clone, Debug, Default)]
pub struct ChartShape {
pub(crate) frame: OxmlFrame,
}
impl ChartShape {
pub fn new(chart_type: ChartType, data: ChartData) -> Self {
let chart = OxmlChart::new(chart_type, data);
let frame = OxmlFrame {
graphic: OxmlGraphic::Chart(chart),
..Default::default()
};
ChartShape { frame }
}
pub fn from_frame(frame: OxmlFrame) -> Self {
ChartShape { frame }
}
pub fn chart(&self) -> Option<&OxmlChart> {
match &self.frame.graphic {
OxmlGraphic::Chart(c) => Some(c),
_ => None,
}
}
pub fn chart_mut(&mut self) -> Option<&mut OxmlChart> {
match &mut self.frame.graphic {
OxmlGraphic::Chart(c) => Some(c),
_ => None,
}
}
pub fn chart_type(&self) -> ChartType {
self.chart()
.map(|c| c.chart_type)
.unwrap_or(ChartType::Column)
}
pub fn set_chart_type(&mut self, chart_type: ChartType) {
if let Some(c) = self.chart_mut() {
c.chart_type = chart_type;
}
}
pub fn data(&self) -> Option<&ChartData> {
self.chart().map(|c| &c.data)
}
pub fn data_mut(&mut self) -> Option<&mut ChartData> {
self.chart_mut().map(|c| &mut c.data)
}
pub fn rid(&self) -> &str {
match &self.frame.graphic {
OxmlGraphic::Chart(c) => &c.rid,
_ => "",
}
}
pub fn set_rid(&mut self, rid: impl Into<String>) {
if let Some(c) = self.chart_mut() {
c.rid = rid.into();
}
}
pub fn set_placeholder(&mut self, ph_idx: u32, ph_type: Option<&str>) {
self.frame.is_placeholder = true;
self.frame.ph_idx = Some(ph_idx);
self.frame.ph_type = ph_type.map(|s| s.to_string());
}
pub fn clear_placeholder(&mut self) {
self.frame.is_placeholder = false;
self.frame.ph_idx = None;
self.frame.ph_type = None;
}
pub fn is_placeholder(&self) -> bool {
self.frame.is_placeholder
}
pub fn ph_idx(&self) -> Option<u32> {
self.frame.ph_idx
}
pub fn ph_type(&self) -> Option<&str> {
self.frame.ph_type.as_deref()
}
pub fn data_labels(&self) -> Option<&DataLabels> {
self.chart().and_then(|c| c.data.data_labels.as_ref())
}
pub fn set_data_labels(&mut self, labels: Option<DataLabels>) {
if let Some(c) = self.chart_mut() {
c.data.data_labels = labels;
}
}
pub fn series_data_labels(&self, series_idx: usize) -> Option<&DataLabels> {
self.chart()
.and_then(|c| c.data.series.get(series_idx))
.and_then(|s| s.data_labels.as_ref())
}
pub fn set_series_data_labels(&mut self, series_idx: usize, labels: Option<DataLabels>) {
if let Some(c) = self.chart_mut() {
if let Some(s) = c.data.series.get_mut(series_idx) {
s.data_labels = labels;
}
}
}
pub fn series_format(&self, series_idx: usize) -> Option<&SeriesFormat> {
self.chart()
.and_then(|c| c.data.series.get(series_idx))
.and_then(|s| s.format.as_ref())
}
pub fn set_series_format(&mut self, series_idx: usize, format: Option<SeriesFormat>) {
if let Some(c) = self.chart_mut() {
if let Some(s) = c.data.series.get_mut(series_idx) {
s.format = format;
}
}
}
pub fn is_series_secondary(&self, series_idx: usize) -> bool {
self.chart()
.and_then(|c| c.data.series.get(series_idx))
.map(|s| s.secondary_axis)
.unwrap_or(false)
}
pub fn set_series_secondary(&mut self, series_idx: usize, secondary: bool) {
if let Some(c) = self.chart_mut() {
if let Some(s) = c.data.series.get_mut(series_idx) {
s.secondary_axis = secondary;
}
}
}
pub fn secondary_chart_type(&self) -> Option<ChartType> {
self.chart().and_then(|c| c.data.secondary_chart_type)
}
pub fn set_secondary_chart_type(&mut self, chart_type: Option<ChartType>) {
if let Some(c) = self.chart_mut() {
c.data.secondary_chart_type = chart_type;
}
}
pub fn push_series(&mut self, series: ChartSeries) -> Option<usize> {
if let Some(c) = self.chart_mut() {
c.data.series.push(series);
Some(c.data.series.len() - 1)
} else {
None
}
}
}
impl Shape for ChartShape {
fn id(&self) -> u32 {
self.frame.id
}
fn set_id(&mut self, id: u32) {
self.frame.id = id;
}
fn name(&self) -> &str {
&self.frame.name
}
fn set_name(&mut self, name: String) {
self.frame.name = name;
}
fn shape_type(&self) -> &'static str {
"chart"
}
fn left(&self) -> Emu {
self.frame.properties.xfrm.off_x.unwrap_or_default()
}
fn set_left(&mut self, emu: Emu) {
self.frame.properties.xfrm.off_x = Some(emu);
}
fn top(&self) -> Emu {
self.frame.properties.xfrm.off_y.unwrap_or_default()
}
fn set_top(&mut self, emu: Emu) {
self.frame.properties.xfrm.off_y = Some(emu);
}
fn width(&self) -> Emu {
self.frame.properties.xfrm.ext_cx.unwrap_or_default()
}
fn set_width(&mut self, emu: Emu) {
self.frame.properties.xfrm.ext_cx = Some(emu);
}
fn height(&self) -> Emu {
self.frame.properties.xfrm.ext_cy.unwrap_or_default()
}
fn set_height(&mut self, emu: Emu) {
self.frame.properties.xfrm.ext_cy = Some(emu);
}
fn rotation(&self) -> f64 {
0.0
}
fn set_rotation(&mut self, _deg: f64) {}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::oxml::chart::{ChartCategory, ChartSeries};
#[test]
fn new_chart_shape_basics() {
let data = ChartData {
categories: vec![ChartCategory::new("A"), ChartCategory::new("B")],
series: vec![ChartSeries::new("S", vec![1.0, 2.0])],
title: Some("T".to_string()),
data_labels: None,
..Default::default()
};
let mut s = ChartShape::new(ChartType::Column, data);
assert_eq!(s.chart_type(), ChartType::Column);
assert_eq!(s.chart().unwrap().data.categories.len(), 2);
assert_eq!(s.chart().unwrap().data.series.len(), 1);
assert_eq!(s.rid(), "");
s.set_chart_type(ChartType::Line);
assert_eq!(s.chart_type(), ChartType::Line);
assert_eq!(s.chart().unwrap().data.series[0].values, vec![1.0, 2.0]);
}
#[test]
fn set_rid_propagates() {
let mut s = ChartShape::new(ChartType::Pie, ChartData::default());
s.set_rid("rIdChart1");
assert_eq!(s.rid(), "rIdChart1");
}
#[test]
fn shape_trait_geometry() {
let mut s = ChartShape::new(ChartType::Bar, ChartData::default());
s.set_left(Emu(100));
s.set_top(Emu(200));
s.set_width(Emu(300));
s.set_height(Emu(400));
assert_eq!(s.left(), Emu(100));
assert_eq!(s.top(), Emu(200));
assert_eq!(s.width(), Emu(300));
assert_eq!(s.height(), Emu(400));
s.set_rotation(45.0);
assert_eq!(s.rotation(), 0.0);
}
#[test]
fn frame_xml_contains_chart_reference() {
let mut s = ChartShape::new(ChartType::Column, ChartData::default());
s.set_rid("rIdChart7");
let mut w = crate::oxml::writer::XmlWriter::new();
s.frame.write_xml(&mut w);
let xml = w.into_string();
assert!(xml.contains("<p:graphicFrame>"), "xml: {}", xml);
assert!(xml.contains("<c:chart"), "xml: {}", xml);
assert!(xml.contains("r:id=\"rIdChart7\""), "xml: {}", xml);
}
#[test]
fn data_labels_setter_and_getter() {
let mut s = ChartShape::new(ChartType::Column, ChartData::default());
assert!(s.data_labels().is_none());
s.set_data_labels(Some(DataLabels::show_values()));
assert_eq!(s.data_labels().unwrap().show_val, Some(true));
s.set_data_labels(None);
assert!(s.data_labels().is_none());
}
#[test]
fn series_data_labels_by_index() {
let data = ChartData {
categories: vec![ChartCategory::new("A")],
series: vec![
ChartSeries::new("S1", vec![1.0]),
ChartSeries::new("S2", vec![2.0]),
],
title: None,
data_labels: None,
..Default::default()
};
let mut s = ChartShape::new(ChartType::Column, data);
assert!(s.series_data_labels(0).is_none());
s.set_series_data_labels(0, Some(DataLabels::show_values()));
assert_eq!(s.series_data_labels(0).unwrap().show_val, Some(true));
assert!(s.series_data_labels(1).is_none());
assert!(s.series_data_labels(99).is_none());
s.set_series_data_labels(99, Some(DataLabels::show_values()));
}
#[test]
fn series_format_setter_and_getter() {
use crate::oxml::color::Color;
use crate::units::RGBColor;
let data = ChartData {
categories: vec![ChartCategory::new("A")],
series: vec![ChartSeries::new("S1", vec![1.0])],
..Default::default()
};
let mut s = ChartShape::new(ChartType::Column, data);
assert!(s.series_format(0).is_none());
s.set_series_format(
0,
Some(SeriesFormat::solid_fill(Color::RGB(RGBColor(
0xFF, 0x00, 0x00,
)))),
);
assert!(s.series_format(0).is_some());
s.set_series_format(0, None);
assert!(s.series_format(0).is_none());
s.set_series_format(99, Some(SeriesFormat::default()));
assert!(s.series_format(99).is_none());
}
#[test]
fn series_secondary_axis_setter_and_getter() {
let data = ChartData {
categories: vec![ChartCategory::new("A")],
series: vec![
ChartSeries::new("S1", vec![1.0]),
ChartSeries::new("S2", vec![2.0]),
],
title: None,
data_labels: None,
..Default::default()
};
let mut s = ChartShape::new(ChartType::Column, data);
assert!(!s.is_series_secondary(0));
assert!(!s.is_series_secondary(1));
s.set_series_secondary(1, true);
assert!(!s.is_series_secondary(0));
assert!(s.is_series_secondary(1));
assert!(!s.is_series_secondary(99));
s.set_series_secondary(99, true);
}
#[test]
fn push_series_appends_to_end() {
let data = ChartData {
categories: vec![ChartCategory::new("A")],
series: vec![ChartSeries::new("S1", vec![1.0])],
title: None,
data_labels: None,
..Default::default()
};
let mut s = ChartShape::new(ChartType::Column, data);
assert_eq!(s.data().unwrap().series.len(), 1);
let idx = s.push_series(ChartSeries::new("S2", vec![2.0]));
assert_eq!(idx, Some(1));
assert_eq!(s.data().unwrap().series.len(), 2);
assert_eq!(s.data().unwrap().series[1].name, "S2");
}
}