diff --git a/ROADMAP.md b/ROADMAP.md
index 9f3cb9bff..bc8faa925 100644
--- a/ROADMAP.md
+++ b/ROADMAP.md
@@ -159,6 +159,7 @@ app's JSON control channel (`deckcraft --control PORT`).
| 2026-10-17 | While a placeholder's text is being edited, a click on a shape in front of it selects that shape instead of moving the caret, as in PowerPoint (#931) |
| 2026-10-16 | Symbol-font characters: a run's U+F0xx symbol code (from ``) now draws from the installed Symbol face even when it has no private-use mapping — Apple's Symbol on macOS maps the glyphs to real Unicode, so the code is remapped to that character (the Adobe Symbol encoding) instead of falling to a look-alike. Bullets and metafile text take the same remap |
| 2026-10-15 | Native saves keep an imported chart's embedded workbook, drawings and relationships; headless PPTX → edit → native save/reopen → PPTX tests verify the payloads and stale-workbook rejection |
+| 2026-10-18 | Charts: a series' own data-label setting is honoured — numbers PowerPoint shows on a graph no longer disappear, and a bare `c:showVal` means on (#868) |
| 2026-10-15 | PPTX trigger sequences keep separate On Click steps, including paragraph builds; synthetic import/edit/save/reopen and headless show regressions |
| 2026-10-16 | Export: PNG with a transparent background — the slide background is left out, only the slide's content is drawn (Export… dialog, File › Export, web download, `file.export`/`file.render` `transparentBackground`, `deckcraft-cli render --transparent`) (#784) |
| 2026-10-16 | Arrange ▸ Align: Align to Slide and Align Selected Objects, as in PowerPoint. One object aligns to the slide and several to each other until the other choice is ticked; the choice holds for that selection and also drives Distribute |
diff --git a/crates/model/src/chart.rs b/crates/model/src/chart.rs
index 9d96c4fd3..7fcdd9c9f 100644
--- a/crates/model/src/chart.rs
+++ b/crates/model/src/chart.rs
@@ -1,1525 +1,1545 @@
-//! Charts: an embedded data grid plus type and element options.
-
-use serde::{Deserialize, Serialize};
-
-use crate::style::{ColorRef, Fill, Line};
-
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub enum ChartType {
- #[default]
- Column,
- StackedColumn,
- PercentColumn,
- Bar,
- StackedBar,
- PercentBar,
- Line,
- LineMarkers,
- StackedLine,
- Area,
- StackedArea,
- Pie,
- Doughnut,
- Scatter,
- Bubble,
- Radar,
- FilledRadar,
- Stock,
- Surface,
- Histogram,
- Pareto,
- BoxWhisker,
- Waterfall,
- Funnel,
- Treemap,
- Sunburst,
- /// A filled (choropleth) map of regions coloured by value, kept and drawn from its data; it
- /// is not offered in the chart menus (PowerPoint needs an online map service to make one).
- RegionMap,
- Combo,
-}
-
-impl ChartType {
- /// The Office 2016 (`chartEx`) types, offered after [`ChartType::MAIN`] in chart menus.
- pub const OFFICE_2016: [ChartType; 7] = [
- ChartType::Waterfall,
- ChartType::Funnel,
- ChartType::Histogram,
- ChartType::Pareto,
- ChartType::BoxWhisker,
- ChartType::Treemap,
- ChartType::Sunburst,
- ];
-
- /// Whether the type is saved as a `chartEx` part (with a plain chart as the fallback).
- pub fn is_chartex(self) -> bool {
- Self::OFFICE_2016.contains(&self) || self == ChartType::RegionMap
- }
-
- pub const MAIN: [ChartType; 12] = [
- ChartType::Column,
- ChartType::Bar,
- ChartType::Line,
- ChartType::Area,
- ChartType::Pie,
- ChartType::Doughnut,
- ChartType::Scatter,
- ChartType::Radar,
- ChartType::Waterfall,
- ChartType::Funnel,
- ChartType::Histogram,
- ChartType::Treemap,
- ];
- pub fn label(self) -> &'static str {
- match self {
- ChartType::Column => "Clustered Column",
- ChartType::StackedColumn => "Stacked Column",
- ChartType::PercentColumn => "100% Stacked Column",
- ChartType::Bar => "Clustered Bar",
- ChartType::StackedBar => "Stacked Bar",
- ChartType::PercentBar => "100% Stacked Bar",
- ChartType::Line => "Line",
- ChartType::LineMarkers => "Line with Markers",
- ChartType::StackedLine => "Stacked Line",
- ChartType::Area => "Area",
- ChartType::StackedArea => "Stacked Area",
- ChartType::Pie => "Pie",
- ChartType::Doughnut => "Doughnut",
- ChartType::Scatter => "Scatter",
- ChartType::Bubble => "Bubble",
- ChartType::Radar => "Radar",
- ChartType::FilledRadar => "Filled Radar",
- ChartType::Stock => "Stock",
- ChartType::Surface => "Surface",
- ChartType::Histogram => "Histogram",
- ChartType::Pareto => "Pareto",
- ChartType::BoxWhisker => "Box and Whisker",
- ChartType::Waterfall => "Waterfall",
- ChartType::Funnel => "Funnel",
- ChartType::Treemap => "Treemap",
- ChartType::Sunburst => "Sunburst",
- ChartType::RegionMap => "Map",
- ChartType::Combo => "Combo",
- }
- }
- pub fn is_bar_like(self) -> bool {
- matches!(
- self,
- ChartType::Column
- | ChartType::StackedColumn
- | ChartType::PercentColumn
- | ChartType::Bar
- | ChartType::StackedBar
- | ChartType::PercentBar
- | ChartType::Histogram
- | ChartType::Pareto
- | ChartType::Waterfall
- )
- }
- pub fn horizontal(self) -> bool {
- matches!(self, ChartType::Bar | ChartType::StackedBar | ChartType::PercentBar | ChartType::Funnel)
- }
- pub fn stacked(self) -> bool {
- matches!(
- self,
- ChartType::StackedColumn
- | ChartType::StackedBar
- | ChartType::StackedLine
- | ChartType::StackedArea
- | ChartType::PercentColumn
- | ChartType::PercentBar
- )
- }
- pub fn percent(self) -> bool {
- matches!(self, ChartType::PercentColumn | ChartType::PercentBar)
- }
-}
-
-/// `c:trendlineType`: the six trendlines DrawingML charts store.
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
-#[serde(rename_all = "camelCase")]
-pub enum TrendlineType {
- Exp,
- #[default]
- Linear,
- Log,
- MovingAvg,
- Poly,
- Power,
-}
-
-impl TrendlineType {
- pub fn xml(self) -> &'static str {
- match self {
- TrendlineType::Exp => "exp",
- TrendlineType::Linear => "linear",
- TrendlineType::Log => "log",
- TrendlineType::MovingAvg => "movingAvg",
- TrendlineType::Poly => "poly",
- TrendlineType::Power => "power",
- }
- }
- pub fn from_xml(s: &str) -> Option {
- Some(match s {
- "exp" => TrendlineType::Exp,
- "linear" => TrendlineType::Linear,
- "log" => TrendlineType::Log,
- "movingAvg" => TrendlineType::MovingAvg,
- "poly" => TrendlineType::Poly,
- "power" => TrendlineType::Power,
- _ => return None,
- })
- }
-}
-
-/// One `c:trendline` on a series. Polynomials use `order` (2–6); moving averages use `period`.
-#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
-#[serde(default, rename_all = "camelCase")]
-pub struct Trendline {
- pub kind: TrendlineType,
- /// `c:name`, when the file sets one.
- #[serde(skip_serializing_if = "Option::is_none")]
- pub name: Option,
- /// Polynomial order (`c:order`).
- #[serde(skip_serializing_if = "Option::is_none")]
- pub order: Option,
- /// Moving-average period (`c:period`).
- #[serde(skip_serializing_if = "Option::is_none")]
- pub period: Option,
- /// Forecast forward and backward, in category units (`c:forward`, `c:backward`).
- #[serde(skip_serializing_if = "Option::is_none")]
- pub forward: Option,
- #[serde(skip_serializing_if = "Option::is_none")]
- pub backward: Option,
- /// Cross the value axis at this value (`c:intercept`).
- #[serde(skip_serializing_if = "Option::is_none")]
- pub intercept: Option,
- /// Show the equation (`c:dispEq`).
- pub disp_eq: bool,
- /// Show R² (`c:dispRSqr`).
- pub disp_r_sqr: bool,
-}
-
-impl Default for Trendline {
- fn default() -> Self {
- Trendline {
- kind: TrendlineType::Linear,
- name: None,
- order: None,
- period: None,
- forward: None,
- backward: None,
- intercept: None,
- disp_eq: false,
- disp_r_sqr: false,
- }
- }
-}
-
-#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
-#[serde(default, rename_all = "camelCase")]
-pub struct Series {
- pub name: String,
- pub values: Vec