ps-python-pptx 1.1.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.
- pptx/__init__.py +84 -0
- pptx/action.py +270 -0
- pptx/api.py +49 -0
- pptx/chart/__init__.py +0 -0
- pptx/chart/axis.py +523 -0
- pptx/chart/category.py +200 -0
- pptx/chart/chart.py +280 -0
- pptx/chart/data.py +864 -0
- pptx/chart/datalabel.py +288 -0
- pptx/chart/legend.py +79 -0
- pptx/chart/marker.py +70 -0
- pptx/chart/plot.py +412 -0
- pptx/chart/point.py +101 -0
- pptx/chart/series.py +258 -0
- pptx/chart/xlsx.py +272 -0
- pptx/chart/xmlwriter.py +1840 -0
- pptx/dml/__init__.py +0 -0
- pptx/dml/chtfmt.py +40 -0
- pptx/dml/color.py +301 -0
- pptx/dml/effect.py +41 -0
- pptx/dml/fill.py +468 -0
- pptx/dml/line.py +100 -0
- pptx/enum/__init__.py +0 -0
- pptx/enum/action.py +71 -0
- pptx/enum/base.py +175 -0
- pptx/enum/chart.py +492 -0
- pptx/enum/dml.py +405 -0
- pptx/enum/lang.py +685 -0
- pptx/enum/shapes.py +1029 -0
- pptx/enum/text.py +371 -0
- pptx/exc.py +23 -0
- pptx/media.py +197 -0
- pptx/opc/__init__.py +0 -0
- pptx/opc/constants.py +331 -0
- pptx/opc/oxml.py +188 -0
- pptx/opc/package.py +762 -0
- pptx/opc/packuri.py +109 -0
- pptx/opc/serialized.py +296 -0
- pptx/opc/shared.py +20 -0
- pptx/opc/spec.py +44 -0
- pptx/oxml/__init__.py +492 -0
- pptx/oxml/action.py +53 -0
- pptx/oxml/chart/__init__.py +0 -0
- pptx/oxml/chart/axis.py +297 -0
- pptx/oxml/chart/chart.py +282 -0
- pptx/oxml/chart/datalabel.py +252 -0
- pptx/oxml/chart/legend.py +72 -0
- pptx/oxml/chart/marker.py +61 -0
- pptx/oxml/chart/plot.py +345 -0
- pptx/oxml/chart/series.py +254 -0
- pptx/oxml/chart/shared.py +219 -0
- pptx/oxml/coreprops.py +288 -0
- pptx/oxml/dml/__init__.py +0 -0
- pptx/oxml/dml/color.py +111 -0
- pptx/oxml/dml/fill.py +197 -0
- pptx/oxml/dml/line.py +12 -0
- pptx/oxml/ns.py +129 -0
- pptx/oxml/presentation.py +130 -0
- pptx/oxml/shapes/__init__.py +19 -0
- pptx/oxml/shapes/autoshape.py +455 -0
- pptx/oxml/shapes/connector.py +107 -0
- pptx/oxml/shapes/graphfrm.py +342 -0
- pptx/oxml/shapes/groupshape.py +280 -0
- pptx/oxml/shapes/picture.py +270 -0
- pptx/oxml/shapes/shared.py +523 -0
- pptx/oxml/simpletypes.py +740 -0
- pptx/oxml/slide.py +347 -0
- pptx/oxml/table.py +588 -0
- pptx/oxml/text.py +748 -0
- pptx/oxml/theme.py +29 -0
- pptx/oxml/xmlchemy.py +717 -0
- pptx/package.py +222 -0
- pptx/parts/__init__.py +0 -0
- pptx/parts/chart.py +95 -0
- pptx/parts/coreprops.py +167 -0
- pptx/parts/embeddedpackage.py +93 -0
- pptx/parts/image.py +275 -0
- pptx/parts/media.py +37 -0
- pptx/parts/presentation.py +126 -0
- pptx/parts/slide.py +297 -0
- pptx/parts/theme.py +13 -0
- pptx/presentation.py +113 -0
- pptx/py.typed +0 -0
- pptx/shapes/__init__.py +26 -0
- pptx/shapes/autoshape.py +355 -0
- pptx/shapes/base.py +293 -0
- pptx/shapes/connector.py +297 -0
- pptx/shapes/freeform.py +337 -0
- pptx/shapes/graphfrm.py +166 -0
- pptx/shapes/group.py +69 -0
- pptx/shapes/picture.py +203 -0
- pptx/shapes/placeholder.py +407 -0
- pptx/shapes/shapetree.py +1190 -0
- pptx/shared.py +82 -0
- pptx/slide.py +577 -0
- pptx/spec.py +632 -0
- pptx/table.py +520 -0
- pptx/templates/default.pptx +0 -0
- pptx/templates/docx-icon.emf +0 -0
- pptx/templates/generic-icon.emf +0 -0
- pptx/templates/notes.xml +23 -0
- pptx/templates/notesMaster.xml +352 -0
- pptx/templates/pptx-icon.emf +0 -0
- pptx/templates/theme.xml +321 -0
- pptx/templates/xlsx-icon.emf +0 -0
- pptx/text/__init__.py +0 -0
- pptx/text/fonts.py +399 -0
- pptx/text/layout.py +325 -0
- pptx/text/text.py +770 -0
- pptx/theme.py +135 -0
- pptx/types.py +36 -0
- pptx/util.py +284 -0
- ps_python_pptx-1.1.0.dist-info/METADATA +75 -0
- ps_python_pptx-1.1.0.dist-info/RECORD +117 -0
- ps_python_pptx-1.1.0.dist-info/WHEEL +5 -0
- ps_python_pptx-1.1.0.dist-info/licenses/LICENSE +20 -0
- ps_python_pptx-1.1.0.dist-info/top_level.txt +1 -0
pptx/chart/series.py
ADDED
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""Series-related objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Sequence
|
|
6
|
+
|
|
7
|
+
from pptx.chart.datalabel import DataLabels
|
|
8
|
+
from pptx.chart.marker import Marker
|
|
9
|
+
from pptx.chart.point import BubblePoints, CategoryPoints, XyPoints
|
|
10
|
+
from pptx.dml.chtfmt import ChartFormat
|
|
11
|
+
from pptx.oxml.ns import qn
|
|
12
|
+
from pptx.util import lazyproperty
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class _BaseSeries(object):
|
|
16
|
+
"""
|
|
17
|
+
Base class for |BarSeries| and other series classes.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, ser):
|
|
21
|
+
super(_BaseSeries, self).__init__()
|
|
22
|
+
self._element = ser
|
|
23
|
+
self._ser = ser
|
|
24
|
+
|
|
25
|
+
@lazyproperty
|
|
26
|
+
def format(self):
|
|
27
|
+
"""
|
|
28
|
+
The |ChartFormat| instance for this series, providing access to shape
|
|
29
|
+
properties such as fill and line.
|
|
30
|
+
"""
|
|
31
|
+
return ChartFormat(self._ser)
|
|
32
|
+
|
|
33
|
+
@property
|
|
34
|
+
def index(self):
|
|
35
|
+
"""
|
|
36
|
+
The zero-based integer index of this series as reported in its
|
|
37
|
+
`c:ser/c:idx` element.
|
|
38
|
+
"""
|
|
39
|
+
return self._element.idx.val
|
|
40
|
+
|
|
41
|
+
@property
|
|
42
|
+
def name(self):
|
|
43
|
+
"""
|
|
44
|
+
The string label given to this series, appears as the title of the
|
|
45
|
+
column for this series in the Excel worksheet. It also appears as the
|
|
46
|
+
label for this series in the legend.
|
|
47
|
+
"""
|
|
48
|
+
names = self._element.xpath("./c:tx//c:pt/c:v/text()")
|
|
49
|
+
name = names[0] if names else ""
|
|
50
|
+
return name
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class _BaseCategorySeries(_BaseSeries):
|
|
54
|
+
"""Base class for |BarSeries| and other category chart series classes."""
|
|
55
|
+
|
|
56
|
+
@lazyproperty
|
|
57
|
+
def data_labels(self):
|
|
58
|
+
"""|DataLabels| object controlling data labels for this series."""
|
|
59
|
+
return DataLabels(self._ser.get_or_add_dLbls())
|
|
60
|
+
|
|
61
|
+
@lazyproperty
|
|
62
|
+
def points(self):
|
|
63
|
+
"""
|
|
64
|
+
The |CategoryPoints| object providing access to individual data
|
|
65
|
+
points in this series.
|
|
66
|
+
"""
|
|
67
|
+
return CategoryPoints(self._ser)
|
|
68
|
+
|
|
69
|
+
@property
|
|
70
|
+
def values(self):
|
|
71
|
+
"""
|
|
72
|
+
Read-only. A sequence containing the float values for this series, in
|
|
73
|
+
the order they appear on the chart.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
def iter_values():
|
|
77
|
+
val = self._element.val
|
|
78
|
+
if val is None:
|
|
79
|
+
return
|
|
80
|
+
for idx in range(val.ptCount_val):
|
|
81
|
+
yield val.pt_v(idx)
|
|
82
|
+
|
|
83
|
+
return tuple(iter_values())
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class _MarkerMixin(object):
|
|
87
|
+
"""
|
|
88
|
+
Mixin class providing `.marker` property for line-type chart series. The
|
|
89
|
+
line-type charts are Line, XY, and Radar.
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
@lazyproperty
|
|
93
|
+
def marker(self):
|
|
94
|
+
"""
|
|
95
|
+
The |Marker| instance for this series, providing access to data point
|
|
96
|
+
marker properties such as fill and line. Setting these properties
|
|
97
|
+
determines the appearance of markers for all points in this series
|
|
98
|
+
that are not overridden by settings at the point level.
|
|
99
|
+
"""
|
|
100
|
+
return Marker(self._ser)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
class AreaSeries(_BaseCategorySeries):
|
|
104
|
+
"""
|
|
105
|
+
A data point series belonging to an area plot.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
class BarSeries(_BaseCategorySeries):
|
|
110
|
+
"""A data point series belonging to a bar plot."""
|
|
111
|
+
|
|
112
|
+
@property
|
|
113
|
+
def invert_if_negative(self):
|
|
114
|
+
"""
|
|
115
|
+
|True| if a point having a value less than zero should appear with a
|
|
116
|
+
fill different than those with a positive value. |False| if the fill
|
|
117
|
+
should be the same regardless of the bar's value. When |True|, a bar
|
|
118
|
+
with a solid fill appears with white fill; in a bar with gradient
|
|
119
|
+
fill, the direction of the gradient is reversed, e.g. dark -> light
|
|
120
|
+
instead of light -> dark. The term "invert" here should be understood
|
|
121
|
+
to mean "invert the *direction* of the *fill gradient*".
|
|
122
|
+
"""
|
|
123
|
+
invertIfNegative = self._element.invertIfNegative
|
|
124
|
+
if invertIfNegative is None:
|
|
125
|
+
return True
|
|
126
|
+
return invertIfNegative.val
|
|
127
|
+
|
|
128
|
+
@invert_if_negative.setter
|
|
129
|
+
def invert_if_negative(self, value):
|
|
130
|
+
invertIfNegative = self._element.get_or_add_invertIfNegative()
|
|
131
|
+
invertIfNegative.val = value
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class LineSeries(_BaseCategorySeries, _MarkerMixin):
|
|
135
|
+
"""
|
|
136
|
+
A data point series belonging to a line plot.
|
|
137
|
+
"""
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def smooth(self):
|
|
141
|
+
"""
|
|
142
|
+
Read/write boolean specifying whether to use curve smoothing to
|
|
143
|
+
form the line connecting the data points in this series into
|
|
144
|
+
a continuous curve. If |False|, a series of straight line segments
|
|
145
|
+
are used to connect the points.
|
|
146
|
+
"""
|
|
147
|
+
smooth = self._element.smooth
|
|
148
|
+
if smooth is None:
|
|
149
|
+
return True
|
|
150
|
+
return smooth.val
|
|
151
|
+
|
|
152
|
+
@smooth.setter
|
|
153
|
+
def smooth(self, value):
|
|
154
|
+
self._element.get_or_add_smooth().val = value
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class PieSeries(_BaseCategorySeries):
|
|
158
|
+
"""
|
|
159
|
+
A data point series belonging to a pie plot.
|
|
160
|
+
"""
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class RadarSeries(_BaseCategorySeries, _MarkerMixin):
|
|
164
|
+
"""
|
|
165
|
+
A data point series belonging to a radar plot.
|
|
166
|
+
"""
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
class XySeries(_BaseSeries, _MarkerMixin):
|
|
170
|
+
"""
|
|
171
|
+
A data point series belonging to an XY (scatter) plot.
|
|
172
|
+
"""
|
|
173
|
+
|
|
174
|
+
def iter_values(self):
|
|
175
|
+
"""
|
|
176
|
+
Generate each float Y value in this series, in the order they appear
|
|
177
|
+
on the chart. A value of `None` represents a missing Y value
|
|
178
|
+
(corresponding to a blank Excel cell).
|
|
179
|
+
"""
|
|
180
|
+
yVal = self._element.yVal
|
|
181
|
+
if yVal is None:
|
|
182
|
+
return
|
|
183
|
+
|
|
184
|
+
for idx in range(yVal.ptCount_val):
|
|
185
|
+
yield yVal.pt_v(idx)
|
|
186
|
+
|
|
187
|
+
@lazyproperty
|
|
188
|
+
def points(self):
|
|
189
|
+
"""
|
|
190
|
+
The |XyPoints| object providing access to individual data points in
|
|
191
|
+
this series.
|
|
192
|
+
"""
|
|
193
|
+
return XyPoints(self._ser)
|
|
194
|
+
|
|
195
|
+
@property
|
|
196
|
+
def values(self):
|
|
197
|
+
"""
|
|
198
|
+
Read-only. A sequence containing the float values for this series, in
|
|
199
|
+
the order they appear on the chart.
|
|
200
|
+
"""
|
|
201
|
+
return tuple(self.iter_values())
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
class BubbleSeries(XySeries):
|
|
205
|
+
"""
|
|
206
|
+
A data point series belonging to a bubble plot.
|
|
207
|
+
"""
|
|
208
|
+
|
|
209
|
+
@lazyproperty
|
|
210
|
+
def points(self):
|
|
211
|
+
"""
|
|
212
|
+
The |BubblePoints| object providing access to individual data point
|
|
213
|
+
objects used to discover and adjust the formatting and data labels of
|
|
214
|
+
a data point.
|
|
215
|
+
"""
|
|
216
|
+
return BubblePoints(self._ser)
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
class SeriesCollection(Sequence):
|
|
220
|
+
"""
|
|
221
|
+
A sequence of |Series| objects.
|
|
222
|
+
"""
|
|
223
|
+
|
|
224
|
+
def __init__(self, parent_elm):
|
|
225
|
+
# *parent_elm* can be either a c:plotArea or xChart element
|
|
226
|
+
super(SeriesCollection, self).__init__()
|
|
227
|
+
self._element = parent_elm
|
|
228
|
+
|
|
229
|
+
def __getitem__(self, index):
|
|
230
|
+
ser = self._element.sers[index]
|
|
231
|
+
return _SeriesFactory(ser)
|
|
232
|
+
|
|
233
|
+
def __len__(self):
|
|
234
|
+
return len(self._element.sers)
|
|
235
|
+
|
|
236
|
+
|
|
237
|
+
def _SeriesFactory(ser):
|
|
238
|
+
"""
|
|
239
|
+
Return an instance of the appropriate subclass of _BaseSeries based on the
|
|
240
|
+
xChart element *ser* appears in.
|
|
241
|
+
"""
|
|
242
|
+
xChart_tag = ser.getparent().tag
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
SeriesCls = {
|
|
246
|
+
qn("c:areaChart"): AreaSeries,
|
|
247
|
+
qn("c:barChart"): BarSeries,
|
|
248
|
+
qn("c:bubbleChart"): BubbleSeries,
|
|
249
|
+
qn("c:doughnutChart"): PieSeries,
|
|
250
|
+
qn("c:lineChart"): LineSeries,
|
|
251
|
+
qn("c:pieChart"): PieSeries,
|
|
252
|
+
qn("c:radarChart"): RadarSeries,
|
|
253
|
+
qn("c:scatterChart"): XySeries,
|
|
254
|
+
}[xChart_tag]
|
|
255
|
+
except KeyError:
|
|
256
|
+
raise NotImplementedError("series class for %s not yet implemented" % xChart_tag)
|
|
257
|
+
|
|
258
|
+
return SeriesCls(ser)
|
pptx/chart/xlsx.py
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
"""Chart builder and related objects."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import io
|
|
6
|
+
from contextlib import contextmanager
|
|
7
|
+
|
|
8
|
+
from xlsxwriter import Workbook
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class _BaseWorkbookWriter(object):
|
|
12
|
+
"""Base class for workbook writers, providing shared members."""
|
|
13
|
+
|
|
14
|
+
def __init__(self, chart_data):
|
|
15
|
+
super(_BaseWorkbookWriter, self).__init__()
|
|
16
|
+
self._chart_data = chart_data
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def xlsx_blob(self):
|
|
20
|
+
"""bytes for Excel file containing chart_data."""
|
|
21
|
+
xlsx_file = io.BytesIO()
|
|
22
|
+
with self._open_worksheet(xlsx_file) as (workbook, worksheet):
|
|
23
|
+
self._populate_worksheet(workbook, worksheet)
|
|
24
|
+
return xlsx_file.getvalue()
|
|
25
|
+
|
|
26
|
+
@contextmanager
|
|
27
|
+
def _open_worksheet(self, xlsx_file):
|
|
28
|
+
"""
|
|
29
|
+
Enable XlsxWriter Worksheet object to be opened, operated on, and
|
|
30
|
+
then automatically closed within a `with` statement. A filename or
|
|
31
|
+
stream object (such as an `io.BytesIO` instance) is expected as
|
|
32
|
+
*xlsx_file*.
|
|
33
|
+
"""
|
|
34
|
+
workbook = Workbook(xlsx_file, {"in_memory": True})
|
|
35
|
+
worksheet = workbook.add_worksheet()
|
|
36
|
+
yield workbook, worksheet
|
|
37
|
+
workbook.close()
|
|
38
|
+
|
|
39
|
+
def _populate_worksheet(self, workbook, worksheet):
|
|
40
|
+
"""
|
|
41
|
+
Must be overridden by each subclass to provide the particulars of
|
|
42
|
+
writing the spreadsheet data.
|
|
43
|
+
"""
|
|
44
|
+
raise NotImplementedError("must be provided by each subclass")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class CategoryWorkbookWriter(_BaseWorkbookWriter):
|
|
48
|
+
"""
|
|
49
|
+
Determines Excel worksheet layout and can write an Excel workbook from
|
|
50
|
+
a CategoryChartData object. Serves as the authority for Excel worksheet
|
|
51
|
+
ranges.
|
|
52
|
+
"""
|
|
53
|
+
|
|
54
|
+
@property
|
|
55
|
+
def categories_ref(self):
|
|
56
|
+
"""
|
|
57
|
+
The Excel worksheet reference to the categories for this chart (not
|
|
58
|
+
including the column heading).
|
|
59
|
+
"""
|
|
60
|
+
categories = self._chart_data.categories
|
|
61
|
+
if categories.depth == 0:
|
|
62
|
+
raise ValueError("chart data contains no categories")
|
|
63
|
+
right_col = chr(ord("A") + categories.depth - 1)
|
|
64
|
+
bottom_row = categories.leaf_count + 1
|
|
65
|
+
return "Sheet1!$A$2:$%s$%d" % (right_col, bottom_row)
|
|
66
|
+
|
|
67
|
+
def series_name_ref(self, series):
|
|
68
|
+
"""
|
|
69
|
+
Return the Excel worksheet reference to the cell containing the name
|
|
70
|
+
for *series*. This also serves as the column heading for the series
|
|
71
|
+
values.
|
|
72
|
+
"""
|
|
73
|
+
return "Sheet1!$%s$1" % self._series_col_letter(series)
|
|
74
|
+
|
|
75
|
+
def values_ref(self, series):
|
|
76
|
+
"""
|
|
77
|
+
The Excel worksheet reference to the values for this series (not
|
|
78
|
+
including the column heading).
|
|
79
|
+
"""
|
|
80
|
+
return "Sheet1!${col_letter}$2:${col_letter}${bottom_row}".format(
|
|
81
|
+
**{
|
|
82
|
+
"col_letter": self._series_col_letter(series),
|
|
83
|
+
"bottom_row": len(series) + 1,
|
|
84
|
+
}
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
@staticmethod
|
|
88
|
+
def _column_reference(column_number):
|
|
89
|
+
"""Return str Excel column reference like 'BQ' for *column_number*.
|
|
90
|
+
|
|
91
|
+
*column_number* is an int in the range 1-16384 inclusive, where
|
|
92
|
+
1 maps to column 'A'.
|
|
93
|
+
"""
|
|
94
|
+
if column_number < 1 or column_number > 16384:
|
|
95
|
+
raise ValueError("column_number must be in range 1-16384")
|
|
96
|
+
|
|
97
|
+
# ---Work right-to-left, one order of magnitude at a time. Note there
|
|
98
|
+
# is no zero representation in Excel address scheme, so this is
|
|
99
|
+
# not just a conversion to base-26---
|
|
100
|
+
|
|
101
|
+
col_ref = ""
|
|
102
|
+
while column_number:
|
|
103
|
+
remainder = column_number % 26
|
|
104
|
+
if remainder == 0:
|
|
105
|
+
remainder = 26
|
|
106
|
+
|
|
107
|
+
col_letter = chr(ord("A") + remainder - 1)
|
|
108
|
+
col_ref = col_letter + col_ref
|
|
109
|
+
|
|
110
|
+
# ---Advance to next order of magnitude or terminate loop. The
|
|
111
|
+
# minus-one in this expression reflects the fact the next lower
|
|
112
|
+
# order of magnitude has a minumum value of 1 (not zero). This is
|
|
113
|
+
# essentially the complement to the "if it's 0 make it 26' step
|
|
114
|
+
# above.---
|
|
115
|
+
column_number = (column_number - 1) // 26
|
|
116
|
+
|
|
117
|
+
return col_ref
|
|
118
|
+
|
|
119
|
+
def _populate_worksheet(self, workbook, worksheet):
|
|
120
|
+
"""
|
|
121
|
+
Write the chart data contents to *worksheet* in category chart
|
|
122
|
+
layout. Write categories starting in the first column starting in
|
|
123
|
+
the second row, and proceeding one column per category level (for
|
|
124
|
+
charts having multi-level categories). Write series as columns
|
|
125
|
+
starting in the next following column, placing the series title in
|
|
126
|
+
the first cell.
|
|
127
|
+
"""
|
|
128
|
+
self._write_categories(workbook, worksheet)
|
|
129
|
+
self._write_series(workbook, worksheet)
|
|
130
|
+
|
|
131
|
+
def _series_col_letter(self, series):
|
|
132
|
+
"""
|
|
133
|
+
The letter of the Excel worksheet column in which the data for a
|
|
134
|
+
series appears.
|
|
135
|
+
"""
|
|
136
|
+
column_number = 1 + series.categories.depth + series.index
|
|
137
|
+
return self._column_reference(column_number)
|
|
138
|
+
|
|
139
|
+
def _write_categories(self, workbook, worksheet):
|
|
140
|
+
"""
|
|
141
|
+
Write the categories column(s) to *worksheet*. Categories start in
|
|
142
|
+
the first column starting in the second row, and proceeding one
|
|
143
|
+
column per category level (for charts having multi-level categories).
|
|
144
|
+
A date category is formatted as a date. All others are formatted
|
|
145
|
+
`General`.
|
|
146
|
+
"""
|
|
147
|
+
categories = self._chart_data.categories
|
|
148
|
+
num_format = workbook.add_format({"num_format": categories.number_format})
|
|
149
|
+
depth = categories.depth
|
|
150
|
+
for idx, level in enumerate(categories.levels):
|
|
151
|
+
col = depth - idx - 1
|
|
152
|
+
self._write_cat_column(worksheet, col, level, num_format)
|
|
153
|
+
|
|
154
|
+
def _write_cat_column(self, worksheet, col, level, num_format):
|
|
155
|
+
"""
|
|
156
|
+
Write a category column defined by *level* to *worksheet* at offset
|
|
157
|
+
*col* and formatted with *num_format*.
|
|
158
|
+
"""
|
|
159
|
+
worksheet.set_column(col, col, 10) # wide enough for a date
|
|
160
|
+
for off, name in level:
|
|
161
|
+
row = off + 1
|
|
162
|
+
worksheet.write(row, col, name, num_format)
|
|
163
|
+
|
|
164
|
+
def _write_series(self, workbook, worksheet):
|
|
165
|
+
"""
|
|
166
|
+
Write the series column(s) to *worksheet*. Series start in the column
|
|
167
|
+
following the last categories column, placing the series title in the
|
|
168
|
+
first cell.
|
|
169
|
+
"""
|
|
170
|
+
col_offset = self._chart_data.categories.depth
|
|
171
|
+
for idx, series in enumerate(self._chart_data):
|
|
172
|
+
num_format = workbook.add_format({"num_format": series.number_format})
|
|
173
|
+
series_col = idx + col_offset
|
|
174
|
+
worksheet.write(0, series_col, series.name)
|
|
175
|
+
worksheet.write_column(1, series_col, series.values, num_format)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class XyWorkbookWriter(_BaseWorkbookWriter):
|
|
179
|
+
"""
|
|
180
|
+
Determines Excel worksheet layout and can write an Excel workbook from XY
|
|
181
|
+
chart data. Serves as the authority for Excel worksheet ranges.
|
|
182
|
+
"""
|
|
183
|
+
|
|
184
|
+
def series_name_ref(self, series):
|
|
185
|
+
"""
|
|
186
|
+
Return the Excel worksheet reference to the cell containing the name
|
|
187
|
+
for *series*. This also serves as the column heading for the series
|
|
188
|
+
Y values.
|
|
189
|
+
"""
|
|
190
|
+
row = self.series_table_row_offset(series) + 1
|
|
191
|
+
return "Sheet1!$B$%d" % row
|
|
192
|
+
|
|
193
|
+
def series_table_row_offset(self, series):
|
|
194
|
+
"""
|
|
195
|
+
Return the number of rows preceding the data table for *series* in
|
|
196
|
+
the Excel worksheet.
|
|
197
|
+
"""
|
|
198
|
+
title_and_spacer_rows = series.index * 2
|
|
199
|
+
data_point_rows = series.data_point_offset
|
|
200
|
+
return title_and_spacer_rows + data_point_rows
|
|
201
|
+
|
|
202
|
+
def x_values_ref(self, series):
|
|
203
|
+
"""
|
|
204
|
+
The Excel worksheet reference to the X values for this chart (not
|
|
205
|
+
including the column label).
|
|
206
|
+
"""
|
|
207
|
+
top_row = self.series_table_row_offset(series) + 2
|
|
208
|
+
bottom_row = top_row + len(series) - 1
|
|
209
|
+
return "Sheet1!$A$%d:$A$%d" % (top_row, bottom_row)
|
|
210
|
+
|
|
211
|
+
def y_values_ref(self, series):
|
|
212
|
+
"""
|
|
213
|
+
The Excel worksheet reference to the Y values for this chart (not
|
|
214
|
+
including the column label).
|
|
215
|
+
"""
|
|
216
|
+
top_row = self.series_table_row_offset(series) + 2
|
|
217
|
+
bottom_row = top_row + len(series) - 1
|
|
218
|
+
return "Sheet1!$B$%d:$B$%d" % (top_row, bottom_row)
|
|
219
|
+
|
|
220
|
+
def _populate_worksheet(self, workbook, worksheet):
|
|
221
|
+
"""
|
|
222
|
+
Write chart data contents to *worksheet* in the standard XY chart
|
|
223
|
+
layout. Write the data for each series to a separate two-column
|
|
224
|
+
table, X values in column A and Y values in column B. Place the
|
|
225
|
+
series label in the first (heading) cell of the column.
|
|
226
|
+
"""
|
|
227
|
+
chart_num_format = workbook.add_format({"num_format": self._chart_data.number_format})
|
|
228
|
+
for series in self._chart_data:
|
|
229
|
+
series_num_format = workbook.add_format({"num_format": series.number_format})
|
|
230
|
+
offset = self.series_table_row_offset(series)
|
|
231
|
+
# write X values
|
|
232
|
+
worksheet.write_column(offset + 1, 0, series.x_values, chart_num_format)
|
|
233
|
+
# write Y values
|
|
234
|
+
worksheet.write(offset, 1, series.name)
|
|
235
|
+
worksheet.write_column(offset + 1, 1, series.y_values, series_num_format)
|
|
236
|
+
|
|
237
|
+
|
|
238
|
+
class BubbleWorkbookWriter(XyWorkbookWriter):
|
|
239
|
+
"""
|
|
240
|
+
Service object that knows how to write an Excel workbook from bubble
|
|
241
|
+
chart data.
|
|
242
|
+
"""
|
|
243
|
+
|
|
244
|
+
def bubble_sizes_ref(self, series):
|
|
245
|
+
"""
|
|
246
|
+
The Excel worksheet reference to the range containing the bubble
|
|
247
|
+
sizes for *series* (not including the column heading cell).
|
|
248
|
+
"""
|
|
249
|
+
top_row = self.series_table_row_offset(series) + 2
|
|
250
|
+
bottom_row = top_row + len(series) - 1
|
|
251
|
+
return "Sheet1!$C$%d:$C$%d" % (top_row, bottom_row)
|
|
252
|
+
|
|
253
|
+
def _populate_worksheet(self, workbook, worksheet):
|
|
254
|
+
"""
|
|
255
|
+
Write chart data contents to *worksheet* in the bubble chart layout.
|
|
256
|
+
Write the data for each series to a separate three-column table with
|
|
257
|
+
X values in column A, Y values in column B, and bubble sizes in
|
|
258
|
+
column C. Place the series label in the first (heading) cell of the
|
|
259
|
+
values column.
|
|
260
|
+
"""
|
|
261
|
+
chart_num_format = workbook.add_format({"num_format": self._chart_data.number_format})
|
|
262
|
+
for series in self._chart_data:
|
|
263
|
+
series_num_format = workbook.add_format({"num_format": series.number_format})
|
|
264
|
+
offset = self.series_table_row_offset(series)
|
|
265
|
+
# write X values
|
|
266
|
+
worksheet.write_column(offset + 1, 0, series.x_values, chart_num_format)
|
|
267
|
+
# write Y values
|
|
268
|
+
worksheet.write(offset, 1, series.name)
|
|
269
|
+
worksheet.write_column(offset + 1, 1, series.y_values, series_num_format)
|
|
270
|
+
# write bubble sizes
|
|
271
|
+
worksheet.write(offset, 2, "Size")
|
|
272
|
+
worksheet.write_column(offset + 1, 2, series.bubble_sizes, chart_num_format)
|