Skip to content

Commit 5a75d6d

Browse files
committed
feat(charts): ChartEx (cx:) modern charts — Phase A round-trip + Phase B Waterfall writer — issue #14
Manual semantic port of the GetThematic/python-pptx master ChartEx implementation (the epic's named highest-quality cherry-pick candidate) onto this fork's ruff-formatted master. Never cherry-picked (repo §2): the diff was re-derived against current master (#19/#25 merged) and applied as additive shims onto the diverged wiring files. Delivered: - Phase A — cx: round-trip preservation for ALL chartEx types. A deck containing a PowerPoint-authored waterfall/treemap/sunburst/etc. opens, edits unrelated slides, and saves without corrupting the chartEx part. mc:AlternateContent/mc:Choice unwrap in CT_GroupShape.iter_shape_elms. - Phase B — Waterfall WRITER. add_chartex(WaterfallChartData, x,y,cx,cy) and a thin add_chart(XL_CHART_TYPE.WATERFALL, ...) dispatch shim. - Foundation: cx + mc namespaces (ns.py), RT.CHARTEX/CHART_STYLE (constants.py), GRAPHIC_DATA_URI_CHARTEX (spec.py), 28 cx: oxml element classes registered (oxml/__init__.py), ChartExPart/ChartStylePart/ ChartColorStylePart part-factory wiring (__init__.py), new oxml/chart/chartex.py, chart/chartex.py, parts/chartex.py, and WaterfallChartData in chart/data.py. - XL_CHART_TYPE extended: WATERFALL (writable) + TREEMAP/SUNBURST/FUNNEL/ BOX_WHISKER/HISTOGRAM/PARETO. The six non-waterfall types raise NotImplementedError from add_chart (round-trip only); writers tracked as a Phase C follow-up (uat/FOLLOWUP_issue14_phaseC_writers.md). Adaptation notes: - GetThematic's cxml.py changes deliberately NOT ported: they migrate to pyparsing-3 APIs but this fork pins pyparsing<3 (2.4.7). The existing generic-grammar cxml.py already parses cx: tags; porting would break. - Treemap/Sunburst writers do not exist in GetThematic; shipping unvalidated hand-authored cx: OOXML would violate round-trip safety. Tests (this fork, .venv toolchain): - pytest: 3812 passed, 0 failed (3761 baseline + 51 new chartex tests). - ruff check src tests: All checks passed; ruff format: fixed point. - behave: 1094 scenarios passed, 0 failed (32 new cx: scenarios across cht-chartex-{waterfall,roundtrip,types}.feature; baseline preserved). UAT (maintainer-gated, NOT signoff): uat/uat_cx_modern_charts.py builds a multi-slide waterfall deck + round-trip read-back, exits non-zero on failure. Agent ran it for QA-on-the-test only; visual signoff in PowerPoint/Keynote remains the maintainer's (repo §6a). Port provenance: GetThematic/python-pptx master (clean waterfall + chartEx round-trip). Refs #14.
1 parent 2b70119 commit 5a75d6d

26 files changed

Lines changed: 3629 additions & 4 deletions

docs/api/enum/XlChartType.rst

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,3 +231,24 @@ XY_SCATTER_SMOOTH
231231

232232
XY_SCATTER_SMOOTH_NO_MARKERS
233233
Scatter with Smoothed Lines and No Data Markers.
234+
235+
WATERFALL
236+
Waterfall (ChartEx). Office 2016+. Write + round-trip supported.
237+
238+
TREEMAP
239+
Treemap (ChartEx). Office 2016+. Round-trip preservation only.
240+
241+
SUNBURST
242+
Sunburst (ChartEx). Office 2016+. Round-trip preservation only.
243+
244+
FUNNEL
245+
Funnel (ChartEx). Office 2016+. Round-trip preservation only.
246+
247+
BOX_WHISKER
248+
Box & Whisker (ChartEx). Office 2016+. Round-trip preservation only.
249+
250+
HISTOGRAM
251+
Histogram (ChartEx). Office 2016+. Round-trip preservation only.
252+
253+
PARETO
254+
Pareto (ChartEx). Office 2016+. Round-trip preservation only.

docs/user/charts.rst

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,54 @@ presentation with |pp|. There are more details in the API documentation for
265265
charts here: :ref:`chart-api`
266266

267267

268+
ChartEx — modern Office 2016 charts (waterfall, treemap, sunburst, ...)
269+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
270+
271+
Office 2016 introduced a new chart family — Waterfall, Treemap, Sunburst,
272+
Funnel, Box & Whisker, Histogram, and Pareto — that lives in a separate XML
273+
namespace (``cx:``, the *chart extensions* or "chartEx" part) rather than the
274+
classic ``c:`` chart tree. |pp| supports this family with two distinct
275+
capability levels:
276+
277+
================== ============================ =========================
278+
Capability Chart types What you can do
279+
================== ============================ =========================
280+
**Write** ``WATERFALL`` Author a brand-new chart
281+
**Round-trip** ``WATERFALL``, ``TREEMAP``, Open a deck that already
282+
only ``SUNBURST``, ``FUNNEL``, contains the chart, edit
283+
``BOX_WHISKER``, unrelated slides, and
284+
``HISTOGRAM``, ``PARETO`` save without corrupting
285+
the chartEx part
286+
================== ============================ =========================
287+
288+
Authoring a waterfall chart uses the dedicated
289+
:class:`~pptx.chart.data.WaterfallChartData` container::
290+
291+
from pptx.chart.data import WaterfallChartData
292+
from pptx.enum.chart import XL_CHART_TYPE
293+
294+
chart_data = WaterfallChartData()
295+
chart_data.categories = ['Q1', 'Q2', 'Q3', 'Q4', 'Total']
296+
chart_data.add_series('Revenue', (100, 50, -30, 80, 200), subtotals=[4])
297+
298+
graphic_frame = slide.shapes.add_chart(
299+
XL_CHART_TYPE.WATERFALL, x, y, cx, cy, chart_data
300+
)
301+
302+
The returned |GraphicFrame| reports ``graphic_frame.has_chartex == True`` and
303+
its :attr:`~pptx.shapes.graphfrm.GraphicFrame.chartex` property returns a
304+
ChartEx proxy. (Classic charts continue to use ``.has_chart`` / ``.chart``.)
305+
306+
The remaining ``cx:`` types currently have **round-trip preservation only** —
307+
``add_chart`` raises ``NotImplementedError`` for them, but a deck authored in
308+
PowerPoint that already contains a treemap, sunburst, etc. will read, modify,
309+
and save without damaging the existing chart. Writer support for those types
310+
is tracked as a follow-up to issue #14.
311+
312+
The full set of ``cx:`` enum members is documented under
313+
:ref:`XlChartType`.
314+
315+
268316
About colors
269317
~~~~~~~~~~~~
270318

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
Feature: ChartEx round-trip behavior
2+
In order to preserve modern chart parts in saved presentations
3+
As a developer using python-pptx
4+
I need ChartEx content and classic-chart content to round-trip correctly
5+
6+
7+
Scenario Outline: A waterfall ChartEx part survives save and reopen
8+
Given a blank slide
9+
And ChartEx waterfall data case q4-total
10+
When I add the ChartEx waterfall via <add-path>
11+
And I round-trip the presentation for ChartEx inspection
12+
Then the active ChartEx frame exposes ChartEx but not a classic chart
13+
And the active ChartEx part content type is the ChartEx content type
14+
And the active ChartEx partname contains chartEx
15+
And the active ChartEx series values are 100.0, 50.0, -30.0, 80.0, 200.0
16+
17+
Examples: round-trip entry points
18+
| add-path |
19+
| add_chart |
20+
| add_chartex |
21+
22+
23+
Scenario: Classic and ChartEx chart frames coexist on one slide across round-trip
24+
Given a blank slide
25+
And ChartEx waterfall data case regional-rollup
26+
When I add a classic chart beside a ChartEx waterfall
27+
Then the slide has one classic chart frame and one ChartEx frame
28+
And the classic chart frame still exposes only a classic chart
29+
And the ChartEx frame still exposes only a ChartEx chart
30+
When I round-trip the presentation for ChartEx inspection
31+
Then the slide has one classic chart frame and one ChartEx frame
32+
And the classic chart frame still exposes only a classic chart
33+
And the ChartEx frame still exposes only a ChartEx chart
34+
And the active ChartEx series values are 30.0, -10.0, 25.0, 15.0, 60.0
35+
36+
37+
Scenario: A blank presentation exposes no ChartEx frames
38+
Given a blank slide
39+
Then the slide has no ChartEx graphic frames
40+
41+
42+
Scenario: A blank presentation saves without any ChartEx package parts
43+
Given a blank slide
44+
When I round-trip the presentation for ChartEx inspection
45+
Then the slide has no ChartEx graphic frames
46+
And the saved package contains no ChartEx partnames
47+
And the saved package contains no ChartEx content type declaration

features/cht-chartex-types.feature

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
Feature: ChartEx chart type members
2+
In order to use the ChartEx chart type enumeration safely
3+
As a developer using python-pptx
4+
I need deferred members to fail explicitly and modern members to exist in a private range
5+
6+
7+
Scenario Outline: Writer-deferred ChartEx types fail through add_chart
8+
Given a blank slide
9+
And ChartEx waterfall data case q4-total
10+
When I attempt to add deferred ChartEx type <member-name>
11+
Then adding deferred ChartEx type <member-name> raises NotImplementedError
12+
13+
Examples: writer-deferred ChartEx members
14+
| member-name |
15+
| TREEMAP |
16+
| SUNBURST |
17+
| FUNNEL |
18+
| BOX_WHISKER |
19+
| HISTOGRAM |
20+
| PARETO |
21+
22+
23+
Scenario Outline: ChartEx enum members exist in the private high range
24+
Then XL_CHART_TYPE.<member-name> exists with value <value>
25+
26+
Examples: ChartEx enum members
27+
| member-name | value |
28+
| WATERFALL | 1001 |
29+
| TREEMAP | 1002 |
30+
| SUNBURST | 1003 |
31+
| FUNNEL | 1004 |
32+
| BOX_WHISKER | 1005 |
33+
| HISTOGRAM | 1006 |
34+
| PARETO | 1007 |
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
Feature: ChartEx waterfall charts
2+
In order to add Office 2016 waterfall charts to a slide
3+
As a developer using python-pptx
4+
I need the ChartEx writer path to create modern chart graphic frames
5+
6+
7+
Scenario Outline: Add a waterfall chart through either public entry point
8+
Given a blank slide
9+
And ChartEx waterfall data case q4-total
10+
When I add the ChartEx waterfall via <add-path>
11+
Then the active ChartEx frame exposes ChartEx but not a classic chart
12+
And the active ChartEx chart type is waterfall
13+
And the active ChartEx series is named Revenue
14+
And the active ChartEx series values are 100.0, 50.0, -30.0, 80.0, 200.0
15+
16+
Examples: public waterfall entry points
17+
| add-path |
18+
| add_chart |
19+
| add_chartex |
20+
21+
22+
Scenario Outline: Waterfall category labels are preserved on creation
23+
Given a blank slide
24+
And ChartEx waterfall data case <data-case>
25+
When I add the ChartEx waterfall via <add-path>
26+
Then the active ChartEx category labels are <category-labels>
27+
28+
Examples: category label sets
29+
| data-case | add-path | category-labels |
30+
| q4-total | add_chart | Q1, Q2, Q3, Q4, Total |
31+
| q4-total | add_chartex | Q1, Q2, Q3, Q4, Total |
32+
| cash-bridge | add_chart | Start, Sales, Returns, Ops, Tax, End |
33+
| cash-bridge | add_chartex | Start, Sales, Returns, Ops, Tax, End |
34+
| regional-rollup | add_chart | East, West, Midwest, Online, Total |
35+
| regional-rollup | add_chartex | East, West, Midwest, Online, Total |
36+
37+
38+
Scenario Outline: Waterfall subtotal markers survive round-trip
39+
Given a blank slide
40+
And ChartEx waterfall data case <data-case>
41+
When I add the ChartEx waterfall via <add-path>
42+
And I round-trip the presentation for ChartEx inspection
43+
Then the active ChartEx frame exposes ChartEx but not a classic chart
44+
And the active ChartEx subtotal indices are <subtotal-indices>
45+
And the active ChartEx category labels are <category-labels>
46+
47+
Examples: subtotal preservation cases
48+
| data-case | add-path | subtotal-indices | category-labels |
49+
| q4-total | add_chart | 4 | Q1, Q2, Q3, Q4, Total |
50+
| q4-total | add_chartex | 4 | Q1, Q2, Q3, Q4, Total |
51+
| cash-bridge | add_chart | 0, 5 | Start, Sales, Returns, Ops, Tax, End |
52+
| cash-bridge | add_chartex | 0, 5 | Start, Sales, Returns, Ops, Tax, End |
53+
| regional-rollup | add_chart | 4 | East, West, Midwest, Online, Total |
54+
| regional-rollup | add_chartex | 4 | East, West, Midwest, Online, Total |

0 commit comments

Comments
 (0)