#![warn(missing_docs)]
mod tests;
use std::io::Cursor;
use std::{fmt, mem, sync::OnceLock};
use crate::drawing::{DrawingObject, DrawingType};
use crate::utility::{self, ToXmlBoolean};
use crate::xmlwriter::{
xml_data_element_only, xml_declaration, xml_empty_tag, xml_empty_tag_only, xml_end_tag,
xml_start_tag, xml_start_tag_only,
};
use crate::{
ColNum, Color, IntoExcelDateTime, ObjectMovement, RowNum, XlsxError, COL_MAX, ROW_MAX,
};
pub(crate) const UNPARSED_SHEET_RANGE: &str = "UNPARSED_SHEET_RANGE";
#[derive(Clone)]
pub struct Chart {
pub(crate) id: u32,
pub(crate) writer: Cursor<Vec<u8>>,
pub(crate) x_offset: u32,
pub(crate) y_offset: u32,
pub(crate) name: String,
pub(crate) alt_text: String,
pub(crate) object_movement: ObjectMovement,
pub(crate) decorative: bool,
pub(crate) drawing_type: DrawingType,
pub(crate) series: Vec<ChartSeries>,
pub(crate) default_label_position: ChartDataLabelPosition,
height: f64,
width: f64,
scale_width: f64,
scale_height: f64,
axis_ids: (u32, u32),
axis2_ids: (u32, u32),
category_has_num_format: bool,
chart_type: ChartType,
chart_group_type: ChartType,
pub(crate) title: ChartTitle,
pub(crate) x_axis: ChartAxis,
pub(crate) y_axis: ChartAxis,
pub(crate) x2_axis: ChartAxis,
pub(crate) y2_axis: ChartAxis,
pub(crate) combined_chart: Option<Box<Chart>>,
pub(crate) chart_area: ChartArea,
pub(crate) plot_area: ChartPlotArea,
pub(crate) is_chartsheet: bool,
pub(crate) protection_on: bool,
legend: ChartLegend,
grouping: ChartGrouping,
show_empty_cells_as: Option<ChartEmptyCells>,
show_hidden_data: bool,
show_na_as_empty: bool,
default_num_format: String,
overlap: Option<i8>,
gap: u16,
style: u8,
hole_size: u8,
rotation: u16,
has_up_down_bars: bool,
up_bar_format: ChartFormat,
down_bar_format: ChartFormat,
has_high_low_lines: bool,
high_low_lines_format: ChartFormat,
has_drop_lines: bool,
drop_lines_format: ChartFormat,
table: Option<ChartDataTable>,
series_index: usize,
has_secondary_axis: bool,
has_crosses: bool,
}
impl Chart {
#[allow(clippy::new_without_default)]
pub fn new(chart_type: ChartType) -> Chart {
let writer = Cursor::new(Vec::with_capacity(2048));
let chart = Chart {
writer,
id: 0,
height: 288.0,
width: 480.0,
scale_width: 1.0,
scale_height: 1.0,
x_offset: 0,
y_offset: 0,
name: String::new(),
alt_text: String::new(),
object_movement: ObjectMovement::MoveAndSizeWithCells,
decorative: false,
drawing_type: DrawingType::Chart,
axis_ids: (0, 0),
axis2_ids: (0, 0),
series: vec![],
category_has_num_format: false,
chart_type,
chart_group_type: chart_type,
title: ChartTitle::new(),
x_axis: ChartAxis::new(),
y_axis: ChartAxis::new(),
x2_axis: ChartAxis::new(),
y2_axis: ChartAxis::new(),
legend: ChartLegend::new(),
chart_area: ChartArea::default(),
plot_area: ChartPlotArea::default(),
grouping: ChartGrouping::Standard,
show_empty_cells_as: None,
show_hidden_data: false,
show_na_as_empty: false,
default_num_format: "General".to_string(),
overlap: None,
gap: 150,
style: 2,
hole_size: 50,
rotation: 0,
default_label_position: ChartDataLabelPosition::Default,
has_up_down_bars: false,
up_bar_format: ChartFormat::default(),
down_bar_format: ChartFormat::default(),
has_high_low_lines: false,
high_low_lines_format: ChartFormat::default(),
has_drop_lines: false,
drop_lines_format: ChartFormat::default(),
table: None,
combined_chart: None,
series_index: 0,
has_secondary_axis: false,
has_crosses: true,
is_chartsheet: false,
protection_on: false,
};
match chart_type {
ChartType::Area | ChartType::AreaStacked | ChartType::AreaPercentStacked => {
Self::initialize_area_chart(chart)
}
ChartType::Bar | ChartType::BarStacked | ChartType::BarPercentStacked => {
Self::initialize_bar_chart(chart)
}
ChartType::Column | ChartType::ColumnStacked | ChartType::ColumnPercentStacked => {
Self::initialize_column_chart(chart)
}
ChartType::Doughnut => Self::initialize_doughnut_chart(chart),
ChartType::Line | ChartType::LineStacked | ChartType::LinePercentStacked => {
Self::initialize_line_chart(chart)
}
ChartType::Pie => Self::initialize_pie_chart(chart),
ChartType::Radar | ChartType::RadarWithMarkers | ChartType::RadarFilled => {
Self::initialize_radar_chart(chart)
}
ChartType::Scatter
| ChartType::ScatterStraight
| ChartType::ScatterStraightWithMarkers
| ChartType::ScatterSmooth
| ChartType::ScatterSmoothWithMarkers => Self::initialize_scatter_chart(chart),
ChartType::Stock => Self::initialize_stock_chart(chart),
}
}
pub fn new_area() -> Chart {
Self::new(ChartType::Area)
}
pub fn new_bar() -> Chart {
Self::new(ChartType::Bar)
}
pub fn new_column() -> Chart {
Self::new(ChartType::Column)
}
pub fn new_doughnut() -> Chart {
Self::new(ChartType::Doughnut)
}
pub fn new_line() -> Chart {
Self::new(ChartType::Line)
}
pub fn new_pie() -> Chart {
Self::new(ChartType::Pie)
}
pub fn new_radar() -> Chart {
Self::new(ChartType::Radar)
}
pub fn new_scatter() -> Chart {
Self::new(ChartType::Scatter)
}
pub fn new_stock() -> Chart {
Self::new(ChartType::Stock)
}
pub fn add_series(&mut self) -> &mut ChartSeries {
let mut series = ChartSeries::new();
if self.chart_type == ChartType::Scatter {
series.set_format(
ChartFormat::new().set_line(ChartLine::new().set_width(2.25).set_hidden(true)),
);
}
if self.chart_type == ChartType::ScatterStraight
|| self.chart_type == ChartType::ScatterSmooth
|| self.chart_group_type == ChartType::Line
|| self.chart_type == ChartType::Radar
{
series.marker = Some(ChartMarker::new().set_none().clone());
}
self.series.push(series);
self.series.last_mut().unwrap()
}
pub fn push_series(&mut self, series: &ChartSeries) -> &mut Chart {
let mut series = series.clone();
if self.chart_type == ChartType::Scatter {
series.set_format(
ChartFormat::new().set_line(ChartLine::new().set_width(2.25).set_hidden(true)),
);
}
if self.chart_type == ChartType::ScatterStraight
|| self.chart_type == ChartType::ScatterSmooth
|| self.chart_group_type == ChartType::Line
|| self.chart_type == ChartType::Radar
{
series.marker = Some(ChartMarker::new().set_none().clone());
}
self.series.push(series);
self
}
pub fn title(&mut self) -> &mut ChartTitle {
&mut self.title
}
pub fn x_axis(&mut self) -> &mut ChartAxis {
&mut self.x_axis
}
pub fn y_axis(&mut self) -> &mut ChartAxis {
&mut self.y_axis
}
pub fn x2_axis(&mut self) -> &mut ChartAxis {
&mut self.x2_axis
}
pub fn y2_axis(&mut self) -> &mut ChartAxis {
&mut self.y2_axis
}
pub fn legend(&mut self) -> &mut ChartLegend {
&mut self.legend
}
pub fn chart_area(&mut self) -> &mut ChartArea {
&mut self.chart_area
}
pub fn plot_area(&mut self) -> &mut ChartPlotArea {
&mut self.plot_area
}
pub fn combine(&mut self, chart: &Chart) -> &mut Chart {
self.combined_chart = Some(Box::new(chart.clone()));
self
}
pub fn set_style(&mut self, style: u8) -> &mut Chart {
if (1..=48).contains(&style) {
self.style = style;
} else {
eprintln!("Style id '{style}' outside Excel range: 1 <= style <= 48.");
}
self
}
pub fn set_rotation(&mut self, rotation: u16) -> &mut Chart {
if (0..=360).contains(&rotation) {
self.rotation = rotation;
}
self
}
pub fn set_hole_size(&mut self, hole_size: u8) -> &mut Chart {
if (0..=90).contains(&hole_size) {
self.hole_size = hole_size;
}
self
}
pub fn set_up_down_bars(&mut self, enable: bool) -> &mut Chart {
self.has_up_down_bars = enable;
self
}
pub fn set_up_bar_format<T>(&mut self, format: T) -> &mut Chart
where
T: IntoChartFormat,
{
self.has_up_down_bars = true;
self.up_bar_format = format.new_chart_format();
self
}
pub fn set_down_bar_format<T>(&mut self, format: T) -> &mut Chart
where
T: IntoChartFormat,
{
self.has_up_down_bars = true;
self.down_bar_format = format.new_chart_format();
self
}
pub fn set_high_low_lines(&mut self, enable: bool) -> &mut Chart {
self.has_high_low_lines = enable;
self
}
pub fn set_high_low_lines_format<T>(&mut self, format: T) -> &mut Chart
where
T: IntoChartFormat,
{
self.has_high_low_lines = true;
self.high_low_lines_format = format.new_chart_format();
self
}
pub fn set_drop_lines(&mut self, enable: bool) -> &mut Chart {
self.has_drop_lines = enable;
self
}
pub fn set_drop_lines_format<T>(&mut self, format: T) -> &mut Chart
where
T: IntoChartFormat,
{
self.has_drop_lines = true;
self.drop_lines_format = format.new_chart_format();
self
}
pub fn set_data_table(&mut self, table: &ChartDataTable) -> &mut Chart {
self.table = Some(table.clone());
self
}
pub fn set_width(&mut self, width: u32) -> &mut Chart {
if width == 0 {
return self;
}
self.width = f64::from(width);
self
}
pub fn set_height(&mut self, height: u32) -> &mut Chart {
if height == 0 {
return self;
}
self.height = f64::from(height);
self
}
pub fn set_scale_height(&mut self, scale: f64) -> &mut Chart {
if scale <= 0.0 {
return self;
}
self.scale_height = scale;
self
}
pub fn set_scale_width(&mut self, scale: f64) -> &mut Chart {
if scale <= 0.0 {
return self;
}
self.scale_width = scale;
self
}
pub fn set_name(&mut self, name: impl Into<String>) -> &mut Chart {
self.name = name.into();
self
}
pub fn set_alt_text(&mut self, alt_text: impl Into<String>) -> &mut Chart {
let alt_text = alt_text.into();
if alt_text.chars().count() > 255 {
eprintln!("Alternative text is greater than Excel's limit of 255 characters.");
return self;
}
self.alt_text = alt_text;
self
}
pub fn set_decorative(&mut self, enable: bool) -> &mut Chart {
self.decorative = enable;
self
}
pub fn set_object_movement(&mut self, option: ObjectMovement) -> &mut Chart {
self.object_movement = option;
self
}
pub fn validate(&mut self) -> Result<&mut Chart, XlsxError> {
if self.series.is_empty() {
return Err(XlsxError::ChartError(
"Chart must contain at least one series".to_string(),
));
}
for series in &self.series {
if !series.value_range.has_data() {
return Err(XlsxError::ChartError(
"Chart series must contain a 'values' range".to_string(),
));
}
if self.chart_group_type == ChartType::Scatter && !series.category_range.has_data() {
return Err(XlsxError::ChartError(
"Scatter style charts must contain a 'categories' range".to_string(),
));
}
series.value_range.validate()?;
if series.category_range.has_data() {
series.category_range.validate()?;
}
if let ChartTrendlineType::Polynomial(order) = series.trendline.trend_type {
if !(2..6).contains(&order) {
return Err(XlsxError::ChartError(
"Chart series Polynomial trendline order must be in the Excel range 2-6"
.to_string(),
));
}
}
if let ChartTrendlineType::MovingAverage(period) = series.trendline.trend_type {
if !(2..4).contains(&period) {
return Err(XlsxError::ChartError(
"Chart series Moving Average trendline period must be in the Excel range 2-4"
.to_string(),
));
}
}
}
Ok(self)
}
pub fn show_empty_cells_as(&mut self, option: ChartEmptyCells) -> &mut Chart {
self.show_empty_cells_as = Some(option);
self
}
pub fn show_na_as_empty_cell(&mut self) -> &mut Chart {
self.show_na_as_empty = true;
self
}
pub fn show_hidden_data(&mut self) -> &mut Chart {
self.show_hidden_data = true;
self
}
#[doc(hidden)]
pub fn set_axis_ids(&mut self, axis_id1: u32, axis_id2: u32) {
self.axis_ids = (axis_id1, axis_id2);
}
#[doc(hidden)]
pub fn set_axis2_ids(&mut self, axis_id1: u32, axis_id2: u32) {
self.axis2_ids = (axis_id1, axis_id2);
}
pub(crate) fn add_axis_ids(&mut self, chart_id: u32) {
if self.axis_ids.0 != 0 {
return;
}
let axis_id = (5000 + chart_id) * 10000 + 1;
self.axis_ids = (axis_id, axis_id + 1);
if self.combined_chart.is_none() {
self.axis2_ids = (axis_id + 2, axis_id + 3);
} else {
self.axis2_ids = (axis_id + 10_000_000, axis_id + 10_000_001);
}
}
fn deleted_legend_entries(&self) -> Vec<usize> {
if !self.legend.deleted_entries.is_empty() {
return self.legend.deleted_entries.clone();
}
let mut deleted_entries = vec![];
let mut index = 0;
for series in &self.series {
if series.delete_from_legend {
deleted_entries.push(index);
}
index += 1;
}
for series in &self.series {
if series.trendline.trend_type != ChartTrendlineType::None {
if series.trendline.delete_from_legend {
deleted_entries.push(index);
}
index += 1;
}
}
deleted_entries
}
fn check_for_secondary_axis(&mut self) {
if let Some(combined_chart) = &self.combined_chart {
for series in &combined_chart.series {
if series.secondary_axis {
self.has_secondary_axis = true;
}
}
}
for series in &self.series {
if series.secondary_axis {
self.has_secondary_axis = true;
return;
}
}
}
fn initialize_area_chart(mut self) -> Chart {
self.has_crosses = false;
self.x_axis.axis_type = ChartAxisType::Category;
self.x_axis.axis_position = ChartAxisPosition::Bottom;
self.x_axis.position_between_ticks = false;
self.y_axis.axis_type = ChartAxisType::Value;
self.y_axis.axis_position = ChartAxisPosition::Left;
self.y_axis.title.is_horizontal = true;
self.y_axis.major_gridlines = true;
self.x2_axis.axis_type = ChartAxisType::Category;
self.x2_axis.position_between_ticks = false;
self.x2_axis.crossing = ChartAxisCrossing::Max;
self.x2_axis.is_hidden = true;
self.x2_axis.label_position = ChartAxisLabelPosition::None;
self.y2_axis.axis_type = ChartAxisType::Value;
self.y2_axis.axis_position = ChartAxisPosition::Left;
self.chart_group_type = ChartType::Area;
if self.chart_type == ChartType::Area {
self.grouping = ChartGrouping::Standard;
} else if self.chart_type == ChartType::AreaStacked {
self.grouping = ChartGrouping::Stacked;
} else if self.chart_type == ChartType::AreaPercentStacked {
self.grouping = ChartGrouping::PercentStacked;
self.default_num_format = "0%".to_string();
}
self.default_label_position = ChartDataLabelPosition::Center;
self
}
fn initialize_bar_chart(mut self) -> Chart {
self.has_crosses = false;
self.x_axis.axis_type = ChartAxisType::Value;
self.x_axis.axis_position = ChartAxisPosition::Bottom;
self.x_axis.major_gridlines = true;
self.y_axis.axis_type = ChartAxisType::Category;
self.y_axis.axis_position = ChartAxisPosition::Left;
self.y_axis.title.is_horizontal = true;
self.x2_axis.axis_type = ChartAxisType::Category;
self.x2_axis.axis_position = ChartAxisPosition::Bottom;
self.x2_axis.crossing = ChartAxisCrossing::Automatic;
self.y2_axis.axis_type = ChartAxisType::Value;
self.y2_axis.axis_position = ChartAxisPosition::Left;
self.y2_axis.crossing = ChartAxisCrossing::Max;
self.y2_axis.is_hidden = true;
self.y2_axis.label_position = ChartAxisLabelPosition::None;
self.chart_group_type = ChartType::Bar;
if self.chart_type == ChartType::Bar {
self.grouping = ChartGrouping::Clustered;
} else if self.chart_type == ChartType::BarStacked {
self.grouping = ChartGrouping::Stacked;
self.overlap = Some(100);
} else if self.chart_type == ChartType::BarPercentStacked {
self.grouping = ChartGrouping::PercentStacked;
self.default_num_format = "0%".to_string();
self.overlap = Some(100);
}
self.default_label_position = ChartDataLabelPosition::OutsideEnd;
self
}
fn initialize_column_chart(mut self) -> Chart {
self.x_axis.axis_type = ChartAxisType::Category;
self.x_axis.axis_position = ChartAxisPosition::Bottom;
self.y_axis.axis_type = ChartAxisType::Value;
self.y_axis.axis_position = ChartAxisPosition::Left;
self.y_axis.major_gridlines = true;
self.x2_axis.axis_type = ChartAxisType::Category;
self.x2_axis.crossing = ChartAxisCrossing::Max;
self.x2_axis.is_hidden = true;
self.x2_axis.label_position = ChartAxisLabelPosition::None;
self.y2_axis.axis_type = ChartAxisType::Value;
self.y2_axis.axis_position = ChartAxisPosition::Left;
self.chart_group_type = ChartType::Column;
if self.chart_type == ChartType::Column {
self.grouping = ChartGrouping::Clustered;
} else if self.chart_type == ChartType::ColumnStacked {
self.grouping = ChartGrouping::Stacked;
self.overlap = Some(100);
} else if self.chart_type == ChartType::ColumnPercentStacked {
self.grouping = ChartGrouping::PercentStacked;
self.default_num_format = "0%".to_string();
self.overlap = Some(100);
}
self.default_label_position = ChartDataLabelPosition::OutsideEnd;
self
}
fn initialize_doughnut_chart(mut self) -> Chart {
self.chart_group_type = ChartType::Doughnut;
self.default_label_position = ChartDataLabelPosition::BestFit;
self
}
fn initialize_line_chart(mut self) -> Chart {
self.x_axis.axis_type = ChartAxisType::Category;
self.x_axis.axis_position = ChartAxisPosition::Bottom;
self.y_axis.axis_type = ChartAxisType::Value;
self.y_axis.axis_position = ChartAxisPosition::Left;
self.y_axis.title.is_horizontal = true;
self.y_axis.major_gridlines = true;
self.x2_axis.axis_type = ChartAxisType::Category;
self.x2_axis.crossing = ChartAxisCrossing::Max;
self.x2_axis.is_hidden = true;
self.x2_axis.label_position = ChartAxisLabelPosition::None;
self.y2_axis.axis_type = ChartAxisType::Value;
self.y2_axis.axis_position = ChartAxisPosition::Left;
self.chart_group_type = ChartType::Line;
if self.chart_type == ChartType::Line {
self.grouping = ChartGrouping::Standard;
} else if self.chart_type == ChartType::LineStacked {
self.grouping = ChartGrouping::Stacked;
} else if self.chart_type == ChartType::LinePercentStacked {
self.grouping = ChartGrouping::PercentStacked;
self.default_num_format = "0%".to_string();
}
self.default_label_position = ChartDataLabelPosition::Right;
self
}
fn initialize_pie_chart(mut self) -> Chart {
self.chart_group_type = ChartType::Pie;
self.default_label_position = ChartDataLabelPosition::BestFit;
self
}
fn initialize_radar_chart(mut self) -> Chart {
self.x_axis.axis_type = ChartAxisType::Category;
self.x_axis.axis_position = ChartAxisPosition::Bottom;
self.x_axis.major_gridlines = true;
self.y_axis.axis_type = ChartAxisType::Value;
self.y_axis.axis_position = ChartAxisPosition::Left;
self.y_axis.major_gridlines = true;
self.y_axis.major_tick_type = Some(ChartAxisTickType::Cross);
self.chart_group_type = ChartType::Radar;
self.default_label_position = ChartDataLabelPosition::Center;
self
}
fn initialize_scatter_chart(mut self) -> Chart {
self.x_axis.axis_type = ChartAxisType::Value;
self.x_axis.axis_position = ChartAxisPosition::Bottom;
self.x_axis.position_between_ticks = false;
self.y_axis.axis_type = ChartAxisType::Value;
self.y_axis.axis_position = ChartAxisPosition::Left;
self.y_axis.position_between_ticks = false;
self.y_axis.title.is_horizontal = true;
self.y_axis.major_gridlines = true;
self.x2_axis.axis_type = ChartAxisType::Value;
self.x2_axis.position_between_ticks = false;
self.x2_axis.crossing = ChartAxisCrossing::Max;
self.x2_axis.is_hidden = true;
self.x2_axis.label_position = ChartAxisLabelPosition::None;
self.y2_axis.axis_type = ChartAxisType::Value;
self.y2_axis.axis_position = ChartAxisPosition::Left;
self.y2_axis.position_between_ticks = false;
self.chart_group_type = ChartType::Scatter;
self.default_label_position = ChartDataLabelPosition::Right;
self
}
fn initialize_stock_chart(mut self) -> Chart {
self.has_crosses = false;
self.x_axis.axis_type = ChartAxisType::Date;
self.x_axis.axis_position = ChartAxisPosition::Bottom;
self.x_axis.automatic = true;
self.y_axis.axis_type = ChartAxisType::Value;
self.y_axis.axis_position = ChartAxisPosition::Left;
self.y_axis.title.is_horizontal = true;
self.y_axis.major_gridlines = true;
self.x2_axis.axis_type = ChartAxisType::Date;
self.x2_axis.crossing = ChartAxisCrossing::Max;
self.x2_axis.is_hidden = true;
self.x2_axis.label_position = ChartAxisLabelPosition::None;
self.x2_axis.automatic = true;
self.y2_axis.axis_type = ChartAxisType::Value;
self.y2_axis.axis_position = ChartAxisPosition::Left;
self.chart_group_type = ChartType::Stock;
self.default_label_position = ChartDataLabelPosition::Right;
self
}
fn write_area_chart(&mut self, primary_axis: bool) {
let series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:areaChart");
self.write_grouping();
self.write_series(&series);
if self.has_drop_lines {
self.write_drop_lines();
}
self.write_ax_ids(primary_axis);
xml_end_tag(&mut self.writer, "c:areaChart");
}
fn write_bar_chart(&mut self, primary_axis: bool) {
let series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:barChart");
self.write_bar_dir("bar");
self.write_grouping();
self.write_series(&series);
if self.gap != 150 {
self.write_gap_width(self.gap);
}
self.write_overlap();
self.write_ax_ids(primary_axis);
xml_end_tag(&mut self.writer, "c:barChart");
}
fn write_column_chart(&mut self, primary_axis: bool) {
let series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:barChart");
self.write_bar_dir("col");
self.write_grouping();
self.write_series(&series);
if self.gap != 150 {
self.write_gap_width(self.gap);
}
self.write_overlap();
self.write_ax_ids(primary_axis);
xml_end_tag(&mut self.writer, "c:barChart");
}
fn write_doughnut_chart(&mut self, primary_axis: bool) {
let series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:doughnutChart");
self.write_vary_colors();
self.write_series(&series);
self.write_first_slice_ang();
self.write_hole_size();
xml_end_tag(&mut self.writer, "c:doughnutChart");
}
fn write_line_chart(&mut self, primary_axis: bool) {
let series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:lineChart");
self.write_grouping();
self.write_series(&series);
if self.has_drop_lines {
self.write_drop_lines();
}
if self.has_high_low_lines {
self.write_hi_low_lines();
}
if self.has_up_down_bars {
self.write_up_down_bars();
}
self.write_marker_value();
self.write_ax_ids(primary_axis);
xml_end_tag(&mut self.writer, "c:lineChart");
}
fn write_pie_chart(&mut self, primary_axis: bool) {
let series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:pieChart");
self.write_vary_colors();
self.write_series(&series);
self.write_first_slice_ang();
xml_end_tag(&mut self.writer, "c:pieChart");
}
fn write_radar_chart(&mut self, primary_axis: bool) {
let series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:radarChart");
self.write_radar_style();
self.write_series(&series);
self.write_ax_ids(primary_axis);
xml_end_tag(&mut self.writer, "c:radarChart");
}
fn write_scatter_chart(&mut self, primary_axis: bool) {
let mut series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:scatterChart");
self.write_scatter_style();
self.write_scatter_series(&mut series);
self.write_ax_ids(primary_axis);
xml_end_tag(&mut self.writer, "c:scatterChart");
}
fn write_stock_chart(&mut self, primary_axis: bool) {
let series = self.get_series(primary_axis);
if series.is_empty() {
return;
}
xml_start_tag_only(&mut self.writer, "c:stockChart");
self.write_series(&series);
if self.has_drop_lines {
self.write_drop_lines();
}
if primary_axis && self.has_high_low_lines {
self.write_hi_low_lines();
}
if self.has_up_down_bars {
self.write_up_down_bars();
}
self.write_ax_ids(primary_axis);
xml_end_tag(&mut self.writer, "c:stockChart");
}
pub(crate) fn assemble_xml_file(&mut self) {
xml_declaration(&mut self.writer);
self.write_chart_space();
self.write_lang();
if self.style != 2 {
self.write_style();
}
if self.protection_on {
self.write_protection();
}
self.write_chart();
self.write_sp_pr(&self.chart_area.format.clone());
if !self.is_chartsheet {
self.write_print_settings();
}
xml_end_tag(&mut self.writer, "c:chartSpace");
}
fn write_chart_space(&mut self) {
let attributes = [
(
"xmlns:c",
"http://schemas.openxmlformats.org/drawingml/2006/chart",
),
(
"xmlns:a",
"http://schemas.openxmlformats.org/drawingml/2006/main",
),
(
"xmlns:r",
"http://schemas.openxmlformats.org/officeDocument/2006/relationships",
),
];
xml_start_tag(&mut self.writer, "c:chartSpace", &attributes);
}
fn write_lang(&mut self) {
let attributes = [("val", "en-US")];
xml_empty_tag(&mut self.writer, "c:lang", &attributes);
}
fn write_chart(&mut self) {
xml_start_tag_only(&mut self.writer, "c:chart");
if self.title.hidden {
self.write_auto_title_deleted();
} else {
self.write_chart_title(&self.title.clone());
}
self.write_plot_area();
self.write_legend();
if !self.show_hidden_data {
self.write_plot_vis_only();
}
self.write_disp_blanks_as();
if self.show_na_as_empty {
self.write_disp_na_as_blank();
}
xml_end_tag(&mut self.writer, "c:chart");
}
fn write_chart_title(&mut self, title: &ChartTitle) {
if !title.name.is_empty() {
self.write_title_rich(title);
} else if title.range.has_data() {
self.write_title_formula(title);
} else if title.format.has_formatting() {
self.write_title_format_only(title);
}
}
fn write_series_title(&mut self, title: &ChartTitle) {
if !title.name.is_empty() {
self.write_tx_value(title);
} else if title.range.has_data() {
self.write_tx_formula(title);
}
}
fn write_plot_area(&mut self) {
self.series_index = 0;
xml_start_tag_only(&mut self.writer, "c:plotArea");
self.write_layout(&self.plot_area.layout.clone());
self.write_chart_type();
if let Some(combined_chart) = &mut self.combined_chart {
combined_chart.axis_ids = self.axis_ids;
combined_chart.axis2_ids = self.axis2_ids;
combined_chart.series_index = self.series.len();
mem::swap(&mut combined_chart.writer, &mut self.writer);
combined_chart.write_chart_type();
mem::swap(&mut combined_chart.writer, &mut self.writer);
}
let mut x_axis = self.x_axis.clone();
let mut y_axis = self.y_axis.clone();
if self.chart_group_type == ChartType::Bar {
std::mem::swap(&mut x_axis, &mut y_axis);
}
match self.chart_group_type {
ChartType::Pie | ChartType::Doughnut => {}
ChartType::Scatter => {
self.write_cat_val_ax(&x_axis, &y_axis, self.axis_ids);
self.write_val_ax(&x_axis, &y_axis, self.axis_ids);
}
_ => {
if self.x_axis.axis_type == ChartAxisType::Date {
self.write_date_ax(&x_axis, &y_axis, self.axis_ids);
} else {
self.write_cat_ax(&x_axis, &y_axis, self.axis_ids);
}
self.write_val_ax(&x_axis, &y_axis, self.axis_ids);
}
}
self.check_for_secondary_axis();
if self.has_secondary_axis {
let mut x_axis = self.x2_axis.clone();
let mut y_axis = self.y2_axis.clone();
let mut chart_group_type = self.chart_group_type;
let mut is_combined = false;
if let Some(combined_chart) = &self.combined_chart {
chart_group_type = combined_chart.chart_group_type;
is_combined = true;
}
if chart_group_type == ChartType::Bar {
std::mem::swap(&mut x_axis, &mut y_axis);
}
match chart_group_type {
ChartType::Pie | ChartType::Doughnut => {}
ChartType::Scatter => {
if is_combined {
self.write_val_ax(&x_axis, &y_axis, self.axis2_ids);
self.write_cat_val_ax(&x_axis, &y_axis, self.axis2_ids);
} else {
self.write_cat_val_ax(&x_axis, &y_axis, self.axis2_ids);
self.write_val_ax(&x_axis, &y_axis, self.axis2_ids);
}
}
_ => {
self.write_val_ax(&x_axis, &y_axis, self.axis2_ids);
if self.x_axis.axis_type == ChartAxisType::Date {
self.write_date_ax(&x_axis, &y_axis, self.axis2_ids);
} else {
self.write_cat_ax(&x_axis, &y_axis, self.axis2_ids);
}
}
}
}
if let Some(table) = &self.table {
self.write_data_table(&table.clone());
}
self.write_sp_pr(&self.plot_area.format.clone());
xml_end_tag(&mut self.writer, "c:plotArea");
}
fn write_chart_type(&mut self) {
match self.chart_type {
ChartType::Area | ChartType::AreaStacked | ChartType::AreaPercentStacked => {
self.write_area_chart(true);
self.write_area_chart(false);
}
ChartType::Bar | ChartType::BarStacked | ChartType::BarPercentStacked => {
self.write_bar_chart(true);
self.write_bar_chart(false);
}
ChartType::Column | ChartType::ColumnStacked | ChartType::ColumnPercentStacked => {
self.write_column_chart(true);
self.write_column_chart(false);
}
ChartType::Doughnut => {
self.write_doughnut_chart(true);
self.write_doughnut_chart(false);
}
ChartType::Line | ChartType::LineStacked | ChartType::LinePercentStacked => {
self.write_line_chart(true);
self.write_line_chart(false);
}
ChartType::Pie => {
self.write_pie_chart(true);
self.write_pie_chart(false);
}
ChartType::Radar | ChartType::RadarWithMarkers | ChartType::RadarFilled => {
self.write_radar_chart(true);
self.write_radar_chart(false);
}
ChartType::Scatter
| ChartType::ScatterStraight
| ChartType::ScatterStraightWithMarkers
| ChartType::ScatterSmooth
| ChartType::ScatterSmoothWithMarkers => {
self.write_scatter_chart(true);
self.write_scatter_chart(false);
}
ChartType::Stock => {
self.write_stock_chart(true);
self.write_stock_chart(false);
}
}
}
fn write_layout(&mut self, layout: &ChartLayout) {
if layout.is_not_default() {
xml_start_tag_only(&mut self.writer, "c:layout");
self.write_manual_layout(layout);
xml_end_tag(&mut self.writer, "c:layout");
} else {
xml_empty_tag_only(&mut self.writer, "c:layout");
}
}
fn write_manual_layout(&mut self, layout: &ChartLayout) {
xml_start_tag_only(&mut self.writer, "c:manualLayout");
if layout.has_inner {
xml_empty_tag(&mut self.writer, "c:layoutTarget", &[("val", "inner")]);
}
xml_empty_tag(&mut self.writer, "c:xMode", &[("val", "edge")]);
xml_empty_tag(&mut self.writer, "c:yMode", &[("val", "edge")]);
if let Some(value) = layout.x_offset {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:x", &attributes);
}
if let Some(value) = layout.y_offset {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:y", &attributes);
}
if layout.has_dimensions {
if let Some(value) = layout.width {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:w", &attributes);
}
if let Some(value) = layout.height {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:h", &attributes);
}
}
xml_end_tag(&mut self.writer, "c:manualLayout");
}
fn write_bar_dir(&mut self, direction: &str) {
let attributes = [("val", direction.to_string())];
xml_empty_tag(&mut self.writer, "c:barDir", &attributes);
}
fn write_grouping(&mut self) {
let attributes = [("val", self.grouping.to_string())];
xml_empty_tag(&mut self.writer, "c:grouping", &attributes);
}
fn write_scatter_style(&mut self) {
let mut attributes = vec![];
if self.chart_type == ChartType::ScatterSmooth
|| self.chart_type == ChartType::ScatterSmoothWithMarkers
{
attributes.push(("val", "smoothMarker".to_string()));
} else {
attributes.push(("val", "lineMarker".to_string()));
}
xml_empty_tag(&mut self.writer, "c:scatterStyle", &attributes);
}
fn get_series(&self, primary_axis: bool) -> Vec<ChartSeries> {
let mut series_copy = vec![];
for each_series in &self.series {
if each_series.secondary_axis != primary_axis {
series_copy.push(each_series.clone());
}
}
series_copy
}
fn write_series(&mut self, series: &Vec<ChartSeries>) {
for series in series {
let max_points = series.value_range.number_of_points();
xml_start_tag_only(&mut self.writer, "c:ser");
if series.overlap.is_some() {
self.overlap = series.overlap;
}
if series.gap != 150 {
self.gap = series.gap;
}
self.write_idx(self.series_index);
self.write_order(self.series_index);
self.write_series_title(&series.title);
self.write_sp_pr(&series.format);
if let Some(marker) = &series.marker {
if !marker.automatic {
self.write_marker(marker);
}
}
if series.invert_if_negative {
self.write_invert_if_negative();
}
if !series.points.is_empty() {
self.write_d_pt(&series.points, max_points);
}
if let Some(data_label) = &series.data_label {
self.write_data_labels(data_label, &series.custom_data_labels, max_points);
}
if series.trendline.trend_type != ChartTrendlineType::None {
self.write_trendline(&series.trendline);
}
if self.chart_group_type == ChartType::Bar {
if let Some(error_bars) = &series.x_error_bars {
self.write_error_bar("", error_bars);
}
} else if self.chart_group_type == ChartType::Column {
if let Some(error_bars) = &series.y_error_bars {
self.write_error_bar("", error_bars);
}
} else if let Some(error_bars) = &series.y_error_bars {
self.write_error_bar("y", error_bars);
}
if series.category_range.has_data() {
self.category_has_num_format = matches!(
series.category_range.cache.cache_type,
ChartRangeCacheDataType::Number | ChartRangeCacheDataType::Date
);
self.write_cat(&series.category_range);
}
self.write_val(&series.value_range);
if !series.inverted_color.is_auto_or_default() {
self.write_extension_list(series.inverted_color);
}
if self.chart_group_type == ChartType::Line {
if let Some(smooth) = series.smooth {
if smooth {
self.write_smooth();
}
}
}
self.series_index += 1;
xml_end_tag(&mut self.writer, "c:ser");
}
}
fn write_scatter_series(&mut self, series: &mut Vec<ChartSeries>) {
for series in series {
let max_points = series.value_range.number_of_points();
xml_start_tag_only(&mut self.writer, "c:ser");
self.write_idx(self.series_index);
self.write_order(self.series_index);
self.write_series_title(&series.title);
if let Some(marker) = &series.marker {
if !marker.automatic {
self.write_marker(marker);
}
}
if self.chart_type == ChartType::Scatter && series.format.line.is_none() {
let mut line = ChartLine::new();
line.set_width(2.25);
series.format.line = Some(line);
}
self.write_sp_pr(&series.format);
if !series.points.is_empty() {
self.write_d_pt(&series.points, max_points);
}
if let Some(data_label) = &series.data_label {
self.write_data_labels(data_label, &series.custom_data_labels, max_points);
}
if series.trendline.trend_type != ChartTrendlineType::None {
self.write_trendline(&series.trendline);
}
if let Some(error_bars) = &series.x_error_bars {
self.write_error_bar("x", error_bars);
}
if let Some(error_bars) = &series.y_error_bars {
self.write_error_bar("y", error_bars);
}
self.write_x_val(&series.category_range);
self.write_y_val(&series.value_range);
if self.chart_group_type == ChartType::Scatter {
if let Some(smooth) = series.smooth {
if smooth {
self.write_smooth();
}
} else if self.chart_type == ChartType::ScatterSmooth
|| self.chart_type == ChartType::ScatterSmoothWithMarkers
{
self.write_smooth();
}
}
self.series_index += 1;
xml_end_tag(&mut self.writer, "c:ser");
}
}
fn write_d_pt(&mut self, points: &[ChartPoint], max_points: usize) {
let has_marker =
self.chart_group_type == ChartType::Scatter || self.chart_group_type == ChartType::Line;
for (index, point) in points.iter().enumerate() {
if index >= max_points {
break;
}
if point.is_not_default() {
xml_start_tag_only(&mut self.writer, "c:dPt");
self.write_idx(index);
if has_marker {
xml_start_tag_only(&mut self.writer, "c:marker");
}
self.write_sp_pr(&point.format);
if has_marker {
xml_end_tag(&mut self.writer, "c:marker");
}
xml_end_tag(&mut self.writer, "c:dPt");
}
}
}
fn write_idx(&mut self, index: usize) {
let attributes = [("val", index.to_string())];
xml_empty_tag(&mut self.writer, "c:idx", &attributes);
}
fn write_order(&mut self, index: usize) {
let attributes = [("val", index.to_string())];
xml_empty_tag(&mut self.writer, "c:order", &attributes);
}
fn write_invert_if_negative(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:invertIfNegative", &attributes);
}
fn write_extension_list(&mut self, color: Color) {
let attributes1 = [
("uri", "{6F2FDCE9-48DA-4B69-8628-5D25D57E5C99}"),
(
"xmlns:c14",
"http://schemas.microsoft.com/office/drawing/2007/8/2/chart",
),
];
let attributes2 = [(
"xmlns:c14",
"http://schemas.microsoft.com/office/drawing/2007/8/2/chart",
)];
xml_start_tag_only(&mut self.writer, "c:extLst");
xml_start_tag(&mut self.writer, "c:ext", &attributes1);
xml_start_tag_only(&mut self.writer, "c14:invertSolidFillFmt");
xml_start_tag(&mut self.writer, "c14:spPr", &attributes2);
self.write_a_solid_fill(color, 0);
xml_end_tag(&mut self.writer, "c14:spPr");
xml_end_tag(&mut self.writer, "c14:invertSolidFillFmt");
xml_end_tag(&mut self.writer, "c:ext");
xml_end_tag(&mut self.writer, "c:extLst");
}
fn write_cat(&mut self, range: &ChartRange) {
xml_start_tag_only(&mut self.writer, "c:cat");
self.write_cache_ref(range, false);
xml_end_tag(&mut self.writer, "c:cat");
}
fn write_val(&mut self, range: &ChartRange) {
xml_start_tag_only(&mut self.writer, "c:val");
self.write_cache_ref(range, true);
xml_end_tag(&mut self.writer, "c:val");
}
fn write_x_val(&mut self, range: &ChartRange) {
xml_start_tag_only(&mut self.writer, "c:xVal");
self.write_cache_ref(range, false);
xml_end_tag(&mut self.writer, "c:xVal");
}
fn write_y_val(&mut self, range: &ChartRange) {
xml_start_tag_only(&mut self.writer, "c:yVal");
self.write_cache_ref(range, true);
xml_end_tag(&mut self.writer, "c:yVal");
}
fn write_cache_ref(&mut self, range: &ChartRange, is_num_only: bool) {
if range.cache.cache_type == ChartRangeCacheDataType::String && !is_num_only {
self.write_str_ref(range);
} else if range.cache.cache_type == ChartRangeCacheDataType::MultiLevelString {
self.write_multi_level_str_ref(range);
} else {
self.write_num_ref(range);
}
}
fn write_num_ref(&mut self, range: &ChartRange) {
xml_start_tag_only(&mut self.writer, "c:numRef");
self.write_range_formula(&range.formula_string());
if range.cache.has_data() {
self.write_num_cache(&range.cache);
}
xml_end_tag(&mut self.writer, "c:numRef");
}
fn write_str_ref(&mut self, range: &ChartRange) {
xml_start_tag_only(&mut self.writer, "c:strRef");
self.write_range_formula(&range.formula_string());
if range.cache.has_data() {
self.write_str_cache(&range.cache);
}
xml_end_tag(&mut self.writer, "c:strRef");
}
fn write_num_cache(&mut self, cache: &ChartRangeCacheData) {
xml_start_tag_only(&mut self.writer, "c:numCache");
if cache.cache_type == ChartRangeCacheDataType::Date {
self.write_format_code("dd/mm/yyyy");
} else {
self.write_format_code("General");
}
self.write_pt_count(cache.data.len());
for (index, value) in cache.data.iter().enumerate() {
if !value.is_empty() {
if value.parse::<f64>().is_err() {
self.write_pt(index, "0");
} else {
self.write_pt(index, value);
}
}
}
xml_end_tag(&mut self.writer, "c:numCache");
}
fn write_multi_level_str_ref(&mut self, range: &ChartRange) {
xml_start_tag_only(&mut self.writer, "c:multiLvlStrRef");
self.write_range_formula(&range.formula_string());
if range.cache.has_data() {
self.write_multi_level_str_cache(&range.cache);
}
xml_end_tag(&mut self.writer, "c:multiLvlStrRef");
}
fn write_str_cache(&mut self, cache: &ChartRangeCacheData) {
xml_start_tag_only(&mut self.writer, "c:strCache");
self.write_pt_count(cache.data.len());
for (index, value) in cache.data.iter().enumerate() {
self.write_pt(index, value);
}
xml_end_tag(&mut self.writer, "c:strCache");
}
fn write_multi_level_str_cache(&mut self, cache: &ChartRangeCacheData) {
xml_start_tag_only(&mut self.writer, "c:multiLvlStrCache");
self.write_pt_count(cache.major_dim);
for depth in (0..cache.minor_dim).rev() {
xml_start_tag_only(&mut self.writer, "c:lvl");
for index in 0..cache.major_dim {
let offset = depth + cache.minor_dim * index;
let value = &cache.data[offset];
if !value.is_empty() {
self.write_pt(index, value);
}
}
xml_end_tag(&mut self.writer, "c:lvl");
}
xml_end_tag(&mut self.writer, "c:multiLvlStrCache");
}
fn write_range_formula(&mut self, formula: &str) {
xml_data_element_only(&mut self.writer, "c:f", formula);
}
fn write_format_code(&mut self, format_code: &str) {
xml_data_element_only(&mut self.writer, "c:formatCode", format_code);
}
fn write_pt_count(&mut self, count: usize) {
let attributes = [("val", count.to_string())];
xml_empty_tag(&mut self.writer, "c:ptCount", &attributes);
}
fn write_pt(&mut self, index: usize, value: &str) {
let attributes = [("idx", index.to_string())];
xml_start_tag(&mut self.writer, "c:pt", &attributes);
xml_data_element_only(&mut self.writer, "c:v", value);
xml_end_tag(&mut self.writer, "c:pt");
}
fn write_ax_ids(&mut self, primary_axis: bool) {
if primary_axis {
self.write_ax_id(self.axis_ids.0);
self.write_ax_id(self.axis_ids.1);
} else {
self.write_ax_id(self.axis2_ids.0);
self.write_ax_id(self.axis2_ids.1);
}
}
fn write_ax_id(&mut self, axis_id: u32) {
let attributes = [("val", axis_id.to_string())];
xml_empty_tag(&mut self.writer, "c:axId", &attributes);
}
fn write_cat_ax(&mut self, x_axis: &ChartAxis, y_axis: &ChartAxis, axis_ids: (u32, u32)) {
xml_start_tag_only(&mut self.writer, "c:catAx");
self.write_ax_id(axis_ids.0);
self.write_scaling(x_axis);
if x_axis.is_hidden {
self.write_delete();
}
self.write_ax_pos(x_axis.axis_position, y_axis.reverse, y_axis.crossing);
self.write_major_gridlines(x_axis);
self.write_minor_gridlines(x_axis);
self.write_chart_title(&x_axis.title);
if !x_axis.num_format.is_empty() {
self.write_number_format(&x_axis.num_format, x_axis.num_format_linked_to_source);
} else if self.category_has_num_format {
self.write_number_format("General", true);
}
if let Some(tick_type) = x_axis.major_tick_type {
self.write_major_tick_mark(tick_type);
}
if let Some(tick_type) = x_axis.minor_tick_type {
self.write_minor_tick_mark(tick_type);
}
self.write_tick_label_position(x_axis.label_position);
if x_axis.format.has_formatting() {
self.write_sp_pr(&x_axis.format);
}
if let Some(font) = &x_axis.font {
self.write_axis_font(font);
}
self.write_cross_ax(axis_ids.1);
if self.has_crosses || !x_axis.is_hidden {
match y_axis.crossing {
ChartAxisCrossing::Automatic | ChartAxisCrossing::Min | ChartAxisCrossing::Max => {
self.write_crosses(&y_axis.crossing.to_string());
}
ChartAxisCrossing::AxisValue(_) => {
self.write_crosses_at(&y_axis.crossing.to_string());
}
ChartAxisCrossing::CategoryNumber(_) => {
self.write_crosses(&ChartAxisCrossing::Automatic.to_string());
}
}
}
if !x_axis.automatic {
self.write_auto();
}
self.write_lbl_algn(&x_axis.label_alignment.to_string());
self.write_lbl_offset();
if x_axis.label_interval > 1 {
self.write_tick_lbl_skip(x_axis.label_interval);
}
if x_axis.tick_interval > 1 {
self.write_tick_mark_skip(x_axis.tick_interval);
}
xml_end_tag(&mut self.writer, "c:catAx");
}
fn write_date_ax(&mut self, x_axis: &ChartAxis, y_axis: &ChartAxis, axis_ids: (u32, u32)) {
xml_start_tag_only(&mut self.writer, "c:dateAx");
self.write_ax_id(axis_ids.0);
self.write_scaling(x_axis);
if x_axis.is_hidden {
self.write_delete();
}
self.write_ax_pos(x_axis.axis_position, y_axis.reverse, y_axis.crossing);
self.write_major_gridlines(x_axis);
self.write_minor_gridlines(x_axis);
self.write_chart_title(&x_axis.title);
if !x_axis.num_format.is_empty() {
self.write_number_format(&x_axis.num_format, x_axis.num_format_linked_to_source);
} else if self.category_has_num_format {
self.write_number_format("dd/mm/yyyy", true);
}
if let Some(tick_type) = x_axis.major_tick_type {
self.write_major_tick_mark(tick_type);
}
if let Some(tick_type) = x_axis.minor_tick_type {
self.write_minor_tick_mark(tick_type);
}
self.write_tick_label_position(x_axis.label_position);
if x_axis.format.has_formatting() {
self.write_sp_pr(&x_axis.format);
}
if let Some(font) = &x_axis.font {
self.write_axis_font(&font.clone());
}
self.write_cross_ax(axis_ids.1);
if self.has_crosses || !x_axis.is_hidden {
match y_axis.crossing {
ChartAxisCrossing::Automatic | ChartAxisCrossing::Min | ChartAxisCrossing::Max => {
self.write_crosses(&y_axis.crossing.to_string());
}
ChartAxisCrossing::AxisValue(_) => {
self.write_crosses_at(&y_axis.crossing.to_string());
}
ChartAxisCrossing::CategoryNumber(_) => {
self.write_crosses(&ChartAxisCrossing::Automatic.to_string());
}
}
}
if x_axis.automatic {
self.write_auto();
}
self.write_lbl_offset();
if x_axis.label_interval > 1 {
self.write_tick_lbl_skip(x_axis.label_interval);
}
if x_axis.tick_interval > 1 {
self.write_tick_mark_skip(x_axis.tick_interval);
}
if !x_axis.major_unit.is_empty() {
self.write_major_unit(&x_axis.major_unit);
}
if let Some(unit) = x_axis.major_unit_date_type {
self.write_major_time_unit(unit);
}
if !x_axis.minor_unit.is_empty() {
self.write_minor_unit(&x_axis.minor_unit);
}
if let Some(unit) = x_axis.minor_unit_date_type {
self.write_minor_time_unit(unit);
}
xml_end_tag(&mut self.writer, "c:dateAx");
}
fn write_val_ax(&mut self, x_axis: &ChartAxis, y_axis: &ChartAxis, axis_ids: (u32, u32)) {
xml_start_tag_only(&mut self.writer, "c:valAx");
self.write_ax_id(axis_ids.1);
self.write_scaling(y_axis);
if y_axis.is_hidden {
self.write_delete();
}
self.write_ax_pos(y_axis.axis_position, x_axis.reverse, x_axis.crossing);
self.write_major_gridlines(y_axis);
self.write_minor_gridlines(y_axis);
self.write_chart_title(&y_axis.title);
if y_axis.num_format.is_empty() {
self.write_number_format(&self.default_num_format.clone(), true);
} else {
self.write_number_format(&y_axis.num_format, y_axis.num_format_linked_to_source);
}
if let Some(position) = y_axis.major_tick_type {
self.write_major_tick_mark(position);
}
if let Some(position) = y_axis.minor_tick_type {
self.write_minor_tick_mark(position);
}
self.write_tick_label_position(y_axis.label_position);
if y_axis.format.has_formatting() {
self.write_sp_pr(&y_axis.format);
}
if let Some(font) = &y_axis.font {
self.write_axis_font(font);
}
self.write_cross_ax(axis_ids.0);
match x_axis.crossing {
ChartAxisCrossing::Automatic | ChartAxisCrossing::Min | ChartAxisCrossing::Max => {
self.write_crosses(&x_axis.crossing.to_string());
}
ChartAxisCrossing::CategoryNumber(_) | ChartAxisCrossing::AxisValue(_) => {
self.write_crosses_at(&x_axis.crossing.to_string());
}
}
self.write_cross_between(x_axis.position_between_ticks);
if y_axis.axis_type != ChartAxisType::Category && !y_axis.major_unit.is_empty() {
self.write_major_unit(&y_axis.major_unit);
}
if y_axis.axis_type != ChartAxisType::Category && !y_axis.minor_unit.is_empty() {
self.write_minor_unit(&y_axis.minor_unit);
}
if y_axis.display_units_type != ChartAxisDisplayUnitType::None {
self.write_disp_units(y_axis.display_units_type, y_axis.display_units_visible);
}
xml_end_tag(&mut self.writer, "c:valAx");
}
fn write_cat_val_ax(&mut self, x_axis: &ChartAxis, y_axis: &ChartAxis, axis_ids: (u32, u32)) {
xml_start_tag_only(&mut self.writer, "c:valAx");
self.write_ax_id(axis_ids.0);
self.write_scaling(x_axis);
if x_axis.is_hidden {
self.write_delete();
}
self.write_ax_pos(x_axis.axis_position, y_axis.reverse, y_axis.crossing);
self.write_major_gridlines(x_axis);
self.write_minor_gridlines(x_axis);
self.write_chart_title(&x_axis.title);
if x_axis.num_format.is_empty() {
self.write_number_format(&self.default_num_format.clone(), true);
} else {
self.write_number_format(&x_axis.num_format, x_axis.num_format_linked_to_source);
}
if let Some(position) = x_axis.major_tick_type {
self.write_major_tick_mark(position);
}
if let Some(position) = x_axis.minor_tick_type {
self.write_minor_tick_mark(position);
}
self.write_tick_label_position(x_axis.label_position);
if x_axis.format.has_formatting() {
self.write_sp_pr(&x_axis.format);
}
if let Some(font) = &x_axis.font {
self.write_axis_font(font);
}
self.write_cross_ax(axis_ids.1);
match y_axis.crossing {
ChartAxisCrossing::Automatic | ChartAxisCrossing::Min | ChartAxisCrossing::Max => {
self.write_crosses(&y_axis.crossing.to_string());
}
ChartAxisCrossing::CategoryNumber(_) | ChartAxisCrossing::AxisValue(_) => {
self.write_crosses_at(&y_axis.crossing.to_string());
}
}
self.write_cross_between(y_axis.position_between_ticks);
if x_axis.axis_type != ChartAxisType::Category && !x_axis.major_unit.is_empty() {
self.write_major_unit(&x_axis.major_unit);
}
if x_axis.axis_type != ChartAxisType::Category && !x_axis.minor_unit.is_empty() {
self.write_minor_unit(&x_axis.minor_unit);
}
if x_axis.display_units_type != ChartAxisDisplayUnitType::None {
self.write_disp_units(x_axis.display_units_type, x_axis.display_units_visible);
}
xml_end_tag(&mut self.writer, "c:valAx");
}
fn write_scaling(&mut self, axis: &ChartAxis) {
xml_start_tag_only(&mut self.writer, "c:scaling");
if axis.axis_type != ChartAxisType::Category && axis.log_base >= 2 {
self.write_log_base(axis.log_base);
}
self.write_orientation(axis.reverse);
if axis.axis_type != ChartAxisType::Category && !axis.max.is_empty() {
self.write_max(&axis.max);
}
if axis.axis_type != ChartAxisType::Category && !axis.min.is_empty() {
self.write_min(&axis.min);
}
xml_end_tag(&mut self.writer, "c:scaling");
}
fn write_log_base(&mut self, base: u16) {
let attributes = [("val", base.to_string())];
xml_empty_tag(&mut self.writer, "c:logBase", &attributes);
}
fn write_orientation(&mut self, reverse: bool) {
let attributes = if reverse {
[("val", "maxMin")]
} else {
[("val", "minMax")]
};
xml_empty_tag(&mut self.writer, "c:orientation", &attributes);
}
fn write_max(&mut self, max: &str) {
let attributes = [("val", max.to_string())];
xml_empty_tag(&mut self.writer, "c:max", &attributes);
}
fn write_min(&mut self, min: &str) {
let attributes = [("val", min.to_string())];
xml_empty_tag(&mut self.writer, "c:min", &attributes);
}
fn write_ax_pos(
&mut self,
position: ChartAxisPosition,
reverse: bool,
crossing: ChartAxisCrossing,
) {
let mut position = position;
if reverse || crossing == ChartAxisCrossing::Max {
position = position.reverse();
}
let attributes = [("val", position.to_string())];
xml_empty_tag(&mut self.writer, "c:axPos", &attributes);
}
fn write_number_format(&mut self, format: &str, linked: bool) {
let attributes = [
("formatCode", format.to_string()),
("sourceLinked", linked.to_xml_bool()),
];
xml_empty_tag(&mut self.writer, "c:numFmt", &attributes);
}
fn write_major_gridlines(&mut self, axis: &ChartAxis) {
if axis.major_gridlines {
if let Some(line) = &axis.major_gridlines_line {
xml_start_tag_only(&mut self.writer, "c:majorGridlines");
xml_start_tag_only(&mut self.writer, "c:spPr");
self.write_a_ln(line);
xml_end_tag(&mut self.writer, "c:spPr");
xml_end_tag(&mut self.writer, "c:majorGridlines");
} else {
xml_empty_tag_only(&mut self.writer, "c:majorGridlines");
}
}
}
fn write_minor_gridlines(&mut self, axis: &ChartAxis) {
if axis.minor_gridlines {
if let Some(line) = &axis.minor_gridlines_line {
xml_start_tag_only(&mut self.writer, "c:minorGridlines");
xml_start_tag_only(&mut self.writer, "c:spPr");
self.write_a_ln(line);
xml_end_tag(&mut self.writer, "c:spPr");
xml_end_tag(&mut self.writer, "c:minorGridlines");
} else {
xml_empty_tag_only(&mut self.writer, "c:minorGridlines");
}
}
}
fn write_tick_label_position(&mut self, position: ChartAxisLabelPosition) {
let attributes = [("val", position.to_string())];
xml_empty_tag(&mut self.writer, "c:tickLblPos", &attributes);
}
fn write_cross_ax(&mut self, axis_id: u32) {
let attributes = [("val", axis_id.to_string())];
xml_empty_tag(&mut self.writer, "c:crossAx", &attributes);
}
fn write_crosses(&mut self, crossing: &str) {
let attributes = [("val", crossing)];
xml_empty_tag(&mut self.writer, "c:crosses", &attributes);
}
fn write_crosses_at(&mut self, crossing: &str) {
let attributes = [("val", crossing)];
xml_empty_tag(&mut self.writer, "c:crossesAt", &attributes);
}
fn write_auto(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:auto", &attributes);
}
fn write_lbl_algn(&mut self, position: &str) {
let attributes = [("val", position)];
xml_empty_tag(&mut self.writer, "c:lblAlgn", &attributes);
}
fn write_lbl_offset(&mut self) {
let attributes = [("val", "100")];
xml_empty_tag(&mut self.writer, "c:lblOffset", &attributes);
}
fn write_cross_between(&mut self, position_between_ticks: bool) {
let attributes = if position_between_ticks {
[("val", "between")]
} else {
[("val", "midCat")]
};
xml_empty_tag(&mut self.writer, "c:crossBetween", &attributes);
}
fn write_tick_lbl_skip(&mut self, units: u16) {
let attributes = [("val", units.to_string())];
xml_empty_tag(&mut self.writer, "c:tickLblSkip", &attributes);
}
fn write_tick_mark_skip(&mut self, units: u16) {
let attributes = [("val", units.to_string())];
xml_empty_tag(&mut self.writer, "c:tickMarkSkip", &attributes);
}
fn write_major_unit(&mut self, value: &str) {
let attributes = [("val", value)];
xml_empty_tag(&mut self.writer, "c:majorUnit", &attributes);
}
fn write_minor_unit(&mut self, value: &str) {
let attributes = [("val", value)];
xml_empty_tag(&mut self.writer, "c:minorUnit", &attributes);
}
fn write_major_time_unit(&mut self, units: ChartAxisDateUnitType) {
let attributes = [("val", units.to_string())];
xml_empty_tag(&mut self.writer, "c:majorTimeUnit", &attributes);
}
fn write_minor_time_unit(&mut self, units: ChartAxisDateUnitType) {
let attributes = [("val", units.to_string())];
xml_empty_tag(&mut self.writer, "c:minorTimeUnit", &attributes);
}
fn write_disp_units(&mut self, units: ChartAxisDisplayUnitType, visible: bool) {
xml_start_tag_only(&mut self.writer, "c:dispUnits");
self.write_built_in_unit(units);
if visible {
self.write_disp_units_lbl();
}
xml_end_tag(&mut self.writer, "c:dispUnits");
}
fn write_built_in_unit(&mut self, units: ChartAxisDisplayUnitType) {
let attributes = [("val", units.to_string())];
xml_empty_tag(&mut self.writer, "c:builtInUnit", &attributes);
}
fn write_disp_units_lbl(&mut self) {
xml_start_tag_only(&mut self.writer, "c:dispUnitsLbl");
let layout = ChartLayout::default();
self.write_layout(&layout);
xml_end_tag(&mut self.writer, "c:dispUnitsLbl");
}
fn write_legend(&mut self) {
if self.legend.hidden {
return;
}
xml_start_tag_only(&mut self.writer, "c:legend");
self.write_legend_pos();
let deleted_entries = self.deleted_legend_entries();
if !deleted_entries.is_empty() {
for index in deleted_entries {
self.write_legend_entry(index);
}
}
self.write_layout(&self.legend.layout.clone());
self.write_sp_pr(&self.legend.format.clone());
if self.legend.has_overlay {
self.write_overlay();
}
if self.chart_type == ChartType::Pie || self.chart_type == ChartType::Doughnut {
match &mut self.legend.font {
Some(font) => {
if font.right_to_left.is_none() {
font.set_right_to_left(false);
}
}
None => {
let mut font = ChartFont::new();
font.set_right_to_left(false);
self.legend.font = Some(font);
}
}
}
if let Some(font) = &self.legend.font {
self.write_tx_pr(&font.clone(), false);
}
xml_end_tag(&mut self.writer, "c:legend");
}
fn write_legend_pos(&mut self) {
let attributes = [("val", self.legend.position.to_string())];
xml_empty_tag(&mut self.writer, "c:legendPos", &attributes);
}
fn write_legend_entry(&mut self, index: usize) {
xml_start_tag_only(&mut self.writer, "c:legendEntry");
self.write_idx(index);
self.write_delete();
xml_end_tag(&mut self.writer, "c:legendEntry");
}
fn write_overlay(&mut self) {
xml_empty_tag(&mut self.writer, "c:overlay", &[("val", "1")]);
}
fn write_plot_vis_only(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:plotVisOnly", &attributes);
}
fn write_print_settings(&mut self) {
xml_start_tag_only(&mut self.writer, "c:printSettings");
self.write_header_footer();
self.write_page_margins();
self.write_page_setup();
xml_end_tag(&mut self.writer, "c:printSettings");
}
fn write_header_footer(&mut self) {
xml_empty_tag_only(&mut self.writer, "c:headerFooter");
}
fn write_page_margins(&mut self) {
let attributes = [
("b", "0.75"),
("l", "0.7"),
("r", "0.7"),
("t", "0.75"),
("header", "0.3"),
("footer", "0.3"),
];
xml_empty_tag(&mut self.writer, "c:pageMargins", &attributes);
}
fn write_page_setup(&mut self) {
xml_empty_tag_only(&mut self.writer, "c:pageSetup");
}
fn write_marker_value(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:marker", &attributes);
}
fn write_marker(&mut self, marker: &ChartMarker) {
xml_start_tag_only(&mut self.writer, "c:marker");
self.write_symbol(marker);
if marker.size != 0 {
self.write_size(marker.size);
}
if marker.format.has_formatting() {
self.write_sp_pr(&marker.format);
}
xml_end_tag(&mut self.writer, "c:marker");
}
fn write_data_labels(
&mut self,
data_label: &ChartDataLabel,
custom_data_labels: &[ChartDataLabel],
max_points: usize,
) {
xml_start_tag_only(&mut self.writer, "c:dLbls");
if !custom_data_labels.is_empty() {
self.write_custom_data_labels(custom_data_labels, max_points);
}
self.write_data_label(data_label);
xml_end_tag(&mut self.writer, "c:dLbls");
}
fn write_custom_data_labels(&mut self, data_labels: &[ChartDataLabel], max_points: usize) {
for (index, data_label) in data_labels.iter().enumerate() {
let mut write_layout = true;
if index >= max_points {
break;
}
if data_label.is_default() {
continue;
}
xml_start_tag_only(&mut self.writer, "c:dLbl");
self.write_idx(index);
if data_label.is_hidden {
self.write_delete();
} else {
if !data_label.format.has_formatting() {
if let Some(font) = &data_label.font {
if font.color.is_auto_or_default() {
xml_empty_tag_only(&mut self.writer, "c:spPr");
}
}
}
let mut data_label = data_label.clone();
data_label.is_custom = true;
if let Some(font) = &mut data_label.font {
font.has_baseline = false;
write_layout = false;
}
if !data_label.title.name.is_empty() || data_label.title.range.has_data() {
if let Some(font) = &data_label.font {
data_label.title.set_font(font);
data_label.title.font.has_baseline = false;
if !data_label.title.name.is_empty() {
data_label.font = None;
}
write_layout = true;
}
}
if write_layout {
let layout = ChartLayout::default();
self.write_layout(&layout);
}
if !data_label.title.name.is_empty() {
self.write_tx_rich(&data_label.title);
} else if data_label.title.range.has_data() {
self.write_tx_formula(&data_label.title);
}
self.write_data_label(&data_label);
}
xml_end_tag(&mut self.writer, "c:dLbl");
}
}
fn write_data_label(&mut self, data_label: &ChartDataLabel) {
if !data_label.num_format.is_empty() {
self.write_number_format(&data_label.num_format, false);
}
self.write_sp_pr(&data_label.format);
if let Some(font) = &data_label.font {
self.write_tx_pr(&font.clone(), false);
}
if data_label.position != ChartDataLabelPosition::Default
&& data_label.position != self.default_label_position
{
self.write_d_lbl_pos(data_label.position);
}
if data_label.show_legend_key {
self.write_show_legend_key();
}
if data_label.show_value
|| (!data_label.is_custom
&& !data_label.show_category_name
&& !data_label.show_percentage)
{
self.write_show_val();
}
if data_label.show_category_name {
self.write_show_category_name();
}
if data_label.show_series_name {
self.write_show_series_name();
}
if data_label.show_percentage {
self.write_show_percent();
}
if data_label.separator != ',' {
self.write_separator(data_label.separator);
}
if data_label.show_leader_lines {
match self.chart_group_type {
ChartType::Pie | ChartType::Doughnut => {
self.write_show_leader_lines_2007();
}
_ => {
self.write_show_leader_lines_2015();
}
}
}
}
fn write_trendline(&mut self, trendline: &ChartTrendline) {
xml_start_tag_only(&mut self.writer, "c:trendline");
if !trendline.name.is_empty() {
self.write_trendline_name(&trendline.name);
}
self.write_sp_pr(&trendline.format);
self.write_trendline_type(trendline);
if let ChartTrendlineType::Polynomial(order) = trendline.trend_type {
self.write_order(order as usize);
}
if let ChartTrendlineType::MovingAverage(period) = trendline.trend_type {
self.write_trendline_period(period);
}
if trendline.forward_period > 0.0 {
self.write_trendline_forward(trendline.forward_period);
}
if trendline.backward_period > 0.0 {
self.write_trendline_backward(trendline.backward_period);
}
if let Some(intercept) = trendline.intercept {
self.write_trendline_intercept(intercept);
}
if trendline.display_r_squared {
self.write_disp_rsqr();
}
if trendline.display_equation {
self.write_trendline_display_equation(trendline);
}
xml_end_tag(&mut self.writer, "c:trendline");
}
fn write_trendline_name(&mut self, name: &str) {
xml_data_element_only(&mut self.writer, "c:name", name);
}
fn write_trendline_type(&mut self, trendline: &ChartTrendline) {
let attributes = [("val", trendline.trend_type.to_string())];
xml_empty_tag(&mut self.writer, "c:trendlineType", &attributes);
}
fn write_trendline_forward(&mut self, value: f64) {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:forward", &attributes);
}
fn write_trendline_backward(&mut self, value: f64) {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:backward", &attributes);
}
fn write_disp_rsqr(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:dispRSqr", &attributes);
}
fn write_trendline_display_equation(&mut self, trendline: &ChartTrendline) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:dispEq", &attributes);
self.write_trendline_label(trendline);
}
fn write_trendline_label(&mut self, trendline: &ChartTrendline) {
xml_start_tag_only(&mut self.writer, "c:trendlineLbl");
let layout = ChartLayout::default();
self.write_layout(&layout);
self.write_number_format("General", false);
self.write_sp_pr(&trendline.label_format);
if let Some(font) = &trendline.label_font {
self.write_axis_font(font);
}
xml_end_tag(&mut self.writer, "c:trendlineLbl");
}
fn write_trendline_period(&mut self, value: u8) {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:period", &attributes);
}
fn write_trendline_intercept(&mut self, value: f64) {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:intercept", &attributes);
}
fn write_error_bar(&mut self, axis: &str, error_bars: &ChartErrorBars) {
xml_start_tag_only(&mut self.writer, "c:errBars");
self.write_error_bar_direction(axis);
self.write_error_bar_type(error_bars.direction);
self.write_err_direction_type(&error_bars.error_type);
if !error_bars.has_end_cap {
self.write_error_bar_no_end_cap();
}
match &error_bars.error_type {
ChartErrorBarsType::FixedValue(value)
| ChartErrorBarsType::Percentage(value)
| ChartErrorBarsType::StandardDeviation(value) => {
self.write_error_value(*value);
}
ChartErrorBarsType::Custom(_, _) => self.write_custom_error_bar_values(error_bars),
ChartErrorBarsType::StandardError => {}
}
self.write_sp_pr(&error_bars.format);
xml_end_tag(&mut self.writer, "c:errBars");
}
fn write_error_bar_direction(&mut self, axis: &str) {
if !axis.is_empty() {
let attributes = vec![("val", axis.to_string())];
xml_empty_tag(&mut self.writer, "c:errDir", &attributes);
}
}
fn write_error_bar_type(&mut self, direction: ChartErrorBarsDirection) {
let attributes = vec![("val", direction.to_string())];
xml_empty_tag(&mut self.writer, "c:errBarType", &attributes);
}
fn write_err_direction_type(&mut self, bar_type: &ChartErrorBarsType) {
let attributes = vec![("val", bar_type.to_string())];
xml_empty_tag(&mut self.writer, "c:errValType", &attributes);
}
fn write_error_bar_no_end_cap(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:noEndCap", &attributes);
}
fn write_error_value(&mut self, value: f64) {
let attributes = [("val", value.to_string())];
xml_empty_tag(&mut self.writer, "c:val", &attributes);
}
fn write_custom_error_bar_values(&mut self, error_bars: &ChartErrorBars) {
xml_start_tag_only(&mut self.writer, "c:plus");
self.write_cache_ref(&error_bars.plus_range, true);
xml_end_tag(&mut self.writer, "c:plus");
xml_start_tag_only(&mut self.writer, "c:minus");
self.write_cache_ref(&error_bars.minus_range, true);
xml_end_tag(&mut self.writer, "c:minus");
}
fn write_up_down_bars(&mut self) {
xml_start_tag_only(&mut self.writer, "c:upDownBars");
self.write_gap_width(150);
self.write_up_bars();
self.write_down_bars();
xml_end_tag(&mut self.writer, "c:upDownBars");
}
fn write_up_bars(&mut self) {
if self.up_bar_format.has_formatting() {
xml_start_tag_only(&mut self.writer, "c:upBars");
self.write_sp_pr(&self.up_bar_format.clone());
xml_end_tag(&mut self.writer, "c:upBars");
} else {
xml_empty_tag_only(&mut self.writer, "c:upBars");
}
}
fn write_down_bars(&mut self) {
if self.down_bar_format.has_formatting() {
xml_start_tag_only(&mut self.writer, "c:downBars");
self.write_sp_pr(&self.down_bar_format.clone());
xml_end_tag(&mut self.writer, "c:downBars");
} else {
xml_empty_tag_only(&mut self.writer, "c:downBars");
}
}
fn write_hi_low_lines(&mut self) {
if self.high_low_lines_format.has_formatting() {
xml_start_tag_only(&mut self.writer, "c:hiLowLines");
self.write_sp_pr(&self.high_low_lines_format.clone());
xml_end_tag(&mut self.writer, "c:hiLowLines");
} else {
xml_empty_tag_only(&mut self.writer, "c:hiLowLines");
}
}
fn write_drop_lines(&mut self) {
if self.drop_lines_format.has_formatting() {
xml_start_tag_only(&mut self.writer, "c:dropLines");
self.write_sp_pr(&self.drop_lines_format.clone());
xml_end_tag(&mut self.writer, "c:dropLines");
} else {
xml_empty_tag_only(&mut self.writer, "c:dropLines");
}
}
fn write_data_table(&mut self, table: &ChartDataTable) {
xml_start_tag_only(&mut self.writer, "c:dTable");
if table.show_horizontal_borders {
self.write_show_horz_border();
}
if table.show_vertical_borders {
self.write_show_vert_border();
}
if table.show_outline_borders {
self.write_show_outline();
}
if table.show_legend_keys {
self.write_show_keys();
}
self.write_sp_pr(&table.format);
if let Some(font) = &table.font {
self.write_axis_font(font);
}
xml_end_tag(&mut self.writer, "c:dTable");
}
fn write_show_keys(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showKeys", &attributes);
}
fn write_show_horz_border(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showHorzBorder", &attributes);
}
fn write_show_vert_border(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showVertBorder", &attributes);
}
fn write_show_outline(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showOutline", &attributes);
}
fn write_show_val(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showVal", &attributes);
}
fn write_show_category_name(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showCatName", &attributes);
}
fn write_show_series_name(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showSerName", &attributes);
}
fn write_show_percent(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showPercent", &attributes);
}
fn write_separator(&mut self, separator: char) {
xml_data_element_only(&mut self.writer, "c:separator", &format!("{separator} "));
}
fn write_show_leader_lines_2007(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showLeaderLines", &attributes);
}
fn write_show_leader_lines_2015(&mut self) {
let attributes = [
("uri", "{CE6537A1-D6FC-4f65-9D91-7224C49458BB}"),
(
"xmlns:c15",
"http://schemas.microsoft.com/office/drawing/2012/chart",
),
];
xml_start_tag_only(&mut self.writer, "c:extLst");
xml_start_tag(&mut self.writer, "c:ext", &attributes);
xml_empty_tag(&mut self.writer, "c15:showLeaderLines", &[("val", "1")]);
xml_end_tag(&mut self.writer, "c:ext");
xml_end_tag(&mut self.writer, "c:extLst");
}
fn write_show_legend_key(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:showLegendKey", &attributes);
}
fn write_d_lbl_pos(&mut self, position: ChartDataLabelPosition) {
let attributes = [("val", position.to_string())];
xml_empty_tag(&mut self.writer, "c:dLblPos", &attributes);
}
fn write_delete(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:delete", &attributes);
}
fn write_symbol(&mut self, marker: &ChartMarker) {
let mut attributes = vec![];
if let Some(marker_type) = marker.marker_type {
attributes.push(("val", marker_type.to_string()));
} else if marker.none {
attributes.push(("val", "none".to_string()));
}
xml_empty_tag(&mut self.writer, "c:symbol", &attributes);
}
fn write_size(&mut self, size: u8) {
let attributes = [("val", size.to_string())];
xml_empty_tag(&mut self.writer, "c:size", &attributes);
}
fn write_vary_colors(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:varyColors", &attributes);
}
fn write_first_slice_ang(&mut self) {
let attributes = [("val", self.rotation.to_string())];
xml_empty_tag(&mut self.writer, "c:firstSliceAng", &attributes);
}
fn write_hole_size(&mut self) {
let attributes = [("val", self.hole_size.to_string())];
xml_empty_tag(&mut self.writer, "c:holeSize", &attributes);
}
fn write_axis_font(&mut self, font: &ChartFont) {
xml_start_tag_only(&mut self.writer, "c:txPr");
self.write_a_body_pr(font, false);
self.write_a_lst_style();
xml_start_tag_only(&mut self.writer, "a:p");
self.write_a_p_pr_rich(font);
self.write_a_end_para_rpr();
xml_end_tag(&mut self.writer, "a:p");
xml_end_tag(&mut self.writer, "c:txPr");
}
fn write_tx_pr(&mut self, font: &ChartFont, is_horizontal: bool) {
xml_start_tag_only(&mut self.writer, "c:txPr");
self.write_a_body_pr(font, is_horizontal);
self.write_a_lst_style();
self.write_a_p_formula(font);
xml_end_tag(&mut self.writer, "c:txPr");
}
fn write_a_p_formula(&mut self, font: &ChartFont) {
xml_start_tag_only(&mut self.writer, "a:p");
self.write_a_p_pr(font);
self.write_a_end_para_rpr();
xml_end_tag(&mut self.writer, "a:p");
}
fn write_a_p_pr(&mut self, font: &ChartFont) {
let mut attributes = vec![];
if let Some(right_to_left) = font.right_to_left {
attributes.push(("rtl", right_to_left.to_xml_bool()));
}
xml_start_tag(&mut self.writer, "a:pPr", &attributes);
self.write_a_def_rpr(font);
xml_end_tag(&mut self.writer, "a:pPr");
}
fn write_a_body_pr(&mut self, font: &ChartFont, is_horizontal: bool) {
let mut attributes = vec![];
let rotation = match font.rotation {
Some(rotation) => rotation,
None => {
if is_horizontal {
-90
} else {
360 }
}
};
match rotation {
360 => {}
270 => {
attributes.push(("rot", "0".to_string()));
attributes.push(("vert", "wordArtVert".to_string()));
}
271 => {
attributes.push(("rot", "0".to_string()));
attributes.push(("vert", "eaVert".to_string()));
}
_ => {
let rotation = i32::from(rotation) * 60_000;
attributes.push(("rot", rotation.to_string()));
attributes.push(("vert", "horz".to_string()));
}
}
xml_empty_tag(&mut self.writer, "a:bodyPr", &attributes);
}
fn write_a_lst_style(&mut self) {
xml_empty_tag_only(&mut self.writer, "a:lstStyle");
}
fn write_a_def_rpr(&mut self, font: &ChartFont) {
self.write_font_elements("a:defRPr", font);
}
fn write_a_r_pr(&mut self, font: &ChartFont) {
self.write_font_elements("a:rPr", font);
}
fn write_font_elements(&mut self, tag: &str, font: &ChartFont) {
let mut attributes = vec![];
if tag == "a:rPr" {
attributes.push(("lang", "en-US".to_string()));
}
if font.size > 0.0 {
attributes.push(("sz", font.size.to_string()));
}
if let Some(boolean) = font.bold {
attributes.push(("b", boolean.to_xml_bool()));
}
if font.italic || (font.bold.is_some() && !font.has_default_bold) {
attributes.push(("i", font.italic.to_xml_bool()));
}
if font.underline {
attributes.push(("u", "sng".to_string()));
}
if font.has_baseline {
attributes.push(("baseline", "0".to_string()));
}
if font.is_latin() || !font.color.is_auto_or_default() {
xml_start_tag(&mut self.writer, tag, &attributes);
if !font.color.is_auto_or_default() {
self.write_a_solid_fill(font.color, 0);
}
if font.is_latin() {
self.write_a_latin(font);
}
xml_end_tag(&mut self.writer, tag);
} else {
xml_empty_tag(&mut self.writer, tag, &attributes);
}
}
fn write_a_latin(&mut self, font: &ChartFont) {
let mut attributes = vec![];
if !font.name.is_empty() {
attributes.push(("typeface", font.name.clone()));
}
if font.pitch_family > 0 {
attributes.push(("pitchFamily", font.pitch_family.to_string()));
}
if font.character_set > 0 || font.pitch_family > 0 {
attributes.push(("charset", font.character_set.to_string()));
}
xml_empty_tag(&mut self.writer, "a:latin", &attributes);
}
fn write_a_t(&mut self, name: &str) {
xml_data_element_only(&mut self.writer, "a:t", name);
}
fn write_a_end_para_rpr(&mut self) {
let attributes = [("lang", "en-US")];
xml_empty_tag(&mut self.writer, "a:endParaRPr", &attributes);
}
fn write_sp_pr(&mut self, format: &ChartFormat) {
if !format.has_formatting() {
return;
}
xml_start_tag_only(&mut self.writer, "c:spPr");
if format.no_fill {
xml_empty_tag_only(&mut self.writer, "a:noFill");
} else if let Some(solid_fill) = &format.solid_fill {
self.write_a_solid_fill(solid_fill.color, solid_fill.transparency);
} else if let Some(pattern_fill) = &format.pattern_fill {
self.write_a_patt_fill(pattern_fill);
} else if let Some(gradient_fill) = &format.gradient_fill {
self.write_gradient_fill(gradient_fill);
}
if format.no_line {
self.write_a_ln_none();
} else if let Some(line) = &format.line {
self.write_a_ln(line);
}
xml_end_tag(&mut self.writer, "c:spPr");
}
fn write_a_ln(&mut self, line: &ChartLine) {
let mut attributes = vec![];
if let Some(width) = &line.width {
let width = ((*width + 0.125) * 4.0).floor() / 4.0;
let width = (12700.0 * width).ceil() as u32;
attributes.push(("w", width.to_string()));
}
if line.color != Color::Default || line.dash_type != ChartLineDashType::Solid || line.hidden
{
xml_start_tag(&mut self.writer, "a:ln", &attributes);
if line.hidden {
self.write_a_no_fill();
} else {
if line.color != Color::Default {
self.write_a_solid_fill(line.color, line.transparency);
}
if line.dash_type != ChartLineDashType::Solid {
self.write_a_prst_dash(line);
}
}
xml_end_tag(&mut self.writer, "a:ln");
} else {
xml_empty_tag(&mut self.writer, "a:ln", &attributes);
}
}
fn write_a_ln_none(&mut self) {
xml_start_tag_only(&mut self.writer, "a:ln");
self.write_a_no_fill();
xml_end_tag(&mut self.writer, "a:ln");
}
fn write_a_solid_fill(&mut self, color: Color, transparency: u8) {
xml_start_tag_only(&mut self.writer, "a:solidFill");
self.write_color(color, transparency);
xml_end_tag(&mut self.writer, "a:solidFill");
}
fn write_a_patt_fill(&mut self, fill: &ChartPatternFill) {
let attributes = [("prst", fill.pattern.to_string())];
xml_start_tag(&mut self.writer, "a:pattFill", &attributes);
if fill.foreground_color != Color::Default {
xml_start_tag_only(&mut self.writer, "a:fgClr");
self.write_color(fill.foreground_color, 0);
xml_end_tag(&mut self.writer, "a:fgClr");
}
if fill.background_color != Color::Default {
xml_start_tag_only(&mut self.writer, "a:bgClr");
self.write_color(fill.background_color, 0);
xml_end_tag(&mut self.writer, "a:bgClr");
} else if fill.background_color == Color::Default && fill.foreground_color != Color::Default
{
xml_start_tag_only(&mut self.writer, "a:bgClr");
self.write_color(Color::White, 0);
xml_end_tag(&mut self.writer, "a:bgClr");
}
xml_end_tag(&mut self.writer, "a:pattFill");
}
fn write_gradient_fill(&mut self, fill: &ChartGradientFill) {
let mut attributes = vec![];
if fill.gradient_type != ChartGradientFillType::Linear {
attributes.push(("flip", "none"));
attributes.push(("rotWithShape", "1"));
}
xml_start_tag(&mut self.writer, "a:gradFill", &attributes);
xml_start_tag_only(&mut self.writer, "a:gsLst");
for gradient_stop in &fill.gradient_stops {
self.write_gradient_stop(gradient_stop);
}
xml_end_tag(&mut self.writer, "a:gsLst");
if fill.gradient_type == ChartGradientFillType::Linear {
self.write_gradient_fill_angle(fill.angle);
} else {
self.write_gradient_path(fill.gradient_type);
}
xml_end_tag(&mut self.writer, "a:gradFill");
}
fn write_gradient_stop(&mut self, gradient_stop: &ChartGradientStop) {
let position = 1000 * u32::from(gradient_stop.position);
let attributes = [("pos", position.to_string())];
xml_start_tag(&mut self.writer, "a:gs", &attributes);
self.write_color(gradient_stop.color, 0);
xml_end_tag(&mut self.writer, "a:gs");
}
fn write_gradient_fill_angle(&mut self, angle: u16) {
let angle = 60_000 * u32::from(angle);
let attributes = [("ang", angle.to_string()), ("scaled", "0".to_string())];
xml_empty_tag(&mut self.writer, "a:lin", &attributes);
}
fn write_gradient_path(&mut self, gradient_type: ChartGradientFillType) {
let mut attributes = vec![];
match gradient_type {
ChartGradientFillType::Radial => attributes.push(("path", "circle")),
ChartGradientFillType::Rectangular => attributes.push(("path", "rect")),
ChartGradientFillType::Path => attributes.push(("path", "shape")),
ChartGradientFillType::Linear => {}
}
xml_start_tag(&mut self.writer, "a:path", &attributes);
self.write_a_fill_to_rect(gradient_type);
xml_end_tag(&mut self.writer, "a:path");
self.write_a_tile_rect(gradient_type);
}
fn write_a_fill_to_rect(&mut self, gradient_type: ChartGradientFillType) {
let mut attributes = vec![];
match gradient_type {
ChartGradientFillType::Path => {
attributes.push(("l", "50000"));
attributes.push(("t", "50000"));
attributes.push(("r", "50000"));
attributes.push(("b", "50000"));
}
_ => {
attributes.push(("l", "100000"));
attributes.push(("t", "100000"));
}
}
xml_empty_tag(&mut self.writer, "a:fillToRect", &attributes);
}
fn write_a_tile_rect(&mut self, gradient_type: ChartGradientFillType) {
let mut attributes = vec![];
match gradient_type {
ChartGradientFillType::Rectangular | ChartGradientFillType::Radial => {
attributes.push(("r", "-100000"));
attributes.push(("b", "-100000"));
}
_ => {}
}
xml_empty_tag(&mut self.writer, "a:tileRect", &attributes);
}
fn write_color(&mut self, color: Color, transparency: u8) {
match color {
Color::Theme(_, _) => {
let (scheme, lum_mod, lum_off) = color.chart_scheme();
if !scheme.is_empty() {
self.write_a_scheme_clr(scheme, lum_mod, lum_off, transparency);
}
}
Color::Automatic => {
let attributes = [("val", "window"), ("lastClr", "FFFFFF")];
xml_empty_tag(&mut self.writer, "a:sysClr", &attributes);
}
_ => {
let attributes = [("val", color.rgb_hex_value())];
if transparency > 0 {
xml_start_tag(&mut self.writer, "a:srgbClr", &attributes);
self.write_a_alpha(transparency);
xml_end_tag(&mut self.writer, "a:srgbClr");
} else {
xml_empty_tag(&mut self.writer, "a:srgbClr", &attributes);
}
}
}
}
fn write_a_scheme_clr(&mut self, scheme: String, lum_mod: u32, lum_off: u32, transparency: u8) {
let attributes = [("val", scheme)];
if lum_mod > 0 || lum_off > 0 || transparency > 0 {
xml_start_tag(&mut self.writer, "a:schemeClr", &attributes);
if lum_mod > 0 {
self.write_a_lum_mod(lum_mod);
}
if lum_off > 0 {
self.write_a_lum_off(lum_off);
}
if transparency > 0 {
self.write_a_alpha(transparency);
}
xml_end_tag(&mut self.writer, "a:schemeClr");
} else {
xml_empty_tag(&mut self.writer, "a:schemeClr", &attributes);
}
}
fn write_a_lum_mod(&mut self, lum_mod: u32) {
let attributes = [("val", lum_mod.to_string())];
xml_empty_tag(&mut self.writer, "a:lumMod", &attributes);
}
fn write_a_lum_off(&mut self, lum_off: u32) {
let attributes = [("val", lum_off.to_string())];
xml_empty_tag(&mut self.writer, "a:lumOff", &attributes);
}
fn write_a_alpha(&mut self, transparency: u8) {
let transparency = u32::from(100 - transparency) * 1000;
let attributes = [("val", transparency.to_string())];
xml_empty_tag(&mut self.writer, "a:alpha", &attributes);
}
fn write_a_no_fill(&mut self) {
xml_empty_tag_only(&mut self.writer, "a:noFill");
}
fn write_a_prst_dash(&mut self, line: &ChartLine) {
let attributes = [("val", line.dash_type.to_string())];
xml_empty_tag(&mut self.writer, "a:prstDash", &attributes);
}
fn write_radar_style(&mut self) {
let mut attributes = vec![];
if self.chart_type == ChartType::RadarFilled {
attributes.push(("val", "filled".to_string()));
} else {
attributes.push(("val", "marker".to_string()));
}
xml_empty_tag(&mut self.writer, "c:radarStyle", &attributes);
}
fn write_major_tick_mark(&mut self, position: ChartAxisTickType) {
let attributes = [("val", position.to_string())];
xml_empty_tag(&mut self.writer, "c:majorTickMark", &attributes);
}
fn write_minor_tick_mark(&mut self, tick_type: ChartAxisTickType) {
let attributes = [("val", tick_type.to_string())];
xml_empty_tag(&mut self.writer, "c:minorTickMark", &attributes);
}
fn write_gap_width(&mut self, gap: u16) {
let attributes = [("val", gap.to_string())];
xml_empty_tag(&mut self.writer, "c:gapWidth", &attributes);
}
fn write_overlap(&mut self) {
if let Some(overlap) = &self.overlap {
let attributes = [("val", overlap.to_string())];
xml_empty_tag(&mut self.writer, "c:overlap", &attributes);
}
}
fn write_smooth(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:smooth", &attributes);
}
fn write_style(&mut self) {
let attributes = [("val", self.style.to_string())];
xml_empty_tag(&mut self.writer, "c:style", &attributes);
}
fn write_auto_title_deleted(&mut self) {
let attributes = [("val", "1")];
xml_empty_tag(&mut self.writer, "c:autoTitleDeleted", &attributes);
}
fn write_title_formula(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "c:title");
self.write_tx_formula(title);
self.write_layout(&title.layout);
if title.has_overlay {
self.write_overlay();
}
self.write_sp_pr(&title.format.clone());
self.write_tx_pr(&title.font, title.is_horizontal);
xml_end_tag(&mut self.writer, "c:title");
}
fn write_tx_formula(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "c:tx");
self.write_str_ref(&title.range);
xml_end_tag(&mut self.writer, "c:tx");
}
fn write_title_rich(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "c:title");
self.write_tx_rich(title);
self.write_layout(&title.layout);
if title.has_overlay {
self.write_overlay();
}
if title.format.has_formatting() {
self.write_sp_pr(&title.format.clone());
}
xml_end_tag(&mut self.writer, "c:title");
}
fn write_title_format_only(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "c:title");
self.write_layout(&title.layout);
if title.has_overlay {
self.write_overlay();
}
self.write_sp_pr(&title.format.clone());
xml_end_tag(&mut self.writer, "c:title");
}
fn write_tx_rich(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "c:tx");
self.write_rich(title);
xml_end_tag(&mut self.writer, "c:tx");
}
fn write_tx_value(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "c:tx");
xml_data_element_only(&mut self.writer, "c:v", &title.name);
xml_end_tag(&mut self.writer, "c:tx");
}
fn write_rich(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "c:rich");
self.write_a_body_pr(&title.font, title.is_horizontal);
self.write_a_lst_style();
self.write_a_p_rich(title);
xml_end_tag(&mut self.writer, "c:rich");
}
fn write_a_p_rich(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "a:p");
if !title.ignore_rich_para {
self.write_a_p_pr_rich(&title.font);
}
self.write_a_r(title);
xml_end_tag(&mut self.writer, "a:p");
}
fn write_a_p_pr_rich(&mut self, font: &ChartFont) {
let mut attributes = vec![];
if let Some(right_to_left) = font.right_to_left {
attributes.push(("rtl", right_to_left.to_xml_bool()));
}
xml_start_tag(&mut self.writer, "a:pPr", &attributes);
self.write_a_def_rpr(font);
xml_end_tag(&mut self.writer, "a:pPr");
}
fn write_a_r(&mut self, title: &ChartTitle) {
xml_start_tag_only(&mut self.writer, "a:r");
self.write_a_r_pr(&title.font);
self.write_a_t(&title.name);
xml_end_tag(&mut self.writer, "a:r");
}
fn write_disp_blanks_as(&mut self) {
if let Some(show_empty_cells) = self.show_empty_cells_as {
let attributes = [("val", show_empty_cells.to_string())];
xml_empty_tag(&mut self.writer, "c:dispBlanksAs", &attributes);
}
}
fn write_disp_na_as_blank(&mut self) {
let attributes = [
("uri", "{56B9EC1D-385E-4148-901F-78D8002777C0}"),
(
"xmlns:c16r3",
"http://schemas.microsoft.com/office/drawing/2017/03/chart",
),
];
xml_start_tag_only(&mut self.writer, "c:extLst");
xml_start_tag(&mut self.writer, "c:ext", &attributes);
xml_start_tag_only(&mut self.writer, "c16r3:dataDisplayOptions16");
xml_empty_tag(&mut self.writer, "c16r3:dispNaAsBlank", &[("val", "1")]);
xml_end_tag(&mut self.writer, "c16r3:dataDisplayOptions16");
xml_end_tag(&mut self.writer, "c:ext");
xml_end_tag(&mut self.writer, "c:extLst");
}
fn write_protection(&mut self) {
xml_empty_tag_only(&mut self.writer, "c:protection");
}
}
pub trait IntoChartRange {
fn new_chart_range(&self) -> ChartRange;
}
impl IntoChartRange for &ChartRange {
fn new_chart_range(&self) -> ChartRange {
(*self).clone()
}
}
impl IntoChartRange for (&str, RowNum, ColNum, RowNum, ColNum) {
fn new_chart_range(&self) -> ChartRange {
ChartRange::new_from_range(self.0, self.1, self.2, self.3, self.4)
}
}
impl IntoChartRange for (&str, RowNum, ColNum) {
fn new_chart_range(&self) -> ChartRange {
ChartRange::new_from_range(self.0, self.1, self.2, self.1, self.2)
}
}
impl IntoChartRange for &str {
fn new_chart_range(&self) -> ChartRange {
ChartRange::new_from_string(self)
}
}
impl IntoChartRange for &String {
fn new_chart_range(&self) -> ChartRange {
ChartRange::new_from_string(self)
}
}
pub trait IntoChartFormat {
fn new_chart_format(&self) -> ChartFormat;
}
impl IntoChartFormat for &mut ChartFormat {
fn new_chart_format(&self) -> ChartFormat {
(*self).clone()
}
}
impl IntoChartFormat for &mut ChartLine {
fn new_chart_format(&self) -> ChartFormat {
ChartFormat::new().set_line(self).clone()
}
}
impl IntoChartFormat for &mut ChartSolidFill {
fn new_chart_format(&self) -> ChartFormat {
ChartFormat::new().set_solid_fill(self).clone()
}
}
impl IntoChartFormat for &mut ChartPatternFill {
fn new_chart_format(&self) -> ChartFormat {
ChartFormat::new().set_pattern_fill(self).clone()
}
}
impl IntoChartFormat for &mut ChartGradientFill {
fn new_chart_format(&self) -> ChartFormat {
ChartFormat::new().set_gradient_fill(self).clone()
}
}
impl DrawingObject for Chart {
fn x_offset(&self) -> u32 {
self.x_offset
}
fn y_offset(&self) -> u32 {
self.y_offset
}
fn width_scaled(&self) -> f64 {
self.width * self.scale_width
}
fn height_scaled(&self) -> f64 {
self.height * self.scale_height
}
fn object_movement(&self) -> ObjectMovement {
self.object_movement
}
fn name(&self) -> String {
self.name.clone()
}
fn alt_text(&self) -> String {
self.alt_text.clone()
}
fn decorative(&self) -> bool {
self.decorative
}
fn drawing_type(&self) -> DrawingType {
self.drawing_type
}
}
#[derive(Clone)]
pub struct ChartSeries {
pub(crate) value_range: ChartRange,
pub(crate) category_range: ChartRange,
pub(crate) title: ChartTitle,
pub(crate) format: ChartFormat,
pub(crate) marker: Option<ChartMarker>,
pub(crate) data_label: Option<ChartDataLabel>,
pub(crate) custom_data_labels: Vec<ChartDataLabel>,
pub(crate) points: Vec<ChartPoint>,
pub(crate) gap: u16,
pub(crate) overlap: Option<i8>,
pub(crate) invert_if_negative: bool,
pub(crate) inverted_color: Color,
pub(crate) trendline: ChartTrendline,
pub(crate) x_error_bars: Option<ChartErrorBars>,
pub(crate) y_error_bars: Option<ChartErrorBars>,
pub(crate) delete_from_legend: bool,
pub(crate) smooth: Option<bool>,
pub(crate) secondary_axis: bool,
}
#[allow(clippy::new_without_default)]
impl ChartSeries {
pub fn new() -> ChartSeries {
ChartSeries {
value_range: ChartRange::default(),
category_range: ChartRange::default(),
title: ChartTitle::new(),
format: ChartFormat::default(),
marker: None,
data_label: None,
points: vec![],
custom_data_labels: vec![],
gap: 150,
overlap: None,
invert_if_negative: false,
inverted_color: Color::Default,
trendline: ChartTrendline::new(),
x_error_bars: None,
y_error_bars: None,
delete_from_legend: false,
smooth: None,
secondary_axis: false,
}
}
pub fn set_values<T>(&mut self, range: T) -> &mut ChartSeries
where
T: IntoChartRange,
{
self.value_range = range.new_chart_range();
self
}
pub fn set_categories<T>(&mut self, range: T) -> &mut ChartSeries
where
T: IntoChartRange,
{
self.category_range = range.new_chart_range();
self
}
pub fn set_secondary_axis(&mut self, enable: bool) -> &mut ChartSeries {
self.secondary_axis = enable;
self
}
pub fn set_name<T>(&mut self, name: T) -> &mut ChartSeries
where
T: IntoChartRange,
{
self.title.set_name(name);
self
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartSeries
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
pub fn set_marker(&mut self, marker: &ChartMarker) -> &mut ChartSeries {
self.marker = Some(marker.clone());
self
}
pub fn set_data_label(&mut self, data_label: &ChartDataLabel) -> &mut ChartSeries {
self.data_label = Some(data_label.clone());
self
}
pub fn set_custom_data_labels(&mut self, data_labels: &[ChartDataLabel]) -> &mut ChartSeries {
if self.data_label.is_none() {
self.data_label = Some(ChartDataLabel::default());
}
self.custom_data_labels = data_labels.to_vec();
self
}
pub fn set_points(&mut self, points: &[ChartPoint]) -> &mut ChartSeries {
self.points = points.to_vec();
self
}
pub fn set_point_colors(&mut self, colors: &[impl Into<Color> + Copy]) -> &mut ChartSeries {
self.points = colors
.iter()
.map(|color| ChartPoint::new().set_format(ChartSolidFill::new().set_color(*color)))
.collect();
self
}
pub fn set_trendline(&mut self, trendline: &ChartTrendline) -> &mut ChartSeries {
self.trendline = trendline.clone();
self
}
pub fn set_y_error_bars(&mut self, error_bars: &ChartErrorBars) -> &mut ChartSeries {
self.y_error_bars = Some(error_bars.clone());
self
}
pub fn set_x_error_bars(&mut self, error_bars: &ChartErrorBars) -> &mut ChartSeries {
self.x_error_bars = Some(error_bars.clone());
self
}
pub fn set_overlap(&mut self, overlap: i8) -> &mut ChartSeries {
if (-100..=100).contains(&overlap) {
self.overlap = Some(overlap);
}
self
}
pub fn set_gap(&mut self, gap: u16) -> &mut ChartSeries {
if gap <= 500 {
self.gap = gap;
}
self
}
pub fn set_smooth(&mut self, enable: bool) -> &mut ChartSeries {
self.smooth = Some(enable);
self
}
pub fn set_invert_if_negative(&mut self) -> &mut ChartSeries {
self.invert_if_negative = true;
self
}
pub fn set_invert_if_negative_color(&mut self, color: impl Into<Color>) -> &mut ChartSeries {
let color = color.into();
if color.is_valid() {
self.invert_if_negative = true;
self.inverted_color = color;
}
self
}
pub fn delete_from_legend(&mut self, enable: bool) -> &mut ChartSeries {
self.delete_from_legend = enable;
self
}
}
#[derive(Clone, PartialEq)]
pub struct ChartRange {
sheet_name: String,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
range_string: String,
is_formula_string_only: bool,
pub(crate) cache: ChartRangeCacheData,
}
impl Default for ChartRange {
fn default() -> Self {
Self::new_from_range("", 0, 0, 0, 0)
}
}
impl ChartRange {
pub fn new_from_range(
sheet_name: &str,
first_row: RowNum,
first_col: ColNum,
last_row: RowNum,
last_col: ColNum,
) -> ChartRange {
ChartRange {
sheet_name: sheet_name.to_string(),
first_row,
first_col,
last_row,
last_col,
range_string: String::new(),
is_formula_string_only: false,
cache: ChartRangeCacheData::new(),
}
}
pub fn new_from_string(range_string: &str) -> ChartRange {
let mut sheet_name = "";
let mut first_row = 0;
let mut first_col = 0;
let mut last_row = 0;
let mut last_col = 0;
if range_string.starts_with('=')
&& (range_string.starts_with("=(") || range_string.contains('['))
{
let mut range_string = range_string;
if range_string.starts_with('=') {
range_string = &range_string[1..];
}
return ChartRange {
sheet_name: UNPARSED_SHEET_RANGE.to_string(),
first_row: 0,
first_col: 0,
last_row: 0,
last_col: 0,
range_string: range_string.to_string(),
is_formula_string_only: true,
cache: ChartRangeCacheData::new(),
};
}
if let Some(position) = range_string.find('!') {
let range = &range_string[position + 1..].replace('$', "");
if utility::is_valid_range(range) {
sheet_name = &range_string[..position];
match range.find(':') {
Some(position) => {
let first_cell = &range[..position];
let last_cell = &range[position + 1..];
let (first_col_string, first_row_string) =
utility::split_cell_reference(first_cell);
let (last_col_string, last_row_string) =
utility::split_cell_reference(last_cell);
first_row = first_row_string.parse::<u32>().unwrap_or_default();
first_row = first_row.saturating_sub(1);
last_row = last_row_string.parse::<u32>().unwrap_or_default();
last_row = last_row.saturating_sub(1);
first_col = utility::column_name_to_number(&first_col_string);
last_col = utility::column_name_to_number(&last_col_string);
}
None => {
let (first_col_string, first_row_string) =
utility::split_cell_reference(range);
first_row = first_row_string.parse::<u32>().unwrap_or_default();
first_row = first_row.saturating_sub(1);
first_col = utility::column_name_to_number(&first_col_string);
last_row = first_row;
last_col = first_col;
}
}
}
}
if sheet_name.starts_with('=') {
sheet_name = &sheet_name[1..];
}
if sheet_name.starts_with('\'') && sheet_name.ends_with('\'') {
sheet_name = &sheet_name[1..sheet_name.len() - 1];
}
ChartRange {
sheet_name: sheet_name.to_string(),
first_row,
first_col,
last_row,
last_col,
range_string: range_string.to_string(),
is_formula_string_only: false,
cache: ChartRangeCacheData::new(),
}
}
pub(crate) fn formula(&self) -> String {
utility::chart_range(
&self.sheet_name,
self.first_row,
self.first_col,
self.last_row,
self.last_col,
)
}
pub(crate) fn formula_abs(&self) -> String {
utility::chart_range_abs(
&self.sheet_name,
self.first_row,
self.first_col,
self.last_row,
self.last_col,
)
}
pub(crate) fn formula_string(&self) -> String {
if self.is_formula_string_only {
return self.range_string.clone();
}
self.formula_abs()
}
pub(crate) fn error_range(&self) -> String {
utility::chart_error_range(
&self.sheet_name,
self.first_row,
self.first_col,
self.last_row,
self.last_col,
)
}
pub(crate) fn key(&self) -> (String, RowNum, ColNum, RowNum, ColNum) {
(
self.sheet_name.clone(),
self.first_row,
self.first_col,
self.last_row,
self.last_col,
)
}
pub(crate) fn has_data(&self) -> bool {
self.is_formula_string_only || !self.sheet_name.is_empty()
}
pub(crate) fn number_of_points(&self) -> usize {
let row_range = (self.last_row - self.first_row + 1) as usize;
let col_range = (self.last_col - self.first_col + 1) as usize;
std::cmp::max(row_range, col_range)
}
pub(crate) fn number_of_range_points(&self) -> (usize, usize) {
let row_range = (self.last_row - self.first_row + 1) as usize;
let col_range = (self.last_col - self.first_col + 1) as usize;
(row_range, col_range)
}
pub(crate) fn set_baseline(&mut self, row_order: bool) {
if row_order {
self.last_row = self.first_row;
} else {
self.last_col = self.first_col;
}
}
pub(crate) fn increment(&mut self, row_order: bool) {
if row_order {
self.first_row += 1;
self.last_row = self.first_row;
} else {
self.first_col += 1;
self.last_col = self.first_col;
}
}
pub(crate) fn validate(&self) -> Result<(), XlsxError> {
let range = self.error_range();
if self.is_formula_string_only {
return Ok(());
}
let error_message = format!("Sheet name error for range: '{range}'");
utility::validate_sheetname(&self.sheet_name, &error_message)?;
if self.first_row > self.last_row {
return Err(XlsxError::ChartError(format!(
"Range '{range}' has a first row '{}' greater than the last row '{}'",
self.first_row, self.last_row
)));
}
if self.first_col > self.last_col {
return Err(XlsxError::ChartError(format!(
"Range '{range}' has a first column '{}' greater than the last column '{}'",
self.first_col, self.last_col
)));
}
if self.first_row >= ROW_MAX || self.last_row >= ROW_MAX {
return Err(XlsxError::ChartError(format!(
"Range '{range}' has a row '{}/{}' greater than Excel limit of 1048576",
self.first_row, self.last_row
)));
}
if self.first_col >= COL_MAX || self.last_col >= COL_MAX {
return Err(XlsxError::ChartError(format!(
"Range '{range}' has a column '{}/{}' greater than Excel limit of XFD/16384",
self.first_col, self.last_col
)));
}
Ok(())
}
pub(crate) fn is_1d(&self) -> bool {
self.last_row - self.first_row == 0 || self.last_col - self.first_col == 0
}
#[allow(dead_code)] pub(crate) fn set_cache(
&mut self,
data: &[&str],
cache_type: ChartRangeCacheDataType,
) -> &mut ChartRange {
self.cache = ChartRangeCacheData {
major_dim: data.len(),
minor_dim: 1,
cache_type,
data: data.iter().map(std::string::ToString::to_string).collect(),
};
self
}
}
#[derive(Clone, PartialEq)]
pub(crate) struct ChartRangeCacheData {
pub(crate) major_dim: usize,
pub(crate) minor_dim: usize,
pub(crate) cache_type: ChartRangeCacheDataType,
pub(crate) data: Vec<String>,
}
impl ChartRangeCacheData {
pub(crate) fn new() -> ChartRangeCacheData {
ChartRangeCacheData {
major_dim: 0,
minor_dim: 0,
cache_type: ChartRangeCacheDataType::None,
data: vec![],
}
}
pub(crate) fn has_data(&self) -> bool {
!self.data.is_empty()
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(crate) enum ChartRangeCacheDataType {
None,
String,
MultiLevelString,
Number,
Date,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartType {
Area,
AreaStacked,
AreaPercentStacked,
Bar,
BarStacked,
BarPercentStacked,
Column,
ColumnStacked,
ColumnPercentStacked,
Doughnut,
Line,
LineStacked,
LinePercentStacked,
Pie,
Radar,
RadarWithMarkers,
RadarFilled,
Scatter,
ScatterStraight,
ScatterStraightWithMarkers,
ScatterSmooth,
ScatterSmoothWithMarkers,
Stock,
}
#[derive(Clone, PartialEq)]
pub struct ChartTitle {
pub(crate) range: ChartRange,
pub(crate) format: ChartFormat,
pub(crate) font: ChartFont,
name: String,
hidden: bool,
is_horizontal: bool,
ignore_rich_para: bool,
layout: ChartLayout,
has_overlay: bool,
}
impl ChartTitle {
pub(crate) fn new() -> ChartTitle {
ChartTitle {
range: ChartRange::default(),
format: ChartFormat::default(),
font: ChartFont::default(),
name: String::new(),
hidden: false,
is_horizontal: false,
ignore_rich_para: false,
layout: ChartLayout::default(),
has_overlay: false,
}
}
pub fn set_name<T>(&mut self, name: T) -> &mut ChartTitle
where
T: IntoChartRange,
{
self.range = name.new_chart_range();
if !self.range.has_data() {
self.name.clone_from(&self.range.range_string);
}
self
}
pub fn set_hidden(&mut self) -> &mut ChartTitle {
self.hidden = true;
self
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartTitle
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
pub fn set_font(&mut self, font: &ChartFont) -> &mut ChartTitle {
let mut font = font.clone();
font.has_default_bold = true;
if font.italic || font.is_latin() {
font.has_baseline = true;
}
self.font = font;
self
}
pub fn set_layout(&mut self, layout: &ChartLayout) -> &mut ChartTitle {
self.layout = layout.clone();
self
}
pub fn set_overlay(&mut self, enable: bool) -> &mut ChartTitle {
self.has_overlay = enable;
self
}
}
#[derive(Clone)]
pub struct ChartMarker {
pub(crate) automatic: bool,
pub(crate) none: bool,
pub(crate) size: u8,
pub(crate) marker_type: Option<ChartMarkerType>,
pub(crate) format: ChartFormat,
}
#[allow(clippy::new_without_default)]
impl ChartMarker {
pub fn new() -> ChartMarker {
ChartMarker {
automatic: false,
none: false,
marker_type: None,
size: 0,
format: ChartFormat::default(),
}
}
pub fn set_automatic(&mut self) -> &mut ChartMarker {
self.automatic = true;
self
}
pub fn set_none(&mut self) -> &mut ChartMarker {
self.none = true;
self
}
pub fn set_type(&mut self, marker_type: ChartMarkerType) -> &mut ChartMarker {
self.marker_type = Some(marker_type);
self.automatic = false;
self
}
pub fn set_size(&mut self, size: u8) -> &mut ChartMarker {
if (2..=72).contains(&size) {
self.size = size;
self.automatic = false;
}
self
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartMarker
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartMarkerType {
Square,
Diamond,
Triangle,
X,
Star,
ShortDash,
LongDash,
Circle,
PlusSign,
}
impl fmt::Display for ChartMarkerType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::X => write!(f, "x"),
Self::Star => write!(f, "star"),
Self::Circle => write!(f, "circle"),
Self::Square => write!(f, "square"),
Self::Diamond => write!(f, "diamond"),
Self::LongDash => write!(f, "dash"),
Self::PlusSign => write!(f, "plus"),
Self::Triangle => write!(f, "triangle"),
Self::ShortDash => write!(f, "dot"),
}
}
}
#[derive(Clone, PartialEq)]
pub struct ChartDataLabel {
pub(crate) format: ChartFormat,
pub(crate) show_value: bool,
pub(crate) show_category_name: bool,
pub(crate) show_series_name: bool,
pub(crate) show_leader_lines: bool,
pub(crate) show_legend_key: bool,
pub(crate) show_percentage: bool,
pub(crate) position: ChartDataLabelPosition,
pub(crate) separator: char,
pub(crate) title: ChartTitle,
pub(crate) is_hidden: bool,
pub(crate) is_custom: bool,
pub(crate) font: Option<ChartFont>,
pub(crate) num_format: String,
}
impl Default for ChartDataLabel {
fn default() -> Self {
Self::new()
}
}
impl ChartDataLabel {
pub fn new() -> ChartDataLabel {
ChartDataLabel {
format: ChartFormat::default(),
show_value: false,
show_category_name: false,
show_series_name: false,
show_leader_lines: false,
show_legend_key: false,
show_percentage: false,
position: ChartDataLabelPosition::Default,
separator: ',',
title: ChartTitle::new(),
is_hidden: false,
is_custom: false,
font: None,
num_format: String::new(),
}
}
pub fn show_value(&mut self) -> &mut ChartDataLabel {
self.show_value = true;
self
}
pub fn show_category_name(&mut self) -> &mut ChartDataLabel {
self.show_category_name = true;
self
}
pub fn show_series_name(&mut self) -> &mut ChartDataLabel {
self.show_series_name = true;
self
}
pub fn show_leader_lines(&mut self) -> &mut ChartDataLabel {
self.show_leader_lines = true;
self
}
pub fn show_legend_key(&mut self) -> &mut ChartDataLabel {
self.show_legend_key = true;
self
}
pub fn show_percentage(&mut self) -> &mut ChartDataLabel {
self.show_percentage = true;
self
}
pub fn set_position(&mut self, position: ChartDataLabelPosition) -> &mut ChartDataLabel {
self.position = position;
self
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartDataLabel
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self.title.ignore_rich_para = false;
self
}
pub fn set_font(&mut self, font: &ChartFont) -> &mut ChartDataLabel {
let mut font = font.clone();
if font.italic {
font.has_baseline = true;
}
self.font = Some(font);
self
}
pub fn set_num_format(&mut self, num_format: impl Into<String>) -> &mut ChartDataLabel {
self.num_format = num_format.into();
self
}
pub fn set_separator(&mut self, separator: char) -> &mut ChartDataLabel {
if ";. \n".contains(separator) {
self.separator = separator;
}
self
}
pub fn show_y_value(&mut self) -> &mut ChartDataLabel {
self.show_value()
}
pub fn show_x_value(&mut self) -> &mut ChartDataLabel {
self.show_category_name()
}
pub fn set_value<T>(&mut self, value: T) -> &mut ChartDataLabel
where
T: IntoChartRange,
{
self.title.set_name(value);
self.title.ignore_rich_para = true;
self.show_value = true;
self
}
pub fn set_hidden(&mut self) -> &mut ChartDataLabel {
self.is_hidden = true;
self
}
pub fn to_custom(&mut self) -> ChartDataLabel {
self.clone()
}
pub(crate) fn is_default(&self) -> bool {
static DEFAULT_STATE: OnceLock<ChartDataLabel> = OnceLock::new();
let default_state = DEFAULT_STATE.get_or_init(ChartDataLabel::default);
self == default_state
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartDataLabelPosition {
Default,
Center,
Right,
Left,
Above,
Below,
InsideBase,
InsideEnd,
OutsideEnd,
BestFit,
}
impl fmt::Display for ChartDataLabelPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Left => write!(f, "l"),
Self::Right => write!(f, "r"),
Self::Above => write!(f, "t"),
Self::Below => write!(f, "b"),
Self::Center => write!(f, "ctr"),
Self::Default => write!(f, ""),
Self::BestFit => write!(f, "bestFit"),
Self::InsideEnd => write!(f, "inEnd"),
Self::InsideBase => write!(f, "inBase"),
Self::OutsideEnd => write!(f, "outEnd"),
}
}
}
#[derive(Clone)]
pub struct ChartPoint {
pub(crate) format: ChartFormat,
}
impl Default for ChartPoint {
fn default() -> Self {
Self::new()
}
}
impl ChartPoint {
pub fn new() -> ChartPoint {
ChartPoint {
format: ChartFormat::default(),
}
}
pub fn set_format<T>(mut self, format: T) -> ChartPoint
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
pub(crate) fn is_not_default(&self) -> bool {
self.format.has_formatting()
}
}
#[derive(Clone)]
pub struct ChartAxis {
axis_type: ChartAxisType,
axis_position: ChartAxisPosition,
label_position: ChartAxisLabelPosition,
pub(crate) title: ChartTitle,
pub(crate) format: ChartFormat,
pub(crate) font: Option<ChartFont>,
pub(crate) num_format: String,
pub(crate) num_format_linked_to_source: bool,
pub(crate) reverse: bool,
pub(crate) is_hidden: bool,
pub(crate) automatic: bool,
pub(crate) position_between_ticks: bool,
pub(crate) max: String,
pub(crate) min: String,
pub(crate) major_unit: String,
pub(crate) minor_unit: String,
pub(crate) major_gridlines: bool,
pub(crate) minor_gridlines: bool,
pub(crate) major_gridlines_line: Option<ChartLine>,
pub(crate) minor_gridlines_line: Option<ChartLine>,
pub(crate) log_base: u16,
pub(crate) label_interval: u16,
pub(crate) tick_interval: u16,
pub(crate) major_tick_type: Option<ChartAxisTickType>,
pub(crate) minor_tick_type: Option<ChartAxisTickType>,
pub(crate) major_unit_date_type: Option<ChartAxisDateUnitType>,
pub(crate) minor_unit_date_type: Option<ChartAxisDateUnitType>,
pub(crate) display_units_type: ChartAxisDisplayUnitType,
pub(crate) display_units_visible: bool,
pub(crate) crossing: ChartAxisCrossing,
pub(crate) label_alignment: ChartAxisLabelAlignment,
}
impl ChartAxis {
pub(crate) fn new() -> ChartAxis {
ChartAxis {
axis_type: ChartAxisType::Value,
axis_position: ChartAxisPosition::Bottom,
label_position: ChartAxisLabelPosition::NextTo,
title: ChartTitle::new(),
format: ChartFormat::default(),
font: None,
num_format: String::new(),
num_format_linked_to_source: false,
reverse: false,
is_hidden: false,
automatic: false,
position_between_ticks: true,
max: String::new(),
min: String::new(),
major_unit: String::new(),
minor_unit: String::new(),
major_gridlines: false,
minor_gridlines: false,
major_gridlines_line: None,
minor_gridlines_line: None,
log_base: 0,
label_interval: 0,
tick_interval: 0,
major_tick_type: None,
minor_tick_type: None,
major_unit_date_type: None,
minor_unit_date_type: None,
display_units_type: ChartAxisDisplayUnitType::None,
display_units_visible: false,
crossing: ChartAxisCrossing::Automatic,
label_alignment: ChartAxisLabelAlignment::Center,
}
}
pub fn set_name<T>(&mut self, name: T) -> &mut ChartAxis
where
T: IntoChartRange,
{
self.title.set_name(name);
self
}
pub fn set_name_font(&mut self, font: &ChartFont) -> &mut ChartAxis {
self.title.set_font(font);
self
}
pub fn set_name_format<T>(&mut self, format: T) -> &mut ChartAxis
where
T: IntoChartFormat,
{
self.title.set_format(format);
self
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartAxis
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
pub fn set_font(&mut self, font: &ChartFont) -> &mut ChartAxis {
let mut font = font.clone();
if font.italic || font.is_latin() {
font.has_baseline = true;
}
if font.italic && font.bold.is_none() {
font.bold = Some(false);
}
self.font = Some(font);
self
}
pub fn set_num_format(&mut self, num_format: impl Into<String>) -> &mut ChartAxis {
self.num_format = num_format.into();
self
}
pub fn set_date_axis(&mut self, enable: bool) -> &mut ChartAxis {
if enable {
self.axis_type = ChartAxisType::Date;
} else {
self.axis_type = ChartAxisType::Category;
}
self.automatic = !enable;
self
}
pub fn set_text_axis(&mut self, enable: bool) -> &mut ChartAxis {
self.set_automatic_axis(enable);
self
}
pub fn set_automatic_axis(&mut self, enable: bool) -> &mut ChartAxis {
self.automatic = enable;
self
}
pub fn set_crossing(&mut self, crossing: ChartAxisCrossing) -> &mut ChartAxis {
self.crossing = crossing;
self
}
pub fn set_reverse(&mut self) -> &mut ChartAxis {
self.reverse = true;
self
}
pub fn set_max<T>(&mut self, max: T) -> &mut ChartAxis
where
T: Into<f64>,
{
self.max = max.into().to_string();
self
}
pub fn set_min<T>(&mut self, min: T) -> &mut ChartAxis
where
T: Into<f64>,
{
self.min = min.into().to_string();
self
}
pub fn set_max_date(&mut self, datetime: impl IntoExcelDateTime) -> &mut ChartAxis {
self.max = datetime.to_excel_serial_date().to_string();
self
}
pub fn set_min_date(&mut self, datetime: impl IntoExcelDateTime) -> &mut ChartAxis {
self.min = datetime.to_excel_serial_date().to_string();
self
}
pub fn set_major_unit<T>(&mut self, value: T) -> &mut ChartAxis
where
T: Into<f64>,
{
let value = value.into();
if value < 0.0 {
eprintln!("Chart axis major unit '{value}' must be >= 0.0 in Excel");
return self;
}
self.major_unit = value.to_string();
self
}
pub fn set_minor_unit<T>(&mut self, value: T) -> &mut ChartAxis
where
T: Into<f64>,
{
let value = value.into();
if value < 0.0 {
eprintln!("Chart axis minor unit '{value}' must be >= 0.0 in Excel");
return self;
}
self.minor_unit = value.to_string();
self
}
pub fn set_display_unit_type(&mut self, unit_type: ChartAxisDisplayUnitType) -> &mut ChartAxis {
self.display_units_type = unit_type;
self.display_units_visible = true;
self
}
pub fn set_display_units_visible(&mut self, enable: bool) -> &mut ChartAxis {
self.display_units_visible = enable;
self
}
pub fn set_major_unit_date_type(&mut self, unit_type: ChartAxisDateUnitType) -> &mut ChartAxis {
self.major_unit_date_type = Some(unit_type);
self
}
pub fn set_minor_unit_date_type(&mut self, unit_type: ChartAxisDateUnitType) -> &mut ChartAxis {
self.minor_unit_date_type = Some(unit_type);
self
}
pub fn set_label_alignment(&mut self, alignment: ChartAxisLabelAlignment) -> &mut ChartAxis {
self.label_alignment = alignment;
self
}
pub fn set_major_gridlines(&mut self, enable: bool) -> &mut ChartAxis {
self.major_gridlines = enable;
self
}
pub fn set_minor_gridlines(&mut self, enable: bool) -> &mut ChartAxis {
self.minor_gridlines = enable;
self
}
pub fn set_major_gridlines_line(&mut self, line: &ChartLine) -> &mut ChartAxis {
self.major_gridlines_line = Some(line.clone());
self.major_gridlines = true;
self
}
pub fn set_minor_gridlines_line(&mut self, line: &ChartLine) -> &mut ChartAxis {
self.minor_gridlines_line = Some(line.clone());
self.minor_gridlines = true;
self
}
pub fn set_label_position(&mut self, position: ChartAxisLabelPosition) -> &mut ChartAxis {
self.label_position = position;
self
}
pub fn set_position_between_ticks(&mut self, enable: bool) -> &mut ChartAxis {
self.position_between_ticks = enable;
self
}
pub fn set_label_interval(&mut self, interval: u16) -> &mut ChartAxis {
self.label_interval = interval;
self
}
pub fn set_tick_interval(&mut self, interval: u16) -> &mut ChartAxis {
self.tick_interval = interval;
self
}
pub fn set_major_tick_type(&mut self, tick_type: ChartAxisTickType) -> &mut ChartAxis {
self.major_tick_type = Some(tick_type);
self
}
pub fn set_minor_tick_type(&mut self, tick_type: ChartAxisTickType) -> &mut ChartAxis {
self.minor_tick_type = Some(tick_type);
self
}
pub fn set_log_base(&mut self, base: u16) -> &mut ChartAxis {
if base >= 2 {
self.log_base = base;
}
self
}
pub fn set_hidden(&mut self, enable: bool) -> &mut ChartAxis {
self.is_hidden = enable;
self
}
pub fn set_label_layout(&mut self, layout: &ChartLayout) -> &mut ChartAxis {
self.title.layout = layout.clone();
self
}
}
#[derive(Clone, PartialEq)]
pub(crate) enum ChartAxisType {
Category,
Value,
Date,
}
#[derive(Clone, Copy)]
pub(crate) enum ChartAxisPosition {
Top,
Bottom,
Left,
Right,
}
impl ChartAxisPosition {
pub(crate) fn reverse(self) -> ChartAxisPosition {
match self {
Self::Top => Self::Bottom,
Self::Left => Self::Right,
Self::Right => Self::Left,
Self::Bottom => Self::Top,
}
}
}
impl fmt::Display for ChartAxisPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Top => write!(f, "t"),
Self::Left => write!(f, "l"),
Self::Right => write!(f, "r"),
Self::Bottom => write!(f, "b"),
}
}
}
#[derive(Clone, Copy)]
pub enum ChartAxisLabelPosition {
NextTo,
High,
Low,
None,
}
impl fmt::Display for ChartAxisLabelPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Low => write!(f, "low"),
Self::High => write!(f, "high"),
Self::None => write!(f, "none"),
Self::NextTo => write!(f, "nextTo"),
}
}
}
#[derive(Clone, Copy)]
pub enum ChartAxisTickType {
None,
Inside,
Outside,
Cross,
}
impl fmt::Display for ChartAxisTickType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Cross => write!(f, "cross"),
Self::Inside => write!(f, "in"),
Self::Outside => write!(f, "out"),
}
}
}
#[derive(Clone, Copy)]
pub enum ChartAxisDateUnitType {
Days,
Months,
Years,
}
impl fmt::Display for ChartAxisDateUnitType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Days => write!(f, "days"),
Self::Years => write!(f, "years"),
Self::Months => write!(f, "months"),
}
}
}
#[derive(Clone, Copy)]
pub(crate) enum ChartGrouping {
Stacked,
Standard,
Clustered,
PercentStacked,
}
impl fmt::Display for ChartGrouping {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Stacked => write!(f, "stacked"),
Self::Standard => write!(f, "standard"),
Self::Clustered => write!(f, "clustered"),
Self::PercentStacked => write!(f, "percentStacked"),
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum ChartAxisDisplayUnitType {
None,
Hundreds,
Thousands,
TenThousands,
HundredThousands,
Millions,
TenMillions,
HundredMillions,
Billions,
Trillions,
}
impl fmt::Display for ChartAxisDisplayUnitType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Hundreds => write!(f, "hundreds"),
Self::Thousands => write!(f, "thousands"),
Self::TenThousands => write!(f, "tenThousands"),
Self::HundredThousands => write!(f, "hundredThousands"),
Self::Millions => write!(f, "millions"),
Self::TenMillions => write!(f, "tenMillions"),
Self::HundredMillions => write!(f, "hundredMillions"),
Self::Billions => write!(f, "billions"),
Self::Trillions => write!(f, "trillions"),
}
}
}
#[derive(Clone)]
pub struct ChartLegend {
position: ChartLegendPosition,
hidden: bool,
has_overlay: bool,
pub(crate) format: ChartFormat,
pub(crate) font: Option<ChartFont>,
deleted_entries: Vec<usize>,
layout: ChartLayout,
}
impl ChartLegend {
pub(crate) fn new() -> ChartLegend {
ChartLegend {
position: ChartLegendPosition::Right,
hidden: false,
has_overlay: false,
format: ChartFormat::default(),
font: None,
deleted_entries: vec![],
layout: ChartLayout::default(),
}
}
pub fn set_hidden(&mut self) -> &mut ChartLegend {
self.hidden = true;
self
}
pub fn set_position(&mut self, position: ChartLegendPosition) -> &mut ChartLegend {
self.position = position;
self
}
pub fn set_overlay(&mut self, enable: bool) -> &mut ChartLegend {
self.has_overlay = enable;
self
}
pub fn set_layout(&mut self, layout: &ChartLayout) -> &mut ChartLegend {
let mut layout = layout.clone();
layout.has_dimensions = true;
self.layout = layout;
self
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartLegend
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
pub fn set_font(&mut self, font: &ChartFont) -> &mut ChartLegend {
self.font = Some(font.clone());
self
}
pub fn delete_entries(&mut self, entries: &[usize]) -> &mut ChartLegend {
self.deleted_entries = entries.to_vec();
self
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartLegendPosition {
Right,
Left,
Top,
Bottom,
TopRight,
}
impl fmt::Display for ChartLegendPosition {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Top => write!(f, "t"),
Self::Left => write!(f, "l"),
Self::Right => write!(f, "r"),
Self::Bottom => write!(f, "b"),
Self::TopRight => write!(f, "tr"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartEmptyCells {
Gaps,
Zero,
Connected,
}
impl fmt::Display for ChartEmptyCells {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Gaps => write!(f, "gap"),
Self::Zero => write!(f, "zero"),
Self::Connected => write!(f, "span"),
}
}
}
#[derive(Clone, PartialEq)]
pub struct ChartFormat {
no_fill: bool,
no_line: bool,
line: Option<ChartLine>,
solid_fill: Option<ChartSolidFill>,
pattern_fill: Option<ChartPatternFill>,
gradient_fill: Option<ChartGradientFill>,
}
impl Default for ChartFormat {
fn default() -> Self {
Self::new()
}
}
impl ChartFormat {
pub fn new() -> ChartFormat {
ChartFormat {
no_fill: false,
no_line: false,
line: None,
solid_fill: None,
pattern_fill: None,
gradient_fill: None,
}
}
pub fn set_line(&mut self, line: &ChartLine) -> &mut ChartFormat {
self.line = Some(line.clone());
self
}
pub fn set_border(&mut self, line: &ChartLine) -> &mut ChartFormat {
self.set_line(line)
}
pub fn set_no_line(&mut self) -> &mut ChartFormat {
self.no_line = true;
self
}
pub fn set_no_border(&mut self) -> &mut ChartFormat {
self.set_no_line()
}
pub fn set_no_fill(&mut self) -> &mut ChartFormat {
self.no_fill = true;
self
}
pub fn set_solid_fill(&mut self, fill: &ChartSolidFill) -> &mut ChartFormat {
self.solid_fill = Some(fill.clone());
self
}
pub fn set_pattern_fill(&mut self, fill: &ChartPatternFill) -> &mut ChartFormat {
self.pattern_fill = Some(fill.clone());
self
}
pub fn set_gradient_fill(&mut self, fill: &ChartGradientFill) -> &mut ChartFormat {
self.gradient_fill = Some(fill.clone());
self
}
fn has_formatting(&self) -> bool {
self.line.is_some()
|| self.solid_fill.is_some()
|| self.pattern_fill.is_some()
|| self.gradient_fill.is_some()
|| self.no_fill
|| self.no_line
}
}
#[derive(Clone, PartialEq)]
pub struct ChartLine {
color: Color,
width: Option<f64>,
transparency: u8,
dash_type: ChartLineDashType,
hidden: bool,
}
impl ChartLine {
#[allow(clippy::new_without_default)]
pub fn new() -> ChartLine {
ChartLine {
color: Color::Default,
width: None,
transparency: 0,
dash_type: ChartLineDashType::Solid,
hidden: false,
}
}
pub fn set_color(&mut self, color: impl Into<Color>) -> &mut ChartLine {
let color = color.into();
if color.is_valid() {
self.color = color;
}
self
}
pub fn set_width<T>(&mut self, width: T) -> &mut ChartLine
where
T: Into<f64>,
{
let width = width.into();
if width <= 1584.0 {
self.width = Some(width);
}
self
}
pub fn set_dash_type(&mut self, dash_type: ChartLineDashType) -> &mut ChartLine {
self.dash_type = dash_type;
self
}
pub fn set_transparency(&mut self, transparency: u8) -> &mut ChartLine {
if transparency <= 100 {
self.transparency = transparency;
}
self
}
pub fn set_hidden(&mut self, enable: bool) -> &mut ChartLine {
self.hidden = enable;
self
}
}
pub type ChartBorder = ChartLine;
#[derive(Clone, PartialEq)]
pub struct ChartSolidFill {
color: Color,
transparency: u8,
}
impl ChartSolidFill {
#[allow(clippy::new_without_default)]
pub fn new() -> ChartSolidFill {
ChartSolidFill {
color: Color::Default,
transparency: 0,
}
}
pub fn set_color(&mut self, color: impl Into<Color>) -> &mut ChartSolidFill {
let color = color.into();
if color.is_valid() {
self.color = color;
}
self
}
pub fn set_transparency(&mut self, transparency: u8) -> &mut ChartSolidFill {
if transparency <= 100 {
self.transparency = transparency;
}
self
}
}
#[derive(Clone, PartialEq)]
pub struct ChartPatternFill {
background_color: Color,
foreground_color: Color,
pattern: ChartPatternFillType,
}
impl ChartPatternFill {
#[allow(clippy::new_without_default)]
pub fn new() -> ChartPatternFill {
ChartPatternFill {
background_color: Color::Default,
foreground_color: Color::Default,
pattern: ChartPatternFillType::Dotted5Percent,
}
}
pub fn set_pattern(&mut self, pattern: ChartPatternFillType) -> &mut ChartPatternFill {
self.pattern = pattern;
self
}
pub fn set_background_color(&mut self, color: impl Into<Color>) -> &mut ChartPatternFill {
let color = color.into();
if color.is_valid() {
self.background_color = color;
}
self
}
pub fn set_foreground_color(&mut self, color: impl Into<Color>) -> &mut ChartPatternFill {
let color = color.into();
if color.is_valid() {
self.foreground_color = color;
}
self
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartLineDashType {
Solid,
RoundDot,
SquareDot,
Dash,
DashDot,
LongDash,
LongDashDot,
LongDashDotDot,
}
impl fmt::Display for ChartLineDashType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Dash => write!(f, "dash"),
Self::Solid => write!(f, "solid"),
Self::DashDot => write!(f, "dashDot"),
Self::LongDash => write!(f, "lgDash"),
Self::RoundDot => write!(f, "sysDot"),
Self::SquareDot => write!(f, "sysDash"),
Self::LongDashDot => write!(f, "lgDashDot"),
Self::LongDashDotDot => write!(f, "lgDashDotDot"),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartPatternFillType {
Dotted5Percent,
Dotted10Percent,
Dotted20Percent,
Dotted25Percent,
Dotted30Percent,
Dotted40Percent,
Dotted50Percent,
Dotted60Percent,
Dotted70Percent,
Dotted75Percent,
Dotted80Percent,
Dotted90Percent,
DiagonalStripesLightDownwards,
DiagonalStripesLightUpwards,
DiagonalStripesDarkDownwards,
DiagonalStripesDarkUpwards,
DiagonalStripesWideDownwards,
DiagonalStripesWideUpwards,
VerticalStripesLight,
HorizontalStripesLight,
VerticalStripesNarrow,
HorizontalStripesNarrow,
VerticalStripesDark,
HorizontalStripesDark,
StripesBackslashes,
StripesForwardSlashes,
HorizontalStripesAlternating,
VerticalStripesAlternating,
SmallConfetti,
LargeConfetti,
Zigzag,
Wave,
DiagonalBrick,
HorizontalBrick,
Weave,
Plaid,
Divot,
DottedGrid,
DottedDiamond,
Shingle,
Trellis,
Sphere,
SmallGrid,
LargeGrid,
SmallCheckerboard,
LargeCheckerboard,
OutlinedDiamondGrid,
SolidDiamondGrid,
}
impl fmt::Display for ChartPatternFillType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Wave => write!(f, "wave"),
Self::Weave => write!(f, "weave"),
Self::Plaid => write!(f, "plaid"),
Self::Divot => write!(f, "divot"),
Self::Zigzag => write!(f, "zigZag"),
Self::Sphere => write!(f, "sphere"),
Self::Shingle => write!(f, "shingle"),
Self::Trellis => write!(f, "trellis"),
Self::SmallGrid => write!(f, "smGrid"),
Self::LargeGrid => write!(f, "lgGrid"),
Self::DottedGrid => write!(f, "dotGrid"),
Self::DottedDiamond => write!(f, "dotDmnd"),
Self::DiagonalBrick => write!(f, "diagBrick"),
Self::LargeConfetti => write!(f, "lgConfetti"),
Self::SmallConfetti => write!(f, "smConfetti"),
Self::Dotted5Percent => write!(f, "pct5"),
Self::Dotted10Percent => write!(f, "pct10"),
Self::Dotted20Percent => write!(f, "pct20"),
Self::Dotted25Percent => write!(f, "pct25"),
Self::Dotted30Percent => write!(f, "pct30"),
Self::Dotted40Percent => write!(f, "pct40"),
Self::Dotted50Percent => write!(f, "pct50"),
Self::Dotted60Percent => write!(f, "pct60"),
Self::Dotted70Percent => write!(f, "pct70"),
Self::Dotted75Percent => write!(f, "pct75"),
Self::Dotted80Percent => write!(f, "pct80"),
Self::Dotted90Percent => write!(f, "pct90"),
Self::HorizontalBrick => write!(f, "horzBrick"),
Self::SolidDiamondGrid => write!(f, "solidDmnd"),
Self::SmallCheckerboard => write!(f, "smCheck"),
Self::LargeCheckerboard => write!(f, "lgCheck"),
Self::StripesBackslashes => write!(f, "dashDnDiag"),
Self::VerticalStripesDark => write!(f, "dkVert"),
Self::OutlinedDiamondGrid => write!(f, "openDmnd"),
Self::VerticalStripesLight => write!(f, "ltVert"),
Self::HorizontalStripesDark => write!(f, "dkHorz"),
Self::StripesForwardSlashes => write!(f, "dashUpDiag"),
Self::VerticalStripesNarrow => write!(f, "narVert"),
Self::HorizontalStripesLight => write!(f, "ltHorz"),
Self::HorizontalStripesNarrow => write!(f, "narHorz"),
Self::DiagonalStripesDarkUpwards => write!(f, "dkUpDiag"),
Self::DiagonalStripesWideUpwards => write!(f, "wdUpDiag"),
Self::VerticalStripesAlternating => write!(f, "dashVert"),
Self::DiagonalStripesLightUpwards => write!(f, "ltUpDiag"),
Self::DiagonalStripesDarkDownwards => write!(f, "dkDnDiag"),
Self::DiagonalStripesWideDownwards => write!(f, "wdDnDiag"),
Self::HorizontalStripesAlternating => write!(f, "dashHorz"),
Self::DiagonalStripesLightDownwards => write!(f, "ltDnDiag"),
}
}
}
#[derive(Clone, PartialEq)]
pub struct ChartFont {
pub(crate) bold: Option<bool>,
pub(crate) has_default_bold: bool,
pub(crate) italic: bool,
pub(crate) underline: bool,
pub(crate) name: String,
pub(crate) size: f64,
pub(crate) color: Color,
pub(crate) strikethrough: bool,
pub(crate) pitch_family: u8,
pub(crate) character_set: u8,
pub(crate) rotation: Option<i16>,
pub(crate) has_baseline: bool,
pub(crate) right_to_left: Option<bool>,
}
impl Default for ChartFont {
fn default() -> Self {
Self::new()
}
}
impl ChartFont {
pub fn new() -> ChartFont {
ChartFont {
bold: None,
italic: false,
underline: false,
name: String::new(),
size: 0.0,
color: Color::Default,
strikethrough: false,
pitch_family: 0,
character_set: 0,
rotation: None,
has_baseline: false,
has_default_bold: false,
right_to_left: None,
}
}
pub fn set_bold(&mut self) -> &mut ChartFont {
self.bold = Some(true);
self
}
pub fn set_italic(&mut self) -> &mut ChartFont {
self.italic = true;
self
}
pub fn set_color(&mut self, color: impl Into<Color>) -> &mut ChartFont {
let color = color.into();
if color.is_valid() {
self.color = color;
}
self
}
pub fn set_name(&mut self, font_name: impl Into<String>) -> &mut ChartFont {
self.name = font_name.into();
self
}
pub fn set_size<T>(&mut self, font_size: T) -> &mut ChartFont
where
T: Into<f64>,
{
self.size = font_size.into() * 100.0;
self
}
pub fn set_rotation(&mut self, rotation: i16) -> &mut ChartFont {
match rotation {
270..=271 | -90..=90 => self.rotation = Some(rotation),
_ => eprintln!("Rotation '{rotation}' outside range: -90 <= angle <= 90."),
}
self
}
pub fn set_underline(&mut self) -> &mut ChartFont {
self.underline = true;
self
}
pub fn set_strikethrough(&mut self) -> &mut ChartFont {
self.strikethrough = true;
self
}
pub fn unset_bold(&mut self) -> &mut ChartFont {
self.bold = Some(false);
self
}
pub fn set_right_to_left(&mut self, enable: bool) -> &mut ChartFont {
self.right_to_left = Some(enable);
self
}
pub fn set_pitch_family(&mut self, family: u8) -> &mut ChartFont {
self.pitch_family = family;
self
}
pub fn set_character_set(&mut self, character_set: u8) -> &mut ChartFont {
self.character_set = character_set;
self
}
#[doc(hidden)]
pub fn set_default_bold(&mut self, enable: bool) -> &mut ChartFont {
self.has_default_bold = enable;
self
}
pub(crate) fn is_latin(&self) -> bool {
!self.name.is_empty() || self.pitch_family > 0 || self.character_set > 0
}
}
#[derive(Clone)]
pub struct ChartTrendline {
name: String,
trend_type: ChartTrendlineType,
format: ChartFormat,
label_format: ChartFormat,
label_font: Option<ChartFont>,
forward_period: f64,
backward_period: f64,
display_equation: bool,
display_r_squared: bool,
intercept: Option<f64>,
delete_from_legend: bool,
}
impl ChartTrendline {
#[allow(clippy::new_without_default)]
pub fn new() -> ChartTrendline {
ChartTrendline {
name: String::new(),
trend_type: ChartTrendlineType::None,
format: ChartFormat::default(),
label_format: ChartFormat::default(),
label_font: None,
forward_period: 0.0,
backward_period: 0.0,
display_r_squared: false,
display_equation: false,
intercept: None,
delete_from_legend: false,
}
}
pub fn set_type(&mut self, trend: ChartTrendlineType) -> &mut ChartTrendline {
self.trend_type = trend;
self
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartTrendline
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
pub fn set_label_format<T>(&mut self, format: T) -> &mut ChartTrendline
where
T: IntoChartFormat,
{
self.label_format = format.new_chart_format();
self
}
pub fn set_label_font(&mut self, font: &ChartFont) -> &mut ChartTrendline {
let mut font = font.clone();
font.has_baseline = true;
self.label_font = Some(font);
self
}
pub fn set_name(&mut self, name: impl Into<String>) -> &mut ChartTrendline {
self.name = name.into();
self
}
pub fn set_forward_period(&mut self, period: impl Into<f64>) -> &mut ChartTrendline {
self.forward_period = period.into();
self
}
pub fn set_backward_period(&mut self, period: impl Into<f64>) -> &mut ChartTrendline {
self.backward_period = period.into();
self
}
pub fn display_equation(&mut self, enable: bool) -> &mut ChartTrendline {
self.display_equation = enable;
self
}
pub fn display_r_squared(&mut self, enable: bool) -> &mut ChartTrendline {
self.display_r_squared = enable;
self
}
pub fn set_intercept(&mut self, intercept: impl Into<f64>) -> &mut ChartTrendline {
self.intercept = Some(intercept.into());
self
}
pub fn delete_from_legend(&mut self, enable: bool) -> &mut ChartTrendline {
self.delete_from_legend = enable;
self
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartTrendlineType {
None,
Exponential,
Linear,
Logarithmic,
Polynomial(u8),
Power,
MovingAverage(u8),
}
impl fmt::Display for ChartTrendlineType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Power => write!(f, "power"),
Self::Linear => write!(f, "linear"),
Self::Exponential => write!(f, "exp"),
Self::Logarithmic => write!(f, "log"),
Self::Polynomial(_) => write!(f, "poly"),
Self::MovingAverage(_) => write!(f, "movingAvg"),
}
}
}
#[derive(Clone, PartialEq)]
pub struct ChartGradientFill {
gradient_type: ChartGradientFillType,
gradient_stops: Vec<ChartGradientStop>,
angle: u16,
}
impl Default for ChartGradientFill {
fn default() -> Self {
Self::new()
}
}
impl ChartGradientFill {
pub fn new() -> ChartGradientFill {
ChartGradientFill {
gradient_type: ChartGradientFillType::Linear,
gradient_stops: vec![],
angle: 90,
}
}
pub fn set_type(&mut self, gradient_type: ChartGradientFillType) -> &mut ChartGradientFill {
self.gradient_type = gradient_type;
self
}
pub fn set_gradient_stops(
&mut self,
gradient_stops: &[ChartGradientStop],
) -> &mut ChartGradientFill {
let mut valid_gradient_stops = vec![];
for gradient_stop in gradient_stops {
if gradient_stop.is_valid() {
valid_gradient_stops.push(gradient_stop.clone());
}
}
if (2..=10).contains(&valid_gradient_stops.len()) {
self.gradient_stops = valid_gradient_stops;
} else {
eprintln!("Gradient stops must contain between 2 and 10 valid entries.");
}
self
}
pub fn set_angle(&mut self, angle: u16) -> &mut ChartGradientFill {
if (0..360).contains(&angle) {
self.angle = angle;
} else {
eprintln!("Gradient angle '{angle}' must be in the Excel range 0 <= angle < 360");
}
self
}
}
#[derive(Clone, PartialEq)]
pub struct ChartGradientStop {
color: Color,
position: u8,
}
impl ChartGradientStop {
pub fn new(color: impl Into<Color>, position: u8) -> ChartGradientStop {
let color = color.into();
if !color.is_valid() {
eprintln!("Gradient stop color isn't valid.");
}
if !(0..=100).contains(&position) {
eprintln!("Gradient stop '{position}' outside Excel range: 0 <= position <= 100.");
}
ChartGradientStop { color, position }
}
pub(crate) fn is_valid(&self) -> bool {
self.color.is_valid() && (0..=100).contains(&self.position)
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum ChartGradientFillType {
Linear,
Radial,
Rectangular,
Path,
}
#[derive(Clone, PartialEq)]
pub struct ChartErrorBars {
has_end_cap: bool,
error_type: ChartErrorBarsType,
direction: ChartErrorBarsDirection,
format: ChartFormat,
pub(crate) plus_range: ChartRange,
pub(crate) minus_range: ChartRange,
}
impl Default for ChartErrorBars {
fn default() -> Self {
Self::new()
}
}
impl ChartErrorBars {
pub fn new() -> ChartErrorBars {
ChartErrorBars {
has_end_cap: true,
error_type: ChartErrorBarsType::StandardError,
direction: ChartErrorBarsDirection::Both,
format: ChartFormat::default(),
plus_range: ChartRange::default(),
minus_range: ChartRange::default(),
}
}
pub fn set_type(&mut self, error_type: ChartErrorBarsType) -> &mut ChartErrorBars {
match &error_type {
ChartErrorBarsType::FixedValue(value) => {
if *value <= 0.0 {
eprintln!("Error bar Fixed Value '{value}' must be > 0.0 in Excel");
return self;
}
}
ChartErrorBarsType::Percentage(value) => {
if *value < 0.0 {
eprintln!("Error bar Percentage '{value}' must be >= 0.0 in Excel");
return self;
}
}
ChartErrorBarsType::StandardDeviation(value) => {
if *value < 0.0 {
eprintln!("Error bar Standard Deviation '{value}' must be >= 0.0 in Excel");
return self;
}
}
ChartErrorBarsType::Custom(plus, minus) => {
self.plus_range = (*plus).clone();
self.minus_range = (*minus).clone();
}
ChartErrorBarsType::StandardError => {}
}
self.error_type = error_type;
self
}
pub fn set_direction(&mut self, direction: ChartErrorBarsDirection) -> &mut ChartErrorBars {
self.direction = direction;
self
}
pub fn set_end_cap(&mut self, enable: bool) -> &mut ChartErrorBars {
self.has_end_cap = enable;
self
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartErrorBars
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
}
#[derive(Clone, PartialEq)]
#[allow(clippy::large_enum_variant)]
pub enum ChartErrorBarsType {
FixedValue(f64),
Percentage(f64),
StandardDeviation(f64),
StandardError,
Custom(ChartRange, ChartRange),
}
impl fmt::Display for ChartErrorBarsType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Custom(_, _) => write!(f, "cust"),
Self::StandardError => write!(f, "stdErr"),
Self::FixedValue(_) => write!(f, "fixedVal"),
Self::Percentage(_) => write!(f, "percentage"),
Self::StandardDeviation(_) => write!(f, "stdDev"),
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum ChartErrorBarsDirection {
Both,
Minus,
Plus,
}
impl fmt::Display for ChartErrorBarsDirection {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Both => write!(f, "both"),
Self::Minus => write!(f, "minus"),
Self::Plus => write!(f, "plus"),
}
}
}
#[derive(Clone, PartialEq)]
pub struct ChartDataTable {
show_horizontal_borders: bool,
show_vertical_borders: bool,
show_outline_borders: bool,
show_legend_keys: bool,
font: Option<ChartFont>,
format: ChartFormat,
}
impl Default for ChartDataTable {
fn default() -> Self {
Self::new()
}
}
impl ChartDataTable {
pub fn new() -> ChartDataTable {
ChartDataTable {
show_horizontal_borders: true,
show_vertical_borders: true,
show_outline_borders: true,
show_legend_keys: false,
font: None,
format: ChartFormat::default(),
}
}
pub fn show_horizontal_borders(mut self, enable: bool) -> ChartDataTable {
self.show_horizontal_borders = enable;
self
}
pub fn show_vertical_borders(mut self, enable: bool) -> ChartDataTable {
self.show_vertical_borders = enable;
self
}
pub fn show_outline_borders(mut self, enable: bool) -> ChartDataTable {
self.show_outline_borders = enable;
self
}
pub fn show_legend_keys(mut self, enable: bool) -> ChartDataTable {
self.show_legend_keys = enable;
self
}
pub fn set_format<T>(mut self, format: T) -> ChartDataTable
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
pub fn set_font(mut self, font: &ChartFont) -> ChartDataTable {
self.font = Some(font.clone());
self
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum ChartAxisCrossing {
Automatic,
Min,
Max,
CategoryNumber(u32),
AxisValue(f64),
}
impl fmt::Display for ChartAxisCrossing {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Min => write!(f, "min"),
Self::Max => write!(f, "max"),
Self::Automatic => write!(f, "autoZero"),
Self::AxisValue(value) => write!(f, "{value}"),
Self::CategoryNumber(index) => write!(f, "{index}"),
}
}
}
#[derive(Clone, Copy, PartialEq)]
pub enum ChartAxisLabelAlignment {
Center,
Left,
Right,
}
impl fmt::Display for ChartAxisLabelAlignment {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Left => write!(f, "l"),
Self::Right => write!(f, "r"),
Self::Center => write!(f, "ctr"),
}
}
}
#[derive(Clone, PartialEq)]
pub struct ChartArea {
pub(crate) format: ChartFormat,
}
impl Default for ChartArea {
fn default() -> Self {
Self::new()
}
}
impl ChartArea {
pub fn new() -> ChartArea {
ChartArea {
format: ChartFormat::default(),
}
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartArea
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
}
#[derive(Clone, PartialEq)]
pub struct ChartPlotArea {
pub(crate) format: ChartFormat,
pub(crate) layout: ChartLayout,
}
impl Default for ChartPlotArea {
fn default() -> Self {
Self::new()
}
}
impl ChartPlotArea {
pub fn new() -> ChartPlotArea {
ChartPlotArea {
format: ChartFormat::default(),
layout: ChartLayout::default(),
}
}
pub fn set_format<T>(&mut self, format: T) -> &mut ChartPlotArea
where
T: IntoChartFormat,
{
self.format = format.new_chart_format();
self
}
pub fn set_layout(&mut self, layout: &ChartLayout) -> &mut ChartPlotArea {
let mut layout = layout.clone();
layout.has_inner = true;
layout.has_dimensions = true;
self.layout = layout;
self
}
}
#[derive(Clone, PartialEq)]
pub struct ChartLayout {
pub(crate) x_offset: Option<f64>,
pub(crate) y_offset: Option<f64>,
pub(crate) width: Option<f64>,
pub(crate) height: Option<f64>,
pub(crate) has_inner: bool,
pub(crate) has_dimensions: bool,
}
impl Default for ChartLayout {
fn default() -> Self {
Self::new()
}
}
impl ChartLayout {
pub fn new() -> ChartLayout {
ChartLayout {
x_offset: None,
y_offset: None,
width: None,
height: None,
has_inner: false,
has_dimensions: false,
}
}
pub fn set_offset(mut self, x_offset: f64, y_offset: f64) -> ChartLayout {
if !(0.0..=1.0).contains(&x_offset) {
eprintln!("X offset '{x_offset}' outside Excel range: 0.0 < x <= 1.0.");
return self;
}
if !(0.0..=1.0).contains(&y_offset) {
eprintln!("Y offset '{y_offset}' outside Excel range: 0.0 < y <= 1.0.");
return self;
}
self.x_offset = Some(x_offset);
self.y_offset = Some(y_offset);
self
}
pub fn set_dimensions(mut self, width: f64, height: f64) -> ChartLayout {
if !(0.0..=1.0).contains(&width) {
eprintln!("Width '{width}' outside Excel range: 0.0 < width <= 1.0.");
return self;
}
if !(0.0..=1.0).contains(&height) {
eprintln!("Height '{height}' outside Excel range: 0.0 < height <= 1.0.");
return self;
}
self.width = Some(width);
self.height = Some(height);
self
}
pub(crate) fn is_not_default(&self) -> bool {
self.x_offset.is_some()
|| self.y_offset.is_some()
|| self.width.is_some()
|| self.height.is_some()
}
}