Files
gridcraft/crates/engine/src/pivot/charts.rs
T

109 lines
4.2 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use super::output::Output;
use super::{CellRef, Chart, Series, Workbook, quote_sheet};
// ---------------------------------------------------------------- PivotCharts
/// The link a PivotChart stores to its PivotTable: `Sheet2!PivotTable1`.
pub fn chart_link(sheet: &str, pivot: &str) -> String {
format!("{}!{pivot}", quote_sheet(sheet))
}
/// (sheet, PivotTable name) of a PivotChart link; a leading `[Book.xlsx]` is ignored.
pub fn parse_chart_link(link: &str) -> Option<(String, String)> {
let t = link.trim();
let t = match t.strip_prefix('[') {
Some(rest) => rest.split_once(']').map(|(_, r)| r).unwrap_or(rest),
None => t,
};
let (sheet, name) = t.rsplit_once('!')?;
let sheet = sheet.trim();
let sheet = gridcraft_core::unquote_sheet(sheet);
let name = name.trim();
if sheet.is_empty() || name.is_empty() {
return None;
}
Some((sheet, name.to_string()))
}
/// Whether a PivotChart link points at `pivot` on `sheet`.
pub fn links_to(link: &str, sheet: &str, pivot: &str) -> bool {
parse_chart_link(link).is_some_and(|(s, n)| s.eq_ignore_ascii_case(sheet) && n.eq_ignore_ascii_case(pivot))
}
/// An absolute reference to `col` over grid `rows` (contiguous runs joined into a union).
fn rows_ref(sheet: &str, origin: CellRef, col: u32, rows: &[u32]) -> Option<String> {
let q = quote_sheet(sheet);
let c = origin.col.checked_add(col)?;
let letters = gridcraft_core::col_to_letters(c);
let mut parts: Vec<String> = Vec::new();
let mut i = 0;
while let Some(&start) = rows.get(i) {
let mut end = start;
while rows.get(i + 1).is_some_and(|n| Some(*n) == end.checked_add(1)) {
end += 1;
i += 1;
}
i += 1;
let (a, b) = (origin.row.checked_add(start)? + 1, origin.row.checked_add(end)? + 1);
parts.push(if a == b { format!("{q}!${letters}${a}") } else { format!("{q}!${letters}${a}:${letters}${b}") });
}
match parts.len() {
0 => None,
1 => parts.pop(),
_ => Some(format!("({})", parts.join(","))),
}
}
/// Most category rows a PivotChart plots.
const MAX_CHART_ROWS: usize = 10_000;
/// A PivotChart's series for a report written at `origin`: categories are the innermost row
/// items, one series per item column (column items × value fields); totals are left out.
pub fn chart_series(sheet: &str, origin: CellRef, out: &Output) -> Vec<Series> {
let rows: Vec<u32> = out.chart_rows.iter().copied().take(MAX_CHART_ROWS).collect();
let categories = out.label_col.and_then(|c| rows_ref(sheet, origin, c, &rows));
out.chart_cols
.iter()
.filter_map(|(c, name)| {
Some(Series {
name: Some(format!("\"{}\"", name.replace('"', "\"\""))),
categories: categories.clone(),
values: rows_ref(sheet, origin, *c, &rows)?,
bubble_sizes: None,
color: None,
secondary: false,
kind: None,
quartiles: Default::default(),
smooth: false,
trendlines: Vec::new(),
})
})
.collect()
}
/// Rebuilds the series of every PivotChart of a PivotTable after its report was written.
pub(super) fn rebind_charts(wb: &mut Workbook, sheet: &str, pivot: &str, origin: CellRef, out: &Output) {
let linked = |ch: &Chart| ch.pivot.as_deref().is_some_and(|l| links_to(l, sheet, pivot));
let hits: Vec<usize> = (0..wb.sheets.len()).filter(|i| wb.sheet(*i).is_some_and(|sh| sh.charts.iter().any(linked))).collect();
if hits.is_empty() {
return;
}
let series = chart_series(sheet, origin, out);
for i in hits {
let Some(sh) = wb.sheet_mut(i) else { continue };
for ch in sh.charts.iter_mut().filter(|c| linked(c)) {
ch.series = series.clone();
}
}
}
/// Clears a pivot's last output area.
pub fn clear_output(wb: &mut Workbook, sheet: usize, idx: usize) {
let Some(shm) = wb.sheet_mut(sheet) else { return };
let Some(r) = shm.pivots.get(idx).and_then(|p| p.last_range) else { return };
shm.take_cells(r);
if let Some(p) = shm.pivots.get_mut(idx) {
p.last_range = None;
}
}