python-pptx2 2.13.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pptx2/__init__.py +152 -0
- pptx2/_color.py +75 -0
- pptx2/_slide_importer.py +597 -0
- pptx2/_svg.py +155 -0
- pptx2/_template_applier.py +292 -0
- pptx2/_textstyle.py +187 -0
- pptx2/accessibility.py +365 -0
- pptx2/action.py +270 -0
- pptx2/animation.py +2237 -0
- pptx2/api.py +49 -0
- pptx2/audit.py +258 -0
- pptx2/chart/__init__.py +0 -0
- pptx2/chart/analytics.py +381 -0
- pptx2/chart/axis.py +543 -0
- pptx2/chart/category.py +200 -0
- pptx2/chart/chart.py +670 -0
- pptx2/chart/data.py +864 -0
- pptx2/chart/datalabel.py +406 -0
- pptx2/chart/legend.py +86 -0
- pptx2/chart/marker.py +70 -0
- pptx2/chart/palettes.py +129 -0
- pptx2/chart/plot.py +462 -0
- pptx2/chart/point.py +101 -0
- pptx2/chart/quick_layouts.py +325 -0
- pptx2/chart/series.py +334 -0
- pptx2/chart/xlsx.py +272 -0
- pptx2/chart/xmlwriter.py +1845 -0
- pptx2/compose/__init__.py +28 -0
- pptx2/compose/from_spec.py +1094 -0
- pptx2/design/__init__.py +8 -0
- pptx2/design/components.py +607 -0
- pptx2/design/figures.py +389 -0
- pptx2/design/layout.py +370 -0
- pptx2/design/recipes.py +1967 -0
- pptx2/design/style.py +209 -0
- pptx2/design/tokens.py +915 -0
- pptx2/diagrams.py +754 -0
- pptx2/dml/__init__.py +0 -0
- pptx2/dml/chtfmt.py +40 -0
- pptx2/dml/color.py +496 -0
- pptx2/dml/effect.py +909 -0
- pptx2/dml/fill.py +691 -0
- pptx2/dml/line.py +287 -0
- pptx2/dml/picture.py +212 -0
- pptx2/dml/three_d.py +381 -0
- pptx2/enum/__init__.py +0 -0
- pptx2/enum/action.py +71 -0
- pptx2/enum/animation.py +31 -0
- pptx2/enum/base.py +218 -0
- pptx2/enum/chart.py +574 -0
- pptx2/enum/dml.py +740 -0
- pptx2/enum/lang.py +685 -0
- pptx2/enum/presentation.py +133 -0
- pptx2/enum/shapes.py +1029 -0
- pptx2/enum/text.py +230 -0
- pptx2/exc.py +42 -0
- pptx2/formats.py +139 -0
- pptx2/geometry.py +420 -0
- pptx2/inherit.py +109 -0
- pptx2/lint.py +2256 -0
- pptx2/math.py +177 -0
- pptx2/media.py +197 -0
- pptx2/opc/__init__.py +0 -0
- pptx2/opc/constants.py +332 -0
- pptx2/opc/oxml.py +188 -0
- pptx2/opc/package.py +762 -0
- pptx2/opc/packuri.py +109 -0
- pptx2/opc/serialized.py +296 -0
- pptx2/opc/shared.py +20 -0
- pptx2/opc/spec.py +45 -0
- pptx2/oxml/__init__.py +555 -0
- pptx2/oxml/action.py +53 -0
- pptx2/oxml/chart/__init__.py +0 -0
- pptx2/oxml/chart/axis.py +337 -0
- pptx2/oxml/chart/chart.py +481 -0
- pptx2/oxml/chart/datalabel.py +253 -0
- pptx2/oxml/chart/legend.py +72 -0
- pptx2/oxml/chart/marker.py +61 -0
- pptx2/oxml/chart/plot.py +365 -0
- pptx2/oxml/chart/series.py +425 -0
- pptx2/oxml/chart/shared.py +220 -0
- pptx2/oxml/coreprops.py +288 -0
- pptx2/oxml/dml/__init__.py +0 -0
- pptx2/oxml/dml/color.py +135 -0
- pptx2/oxml/dml/effect.py +213 -0
- pptx2/oxml/dml/fill.py +316 -0
- pptx2/oxml/dml/line.py +12 -0
- pptx2/oxml/dml/three_d.py +110 -0
- pptx2/oxml/ns.py +135 -0
- pptx2/oxml/presentation.py +313 -0
- pptx2/oxml/shapes/__init__.py +19 -0
- pptx2/oxml/shapes/autoshape.py +467 -0
- pptx2/oxml/shapes/connector.py +107 -0
- pptx2/oxml/shapes/graphfrm.py +347 -0
- pptx2/oxml/shapes/groupshape.py +329 -0
- pptx2/oxml/shapes/picture.py +270 -0
- pptx2/oxml/shapes/shared.py +577 -0
- pptx2/oxml/simpletypes.py +1027 -0
- pptx2/oxml/slide.py +563 -0
- pptx2/oxml/table.py +650 -0
- pptx2/oxml/text.py +815 -0
- pptx2/oxml/theme.py +36 -0
- pptx2/oxml/xmlchemy.py +717 -0
- pptx2/package.py +222 -0
- pptx2/parts/__init__.py +0 -0
- pptx2/parts/chart.py +95 -0
- pptx2/parts/coreprops.py +167 -0
- pptx2/parts/diagram.py +37 -0
- pptx2/parts/embeddedpackage.py +93 -0
- pptx2/parts/image.py +275 -0
- pptx2/parts/media.py +37 -0
- pptx2/parts/presentation.py +136 -0
- pptx2/parts/slide.py +371 -0
- pptx2/presentation.py +408 -0
- pptx2/py.typed +0 -0
- pptx2/render.py +586 -0
- pptx2/section.py +272 -0
- pptx2/shapes/__init__.py +26 -0
- pptx2/shapes/autoshape.py +442 -0
- pptx2/shapes/base.py +1078 -0
- pptx2/shapes/connector.py +297 -0
- pptx2/shapes/freeform.py +337 -0
- pptx2/shapes/graphfrm.py +316 -0
- pptx2/shapes/group.py +264 -0
- pptx2/shapes/picture.py +422 -0
- pptx2/shapes/placeholder.py +468 -0
- pptx2/shapes/shapetree.py +2027 -0
- pptx2/shared.py +82 -0
- pptx2/skill/SKILL.md +450 -0
- pptx2/skill/__init__.py +78 -0
- pptx2/skill/__main__.py +64 -0
- pptx2/skill/references/animations.md +189 -0
- pptx2/skill/references/basics.md +421 -0
- pptx2/skill/references/charts.md +254 -0
- pptx2/skill/references/compose.md +234 -0
- pptx2/skill/references/design.md +366 -0
- pptx2/skill/references/effects.md +249 -0
- pptx2/skill/references/end-to-end-deck.md +231 -0
- pptx2/skill/references/geometry-and-arrows.md +334 -0
- pptx2/skill/references/lint.md +275 -0
- pptx2/skill/references/math.md +86 -0
- pptx2/skill/references/picture-effects.md +129 -0
- pptx2/skill/references/render.md +151 -0
- pptx2/skill/references/smart-art.md +75 -0
- pptx2/skill/references/space-aware-authoring.md +249 -0
- pptx2/skill/references/tables.md +244 -0
- pptx2/skill/references/theme.md +127 -0
- pptx2/skill/references/three-d.md +109 -0
- pptx2/skill/references/transitions.md +100 -0
- pptx2/slide.py +1244 -0
- pptx2/smart_art.py +220 -0
- pptx2/spec.py +633 -0
- pptx2/table.py +1181 -0
- pptx2/table_styles.py +184 -0
- pptx2/templates/default.pptx +0 -0
- pptx2/templates/docx-icon.emf +0 -0
- pptx2/templates/generic-icon.emf +0 -0
- pptx2/templates/notes.xml +23 -0
- pptx2/templates/notesMaster.xml +352 -0
- pptx2/templates/pptx-icon.emf +0 -0
- pptx2/templates/theme.xml +321 -0
- pptx2/templates/xlsx-icon.emf +0 -0
- pptx2/text/__init__.py +0 -0
- pptx2/text/fonts.py +482 -0
- pptx2/text/layout.py +374 -0
- pptx2/text/text.py +1272 -0
- pptx2/theme.py +721 -0
- pptx2/types.py +36 -0
- pptx2/util.py +263 -0
- python_pptx2-2.13.0.dist-info/METADATA +351 -0
- python_pptx2-2.13.0.dist-info/RECORD +175 -0
- python_pptx2-2.13.0.dist-info/WHEEL +5 -0
- python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
- python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
- python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,481 @@
|
|
|
1
|
+
"""Custom element classes for top-level chart-related XML elements."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import cast
|
|
6
|
+
|
|
7
|
+
from pptx2.oxml import parse_xml
|
|
8
|
+
from pptx2.oxml.chart.shared import CT_Title
|
|
9
|
+
from pptx2.oxml.ns import nsdecls, qn
|
|
10
|
+
from pptx2.oxml.simpletypes import ST_Style, XsdString
|
|
11
|
+
from pptx2.oxml.text import CT_TextBody
|
|
12
|
+
from pptx2.oxml.xmlchemy import (
|
|
13
|
+
BaseOxmlElement,
|
|
14
|
+
OneAndOnlyOne,
|
|
15
|
+
RequiredAttribute,
|
|
16
|
+
ZeroOrMore,
|
|
17
|
+
ZeroOrOne,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CT_Chart(BaseOxmlElement):
|
|
22
|
+
"""`c:chart` custom element class."""
|
|
23
|
+
|
|
24
|
+
_tag_seq = (
|
|
25
|
+
"c:title",
|
|
26
|
+
"c:autoTitleDeleted",
|
|
27
|
+
"c:pivotFmts",
|
|
28
|
+
"c:view3D",
|
|
29
|
+
"c:floor",
|
|
30
|
+
"c:sideWall",
|
|
31
|
+
"c:backWall",
|
|
32
|
+
"c:plotArea",
|
|
33
|
+
"c:legend",
|
|
34
|
+
"c:plotVisOnly",
|
|
35
|
+
"c:dispBlanksAs",
|
|
36
|
+
"c:showDLblsOverMax",
|
|
37
|
+
"c:extLst",
|
|
38
|
+
)
|
|
39
|
+
title = ZeroOrOne("c:title", successors=_tag_seq[1:])
|
|
40
|
+
autoTitleDeleted = ZeroOrOne("c:autoTitleDeleted", successors=_tag_seq[2:])
|
|
41
|
+
plotArea = OneAndOnlyOne("c:plotArea")
|
|
42
|
+
legend = ZeroOrOne("c:legend", successors=_tag_seq[9:])
|
|
43
|
+
rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def has_legend(self):
|
|
47
|
+
"""
|
|
48
|
+
True if this chart has a legend defined, False otherwise.
|
|
49
|
+
"""
|
|
50
|
+
legend = self.legend
|
|
51
|
+
if legend is None:
|
|
52
|
+
return False
|
|
53
|
+
return True
|
|
54
|
+
|
|
55
|
+
@has_legend.setter
|
|
56
|
+
def has_legend(self, bool_value):
|
|
57
|
+
"""
|
|
58
|
+
Add, remove, or leave alone the ``<c:legend>`` child element depending
|
|
59
|
+
on current state and *bool_value*. If *bool_value* is |True| and no
|
|
60
|
+
``<c:legend>`` element is present, a new default element is added.
|
|
61
|
+
When |False|, any existing legend element is removed.
|
|
62
|
+
"""
|
|
63
|
+
if bool(bool_value) is False:
|
|
64
|
+
self._remove_legend()
|
|
65
|
+
else:
|
|
66
|
+
if self.legend is None:
|
|
67
|
+
self._add_legend()
|
|
68
|
+
|
|
69
|
+
@staticmethod
|
|
70
|
+
def new_chart(rId: str) -> CT_Chart:
|
|
71
|
+
"""Return a new `c:chart` element."""
|
|
72
|
+
return cast(CT_Chart, parse_xml(f'<c:chart {nsdecls("c")} {nsdecls("r")} r:id="{rId}"/>'))
|
|
73
|
+
|
|
74
|
+
def _new_title(self):
|
|
75
|
+
return CT_Title.new_title()
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
class CT_ChartSpace(BaseOxmlElement):
|
|
79
|
+
"""`c:chartSpace` root element of a chart part."""
|
|
80
|
+
|
|
81
|
+
_tag_seq = (
|
|
82
|
+
"c:date1904",
|
|
83
|
+
"c:lang",
|
|
84
|
+
"c:roundedCorners",
|
|
85
|
+
"c:style",
|
|
86
|
+
"c:clrMapOvr",
|
|
87
|
+
"c:pivotSource",
|
|
88
|
+
"c:protection",
|
|
89
|
+
"c:chart",
|
|
90
|
+
"c:spPr",
|
|
91
|
+
"c:txPr",
|
|
92
|
+
"c:externalData",
|
|
93
|
+
"c:printSettings",
|
|
94
|
+
"c:userShapes",
|
|
95
|
+
"c:extLst",
|
|
96
|
+
)
|
|
97
|
+
date1904 = ZeroOrOne("c:date1904", successors=_tag_seq[1:])
|
|
98
|
+
style = ZeroOrOne("c:style", successors=_tag_seq[4:])
|
|
99
|
+
chart = OneAndOnlyOne("c:chart")
|
|
100
|
+
txPr = ZeroOrOne("c:txPr", successors=_tag_seq[10:])
|
|
101
|
+
externalData = ZeroOrOne("c:externalData", successors=_tag_seq[11:])
|
|
102
|
+
del _tag_seq
|
|
103
|
+
|
|
104
|
+
@property
|
|
105
|
+
def catAx_lst(self):
|
|
106
|
+
return self.chart.plotArea.catAx_lst
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def date_1904(self):
|
|
110
|
+
"""
|
|
111
|
+
Return |True| if the `c:date1904` child element resolves truthy,
|
|
112
|
+
|False| otherwise. This value indicates whether date number values
|
|
113
|
+
are based on the 1900 or 1904 epoch.
|
|
114
|
+
"""
|
|
115
|
+
date1904 = self.date1904
|
|
116
|
+
if date1904 is None:
|
|
117
|
+
return False
|
|
118
|
+
return date1904.val
|
|
119
|
+
|
|
120
|
+
@property
|
|
121
|
+
def dateAx_lst(self):
|
|
122
|
+
return self.xpath("c:chart/c:plotArea/c:dateAx")
|
|
123
|
+
|
|
124
|
+
def get_or_add_title(self):
|
|
125
|
+
"""Return the `c:title` grandchild, newly created if not present."""
|
|
126
|
+
return self.chart.get_or_add_title()
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def plotArea(self):
|
|
130
|
+
"""
|
|
131
|
+
Return the required `c:chartSpace/c:chart/c:plotArea` grandchild
|
|
132
|
+
element.
|
|
133
|
+
"""
|
|
134
|
+
return self.chart.plotArea
|
|
135
|
+
|
|
136
|
+
@property
|
|
137
|
+
def valAx_lst(self):
|
|
138
|
+
return self.chart.plotArea.valAx_lst
|
|
139
|
+
|
|
140
|
+
@property
|
|
141
|
+
def xlsx_part_rId(self):
|
|
142
|
+
"""
|
|
143
|
+
The string in the required ``r:id`` attribute of the
|
|
144
|
+
`<c:externalData>` child, or |None| if no externalData element is
|
|
145
|
+
present.
|
|
146
|
+
"""
|
|
147
|
+
externalData = self.externalData
|
|
148
|
+
if externalData is None:
|
|
149
|
+
return None
|
|
150
|
+
return externalData.rId
|
|
151
|
+
|
|
152
|
+
def _add_externalData(self):
|
|
153
|
+
"""
|
|
154
|
+
Always add a ``<c:autoUpdate val="0"/>`` child so auto-updating
|
|
155
|
+
behavior is off by default.
|
|
156
|
+
"""
|
|
157
|
+
externalData = self._new_externalData()
|
|
158
|
+
externalData._add_autoUpdate(val=False)
|
|
159
|
+
self._insert_externalData(externalData)
|
|
160
|
+
return externalData
|
|
161
|
+
|
|
162
|
+
def _new_txPr(self):
|
|
163
|
+
return CT_TextBody.new_txPr()
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class CT_ExternalData(BaseOxmlElement):
|
|
167
|
+
"""
|
|
168
|
+
`<c:externalData>` element, defining link to embedded Excel package part
|
|
169
|
+
containing the chart data.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
autoUpdate = ZeroOrOne("c:autoUpdate")
|
|
173
|
+
rId = RequiredAttribute("r:id", XsdString)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class CT_PlotArea(BaseOxmlElement):
|
|
177
|
+
"""
|
|
178
|
+
``<c:plotArea>`` element.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
catAx = ZeroOrMore("c:catAx")
|
|
182
|
+
valAx = ZeroOrMore("c:valAx")
|
|
183
|
+
|
|
184
|
+
def iter_sers(self):
|
|
185
|
+
"""
|
|
186
|
+
Generate each of the `c:ser` elements in this chart, ordered first by
|
|
187
|
+
the document order of the containing xChart element, then by their
|
|
188
|
+
ordering within the xChart element (not necessarily document order).
|
|
189
|
+
"""
|
|
190
|
+
for xChart in self.iter_xCharts():
|
|
191
|
+
for ser in xChart.iter_sers():
|
|
192
|
+
yield ser
|
|
193
|
+
|
|
194
|
+
def iter_xCharts(self):
|
|
195
|
+
"""
|
|
196
|
+
Generate each xChart child element in document.
|
|
197
|
+
"""
|
|
198
|
+
plot_tags = (
|
|
199
|
+
qn("c:area3DChart"),
|
|
200
|
+
qn("c:areaChart"),
|
|
201
|
+
qn("c:bar3DChart"),
|
|
202
|
+
qn("c:barChart"),
|
|
203
|
+
qn("c:bubbleChart"),
|
|
204
|
+
qn("c:doughnutChart"),
|
|
205
|
+
qn("c:line3DChart"),
|
|
206
|
+
qn("c:lineChart"),
|
|
207
|
+
qn("c:ofPieChart"),
|
|
208
|
+
qn("c:pie3DChart"),
|
|
209
|
+
qn("c:pieChart"),
|
|
210
|
+
qn("c:radarChart"),
|
|
211
|
+
qn("c:scatterChart"),
|
|
212
|
+
qn("c:stockChart"),
|
|
213
|
+
qn("c:surface3DChart"),
|
|
214
|
+
qn("c:surfaceChart"),
|
|
215
|
+
)
|
|
216
|
+
|
|
217
|
+
for child in self.iterchildren():
|
|
218
|
+
if child.tag not in plot_tags:
|
|
219
|
+
continue
|
|
220
|
+
yield child
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def last_ser(self):
|
|
224
|
+
"""
|
|
225
|
+
Return the last `<c:ser>` element in the last xChart element, based
|
|
226
|
+
on series order (not necessarily the same element as document order).
|
|
227
|
+
"""
|
|
228
|
+
last_xChart = self.xCharts[-1]
|
|
229
|
+
sers = last_xChart.sers
|
|
230
|
+
if not sers:
|
|
231
|
+
return None
|
|
232
|
+
return sers[-1]
|
|
233
|
+
|
|
234
|
+
@property
|
|
235
|
+
def next_idx(self):
|
|
236
|
+
"""
|
|
237
|
+
Return the next available `c:ser/c:idx` value within the scope of
|
|
238
|
+
this chart, the maximum idx value found on existing series,
|
|
239
|
+
incremented by one.
|
|
240
|
+
"""
|
|
241
|
+
idx_vals = [s.idx.val for s in self.sers]
|
|
242
|
+
if not idx_vals:
|
|
243
|
+
return 0
|
|
244
|
+
return max(idx_vals) + 1
|
|
245
|
+
|
|
246
|
+
@property
|
|
247
|
+
def next_order(self):
|
|
248
|
+
"""
|
|
249
|
+
Return the next available `c:ser/c:order` value within the scope of
|
|
250
|
+
this chart, the maximum order value found on existing series,
|
|
251
|
+
incremented by one.
|
|
252
|
+
"""
|
|
253
|
+
order_vals = [s.order.val for s in self.sers]
|
|
254
|
+
if not order_vals:
|
|
255
|
+
return 0
|
|
256
|
+
return max(order_vals) + 1
|
|
257
|
+
|
|
258
|
+
@property
|
|
259
|
+
def sers(self):
|
|
260
|
+
"""
|
|
261
|
+
Return a sequence containing all the `c:ser` elements in this chart,
|
|
262
|
+
ordered first by the document order of the containing xChart element,
|
|
263
|
+
then by their ordering within the xChart element (not necessarily
|
|
264
|
+
document order).
|
|
265
|
+
"""
|
|
266
|
+
return tuple(self.iter_sers())
|
|
267
|
+
|
|
268
|
+
@property
|
|
269
|
+
def xCharts(self):
|
|
270
|
+
"""
|
|
271
|
+
Return a sequence containing all the `c:{x}Chart` elements in this
|
|
272
|
+
chart, in document order.
|
|
273
|
+
"""
|
|
274
|
+
return tuple(self.iter_xCharts())
|
|
275
|
+
|
|
276
|
+
# -- secondary-axis support -----------------------------------------
|
|
277
|
+
|
|
278
|
+
def iter_axIds(self):
|
|
279
|
+
"""Generate every ``c:axId`` value defined anywhere in the plot area.
|
|
280
|
+
|
|
281
|
+
Includes both the axId references inside each xChart and the axId of
|
|
282
|
+
each axis element, so a freshly-allocated id can be checked against
|
|
283
|
+
the complete set already in use.
|
|
284
|
+
"""
|
|
285
|
+
for axId in self.xpath(".//c:axId"):
|
|
286
|
+
yield int(axId.get("val"))
|
|
287
|
+
|
|
288
|
+
def _next_axId(self, used):
|
|
289
|
+
"""Return a fresh ``c:axId`` value not present in *used*.
|
|
290
|
+
|
|
291
|
+
Stays within the signed-int32 range ``1..2**31-1`` — ids at or above
|
|
292
|
+
``2**31`` make PowerPoint flag the file for repair, a release-blocking
|
|
293
|
+
bug this allocator deliberately avoids.
|
|
294
|
+
"""
|
|
295
|
+
# -- start just past the current max so the new ids read as "later"
|
|
296
|
+
# -- axes in document order, but cap into signed-int32 range and fall
|
|
297
|
+
# -- back to a linear scan if we'd overflow.
|
|
298
|
+
_INT32_MAX = 2**31 - 1
|
|
299
|
+
candidate = (max(used) + 1) if used else 1
|
|
300
|
+
if candidate > _INT32_MAX:
|
|
301
|
+
candidate = 1
|
|
302
|
+
while candidate in used or candidate < 1:
|
|
303
|
+
candidate += 1
|
|
304
|
+
if candidate > _INT32_MAX:
|
|
305
|
+
# -- wrap and scan from the bottom; the used-set is tiny so a
|
|
306
|
+
# -- free slot is guaranteed to exist far below the cap.
|
|
307
|
+
candidate = 1
|
|
308
|
+
return candidate
|
|
309
|
+
|
|
310
|
+
@property
|
|
311
|
+
def _last_xChart(self):
|
|
312
|
+
xCharts = self.xCharts
|
|
313
|
+
return xCharts[-1] if xCharts else None
|
|
314
|
+
|
|
315
|
+
@property
|
|
316
|
+
def secondary_value_axis(self):
|
|
317
|
+
"""The secondary ``c:valAx`` element, or |None| if none has been added.
|
|
318
|
+
|
|
319
|
+
The secondary value axis is the (single) visible value axis drawn on
|
|
320
|
+
the right (``axPos="r"``) — the orientation :meth:`add_secondary_value_axis`
|
|
321
|
+
always gives it. Detecting by ``axPos`` is robust for scatter / bubble
|
|
322
|
+
charts, which natively carry two value axes on a single plot, so a mere
|
|
323
|
+
count of ``c:valAx`` elements is not a reliable signal there. (Primary
|
|
324
|
+
value axes are emitted with ``axPos="l"``.)
|
|
325
|
+
"""
|
|
326
|
+
# Deliberately no `c:delete` predicate: a hidden axis writes
|
|
327
|
+
# `<c:delete/>` with its val attribute dropped (the schema default is
|
|
328
|
+
# stripped), so filtering on delete-state made a hidden secondary axis
|
|
329
|
+
# invisible here and each later access piled a fresh axis pair onto
|
|
330
|
+
# the plot area.
|
|
331
|
+
matches = self.xpath('c:valAx[c:axPos/@val="r"]')
|
|
332
|
+
return matches[0] if matches else None
|
|
333
|
+
|
|
334
|
+
def add_secondary_value_axis(self, target_xChart=None):
|
|
335
|
+
"""Create a secondary value axis and return its ``c:valAx`` element.
|
|
336
|
+
|
|
337
|
+
Allocates two fresh signed-int32 axId values, builds a secondary value
|
|
338
|
+
axis (drawn on the right) plus a hidden secondary cross axis that it
|
|
339
|
+
crosses, and re-points *target_xChart*'s two ``c:axId`` children to the
|
|
340
|
+
new ids so that plot is rendered against the secondary axes. When
|
|
341
|
+
*target_xChart* is ``None`` the front-most plot is used (back-compat).
|
|
342
|
+
|
|
343
|
+
Returns the existing secondary ``c:valAx`` if one is already present,
|
|
344
|
+
making this method idempotent.
|
|
345
|
+
"""
|
|
346
|
+
existing_secondary = self.secondary_value_axis
|
|
347
|
+
if existing_secondary is not None:
|
|
348
|
+
return existing_secondary
|
|
349
|
+
|
|
350
|
+
used = set(self.iter_axIds())
|
|
351
|
+
val2_id = self._next_axId(used)
|
|
352
|
+
used.add(val2_id)
|
|
353
|
+
cross2_id = self._next_axId(used)
|
|
354
|
+
used.add(cross2_id)
|
|
355
|
+
|
|
356
|
+
# -- mirror the primary horizontal axis kind: a real catAx
|
|
357
|
+
# -- (bar/line/area), a dateAx (date-category charts), or a valAx
|
|
358
|
+
# -- (scatter/bubble). Getting this wrong (e.g. emitting a valAx cross
|
|
359
|
+
# -- axis for a date-category chart) detaches the secondary plot from
|
|
360
|
+
# -- the deck's real category axis.
|
|
361
|
+
if self.xpath("c:catAx"):
|
|
362
|
+
cross_kind = "cat"
|
|
363
|
+
elif self.xpath("c:dateAx"):
|
|
364
|
+
cross_kind = "date"
|
|
365
|
+
else:
|
|
366
|
+
cross_kind = "val"
|
|
367
|
+
|
|
368
|
+
c = nsdecls("c")
|
|
369
|
+
valAx_xml = (
|
|
370
|
+
f"<c:valAx {c}>\n"
|
|
371
|
+
f' <c:axId val="{val2_id}"/>\n'
|
|
372
|
+
' <c:scaling><c:orientation val="minMax"/></c:scaling>\n'
|
|
373
|
+
' <c:delete val="0"/>\n'
|
|
374
|
+
' <c:axPos val="r"/>\n'
|
|
375
|
+
' <c:numFmt formatCode="General" sourceLinked="1"/>\n'
|
|
376
|
+
' <c:majorTickMark val="out"/>\n'
|
|
377
|
+
' <c:minorTickMark val="none"/>\n'
|
|
378
|
+
' <c:tickLblPos val="nextTo"/>\n'
|
|
379
|
+
f' <c:crossAx val="{cross2_id}"/>\n'
|
|
380
|
+
' <c:crosses val="max"/>\n'
|
|
381
|
+
' <c:crossBetween val="between"/>\n'
|
|
382
|
+
"</c:valAx>\n"
|
|
383
|
+
)
|
|
384
|
+
if cross_kind == "val":
|
|
385
|
+
cross_xml = (
|
|
386
|
+
f"<c:valAx {c}>\n"
|
|
387
|
+
f' <c:axId val="{cross2_id}"/>\n'
|
|
388
|
+
' <c:scaling><c:orientation val="minMax"/></c:scaling>\n'
|
|
389
|
+
' <c:delete val="1"/>\n'
|
|
390
|
+
' <c:axPos val="b"/>\n'
|
|
391
|
+
' <c:numFmt formatCode="General" sourceLinked="1"/>\n'
|
|
392
|
+
' <c:majorTickMark val="out"/>\n'
|
|
393
|
+
' <c:minorTickMark val="none"/>\n'
|
|
394
|
+
' <c:tickLblPos val="nextTo"/>\n'
|
|
395
|
+
f' <c:crossAx val="{val2_id}"/>\n'
|
|
396
|
+
' <c:crosses val="autoZero"/>\n'
|
|
397
|
+
' <c:crossBetween val="between"/>\n'
|
|
398
|
+
"</c:valAx>\n"
|
|
399
|
+
)
|
|
400
|
+
elif cross_kind == "date":
|
|
401
|
+
cross_xml = (
|
|
402
|
+
f"<c:dateAx {c}>\n"
|
|
403
|
+
f' <c:axId val="{cross2_id}"/>\n'
|
|
404
|
+
' <c:scaling><c:orientation val="minMax"/></c:scaling>\n'
|
|
405
|
+
' <c:delete val="1"/>\n'
|
|
406
|
+
' <c:axPos val="b"/>\n'
|
|
407
|
+
' <c:numFmt formatCode="General" sourceLinked="1"/>\n'
|
|
408
|
+
' <c:majorTickMark val="out"/>\n'
|
|
409
|
+
' <c:minorTickMark val="none"/>\n'
|
|
410
|
+
' <c:tickLblPos val="nextTo"/>\n'
|
|
411
|
+
f' <c:crossAx val="{val2_id}"/>\n'
|
|
412
|
+
' <c:crosses val="autoZero"/>\n'
|
|
413
|
+
' <c:auto val="1"/>\n'
|
|
414
|
+
' <c:lblOffset val="100"/>\n'
|
|
415
|
+
' <c:baseTimeUnit val="days"/>\n'
|
|
416
|
+
"</c:dateAx>\n"
|
|
417
|
+
)
|
|
418
|
+
else:
|
|
419
|
+
cross_xml = (
|
|
420
|
+
f"<c:catAx {c}>\n"
|
|
421
|
+
f' <c:axId val="{cross2_id}"/>\n'
|
|
422
|
+
' <c:scaling><c:orientation val="minMax"/></c:scaling>\n'
|
|
423
|
+
' <c:delete val="1"/>\n'
|
|
424
|
+
' <c:axPos val="b"/>\n'
|
|
425
|
+
' <c:numFmt formatCode="General" sourceLinked="1"/>\n'
|
|
426
|
+
' <c:majorTickMark val="out"/>\n'
|
|
427
|
+
' <c:minorTickMark val="none"/>\n'
|
|
428
|
+
' <c:tickLblPos val="nextTo"/>\n'
|
|
429
|
+
f' <c:crossAx val="{val2_id}"/>\n'
|
|
430
|
+
' <c:crosses val="autoZero"/>\n'
|
|
431
|
+
' <c:auto val="1"/>\n'
|
|
432
|
+
' <c:lblAlgn val="ctr"/>\n'
|
|
433
|
+
' <c:lblOffset val="100"/>\n'
|
|
434
|
+
' <c:noMultiLvlLbl val="0"/>\n'
|
|
435
|
+
"</c:catAx>\n"
|
|
436
|
+
)
|
|
437
|
+
|
|
438
|
+
valAx = parse_xml(valAx_xml)
|
|
439
|
+
cross_ax = parse_xml(cross_xml)
|
|
440
|
+
|
|
441
|
+
# -- insert the new axes after the last existing axis (or after the
|
|
442
|
+
# -- last xChart when no axes exist), preserving plotArea child order:
|
|
443
|
+
# -- ...xCharts, axes..., (dTable?, spPr?, extLst?).
|
|
444
|
+
axes = self.xpath("c:valAx | c:catAx | c:dateAx | c:serAx")
|
|
445
|
+
anchor = axes[-1] if axes else self._last_xChart
|
|
446
|
+
# -- valAx first then the (hidden) cross axis, matching PowerPoint's
|
|
447
|
+
# -- own ordering for a secondary axis.
|
|
448
|
+
anchor.addnext(cross_ax)
|
|
449
|
+
anchor.addnext(valAx)
|
|
450
|
+
|
|
451
|
+
# -- re-point the target plot (front-most by default) onto the new ids.
|
|
452
|
+
self._repoint_front_plot_axes(val2_id, cross2_id, target_xChart)
|
|
453
|
+
return valAx
|
|
454
|
+
|
|
455
|
+
def _repoint_front_plot_axes(self, val2_id, cross2_id, xChart=None):
|
|
456
|
+
"""Re-point a plot's two ``c:axId`` children to the new ids.
|
|
457
|
+
|
|
458
|
+
The category-side id is set to *cross2_id* and the value-side id to
|
|
459
|
+
*val2_id*, matching how the new secondary axes cross-reference each
|
|
460
|
+
other. *xChart* defaults to the front-most plot; pass a specific
|
|
461
|
+
``c:*Chart`` element to move that plot (combo charts have several).
|
|
462
|
+
"""
|
|
463
|
+
if xChart is None:
|
|
464
|
+
xChart = self._last_xChart
|
|
465
|
+
if xChart is None:
|
|
466
|
+
return
|
|
467
|
+
axIds = xChart.xpath("c:axId")
|
|
468
|
+
if len(axIds) < 2:
|
|
469
|
+
return
|
|
470
|
+
# -- by convention the first axId is the category axis, the second the
|
|
471
|
+
# -- value axis (the writers emit them in that order).
|
|
472
|
+
axIds[0].set("val", str(cross2_id))
|
|
473
|
+
axIds[1].set("val", str(val2_id))
|
|
474
|
+
|
|
475
|
+
|
|
476
|
+
class CT_Style(BaseOxmlElement):
|
|
477
|
+
"""
|
|
478
|
+
``<c:style>`` element; defines the chart style.
|
|
479
|
+
"""
|
|
480
|
+
|
|
481
|
+
val = RequiredAttribute("val", ST_Style)
|