diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/pptx/oxml/chart')
9 files changed, 1782 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/__init__.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/__init__.py diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/axis.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/axis.py new file mode 100644 index 00000000..7129810c --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/axis.py @@ -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 diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/chart.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/chart.py new file mode 100644 index 00000000..f4cd0dc7 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/chart.py @@ -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) diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/datalabel.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/datalabel.py new file mode 100644 index 00000000..b6aac2fd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/datalabel.py @@ -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() diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/legend.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/legend.py new file mode 100644 index 00000000..196ca15d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/legend.py @@ -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) diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/marker.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/marker.py new file mode 100644 index 00000000..34afd13d --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/marker.py @@ -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) diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/plot.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/plot.py new file mode 100644 index 00000000..9c695a43 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/plot.py @@ -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 diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/series.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/series.py new file mode 100644 index 00000000..9264d552 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/series.py @@ -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) diff --git a/.venv/lib/python3.12/site-packages/pptx/oxml/chart/shared.py b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/shared.py new file mode 100644 index 00000000..5515aa4b --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/oxml/chart/shared.py @@ -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) |
