init: 폴더구조 설계 및 인프라 설계
This commit is contained in:
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -0,0 +1,297 @@
|
||||
"""Axis-related oxml objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pptx.enum.chart import XL_AXIS_CROSSES, XL_TICK_LABEL_POSITION, XL_TICK_MARK
|
||||
from pptx.oxml.chart.shared import CT_Title
|
||||
from pptx.oxml.simpletypes import ST_AxisUnit, ST_LblOffset, ST_Orientation
|
||||
from pptx.oxml.text import CT_TextBody
|
||||
from pptx.oxml.xmlchemy import (
|
||||
BaseOxmlElement,
|
||||
OneAndOnlyOne,
|
||||
OptionalAttribute,
|
||||
RequiredAttribute,
|
||||
ZeroOrOne,
|
||||
)
|
||||
|
||||
|
||||
class BaseAxisElement(BaseOxmlElement):
|
||||
"""Base class for catAx, dateAx, valAx, and perhaps other axis elements."""
|
||||
|
||||
@property
|
||||
def defRPr(self):
|
||||
"""
|
||||
``<a:defRPr>`` great-great-grandchild element, added with its
|
||||
ancestors if not present.
|
||||
"""
|
||||
txPr = self.get_or_add_txPr()
|
||||
defRPr = txPr.defRPr
|
||||
return defRPr
|
||||
|
||||
@property
|
||||
def orientation(self):
|
||||
"""Value of `val` attribute of `c:scaling/c:orientation` grandchild element.
|
||||
|
||||
Defaults to `ST_Orientation.MIN_MAX` if attribute or any ancestors are not
|
||||
present.
|
||||
"""
|
||||
orientation = self.scaling.orientation
|
||||
if orientation is None:
|
||||
return ST_Orientation.MIN_MAX
|
||||
return orientation.val
|
||||
|
||||
@orientation.setter
|
||||
def orientation(self, value):
|
||||
"""`value` is a member of `ST_Orientation`."""
|
||||
self.scaling._remove_orientation()
|
||||
if value == ST_Orientation.MAX_MIN:
|
||||
self.scaling.get_or_add_orientation().val = value
|
||||
|
||||
def _new_title(self):
|
||||
return CT_Title.new_title()
|
||||
|
||||
def _new_txPr(self):
|
||||
return CT_TextBody.new_txPr()
|
||||
|
||||
|
||||
class CT_AxisUnit(BaseOxmlElement):
|
||||
"""Used for `c:majorUnit` and `c:minorUnit` elements, and others."""
|
||||
|
||||
val = RequiredAttribute("val", ST_AxisUnit)
|
||||
|
||||
|
||||
class CT_CatAx(BaseAxisElement):
|
||||
"""`c:catAx` element, defining a category axis."""
|
||||
|
||||
_tag_seq = (
|
||||
"c:axId",
|
||||
"c:scaling",
|
||||
"c:delete",
|
||||
"c:axPos",
|
||||
"c:majorGridlines",
|
||||
"c:minorGridlines",
|
||||
"c:title",
|
||||
"c:numFmt",
|
||||
"c:majorTickMark",
|
||||
"c:minorTickMark",
|
||||
"c:tickLblPos",
|
||||
"c:spPr",
|
||||
"c:txPr",
|
||||
"c:crossAx",
|
||||
"c:crosses",
|
||||
"c:crossesAt",
|
||||
"c:auto",
|
||||
"c:lblAlgn",
|
||||
"c:lblOffset",
|
||||
"c:tickLblSkip",
|
||||
"c:tickMarkSkip",
|
||||
"c:noMultiLvlLbl",
|
||||
"c:extLst",
|
||||
)
|
||||
scaling = OneAndOnlyOne("c:scaling")
|
||||
delete_ = ZeroOrOne("c:delete", successors=_tag_seq[3:])
|
||||
majorGridlines = ZeroOrOne("c:majorGridlines", successors=_tag_seq[5:])
|
||||
minorGridlines = ZeroOrOne("c:minorGridlines", successors=_tag_seq[6:])
|
||||
title = ZeroOrOne("c:title", successors=_tag_seq[7:])
|
||||
numFmt = ZeroOrOne("c:numFmt", successors=_tag_seq[8:])
|
||||
majorTickMark = ZeroOrOne("c:majorTickMark", successors=_tag_seq[9:])
|
||||
minorTickMark = ZeroOrOne("c:minorTickMark", successors=_tag_seq[10:])
|
||||
tickLblPos = ZeroOrOne("c:tickLblPos", successors=_tag_seq[11:])
|
||||
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[12:])
|
||||
txPr = ZeroOrOne("c:txPr", successors=_tag_seq[13:])
|
||||
crosses = ZeroOrOne("c:crosses", successors=_tag_seq[15:])
|
||||
crossesAt = ZeroOrOne("c:crossesAt", successors=_tag_seq[16:])
|
||||
lblOffset = ZeroOrOne("c:lblOffset", successors=_tag_seq[19:])
|
||||
del _tag_seq
|
||||
|
||||
|
||||
class CT_ChartLines(BaseOxmlElement):
|
||||
"""Used for `c:majorGridlines` and `c:minorGridlines`.
|
||||
|
||||
Specifies gridlines visual properties such as color and width.
|
||||
"""
|
||||
|
||||
spPr = ZeroOrOne("c:spPr", successors=())
|
||||
|
||||
|
||||
class CT_Crosses(BaseOxmlElement):
|
||||
"""`c:crosses` element, specifying where the other axis crosses this one."""
|
||||
|
||||
val = RequiredAttribute("val", XL_AXIS_CROSSES)
|
||||
|
||||
|
||||
class CT_DateAx(BaseAxisElement):
|
||||
"""`c:dateAx` element, defining a date (category) axis."""
|
||||
|
||||
_tag_seq = (
|
||||
"c:axId",
|
||||
"c:scaling",
|
||||
"c:delete",
|
||||
"c:axPos",
|
||||
"c:majorGridlines",
|
||||
"c:minorGridlines",
|
||||
"c:title",
|
||||
"c:numFmt",
|
||||
"c:majorTickMark",
|
||||
"c:minorTickMark",
|
||||
"c:tickLblPos",
|
||||
"c:spPr",
|
||||
"c:txPr",
|
||||
"c:crossAx",
|
||||
"c:crosses",
|
||||
"c:crossesAt",
|
||||
"c:auto",
|
||||
"c:lblOffset",
|
||||
"c:baseTimeUnit",
|
||||
"c:majorUnit",
|
||||
"c:majorTimeUnit",
|
||||
"c:minorUnit",
|
||||
"c:minorTimeUnit",
|
||||
"c:extLst",
|
||||
)
|
||||
scaling = OneAndOnlyOne("c:scaling")
|
||||
delete_ = ZeroOrOne("c:delete", successors=_tag_seq[3:])
|
||||
majorGridlines = ZeroOrOne("c:majorGridlines", successors=_tag_seq[5:])
|
||||
minorGridlines = ZeroOrOne("c:minorGridlines", successors=_tag_seq[6:])
|
||||
title = ZeroOrOne("c:title", successors=_tag_seq[7:])
|
||||
numFmt = ZeroOrOne("c:numFmt", successors=_tag_seq[8:])
|
||||
majorTickMark = ZeroOrOne("c:majorTickMark", successors=_tag_seq[9:])
|
||||
minorTickMark = ZeroOrOne("c:minorTickMark", successors=_tag_seq[10:])
|
||||
tickLblPos = ZeroOrOne("c:tickLblPos", successors=_tag_seq[11:])
|
||||
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[12:])
|
||||
txPr = ZeroOrOne("c:txPr", successors=_tag_seq[13:])
|
||||
crosses = ZeroOrOne("c:crosses", successors=_tag_seq[15:])
|
||||
crossesAt = ZeroOrOne("c:crossesAt", successors=_tag_seq[16:])
|
||||
lblOffset = ZeroOrOne("c:lblOffset", successors=_tag_seq[18:])
|
||||
del _tag_seq
|
||||
|
||||
|
||||
class CT_LblOffset(BaseOxmlElement):
|
||||
"""`c:lblOffset` custom element class."""
|
||||
|
||||
val = OptionalAttribute("val", ST_LblOffset, default=100)
|
||||
|
||||
|
||||
class CT_Orientation(BaseOxmlElement):
|
||||
"""`c:xAx/c:scaling/c:orientation` element, defining category order.
|
||||
|
||||
Used to reverse the order categories appear in on a bar chart so they start at the
|
||||
top rather than the bottom. Because we read top-to-bottom, the default way looks odd
|
||||
to many and perhaps most folks. Also applicable to value and date axes.
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", ST_Orientation, default=ST_Orientation.MIN_MAX)
|
||||
|
||||
|
||||
class CT_Scaling(BaseOxmlElement):
|
||||
"""`c:scaling` element.
|
||||
|
||||
Defines axis scale characteristics such as maximum value, log vs. linear, etc.
|
||||
"""
|
||||
|
||||
_tag_seq = ("c:logBase", "c:orientation", "c:max", "c:min", "c:extLst")
|
||||
orientation = ZeroOrOne("c:orientation", successors=_tag_seq[2:])
|
||||
max = ZeroOrOne("c:max", successors=_tag_seq[3:])
|
||||
min = ZeroOrOne("c:min", successors=_tag_seq[4:])
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def maximum(self):
|
||||
"""
|
||||
The float value of the ``<c:max>`` child element, or |None| if no max
|
||||
element is present.
|
||||
"""
|
||||
max = self.max
|
||||
if max is None:
|
||||
return None
|
||||
return max.val
|
||||
|
||||
@maximum.setter
|
||||
def maximum(self, value):
|
||||
"""
|
||||
Set the value of the ``<c:max>`` child element to the float *value*,
|
||||
or remove the max element if *value* is |None|.
|
||||
"""
|
||||
self._remove_max()
|
||||
if value is None:
|
||||
return
|
||||
self._add_max(val=value)
|
||||
|
||||
@property
|
||||
def minimum(self):
|
||||
"""
|
||||
The float value of the ``<c:min>`` child element, or |None| if no min
|
||||
element is present.
|
||||
"""
|
||||
min = self.min
|
||||
if min is None:
|
||||
return None
|
||||
return min.val
|
||||
|
||||
@minimum.setter
|
||||
def minimum(self, value):
|
||||
"""
|
||||
Set the value of the ``<c:min>`` child element to the float *value*,
|
||||
or remove the min element if *value* is |None|.
|
||||
"""
|
||||
self._remove_min()
|
||||
if value is None:
|
||||
return
|
||||
self._add_min(val=value)
|
||||
|
||||
|
||||
class CT_TickLblPos(BaseOxmlElement):
|
||||
"""`c:tickLblPos` element."""
|
||||
|
||||
val = OptionalAttribute("val", XL_TICK_LABEL_POSITION)
|
||||
|
||||
|
||||
class CT_TickMark(BaseOxmlElement):
|
||||
"""Used for `c:minorTickMark` and `c:majorTickMark`."""
|
||||
|
||||
val = OptionalAttribute("val", XL_TICK_MARK, default=XL_TICK_MARK.CROSS)
|
||||
|
||||
|
||||
class CT_ValAx(BaseAxisElement):
|
||||
"""`c:valAx` element, defining a value axis."""
|
||||
|
||||
_tag_seq = (
|
||||
"c:axId",
|
||||
"c:scaling",
|
||||
"c:delete",
|
||||
"c:axPos",
|
||||
"c:majorGridlines",
|
||||
"c:minorGridlines",
|
||||
"c:title",
|
||||
"c:numFmt",
|
||||
"c:majorTickMark",
|
||||
"c:minorTickMark",
|
||||
"c:tickLblPos",
|
||||
"c:spPr",
|
||||
"c:txPr",
|
||||
"c:crossAx",
|
||||
"c:crosses",
|
||||
"c:crossesAt",
|
||||
"c:crossBetween",
|
||||
"c:majorUnit",
|
||||
"c:minorUnit",
|
||||
"c:dispUnits",
|
||||
"c:extLst",
|
||||
)
|
||||
scaling = OneAndOnlyOne("c:scaling")
|
||||
delete_ = ZeroOrOne("c:delete", successors=_tag_seq[3:])
|
||||
majorGridlines = ZeroOrOne("c:majorGridlines", successors=_tag_seq[5:])
|
||||
minorGridlines = ZeroOrOne("c:minorGridlines", successors=_tag_seq[6:])
|
||||
title = ZeroOrOne("c:title", successors=_tag_seq[7:])
|
||||
numFmt = ZeroOrOne("c:numFmt", successors=_tag_seq[8:])
|
||||
majorTickMark = ZeroOrOne("c:majorTickMark", successors=_tag_seq[9:])
|
||||
minorTickMark = ZeroOrOne("c:minorTickMark", successors=_tag_seq[10:])
|
||||
tickLblPos = ZeroOrOne("c:tickLblPos", successors=_tag_seq[11:])
|
||||
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[12:])
|
||||
txPr = ZeroOrOne("c:txPr", successors=_tag_seq[13:])
|
||||
crossAx = ZeroOrOne("c:crossAx", successors=_tag_seq[14:])
|
||||
crosses = ZeroOrOne("c:crosses", successors=_tag_seq[15:])
|
||||
crossesAt = ZeroOrOne("c:crossesAt", successors=_tag_seq[16:])
|
||||
majorUnit = ZeroOrOne("c:majorUnit", successors=_tag_seq[18:])
|
||||
minorUnit = ZeroOrOne("c:minorUnit", successors=_tag_seq[19:])
|
||||
del _tag_seq
|
||||
@@ -0,0 +1,282 @@
|
||||
"""Custom element classes for top-level chart-related XML elements."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import cast
|
||||
|
||||
from pptx.oxml import parse_xml
|
||||
from pptx.oxml.chart.shared import CT_Title
|
||||
from pptx.oxml.ns import nsdecls, qn
|
||||
from pptx.oxml.simpletypes import ST_Style, XsdString
|
||||
from pptx.oxml.text import CT_TextBody
|
||||
from pptx.oxml.xmlchemy import (
|
||||
BaseOxmlElement,
|
||||
OneAndOnlyOne,
|
||||
RequiredAttribute,
|
||||
ZeroOrMore,
|
||||
ZeroOrOne,
|
||||
)
|
||||
|
||||
|
||||
class CT_Chart(BaseOxmlElement):
|
||||
"""`c:chart` custom element class."""
|
||||
|
||||
_tag_seq = (
|
||||
"c:title",
|
||||
"c:autoTitleDeleted",
|
||||
"c:pivotFmts",
|
||||
"c:view3D",
|
||||
"c:floor",
|
||||
"c:sideWall",
|
||||
"c:backWall",
|
||||
"c:plotArea",
|
||||
"c:legend",
|
||||
"c:plotVisOnly",
|
||||
"c:dispBlanksAs",
|
||||
"c:showDLblsOverMax",
|
||||
"c:extLst",
|
||||
)
|
||||
title = ZeroOrOne("c:title", successors=_tag_seq[1:])
|
||||
autoTitleDeleted = ZeroOrOne("c:autoTitleDeleted", successors=_tag_seq[2:])
|
||||
plotArea = OneAndOnlyOne("c:plotArea")
|
||||
legend = ZeroOrOne("c:legend", successors=_tag_seq[9:])
|
||||
rId: str = RequiredAttribute("r:id", XsdString) # pyright: ignore[reportAssignmentType]
|
||||
|
||||
@property
|
||||
def has_legend(self):
|
||||
"""
|
||||
True if this chart has a legend defined, False otherwise.
|
||||
"""
|
||||
legend = self.legend
|
||||
if legend is None:
|
||||
return False
|
||||
return True
|
||||
|
||||
@has_legend.setter
|
||||
def has_legend(self, bool_value):
|
||||
"""
|
||||
Add, remove, or leave alone the ``<c:legend>`` child element depending
|
||||
on current state and *bool_value*. If *bool_value* is |True| and no
|
||||
``<c:legend>`` element is present, a new default element is added.
|
||||
When |False|, any existing legend element is removed.
|
||||
"""
|
||||
if bool(bool_value) is False:
|
||||
self._remove_legend()
|
||||
else:
|
||||
if self.legend is None:
|
||||
self._add_legend()
|
||||
|
||||
@staticmethod
|
||||
def new_chart(rId: str) -> CT_Chart:
|
||||
"""Return a new `c:chart` element."""
|
||||
return cast(CT_Chart, parse_xml(f'<c:chart {nsdecls("c")} {nsdecls("r")} r:id="{rId}"/>'))
|
||||
|
||||
def _new_title(self):
|
||||
return CT_Title.new_title()
|
||||
|
||||
|
||||
class CT_ChartSpace(BaseOxmlElement):
|
||||
"""`c:chartSpace` root element of a chart part."""
|
||||
|
||||
_tag_seq = (
|
||||
"c:date1904",
|
||||
"c:lang",
|
||||
"c:roundedCorners",
|
||||
"c:style",
|
||||
"c:clrMapOvr",
|
||||
"c:pivotSource",
|
||||
"c:protection",
|
||||
"c:chart",
|
||||
"c:spPr",
|
||||
"c:txPr",
|
||||
"c:externalData",
|
||||
"c:printSettings",
|
||||
"c:userShapes",
|
||||
"c:extLst",
|
||||
)
|
||||
date1904 = ZeroOrOne("c:date1904", successors=_tag_seq[1:])
|
||||
style = ZeroOrOne("c:style", successors=_tag_seq[4:])
|
||||
chart = OneAndOnlyOne("c:chart")
|
||||
txPr = ZeroOrOne("c:txPr", successors=_tag_seq[10:])
|
||||
externalData = ZeroOrOne("c:externalData", successors=_tag_seq[11:])
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def catAx_lst(self):
|
||||
return self.chart.plotArea.catAx_lst
|
||||
|
||||
@property
|
||||
def date_1904(self):
|
||||
"""
|
||||
Return |True| if the `c:date1904` child element resolves truthy,
|
||||
|False| otherwise. This value indicates whether date number values
|
||||
are based on the 1900 or 1904 epoch.
|
||||
"""
|
||||
date1904 = self.date1904
|
||||
if date1904 is None:
|
||||
return False
|
||||
return date1904.val
|
||||
|
||||
@property
|
||||
def dateAx_lst(self):
|
||||
return self.xpath("c:chart/c:plotArea/c:dateAx")
|
||||
|
||||
def get_or_add_title(self):
|
||||
"""Return the `c:title` grandchild, newly created if not present."""
|
||||
return self.chart.get_or_add_title()
|
||||
|
||||
@property
|
||||
def plotArea(self):
|
||||
"""
|
||||
Return the required `c:chartSpace/c:chart/c:plotArea` grandchild
|
||||
element.
|
||||
"""
|
||||
return self.chart.plotArea
|
||||
|
||||
@property
|
||||
def valAx_lst(self):
|
||||
return self.chart.plotArea.valAx_lst
|
||||
|
||||
@property
|
||||
def xlsx_part_rId(self):
|
||||
"""
|
||||
The string in the required ``r:id`` attribute of the
|
||||
`<c:externalData>` child, or |None| if no externalData element is
|
||||
present.
|
||||
"""
|
||||
externalData = self.externalData
|
||||
if externalData is None:
|
||||
return None
|
||||
return externalData.rId
|
||||
|
||||
def _add_externalData(self):
|
||||
"""
|
||||
Always add a ``<c:autoUpdate val="0"/>`` child so auto-updating
|
||||
behavior is off by default.
|
||||
"""
|
||||
externalData = self._new_externalData()
|
||||
externalData._add_autoUpdate(val=False)
|
||||
self._insert_externalData(externalData)
|
||||
return externalData
|
||||
|
||||
def _new_txPr(self):
|
||||
return CT_TextBody.new_txPr()
|
||||
|
||||
|
||||
class CT_ExternalData(BaseOxmlElement):
|
||||
"""
|
||||
`<c:externalData>` element, defining link to embedded Excel package part
|
||||
containing the chart data.
|
||||
"""
|
||||
|
||||
autoUpdate = ZeroOrOne("c:autoUpdate")
|
||||
rId = RequiredAttribute("r:id", XsdString)
|
||||
|
||||
|
||||
class CT_PlotArea(BaseOxmlElement):
|
||||
"""
|
||||
``<c:plotArea>`` element.
|
||||
"""
|
||||
|
||||
catAx = ZeroOrMore("c:catAx")
|
||||
valAx = ZeroOrMore("c:valAx")
|
||||
|
||||
def iter_sers(self):
|
||||
"""
|
||||
Generate each of the `c:ser` elements in this chart, ordered first by
|
||||
the document order of the containing xChart element, then by their
|
||||
ordering within the xChart element (not necessarily document order).
|
||||
"""
|
||||
for xChart in self.iter_xCharts():
|
||||
for ser in xChart.iter_sers():
|
||||
yield ser
|
||||
|
||||
def iter_xCharts(self):
|
||||
"""
|
||||
Generate each xChart child element in document.
|
||||
"""
|
||||
plot_tags = (
|
||||
qn("c:area3DChart"),
|
||||
qn("c:areaChart"),
|
||||
qn("c:bar3DChart"),
|
||||
qn("c:barChart"),
|
||||
qn("c:bubbleChart"),
|
||||
qn("c:doughnutChart"),
|
||||
qn("c:line3DChart"),
|
||||
qn("c:lineChart"),
|
||||
qn("c:ofPieChart"),
|
||||
qn("c:pie3DChart"),
|
||||
qn("c:pieChart"),
|
||||
qn("c:radarChart"),
|
||||
qn("c:scatterChart"),
|
||||
qn("c:stockChart"),
|
||||
qn("c:surface3DChart"),
|
||||
qn("c:surfaceChart"),
|
||||
)
|
||||
|
||||
for child in self.iterchildren():
|
||||
if child.tag not in plot_tags:
|
||||
continue
|
||||
yield child
|
||||
|
||||
@property
|
||||
def last_ser(self):
|
||||
"""
|
||||
Return the last `<c:ser>` element in the last xChart element, based
|
||||
on series order (not necessarily the same element as document order).
|
||||
"""
|
||||
last_xChart = self.xCharts[-1]
|
||||
sers = last_xChart.sers
|
||||
if not sers:
|
||||
return None
|
||||
return sers[-1]
|
||||
|
||||
@property
|
||||
def next_idx(self):
|
||||
"""
|
||||
Return the next available `c:ser/c:idx` value within the scope of
|
||||
this chart, the maximum idx value found on existing series,
|
||||
incremented by one.
|
||||
"""
|
||||
idx_vals = [s.idx.val for s in self.sers]
|
||||
if not idx_vals:
|
||||
return 0
|
||||
return max(idx_vals) + 1
|
||||
|
||||
@property
|
||||
def next_order(self):
|
||||
"""
|
||||
Return the next available `c:ser/c:order` value within the scope of
|
||||
this chart, the maximum order value found on existing series,
|
||||
incremented by one.
|
||||
"""
|
||||
order_vals = [s.order.val for s in self.sers]
|
||||
if not order_vals:
|
||||
return 0
|
||||
return max(order_vals) + 1
|
||||
|
||||
@property
|
||||
def sers(self):
|
||||
"""
|
||||
Return a sequence containing all the `c:ser` elements in this chart,
|
||||
ordered first by the document order of the containing xChart element,
|
||||
then by their ordering within the xChart element (not necessarily
|
||||
document order).
|
||||
"""
|
||||
return tuple(self.iter_sers())
|
||||
|
||||
@property
|
||||
def xCharts(self):
|
||||
"""
|
||||
Return a sequence containing all the `c:{x}Chart` elements in this
|
||||
chart, in document order.
|
||||
"""
|
||||
return tuple(self.iter_xCharts())
|
||||
|
||||
|
||||
class CT_Style(BaseOxmlElement):
|
||||
"""
|
||||
``<c:style>`` element; defines the chart style.
|
||||
"""
|
||||
|
||||
val = RequiredAttribute("val", ST_Style)
|
||||
@@ -0,0 +1,252 @@
|
||||
"""Chart data-label related oxml objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pptx.enum.chart import XL_DATA_LABEL_POSITION
|
||||
from pptx.oxml import parse_xml
|
||||
from pptx.oxml.ns import nsdecls
|
||||
from pptx.oxml.text import CT_TextBody
|
||||
from pptx.oxml.xmlchemy import (
|
||||
BaseOxmlElement,
|
||||
OneAndOnlyOne,
|
||||
RequiredAttribute,
|
||||
ZeroOrMore,
|
||||
ZeroOrOne,
|
||||
)
|
||||
|
||||
|
||||
class CT_DLbl(BaseOxmlElement):
|
||||
"""
|
||||
``<c:dLbl>`` element specifying the properties of the data label for an
|
||||
individual data point.
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:idx",
|
||||
"c:layout",
|
||||
"c:tx",
|
||||
"c:numFmt",
|
||||
"c:spPr",
|
||||
"c:txPr",
|
||||
"c:dLblPos",
|
||||
"c:showLegendKey",
|
||||
"c:showVal",
|
||||
"c:showCatName",
|
||||
"c:showSerName",
|
||||
"c:showPercent",
|
||||
"c:showBubbleSize",
|
||||
"c:separator",
|
||||
"c:extLst",
|
||||
)
|
||||
idx = OneAndOnlyOne("c:idx")
|
||||
tx = ZeroOrOne("c:tx", successors=_tag_seq[3:])
|
||||
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[5:])
|
||||
txPr = ZeroOrOne("c:txPr", successors=_tag_seq[6:])
|
||||
dLblPos = ZeroOrOne("c:dLblPos", successors=_tag_seq[7:])
|
||||
del _tag_seq
|
||||
|
||||
def get_or_add_rich(self):
|
||||
"""
|
||||
Return the `c:rich` descendant representing the text frame of the
|
||||
data label, newly created if not present. Any existing `c:strRef`
|
||||
element is removed along with its contents.
|
||||
"""
|
||||
tx = self.get_or_add_tx()
|
||||
tx._remove_strRef()
|
||||
return tx.get_or_add_rich()
|
||||
|
||||
def get_or_add_tx_rich(self):
|
||||
"""
|
||||
Return the `c:tx[c:rich]` subtree, newly created if not present.
|
||||
"""
|
||||
tx = self.get_or_add_tx()
|
||||
tx._remove_strRef()
|
||||
tx.get_or_add_rich()
|
||||
return tx
|
||||
|
||||
@property
|
||||
def idx_val(self):
|
||||
"""
|
||||
The integer value of the `val` attribute on the required `c:idx`
|
||||
child.
|
||||
"""
|
||||
return self.idx.val
|
||||
|
||||
@classmethod
|
||||
def new_dLbl(cls):
|
||||
"""Return a newly created "loose" `c:dLbl` element.
|
||||
|
||||
The `c:dLbl` element contains the same (fairly extensive) default
|
||||
subtree added by PowerPoint when an individual data label is
|
||||
customized in the UI. Note that the idx value must be set by the
|
||||
client. Failure to set the idx value will likely result in any
|
||||
changes not being visible and may result in a repair error on open.
|
||||
"""
|
||||
return parse_xml(
|
||||
"<c:dLbl %s>\n"
|
||||
' <c:idx val="666"/>\n'
|
||||
" <c:spPr/>\n"
|
||||
" <c:txPr>\n"
|
||||
" <a:bodyPr/>\n"
|
||||
" <a:lstStyle/>\n"
|
||||
" <a:p>\n"
|
||||
" <a:pPr>\n"
|
||||
" <a:defRPr/>\n"
|
||||
" </a:pPr>\n"
|
||||
" </a:p>\n"
|
||||
" </c:txPr>\n"
|
||||
' <c:showLegendKey val="0"/>\n'
|
||||
' <c:showVal val="1"/>\n'
|
||||
' <c:showCatName val="0"/>\n'
|
||||
' <c:showSerName val="0"/>\n'
|
||||
' <c:showPercent val="0"/>\n'
|
||||
' <c:showBubbleSize val="0"/>\n'
|
||||
"</c:dLbl>" % nsdecls("c", "a")
|
||||
)
|
||||
|
||||
def remove_tx_rich(self):
|
||||
"""
|
||||
Remove any `c:tx[c:rich]` child, or do nothing if not present.
|
||||
"""
|
||||
matches = self.xpath("c:tx[c:rich]")
|
||||
if not matches:
|
||||
return
|
||||
tx = matches[0]
|
||||
self.remove(tx)
|
||||
|
||||
def _new_txPr(self):
|
||||
return CT_TextBody.new_txPr()
|
||||
|
||||
|
||||
class CT_DLblPos(BaseOxmlElement):
|
||||
"""
|
||||
``<c:dLblPos>`` element specifying the positioning of a data label with
|
||||
respect to its data point.
|
||||
"""
|
||||
|
||||
val = RequiredAttribute("val", XL_DATA_LABEL_POSITION)
|
||||
|
||||
|
||||
class CT_DLbls(BaseOxmlElement):
|
||||
"""`c:dLbls` element specifying properties for a set of data labels."""
|
||||
|
||||
_tag_seq = (
|
||||
"c:dLbl",
|
||||
"c:numFmt",
|
||||
"c:spPr",
|
||||
"c:txPr",
|
||||
"c:dLblPos",
|
||||
"c:showLegendKey",
|
||||
"c:showVal",
|
||||
"c:showCatName",
|
||||
"c:showSerName",
|
||||
"c:showPercent",
|
||||
"c:showBubbleSize",
|
||||
"c:separator",
|
||||
"c:showLeaderLines",
|
||||
"c:leaderLines",
|
||||
"c:extLst",
|
||||
)
|
||||
dLbl = ZeroOrMore("c:dLbl", successors=_tag_seq[1:])
|
||||
numFmt = ZeroOrOne("c:numFmt", successors=_tag_seq[2:])
|
||||
txPr = ZeroOrOne("c:txPr", successors=_tag_seq[4:])
|
||||
dLblPos = ZeroOrOne("c:dLblPos", successors=_tag_seq[5:])
|
||||
showLegendKey = ZeroOrOne("c:showLegendKey", successors=_tag_seq[6:])
|
||||
showVal = ZeroOrOne("c:showVal", successors=_tag_seq[7:])
|
||||
showCatName = ZeroOrOne("c:showCatName", successors=_tag_seq[8:])
|
||||
showSerName = ZeroOrOne("c:showSerName", successors=_tag_seq[9:])
|
||||
showPercent = ZeroOrOne("c:showPercent", successors=_tag_seq[10:])
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def defRPr(self):
|
||||
"""
|
||||
``<a:defRPr>`` great-great-grandchild element, added with its
|
||||
ancestors if not present.
|
||||
"""
|
||||
txPr = self.get_or_add_txPr()
|
||||
defRPr = txPr.defRPr
|
||||
return defRPr
|
||||
|
||||
def get_dLbl_for_point(self, idx):
|
||||
"""
|
||||
Return the `c:dLbl` child representing the label for the data point
|
||||
at index *idx*.
|
||||
"""
|
||||
matches = self.xpath('c:dLbl[c:idx[@val="%d"]]' % idx)
|
||||
if matches:
|
||||
return matches[0]
|
||||
return None
|
||||
|
||||
def get_or_add_dLbl_for_point(self, idx):
|
||||
"""
|
||||
Return the `c:dLbl` element representing the label of the point at
|
||||
index *idx*.
|
||||
"""
|
||||
matches = self.xpath('c:dLbl[c:idx[@val="%d"]]' % idx)
|
||||
if matches:
|
||||
return matches[0]
|
||||
return self._insert_dLbl_in_sequence(idx)
|
||||
|
||||
@classmethod
|
||||
def new_dLbls(cls):
|
||||
"""Return a newly created "loose" `c:dLbls` element."""
|
||||
return parse_xml(
|
||||
"<c:dLbls %s>\n"
|
||||
' <c:showLegendKey val="0"/>\n'
|
||||
' <c:showVal val="0"/>\n'
|
||||
' <c:showCatName val="0"/>\n'
|
||||
' <c:showSerName val="0"/>\n'
|
||||
' <c:showPercent val="0"/>\n'
|
||||
' <c:showBubbleSize val="0"/>\n'
|
||||
' <c:showLeaderLines val="1"/>\n'
|
||||
"</c:dLbls>" % nsdecls("c")
|
||||
)
|
||||
|
||||
def _insert_dLbl_in_sequence(self, idx):
|
||||
"""
|
||||
Return a newly created `c:dLbl` element having `c:idx` child of *idx*
|
||||
and inserted in numeric sequence among the `c:dLbl` children of this
|
||||
element.
|
||||
"""
|
||||
new_dLbl = self._new_dLbl()
|
||||
new_dLbl.idx.val = idx
|
||||
|
||||
dLbl = None
|
||||
for dLbl in self.dLbl_lst:
|
||||
if dLbl.idx_val > idx:
|
||||
dLbl.addprevious(new_dLbl)
|
||||
return new_dLbl
|
||||
if dLbl is not None:
|
||||
dLbl.addnext(new_dLbl)
|
||||
else:
|
||||
self.insert(0, new_dLbl)
|
||||
return new_dLbl
|
||||
|
||||
def _new_dLbl(self):
|
||||
return CT_DLbl.new_dLbl()
|
||||
|
||||
def _new_showCatName(self):
|
||||
"""Return a new `c:showCatName` with value initialized.
|
||||
|
||||
This method is called by the metaclass-generated code whenever a new
|
||||
`c:showCatName` element is required. In this case, it defaults to
|
||||
`val=true`, which is not what we need so we override to make val
|
||||
explicitly False.
|
||||
"""
|
||||
return parse_xml('<c:showCatName %s val="0"/>' % nsdecls("c"))
|
||||
|
||||
def _new_showLegendKey(self):
|
||||
return parse_xml('<c:showLegendKey %s val="0"/>' % nsdecls("c"))
|
||||
|
||||
def _new_showPercent(self):
|
||||
return parse_xml('<c:showPercent %s val="0"/>' % nsdecls("c"))
|
||||
|
||||
def _new_showSerName(self):
|
||||
return parse_xml('<c:showSerName %s val="0"/>' % nsdecls("c"))
|
||||
|
||||
def _new_showVal(self):
|
||||
return parse_xml('<c:showVal %s val="0"/>' % nsdecls("c"))
|
||||
|
||||
def _new_txPr(self):
|
||||
return CT_TextBody.new_txPr()
|
||||
@@ -0,0 +1,72 @@
|
||||
"""lxml custom element classes for legend-related XML elements."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pptx.enum.chart import XL_LEGEND_POSITION
|
||||
from pptx.oxml.text import CT_TextBody
|
||||
from pptx.oxml.xmlchemy import BaseOxmlElement, OptionalAttribute, ZeroOrOne
|
||||
|
||||
|
||||
class CT_Legend(BaseOxmlElement):
|
||||
"""
|
||||
``<c:legend>`` custom element class
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:legendPos",
|
||||
"c:legendEntry",
|
||||
"c:layout",
|
||||
"c:overlay",
|
||||
"c:spPr",
|
||||
"c:txPr",
|
||||
"c:extLst",
|
||||
)
|
||||
legendPos = ZeroOrOne("c:legendPos", successors=_tag_seq[1:])
|
||||
layout = ZeroOrOne("c:layout", successors=_tag_seq[3:])
|
||||
overlay = ZeroOrOne("c:overlay", successors=_tag_seq[4:])
|
||||
txPr = ZeroOrOne("c:txPr", successors=_tag_seq[6:])
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def defRPr(self):
|
||||
"""
|
||||
`./c:txPr/a:p/a:pPr/a:defRPr` great-great-grandchild element, added
|
||||
with its ancestors if not present.
|
||||
"""
|
||||
txPr = self.get_or_add_txPr()
|
||||
defRPr = txPr.defRPr
|
||||
return defRPr
|
||||
|
||||
@property
|
||||
def horz_offset(self):
|
||||
"""
|
||||
The float value in ./c:layout/c:manualLayout/c:x when
|
||||
./c:layout/c:manualLayout/c:xMode@val == "factor". 0.0 if that
|
||||
XPath expression has no match.
|
||||
"""
|
||||
layout = self.layout
|
||||
if layout is None:
|
||||
return 0.0
|
||||
return layout.horz_offset
|
||||
|
||||
@horz_offset.setter
|
||||
def horz_offset(self, offset):
|
||||
"""
|
||||
Set the value of ./c:layout/c:manualLayout/c:x@val to *offset* and
|
||||
./c:layout/c:manualLayout/c:xMode@val to "factor". Remove
|
||||
./c:layout/c:manualLayout if *offset* == 0.
|
||||
"""
|
||||
layout = self.get_or_add_layout()
|
||||
layout.horz_offset = offset
|
||||
|
||||
def _new_txPr(self):
|
||||
return CT_TextBody.new_txPr()
|
||||
|
||||
|
||||
class CT_LegendPos(BaseOxmlElement):
|
||||
"""
|
||||
``<c:legendPos>`` element specifying position of legend with respect to
|
||||
chart as a member of ST_LegendPos.
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", XL_LEGEND_POSITION, default=XL_LEGEND_POSITION.RIGHT)
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Series-related oxml objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pptx.enum.chart import XL_MARKER_STYLE
|
||||
from pptx.oxml.simpletypes import ST_MarkerSize
|
||||
from pptx.oxml.xmlchemy import BaseOxmlElement, RequiredAttribute, ZeroOrOne
|
||||
|
||||
|
||||
class CT_Marker(BaseOxmlElement):
|
||||
"""
|
||||
`c:marker` custom element class, containing visual properties for a data
|
||||
point marker on line-type charts.
|
||||
"""
|
||||
|
||||
_tag_seq = ("c:symbol", "c:size", "c:spPr", "c:extLst")
|
||||
symbol = ZeroOrOne("c:symbol", successors=_tag_seq[1:])
|
||||
size = ZeroOrOne("c:size", successors=_tag_seq[2:])
|
||||
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[3:])
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def size_val(self):
|
||||
"""
|
||||
Return the value of `./c:size/@val`, specifying the size of this
|
||||
marker in points. Returns |None| if no `c:size` element is present or
|
||||
its val attribute is not present.
|
||||
"""
|
||||
size = self.size
|
||||
if size is None:
|
||||
return None
|
||||
return size.val
|
||||
|
||||
@property
|
||||
def symbol_val(self):
|
||||
"""
|
||||
Return the value of `./c:symbol/@val`, specifying the shape of this
|
||||
marker. Returns |None| if no `c:symbol` element is present.
|
||||
"""
|
||||
symbol = self.symbol
|
||||
if symbol is None:
|
||||
return None
|
||||
return symbol.val
|
||||
|
||||
|
||||
class CT_MarkerSize(BaseOxmlElement):
|
||||
"""
|
||||
`c:size` custom element class, specifying the size (in points) of a data
|
||||
point marker for a line, XY, or radar chart.
|
||||
"""
|
||||
|
||||
val = RequiredAttribute("val", ST_MarkerSize)
|
||||
|
||||
|
||||
class CT_MarkerStyle(BaseOxmlElement):
|
||||
"""
|
||||
`c:symbol` custom element class, specifying the shape of a data point
|
||||
marker for a line, XY, or radar chart.
|
||||
"""
|
||||
|
||||
val = RequiredAttribute("val", XL_MARKER_STYLE)
|
||||
@@ -0,0 +1,345 @@
|
||||
"""Plot-related oxml objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pptx.oxml.chart.datalabel import CT_DLbls
|
||||
from pptx.oxml.simpletypes import (
|
||||
ST_BarDir,
|
||||
ST_BubbleScale,
|
||||
ST_GapAmount,
|
||||
ST_Grouping,
|
||||
ST_Overlap,
|
||||
)
|
||||
from pptx.oxml.xmlchemy import (
|
||||
BaseOxmlElement,
|
||||
OneAndOnlyOne,
|
||||
OptionalAttribute,
|
||||
ZeroOrMore,
|
||||
ZeroOrOne,
|
||||
)
|
||||
|
||||
|
||||
class BaseChartElement(BaseOxmlElement):
|
||||
"""
|
||||
Base class for barChart, lineChart, and other plot elements.
|
||||
"""
|
||||
|
||||
@property
|
||||
def cat(self):
|
||||
"""
|
||||
Return the `c:cat` element of the first series in this xChart, or
|
||||
|None| if not present.
|
||||
"""
|
||||
cats = self.xpath("./c:ser[1]/c:cat")
|
||||
return cats[0] if cats else None
|
||||
|
||||
@property
|
||||
def cat_pt_count(self):
|
||||
"""
|
||||
Return the value of the `c:ptCount` descendent of this xChart
|
||||
element. Its parent can be one of three element types. This value
|
||||
represents the true number of (leaf) categories, although they might
|
||||
not all have a corresponding `c:pt` sibling; a category with no label
|
||||
does not get a `c:pt` element. Returns 0 if there is no `c:ptCount`
|
||||
descendent.
|
||||
"""
|
||||
cat_ptCounts = self.xpath("./c:ser//c:cat//c:ptCount")
|
||||
if not cat_ptCounts:
|
||||
return 0
|
||||
return cat_ptCounts[0].val
|
||||
|
||||
@property
|
||||
def cat_pts(self):
|
||||
"""
|
||||
Return a sequence representing the `c:pt` elements under the `c:cat`
|
||||
element of the first series in this xChart element. A category having
|
||||
no value will have no corresponding `c:pt` element; |None| will
|
||||
appear in that position in such cases. Items appear in `idx` order.
|
||||
Only those in the first ``<c:lvl>`` element are included in the case
|
||||
of multi-level categories.
|
||||
"""
|
||||
cat_pts = self.xpath("./c:ser[1]/c:cat//c:lvl[1]/c:pt")
|
||||
if not cat_pts:
|
||||
cat_pts = self.xpath("./c:ser[1]/c:cat//c:pt")
|
||||
|
||||
cat_pt_dict = dict((pt.idx, pt) for pt in cat_pts)
|
||||
|
||||
return [cat_pt_dict.get(idx, None) for idx in range(self.cat_pt_count)]
|
||||
|
||||
@property
|
||||
def grouping_val(self):
|
||||
"""
|
||||
Return the value of the ``./c:grouping{val=?}`` attribute, taking
|
||||
defaults into account when items are not present.
|
||||
"""
|
||||
grouping = self.grouping
|
||||
if grouping is None:
|
||||
return ST_Grouping.STANDARD
|
||||
val = grouping.val
|
||||
if val is None:
|
||||
return ST_Grouping.STANDARD
|
||||
return val
|
||||
|
||||
def iter_sers(self):
|
||||
"""
|
||||
Generate each ``<c:ser>`` child element in this xChart in
|
||||
c:order/@val sequence (not document or c:idx order).
|
||||
"""
|
||||
|
||||
def ser_order(ser):
|
||||
return ser.order.val
|
||||
|
||||
return (ser for ser in sorted(self.xpath("./c:ser"), key=ser_order))
|
||||
|
||||
@property
|
||||
def sers(self):
|
||||
"""
|
||||
Sequence of ``<c:ser>`` child elements in this xChart in c:order/@val
|
||||
sequence (not document or c:idx order).
|
||||
"""
|
||||
return tuple(self.iter_sers())
|
||||
|
||||
def _new_dLbls(self):
|
||||
return CT_DLbls.new_dLbls()
|
||||
|
||||
|
||||
class CT_Area3DChart(BaseChartElement):
|
||||
"""
|
||||
``<c:area3DChart>`` element.
|
||||
"""
|
||||
|
||||
grouping = ZeroOrOne(
|
||||
"c:grouping",
|
||||
successors=(
|
||||
"c:varyColors",
|
||||
"c:ser",
|
||||
"c:dLbls",
|
||||
"c:dropLines",
|
||||
"c:gapDepth",
|
||||
"c:axId",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class CT_AreaChart(BaseChartElement):
|
||||
"""
|
||||
``<c:areaChart>`` element.
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:grouping",
|
||||
"c:varyColors",
|
||||
"c:ser",
|
||||
"c:dLbls",
|
||||
"c:dropLines",
|
||||
"c:axId",
|
||||
"c:extLst",
|
||||
)
|
||||
grouping = ZeroOrOne("c:grouping", successors=_tag_seq[1:])
|
||||
varyColors = ZeroOrOne("c:varyColors", successors=_tag_seq[2:])
|
||||
ser = ZeroOrMore("c:ser", successors=_tag_seq[3:])
|
||||
dLbls = ZeroOrOne("c:dLbls", successors=_tag_seq[4:])
|
||||
del _tag_seq
|
||||
|
||||
|
||||
class CT_BarChart(BaseChartElement):
|
||||
"""
|
||||
``<c:barChart>`` element.
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:barDir",
|
||||
"c:grouping",
|
||||
"c:varyColors",
|
||||
"c:ser",
|
||||
"c:dLbls",
|
||||
"c:gapWidth",
|
||||
"c:overlap",
|
||||
"c:serLines",
|
||||
"c:axId",
|
||||
"c:extLst",
|
||||
)
|
||||
barDir = OneAndOnlyOne("c:barDir")
|
||||
grouping = ZeroOrOne("c:grouping", successors=_tag_seq[2:])
|
||||
varyColors = ZeroOrOne("c:varyColors", successors=_tag_seq[3:])
|
||||
ser = ZeroOrMore("c:ser", successors=_tag_seq[4:])
|
||||
dLbls = ZeroOrOne("c:dLbls", successors=_tag_seq[5:])
|
||||
gapWidth = ZeroOrOne("c:gapWidth", successors=_tag_seq[6:])
|
||||
overlap = ZeroOrOne("c:overlap", successors=_tag_seq[7:])
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def grouping_val(self):
|
||||
"""
|
||||
Return the value of the ``./c:grouping{val=?}`` attribute, taking
|
||||
defaults into account when items are not present.
|
||||
"""
|
||||
grouping = self.grouping
|
||||
if grouping is None:
|
||||
return ST_Grouping.CLUSTERED
|
||||
val = grouping.val
|
||||
if val is None:
|
||||
return ST_Grouping.CLUSTERED
|
||||
return val
|
||||
|
||||
|
||||
class CT_BarDir(BaseOxmlElement):
|
||||
"""
|
||||
``<c:barDir>`` child of a barChart element, specifying the orientation of
|
||||
the bars, 'bar' if they are horizontal and 'col' if they are vertical.
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", ST_BarDir, default=ST_BarDir.COL)
|
||||
|
||||
|
||||
class CT_BubbleChart(BaseChartElement):
|
||||
"""
|
||||
``<c:bubbleChart>`` custom element class
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:varyColors",
|
||||
"c:ser",
|
||||
"c:dLbls",
|
||||
"c:axId",
|
||||
"c:bubble3D",
|
||||
"c:bubbleScale",
|
||||
"c:showNegBubbles",
|
||||
"c:sizeRepresents",
|
||||
"c:axId",
|
||||
"c:extLst",
|
||||
)
|
||||
ser = ZeroOrMore("c:ser", successors=_tag_seq[2:])
|
||||
dLbls = ZeroOrOne("c:dLbls", successors=_tag_seq[3:])
|
||||
bubble3D = ZeroOrOne("c:bubble3D", successors=_tag_seq[5:])
|
||||
bubbleScale = ZeroOrOne("c:bubbleScale", successors=_tag_seq[6:])
|
||||
del _tag_seq
|
||||
|
||||
|
||||
class CT_BubbleScale(BaseChartElement):
|
||||
"""
|
||||
``<c:bubbleScale>`` custom element class
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", ST_BubbleScale, default=100)
|
||||
|
||||
|
||||
class CT_DoughnutChart(BaseChartElement):
|
||||
"""
|
||||
``<c:doughnutChart>`` element.
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:varyColors",
|
||||
"c:ser",
|
||||
"c:dLbls",
|
||||
"c:firstSliceAng",
|
||||
"c:holeSize",
|
||||
"c:extLst",
|
||||
)
|
||||
varyColors = ZeroOrOne("c:varyColors", successors=_tag_seq[1:])
|
||||
ser = ZeroOrMore("c:ser", successors=_tag_seq[2:])
|
||||
dLbls = ZeroOrOne("c:dLbls", successors=_tag_seq[3:])
|
||||
del _tag_seq
|
||||
|
||||
|
||||
class CT_GapAmount(BaseOxmlElement):
|
||||
"""
|
||||
``<c:gapWidth>`` child of ``<c:barChart>`` element, also used for other
|
||||
purposes like error bars.
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", ST_GapAmount, default=150)
|
||||
|
||||
|
||||
class CT_Grouping(BaseOxmlElement):
|
||||
"""
|
||||
``<c:grouping>`` child of an xChart element, specifying a value like
|
||||
'clustered' or 'stacked'. Also used for variants with the same tag name
|
||||
like CT_BarGrouping.
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", ST_Grouping)
|
||||
|
||||
|
||||
class CT_LineChart(BaseChartElement):
|
||||
"""
|
||||
``<c:lineChart>`` custom element class
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:grouping",
|
||||
"c:varyColors",
|
||||
"c:ser",
|
||||
"c:dLbls",
|
||||
"c:dropLines",
|
||||
"c:hiLowLines",
|
||||
"c:upDownBars",
|
||||
"c:marker",
|
||||
"c:smooth",
|
||||
"c:axId",
|
||||
"c:extLst",
|
||||
)
|
||||
grouping = ZeroOrOne("c:grouping", successors=(_tag_seq[1:]))
|
||||
varyColors = ZeroOrOne("c:varyColors", successors=_tag_seq[2:])
|
||||
ser = ZeroOrMore("c:ser", successors=_tag_seq[3:])
|
||||
dLbls = ZeroOrOne("c:dLbls", successors=(_tag_seq[4:]))
|
||||
del _tag_seq
|
||||
|
||||
|
||||
class CT_Overlap(BaseOxmlElement):
|
||||
"""
|
||||
``<c:overlap>`` element specifying bar overlap as an integer percentage
|
||||
of bar width, in range -100 to 100.
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", ST_Overlap, default=0)
|
||||
|
||||
|
||||
class CT_PieChart(BaseChartElement):
|
||||
"""
|
||||
``<c:pieChart>`` custom element class
|
||||
"""
|
||||
|
||||
_tag_seq = ("c:varyColors", "c:ser", "c:dLbls", "c:firstSliceAng", "c:extLst")
|
||||
varyColors = ZeroOrOne("c:varyColors", successors=_tag_seq[1:])
|
||||
ser = ZeroOrMore("c:ser", successors=_tag_seq[2:])
|
||||
dLbls = ZeroOrOne("c:dLbls", successors=_tag_seq[3:])
|
||||
del _tag_seq
|
||||
|
||||
|
||||
class CT_RadarChart(BaseChartElement):
|
||||
"""
|
||||
``<c:radarChart>`` custom element class
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:radarStyle",
|
||||
"c:varyColors",
|
||||
"c:ser",
|
||||
"c:dLbls",
|
||||
"c:axId",
|
||||
"c:extLst",
|
||||
)
|
||||
varyColors = ZeroOrOne("c:varyColors", successors=_tag_seq[2:])
|
||||
ser = ZeroOrMore("c:ser", successors=_tag_seq[3:])
|
||||
dLbls = ZeroOrOne("c:dLbls", successors=(_tag_seq[4:]))
|
||||
del _tag_seq
|
||||
|
||||
|
||||
class CT_ScatterChart(BaseChartElement):
|
||||
"""
|
||||
``<c:scatterChart>`` custom element class
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:scatterStyle",
|
||||
"c:varyColors",
|
||||
"c:ser",
|
||||
"c:dLbls",
|
||||
"c:axId",
|
||||
"c:extLst",
|
||||
)
|
||||
varyColors = ZeroOrOne("c:varyColors", successors=_tag_seq[2:])
|
||||
ser = ZeroOrMore("c:ser", successors=_tag_seq[3:])
|
||||
del _tag_seq
|
||||
@@ -0,0 +1,254 @@
|
||||
"""Series-related oxml objects."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pptx.oxml.chart.datalabel import CT_DLbls
|
||||
from pptx.oxml.simpletypes import XsdUnsignedInt
|
||||
from pptx.oxml.xmlchemy import (
|
||||
BaseOxmlElement,
|
||||
OneAndOnlyOne,
|
||||
OxmlElement,
|
||||
RequiredAttribute,
|
||||
ZeroOrMore,
|
||||
ZeroOrOne,
|
||||
)
|
||||
|
||||
|
||||
class CT_AxDataSource(BaseOxmlElement):
|
||||
"""
|
||||
``<c:cat>`` custom element class used in category charts to specify
|
||||
category labels and hierarchy.
|
||||
"""
|
||||
|
||||
multiLvlStrRef = ZeroOrOne("c:multiLvlStrRef", successors=())
|
||||
|
||||
@property
|
||||
def lvls(self):
|
||||
"""
|
||||
Return a list containing the `c:lvl` descendent elements in document
|
||||
order. These will only be present when the required single child
|
||||
is a `c:multiLvlStrRef` element. Returns an empty list when no
|
||||
`c:lvl` descendent elements are present.
|
||||
"""
|
||||
return self.xpath(".//c:lvl")
|
||||
|
||||
|
||||
class CT_DPt(BaseOxmlElement):
|
||||
"""
|
||||
``<c:dPt>`` custom element class, containing visual properties for a data
|
||||
point.
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:idx",
|
||||
"c:invertIfNegative",
|
||||
"c:marker",
|
||||
"c:bubble3D",
|
||||
"c:explosion",
|
||||
"c:spPr",
|
||||
"c:pictureOptions",
|
||||
"c:extLst",
|
||||
)
|
||||
idx = OneAndOnlyOne("c:idx")
|
||||
marker = ZeroOrOne("c:marker", successors=_tag_seq[3:])
|
||||
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[6:])
|
||||
del _tag_seq
|
||||
|
||||
@classmethod
|
||||
def new_dPt(cls):
|
||||
"""
|
||||
Return a newly created "loose" `c:dPt` element containing its default
|
||||
subtree.
|
||||
"""
|
||||
dPt = OxmlElement("c:dPt")
|
||||
dPt.append(OxmlElement("c:idx"))
|
||||
return dPt
|
||||
|
||||
|
||||
class CT_Lvl(BaseOxmlElement):
|
||||
"""
|
||||
``<c:lvl>`` custom element class used in multi-level categories to
|
||||
specify a level of hierarchy.
|
||||
"""
|
||||
|
||||
pt = ZeroOrMore("c:pt", successors=())
|
||||
|
||||
|
||||
class CT_NumDataSource(BaseOxmlElement):
|
||||
"""
|
||||
``<c:yVal>`` custom element class used in XY and bubble charts, and
|
||||
perhaps others.
|
||||
"""
|
||||
|
||||
numRef = OneAndOnlyOne("c:numRef")
|
||||
|
||||
@property
|
||||
def ptCount_val(self):
|
||||
"""
|
||||
Return the value of `./c:numRef/c:numCache/c:ptCount/@val`,
|
||||
specifying how many `c:pt` elements are in this numeric data cache.
|
||||
Returns 0 if no `c:ptCount` element is present, as this is the least
|
||||
disruptive way to degrade when no cached point data is available.
|
||||
This situation is not expected, but is valid according to the schema.
|
||||
"""
|
||||
results = self.xpath(".//c:ptCount/@val")
|
||||
return int(results[0]) if results else 0
|
||||
|
||||
def pt_v(self, idx):
|
||||
"""
|
||||
Return the Y value for data point *idx* in this cache, or None if no
|
||||
value is present for that data point.
|
||||
"""
|
||||
results = self.xpath(".//c:pt[@idx=%d]" % idx)
|
||||
return results[0].value if results else None
|
||||
|
||||
|
||||
class CT_SeriesComposite(BaseOxmlElement):
|
||||
"""
|
||||
``<c:ser>`` custom element class. Note there are several different series
|
||||
element types in the schema, such as ``CT_LineSer`` and ``CT_BarSer``,
|
||||
but they all share the same tag name. This class acts as a composite and
|
||||
depends on the caller not to do anything invalid for a series belonging
|
||||
to a particular plot type.
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:idx",
|
||||
"c:order",
|
||||
"c:tx",
|
||||
"c:spPr",
|
||||
"c:invertIfNegative",
|
||||
"c:pictureOptions",
|
||||
"c:marker",
|
||||
"c:explosion",
|
||||
"c:dPt",
|
||||
"c:dLbls",
|
||||
"c:trendline",
|
||||
"c:errBars",
|
||||
"c:cat",
|
||||
"c:val",
|
||||
"c:xVal",
|
||||
"c:yVal",
|
||||
"c:shape",
|
||||
"c:smooth",
|
||||
"c:bubbleSize",
|
||||
"c:bubble3D",
|
||||
"c:extLst",
|
||||
)
|
||||
idx = OneAndOnlyOne("c:idx")
|
||||
order = OneAndOnlyOne("c:order")
|
||||
tx = ZeroOrOne("c:tx", successors=_tag_seq[3:])
|
||||
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[4:])
|
||||
invertIfNegative = ZeroOrOne("c:invertIfNegative", successors=_tag_seq[5:])
|
||||
marker = ZeroOrOne("c:marker", successors=_tag_seq[7:])
|
||||
dPt = ZeroOrMore("c:dPt", successors=_tag_seq[9:])
|
||||
dLbls = ZeroOrOne("c:dLbls", successors=_tag_seq[10:])
|
||||
cat = ZeroOrOne("c:cat", successors=_tag_seq[13:])
|
||||
val = ZeroOrOne("c:val", successors=_tag_seq[14:])
|
||||
xVal = ZeroOrOne("c:xVal", successors=_tag_seq[15:])
|
||||
yVal = ZeroOrOne("c:yVal", successors=_tag_seq[16:])
|
||||
smooth = ZeroOrOne("c:smooth", successors=_tag_seq[18:])
|
||||
bubbleSize = ZeroOrOne("c:bubbleSize", successors=_tag_seq[19:])
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def bubbleSize_ptCount_val(self):
|
||||
"""
|
||||
Return the number of bubble size values as reflected in the `val`
|
||||
attribute of `./c:bubbleSize//c:ptCount`, or 0 if not present.
|
||||
"""
|
||||
vals = self.xpath("./c:bubbleSize//c:ptCount/@val")
|
||||
if not vals:
|
||||
return 0
|
||||
return int(vals[0])
|
||||
|
||||
@property
|
||||
def cat_ptCount_val(self):
|
||||
"""
|
||||
Return the number of categories as reflected in the `val` attribute
|
||||
of `./c:cat//c:ptCount`, or 0 if not present.
|
||||
"""
|
||||
vals = self.xpath("./c:cat//c:ptCount/@val")
|
||||
if not vals:
|
||||
return 0
|
||||
return int(vals[0])
|
||||
|
||||
def get_dLbl(self, idx):
|
||||
"""
|
||||
Return the `c:dLbl` element representing the label for the data point
|
||||
at offset *idx* in this series, or |None| if not present.
|
||||
"""
|
||||
dLbls = self.dLbls
|
||||
if dLbls is None:
|
||||
return None
|
||||
return dLbls.get_dLbl_for_point(idx)
|
||||
|
||||
def get_or_add_dLbl(self, idx):
|
||||
"""
|
||||
Return the `c:dLbl` element representing the label of the point at
|
||||
offset *idx* in this series, newly created if not yet present.
|
||||
"""
|
||||
dLbls = self.get_or_add_dLbls()
|
||||
return dLbls.get_or_add_dLbl_for_point(idx)
|
||||
|
||||
def get_or_add_dPt_for_point(self, idx):
|
||||
"""
|
||||
Return the `c:dPt` child representing the visual properties of the
|
||||
data point at index *idx*.
|
||||
"""
|
||||
matches = self.xpath('c:dPt[c:idx[@val="%d"]]' % idx)
|
||||
if matches:
|
||||
return matches[0]
|
||||
dPt = self._add_dPt()
|
||||
dPt.idx.val = idx
|
||||
return dPt
|
||||
|
||||
@property
|
||||
def xVal_ptCount_val(self):
|
||||
"""
|
||||
Return the number of X values as reflected in the `val` attribute of
|
||||
`./c:xVal//c:ptCount`, or 0 if not present.
|
||||
"""
|
||||
vals = self.xpath("./c:xVal//c:ptCount/@val")
|
||||
if not vals:
|
||||
return 0
|
||||
return int(vals[0])
|
||||
|
||||
@property
|
||||
def yVal_ptCount_val(self):
|
||||
"""
|
||||
Return the number of Y values as reflected in the `val` attribute of
|
||||
`./c:yVal//c:ptCount`, or 0 if not present.
|
||||
"""
|
||||
vals = self.xpath("./c:yVal//c:ptCount/@val")
|
||||
if not vals:
|
||||
return 0
|
||||
return int(vals[0])
|
||||
|
||||
def _new_dLbls(self):
|
||||
"""Override metaclass method that creates `c:dLbls` element."""
|
||||
return CT_DLbls.new_dLbls()
|
||||
|
||||
def _new_dPt(self):
|
||||
"""
|
||||
Overrides the metaclass generated method to get `c:dPt` with minimal
|
||||
subtree.
|
||||
"""
|
||||
return CT_DPt.new_dPt()
|
||||
|
||||
|
||||
class CT_StrVal_NumVal_Composite(BaseOxmlElement):
|
||||
"""
|
||||
``<c:pt>`` element, can be either CT_StrVal or CT_NumVal complex type.
|
||||
Using this class for both, differentiating as needed.
|
||||
"""
|
||||
|
||||
v = OneAndOnlyOne("c:v")
|
||||
idx = RequiredAttribute("idx", XsdUnsignedInt)
|
||||
|
||||
@property
|
||||
def value(self):
|
||||
"""
|
||||
The float value of the text in the required ``<c:v>`` child.
|
||||
"""
|
||||
return float(self.v.text)
|
||||
@@ -0,0 +1,219 @@
|
||||
"""Shared oxml objects for charts."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from pptx.oxml import parse_xml
|
||||
from pptx.oxml.ns import nsdecls
|
||||
from pptx.oxml.simpletypes import (
|
||||
ST_LayoutMode,
|
||||
XsdBoolean,
|
||||
XsdDouble,
|
||||
XsdString,
|
||||
XsdUnsignedInt,
|
||||
)
|
||||
from pptx.oxml.xmlchemy import (
|
||||
BaseOxmlElement,
|
||||
OptionalAttribute,
|
||||
RequiredAttribute,
|
||||
ZeroOrOne,
|
||||
)
|
||||
|
||||
|
||||
class CT_Boolean(BaseOxmlElement):
|
||||
"""
|
||||
Common complex type used for elements having a True/False value.
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", XsdBoolean, default=True)
|
||||
|
||||
|
||||
class CT_Boolean_Explicit(BaseOxmlElement):
|
||||
"""Always spells out the `val` attribute, e.g. `val=1`.
|
||||
|
||||
At least one boolean element is improperly interpreted by one or more
|
||||
versions of PowerPoint. The `c:overlay` element is interpreted as |False|
|
||||
when no `val` attribute is present, contrary to the behavior described in
|
||||
the schema. A remedy for this is to interpret a missing `val` attribute
|
||||
as |True| (consistent with the spec), but always write the attribute
|
||||
whenever there is occasion for changing the element.
|
||||
"""
|
||||
|
||||
_val = OptionalAttribute("val", XsdBoolean, default=True)
|
||||
|
||||
@property
|
||||
def val(self):
|
||||
return self._val
|
||||
|
||||
@val.setter
|
||||
def val(self, value):
|
||||
val_str = "1" if bool(value) is True else "0"
|
||||
self.set("val", val_str)
|
||||
|
||||
|
||||
class CT_Double(BaseOxmlElement):
|
||||
"""
|
||||
Used for floating point values.
|
||||
"""
|
||||
|
||||
val = RequiredAttribute("val", XsdDouble)
|
||||
|
||||
|
||||
class CT_Layout(BaseOxmlElement):
|
||||
"""
|
||||
``<c:layout>`` custom element class
|
||||
"""
|
||||
|
||||
manualLayout = ZeroOrOne("c:manualLayout", successors=("c:extLst",))
|
||||
|
||||
@property
|
||||
def horz_offset(self):
|
||||
"""
|
||||
The float value in ./c:manualLayout/c:x when
|
||||
c:layout/c:manualLayout/c:xMode@val == "factor". 0.0 if that XPath
|
||||
expression finds no match.
|
||||
"""
|
||||
manualLayout = self.manualLayout
|
||||
if manualLayout is None:
|
||||
return 0.0
|
||||
return manualLayout.horz_offset
|
||||
|
||||
@horz_offset.setter
|
||||
def horz_offset(self, offset):
|
||||
"""
|
||||
Set the value of ./c:manualLayout/c:x@val to *offset* and
|
||||
./c:manualLayout/c:xMode@val to "factor". Remove ./c:manualLayout if
|
||||
*offset* == 0.
|
||||
"""
|
||||
if offset == 0.0:
|
||||
self._remove_manualLayout()
|
||||
return
|
||||
manualLayout = self.get_or_add_manualLayout()
|
||||
manualLayout.horz_offset = offset
|
||||
|
||||
|
||||
class CT_LayoutMode(BaseOxmlElement):
|
||||
"""
|
||||
Used for ``<c:xMode>``, ``<c:yMode>``, ``<c:wMode>``, and ``<c:hMode>``
|
||||
child elements of CT_ManualLayout.
|
||||
"""
|
||||
|
||||
val = OptionalAttribute("val", ST_LayoutMode, default=ST_LayoutMode.FACTOR)
|
||||
|
||||
|
||||
class CT_ManualLayout(BaseOxmlElement):
|
||||
"""
|
||||
``<c:manualLayout>`` custom element class
|
||||
"""
|
||||
|
||||
_tag_seq = (
|
||||
"c:layoutTarget",
|
||||
"c:xMode",
|
||||
"c:yMode",
|
||||
"c:wMode",
|
||||
"c:hMode",
|
||||
"c:x",
|
||||
"c:y",
|
||||
"c:w",
|
||||
"c:h",
|
||||
"c:extLst",
|
||||
)
|
||||
xMode = ZeroOrOne("c:xMode", successors=_tag_seq[2:])
|
||||
x = ZeroOrOne("c:x", successors=_tag_seq[6:])
|
||||
del _tag_seq
|
||||
|
||||
@property
|
||||
def horz_offset(self):
|
||||
"""
|
||||
The float value in ./c:x@val when ./c:xMode@val == "factor". 0.0 when
|
||||
./c:x is not present or ./c:xMode@val != "factor".
|
||||
"""
|
||||
x, xMode = self.x, self.xMode
|
||||
if x is None or xMode is None or xMode.val != ST_LayoutMode.FACTOR:
|
||||
return 0.0
|
||||
return x.val
|
||||
|
||||
@horz_offset.setter
|
||||
def horz_offset(self, offset):
|
||||
"""
|
||||
Set the value of ./c:x@val to *offset* and ./c:xMode@val to "factor".
|
||||
"""
|
||||
self.get_or_add_xMode().val = ST_LayoutMode.FACTOR
|
||||
self.get_or_add_x().val = offset
|
||||
|
||||
|
||||
class CT_NumFmt(BaseOxmlElement):
|
||||
"""
|
||||
``<c:numFmt>`` element specifying the formatting for number labels on a
|
||||
tick mark or data point.
|
||||
"""
|
||||
|
||||
formatCode = RequiredAttribute("formatCode", XsdString)
|
||||
sourceLinked = OptionalAttribute("sourceLinked", XsdBoolean)
|
||||
|
||||
|
||||
class CT_Title(BaseOxmlElement):
|
||||
"""`c:title` custom element class."""
|
||||
|
||||
_tag_seq = ("c:tx", "c:layout", "c:overlay", "c:spPr", "c:txPr", "c:extLst")
|
||||
tx = ZeroOrOne("c:tx", successors=_tag_seq[1:])
|
||||
spPr = ZeroOrOne("c:spPr", successors=_tag_seq[4:])
|
||||
del _tag_seq
|
||||
|
||||
def get_or_add_tx_rich(self):
|
||||
"""Return `c:tx/c:rich`, newly created if not present.
|
||||
|
||||
Return the `c:rich` grandchild at `c:tx/c:rich`. Both the `c:tx` and
|
||||
`c:rich` elements are created if not already present. Any
|
||||
`c:tx/c:strRef` element is removed. (Such an element would contain
|
||||
a cell reference for the axis title text in the chart's Excel
|
||||
worksheet.)
|
||||
"""
|
||||
tx = self.get_or_add_tx()
|
||||
tx._remove_strRef()
|
||||
return tx.get_or_add_rich()
|
||||
|
||||
@property
|
||||
def tx_rich(self):
|
||||
"""Return `c:tx/c:rich` or |None| if not present."""
|
||||
richs = self.xpath("c:tx/c:rich")
|
||||
if not richs:
|
||||
return None
|
||||
return richs[0]
|
||||
|
||||
@staticmethod
|
||||
def new_title():
|
||||
"""Return "loose" `c:title` element containing default children."""
|
||||
return parse_xml(
|
||||
"<c:title %s>" " <c:layout/>" ' <c:overlay val="0"/>' "</c:title>" % nsdecls("c")
|
||||
)
|
||||
|
||||
|
||||
class CT_Tx(BaseOxmlElement):
|
||||
"""
|
||||
``<c:tx>`` element containing the text for a label on a data point or
|
||||
other chart item.
|
||||
"""
|
||||
|
||||
strRef = ZeroOrOne("c:strRef")
|
||||
rich = ZeroOrOne("c:rich")
|
||||
|
||||
def _new_rich(self):
|
||||
return parse_xml(
|
||||
"<c:rich %s>"
|
||||
" <a:bodyPr/>"
|
||||
" <a:lstStyle/>"
|
||||
" <a:p>"
|
||||
" <a:pPr>"
|
||||
" <a:defRPr/>"
|
||||
" </a:pPr>"
|
||||
" </a:p>"
|
||||
"</c:rich>" % nsdecls("c", "a")
|
||||
)
|
||||
|
||||
|
||||
class CT_UnsignedInt(BaseOxmlElement):
|
||||
"""
|
||||
``<c:idx>`` element and others.
|
||||
"""
|
||||
|
||||
val = RequiredAttribute("val", XsdUnsignedInt)
|
||||
Reference in New Issue
Block a user