Skip to content

Charts: every LibreOffice chart type, a data editor, and formatting for each - #255

Open
pierrevbc wants to merge 9 commits into
storytold:mainfrom
pierrevbc:charts-all
Open

pierrevbc wants to merge 9 commits into
storytold:mainfrom
pierrevbc:charts-all

Conversation

@pierrevbc

@pierrevbc pierrevbc commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Builds on #241 (chart workbooks; please land it first: this branch starts with #241's two code
commits, rebased on current main) and on yefan911's #195 (the chart data window, live title
editing and the type picker), re-expressed in our own code and credited with a Co-authored-by
trailer on the data editor commit. Rebased on main at 9859da3, after the chart work that landed
since the first version of this PR (NikitkaRa's chart series, #355, #150, #151, #157, #486, #502).

Review points addressed

  • No process launching, no paths from command params. chart.gridcraft is gone, with the
    CSV export (Chart::to_csv / load_csv), the "Open in GridCraft" button, the hard-coded
    C:\Users\parra\gridcraft path, the gridcraft on PATH and every std::process and
    std::env::temp_dir use from that code. Chart data is edited inside DeckCraft only: the Edit Data
    window and the chart.* data commands. git diff origin/main..charts-v2 adds no
    std::process, Command::new or temp_dir.
  • No CLAUDE.md / AGENTS.md changes. Untouched.
  • No Cargo.lock changes. Untouched: no dependency was added, nothing is downgraded.
  • Charts: GridCraft spreadsheet data editor and live editing for the first 5 chart types #195's commit is no longer in the branch. What this PR keeps from it (the data window, title
    editing, the type picker, double-click to edit data, Edit Data opening after Insert ▸ Chart) is
    written in the data editor commit, with yefan911 as co-author.

Commits

  1. pptx: write a workbook for edited and new charts (PPTX export: a workbook for edited and new charts, updated in place when the data changes #241, rebased): main now keeps an unchanged
    chart's workbook (Charts: keep the embedded workbook when a chart is saved unchanged (#335) #355); an edited or new chart gets a workbook generated from its data, laid
    out where the chart part's formulas point, so Edit Data in PowerPoint has something to open.
    The workbook lives in main's ChartKept (ChartKept::workbook()), not in a field of its own.
  2. pptx: update chart workbooks in place instead of rebuilding them (PPTX export: a workbook for edited and new charts, updated in place when the data changes #241, rebased):
    Chart::source keeps the series formulas; an edited chart whose data still fits writes its
    original workbook with only the changed cells rewritten.
  3. Charts: every LibreOffice chart type and variant in the model, PPTX and gallery.
  4. Charts: data editor for every chart type, and data commands (co-authored by yefan911).
  5. Charts: keep the workbook when series or points are added or removed.
  6. Charts: formatting model, chart.format.* commands, and PPTX.
  7. Charts: Format pane chart options, Chart Design/Format ribbon, and main's chart fields.
  8. Charts: draw every chart type, variant and formatting.
  9. Charts: progress docs and chart type parity.

What this PR adds

Chart types

  • ChartType gains the variants PowerPoint and Impress offer as types of their own (points-only,
    stacked and 100% lines with or without markers, 100% area, exploded pie and doughnut, pie of pie,
    bar of pie, scatter with lines, radar with markers or points only, stock with an open price or a
    volume column), and Chart::variant holds stepped lines, explosion, the pie of pie split and 3-D
    (view angles, bar shape).
  • ChartPreset::GALLERY: 68 entries in 16 families (Column, Bar, Pie, Area, Line, XY, Bubble,
    Radar, Stock, Combo, then main's Office 2016 types: Treemap, Sunburst, Histogram and Pareto,
    Box & Whisker, Waterfall, Funnel). Insert ▸ Chart and Change Chart Type show it by family, the
    current one checked; insert.chart and chart.type take preset ids (column3d, lineSmooth,
    stockOhlc, columnLineSecondary…) and the old names.
  • PPTX: every variant read and written (grouping, markers, c:explosion, c:ofPieChart, the
    volume group of a stock chart, 3-D groups and c:view3D, combos with their secondary axis).
    Stepped lines have no OOXML element: a chart-space extension keeps them.
  • docs/chart-type-parity.md audits every type.

Data

  • crates/model/src/chart_data.rs: each type's data as a table (categories; X shared or per
    series and Y for scatter charts; sizes for bubbles; stock prices by role; series types in combos).
  • Commands chart.grid, chart.setCell, chart.paste, chart.insertRows, chart.deleteRows,
    chart.moveRow, chart.insertSeries, chart.deleteSeries, chart.moveSeries,
    chart.switchRowColumn, chart.title; undoGroup makes one undo step per edited cell;
    insert.chart and chart.data take X values, sizes and stock roles.
  • The Edit Data window (crates/ui-egui/src/chartdata.rs): spreadsheet letters and row numbers,
    columns following the chart type, live update at each key, paste from Excel or LibreOffice,
    toolbar for rows and series, title field and a Type menu with the gallery. It opens from
    Chart Design ▸ Edit Data, after Insert ▸ Chart (editData, which only the ribbon passes) and on a
    double-click; scripted calls never open it.
  • The workbook (PPTX export: a workbook for edited and new charts, updated in place when the data changes #241) follows added or removed series and points instead of being regenerated.

Formatting

  • crates/model/src/chart_format.rs and chart.format.element|legend|axis|labels|series|point|options,
    chart.colors, chart.style, chart.quickLayout, chart.resetStyle: fonts, fills, borders,
    axes (bounds, units, log scale, reverse, crossing, tick marks, label position and rotation,
    number format, gridlines), data label parts and positions, series and point looks, markers,
    explosion, palettes, styles and layouts.
  • Format pane Chart Options per element; Chart Design gains Add Chart Element, Quick Layout,
    Change Colors and Chart Styles; a Chart Format tab.
  • Read and written on every plot group in schema order; drawn on every chart family.

What comes from main, and how this PR builds on it

Where main now has the same feature, this branch keeps main's model fields and code and makes ours
set, write and draw them; there is no second copy:

Main Kept What this PR does with it
#355 kept chart parts (ChartKept) The kept parts, write_kept, fresh keptN names #241's workbook is the kept part; unchanged charts go back exactly as main writes them
Office 2016 types (#537, #591, #594, #598): waterfall, funnel, treemap, sunburst, histogram, Pareto, box & whisker, region map Their model, chartEx reading and writing, chartex.rs layouts, histogram and box views In the gallery; the painter draws waterfalls, histograms, Pareto and box charts on its category frame, and calls chartex::draw for funnels, treemaps, sunbursts and maps
#602 stock high-low lines and up/down bars, polar radar, combo sample Chart::up_down_bars, c:stockChart writing (hiLowLines, upDownBars), the radar web layout Open-high-low-close presets set up_down_bars; the painter draws up/down bars when it is set. Our radar and stock painters (markers, points only, volume, formatting) replace radar.rs
#603 trendlines, #673 error bars Model, reading and writing, draw_trendlines and its helpers Called from the painter for category and scatter charts (with forecast slots); error bars drawn on bars, lines and scatter points
#638 bar widths, point fills, smoothed lines, chart text colour; #151 varied colours by point The gap/overlap geometry, smooth_path, varyColors default Same geometry and spline in the painter; single series vary by point unless they have a fill
#640 reversed categories, #662 scatter value X axis, #666 minor gridlines, #669 log axes Chart::cat_reversed, minor_gridlines, y_log_base, value_x_scale chart.format.axis and the Format pane set these fields (no copy in AxisFormat for the value or category axis); the painter draws them
#157 Office's automatic axis limits deckcraft_model::chart::axis_scale Every value axis of the painter takes its limits from it (our own "start above zero" rule is gone)
#657 axis titles, #150 series name as title, #486 pie label formats, #502 tiny charts As is Drawn with the element fonts; tiny charts still draw (clamp_soft)
#262 legend overlay, combo axes, number formats, #358 text sizes As in the first version of this PR Unchanged

numfmt gains rounding of halves away from zero, as spreadsheets do (main already had scientific
notation; ours is gone). The PPTX reader no longer gives a stock chart varyColors (a
c:stockChart has no such element).

How it was tested

  • cargo xtask ci passes (fmt, pdf-deps, clippy, test, assets, layers, wasm) from an empty
    target dir: 1753 tests pass (6 ignored).
  • Main's chart tests pass unchanged except two that encoded what this PR changes on purpose:
    chart_embed.rs builds its fixtures without the workbook DeckCraft now writes for its own charts,
    and an edited chart is checked to get a workbook of its own data instead of none (never the stale
    one). Main's render tests (trendlines, axis titles, point fills and bar widths, reversed bars,
    combo axes, Office 2016 layouts, radar, stock and combo, degenerate frames, varied colours) pass.
  • PPTX: every gallery entry round trips (type, variant, data, workbook, same part on a second
    export); formatting round trips on every family; workbooks follow added and removed series and
    points; odd data stays valid.
  • Engine and UI: data commands and undo groups, the data editor flow (insert with editData,
    title, type, double-click), formatting commands setting main's fields, the soak.
  • By hand: a deck with every gallery entry and a deck of formatted charts built with
    deckcraft-cli run, rendered with deckcraft-cli render, exported to PPTX, rendered again from
    the PPTX, and converted with LibreOffice. Not checked in PowerPoint or Excel.

Known limits

  • The workbook lives in main's ChartKept, which a native .deckcraft save does not store: after
    reopening a .deckcraft, an imported chart gets a workbook generated from its data.
  • Stepped lines live in an extension; 3-D charts get a simple depth (no 3-D renderer).
  • Funnels, treemaps and sunbursts keep main's layouts, without the chart formatting.
  • Not formatted yet: the secondary axis' line and font, stock high-low lines and up/down bars,
    bubble scale, pie of pie connector lines, 3-D walls and floor; elements are picked in the pane,
    not on the slide.
  • No "Select Data" range editing.

Related

@pierrevbc
pierrevbc force-pushed the charts-all branch 2 times, most recently from df2f3c4 to cc74716 Compare October 13, 2026 11:52
h-a-n-a-s-h-i pushed a commit that referenced this pull request Oct 14, 2026
AppKit draws the traffic lights 16 pt from the top of the window on macOS 27,
but the title bar was 38 pt, so they sat above its centre line. On macOS the
bar is now 32 pt, centred on them: content starts after the lights with the gap
they keep from the top, and the Present button shrinks to 22 pt to keep a
margin. Same approach as wordcraft #255 and gridcraft #323, with the light
metrics measured on macOS 27. Linux, Windows and web are unchanged.
@echelon

echelon commented Oct 14, 2026

Copy link
Copy Markdown
Member

Thank you for the work here, @pierrevbc — a GridCraft data editor for charts is something we'd like. We can't merge this version, though, for these reasons:

  • Running programs from the engine. chart.gridcraft is an engine command, so MCP and the control channel can call it. It writes a CSV to any caller-given path and then runs cargo run --manifest-path C:\Users\parra\gridcraft\Cargo.toml (or a gridcraft found on PATH). DeckCraft must never build or launch programs from a personal path or PATH on behalf of an automation client, and the engine layer can't spawn processes at all (it has to build for wasm; see AGENTS.md §4).
  • CLAUDE.md is rewritten to point agents at a personal fork. Please leave CLAUDE.md/AGENTS.md out of feature PRs.
  • Locked crates are downgraded (chrono 0.4.45 → 0.4.39, getrandom).

An acceptable shape would keep chart data editing inside DeckCraft (or hand data to another app only through an explicit, user-initiated UI action with no paths taken from command params), without the CLAUDE.md and Cargo.lock changes. We'd be glad to review that.

If you haven't already, come join us on Discord: https://discord.gg/artcraft

pierrevbc and others added 9 commits October 15, 2026 13:08
Main keeps an unchanged chart's embedded workbook (storytold#355), but a chart made
in DeckCraft, or one whose data or options changed, was written with no
workbook: no part under ppt/embeddings/, no package relationship, no
c:externalData. PowerPoint's Edit Data had nothing to open.

- Export: an unchanged chart still goes back with its kept parts; an
  edited or new chart gets a workbook generated from its data, laid out
  where the chart part's formulas point (Sheet1, categories in A, one
  column per series), with its c:externalData.
- Scatter series with different X values and multi-series bubble charts
  get their own columns instead of overlapping ranges.
- ChartKept::workbook finds the kept workbook part.
- The plain-chart fallback of an Office 2016 chart gets none: the
  workbook belongs to the chartEx part.
Every chart command clears the original chart part, so any edit (a new
title, another chart type, a legend) made the export drop the chart's
workbook for a generated one: extra sheets, formulas and formatting were
lost even when the data had not changed.

- Model: Chart.source, the series formulas of the imported chart part
  (name, categories or X values, values, bubble sizes) with the data as
  read. Commands keep it.
- Export: when the data still fits the original ranges, the chart part
  is written with its original formulas. Unchanged data keeps the kept
  workbook byte for byte; changed data rewrites only the changed cells
  in it. Every other zip entry is copied as is.
- Cells keep their style; a new name in a table header renames the
  table column; a value typed over a formula replaces it and drops the
  calculation chain; fullCalcOnLoad is set when the workbook holds
  formulas.
- Series or points added or removed, a category chart turned into a
  scatter chart (or back), literal data, shared or array formulas, a
  table column name that would clash or is used by a structured
  reference: the workbook is generated from the chart data, as before.
…nd gallery

- ChartType gains the variants PowerPoint and LibreOffice Impress offer
  as types of their own: points-only, stacked and 100% stacked lines
  with or without markers, 100% stacked area, exploded pie and
  doughnut, pie of pie and bar of pie, scatter with lines (and
  markers), radar with markers or points only, and the stock charts
  with an open price or a volume column (open-high-low-close,
  volume-high-low-close, volume-open-high-low-close).
- Chart.variant (ChartVariant) holds what makes the other variants:
  stepped lines, explosion, the pie of pie split and second plot size,
  and 3-D (ThreeD: view angles, bar shape box/cylinder/cone/pyramid,
  series in depth).
- ChartPreset::GALLERY lists the gallery by family (Column, Bar, Pie,
  Area, Line, XY (Scatter), Bubble, Radar, Stock, Combo with
  secondary-axis and stacked column-line combos, then main's Office 2016
  types: Treemap, Sunburst, Histogram and Pareto, Box & Whisker,
  Waterfall, Funnel); apply() turns a chart into one, matches() tells
  which one a chart is. The Insert > Chart and Change Chart Type menus
  list it by family, the current type checked.
- insert.chart and chart.type take a preset id (column3d, lineSmooth,
  stockOhlc, columnLineSecondary...) as well as the type names they
  took before.
- Up/down bars stay main's Chart::up_down_bars: open-high-low-close
  presets set it, the type says which series is the open price.
- PPTX: read and write every type and variant (c:ofPieChart, the 3-D
  groups with c:view3D, bar shape and series axis, explosion, the
  volume group of a stock chart, points-only series, a stepped-line
  extension), on top of main's trendlines, error bars, stock high-low
  lines, log axes, minor gridlines, reversed categories and chartEx.
- Tests: every gallery entry round trips (type, variant, data,
  workbook, same part on a second export), odd data stays valid.
The chart data as a data sheet shows it, the commands that edit it, and
the Edit Data window. The window, live title editing and type picker
build on yefan911's storytold#195, re-expressed here without its CSV export and
external spreadsheet launcher: the data stays inside DeckCraft.

Model (chart_data):
- One row per point, series names on the header row, and the columns
  each chart type needs: categories and one value column per series;
  scatter X values (shared, or one column per series) then Y values;
  bubble sizes; stock series labelled by role (volume, open, high, low,
  close); combo series by their own type.
- Cells set from text (decimal comma, thousands separators, percent; an
  empty cell is a gap; a row past the last point adds one). Points and
  series inserted, removed and moved, point fills following their
  points; rows and columns switched; tab-separated blocks pasted at a
  cell (the table grows) and copied back. Notes for data a type can't
  show.

Commands (Chart Design > Data), for the editor, the control channel
and MCP alike:
- chart.grid (query), chart.setCell, chart.paste, chart.insertRows /
  deleteRows / moveRow, chart.insertSeries / deleteSeries / moveSeries,
  chart.switchRowColumn, chart.title.
- chart.data and insert.chart take X values and bubble sizes per series
  (or xValues for all), stock series by role, numbers as numeric text,
  and null for a gap. A data command that changes nothing leaves no
  undo step; commands carrying the same undoGroup in a row make one
  undo step, so typing in a cell updates the chart at each key and
  undoes at once.
- insert.chart opens the data editor only when asked (editData, which
  the ribbon passes); double-clicking a chart opens it. Programmatic
  calls never open dialogs.

Edit Data window (ui-egui chartdata):
- Spreadsheet letters and row numbers, columns following the chart
  type, live update at each key, a cell that isn't a number yet stays
  red; Enter and the arrow keys move; paste from Excel or LibreOffice;
  toolbar to insert, delete and move rows and series, switch row and
  column, copy, undo and redo. Chart title field and a Type menu with
  the chart gallery. It reads the chart every frame, so undo and other
  commands show at once; large tables draw only the rows in view.

Co-authored-by: yefan911 <nodmcaplsmilo@gmail.com>
An imported chart whose data changed shape got a new workbook: other
sheets, formulas and formatting were lost as soon as a series or a point
was added or removed.

- The export now maps every cell the chart read before and every cell it
  reads now. Ranges grow or shrink from their first cell; a new series
  goes next to the last one, as far from it as that one is from the one
  before (or shares the X values or categories they all share); cells no
  longer read are cleared. Series in rows work as series in columns.
- Cells read for the first time must be empty in the workbook, so
  nothing typed there is overwritten; otherwise, or when two series would
  need different values in one cell, the workbook is generated as
  before.
- A table that covers exactly the chart data follows it: its range, its
  filter range and its columns (a new column is named after its header
  cell). Tables with a totals row, sorting or filters are left alone.
- Formulas whose range did not change are written as they were read.

Checked with LibreOffice: the workbooks of every edit in the tests open
with the cells the chart formulas point to.
Model (chart_format) and commands for fonts, element boxes, legend,
axes (bounds, units, log scale, reverse, number format, tick marks,
label position and rotation, crossing, gridlines), data labels (parts,
position, number format, separator), series (fill, line, markers,
smooth, invert if negative, explosion), points, series options (gap
width, overlap, hole size, first slice angle, vary colours), Change
Colors palettes, Chart Styles, Quick Layout and Reset to Match Style.

PPTX: spPr with gradients and dashes, txPr and rich title runs, legend
overlay and box, full axes in schema order (logBase, orientation,
gridline lines, numFmt, tick marks, tickLblPos, rotation, crosses,
units), dLbls with parts, position (only where the chart type allows
it), number format and separator, markers, invertIfNegative,
explosion, dPt, firstSliceAng, palette colours, on every plot group the
chart types write (primary and secondary axes, scatter X axes, 3-D
groups, pie and bar of pie, stock charts). Main's chart fields for the
same options are written as main writes them.

The chart formatting is drawn in a later commit.
…in's chart fields

Format pane and ribbon:
- The Format pane's Chart Options picks a chart element (chart and plot
  area, title, legend, axes, axis titles, gridlines, data labels, each
  series) and shows its options: fill, border, font, legend position,
  axis bounds, units, log scale, reverse order, crossing, tick marks,
  label position and rotation, number format, gridlines, label parts and
  positions, gap width, overlap, pie angle and explosion, hole size,
  markers, smoothing and point colours. Its chart area page keeps storytold#195's
  basics: title, type (the chart gallery), elements, Edit Data.
- Chart Design gains Add Chart Element, Quick Layout, Change Colors and
  Chart Styles; the new Chart Format tab holds the element picker,
  Format Selection, Reset to Match Style, and the element's fill,
  outline and text.
- Scatter and bubble charts get X axis bounds.

Formatting follows the chart type and variant: a marker on a type
without markers turns it into its variant with markers (the series'
own type in a combo); a pie or doughnut explosion is the exploded
type's; point explosions follow points inserted, removed and moved;
styles and layouts know every pie type and combo lines.

Main's chart fields hold what both have, with no second copy in
ChartFormat: the legend overlay, each series' type and axis in a combo,
the value axes' bounds, major unit, number format, visibility, log base
and minor gridlines (Chart::y_*, y2_*, y_log_base, minor_gridlines), a
category axis' direction (Chart::cat_reversed), the chart and title
text sizes, and number formats through main's numfmt (which now rounds
halves away from zero, as spreadsheets do). chart.format.axis and the
pane set them; the reader and writer read and write them where main
does.
The chart renderer lays out the frame (chart box, title, legend) with
the chart's formatting, then fills the plot for the chart's family.

Types and variants:
- Radar charts on a web of spokes and value rings: lines, markers,
  points only, filled.
- Scatter and bubble charts on two value axes (the X axis from zero as
  main draws it), with bubble areas in proportion to their sizes.
- Stock charts: high-low lines with close ticks, or up/down bars from
  open to close when the chart has them (main's up_down_bars), volume
  columns on their own axis.
- Combo charts draw each series as its own type, with a secondary value
  axis on the right.
- Exploded pies and doughnuts, doughnut rings per series, pie of pie and
  bar of pie with their connector lines.
- 100% stacked lines and areas; stacked areas fill between series; area
  points sit on the category ticks; smooth (main's smoothed line) and
  stepped lines, marker shapes, line keys in the legend.
- A simple depth for 3-D charts: boxes, cylinders, cones and pyramids
  for columns and bars, a tilted pie with its rim.

Formatting: element fonts and boxes, legend positions and overlay, axis
titles, value scales with fixed bounds, units, log base and reverse
order, tick marks, minor and category gridlines, rotated tick labels,
gradient and outlined bars with gap and overlap, invert if negative,
marker symbols, data label parts and positions, pie first slice angle
and explosion by series and by point, palettes.

On top of main's chart drawing: waterfalls (increase, decrease and
total colours, connectors), histograms and Pareto charts (the column
chart of their bins), box and whisker boxes, trendlines with forecast
slots, error bars, log axes and minor gridlines from main's fields,
reversed categories, point fills and single-series colours by point,
Office's automatic axis limits (main's axis_scale), and main's funnel,
treemap, sunburst and region map layouts. The radar and stock drawing
of radar.rs is replaced by the painter's, which also draws markers,
points-only radars, volume and formatting.

Tests: every gallery entry draws, variants differ, odd data (no series,
NaN, huge values, thousands of points) never panics, formatting changes
the picture on every type.
chart-type-parity.md rebased on main 9859da3 (main's Office 2016 types in the gallery, main's fields for what both have); checklist rows for chart type variants and chart formatting, Edit chart data and chart elements done, summary regenerated by cargo xtask parity; gaps, file-format-parity, ui-parity and the ROADMAP progress log.
@pierrevbc pierrevbc changed the title Charts: every LibreOffice chart type, data editing and formatting for each Charts: every LibreOffice chart type, a data editor, and formatting for each Oct 15, 2026
@pierrevbc

Copy link
Copy Markdown
Contributor Author

Thanks for the review, and for the clear description of what an acceptable shape looks like.

I've reworked the branch along those lines and force-pushed it:

cargo xtask ci passes. The PR description has the commit list, what comes from main, and the
known limits.

anishguduru pushed a commit to anishguduru/deckcraft that referenced this pull request Oct 16, 2026
…name says (.png, .jpg, .tif or .pam) (storytold#255)

`render --out p1.png` wrote a netpbm PAM stream (P7) whatever the name,
so a `.png` could not be opened by any PNG reader. The CLI already links
the engine, whose export module encodes PNG, JPEG and TIFF, so the output
format now follows the extension: `.png`, `.jpg`/`.jpeg` (quality 90),
`.tif`/`.tiff`, and `.pam` for the raw stream as before. Any other name
is refused up front, before the page is rendered, with the accepted list.

`export::encode_image` wraps the three encoders behind `ImageFormat` so
the CLI and `Exporter::image` share one match.

Tests (apps/pdfcraft-cli/tests/render.rs): a `.png` gets the PNG
signature and an IHDR with the rendered size; `.JPG` and `.tif` get
JPEG/TIFF magic; `.pam` still gets the P7 header and the right number of
bytes; `.bmp` fails with the message and writes nothing.

Co-authored-by: wstock <1394687+wstock@users.noreply.github.com>
Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
Co-authored-by: Brandon Thomas <echelon@gmail.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants