ruby_pptx 0.1.0
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +83 -0
- data/LICENSE +21 -0
- data/NOTICE +48 -0
- data/README.md +272 -0
- data/lib/ruby_pptx/action.rb +120 -0
- data/lib/ruby_pptx/autoshape_spec.rb +337 -0
- data/lib/ruby_pptx/builder.rb +228 -0
- data/lib/ruby_pptx/chart/categories.rb +208 -0
- data/lib/ruby_pptx/chart/chart.rb +443 -0
- data/lib/ruby_pptx/chart/combo.rb +230 -0
- data/lib/ruby_pptx/chart/data.rb +147 -0
- data/lib/ruby_pptx/chart/format.rb +647 -0
- data/lib/ruby_pptx/chart/workbook_writer.rb +212 -0
- data/lib/ruby_pptx/chart/xml_writer.rb +953 -0
- data/lib/ruby_pptx/chart/xy_data.rb +182 -0
- data/lib/ruby_pptx/core_ext.rb +12 -0
- data/lib/ruby_pptx/dml/color.rb +155 -0
- data/lib/ruby_pptx/dml/effect.rb +33 -0
- data/lib/ruby_pptx/dml/fill.rb +234 -0
- data/lib/ruby_pptx/element_proxy.rb +53 -0
- data/lib/ruby_pptx/enum/action.rb +46 -0
- data/lib/ruby_pptx/enum/base.rb +139 -0
- data/lib/ruby_pptx/enum/chart.rb +278 -0
- data/lib/ruby_pptx/enum/dml.rb +237 -0
- data/lib/ruby_pptx/enum/lang.rb +441 -0
- data/lib/ruby_pptx/enum/prog_id.rb +38 -0
- data/lib/ruby_pptx/enum/shapes.rb +514 -0
- data/lib/ruby_pptx/enum/text.rb +108 -0
- data/lib/ruby_pptx/errors.rb +15 -0
- data/lib/ruby_pptx/geometry.rb +89 -0
- data/lib/ruby_pptx/image.rb +347 -0
- data/lib/ruby_pptx/length.rb +120 -0
- data/lib/ruby_pptx/media.rb +64 -0
- data/lib/ruby_pptx/numeric_lengths.rb +30 -0
- data/lib/ruby_pptx/opc/constants.rb +205 -0
- data/lib/ruby_pptx/opc/oxml.rb +99 -0
- data/lib/ruby_pptx/opc/pack_uri.rb +146 -0
- data/lib/ruby_pptx/opc/package.rb +482 -0
- data/lib/ruby_pptx/opc/serialized.rb +229 -0
- data/lib/ruby_pptx/opc/spec.rb +37 -0
- data/lib/ruby_pptx/oxml/action.rb +43 -0
- data/lib/ruby_pptx/oxml/chart.rb +785 -0
- data/lib/ruby_pptx/oxml/content_model.rb +186 -0
- data/lib/ruby_pptx/oxml/core_properties.rb +145 -0
- data/lib/ruby_pptx/oxml/dml/color.rb +115 -0
- data/lib/ruby_pptx/oxml/dml/fill.rb +137 -0
- data/lib/ruby_pptx/oxml/element.rb +329 -0
- data/lib/ruby_pptx/oxml/ns.rb +115 -0
- data/lib/ruby_pptx/oxml/presentation.rb +110 -0
- data/lib/ruby_pptx/oxml/section.rb +65 -0
- data/lib/ruby_pptx/oxml/shapes/autoshape.rb +310 -0
- data/lib/ruby_pptx/oxml/shapes/groupshape.rb +217 -0
- data/lib/ruby_pptx/oxml/shapes/other.rb +405 -0
- data/lib/ruby_pptx/oxml/shapes/shared.rb +292 -0
- data/lib/ruby_pptx/oxml/simple_types.rb +563 -0
- data/lib/ruby_pptx/oxml/slide.rb +290 -0
- data/lib/ruby_pptx/oxml/table.rb +253 -0
- data/lib/ruby_pptx/oxml/text.rb +324 -0
- data/lib/ruby_pptx/oxml/theme.rb +93 -0
- data/lib/ruby_pptx/package.rb +139 -0
- data/lib/ruby_pptx/parts/chart.rb +75 -0
- data/lib/ruby_pptx/parts/core_properties.rb +40 -0
- data/lib/ruby_pptx/parts/embedded_package.rb +43 -0
- data/lib/ruby_pptx/parts/image.rb +55 -0
- data/lib/ruby_pptx/parts/media.rb +17 -0
- data/lib/ruby_pptx/parts/presentation.rb +79 -0
- data/lib/ruby_pptx/parts/slide.rb +221 -0
- data/lib/ruby_pptx/parts/theme.rb +26 -0
- data/lib/ruby_pptx/pattern_matching.rb +50 -0
- data/lib/ruby_pptx/presentation.rb +128 -0
- data/lib/ruby_pptx/refinements.rb +21 -0
- data/lib/ruby_pptx/section.rb +130 -0
- data/lib/ruby_pptx/shapes/adjustments.rb +83 -0
- data/lib/ruby_pptx/shapes/authoring.rb +110 -0
- data/lib/ruby_pptx/shapes/base.rb +138 -0
- data/lib/ruby_pptx/shapes/freeform.rb +141 -0
- data/lib/ruby_pptx/shapes/placeholder.rb +184 -0
- data/lib/ruby_pptx/shapes/shape.rb +337 -0
- data/lib/ruby_pptx/shapes/shape_tree.rb +634 -0
- data/lib/ruby_pptx/sliceable.rb +34 -0
- data/lib/ruby_pptx/slide.rb +430 -0
- data/lib/ruby_pptx/table.rb +316 -0
- data/lib/ruby_pptx/table_paging.rb +91 -0
- data/lib/ruby_pptx/templates/default.pptx +0 -0
- data/lib/ruby_pptx/templates/docx-icon.emf +0 -0
- data/lib/ruby_pptx/templates/generic-icon.emf +0 -0
- data/lib/ruby_pptx/templates/media-speaker.png +0 -0
- data/lib/ruby_pptx/templates/notes.xml +23 -0
- data/lib/ruby_pptx/templates/notesMaster.xml +352 -0
- data/lib/ruby_pptx/templates/pptx-icon.emf +0 -0
- data/lib/ruby_pptx/templates/slideMaster.xml +277 -0
- data/lib/ruby_pptx/templates/theme.xml +321 -0
- data/lib/ruby_pptx/templates/xlsx-icon.emf +0 -0
- data/lib/ruby_pptx/text/fitter.rb +72 -0
- data/lib/ruby_pptx/text/font_metrics.rb +221 -0
- data/lib/ruby_pptx/text/text.rb +393 -0
- data/lib/ruby_pptx/theme.rb +98 -0
- data/lib/ruby_pptx/version.rb +5 -0
- data/lib/ruby_pptx.rb +90 -0
- metadata +174 -0
|
@@ -0,0 +1,785 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "ruby_pptx/oxml/element"
|
|
4
|
+
require "ruby_pptx/oxml/content_model"
|
|
5
|
+
require "ruby_pptx/oxml/simple_types"
|
|
6
|
+
require "ruby_pptx/oxml/text"
|
|
7
|
+
require "ruby_pptx/enum/chart"
|
|
8
|
+
|
|
9
|
+
module Pptx
|
|
10
|
+
module Oxml
|
|
11
|
+
# Chart elements, after python-pptx's `oxml/chart` package.
|
|
12
|
+
#
|
|
13
|
+
# The class boundaries, tag sequences and the split between the two kinds
|
|
14
|
+
# of boolean element follow python-pptx's registrations exactly: what a
|
|
15
|
+
# chart property writes depends on which class its element belongs to,
|
|
16
|
+
# and byte parity with python-pptx is the point.
|
|
17
|
+
|
|
18
|
+
# -------------------------------------------------------------------------
|
|
19
|
+
# Value elements -- one `val` attribute, typed by what it holds
|
|
20
|
+
# -------------------------------------------------------------------------
|
|
21
|
+
|
|
22
|
+
# A boolean `val` whose schema default is true, so writing true removes
|
|
23
|
+
# the attribute: `<c:smooth/>` means smooth. Absent reads as true.
|
|
24
|
+
class CT_Boolean < Element
|
|
25
|
+
tag "c:delete", "c:varyColors", "c:smooth", "c:bubble3D", "c:date1904"
|
|
26
|
+
optional_attr "val", type: SimpleTypes::XsdBoolean, default: true
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# A boolean `val` always written out, `val="1"` or `val="0"`, even though
|
|
30
|
+
# the schema default is the same true. PowerPoint writes these explicitly
|
|
31
|
+
# and python-pptx does too.
|
|
32
|
+
class CT_BooleanExplicit < Element
|
|
33
|
+
tag "c:overlay", "c:showVal", "c:showCatName", "c:showSerName", "c:showPercent",
|
|
34
|
+
"c:showLegendKey", "c:showBubbleSize", "c:autoTitleDeleted", "c:invertIfNegative",
|
|
35
|
+
"c:plotVisOnly", "c:showDLblsOverMax", "c:showLeaderLines"
|
|
36
|
+
|
|
37
|
+
def val
|
|
38
|
+
raw = get("val")
|
|
39
|
+
raw.nil? || SimpleTypes::XsdBoolean.from_xml(raw)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def val=(value)
|
|
43
|
+
set("val", value ? "1" : "0")
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# `c:autoUpdate`, whether the chart refreshes itself from the workbook.
|
|
48
|
+
class CT_AutoUpdate < Element
|
|
49
|
+
tag "c:autoUpdate"
|
|
50
|
+
optional_attr "val", type: SimpleTypes::XsdBoolean
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# A double-valued `val`: axis scale limits, a crossing point, a manual
|
|
54
|
+
# layout coordinate.
|
|
55
|
+
class CT_Double < Element
|
|
56
|
+
tag "c:max", "c:min", "c:crossesAt", "c:x"
|
|
57
|
+
required_attr "val", type: SimpleTypes::XsdDouble
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# `c:majorUnit` and `c:minorUnit`, which must be positive.
|
|
61
|
+
class CT_AxisUnit < Element
|
|
62
|
+
tag "c:majorUnit", "c:minorUnit"
|
|
63
|
+
required_attr "val", type: SimpleTypes::ST_AxisUnit
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# An unsigned integer `val`: series index and order, axis ids.
|
|
67
|
+
class CT_UnsignedInt < Element
|
|
68
|
+
tag "c:idx", "c:order", "c:crossAx", "c:axId", "c:ptCount"
|
|
69
|
+
required_attr "val", type: SimpleTypes::XsdUnsignedInt
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# String-valued elements this library writes from templates but has no
|
|
73
|
+
# typed accessor for.
|
|
74
|
+
class CT_ChartString < Element
|
|
75
|
+
tag "c:axPos", "c:radarStyle", "c:scatterStyle", "c:dispBlanksAs"
|
|
76
|
+
optional_attr "val", type: SimpleTypes::XsdString
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
class CT_LegendPos < Element
|
|
80
|
+
tag "c:legendPos"
|
|
81
|
+
optional_attr "val", type: Enum::XL_LEGEND_POSITION, default: Enum::XL_LEGEND_POSITION::RIGHT
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
class CT_DLblPos < Element
|
|
85
|
+
tag "c:dLblPos"
|
|
86
|
+
required_attr "val", type: Enum::XL_DATA_LABEL_POSITION
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
class CT_TickLblPos < Element
|
|
90
|
+
tag "c:tickLblPos"
|
|
91
|
+
optional_attr "val", type: Enum::XL_TICK_LABEL_POSITION
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
class CT_TickMark < Element
|
|
95
|
+
tag "c:majorTickMark", "c:minorTickMark"
|
|
96
|
+
optional_attr "val", type: Enum::XL_TICK_MARK, default: Enum::XL_TICK_MARK::CROSS
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
class CT_Crosses < Element
|
|
100
|
+
tag "c:crosses"
|
|
101
|
+
required_attr "val", type: Enum::XL_AXIS_CROSSES
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
class CT_Orientation < Element
|
|
105
|
+
tag "c:orientation"
|
|
106
|
+
optional_attr "val", type: SimpleTypes::ST_Orientation,
|
|
107
|
+
default: SimpleTypes::ST_Orientation::MIN_MAX
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
class CT_LblOffset < Element
|
|
111
|
+
tag "c:lblOffset"
|
|
112
|
+
optional_attr "val", type: SimpleTypes::ST_LblOffset, default: 100
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
class CT_Grouping < Element
|
|
116
|
+
tag "c:grouping"
|
|
117
|
+
optional_attr "val", type: SimpleTypes::ST_Grouping
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
class CT_BarDir < Element
|
|
121
|
+
tag "c:barDir"
|
|
122
|
+
optional_attr "val", type: SimpleTypes::ST_BarDir, default: SimpleTypes::ST_BarDir::COL
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
# `c:gapWidth`: space between bar groups, as a percentage of bar width.
|
|
126
|
+
class CT_GapAmount < Element
|
|
127
|
+
tag "c:gapWidth"
|
|
128
|
+
optional_attr "val", type: SimpleTypes::ST_GapAmount, default: 150
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
class CT_Overlap < Element
|
|
132
|
+
tag "c:overlap"
|
|
133
|
+
required_attr "val", type: SimpleTypes::ST_Overlap
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
class CT_BubbleScale < Element
|
|
137
|
+
tag "c:bubbleScale"
|
|
138
|
+
optional_attr "val", type: SimpleTypes::ST_BubbleScale, default: 100
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
# `c:style`, the chart style number shown in PowerPoint's style gallery.
|
|
142
|
+
class CT_Style < Element
|
|
143
|
+
tag "c:style"
|
|
144
|
+
required_attr "val", type: SimpleTypes::ST_Style
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
class CT_MarkerSize < Element
|
|
148
|
+
tag "c:size"
|
|
149
|
+
required_attr "val", type: SimpleTypes::ST_MarkerSize
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
class CT_MarkerStyle < Element
|
|
153
|
+
tag "c:symbol"
|
|
154
|
+
required_attr "val", type: Enum::XL_MARKER_STYLE
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
class CT_LayoutMode < Element
|
|
158
|
+
tag "c:xMode"
|
|
159
|
+
optional_attr "val", type: SimpleTypes::ST_LayoutMode,
|
|
160
|
+
default: SimpleTypes::ST_LayoutMode::FACTOR
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# `c:numFmt`, a number format applied to an axis or to data labels.
|
|
164
|
+
class CT_NumberFormat < Element
|
|
165
|
+
tag "c:numFmt"
|
|
166
|
+
optional_attr "formatCode", type: SimpleTypes::XsdString
|
|
167
|
+
optional_attr "sourceLinked", type: SimpleTypes::XsdBoolean
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# -------------------------------------------------------------------------
|
|
171
|
+
# Shared structure
|
|
172
|
+
# -------------------------------------------------------------------------
|
|
173
|
+
|
|
174
|
+
# Anything carrying a `c:txPr`, which is where a chart element's font
|
|
175
|
+
# lives. The text body is created on first use.
|
|
176
|
+
module ChartTextProperties
|
|
177
|
+
def defRPr = get_or_add_txPr.defRPr
|
|
178
|
+
|
|
179
|
+
def new_txPr = build_from_xml(CT_TextBody::TXPR_XML)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# `c:layout`, holding an optional manual layout.
|
|
183
|
+
class CT_Layout < Element
|
|
184
|
+
tag "c:layout"
|
|
185
|
+
zero_or_one "c:manualLayout", successors: %w[c:extLst]
|
|
186
|
+
|
|
187
|
+
# The horizontal offset as a fraction of the chart width; 0.0 when the
|
|
188
|
+
# position is automatic.
|
|
189
|
+
def horz_offset = manualLayout&.horz_offset || 0.0
|
|
190
|
+
|
|
191
|
+
# Setting 0.0 hands the position back to PowerPoint.
|
|
192
|
+
def horz_offset=(offset)
|
|
193
|
+
if offset.zero?
|
|
194
|
+
remove_manualLayout
|
|
195
|
+
return
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
get_or_add_manualLayout.horz_offset = offset
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
class CT_ManualLayout < Element
|
|
203
|
+
tag "c:manualLayout"
|
|
204
|
+
TAG_SEQ = %w[c:layoutTarget c:xMode c:yMode c:wMode c:hMode c:x c:y c:w c:h
|
|
205
|
+
c:extLst].freeze
|
|
206
|
+
zero_or_one "c:xMode", successors: TAG_SEQ[2..]
|
|
207
|
+
zero_or_one "c:x", successors: TAG_SEQ[6..]
|
|
208
|
+
|
|
209
|
+
# Only a factor-mode x is an offset; an edge-mode x is a position.
|
|
210
|
+
def horz_offset
|
|
211
|
+
return 0.0 if x.nil? || xMode.nil? || xMode.val != SimpleTypes::ST_LayoutMode::FACTOR
|
|
212
|
+
|
|
213
|
+
x.val
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def horz_offset=(offset)
|
|
217
|
+
get_or_add_xMode.val = SimpleTypes::ST_LayoutMode::FACTOR
|
|
218
|
+
get_or_add_x.val = offset
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
# `c:tx`: a title's or data label's rich text, or a series name's cell
|
|
223
|
+
# reference. The same element serves all three, so one class does.
|
|
224
|
+
class CT_Tx < Element
|
|
225
|
+
tag "c:tx"
|
|
226
|
+
zero_or_one "c:strRef", successors: []
|
|
227
|
+
zero_or_one "c:rich", successors: []
|
|
228
|
+
|
|
229
|
+
RICH_XML = <<~XML.freeze
|
|
230
|
+
<c:rich #{Ns.nsdecls("c", "a")}>
|
|
231
|
+
<a:bodyPr/>
|
|
232
|
+
<a:lstStyle/>
|
|
233
|
+
<a:p>
|
|
234
|
+
<a:pPr>
|
|
235
|
+
<a:defRPr/>
|
|
236
|
+
</a:pPr>
|
|
237
|
+
</a:p>
|
|
238
|
+
</c:rich>
|
|
239
|
+
XML
|
|
240
|
+
|
|
241
|
+
def new_rich = build_from_xml(RICH_XML)
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
# `c:title`, the chart or axis title.
|
|
245
|
+
class CT_Title < Element
|
|
246
|
+
tag "c:title"
|
|
247
|
+
TAG_SEQ = %w[c:tx c:layout c:overlay c:spPr c:txPr c:extLst].freeze
|
|
248
|
+
|
|
249
|
+
zero_or_one "c:tx", successors: TAG_SEQ[1..]
|
|
250
|
+
zero_or_one "c:spPr", successors: TAG_SEQ[4..]
|
|
251
|
+
|
|
252
|
+
# A title with no text of its own yet, which is what PowerPoint and
|
|
253
|
+
# python-pptx both write when a title is switched on.
|
|
254
|
+
def self.new_title(context)
|
|
255
|
+
context.build_from_xml(<<~XML)
|
|
256
|
+
<c:title #{Ns.nsdecls("c")}>
|
|
257
|
+
<c:layout/>
|
|
258
|
+
<c:overlay val="0"/>
|
|
259
|
+
</c:title>
|
|
260
|
+
XML
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
# The rich-text body, or nil when the title has none.
|
|
264
|
+
def rich = xpath("./c:tx/c:rich").first
|
|
265
|
+
|
|
266
|
+
# A title takes rich text or a cell reference, never both.
|
|
267
|
+
def get_or_add_rich
|
|
268
|
+
tx = get_or_add_tx
|
|
269
|
+
tx.remove_strRef
|
|
270
|
+
tx.get_or_add_rich
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
|
|
274
|
+
# `c:majorGridlines` and `c:minorGridlines`.
|
|
275
|
+
class CT_ChartLines < Element
|
|
276
|
+
tag "c:majorGridlines", "c:minorGridlines"
|
|
277
|
+
zero_or_one "c:spPr", successors: []
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
# -------------------------------------------------------------------------
|
|
281
|
+
# Axes
|
|
282
|
+
# -------------------------------------------------------------------------
|
|
283
|
+
|
|
284
|
+
# `c:scaling`, the axis range and direction.
|
|
285
|
+
class CT_Scaling < Element
|
|
286
|
+
tag "c:scaling"
|
|
287
|
+
zero_or_one "c:logBase", successors: %w[c:orientation c:max c:min c:extLst]
|
|
288
|
+
zero_or_one "c:orientation", successors: %w[c:max c:min c:extLst]
|
|
289
|
+
zero_or_one "c:max", successors: %w[c:min c:extLst]
|
|
290
|
+
zero_or_one "c:min", successors: %w[c:extLst]
|
|
291
|
+
|
|
292
|
+
# nil means "auto", which is what an absent element says.
|
|
293
|
+
def maximum = max&.val
|
|
294
|
+
|
|
295
|
+
def maximum=(value)
|
|
296
|
+
remove_max
|
|
297
|
+
get_or_add_max.val = value unless value.nil?
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def minimum = min&.val
|
|
301
|
+
|
|
302
|
+
def minimum=(value)
|
|
303
|
+
remove_min
|
|
304
|
+
get_or_add_min.val = value unless value.nil?
|
|
305
|
+
end
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
# What the three axis elements share. They agree up to `c:crossesAt` and
|
|
309
|
+
# then diverge, which is why each declares its own tail below.
|
|
310
|
+
module BaseAxisElement
|
|
311
|
+
include ChartTextProperties
|
|
312
|
+
|
|
313
|
+
def self.included(base)
|
|
314
|
+
base.class_eval do
|
|
315
|
+
seq = self::TAG_SEQ
|
|
316
|
+
one_and_only_one "c:scaling"
|
|
317
|
+
zero_or_one "c:delete", successors: seq[3..]
|
|
318
|
+
zero_or_one "c:majorGridlines", successors: seq[5..]
|
|
319
|
+
zero_or_one "c:minorGridlines", successors: seq[6..]
|
|
320
|
+
zero_or_one "c:title", successors: seq[7..]
|
|
321
|
+
zero_or_one "c:numFmt", successors: seq[8..]
|
|
322
|
+
zero_or_one "c:majorTickMark", successors: seq[9..]
|
|
323
|
+
zero_or_one "c:minorTickMark", successors: seq[10..]
|
|
324
|
+
zero_or_one "c:tickLblPos", successors: seq[11..]
|
|
325
|
+
zero_or_one "c:spPr", successors: seq[12..]
|
|
326
|
+
zero_or_one "c:txPr", successors: seq[13..]
|
|
327
|
+
zero_or_one "c:crossAx", successors: seq[14..]
|
|
328
|
+
zero_or_one "c:crosses", successors: seq[15..]
|
|
329
|
+
zero_or_one "c:crossesAt", successors: seq[16..]
|
|
330
|
+
end
|
|
331
|
+
end
|
|
332
|
+
|
|
333
|
+
def orientation = scaling.orientation&.val || SimpleTypes::ST_Orientation::MIN_MAX
|
|
334
|
+
|
|
335
|
+
# Only a reversed axis is written; the normal direction is the default.
|
|
336
|
+
def orientation=(value)
|
|
337
|
+
scaling.remove_orientation
|
|
338
|
+
return unless value == SimpleTypes::ST_Orientation::MAX_MIN
|
|
339
|
+
|
|
340
|
+
scaling.get_or_add_orientation.val = value
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def new_title = CT_Title.new_title(self)
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
COMMON_AXIS_SEQ = %w[c:axId c:scaling c:delete c:axPos c:majorGridlines c:minorGridlines
|
|
347
|
+
c:title c:numFmt c:majorTickMark c:minorTickMark c:tickLblPos c:spPr
|
|
348
|
+
c:txPr c:crossAx c:crosses c:crossesAt].freeze
|
|
349
|
+
|
|
350
|
+
class CT_CatAx < Element
|
|
351
|
+
tag "c:catAx"
|
|
352
|
+
TAG_SEQ = (COMMON_AXIS_SEQ + %w[c:auto c:lblAlgn c:lblOffset c:tickLblSkip
|
|
353
|
+
c:tickMarkSkip c:noMultiLvlLbl c:extLst]).freeze
|
|
354
|
+
include BaseAxisElement
|
|
355
|
+
|
|
356
|
+
zero_or_one "c:lblOffset", successors: TAG_SEQ[19..]
|
|
357
|
+
end
|
|
358
|
+
|
|
359
|
+
class CT_DateAx < Element
|
|
360
|
+
tag "c:dateAx"
|
|
361
|
+
TAG_SEQ = (COMMON_AXIS_SEQ + %w[c:auto c:lblOffset c:baseTimeUnit c:majorUnit
|
|
362
|
+
c:majorTimeUnit c:minorUnit c:minorTimeUnit
|
|
363
|
+
c:extLst]).freeze
|
|
364
|
+
include BaseAxisElement
|
|
365
|
+
|
|
366
|
+
zero_or_one "c:lblOffset", successors: TAG_SEQ[18..]
|
|
367
|
+
end
|
|
368
|
+
|
|
369
|
+
class CT_ValAx < Element
|
|
370
|
+
tag "c:valAx"
|
|
371
|
+
TAG_SEQ = (COMMON_AXIS_SEQ + %w[c:crossBetween c:majorUnit c:minorUnit c:dispUnits
|
|
372
|
+
c:extLst]).freeze
|
|
373
|
+
include BaseAxisElement
|
|
374
|
+
|
|
375
|
+
zero_or_one "c:majorUnit", successors: TAG_SEQ[18..]
|
|
376
|
+
zero_or_one "c:minorUnit", successors: TAG_SEQ[19..]
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
# -------------------------------------------------------------------------
|
|
380
|
+
# Legend
|
|
381
|
+
# -------------------------------------------------------------------------
|
|
382
|
+
|
|
383
|
+
class CT_Legend < Element
|
|
384
|
+
include ChartTextProperties
|
|
385
|
+
|
|
386
|
+
tag "c:legend"
|
|
387
|
+
TAG_SEQ = %w[c:legendPos c:legendEntry c:layout c:overlay c:spPr c:txPr c:extLst].freeze
|
|
388
|
+
|
|
389
|
+
zero_or_one "c:legendPos", successors: TAG_SEQ[1..]
|
|
390
|
+
zero_or_one "c:layout", successors: TAG_SEQ[3..]
|
|
391
|
+
zero_or_one "c:overlay", successors: TAG_SEQ[4..]
|
|
392
|
+
zero_or_one "c:txPr", successors: TAG_SEQ[6..]
|
|
393
|
+
|
|
394
|
+
def horz_offset = layout&.horz_offset || 0.0
|
|
395
|
+
|
|
396
|
+
def horz_offset=(offset)
|
|
397
|
+
get_or_add_layout.horz_offset = offset
|
|
398
|
+
end
|
|
399
|
+
end
|
|
400
|
+
|
|
401
|
+
# -------------------------------------------------------------------------
|
|
402
|
+
# Data labels
|
|
403
|
+
# -------------------------------------------------------------------------
|
|
404
|
+
|
|
405
|
+
# `c:dLbls`, data-label settings for a plot or a series.
|
|
406
|
+
class CT_DataLabels < Element
|
|
407
|
+
include ChartTextProperties
|
|
408
|
+
|
|
409
|
+
tag "c:dLbls"
|
|
410
|
+
TAG_SEQ = %w[c:dLbl c:numFmt c:spPr c:txPr c:dLblPos c:showLegendKey c:showVal
|
|
411
|
+
c:showCatName c:showSerName c:showPercent c:showBubbleSize
|
|
412
|
+
c:separator c:showLeaderLines c:leaderLines c:extLst].freeze
|
|
413
|
+
|
|
414
|
+
zero_or_more "c:dLbl", successors: TAG_SEQ[1..], as: :dLbl
|
|
415
|
+
zero_or_one "c:numFmt", successors: TAG_SEQ[2..]
|
|
416
|
+
zero_or_one "c:txPr", successors: TAG_SEQ[4..]
|
|
417
|
+
zero_or_one "c:dLblPos", successors: TAG_SEQ[5..]
|
|
418
|
+
zero_or_one "c:showLegendKey", successors: TAG_SEQ[6..]
|
|
419
|
+
zero_or_one "c:showVal", successors: TAG_SEQ[7..]
|
|
420
|
+
zero_or_one "c:showCatName", successors: TAG_SEQ[8..]
|
|
421
|
+
zero_or_one "c:showSerName", successors: TAG_SEQ[9..]
|
|
422
|
+
zero_or_one "c:showPercent", successors: TAG_SEQ[10..]
|
|
423
|
+
zero_or_one "c:showBubbleSize", successors: TAG_SEQ[11..]
|
|
424
|
+
zero_or_one "c:showLeaderLines", successors: TAG_SEQ[13..]
|
|
425
|
+
|
|
426
|
+
# What PowerPoint writes when labels are switched on: every kind of
|
|
427
|
+
# label named and turned off, leader lines allowed.
|
|
428
|
+
DEFAULT_XML = <<~XML.freeze
|
|
429
|
+
<c:dLbls #{Ns.nsdecls("c")}>
|
|
430
|
+
<c:showLegendKey val="0"/>
|
|
431
|
+
<c:showVal val="0"/>
|
|
432
|
+
<c:showCatName val="0"/>
|
|
433
|
+
<c:showSerName val="0"/>
|
|
434
|
+
<c:showPercent val="0"/>
|
|
435
|
+
<c:showBubbleSize val="0"/>
|
|
436
|
+
<c:showLeaderLines val="1"/>
|
|
437
|
+
</c:dLbls>
|
|
438
|
+
XML
|
|
439
|
+
|
|
440
|
+
def self.new_data_labels(context) = context.build_from_xml(DEFAULT_XML)
|
|
441
|
+
|
|
442
|
+
# A flag added on its own starts off, as PowerPoint writes them.
|
|
443
|
+
%w[showLegendKey showVal showCatName showSerName showPercent].each do |flag|
|
|
444
|
+
define_method(:"new_#{flag}") { build_from_xml(%(<c:#{flag} #{Ns.nsdecls("c")} val="0"/>)) }
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
# The label for the point at +idx+, or nil if it has none of its own.
|
|
448
|
+
def dLbl_for_point(idx) = dLbl_list.find { |label| label.idx.val == idx }
|
|
449
|
+
|
|
450
|
+
# The label for the point at +idx+, created in index order if absent.
|
|
451
|
+
def get_or_add_dLbl_for_point(idx)
|
|
452
|
+
dLbl_for_point(idx) || insert_dLbl_in_sequence(idx)
|
|
453
|
+
end
|
|
454
|
+
|
|
455
|
+
private
|
|
456
|
+
|
|
457
|
+
def insert_dLbl_in_sequence(idx)
|
|
458
|
+
label = CT_DLbl.new_dLbl(self)
|
|
459
|
+
label.idx.val = idx
|
|
460
|
+
following = dLbl_list.find { |existing| existing.idx.val > idx }
|
|
461
|
+
if following
|
|
462
|
+
following.node.add_previous_sibling(label.node)
|
|
463
|
+
elsif (last = dLbl_list.last)
|
|
464
|
+
last.node.add_next_sibling(label.node)
|
|
465
|
+
else
|
|
466
|
+
node.prepend_child(label.node)
|
|
467
|
+
end
|
|
468
|
+
label
|
|
469
|
+
end
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# `c:dLbl`, the label of one data point.
|
|
473
|
+
class CT_DLbl < Element
|
|
474
|
+
include ChartTextProperties
|
|
475
|
+
|
|
476
|
+
tag "c:dLbl"
|
|
477
|
+
TAG_SEQ = %w[c:idx c:layout c:tx c:numFmt c:spPr c:txPr c:dLblPos c:showLegendKey
|
|
478
|
+
c:showVal c:showCatName c:showSerName c:showPercent c:showBubbleSize
|
|
479
|
+
c:separator c:extLst].freeze
|
|
480
|
+
|
|
481
|
+
one_and_only_one "c:idx"
|
|
482
|
+
zero_or_one "c:tx", successors: TAG_SEQ[3..]
|
|
483
|
+
zero_or_one "c:spPr", successors: TAG_SEQ[5..]
|
|
484
|
+
zero_or_one "c:txPr", successors: TAG_SEQ[6..]
|
|
485
|
+
zero_or_one "c:dLblPos", successors: TAG_SEQ[7..]
|
|
486
|
+
|
|
487
|
+
DEFAULT_XML = <<~XML.freeze
|
|
488
|
+
<c:dLbl #{Ns.nsdecls("c", "a")}>
|
|
489
|
+
<c:idx val="666"/>
|
|
490
|
+
<c:spPr/>
|
|
491
|
+
<c:txPr>
|
|
492
|
+
<a:bodyPr/>
|
|
493
|
+
<a:lstStyle/>
|
|
494
|
+
<a:p>
|
|
495
|
+
<a:pPr>
|
|
496
|
+
<a:defRPr/>
|
|
497
|
+
</a:pPr>
|
|
498
|
+
</a:p>
|
|
499
|
+
</c:txPr>
|
|
500
|
+
<c:showLegendKey val="0"/>
|
|
501
|
+
<c:showVal val="1"/>
|
|
502
|
+
<c:showCatName val="0"/>
|
|
503
|
+
<c:showSerName val="0"/>
|
|
504
|
+
<c:showPercent val="0"/>
|
|
505
|
+
<c:showBubbleSize val="0"/>
|
|
506
|
+
</c:dLbl>
|
|
507
|
+
XML
|
|
508
|
+
|
|
509
|
+
def self.new_dLbl(context) = context.build_from_xml(DEFAULT_XML)
|
|
510
|
+
|
|
511
|
+
# The label's own text replaces the generated one. A `c:spPr` or
|
|
512
|
+
# `c:txPr` alongside `c:tx` makes a bubble chart unsaveable in
|
|
513
|
+
# PowerPoint, so both go -- python-pptx does the same.
|
|
514
|
+
def get_or_add_rich
|
|
515
|
+
remove_spPr
|
|
516
|
+
remove_txPr
|
|
517
|
+
tx = get_or_add_tx
|
|
518
|
+
tx.remove_strRef
|
|
519
|
+
tx.get_or_add_rich
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
def rich = xpath("./c:tx/c:rich").first
|
|
523
|
+
|
|
524
|
+
def remove_tx_rich
|
|
525
|
+
tx = xpath("./c:tx[c:rich]").first
|
|
526
|
+
remove(tx) if tx
|
|
527
|
+
end
|
|
528
|
+
end
|
|
529
|
+
|
|
530
|
+
# -------------------------------------------------------------------------
|
|
531
|
+
# Series, points and markers
|
|
532
|
+
# -------------------------------------------------------------------------
|
|
533
|
+
|
|
534
|
+
# `c:marker`, the symbol drawn at each point of a line, radar or XY series.
|
|
535
|
+
class CT_Marker < Element
|
|
536
|
+
tag "c:marker"
|
|
537
|
+
zero_or_one "c:symbol", successors: %w[c:size c:spPr c:extLst]
|
|
538
|
+
zero_or_one "c:size", successors: %w[c:spPr c:extLst]
|
|
539
|
+
zero_or_one "c:spPr", successors: %w[c:extLst]
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
# `c:dPt`, the formatting of one data point.
|
|
543
|
+
class CT_DPt < Element
|
|
544
|
+
tag "c:dPt"
|
|
545
|
+
TAG_SEQ = %w[c:idx c:invertIfNegative c:marker c:bubble3D c:explosion c:spPr
|
|
546
|
+
c:pictureOptions c:extLst].freeze
|
|
547
|
+
|
|
548
|
+
one_and_only_one "c:idx"
|
|
549
|
+
zero_or_one "c:marker", successors: TAG_SEQ[3..]
|
|
550
|
+
zero_or_one "c:spPr", successors: TAG_SEQ[6..]
|
|
551
|
+
|
|
552
|
+
def self.new_dPt(context)
|
|
553
|
+
context.build_from_xml(%(<c:dPt #{Ns.nsdecls("c")}><c:idx val="0"/></c:dPt>))
|
|
554
|
+
end
|
|
555
|
+
end
|
|
556
|
+
|
|
557
|
+
# `c:ser`, one series of a plot.
|
|
558
|
+
#
|
|
559
|
+
# Each plot family allows a different subset of these children, in this
|
|
560
|
+
# relative order; python-pptx's superset sequence is used as-is so an
|
|
561
|
+
# inserted element lands where it does there.
|
|
562
|
+
class CT_Series < Element
|
|
563
|
+
tag "c:ser"
|
|
564
|
+
TAG_SEQ = %w[c:idx c:order c:tx c:spPr c:invertIfNegative c:pictureOptions c:marker
|
|
565
|
+
c:explosion c:dPt c:dLbls c:trendline c:errBars c:cat c:val c:xVal c:yVal
|
|
566
|
+
c:shape c:smooth c:bubbleSize c:bubble3D c:extLst].freeze
|
|
567
|
+
|
|
568
|
+
zero_or_one "c:idx", successors: TAG_SEQ[1..]
|
|
569
|
+
zero_or_one "c:order", successors: TAG_SEQ[2..]
|
|
570
|
+
zero_or_one "c:tx", successors: TAG_SEQ[3..]
|
|
571
|
+
zero_or_one "c:spPr", successors: TAG_SEQ[4..]
|
|
572
|
+
zero_or_one "c:invertIfNegative", successors: TAG_SEQ[5..]
|
|
573
|
+
zero_or_one "c:marker", successors: TAG_SEQ[7..]
|
|
574
|
+
zero_or_more "c:dPt", successors: TAG_SEQ[9..], as: :dPt
|
|
575
|
+
zero_or_one "c:dLbls", successors: TAG_SEQ[10..]
|
|
576
|
+
zero_or_one "c:cat", successors: TAG_SEQ[13..]
|
|
577
|
+
zero_or_one "c:val", successors: TAG_SEQ[14..]
|
|
578
|
+
zero_or_one "c:xVal", successors: TAG_SEQ[15..]
|
|
579
|
+
zero_or_one "c:yVal", successors: TAG_SEQ[16..]
|
|
580
|
+
zero_or_one "c:smooth", successors: TAG_SEQ[18..]
|
|
581
|
+
zero_or_one "c:bubbleSize", successors: TAG_SEQ[19..]
|
|
582
|
+
|
|
583
|
+
def index = idx&.val.to_i
|
|
584
|
+
|
|
585
|
+
def name = xpath("./c:tx//c:pt/c:v").first&.text.to_s
|
|
586
|
+
|
|
587
|
+
def new_dLbls = CT_DataLabels.new_data_labels(self)
|
|
588
|
+
|
|
589
|
+
def new_dPt = CT_DPt.new_dPt(self)
|
|
590
|
+
|
|
591
|
+
%w[cat xVal yVal bubbleSize].each do |source|
|
|
592
|
+
define_method(:"#{source}_ptCount_val") do
|
|
593
|
+
xpath("./c:#{source}//c:ptCount/@val").first&.value.to_i
|
|
594
|
+
end
|
|
595
|
+
end
|
|
596
|
+
|
|
597
|
+
# The label of the point at +idx+, or nil.
|
|
598
|
+
def dLbl_for_point(idx) = dLbls&.dLbl_for_point(idx)
|
|
599
|
+
|
|
600
|
+
def get_or_add_dLbl_for_point(idx) = get_or_add_dLbls.get_or_add_dLbl_for_point(idx)
|
|
601
|
+
|
|
602
|
+
# The formatting record for the point at +idx+, created if absent.
|
|
603
|
+
def get_or_add_dPt_for_point(idx)
|
|
604
|
+
dPt_list.find { |point| point.idx.val == idx } || add_dPt.tap { |point| point.idx.val = idx }
|
|
605
|
+
end
|
|
606
|
+
end
|
|
607
|
+
|
|
608
|
+
# A series' category, value and XY sources: a reference into the
|
|
609
|
+
# worksheet plus the cached values.
|
|
610
|
+
class CT_SeriesData < Element
|
|
611
|
+
tag "c:cat", "c:val", "c:xVal", "c:yVal", "c:bubbleSize"
|
|
612
|
+
zero_or_one "c:multiLvlStrRef", successors: []
|
|
613
|
+
|
|
614
|
+
# The levels of a multi-level category source, leaf level first.
|
|
615
|
+
def lvls = xpath(".//c:lvl")
|
|
616
|
+
end
|
|
617
|
+
|
|
618
|
+
# `c:pt`, one cached value in a series source.
|
|
619
|
+
class CT_ChartPoint < Element
|
|
620
|
+
tag "c:pt"
|
|
621
|
+
required_attr "idx", type: SimpleTypes::XsdUnsignedInt
|
|
622
|
+
|
|
623
|
+
def v = xpath("./c:v").first
|
|
624
|
+
|
|
625
|
+
def value = Float(v.text)
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
# -------------------------------------------------------------------------
|
|
629
|
+
# Plots
|
|
630
|
+
# -------------------------------------------------------------------------
|
|
631
|
+
|
|
632
|
+
# A plot element -- `c:barChart` and its siblings.
|
|
633
|
+
#
|
|
634
|
+
# They differ in which options they accept, but share enough that one
|
|
635
|
+
# class covers what this library reads and writes.
|
|
636
|
+
class CT_Plot < Element
|
|
637
|
+
tag "c:barChart", "c:lineChart", "c:pieChart", "c:doughnutChart",
|
|
638
|
+
"c:areaChart", "c:radarChart", "c:scatterChart", "c:bubbleChart", "c:area3DChart"
|
|
639
|
+
|
|
640
|
+
zero_or_one "c:barDir", successors: %w[c:grouping c:varyColors c:ser c:dLbls
|
|
641
|
+
c:gapWidth c:overlap c:serLines c:axId c:extLst]
|
|
642
|
+
zero_or_one "c:grouping", successors: %w[c:varyColors c:ser c:dLbls c:gapWidth
|
|
643
|
+
c:overlap c:serLines c:axId c:extLst]
|
|
644
|
+
zero_or_one "c:varyColors", successors: %w[c:ser c:dLbls c:gapWidth c:overlap
|
|
645
|
+
c:serLines c:axId c:extLst]
|
|
646
|
+
zero_or_one "c:dLbls", successors: %w[c:dropLines c:gapWidth c:overlap c:serLines
|
|
647
|
+
c:bubble3D c:bubbleScale c:axId c:extLst]
|
|
648
|
+
zero_or_one "c:gapWidth", successors: %w[c:overlap c:serLines c:axId c:extLst]
|
|
649
|
+
zero_or_one "c:overlap", successors: %w[c:serLines c:axId c:extLst]
|
|
650
|
+
zero_or_one "c:bubbleScale", successors: %w[c:showNegBubbles c:sizeRepresents c:axId
|
|
651
|
+
c:extLst]
|
|
652
|
+
|
|
653
|
+
# Series in document order.
|
|
654
|
+
def ser_list = find_all("c:ser")
|
|
655
|
+
|
|
656
|
+
# Series in the order the chart draws them, which is `c:order`.
|
|
657
|
+
def sers = ser_list.sort_by { |ser| ser.order&.val.to_i }
|
|
658
|
+
|
|
659
|
+
def grouping_val = grouping&.val || SimpleTypes::ST_Grouping::STANDARD
|
|
660
|
+
|
|
661
|
+
def new_dLbls = CT_DataLabels.new_data_labels(self)
|
|
662
|
+
|
|
663
|
+
# The data-label settings, created with PowerPoint's defaults if the
|
|
664
|
+
# plot has none yet.
|
|
665
|
+
def get_or_add_default_dLbls
|
|
666
|
+
dLbls || begin
|
|
667
|
+
created = new_dLbls
|
|
668
|
+
insert_dLbls(created)
|
|
669
|
+
created
|
|
670
|
+
end
|
|
671
|
+
end
|
|
672
|
+
|
|
673
|
+
# The first series' category source, which the others share.
|
|
674
|
+
def cat = xpath("./c:ser[1]/c:cat").first
|
|
675
|
+
|
|
676
|
+
def cat_pt_count = xpath("./c:ser//c:cat//c:ptCount").first&.val.to_i
|
|
677
|
+
|
|
678
|
+
# One entry per category, nil where the workbook cell is empty -- such
|
|
679
|
+
# a category has no `c:pt` but still counts in `c:ptCount`.
|
|
680
|
+
def cat_pts
|
|
681
|
+
points = xpath("./c:ser[1]/c:cat//c:lvl[1]/c:pt")
|
|
682
|
+
points = xpath("./c:ser[1]/c:cat//c:pt") if points.empty?
|
|
683
|
+
by_idx = points.to_h { |point| [point.idx, point] }
|
|
684
|
+
Array.new(cat_pt_count) { |i| by_idx[i] }
|
|
685
|
+
end
|
|
686
|
+
end
|
|
687
|
+
|
|
688
|
+
# -------------------------------------------------------------------------
|
|
689
|
+
# Plot area, chart and chart space
|
|
690
|
+
# -------------------------------------------------------------------------
|
|
691
|
+
|
|
692
|
+
class CT_PlotArea < Element
|
|
693
|
+
tag "c:plotArea"
|
|
694
|
+
|
|
695
|
+
def plot_elements
|
|
696
|
+
@node.element_children
|
|
697
|
+
.select { |child| child.name.end_with?("Chart") }
|
|
698
|
+
.map { |child| Element.wrap(child) }
|
|
699
|
+
end
|
|
700
|
+
|
|
701
|
+
def category_axis = find("c:catAx") || find("c:dateAx")
|
|
702
|
+
|
|
703
|
+
def value_axes = find_all("c:valAx")
|
|
704
|
+
|
|
705
|
+
# Every series in the chart, in plot order then series order -- which is
|
|
706
|
+
# the order their chart-wide indices follow.
|
|
707
|
+
def series_elements = plot_elements.flat_map(&:ser_list)
|
|
708
|
+
|
|
709
|
+
def last_plot_element = plot_elements.last
|
|
710
|
+
end
|
|
711
|
+
|
|
712
|
+
# `c:chart`, which OOXML uses for two different things under one tag name.
|
|
713
|
+
#
|
|
714
|
+
# Inside `a:graphicData` it is a *reference*: an empty element carrying the
|
|
715
|
+
# relationship id of the chart part. Inside `c:chartSpace` it is the chart
|
|
716
|
+
# itself, with a title, plot area and legend. The registry is keyed by tag,
|
|
717
|
+
# so one class covers both; an element only ever uses one half.
|
|
718
|
+
class CT_Chart < Element
|
|
719
|
+
tag "c:chart"
|
|
720
|
+
|
|
721
|
+
# -- used in the `a:graphicData` reference form --
|
|
722
|
+
optional_attr "r:id", type: SimpleTypes::ST_RelationshipId, as: :rId
|
|
723
|
+
|
|
724
|
+
CHART_NS =
|
|
725
|
+
'xmlns:c="http://schemas.openxmlformats.org/drawingml/2006/chart" ' \
|
|
726
|
+
'xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"'
|
|
727
|
+
|
|
728
|
+
def self.new_chart(r_id)
|
|
729
|
+
Element.parse(%(<c:chart #{CHART_NS} r:id="#{r_id}"/>))
|
|
730
|
+
end
|
|
731
|
+
|
|
732
|
+
# -- used in the `c:chartSpace` form --
|
|
733
|
+
TAG_SEQ = %w[c:title c:autoTitleDeleted c:pivotFmts c:view3D c:floor c:sideWall
|
|
734
|
+
c:backWall c:plotArea c:legend c:plotVisOnly c:dispBlanksAs
|
|
735
|
+
c:showDLblsOverMax c:extLst].freeze
|
|
736
|
+
|
|
737
|
+
zero_or_one "c:title", successors: TAG_SEQ[1..]
|
|
738
|
+
zero_or_one "c:autoTitleDeleted", successors: TAG_SEQ[2..]
|
|
739
|
+
one_and_only_one "c:plotArea"
|
|
740
|
+
zero_or_one "c:legend", successors: TAG_SEQ[9..]
|
|
741
|
+
|
|
742
|
+
def new_title = CT_Title.new_title(self)
|
|
743
|
+
end
|
|
744
|
+
|
|
745
|
+
# `c:chartSpace`, the root of a chart part.
|
|
746
|
+
class CT_ChartSpace < Element
|
|
747
|
+
include ChartTextProperties
|
|
748
|
+
|
|
749
|
+
tag "c:chartSpace"
|
|
750
|
+
TAG_SEQ = %w[c:date1904 c:lang c:roundedCorners c:style c:clrMapOvr
|
|
751
|
+
c:pivotSource c:protection c:chart c:spPr c:txPr
|
|
752
|
+
c:externalData c:printSettings c:userShapes c:extLst].freeze
|
|
753
|
+
|
|
754
|
+
zero_or_one "c:date1904", successors: TAG_SEQ[1..]
|
|
755
|
+
zero_or_one "c:style", successors: TAG_SEQ[4..]
|
|
756
|
+
one_and_only_one "c:chart"
|
|
757
|
+
zero_or_one "c:txPr", successors: TAG_SEQ[10..]
|
|
758
|
+
zero_or_one "c:externalData", successors: TAG_SEQ[11..]
|
|
759
|
+
|
|
760
|
+
# The relationship id of the embedded workbook, or nil when the chart
|
|
761
|
+
# has none.
|
|
762
|
+
def xlsx_part_rId = externalData&.rId
|
|
763
|
+
|
|
764
|
+
# The plot element, e.g. `c:barChart`, that this chart draws with.
|
|
765
|
+
def plot_element
|
|
766
|
+
xpath("./c:chart/c:plotArea/*").find { |e| e.nsptag.end_with?("Chart") }
|
|
767
|
+
end
|
|
768
|
+
|
|
769
|
+
def series_elements = xpath("./c:chart/c:plotArea/*/c:ser")
|
|
770
|
+
end
|
|
771
|
+
|
|
772
|
+
# `c:externalData`, the link from a chart to its embedded workbook.
|
|
773
|
+
class CT_ExternalData < Element
|
|
774
|
+
tag "c:externalData"
|
|
775
|
+
optional_attr "r:id", type: SimpleTypes::ST_RelationshipId, as: :rId
|
|
776
|
+
zero_or_one "c:autoUpdate", successors: []
|
|
777
|
+
|
|
778
|
+
# PowerPoint always writes the auto-update flag, explicitly off.
|
|
779
|
+
def ensure_auto_update
|
|
780
|
+
get_or_add_autoUpdate.set("val", "0")
|
|
781
|
+
self
|
|
782
|
+
end
|
|
783
|
+
end
|
|
784
|
+
end
|
|
785
|
+
end
|