docxchart
Edit the data behind Word charts without disturbing the formatting.
Extract every chart's values from a .docx to JSON, edit the JSON, apply it
back. Fills, label positions, axis settings and theme bindings are never
touched.
Status: early. Handles same-length value edits on standard category charts, and setting the input cells of embedded Excel worksheets (including dropdowns). Anything it can't handle raises rather than guessing — see Limitations.
Install
pip install docxchart
Requires Python 3.11+.
CLI
docxchart list report.docx
docxchart extract report.docx -o data.json
docxchart apply report.docx --data data.json -o report-updated.docx
| Command | |
|---|---|
list FILE |
chart keys, titles, and shape |
extract FILE [-o OUT] |
write chart data to JSON (default data.json) |
apply FILE --data JSON [-o OUT] |
write edited data back to a new docx |
--sidecar |
on extract, also emit the internal manifest |
apply never writes in place. Omit -o and it errors.
Data format
{
"_docxchart": {
"source": "report.docx",
"basedOn": "sha256:1f3a…",
"extractedAt": "2026-07-29T09:14:22Z",
"formatVersion": "1.0"
},
"charts": [
{
"key": "revenue_by_quarter",
"_meta": {
"title": "Revenue by Quarter",
"type": "barChart",
"keyStable": true,
"readOnly": true
},
"series": [
{ "name": "2024", "formatCode": "#,##0" },
{ "name": "2025", "formatCode": "#,##0" }
],
"rows": [
{ "category": "Q1", "2024": 120, "2025": 130 },
{ "category": "Q2", "2024": null, "2025": 110 },
{ "category": "Q3", "2024": 95, "2025": 99 }
]
}
]
}
| Field | |
|---|---|
key |
chart identifier (see Naming) |
_meta |
read-only. apply errors if changed. |
series |
display order of the series. Order matters for legend and stacking. |
rows |
one row per category. Series values keyed by series name. |
basedOn |
hash of the source docx. apply refuses if the file has changed since extract. |
null is a genuine blank in the chart, rendered as a gap. It is not zero.
Omitting a key means the same thing.
Dates are stored as Excel serial numbers with a formatCode, and are passed
through unconverted.
Python API
from docxchart import ChartDoc
doc = ChartDoc.open("report.docx")
list(doc.charts)
# ['revenue_by_quarter', 'headcount']
chart = doc.charts["revenue_by_quarter"]
chart.table
# ChartTable(
# categories=['Q1', 'Q2', 'Q3'],
# series=[Series(name='2024', values=[120, None, 95])],
# )
chart.table.series[0].values = [130, 110, 99] # values (same count)
chart.table.series[0].name = "FY2024" # series name / legend text
chart.table.categories[1] = "Q2 (revised)" # category label (same count)
chart.table.hide_legend_entry(3) # suppress an empty series' legend entry
chart.table.insert_category(1, "Q1.5") # add a row (every series gets a blank)
chart.table.series[0].values[1] = 88 # then fill it
chart.table.delete_category(4) # remove a row
doc.validate() # -> list[Finding]
doc.save("report-updated.docx")
ChartTable and Series are plain dataclasses. No XML is exposed.
Category labels are editable (the count is fixed — adding/removing rows is
not supported). A label write fans out to every series' c:cat and is mirrored
into the embedded workbook; None is a genuine blank, and labels round-trip
byte-exactly (trailing spaces and footnote markers are preserved).
hide_legend_entry(index) / show_legend_entry(index) (or
series.legend_hidden = True) suppress or restore a series' legend entry via
c:legendEntry/c:delete — useful when a template carries a spare, empty series
that would otherwise show a blank rater in the legend.
Category rows can be added and removed with insert_category(index, label)
and delete_category(index) (programmatic path only). This resizes every
series' cache, rewrites the c:f ranges, inserts/deletes the embedded-workbook
row, and shifts per-point formatting (c:dPt/c:dLbl). A chart carrying overlay
shapes (c:userShapes) is refused — a resize moves the plot area out from
under absolutely-positioned annotations — unless you pass force=True.
Embedded worksheets
Some documents embed a live Excel worksheet as an OLE object (an
Excel.Sheet object) rather than a chart — often a small "raw scores in,
converted results out" table. docxchart can set the input cells of these
programmatically, alongside charts.
docxchart list report.docx # embedded worksheets show up too
docxchart extract report.docx -o data.json
docxchart apply report.docx --data data.json -o report-updated.docx
extract adds a worksheets section:
{
"worksheets": [
{
"key": "worksheet_1",
"_meta": { "progId": "Excel.Sheet.12", "sheet": "Sheet1", "readOnly": true },
"cells": { "A1": 40, "A2": 20, "B1": "NEUROTICISM" },
"_derived": { "C1": "=NORM.DIST(A1, 50, 10, TRUE) * 100" }
}
]
}
cellsare the constant (non-formula) cells — the editable inputs._derivedlists the formula cells for context. It is read-only: those values are computed by Excel, not by you.applyerrors if you change one, or if you put a formula cell incells._dropdowns(when present) lists each list-validated cell and its allowed values. You still set the value incells, but it must be one of the options —applyrefuses anything else (e.g. a CliftonStrengths cell that only accepts the 34 theme names).
doc = ChartDoc.open("report.docx")
ws = doc.worksheets["worksheet_1"]
ws.set_cell("A1", 63) # one input cell
ws.set_cells({"A2": 11, "A3": 50}) # several
doc.save("report-updated.docx")
Only cell values change; conditional formatting, formulas, number formats
and every other byte are preserved (the inner .xlsx is edited surgically, not
reserialized).
One thing to know about the display. What Word draws for an embedded object
is a cached picture, and the formula cells (C/D/E…) are recomputed by
Excel — neither refreshes just from editing the data. After apply, activate
the object in Word once (double-click it, or update fields / print) and Excel
recomputes the formulas and regenerates the picture. docxchart sets
fullCalcOnLoad so that recompute is guaranteed when it happens; it cannot
regenerate the picture itself (that needs an Office engine).
Editing input cells that don't exist yet (extending the input range) is not supported yet — it raises rather than guessing a new cell's formatting.
To inspect one of these documents — inputs, formulas, dropdown options, and the cached preview picture — and to see for yourself that editing the data leaves the picture untouched until Word re-renders:
python scripts/probe_worksheet.py report.docx
python scripts/probe_worksheet.py report.docx --set C2=Strategic -o out.docx
Naming
Charts are identified by their Alt Text, which survives Word re-saves, insertions and deletions — unlike file paths and relationship IDs.
- Right-click the chart → Edit Alt Text
- Enter a key, e.g.
revenue_by_quarter
Without Alt Text, the key falls back to the chart's title, slugified. This
works, but the key changes if anyone edits the title. list marks these as
unstable. Duplicate titles are suffixed by document order: revenue,
revenue-2.
Limitations
Unsupported cases raise UnsupportedChart or UnsupportedEdit with a reason.
Nothing is written on a partial or best-guess basis.
Not supported:
- Adding or removing categories via the JSON
applypath — use the programmaticinsert_category/delete_categoryinstead (row resize is refused on charts withc:userShapesunless forced) - Adding or removing series
- Scatter and bubble charts
chartExtypes: waterfall, treemap, sunburst, funnel, histogram, box & whisker, map- Multi-level category axes
- Charts in headers, footers or footnotes
- Charts bound to an external data source
- Any change to appearance — colours, fonts, chart type, axis config and layout are read-only (suppressing a legend entry is supported; other legend styling is not)
Charts must be created in Word. docxchart only rebinds data on charts that
already exist.
Notes
Word renders from the chart's cached values, not from the embedded workbook, but
the workbook is what you see when you click Edit Data. docxchart always
writes both, so the two never disagree.
Parts of the package that weren't changed come out byte-identical.
License
MIT — see LICENSE.
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file docxchart-0.4.1.tar.gz.
File metadata
- Download URL: docxchart-0.4.1.tar.gz
- Upload date:
- Size: 58.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9258ab6e98f71609bfa691a8f8938b85e0df0ada6207e19fb78e37f94128c925
|
|
| MD5 |
9a87d760b7adb625ebff92876db335e1
|
|
| BLAKE2b-256 |
8175b646144e94fc572e83d161cf3090b6672f04665ef061d502635a30ee9211
|
File details
Details for the file docxchart-0.4.1-py3-none-any.whl.
File metadata
- Download URL: docxchart-0.4.1-py3-none-any.whl
- Upload date:
- Size: 54.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via:
twine/7.0.0 CPython/3.14.6
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
09424752d6b9597dc1c8f5d40d09acc4cda7ae5d6e234b5cafa2f89aae7ab47b
|
|
| MD5 |
09c695e548758c87707c3c4635b7dd43
|
|
| BLAKE2b-256 |
4de5ebd65fcb687be5d4b7fa56a323f56c40fd9d2df7edfe259c4e365feb1dba
|