M7: add data tables beneath charts

Add command and UI controls for chart data tables, shared chart and PDF rendering, and native XLSX persistence. Cover live values, blanks, formatting, alignment, undo/redo, bounds and table flags with regression tests.

Co-Authored-By: Codex <noreply@openai.com>
This commit is contained in:
campbellcody
2026-11-01 15:30:46 -05:00
co-authored by Codex
parent f5030432fc
commit b6c4500fa7
37 changed files with 560 additions and 18 deletions
+42
View File
@@ -540,6 +540,22 @@ impl Trendline {
}
}
/// A chart's data table (`c:dTable`), independent of the chart legend.
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct ChartDataTable {
pub show_keys: bool,
pub horizontal_border: bool,
pub vertical_border: bool,
pub outline: bool,
}
impl Default for ChartDataTable {
fn default() -> Self {
Self { show_keys: true, horizontal_border: true, vertical_border: true, outline: true }
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Chart {
pub id: u32,
@@ -549,6 +565,8 @@ pub struct Chart {
pub series: Vec<Series>,
pub legend: LegendPos,
pub data_labels: bool,
#[serde(default)]
pub data_table: Option<ChartDataTable>,
pub gridlines: bool,
pub style: u32,
pub x_title: Option<String>,
@@ -562,6 +580,30 @@ pub struct Chart {
pub pivot: Option<String>,
}
impl Chart {
/// Data tables belong to category-axis column, bar, line and area charts.
pub fn supports_data_table(&self) -> bool {
let eligible = |kind| {
matches!(
kind,
ChartKind::ColumnClustered
| ChartKind::ColumnStacked
| ChartKind::ColumnStacked100
| ChartKind::BarClustered
| ChartKind::BarStacked
| ChartKind::BarStacked100
| ChartKind::Line
| ChartKind::LineMarkers
| ChartKind::LineStacked
| ChartKind::Area
| ChartKind::AreaStacked
| ChartKind::AreaStacked100
)
};
eligible(self.kind) || (self.kind == ChartKind::Combo && self.series.iter().all(|s| eligible(s.kind.unwrap_or(ChartKind::Line))))
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
pub struct Image {
pub id: u32,