From fddd08bec3708ef18c720d2528efbad28a7d1cd9 Mon Sep 17 00:00:00 2001 From: nihilist-narwhal <240798405+nihilist-narwhal@users.noreply.github.com> Date: Fri, 9 Oct 2026 23:31:26 -0500 Subject: [PATCH] Charts: Switch Row/Column on charts read from a file chart.switchRowColumn did nothing on a chart read from a file: only charts made by insert.chart keep their source range. Without one, the command now takes the block the series' names, categories and values span (when they're all on one sheet), and keeps it. --- crates/engine/src/cmd/insert.rs | 29 +++++++++++++++++++++++++++-- crates/engine/src/tests.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 2 deletions(-) diff --git a/crates/engine/src/cmd/insert.rs b/crates/engine/src/cmd/insert.rs index e4b5bc9..b922b4c 100644 --- a/crates/engine/src/cmd/insert.rs +++ b/crates/engine/src/cmd/insert.rs @@ -654,14 +654,16 @@ fn chart_switch(s: &mut Session, p: &Json) -> Result { edit(s, |cx| { let Some(sh) = cx.wb.sheet(si) else { return Ok(Json::Null) }; let Some(c) = sh.charts.get(ci) else { return Ok(Json::Null) }; - let Some(src) = c.source.clone() else { return Ok(Json::Null) }; + // Charts read from a file have no source range: take the block their series span. + let Some((src, by_rows)) = c.source.clone().map(|s| (s, c.by_rows)).or_else(|| series_block(&c.series)) else { return Ok(Json::Null) }; let (sheet_name, body) = split_sheet(&src); let src_sheet = sheet_name.and_then(|n| cx.wb.sheet_index(&n)).unwrap_or(si); let Some(r) = RangeRef::parse(body) else { return Ok(Json::Null) }; let Some(ssh) = cx.wb.sheet(src_sheet) else { return Ok(Json::Null) }; - let by_rows = !c.by_rows; + let by_rows = !by_rows; let series = series_from_range(ssh, r, by_rows); if let Some(c) = cx.sheet_mut(si)?.charts.get_mut(ci) { + c.source.get_or_insert(src); c.by_rows = by_rows; c.series = series; } @@ -669,6 +671,29 @@ fn chart_switch(s: &mut Session, p: &Json) -> Result { }) } +/// The block a chart's series come from, and whether each series is a row of it: the bounding +/// range of the series names, categories and values, when they're all references to one sheet. +fn series_block(series: &[Series]) -> Option<(String, bool)> { + let mut sheet: Option = None; + let mut bounds: Option = None; + let mut rows = 0; + for s in series { + for (i, f) in [Some(&s.values), s.categories.as_ref(), s.name.as_ref()].into_iter().flatten().enumerate() { + let Ok(gridcraft_formula::Expr::Ref(r)) = gridcraft_formula::parse(f) else { return None }; + let gridcraft_formula::SheetSel::Named(n) = &r.sheet else { return None }; + if !sheet.get_or_insert_with(|| n.clone()).eq_ignore_ascii_case(n) { + return None; + } + let range = r.range(); + if i == 0 && range.height() == 1 && range.width() > 1 { + rows += 1; + } + bounds = Some(bounds.map_or(range, |b| b.union(&range))); + } + } + Some((format!("{}!{}", gridcraft_formula::quote_sheet(&sheet?), bounds?.a1()), rows > 0 && rows == series.len())) +} + fn chart_delete(s: &mut Session, p: &Json) -> Result { let (si, ci) = find_chart(s, p)?; edit(s, |cx| { diff --git a/crates/engine/src/tests.rs b/crates/engine/src/tests.rs index f3e36cd..6a01542 100644 --- a/crates/engine/src/tests.rs +++ b/crates/engine/src/tests.rs @@ -174,6 +174,32 @@ fn parity_counts() { assert!(done > 150, "parity {done}/{total}"); } +#[test] +fn switch_row_column_on_a_chart_read_from_a_file() { + let mut s = s(); + s.execute( + "range.setValues", + json!({"range": "A1", "values": [["", "North", "South", "East", "West"], ["Mon", 10, 14, 9, 12], ["Tue", 12, 15, 11, 14], ["Wed", 14, 16, 10, 11], ["Thu", 11, 13, 8, 9], ["Fri", 9, 12, 7, 11], ["Sat", 11, 15, 10, 14]]}), + ) + .unwrap(); + s.execute("insert.chart", json!({"type": "column", "range": "A1:E7"})).unwrap(); + let r = s.execute("file.saveBytes", json!({"format": "xlsx"})).unwrap(); + let b64 = r["base64"].as_str().unwrap().to_string(); + s.execute("file.open", json!({"name": "x.xlsx", "base64": b64})).unwrap(); + let chart = |s: &Session| s.doc().unwrap().wb.active().unwrap().charts[0].clone(); + assert_eq!((chart(&s).source, chart(&s).series.len()), (None, 4)); + s.execute("chart.switchRowColumn", json!({})).unwrap(); + let c = chart(&s); + assert!(c.by_rows); + assert_eq!(c.source.as_deref(), Some("Sheet1!A1:E7")); + assert_eq!(c.series.len(), 6); + assert_eq!(c.series[0].name.as_deref(), Some("Sheet1!$A$2")); + assert_eq!(c.series[0].categories.as_deref(), Some("Sheet1!$B$1:$E$1")); + assert_eq!(c.series[0].values, "Sheet1!$B$2:$E$2"); + s.execute("chart.switchRowColumn", json!({})).unwrap(); + assert_eq!(chart(&s).series.len(), 4); +} + #[test] fn ink_strokes_and_ink_to_shape() { let mut s = s();