wolfxl 1.6.1 planning note - chart contract reconciliation + 3D variants¶
Historical release note: this preserves release history and is not current public claim evidence. Use
docs/trust/public-evidence.mdand the current SOTA audit for today's claim boundary.
Date: historical planning draft; not a current release date.
WolfXL 1.6.1 closes the two debts that v1.6.0 deferred:
-
The Pod-α/Pod-β chart-dict contract gap: 37 advanced sub-feature tests in
tests/test_charts_write.pywere markedxfailin 1.6.0 because the Rust parser (src/native_writer_backend.rs::parse_chart_dict) and the Python emitter (per-classto_rust_dict) diverged on title runs, manual layout xy, gridlines, error bars, trendlines,varyColors, non-default grouping (stacked/percentStacked), scatter style, marker symbol, fill color, and invalid-input rejection. RFC-046 §10 promotes the chart-dict shape to authoritative; Pod-α′ and Pod-β′ both code to §10 verbatim. All 37 xfailed tests now pass. -
The 8 deferred chart families:
BarChart3D,LineChart3D,PieChart3D(aliasPie3D),AreaChart3D,SurfaceChart,SurfaceChart3D,StockChart,ProjectedPieChartship as real classes, replacing the v1.6.0NotImplementedErrorstubs.
Plus: modify-mode Worksheet.add_chart(BarChart(...)) no longer
drops with RuntimeWarning — Pod-γ′ wires the high-level bridge
through Pod-α′'s new serialize_chart_dict PyO3 export.
TL;DR¶
- 37 chart-write xfailed tests → pass. The module-level
pytest.mark.xfailontests/test_charts_write.pyis removed (Pod-δ′); every advanced sub-feature now passes on the green path. - 8 new chart families at full openpyxl 3.1.x depth: 3D
variants (
BarChart3D,LineChart3D,PieChart3D/Pie3D,AreaChart3D,SurfaceChart3D), 2DSurfaceChart,StockChart(OHLC),ProjectedPieChart(pie-of-pie / bar-of-pie). 9 new parity-ratchet entries intests/parity/openpyxl_surface.py. - Modify-mode high-level
add_chartworks end-to-end. The v1.6.0 warn-and-drop fallback inWorkbook._flush_pending_charts_to_patcheris replaced with a real dict→bytes bridge via the newserialize_chart_dictPyO3 helper. - Construction-time validation. Empty series, bad anchor (not
A1 and not a recognised anchor object), out-of-range
Referencebounds, out-of-rangestyle/gap_width/overlap/hole_size/bubble_scale/ poly trendlineorder, anddisplay_blanks_asoutside the closed enum all raiseValueError/TypeErrorat construction oradd_chart()time, not at save.
What's new¶
Chart 3D / Stock / Surface / ProjectedPie families¶
BarChart3D (RFC-046 §11.1)¶
from wolfxl.chart import BarChart3D, Reference
import wolfxl
wb = wolfxl.Workbook()
ws = wb.active
ws.append(["Region", "Q1", "Q2", "Q3", "Q4"])
ws.append(["NA", 100, 120, 90, 140])
ws.append(["EU", 80, 95, 110, 85])
ws.append(["APAC", 60, 70, 85, 100])
chart = BarChart3D()
chart.title = "Quarterly Revenue (3D)"
# view_3d defaults: rot_x=15, rot_y=20, right_angle_axes=True,
# depth_percent=100 — override on the chart instance:
chart.view_3d.rot_x = 30
chart.view_3d.depth_percent = 150
data = Reference(ws, min_col=2, min_row=1, max_col=5, max_row=4)
cats = Reference(ws, min_col=1, min_row=2, max_row=4)
chart.add_data(data, titles_from_data=True)
chart.set_categories(cats)
ws.add_chart(chart, "G2")
wb.save("revenue_3d.xlsx")
LineChart3D / AreaChart3D / PieChart3D / SurfaceChart3D¶
All five 3D variants share the view_3d field; per-type defaults
follow openpyxl 3.1.x (see RFC-046 §11.1 table). Pie3D is the
openpyxl alias for PieChart3D and is exposed at the same name.
from wolfxl.chart import LineChart3D, Reference
chart = LineChart3D()
# defaults: rot_x=15, rot_y=20, perspective=30,
# right_angle_axes=False, depth_percent=100
chart.title = "3D revenue lines"
data = Reference(ws, min_col=2, min_row=1, max_col=4, max_row=10)
chart.add_data(data, titles_from_data=True)
ws.add_chart(chart, "G2")
StockChart (OHLC)¶
StockChart validates its 4-series Open / High / Low / Close
ordering at construction. Pass series in the wrong order and you
get a ValueError immediately, not on save.
from wolfxl.chart import StockChart, Reference, Series
ws.append(["Date", "Open", "High", "Low", "Close"])
ws.append(["2026-04-01", 100.5, 105.0, 99.0, 104.0])
ws.append(["2026-04-02", 104.0, 107.5, 103.5, 106.0])
# … more rows …
chart = StockChart()
xvals = Reference(ws, min_col=1, min_row=2, max_row=N)
for col, name in [(2, "Open"), (3, "High"), (4, "Low"), (5, "Close")]:
yvals = Reference(ws, min_col=col, min_row=2, max_row=N)
chart.series.append(Series(yvals, xvals, title=name))
ws.add_chart(chart, "G2")
# Emits <c:stockChart> with <c:hiLowLines/> + <c:upDownBars/>
SurfaceChart / SurfaceChart3D¶
from wolfxl.chart import SurfaceChart3D, Reference
chart = SurfaceChart3D(wireframe=True)
chart.title = "Surface plot"
data = Reference(ws, min_col=2, min_row=1, max_col=11, max_row=11)
cats = Reference(ws, min_col=1, min_row=2, max_row=11)
chart.add_data(data, titles_from_data=True)
chart.set_categories(cats)
ws.add_chart(chart, "M2")
ProjectedPieChart (pie-of-pie / bar-of-pie)¶
from wolfxl.chart import ProjectedPieChart, Reference
chart = ProjectedPieChart(
of_pie_type="bar", # "bar" or "pie"
split_type="percent", # "auto" | "pos" | "percent" | "val" | "cust"
split_pos=20,
second_pie_size=75,
)
chart.title = "Sales — minor slices in secondary bar"
labels = Reference(ws, min_col=1, min_row=2, max_row=10)
data_ref = Reference(ws, min_col=2, min_row=1, max_row=10)
chart.add_data(data_ref, titles_from_data=True)
chart.set_categories(labels)
ws.add_chart(chart, "G2")
Validation at construction time¶
The 1.6.0 contract was lenient at construction time — many illegal inputs only surfaced as cryptic XML errors at save. v1.6.1 fails fast per RFC-046 §10.11. New raise sites:
from wolfxl.chart import BarChart, Reference
# Empty series — no data to plot.
chart = BarChart()
ws.add_chart(chart, "G2") # ValueError("BarChart has no series; add_data() first")
# Bad anchor — neither A1 nor a recognised anchor object.
chart = BarChart()
chart.add_data(Reference(ws, min_col=2, min_row=1, max_row=4),
titles_from_data=True)
ws.add_chart(chart, "not-an-anchor")
# ValueError("anchor must be an A1 string (e.g. 'G2') or an anchor object")
# Reference out-of-range.
Reference(ws, min_col=5, min_row=10, max_col=2, max_row=4)
# ValueError("min_col=5 > max_col=2")
# style outside 1..48
BarChart(style=99) # ValueError("style must be in 1..48; got 99")
# gap_width outside 0..500
BarChart(gap_width=600) # ValueError("gap_width must be in 0..500; got 600")
# hole_size outside 1..90
DoughnutChart(hole_size=95) # ValueError("hole_size must be in 1..90; got 95")
# bubble_scale outside 0..300
BubbleChart(bubble_scale=500) # ValueError("bubble_scale must be in 0..300; got 500")
# Poly trendline order outside 2..6
trendline = Trendline(trendline_type="poly", order=10)
# ValueError("Polynomial trendline order must be in 2..6; got 10")
# display_blanks_as outside the closed enum
BarChart(display_blanks="nope")
# ValueError("display_blanks must be 'gap', 'span', or 'zero'; got 'nope'")
3D-only fields (view_3d.rot_x / rot_y / perspective /
depth_percent / h_percent) set on a 2D chart kind warn rather
than raise, matching openpyxl's tolerant posture there.
Modify-mode high-level add_chart (Pod-γ′)¶
In v1.6.0 a high-level add_chart(BarChart(...)) in modify mode
hit a warn-and-drop fallback in
Workbook._flush_pending_charts_to_patcher: the chart was queued
on Worksheet._pending_charts but never reached the patcher
because there was no dict→bytes bridge. The bytes-level escape
hatch (Workbook.add_chart_modify_mode(sheet_name, chart_xml_bytes,
anchor)) worked, but only for callers who already had pre-built
chart XML.
v1.6.1 wires the bridge:
import wolfxl
from wolfxl.chart import BarChart, Reference
# Open an existing workbook in modify mode
wb = wolfxl.load_workbook("template.xlsx", modify=True)
ws = wb["Data"]
chart = BarChart()
chart.title = "Sales (auto-added in modify mode)"
data = Reference(ws, min_col=2, min_row=1, max_col=4, max_row=20)
cats = Reference(ws, min_col=1, min_row=2, max_row=20)
chart.add_data(data, titles_from_data=True)
chart.set_categories(cats)
ws.add_chart(chart, "G2") # 1.6.1: bridges through serialize_chart_dict
wb.save("template.xlsx") # NEW chart present, existing charts intact
The patcher Phase 2.5l (Sprint Μ Pod-γ) handles the rest exactly as in 1.6.0.
serialize_chart_dict — internal API¶
wolfxl._backend.serialize_chart_dict(chart_dict) is the new PyO3
helper that materialises a §10 chart-dict into the chart XML
bytes that the patcher's queue_chart_add expects. It is not
public surface; users should keep calling
Worksheet.add_chart(chart, anchor). The helper exists so
Workbook._flush_pending_charts_to_patcher can do the same job
without round-tripping through a temporary write-mode file.
Migration from 1.6.0¶
Most v1.6.0 → v1.6.1 changes are additive (new classes, new validation surfaces). The breaking changes are limited to internals that downstream code rarely touches.
Chart-dict shape (internal, §10)¶
If your code calls chart.to_rust_dict() directly (this is
internal API and almost no one does), the dict shape changed in
the following ways:
solidFill(camelCase) →solid_fill(snake_case). All graphical-property keys are now snake_case to match the Pod-α parser side.tagname(the openpyxl XML tag) is no longer passed through; Pod-β maps it to a shortkind("bar", "line", …) per §10.2.- Per-type unique fields (
bar_dir,grouping,gap_width,overlap,smooth,scatter_style,radar_style,first_slice_ang,hole_size,bubble_3d,bubble_scale,show_neg_bubbles,size_represents) are flat top-level keys, NOT nested in anextrasdict. view_3dis a top-level dict on 3D-kind charts.axesis no longer a list;x_axis,y_axis,z_axisare flat top-level keys, eachNonewhen not used.
Modify-mode warn-and-drop is gone¶
If your code relied on the v1.6.0 RuntimeWarning ("modify-mode
high-level add_chart is not yet wired; chart dropped") to fall
back to Workbook.add_chart_modify_mode(..., chart_xml_bytes,
...), you can drop that fallback. The high-level path now works.
The bytes-level path keeps working unchanged.
NotImplementedError on 3D / Stock / Surface / ProjectedPie is gone¶
Code that defensively caught NotImplementedError around
BarChart3D() / LineChart3D() / PieChart3D() / Pie3D() /
AreaChart3D() / SurfaceChart() / SurfaceChart3D() /
StockChart() / ProjectedPieChart() now succeeds. Audit any
try/except NotImplementedError around chart construction and
remove the fallback branch.
Known gaps remaining (now → v2.0.0)¶
- Pivot-chart linkage (Sprint Ν / v2.0.0). A chart's
<c:pivotSource>referencing a pivot cache definition cannot land before pivot caches are constructible. Pivot tables ship in v2.0.0. - Combination charts (multi-plot — bar + line on shared axes). Not on the v1.6.x roadmap; v1.6.2 if requested.
- Replace / delete existing charts. The
add_chartAPI stays additive only.remove_chart/replace_chartare not yet scheduled. - Conditional series formatting, data tables under chart, display units on value axes, dPt overrides. Tracked in RFC-046 §9 with deferral rationale; no v1.6.x commitment.
Pods that landed 1.6.1¶
- Pod-α′ — Rust parser extensions (
parse_chart_dictcovers §10 verbatim, 8 newChartKindvariants, per-3D-family emit fns) + the newserialize_chart_dictPyO3 export. Commits:ba16137,37a3a6b. Merged via6c5425c. - Pod-β′ — Python flat
to_rust_dict(matches §10) + 8 new chart-family classes (BarChart3D,LineChart3D,PieChart3D/Pie3D,AreaChart3D,SurfaceChart,SurfaceChart3D,StockChart,ProjectedPieChart) + construction-time validation per §10.11. Commits:70ebae1,a6d7442. Merged via8612189. - Pod-γ′ — Modify-mode bridge (
_flush_pending_charts_to_patcherusesserialize_chart_dict) + 3D-family round-trip tests intests/test_charts_3d.py. Commits:70ebae1. Merged viaed08aff. - Pod-δ′ (this release scaffold) — RFC-046 §10/§11 SHA-log
scaffold, 9 new
tests/parity/openpyxl_surface.pyentries,KNOWN_GAPS.mdclose-out, this release notes scaffold,CHANGELOG.mdentry, removal of the module-level xfail ontests/test_charts_write.py. Commits:330500e. Merged via620d606. - Integrator finalize —
parse_graphical_propertiesreads both §10.9 and legacy graphical-property keys; chart-leveldata_labelspropagates to series;_DataLabelBaseaccepts openpyxl-styleposition=;Reference(None, …)rejected; anchor bounds-check against XFD/1048576; 9 ratchet entries flipped towolfxl_supported=True;Pie3Dsurface entry removed (openpyxl 3.1.x doesn't expose the alias). Commit:70492ab.
SHA log¶
| Pod | Branch | Commits | Merge |
|---|---|---|---|
| α′ | feat/sprint-mu-prime-pod-alpha |
ba16137, 37a3a6b |
6c5425c |
| β′ | feat/sprint-mu-prime-pod-beta |
70ebae1, a6d7442 |
8612189 |
| γ′ | feat/sprint-mu-prime-pod-gamma |
70ebae1 |
ed08aff |
| δ′ | feat/sprint-mu-prime-pod-delta |
330500e |
620d606 |
| — | (integrator finalize) | 70492ab |
n/a |
Integrator finalize commit fills these placeholders, performs the
post-merge ratchet flip on the 9 new chart-3D entries in
tests/parity/openpyxl_surface.py (shipped-1.6.1 tag), and tags
v1.6.1.
Original Planning Stats¶
cargo test --workspace --exclude wolfxl: 1.6.0 + per-3D-family emit tests (Pod-α′) +serialize_chart_dictroundtrip tests.pytest tests/: planned increase from about 1300+ to about 1340+ passed (Pod-β′ adds ~30 3D-family construction tests, Pod-γ′ adds ~10 modify-mode + bridge tests; Pod-δ′ removes the v1.6.0 xfail mark so the 37 previously-xfailed tests count as passing).pytest tests/parity: planned increase from about 190+ to about 200+ passed (9 new chart-3D ratchet entries flip towolfxl_supported=Truepost-merge).KNOWN_GAPS.md"Out of scope" pruned: only pivot tables + pivot charts (Sprint Ν / v2.0.0) remain on the construction side. The openpyxl-parity surface for chart construction is exhaustively shipped after 1.6.1.
After 1.6.1 the only remaining v2.0.0 milestone is pivot tables + pivot charts + public launch. Sprint Ν owns that slice; the openpyxl-parity construction roadmap is otherwise complete.
RFCs¶
Plans/rfcs/046-chart-construction.md§10 (chart-dict contract) and §11 (3D / Stock / Surface / ProjectedPie families) — the Sprint Μ-prime preamble. The §12 SHA log gains the v1.6.1 sub-table on integrator finalize.
Acknowledgments¶
Sprint Μ-prime ("Mu-prime") closes out the Sprint Μ chart-construction
slice end-to-end. Thanks to everyone who file-bugged the v1.6.0
xfailed tests and the NotImplementedError on 3D variants — every
report drove this slice.