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.
Files changed (175) hide show
  1. pptx2/__init__.py +152 -0
  2. pptx2/_color.py +75 -0
  3. pptx2/_slide_importer.py +597 -0
  4. pptx2/_svg.py +155 -0
  5. pptx2/_template_applier.py +292 -0
  6. pptx2/_textstyle.py +187 -0
  7. pptx2/accessibility.py +365 -0
  8. pptx2/action.py +270 -0
  9. pptx2/animation.py +2237 -0
  10. pptx2/api.py +49 -0
  11. pptx2/audit.py +258 -0
  12. pptx2/chart/__init__.py +0 -0
  13. pptx2/chart/analytics.py +381 -0
  14. pptx2/chart/axis.py +543 -0
  15. pptx2/chart/category.py +200 -0
  16. pptx2/chart/chart.py +670 -0
  17. pptx2/chart/data.py +864 -0
  18. pptx2/chart/datalabel.py +406 -0
  19. pptx2/chart/legend.py +86 -0
  20. pptx2/chart/marker.py +70 -0
  21. pptx2/chart/palettes.py +129 -0
  22. pptx2/chart/plot.py +462 -0
  23. pptx2/chart/point.py +101 -0
  24. pptx2/chart/quick_layouts.py +325 -0
  25. pptx2/chart/series.py +334 -0
  26. pptx2/chart/xlsx.py +272 -0
  27. pptx2/chart/xmlwriter.py +1845 -0
  28. pptx2/compose/__init__.py +28 -0
  29. pptx2/compose/from_spec.py +1094 -0
  30. pptx2/design/__init__.py +8 -0
  31. pptx2/design/components.py +607 -0
  32. pptx2/design/figures.py +389 -0
  33. pptx2/design/layout.py +370 -0
  34. pptx2/design/recipes.py +1967 -0
  35. pptx2/design/style.py +209 -0
  36. pptx2/design/tokens.py +915 -0
  37. pptx2/diagrams.py +754 -0
  38. pptx2/dml/__init__.py +0 -0
  39. pptx2/dml/chtfmt.py +40 -0
  40. pptx2/dml/color.py +496 -0
  41. pptx2/dml/effect.py +909 -0
  42. pptx2/dml/fill.py +691 -0
  43. pptx2/dml/line.py +287 -0
  44. pptx2/dml/picture.py +212 -0
  45. pptx2/dml/three_d.py +381 -0
  46. pptx2/enum/__init__.py +0 -0
  47. pptx2/enum/action.py +71 -0
  48. pptx2/enum/animation.py +31 -0
  49. pptx2/enum/base.py +218 -0
  50. pptx2/enum/chart.py +574 -0
  51. pptx2/enum/dml.py +740 -0
  52. pptx2/enum/lang.py +685 -0
  53. pptx2/enum/presentation.py +133 -0
  54. pptx2/enum/shapes.py +1029 -0
  55. pptx2/enum/text.py +230 -0
  56. pptx2/exc.py +42 -0
  57. pptx2/formats.py +139 -0
  58. pptx2/geometry.py +420 -0
  59. pptx2/inherit.py +109 -0
  60. pptx2/lint.py +2256 -0
  61. pptx2/math.py +177 -0
  62. pptx2/media.py +197 -0
  63. pptx2/opc/__init__.py +0 -0
  64. pptx2/opc/constants.py +332 -0
  65. pptx2/opc/oxml.py +188 -0
  66. pptx2/opc/package.py +762 -0
  67. pptx2/opc/packuri.py +109 -0
  68. pptx2/opc/serialized.py +296 -0
  69. pptx2/opc/shared.py +20 -0
  70. pptx2/opc/spec.py +45 -0
  71. pptx2/oxml/__init__.py +555 -0
  72. pptx2/oxml/action.py +53 -0
  73. pptx2/oxml/chart/__init__.py +0 -0
  74. pptx2/oxml/chart/axis.py +337 -0
  75. pptx2/oxml/chart/chart.py +481 -0
  76. pptx2/oxml/chart/datalabel.py +253 -0
  77. pptx2/oxml/chart/legend.py +72 -0
  78. pptx2/oxml/chart/marker.py +61 -0
  79. pptx2/oxml/chart/plot.py +365 -0
  80. pptx2/oxml/chart/series.py +425 -0
  81. pptx2/oxml/chart/shared.py +220 -0
  82. pptx2/oxml/coreprops.py +288 -0
  83. pptx2/oxml/dml/__init__.py +0 -0
  84. pptx2/oxml/dml/color.py +135 -0
  85. pptx2/oxml/dml/effect.py +213 -0
  86. pptx2/oxml/dml/fill.py +316 -0
  87. pptx2/oxml/dml/line.py +12 -0
  88. pptx2/oxml/dml/three_d.py +110 -0
  89. pptx2/oxml/ns.py +135 -0
  90. pptx2/oxml/presentation.py +313 -0
  91. pptx2/oxml/shapes/__init__.py +19 -0
  92. pptx2/oxml/shapes/autoshape.py +467 -0
  93. pptx2/oxml/shapes/connector.py +107 -0
  94. pptx2/oxml/shapes/graphfrm.py +347 -0
  95. pptx2/oxml/shapes/groupshape.py +329 -0
  96. pptx2/oxml/shapes/picture.py +270 -0
  97. pptx2/oxml/shapes/shared.py +577 -0
  98. pptx2/oxml/simpletypes.py +1027 -0
  99. pptx2/oxml/slide.py +563 -0
  100. pptx2/oxml/table.py +650 -0
  101. pptx2/oxml/text.py +815 -0
  102. pptx2/oxml/theme.py +36 -0
  103. pptx2/oxml/xmlchemy.py +717 -0
  104. pptx2/package.py +222 -0
  105. pptx2/parts/__init__.py +0 -0
  106. pptx2/parts/chart.py +95 -0
  107. pptx2/parts/coreprops.py +167 -0
  108. pptx2/parts/diagram.py +37 -0
  109. pptx2/parts/embeddedpackage.py +93 -0
  110. pptx2/parts/image.py +275 -0
  111. pptx2/parts/media.py +37 -0
  112. pptx2/parts/presentation.py +136 -0
  113. pptx2/parts/slide.py +371 -0
  114. pptx2/presentation.py +408 -0
  115. pptx2/py.typed +0 -0
  116. pptx2/render.py +586 -0
  117. pptx2/section.py +272 -0
  118. pptx2/shapes/__init__.py +26 -0
  119. pptx2/shapes/autoshape.py +442 -0
  120. pptx2/shapes/base.py +1078 -0
  121. pptx2/shapes/connector.py +297 -0
  122. pptx2/shapes/freeform.py +337 -0
  123. pptx2/shapes/graphfrm.py +316 -0
  124. pptx2/shapes/group.py +264 -0
  125. pptx2/shapes/picture.py +422 -0
  126. pptx2/shapes/placeholder.py +468 -0
  127. pptx2/shapes/shapetree.py +2027 -0
  128. pptx2/shared.py +82 -0
  129. pptx2/skill/SKILL.md +450 -0
  130. pptx2/skill/__init__.py +78 -0
  131. pptx2/skill/__main__.py +64 -0
  132. pptx2/skill/references/animations.md +189 -0
  133. pptx2/skill/references/basics.md +421 -0
  134. pptx2/skill/references/charts.md +254 -0
  135. pptx2/skill/references/compose.md +234 -0
  136. pptx2/skill/references/design.md +366 -0
  137. pptx2/skill/references/effects.md +249 -0
  138. pptx2/skill/references/end-to-end-deck.md +231 -0
  139. pptx2/skill/references/geometry-and-arrows.md +334 -0
  140. pptx2/skill/references/lint.md +275 -0
  141. pptx2/skill/references/math.md +86 -0
  142. pptx2/skill/references/picture-effects.md +129 -0
  143. pptx2/skill/references/render.md +151 -0
  144. pptx2/skill/references/smart-art.md +75 -0
  145. pptx2/skill/references/space-aware-authoring.md +249 -0
  146. pptx2/skill/references/tables.md +244 -0
  147. pptx2/skill/references/theme.md +127 -0
  148. pptx2/skill/references/three-d.md +109 -0
  149. pptx2/skill/references/transitions.md +100 -0
  150. pptx2/slide.py +1244 -0
  151. pptx2/smart_art.py +220 -0
  152. pptx2/spec.py +633 -0
  153. pptx2/table.py +1181 -0
  154. pptx2/table_styles.py +184 -0
  155. pptx2/templates/default.pptx +0 -0
  156. pptx2/templates/docx-icon.emf +0 -0
  157. pptx2/templates/generic-icon.emf +0 -0
  158. pptx2/templates/notes.xml +23 -0
  159. pptx2/templates/notesMaster.xml +352 -0
  160. pptx2/templates/pptx-icon.emf +0 -0
  161. pptx2/templates/theme.xml +321 -0
  162. pptx2/templates/xlsx-icon.emf +0 -0
  163. pptx2/text/__init__.py +0 -0
  164. pptx2/text/fonts.py +482 -0
  165. pptx2/text/layout.py +374 -0
  166. pptx2/text/text.py +1272 -0
  167. pptx2/theme.py +721 -0
  168. pptx2/types.py +36 -0
  169. pptx2/util.py +263 -0
  170. python_pptx2-2.13.0.dist-info/METADATA +351 -0
  171. python_pptx2-2.13.0.dist-info/RECORD +175 -0
  172. python_pptx2-2.13.0.dist-info/WHEEL +5 -0
  173. python_pptx2-2.13.0.dist-info/entry_points.txt +3 -0
  174. python_pptx2-2.13.0.dist-info/licenses/LICENSE +22 -0
  175. python_pptx2-2.13.0.dist-info/top_level.txt +1 -0
pptx2/chart/data.py ADDED
@@ -0,0 +1,864 @@
1
+ """ChartData and related objects."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import datetime
6
+ from collections.abc import Sequence
7
+ from numbers import Number
8
+
9
+ from pptx2.chart.xlsx import (
10
+ BubbleWorkbookWriter,
11
+ CategoryWorkbookWriter,
12
+ XyWorkbookWriter,
13
+ )
14
+ from pptx2.chart.xmlwriter import ChartXmlWriter
15
+ from pptx2.util import lazyproperty
16
+
17
+
18
+ class _BaseChartData(Sequence):
19
+ """Base class providing common members for chart data objects.
20
+
21
+ A chart data object serves as a proxy for the chart data table that will be written to an
22
+ Excel worksheet; operating as a sequence of series as well as providing access to chart-level
23
+ attributes. A chart data object is used as a parameter in :meth:`shapes.add_chart` and
24
+ :meth:`Chart.replace_data`. The data structure varies between major chart categories such as
25
+ category charts and XY charts.
26
+ """
27
+
28
+ def __init__(self, number_format="General"):
29
+ super(_BaseChartData, self).__init__()
30
+ self._number_format = number_format
31
+ self._series = []
32
+
33
+ def __getitem__(self, index):
34
+ return self._series.__getitem__(index)
35
+
36
+ def __len__(self):
37
+ return self._series.__len__()
38
+
39
+ def append(self, series):
40
+ return self._series.append(series)
41
+
42
+ def data_point_offset(self, series):
43
+ """
44
+ The total integer number of data points appearing in the series of
45
+ this chart that are prior to *series* in this sequence.
46
+ """
47
+ count = 0
48
+ for this_series in self:
49
+ if series is this_series:
50
+ return count
51
+ count += len(this_series)
52
+ raise ValueError("series not in chart data object")
53
+
54
+ @property
55
+ def number_format(self):
56
+ """
57
+ The formatting template string, e.g. '#,##0.0', that determines how
58
+ X and Y values are formatted in this chart and in the Excel
59
+ spreadsheet. A number format specified on a series will override this
60
+ value for that series. Likewise, a distinct number format can be
61
+ specified for a particular data point within a series.
62
+ """
63
+ return self._number_format
64
+
65
+ def series_index(self, series):
66
+ """
67
+ Return the integer index of *series* in this sequence.
68
+ """
69
+ for idx, s in enumerate(self):
70
+ if series is s:
71
+ return idx
72
+ raise ValueError("series not in chart data object")
73
+
74
+ def series_name_ref(self, series):
75
+ """
76
+ Return the Excel worksheet reference to the cell containing the name
77
+ for *series*.
78
+ """
79
+ return self._workbook_writer.series_name_ref(series)
80
+
81
+ def x_values_ref(self, series):
82
+ """
83
+ The Excel worksheet reference to the X values for *series* (not
84
+ including the column label).
85
+ """
86
+ return self._workbook_writer.x_values_ref(series)
87
+
88
+ @property
89
+ def xlsx_blob(self):
90
+ """
91
+ Return a blob containing an Excel workbook file populated with the
92
+ contents of this chart data object.
93
+ """
94
+ return self._workbook_writer.xlsx_blob
95
+
96
+ def xml_bytes(self, chart_type):
97
+ """
98
+ Return a blob containing the XML for a chart of *chart_type*
99
+ containing the series in this chart data object, as bytes suitable
100
+ for writing directly to a file.
101
+ """
102
+ return self._xml(chart_type).encode("utf-8")
103
+
104
+ def y_values_ref(self, series):
105
+ """
106
+ The Excel worksheet reference to the Y values for *series* (not
107
+ including the column label).
108
+ """
109
+ return self._workbook_writer.y_values_ref(series)
110
+
111
+ @property
112
+ def _workbook_writer(self):
113
+ """
114
+ The worksheet writer object to which layout and writing of the Excel
115
+ worksheet for this chart will be delegated.
116
+ """
117
+ raise NotImplementedError("must be implemented by all subclasses")
118
+
119
+ def _xml(self, chart_type):
120
+ """
121
+ Return (as unicode text) the XML for a chart of *chart_type*
122
+ populated with the values in this chart data object. The XML is
123
+ a complete XML document, including an XML declaration specifying
124
+ UTF-8 encoding.
125
+ """
126
+ return ChartXmlWriter(chart_type, self).xml
127
+
128
+
129
+ class _BaseSeriesData(Sequence):
130
+ """
131
+ Base class providing common members for series data objects. A series
132
+ data object serves as proxy for a series data column in the Excel
133
+ worksheet. It operates as a sequence of data points, as well as providing
134
+ access to series-level attributes like the series label.
135
+ """
136
+
137
+ def __init__(self, chart_data, name, number_format):
138
+ self._chart_data = chart_data
139
+ self._name = name
140
+ self._number_format = number_format
141
+ self._data_points = []
142
+
143
+ def __getitem__(self, index):
144
+ return self._data_points.__getitem__(index)
145
+
146
+ def __len__(self):
147
+ return self._data_points.__len__()
148
+
149
+ def append(self, data_point):
150
+ return self._data_points.append(data_point)
151
+
152
+ @property
153
+ def data_point_offset(self):
154
+ """
155
+ The integer count of data points that appear in all chart series
156
+ prior to this one.
157
+ """
158
+ return self._chart_data.data_point_offset(self)
159
+
160
+ @property
161
+ def index(self):
162
+ """
163
+ Zero-based integer indicating the sequence position of this series in
164
+ its chart. For example, the second of three series would return `1`.
165
+ """
166
+ return self._chart_data.series_index(self)
167
+
168
+ @property
169
+ def name(self):
170
+ """
171
+ The name of this series, e.g. 'Series 1'. This name is used as the
172
+ column heading for the y-values of this series and may also appear in
173
+ the chart legend and perhaps other chart locations.
174
+ """
175
+ return self._name if self._name is not None else ""
176
+
177
+ @property
178
+ def name_ref(self):
179
+ """
180
+ The Excel worksheet reference to the cell containing the name for
181
+ this series.
182
+ """
183
+ return self._chart_data.series_name_ref(self)
184
+
185
+ @property
186
+ def number_format(self):
187
+ """
188
+ The formatting template string that determines how a number in this
189
+ series is formatted, both in the chart and in the Excel spreadsheet;
190
+ for example '#,##0.0'. If not specified for this series, it is
191
+ inherited from the parent chart data object.
192
+ """
193
+ number_format = self._number_format
194
+ if number_format is None:
195
+ return self._chart_data.number_format
196
+ return number_format
197
+
198
+ @property
199
+ def x_values(self):
200
+ """
201
+ A sequence containing the X value of each datapoint in this series,
202
+ in data point order.
203
+ """
204
+ return [dp.x for dp in self._data_points]
205
+
206
+ @property
207
+ def x_values_ref(self):
208
+ """
209
+ The Excel worksheet reference to the X values for this chart (not
210
+ including the column heading).
211
+ """
212
+ return self._chart_data.x_values_ref(self)
213
+
214
+ @property
215
+ def y_values(self):
216
+ """
217
+ A sequence containing the Y value of each datapoint in this series,
218
+ in data point order.
219
+ """
220
+ return [dp.y for dp in self._data_points]
221
+
222
+ @property
223
+ def y_values_ref(self):
224
+ """
225
+ The Excel worksheet reference to the Y values for this chart (not
226
+ including the column heading).
227
+ """
228
+ return self._chart_data.y_values_ref(self)
229
+
230
+
231
+ class _BaseDataPoint(object):
232
+ """
233
+ Base class providing common members for data point objects.
234
+ """
235
+
236
+ def __init__(self, series_data, number_format):
237
+ super(_BaseDataPoint, self).__init__()
238
+ self._series_data = series_data
239
+ self._number_format = number_format
240
+
241
+ @property
242
+ def number_format(self):
243
+ """
244
+ The formatting template string that determines how the value of this
245
+ data point is formatted, both in the chart and in the Excel
246
+ spreadsheet; for example '#,##0.0'. If not specified for this data
247
+ point, it is inherited from the parent series data object.
248
+ """
249
+ number_format = self._number_format
250
+ if number_format is None:
251
+ return self._series_data.number_format
252
+ return number_format
253
+
254
+
255
+ class CategoryChartData(_BaseChartData):
256
+ """
257
+ Accumulates data specifying the categories and series values for a chart
258
+ and acts as a proxy for the chart data table that will be written to an
259
+ Excel worksheet. Used as a parameter in :meth:`shapes.add_chart` and
260
+ :meth:`Chart.replace_data`.
261
+
262
+ This object is suitable for use with category charts, i.e. all those
263
+ having a discrete set of label values (categories) as the range of their
264
+ independent variable (X-axis) values. Unlike the ChartData types for
265
+ charts supporting a continuous range of independent variable values (such
266
+ as XyChartData), CategoryChartData has a single collection of category
267
+ (X) values and each data point in its series specifies only the Y value.
268
+ The corresponding X value is inferred by its position in the sequence.
269
+ """
270
+
271
+ def add_category(self, label):
272
+ """
273
+ Return a newly created |data.Category| object having *label* and
274
+ appended to the end of the category collection for this chart.
275
+ *label* can be a string, a number, a datetime.date, or
276
+ datetime.datetime object. All category labels in a chart must be the
277
+ same type. All category labels in a chart having multi-level
278
+ categories must be strings.
279
+ """
280
+ return self.categories.add_category(label)
281
+
282
+ def add_series(self, name, values=(), number_format=None):
283
+ """
284
+ Add a series to this data set entitled *name* and having the data
285
+ points specified by *values*, an iterable of numeric values.
286
+ *number_format* specifies how the series values will be displayed,
287
+ and may be a string, e.g. '#,##0' corresponding to an Excel number
288
+ format.
289
+ """
290
+ series_data = CategorySeriesData(self, name, number_format)
291
+ self.append(series_data)
292
+ for value in values:
293
+ series_data.add_data_point(value)
294
+ return series_data
295
+
296
+ @property
297
+ def categories(self):
298
+ """|data.Categories| object providing access to category-object hierarchy.
299
+
300
+ Assigning an iterable of category labels (strings, numbers, or dates) replaces
301
+ the |data.Categories| object with a new one containing a category for each label
302
+ in the sequence.
303
+
304
+ Creating a chart from chart data having date categories will cause the chart to
305
+ have a |DateAxis| for its category axis.
306
+ """
307
+ if not getattr(self, "_categories", False):
308
+ self._categories = Categories()
309
+ return self._categories
310
+
311
+ @categories.setter
312
+ def categories(self, category_labels):
313
+ categories = Categories()
314
+ for label in category_labels:
315
+ categories.add_category(label)
316
+ self._categories = categories
317
+
318
+ @property
319
+ def categories_ref(self):
320
+ """
321
+ The Excel worksheet reference to the categories for this chart (not
322
+ including the column heading).
323
+ """
324
+ return self._workbook_writer.categories_ref
325
+
326
+ def values_ref(self, series):
327
+ """
328
+ The Excel worksheet reference to the values for *series* (not
329
+ including the column heading).
330
+ """
331
+ return self._workbook_writer.values_ref(series)
332
+
333
+ @lazyproperty
334
+ def _workbook_writer(self):
335
+ """
336
+ The worksheet writer object to which layout and writing of the Excel
337
+ worksheet for this chart will be delegated.
338
+ """
339
+ return CategoryWorkbookWriter(self)
340
+
341
+
342
+ class Categories(Sequence):
343
+ """
344
+ A sequence of |data.Category| objects, also having certain hierarchical
345
+ graph behaviors for support of multi-level (nested) categories.
346
+ """
347
+
348
+ def __init__(self):
349
+ super(Categories, self).__init__()
350
+ self._categories = []
351
+ self._number_format = None
352
+
353
+ def __getitem__(self, idx):
354
+ return self._categories.__getitem__(idx)
355
+
356
+ def __len__(self):
357
+ """
358
+ Return the count of the highest level of category in this sequence.
359
+ If it contains hierarchical (multi-level) categories, this number
360
+ will differ from :attr:`category_count`, which is the number of leaf
361
+ nodes.
362
+ """
363
+ return self._categories.__len__()
364
+
365
+ def add_category(self, label):
366
+ """
367
+ Return a newly created |data.Category| object having *label* and
368
+ appended to the end of this category sequence. *label* can be
369
+ a string, a number, a datetime.date, or datetime.datetime object. All
370
+ category labels in a chart must be the same type. All category labels
371
+ in a chart having multi-level categories must be strings.
372
+
373
+ Creating a chart from chart data having date categories will cause
374
+ the chart to have a |DateAxis| for its category axis.
375
+ """
376
+ category = Category(label, self)
377
+ self._categories.append(category)
378
+ return category
379
+
380
+ @property
381
+ def are_dates(self):
382
+ """
383
+ Return |True| if the first category in this collection has a date
384
+ label (as opposed to str or numeric). A date label is one of type
385
+ datetime.date or datetime.datetime. Returns |False| otherwise,
386
+ including when this category collection is empty. It also returns
387
+ False when this category collection is hierarchical, because
388
+ hierarchical categories can only be written as string labels.
389
+ """
390
+ if self.depth != 1:
391
+ return False
392
+ first_cat_label = self[0].label
393
+ date_types = (datetime.date, datetime.datetime)
394
+ if isinstance(first_cat_label, date_types):
395
+ return True
396
+ return False
397
+
398
+ @property
399
+ def are_numeric(self):
400
+ """
401
+ Return |True| if the first category in this collection has a numeric
402
+ label (as opposed to a string label), including if that value is
403
+ a datetime.date or datetime.datetime object (as those are converted
404
+ to integers for storage in Excel). Returns |False| otherwise,
405
+ including when this category collection is empty. It also returns
406
+ False when this category collection is hierarchical, because
407
+ hierarchical categories can only be written as string labels.
408
+ """
409
+ if self.depth != 1:
410
+ return False
411
+ # This method only tests the first category. The categories must
412
+ # be of uniform type, and if they're not, there will be problems
413
+ # later in the process, but it's not this method's job to validate
414
+ # the caller's input.
415
+ first_cat_label = self[0].label
416
+ numeric_types = (Number, datetime.date, datetime.datetime)
417
+ if isinstance(first_cat_label, numeric_types):
418
+ return True
419
+ return False
420
+
421
+ @property
422
+ def depth(self):
423
+ """
424
+ The number of hierarchy levels in this category graph. Returns 0 if
425
+ it contains no categories.
426
+ """
427
+ categories = self._categories
428
+ if not categories:
429
+ return 0
430
+ first_depth = categories[0].depth
431
+ for category in categories[1:]:
432
+ if category.depth != first_depth:
433
+ raise ValueError("category depth not uniform")
434
+ return first_depth
435
+
436
+ def index(self, category):
437
+ """
438
+ The offset of *category* in the overall sequence of leaf categories.
439
+ A non-leaf category gets the index of its first sub-category.
440
+ """
441
+ index = 0
442
+ for this_category in self._categories:
443
+ if category is this_category:
444
+ return index
445
+ index += this_category.leaf_count
446
+ raise ValueError("category not in top-level categories")
447
+
448
+ @property
449
+ def leaf_count(self):
450
+ """
451
+ The number of leaf-level categories in this hierarchy. The return
452
+ value is the same as that of `len()` only when the hierarchy is
453
+ single level.
454
+ """
455
+ return sum(c.leaf_count for c in self._categories)
456
+
457
+ @property
458
+ def levels(self):
459
+ """
460
+ A generator of (idx, label) sequences representing the category
461
+ hierarchy from the bottom up. The first level contains all leaf
462
+ categories, and each subsequent is the next level up.
463
+ """
464
+
465
+ def levels(categories):
466
+ # yield all lower levels
467
+ sub_categories = [sc for c in categories for sc in c.sub_categories]
468
+ if sub_categories:
469
+ for level in levels(sub_categories):
470
+ yield level
471
+ # yield this level
472
+ yield [(cat.idx, cat.label) for cat in categories]
473
+
474
+ for level in levels(self):
475
+ yield level
476
+
477
+ @property
478
+ def number_format(self):
479
+ """
480
+ Read/write. Return a string representing the number format used in
481
+ Excel to format these category values, e.g. '0.0' or 'mm/dd/yyyy'.
482
+ This string is only relevant when the categories are numeric or date
483
+ type, although it returns 'General' without error when the categories
484
+ are string labels. Assigning |None| causes the default number format
485
+ to be used, based on the type of the category labels.
486
+ """
487
+ GENERAL = "General"
488
+
489
+ # defined value takes precedence
490
+ if self._number_format is not None:
491
+ return self._number_format
492
+
493
+ # multi-level (should) always be string labels
494
+ # zero depth means empty in which case we can't tell anyway
495
+ if self.depth != 1:
496
+ return GENERAL
497
+
498
+ # everything except dates gets 'General'
499
+ first_cat_label = self[0].label
500
+ if isinstance(first_cat_label, (datetime.date, datetime.datetime)):
501
+ return r"yyyy\-mm\-dd"
502
+ return GENERAL
503
+
504
+ @number_format.setter
505
+ def number_format(self, value):
506
+ self._number_format = value
507
+
508
+
509
+ class Category(object):
510
+ """
511
+ A chart category, primarily having a label to be displayed on the
512
+ category axis, but also able to be configured in a hierarchy for support
513
+ of multi-level category charts.
514
+ """
515
+
516
+ def __init__(self, label, parent):
517
+ super(Category, self).__init__()
518
+ self._label = label
519
+ self._parent = parent
520
+ self._sub_categories = []
521
+
522
+ def add_sub_category(self, label):
523
+ """
524
+ Return a newly created |data.Category| object having *label* and
525
+ appended to the end of the sub-category sequence for this category.
526
+ """
527
+ category = Category(label, self)
528
+ self._sub_categories.append(category)
529
+ return category
530
+
531
+ @property
532
+ def depth(self):
533
+ """
534
+ The number of hierarchy levels rooted at this category node. Returns
535
+ 1 if this category has no sub-categories.
536
+ """
537
+ sub_categories = self._sub_categories
538
+ if not sub_categories:
539
+ return 1
540
+ first_depth = sub_categories[0].depth
541
+ for category in sub_categories[1:]:
542
+ if category.depth != first_depth:
543
+ raise ValueError("category depth not uniform")
544
+ return first_depth + 1
545
+
546
+ @property
547
+ def idx(self):
548
+ """
549
+ The offset of this category in the overall sequence of leaf
550
+ categories. A non-leaf category gets the index of its first
551
+ sub-category.
552
+ """
553
+ return self._parent.index(self)
554
+
555
+ def index(self, sub_category):
556
+ """
557
+ The offset of *sub_category* in the overall sequence of leaf
558
+ categories.
559
+ """
560
+ index = self._parent.index(self)
561
+ for this_sub_category in self._sub_categories:
562
+ if sub_category is this_sub_category:
563
+ return index
564
+ index += this_sub_category.leaf_count
565
+ raise ValueError("sub_category not in this category")
566
+
567
+ @property
568
+ def leaf_count(self):
569
+ """
570
+ The number of leaf category nodes under this category. Returns
571
+ 1 if this category has no sub-categories.
572
+ """
573
+ if not self._sub_categories:
574
+ return 1
575
+ return sum(category.leaf_count for category in self._sub_categories)
576
+
577
+ @property
578
+ def label(self):
579
+ """
580
+ The value that appears on the axis for this category. The label can
581
+ be a string, a number, or a datetime.date or datetime.datetime
582
+ object.
583
+ """
584
+ return self._label if self._label is not None else ""
585
+
586
+ def numeric_str_val(self, date_1904=False):
587
+ """
588
+ The string representation of the numeric (or date) label of this
589
+ category, suitable for use in the XML `c:pt` element for this
590
+ category. The optional *date_1904* parameter specifies the epoch used
591
+ for calculating Excel date numbers.
592
+ """
593
+ label = self._label
594
+ if isinstance(label, (datetime.date, datetime.datetime)):
595
+ return "%.1f" % self._excel_date_number(date_1904)
596
+ return str(self._label)
597
+
598
+ @property
599
+ def sub_categories(self):
600
+ """
601
+ The sequence of child categories for this category.
602
+ """
603
+ return self._sub_categories
604
+
605
+ def _excel_date_number(self, date_1904):
606
+ """
607
+ Return an integer representing the date label of this category as the
608
+ number of days since January 1, 1900 (or 1904 if date_1904 is
609
+ |True|).
610
+ """
611
+ date, label = datetime.date, self._label
612
+ # -- get date from label in type-independent-ish way
613
+ date_ = date(label.year, label.month, label.day)
614
+ epoch = date(1904, 1, 1) if date_1904 else date(1899, 12, 31)
615
+ delta = date_ - epoch
616
+ excel_day_number = delta.days
617
+
618
+ # -- adjust for Excel mistaking 1900 for a leap year --
619
+ if not date_1904 and excel_day_number > 59:
620
+ excel_day_number += 1
621
+
622
+ return excel_day_number
623
+
624
+
625
+ class ChartData(CategoryChartData):
626
+ """
627
+ |ChartData| is simply an alias for |CategoryChartData| and may be removed
628
+ in a future release. All new development should use |CategoryChartData|
629
+ for creating or replacing the data in chart types other than XY and
630
+ Bubble.
631
+ """
632
+
633
+
634
+ class CategorySeriesData(_BaseSeriesData):
635
+ """
636
+ The data specific to a particular category chart series. It provides
637
+ access to the series label, the series data points, and an optional
638
+ number format to be applied to each data point not having a specified
639
+ number format.
640
+ """
641
+
642
+ def add_data_point(self, value, number_format=None):
643
+ """
644
+ Return a CategoryDataPoint object newly created with value *value*,
645
+ an optional *number_format*, and appended to this sequence.
646
+ """
647
+ data_point = CategoryDataPoint(self, value, number_format)
648
+ self.append(data_point)
649
+ return data_point
650
+
651
+ @property
652
+ def categories(self):
653
+ """
654
+ The |data.Categories| object that provides access to the category
655
+ objects for this series.
656
+ """
657
+ return self._chart_data.categories
658
+
659
+ @property
660
+ def categories_ref(self):
661
+ """
662
+ The Excel worksheet reference to the categories for this chart (not
663
+ including the column heading).
664
+ """
665
+ return self._chart_data.categories_ref
666
+
667
+ @property
668
+ def values(self):
669
+ """
670
+ A sequence containing the (Y) value of each datapoint in this series,
671
+ in data point order.
672
+ """
673
+ return [dp.value for dp in self._data_points]
674
+
675
+ @property
676
+ def values_ref(self):
677
+ """
678
+ The Excel worksheet reference to the (Y) values for this series (not
679
+ including the column heading).
680
+ """
681
+ return self._chart_data.values_ref(self)
682
+
683
+
684
+ class XyChartData(_BaseChartData):
685
+ """
686
+ A specialized ChartData object suitable for use with an XY (aka. scatter)
687
+ chart. Unlike ChartData, it has no category sequence. Rather, each data
688
+ point of each series specifies both an X and a Y value.
689
+ """
690
+
691
+ def add_series(self, name, number_format=None):
692
+ """
693
+ Return an |XySeriesData| object newly created and added at the end of
694
+ this sequence, identified by *name* and values formatted with
695
+ *number_format*.
696
+ """
697
+ series_data = XySeriesData(self, name, number_format)
698
+ self.append(series_data)
699
+ return series_data
700
+
701
+ @lazyproperty
702
+ def _workbook_writer(self):
703
+ """
704
+ The worksheet writer object to which layout and writing of the Excel
705
+ worksheet for this chart will be delegated.
706
+ """
707
+ return XyWorkbookWriter(self)
708
+
709
+
710
+ class BubbleChartData(XyChartData):
711
+ """
712
+ A specialized ChartData object suitable for use with a bubble chart.
713
+ A bubble chart is essentially an XY chart where the markers are scaled to
714
+ provide a third quantitative dimension to the exhibit.
715
+ """
716
+
717
+ def add_series(self, name, number_format=None):
718
+ """
719
+ Return a |BubbleSeriesData| object newly created and added at the end
720
+ of this sequence, and having series named *name* and values formatted
721
+ with *number_format*.
722
+ """
723
+ series_data = BubbleSeriesData(self, name, number_format)
724
+ self.append(series_data)
725
+ return series_data
726
+
727
+ def bubble_sizes_ref(self, series):
728
+ """
729
+ The Excel worksheet reference for the range containing the bubble
730
+ sizes for *series*.
731
+ """
732
+ return self._workbook_writer.bubble_sizes_ref(series)
733
+
734
+ @lazyproperty
735
+ def _workbook_writer(self):
736
+ """
737
+ The worksheet writer object to which layout and writing of the Excel
738
+ worksheet for this chart will be delegated.
739
+ """
740
+ return BubbleWorkbookWriter(self)
741
+
742
+
743
+ class XySeriesData(_BaseSeriesData):
744
+ """
745
+ The data specific to a particular XY chart series. It provides access to
746
+ the series label, the series data points, and an optional number format
747
+ to be applied to each data point not having a specified number format.
748
+
749
+ The sequence of data points in an XY series is significant; lines are
750
+ plotted following the sequence of points, even if that causes a line
751
+ segment to "travel backward" (implying a multi-valued function). The data
752
+ points are not automatically sorted into increasing order by X value.
753
+ """
754
+
755
+ def add_data_point(self, x, y, number_format=None):
756
+ """
757
+ Return an XyDataPoint object newly created with values *x* and *y*,
758
+ and appended to this sequence.
759
+ """
760
+ data_point = XyDataPoint(self, x, y, number_format)
761
+ self.append(data_point)
762
+ return data_point
763
+
764
+
765
+ class BubbleSeriesData(XySeriesData):
766
+ """
767
+ The data specific to a particular Bubble chart series. It provides access
768
+ to the series label, the series data points, and an optional number
769
+ format to be applied to each data point not having a specified number
770
+ format.
771
+
772
+ The sequence of data points in a bubble chart series is maintained
773
+ throughout the chart building process because a data point has no unique
774
+ identifier and can only be retrieved by index.
775
+ """
776
+
777
+ def add_data_point(self, x, y, size, number_format=None):
778
+ """
779
+ Append a new BubbleDataPoint object having the values *x*, *y*, and
780
+ *size*. The optional *number_format* is used to format the Y value.
781
+ If not provided, the number format is inherited from the series data.
782
+ """
783
+ data_point = BubbleDataPoint(self, x, y, size, number_format)
784
+ self.append(data_point)
785
+ return data_point
786
+
787
+ @property
788
+ def bubble_sizes(self):
789
+ """
790
+ A sequence containing the bubble size for each datapoint in this
791
+ series, in data point order.
792
+ """
793
+ return [dp.bubble_size for dp in self._data_points]
794
+
795
+ @property
796
+ def bubble_sizes_ref(self):
797
+ """
798
+ The Excel worksheet reference for the range containing the bubble
799
+ sizes for this series.
800
+ """
801
+ return self._chart_data.bubble_sizes_ref(self)
802
+
803
+
804
+ class CategoryDataPoint(_BaseDataPoint):
805
+ """
806
+ A data point in a category chart series. Provides access to the value of
807
+ the datapoint and the number format with which it should appear in the
808
+ Excel file.
809
+ """
810
+
811
+ def __init__(self, series_data, value, number_format):
812
+ super(CategoryDataPoint, self).__init__(series_data, number_format)
813
+ self._value = value
814
+
815
+ @property
816
+ def value(self):
817
+ """
818
+ The (Y) value for this category data point.
819
+ """
820
+ return self._value
821
+
822
+
823
+ class XyDataPoint(_BaseDataPoint):
824
+ """
825
+ A data point in an XY chart series. Provides access to the x and y values
826
+ of the datapoint.
827
+ """
828
+
829
+ def __init__(self, series_data, x, y, number_format):
830
+ super(XyDataPoint, self).__init__(series_data, number_format)
831
+ self._x = x
832
+ self._y = y
833
+
834
+ @property
835
+ def x(self):
836
+ """
837
+ The X value for this XY data point.
838
+ """
839
+ return self._x
840
+
841
+ @property
842
+ def y(self):
843
+ """
844
+ The Y value for this XY data point.
845
+ """
846
+ return self._y
847
+
848
+
849
+ class BubbleDataPoint(XyDataPoint):
850
+ """
851
+ A data point in a bubble chart series. Provides access to the x, y, and
852
+ size values of the datapoint.
853
+ """
854
+
855
+ def __init__(self, series_data, x, y, size, number_format):
856
+ super(BubbleDataPoint, self).__init__(series_data, x, y, number_format)
857
+ self._size = size
858
+
859
+ @property
860
+ def bubble_size(self):
861
+ """
862
+ The value representing the size of the bubble for this data point.
863
+ """
864
+ return self._size