diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/pptx/chart/xmlwriter.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/pptx/chart/xmlwriter.py | 1840 |
1 files changed, 1840 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pptx/chart/xmlwriter.py b/.venv/lib/python3.12/site-packages/pptx/chart/xmlwriter.py new file mode 100644 index 00000000..703c53dd --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/chart/xmlwriter.py @@ -0,0 +1,1840 @@ +"""Composers for default chart XML for various chart types.""" + +from __future__ import annotations + +from copy import deepcopy +from xml.sax.saxutils import escape + +from pptx.enum.chart import XL_CHART_TYPE +from pptx.oxml import parse_xml +from pptx.oxml.ns import nsdecls + + +def ChartXmlWriter(chart_type, chart_data): + """ + Factory function returning appropriate XML writer object for + *chart_type*, loaded with *chart_type* and *chart_data*. + """ + XL_CT = XL_CHART_TYPE + try: + BuilderCls = { + XL_CT.AREA: _AreaChartXmlWriter, + XL_CT.AREA_STACKED: _AreaChartXmlWriter, + XL_CT.AREA_STACKED_100: _AreaChartXmlWriter, + XL_CT.BAR_CLUSTERED: _BarChartXmlWriter, + XL_CT.BAR_STACKED: _BarChartXmlWriter, + XL_CT.BAR_STACKED_100: _BarChartXmlWriter, + XL_CT.BUBBLE: _BubbleChartXmlWriter, + XL_CT.BUBBLE_THREE_D_EFFECT: _BubbleChartXmlWriter, + XL_CT.COLUMN_CLUSTERED: _BarChartXmlWriter, + XL_CT.COLUMN_STACKED: _BarChartXmlWriter, + XL_CT.COLUMN_STACKED_100: _BarChartXmlWriter, + XL_CT.DOUGHNUT: _DoughnutChartXmlWriter, + XL_CT.DOUGHNUT_EXPLODED: _DoughnutChartXmlWriter, + XL_CT.LINE: _LineChartXmlWriter, + XL_CT.LINE_MARKERS: _LineChartXmlWriter, + XL_CT.LINE_MARKERS_STACKED: _LineChartXmlWriter, + XL_CT.LINE_MARKERS_STACKED_100: _LineChartXmlWriter, + XL_CT.LINE_STACKED: _LineChartXmlWriter, + XL_CT.LINE_STACKED_100: _LineChartXmlWriter, + XL_CT.PIE: _PieChartXmlWriter, + XL_CT.PIE_EXPLODED: _PieChartXmlWriter, + XL_CT.RADAR: _RadarChartXmlWriter, + XL_CT.RADAR_FILLED: _RadarChartXmlWriter, + XL_CT.RADAR_MARKERS: _RadarChartXmlWriter, + XL_CT.XY_SCATTER: _XyChartXmlWriter, + XL_CT.XY_SCATTER_LINES: _XyChartXmlWriter, + XL_CT.XY_SCATTER_LINES_NO_MARKERS: _XyChartXmlWriter, + XL_CT.XY_SCATTER_SMOOTH: _XyChartXmlWriter, + XL_CT.XY_SCATTER_SMOOTH_NO_MARKERS: _XyChartXmlWriter, + }[chart_type] + except KeyError: + raise NotImplementedError("XML writer for chart type %s not yet implemented" % chart_type) + return BuilderCls(chart_type, chart_data) + + +def SeriesXmlRewriterFactory(chart_type, chart_data): + """ + Return a |_BaseSeriesXmlRewriter| subclass appropriate to *chart_type*. + """ + XL_CT = XL_CHART_TYPE + + RewriterCls = { + # There are 73 distinct chart types, only specify non-category + # types, others default to _CategorySeriesXmlRewriter. Stock-type + # charts are multi-plot charts, so no guaratees on how they turn + # out. + XL_CT.BUBBLE: _BubbleSeriesXmlRewriter, + XL_CT.BUBBLE_THREE_D_EFFECT: _BubbleSeriesXmlRewriter, + XL_CT.XY_SCATTER: _XySeriesXmlRewriter, + XL_CT.XY_SCATTER_LINES: _XySeriesXmlRewriter, + XL_CT.XY_SCATTER_LINES_NO_MARKERS: _XySeriesXmlRewriter, + XL_CT.XY_SCATTER_SMOOTH: _XySeriesXmlRewriter, + XL_CT.XY_SCATTER_SMOOTH_NO_MARKERS: _XySeriesXmlRewriter, + }.get(chart_type, _CategorySeriesXmlRewriter) + + return RewriterCls(chart_data) + + +class _BaseChartXmlWriter(object): + """ + Generates XML text (unicode) for a default chart, like the one added by + PowerPoint when you click the *Add Column Chart* button on the ribbon. + Differentiated XML for different chart types is provided by subclasses. + """ + + def __init__(self, chart_type, series_seq): + super(_BaseChartXmlWriter, self).__init__() + self._chart_type = chart_type + self._chart_data = series_seq + self._series_seq = list(series_seq) + + @property + def xml(self): + """ + The full XML stream for the chart specified by this chart builder, as + unicode text. This method must be overridden by each subclass. + """ + raise NotImplementedError("must be implemented by all subclasses") + + +class _BaseSeriesXmlWriter(object): + """ + Provides shared members for series XML writers. + """ + + def __init__(self, series, date_1904=False): + super(_BaseSeriesXmlWriter, self).__init__() + self._series = series + self._date_1904 = date_1904 + + @property + def name(self): + """ + The XML-escaped name for this series. + """ + return escape(self._series.name) + + def numRef_xml(self, wksht_ref, number_format, values): + """ + Return the ``<c:numRef>`` element specified by the parameters as + unicode text. + """ + pt_xml = self.pt_xml(values) + return ( + " <c:numRef>\n" + " <c:f>{wksht_ref}</c:f>\n" + " <c:numCache>\n" + " <c:formatCode>{number_format}</c:formatCode>\n" + "{pt_xml}" + " </c:numCache>\n" + " </c:numRef>\n" + ).format(**{"wksht_ref": wksht_ref, "number_format": number_format, "pt_xml": pt_xml}) + + def pt_xml(self, values): + """ + Return the ``<c:ptCount>`` and sequence of ``<c:pt>`` elements + corresponding to *values* as a single unicode text string. + `c:ptCount` refers to the number of `c:pt` elements in this sequence. + The `idx` attribute value for `c:pt` elements locates the data point + in the overall data point sequence of the chart and is started at + *offset*. + """ + xml = (' <c:ptCount val="{pt_count}"/>\n').format(pt_count=len(values)) + + pt_tmpl = ( + ' <c:pt idx="{idx}">\n' + " <c:v>{value}</c:v>\n" + " </c:pt>\n" + ) + for idx, value in enumerate(values): + if value is None: + continue + xml += pt_tmpl.format(idx=idx, value=value) + + return xml + + @property + def tx(self): + """ + Return a ``<c:tx>`` oxml element for this series, containing the + series name. + """ + xml = self._tx_tmpl.format( + **{ + "wksht_ref": self._series.name_ref, + "series_name": self.name, + "nsdecls": " %s" % nsdecls("c"), + } + ) + return parse_xml(xml) + + @property + def tx_xml(self): + """ + Return the ``<c:tx>`` (tx is short for 'text') element for this + series as unicode text. This element contains the series name. + """ + return self._tx_tmpl.format( + **{ + "wksht_ref": self._series.name_ref, + "series_name": self.name, + "nsdecls": "", + } + ) + + @property + def _tx_tmpl(self): + """ + The string formatting template for the ``<c:tx>`` element for this + series, containing the series title and spreadsheet range reference. + """ + return ( + " <c:tx{nsdecls}>\n" + " <c:strRef>\n" + " <c:f>{wksht_ref}</c:f>\n" + " <c:strCache>\n" + ' <c:ptCount val="1"/>\n' + ' <c:pt idx="0">\n' + " <c:v>{series_name}</c:v>\n" + " </c:pt>\n" + " </c:strCache>\n" + " </c:strRef>\n" + " </c:tx>\n" + ) + + +class _BaseSeriesXmlRewriter(object): + """ + Base class for series XML rewriters. + """ + + def __init__(self, chart_data): + super(_BaseSeriesXmlRewriter, self).__init__() + self._chart_data = chart_data + + def replace_series_data(self, chartSpace): + """ + Rewrite the series data under *chartSpace* using the chart data + contents. All series-level formatting is left undisturbed. If + the chart data contains fewer series than *chartSpace*, the extra + series in *chartSpace* are deleted. If *chart_data* contains more + series than the *chartSpace* element, new series are added to the + last plot in the chart and series formatting is "cloned" from the + last series in that plot. + """ + plotArea, date_1904 = chartSpace.plotArea, chartSpace.date_1904 + chart_data = self._chart_data + self._adjust_ser_count(plotArea, len(chart_data)) + for ser, series_data in zip(plotArea.sers, chart_data): + self._rewrite_ser_data(ser, series_data, date_1904) + + def _add_cloned_sers(self, plotArea, count): + """ + Add `c:ser` elements to the last xChart element in *plotArea*, cloned + from the last `c:ser` child of that last xChart. + """ + + def clone_ser(ser): + new_ser = deepcopy(ser) + new_ser.idx.val = plotArea.next_idx + new_ser.order.val = plotArea.next_order + ser.addnext(new_ser) + return new_ser + + last_ser = plotArea.last_ser + for _ in range(count): + last_ser = clone_ser(last_ser) + + def _adjust_ser_count(self, plotArea, new_ser_count): + """ + Adjust the number of c:ser elements in *plotArea* to *new_ser_count*. + Excess c:ser elements are deleted from the end, along with any xChart + elements that are left empty as a result. Series elements are + considered in xChart + series order. Any new c:ser elements required + are added to the last xChart element and cloned from the last c:ser + element in that xChart. + """ + ser_count_diff = new_ser_count - len(plotArea.sers) + if ser_count_diff > 0: + self._add_cloned_sers(plotArea, ser_count_diff) + elif ser_count_diff < 0: + self._trim_ser_count_by(plotArea, abs(ser_count_diff)) + + def _rewrite_ser_data(self, ser, series_data, date_1904): + """ + Rewrite selected child elements of *ser* based on the values in + *series_data*. + """ + raise NotImplementedError("must be implemented by each subclass") + + def _trim_ser_count_by(self, plotArea, count): + """ + Remove the last *count* ser elements from *plotArea*. Any xChart + elements having no ser child elements after trimming are also + removed. + """ + extra_sers = plotArea.sers[-count:] + for ser in extra_sers: + parent = ser.getparent() + parent.remove(ser) + extra_xCharts = [xChart for xChart in plotArea.iter_xCharts() if len(xChart.sers) == 0] + for xChart in extra_xCharts: + parent = xChart.getparent() + parent.remove(xChart) + + +class _AreaChartXmlWriter(_BaseChartXmlWriter): + """ + Provides specialized methods particular to the ``<c:areaChart>`` element. + """ + + @property + def xml(self): + return ( + "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawin' + 'gml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/draw' + 'ingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/off' + 'iceDocument/2006/relationships">\n' + ' <c:date1904 val="0"/>\n' + ' <c:roundedCorners val="0"/>\n' + " <c:chart>\n" + ' <c:autoTitleDeleted val="0"/>\n' + " <c:plotArea>\n" + " <c:layout/>\n" + " <c:areaChart>\n" + "{grouping_xml}" + ' <c:varyColors val="0"/>\n' + "{ser_xml}" + " <c:dLbls>\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:dLbls>\n" + ' <c:axId val="-2101159928"/>\n' + ' <c:axId val="-2100718248"/>\n' + " </c:areaChart>\n" + "{cat_ax_xml}" + " <c:valAx>\n" + ' <c:axId val="-2100718248"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="l"/>\n' + " <c:majorGridlines/>\n" + ' <c:numFmt formatCode="General" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2101159928"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:crossBetween val="midCat"/>\n' + " </c:valAx>\n" + " </c:plotArea>\n" + " <c:legend>\n" + ' <c:legendPos val="r"/>\n' + " <c:layout/>\n" + ' <c:overlay val="0"/>\n' + " </c:legend>\n" + ' <c:plotVisOnly val="1"/>\n' + ' <c:dispBlanksAs val="zero"/>\n' + ' <c:showDLblsOverMax val="0"/>\n' + " </c:chart>\n" + " <c:txPr>\n" + " <a:bodyPr/>\n" + " <a:lstStyle/>\n" + " <a:p>\n" + " <a:pPr>\n" + ' <a:defRPr sz="1800"/>\n' + " </a:pPr>\n" + " <a:endParaRPr/>\n" + " </a:p>\n" + " </c:txPr>\n" + "</c:chartSpace>\n" + ).format( + **{ + "grouping_xml": self._grouping_xml, + "ser_xml": self._ser_xml, + "cat_ax_xml": self._cat_ax_xml, + } + ) + + @property + def _cat_ax_xml(self): + categories = self._chart_data.categories + + if categories.are_dates: + return ( + " <c:dateAx>\n" + ' <c:axId val="-2101159928"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="b"/>\n' + ' <c:numFmt formatCode="{nf}" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2100718248"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:auto val="1"/>\n' + ' <c:lblOffset val="100"/>\n' + ' <c:baseTimeUnit val="days"/>\n' + " </c:dateAx>\n" + ).format(**{"nf": categories.number_format}) + + return ( + " <c:catAx>\n" + ' <c:axId val="-2101159928"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="b"/>\n' + ' <c:numFmt formatCode="General" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2100718248"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:auto val="1"/>\n' + ' <c:lblAlgn val="ctr"/>\n' + ' <c:lblOffset val="100"/>\n' + ' <c:noMultiLvlLbl val="0"/>\n' + " </c:catAx>\n" + ) + + @property + def _grouping_xml(self): + val = { + XL_CHART_TYPE.AREA: "standard", + XL_CHART_TYPE.AREA_STACKED: "stacked", + XL_CHART_TYPE.AREA_STACKED_100: "percentStacked", + }[self._chart_type] + return ' <c:grouping val="%s"/>\n' % val + + @property + def _ser_xml(self): + xml = "" + for series in self._chart_data: + xml_writer = _CategorySeriesXmlWriter(series) + xml += ( + " <c:ser>\n" + ' <c:idx val="{ser_idx}"/>\n' + ' <c:order val="{ser_order}"/>\n' + "{tx_xml}" + "{cat_xml}" + "{val_xml}" + " </c:ser>\n" + ).format( + **{ + "ser_idx": series.index, + "ser_order": series.index, + "tx_xml": xml_writer.tx_xml, + "cat_xml": xml_writer.cat_xml, + "val_xml": xml_writer.val_xml, + } + ) + return xml + + +class _BarChartXmlWriter(_BaseChartXmlWriter): + """ + Provides specialized methods particular to the ``<c:barChart>`` element. + """ + + @property + def xml(self): + return ( + "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawin' + 'gml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/draw' + 'ingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/off' + 'iceDocument/2006/relationships">\n' + ' <c:date1904 val="0"/>\n' + " <c:chart>\n" + ' <c:autoTitleDeleted val="0"/>\n' + " <c:plotArea>\n" + " <c:barChart>\n" + "{barDir_xml}" + "{grouping_xml}" + "{ser_xml}" + "{overlap_xml}" + ' <c:axId val="-2068027336"/>\n' + ' <c:axId val="-2113994440"/>\n' + " </c:barChart>\n" + "{cat_ax_xml}" + " <c:valAx>\n" + ' <c:axId val="-2113994440"/>\n' + " <c:scaling/>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="{val_ax_pos}"/>\n' + " <c:majorGridlines/>\n" + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2068027336"/>\n' + ' <c:crosses val="autoZero"/>\n' + " </c:valAx>\n" + " </c:plotArea>\n" + ' <c:dispBlanksAs val="gap"/>\n' + " </c:chart>\n" + " <c:txPr>\n" + " <a:bodyPr/>\n" + " <a:lstStyle/>\n" + " <a:p>\n" + " <a:pPr>\n" + ' <a:defRPr sz="1800"/>\n' + " </a:pPr>\n" + ' <a:endParaRPr lang="en-US"/>\n' + " </a:p>\n" + " </c:txPr>\n" + "</c:chartSpace>\n" + ).format( + **{ + "barDir_xml": self._barDir_xml, + "grouping_xml": self._grouping_xml, + "ser_xml": self._ser_xml, + "overlap_xml": self._overlap_xml, + "cat_ax_xml": self._cat_ax_xml, + "val_ax_pos": self._val_ax_pos, + } + ) + + @property + def _barDir_xml(self): + XL = XL_CHART_TYPE + bar_types = (XL.BAR_CLUSTERED, XL.BAR_STACKED, XL.BAR_STACKED_100) + col_types = (XL.COLUMN_CLUSTERED, XL.COLUMN_STACKED, XL.COLUMN_STACKED_100) + if self._chart_type in bar_types: + return ' <c:barDir val="bar"/>\n' + elif self._chart_type in col_types: + return ' <c:barDir val="col"/>\n' + raise NotImplementedError("no _barDir_xml() for chart type %s" % self._chart_type) + + @property + def _cat_ax_pos(self): + return { + XL_CHART_TYPE.BAR_CLUSTERED: "l", + XL_CHART_TYPE.BAR_STACKED: "l", + XL_CHART_TYPE.BAR_STACKED_100: "l", + XL_CHART_TYPE.COLUMN_CLUSTERED: "b", + XL_CHART_TYPE.COLUMN_STACKED: "b", + XL_CHART_TYPE.COLUMN_STACKED_100: "b", + }[self._chart_type] + + @property + def _cat_ax_xml(self): + categories = self._chart_data.categories + + if categories.are_dates: + return ( + " <c:dateAx>\n" + ' <c:axId val="-2068027336"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="{cat_ax_pos}"/>\n' + ' <c:numFmt formatCode="{nf}" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2113994440"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:auto val="1"/>\n' + ' <c:lblOffset val="100"/>\n' + ' <c:baseTimeUnit val="days"/>\n' + " </c:dateAx>\n" + ).format(**{"cat_ax_pos": self._cat_ax_pos, "nf": categories.number_format}) + + return ( + " <c:catAx>\n" + ' <c:axId val="-2068027336"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="{cat_ax_pos}"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2113994440"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:auto val="1"/>\n' + ' <c:lblAlgn val="ctr"/>\n' + ' <c:lblOffset val="100"/>\n' + ' <c:noMultiLvlLbl val="0"/>\n' + " </c:catAx>\n" + ).format(**{"cat_ax_pos": self._cat_ax_pos}) + + @property + def _grouping_xml(self): + XL = XL_CHART_TYPE + clustered_types = (XL.BAR_CLUSTERED, XL.COLUMN_CLUSTERED) + stacked_types = (XL.BAR_STACKED, XL.COLUMN_STACKED) + percentStacked_types = (XL.BAR_STACKED_100, XL.COLUMN_STACKED_100) + if self._chart_type in clustered_types: + return ' <c:grouping val="clustered"/>\n' + elif self._chart_type in stacked_types: + return ' <c:grouping val="stacked"/>\n' + elif self._chart_type in percentStacked_types: + return ' <c:grouping val="percentStacked"/>\n' + raise NotImplementedError("no _grouping_xml() for chart type %s" % self._chart_type) + + @property + def _overlap_xml(self): + XL = XL_CHART_TYPE + percentStacked_types = ( + XL.BAR_STACKED, + XL.BAR_STACKED_100, + XL.COLUMN_STACKED, + XL.COLUMN_STACKED_100, + ) + if self._chart_type in percentStacked_types: + return ' <c:overlap val="100"/>\n' + return "" + + @property + def _ser_xml(self): + xml = "" + for series in self._chart_data: + xml_writer = _CategorySeriesXmlWriter(series) + xml += ( + " <c:ser>\n" + ' <c:idx val="{ser_idx}"/>\n' + ' <c:order val="{ser_order}"/>\n' + "{tx_xml}" + "{cat_xml}" + "{val_xml}" + " </c:ser>\n" + ).format( + **{ + "ser_idx": series.index, + "ser_order": series.index, + "tx_xml": xml_writer.tx_xml, + "cat_xml": xml_writer.cat_xml, + "val_xml": xml_writer.val_xml, + } + ) + return xml + + @property + def _val_ax_pos(self): + return { + XL_CHART_TYPE.BAR_CLUSTERED: "b", + XL_CHART_TYPE.BAR_STACKED: "b", + XL_CHART_TYPE.BAR_STACKED_100: "b", + XL_CHART_TYPE.COLUMN_CLUSTERED: "l", + XL_CHART_TYPE.COLUMN_STACKED: "l", + XL_CHART_TYPE.COLUMN_STACKED_100: "l", + }[self._chart_type] + + +class _DoughnutChartXmlWriter(_BaseChartXmlWriter): + """ + Provides specialized methods particular to the ``<c:doughnutChart>`` + element. + """ + + @property + def xml(self): + return ( + "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawin' + 'gml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/draw' + 'ingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/off' + 'iceDocument/2006/relationships">\n' + ' <c:date1904 val="0"/>\n' + ' <c:roundedCorners val="0"/>\n' + " <c:chart>\n" + ' <c:autoTitleDeleted val="0"/>\n' + " <c:plotArea>\n" + " <c:layout/>\n" + " <c:doughnutChart>\n" + ' <c:varyColors val="1"/>\n' + "{ser_xml}" + " <c:dLbls>\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>\n" + ' <c:firstSliceAng val="0"/>\n' + ' <c:holeSize val="50"/>\n' + " </c:doughnutChart>\n" + " </c:plotArea>\n" + " <c:legend>\n" + ' <c:legendPos val="r"/>\n' + " <c:layout/>\n" + ' <c:overlay val="0"/>\n' + " </c:legend>\n" + ' <c:plotVisOnly val="1"/>\n' + ' <c:dispBlanksAs val="gap"/>\n' + ' <c:showDLblsOverMax val="0"/>\n' + " </c:chart>\n" + " <c:txPr>\n" + " <a:bodyPr/>\n" + " <a:lstStyle/>\n" + " <a:p>\n" + " <a:pPr>\n" + ' <a:defRPr sz="1800"/>\n' + " </a:pPr>\n" + " <a:endParaRPr/>\n" + " </a:p>\n" + " </c:txPr>\n" + "</c:chartSpace>\n" + ).format(**{"ser_xml": self._ser_xml}) + + @property + def _explosion_xml(self): + if self._chart_type == XL_CHART_TYPE.DOUGHNUT_EXPLODED: + return ' <c:explosion val="25"/>\n' + return "" + + @property + def _ser_xml(self): + xml = "" + for series in self._chart_data: + xml_writer = _CategorySeriesXmlWriter(series) + xml += ( + " <c:ser>\n" + ' <c:idx val="{ser_idx}"/>\n' + ' <c:order val="{ser_order}"/>\n' + "{tx_xml}" + "{explosion_xml}" + "{cat_xml}" + "{val_xml}" + " </c:ser>\n" + ).format( + **{ + "ser_idx": series.index, + "ser_order": series.index, + "tx_xml": xml_writer.tx_xml, + "explosion_xml": self._explosion_xml, + "cat_xml": xml_writer.cat_xml, + "val_xml": xml_writer.val_xml, + } + ) + return xml + + +class _LineChartXmlWriter(_BaseChartXmlWriter): + """ + Provides specialized methods particular to the ``<c:lineChart>`` element. + """ + + @property + def xml(self): + return ( + "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawin' + 'gml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/draw' + 'ingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/off' + 'iceDocument/2006/relationships">\n' + ' <c:date1904 val="0"/>\n' + " <c:chart>\n" + ' <c:autoTitleDeleted val="0"/>\n' + " <c:plotArea>\n" + " <c:lineChart>\n" + "{grouping_xml}" + ' <c:varyColors val="0"/>\n' + "{ser_xml}" + ' <c:marker val="1"/>\n' + ' <c:smooth val="0"/>\n' + ' <c:axId val="2118791784"/>\n' + ' <c:axId val="2140495176"/>\n' + " </c:lineChart>\n" + "{cat_ax_xml}" + " <c:valAx>\n" + ' <c:axId val="2140495176"/>\n' + " <c:scaling/>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="l"/>\n' + " <c:majorGridlines/>\n" + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="2118791784"/>\n' + ' <c:crosses val="autoZero"/>\n' + " </c:valAx>\n" + " </c:plotArea>\n" + " <c:legend>\n" + ' <c:legendPos val="r"/>\n' + " <c:layout/>\n" + ' <c:overlay val="0"/>\n' + " </c:legend>\n" + ' <c:plotVisOnly val="1"/>\n' + ' <c:dispBlanksAs val="gap"/>\n' + ' <c:showDLblsOverMax val="0"/>\n' + " </c:chart>\n" + " <c:txPr>\n" + " <a:bodyPr/>\n" + " <a:lstStyle/>\n" + " <a:p>\n" + " <a:pPr>\n" + ' <a:defRPr sz="1800"/>\n' + " </a:pPr>\n" + ' <a:endParaRPr lang="en-US"/>\n' + " </a:p>\n" + " </c:txPr>\n" + "</c:chartSpace>\n" + ).format( + **{ + "grouping_xml": self._grouping_xml, + "ser_xml": self._ser_xml, + "cat_ax_xml": self._cat_ax_xml, + } + ) + + @property + def _cat_ax_xml(self): + categories = self._chart_data.categories + + if categories.are_dates: + return ( + " <c:dateAx>\n" + ' <c:axId val="2118791784"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="b"/>\n' + ' <c:numFmt formatCode="{nf}" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="2140495176"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:auto val="1"/>\n' + ' <c:lblOffset val="100"/>\n' + ' <c:baseTimeUnit val="days"/>\n' + " </c:dateAx>\n" + ).format(**{"nf": categories.number_format}) + + return ( + " <c:catAx>\n" + ' <c:axId val="2118791784"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="b"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="2140495176"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:auto val="1"/>\n' + ' <c:lblAlgn val="ctr"/>\n' + ' <c:lblOffset val="100"/>\n' + ' <c:noMultiLvlLbl val="0"/>\n' + " </c:catAx>\n" + ) + + @property + def _grouping_xml(self): + XL = XL_CHART_TYPE + standard_types = (XL.LINE, XL.LINE_MARKERS) + stacked_types = (XL.LINE_STACKED, XL.LINE_MARKERS_STACKED) + percentStacked_types = (XL.LINE_STACKED_100, XL.LINE_MARKERS_STACKED_100) + if self._chart_type in standard_types: + return ' <c:grouping val="standard"/>\n' + elif self._chart_type in stacked_types: + return ' <c:grouping val="stacked"/>\n' + elif self._chart_type in percentStacked_types: + return ' <c:grouping val="percentStacked"/>\n' + raise NotImplementedError("no _grouping_xml() for chart type %s" % self._chart_type) + + @property + def _marker_xml(self): + XL = XL_CHART_TYPE + no_marker_types = (XL.LINE, XL.LINE_STACKED, XL.LINE_STACKED_100) + if self._chart_type in no_marker_types: + return ( + " <c:marker>\n" + ' <c:symbol val="none"/>\n' + " </c:marker>\n" + ) + return "" + + @property + def _ser_xml(self): + xml = "" + for series in self._chart_data: + xml_writer = _CategorySeriesXmlWriter(series) + xml += ( + " <c:ser>\n" + ' <c:idx val="{ser_idx}"/>\n' + ' <c:order val="{ser_order}"/>\n' + "{tx_xml}" + "{marker_xml}" + "{cat_xml}" + "{val_xml}" + ' <c:smooth val="0"/>\n' + " </c:ser>\n" + ).format( + **{ + "ser_idx": series.index, + "ser_order": series.index, + "tx_xml": xml_writer.tx_xml, + "marker_xml": self._marker_xml, + "cat_xml": xml_writer.cat_xml, + "val_xml": xml_writer.val_xml, + } + ) + return xml + + +class _PieChartXmlWriter(_BaseChartXmlWriter): + """ + Provides specialized methods particular to the ``<c:pieChart>`` element. + """ + + @property + def xml(self): + return ( + "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawin' + 'gml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/draw' + 'ingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/off' + 'iceDocument/2006/relationships">\n' + " <c:chart>\n" + ' <c:autoTitleDeleted val="0"/>\n' + " <c:plotArea>\n" + " <c:pieChart>\n" + ' <c:varyColors val="1"/>\n' + "{ser_xml}" + " </c:pieChart>\n" + " </c:plotArea>\n" + ' <c:dispBlanksAs val="gap"/>\n' + " </c:chart>\n" + " <c:txPr>\n" + " <a:bodyPr/>\n" + " <a:lstStyle/>\n" + " <a:p>\n" + " <a:pPr>\n" + ' <a:defRPr sz="1800"/>\n' + " </a:pPr>\n" + ' <a:endParaRPr lang="en-US"/>\n' + " </a:p>\n" + " </c:txPr>\n" + "</c:chartSpace>\n" + ).format(**{"ser_xml": self._ser_xml}) + + @property + def _explosion_xml(self): + if self._chart_type == XL_CHART_TYPE.PIE_EXPLODED: + return ' <c:explosion val="25"/>\n' + return "" + + @property + def _ser_xml(self): + xml_writer = _CategorySeriesXmlWriter(self._chart_data[0]) + xml = ( + " <c:ser>\n" + ' <c:idx val="0"/>\n' + ' <c:order val="0"/>\n' + "{tx_xml}" + "{explosion_xml}" + "{cat_xml}" + "{val_xml}" + " </c:ser>\n" + ).format( + **{ + "tx_xml": xml_writer.tx_xml, + "explosion_xml": self._explosion_xml, + "cat_xml": xml_writer.cat_xml, + "val_xml": xml_writer.val_xml, + } + ) + return xml + + +class _RadarChartXmlWriter(_BaseChartXmlWriter): + """ + Generates XML for the ``<c:radarChart>`` element. + """ + + @property + def xml(self): + return ( + "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawin' + 'gml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/draw' + 'ingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/off' + 'iceDocument/2006/relationships">\n' + ' <c:date1904 val="0"/>\n' + ' <c:roundedCorners val="0"/>\n' + ' <mc:AlternateContent xmlns:mc="http://schemas.openxmlformats.' + 'org/markup-compatibility/2006">\n' + ' <mc:Choice xmlns:c14="http://schemas.microsoft.com/office/d' + 'rawing/2007/8/2/chart" Requires="c14">\n' + ' <c14:style val="118"/>\n' + " </mc:Choice>\n" + " <mc:Fallback>\n" + ' <c:style val="18"/>\n' + " </mc:Fallback>\n" + " </mc:AlternateContent>\n" + " <c:chart>\n" + ' <c:autoTitleDeleted val="0"/>\n' + " <c:plotArea>\n" + " <c:layout/>\n" + " <c:radarChart>\n" + ' <c:radarStyle val="{radar_style}"/>\n' + ' <c:varyColors val="0"/>\n' + "{ser_xml}" + ' <c:axId val="2073612648"/>\n' + ' <c:axId val="-2112772216"/>\n' + " </c:radarChart>\n" + " <c:catAx>\n" + ' <c:axId val="2073612648"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="b"/>\n' + " <c:majorGridlines/>\n" + ' <c:numFmt formatCode="m/d/yy" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2112772216"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:auto val="1"/>\n' + ' <c:lblAlgn val="ctr"/>\n' + ' <c:lblOffset val="100"/>\n' + ' <c:noMultiLvlLbl val="0"/>\n' + " </c:catAx>\n" + " <c:valAx>\n" + ' <c:axId val="-2112772216"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="l"/>\n' + " <c:majorGridlines/>\n" + ' <c:numFmt formatCode="General" sourceLinked="1"/>\n' + ' <c:majorTickMark val="cross"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="2073612648"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:crossBetween val="between"/>\n' + " </c:valAx>\n" + " </c:plotArea>\n" + ' <c:plotVisOnly val="1"/>\n' + ' <c:dispBlanksAs val="gap"/>\n' + ' <c:showDLblsOverMax val="0"/>\n' + " </c:chart>\n" + " <c:txPr>\n" + " <a:bodyPr/>\n" + " <a:lstStyle/>\n" + " <a:p>\n" + " <a:pPr>\n" + ' <a:defRPr sz="1800"/>\n' + " </a:pPr>\n" + ' <a:endParaRPr lang="en-US"/>\n' + " </a:p>\n" + " </c:txPr>\n" + "</c:chartSpace>\n" + ).format(**{"radar_style": self._radar_style, "ser_xml": self._ser_xml}) + + @property + def _marker_xml(self): + if self._chart_type == XL_CHART_TYPE.RADAR: + return ( + " <c:marker>\n" + ' <c:symbol val="none"/>\n' + " </c:marker>\n" + ) + return "" + + @property + def _radar_style(self): + if self._chart_type == XL_CHART_TYPE.RADAR_FILLED: + return "filled" + return "marker" + + @property + def _ser_xml(self): + xml = "" + for series in self._chart_data: + xml_writer = _CategorySeriesXmlWriter(series) + xml += ( + " <c:ser>\n" + ' <c:idx val="{ser_idx}"/>\n' + ' <c:order val="{ser_order}"/>\n' + "{tx_xml}" + "{marker_xml}" + "{cat_xml}" + "{val_xml}" + ' <c:smooth val="0"/>\n' + " </c:ser>\n" + ).format( + **{ + "ser_idx": series.index, + "ser_order": series.index, + "tx_xml": xml_writer.tx_xml, + "marker_xml": self._marker_xml, + "cat_xml": xml_writer.cat_xml, + "val_xml": xml_writer.val_xml, + } + ) + return xml + + +class _XyChartXmlWriter(_BaseChartXmlWriter): + """ + Generates XML for the ``<c:scatterChart>`` element. + """ + + @property + def xml(self): + xml = ( + "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawin' + 'gml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/draw' + 'ingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/off' + 'iceDocument/2006/relationships">\n' + " <c:chart>\n" + " <c:plotArea>\n" + " <c:scatterChart>\n" + ' <c:scatterStyle val="%s"/>\n' + ' <c:varyColors val="0"/>\n' + "%s" + ' <c:axId val="-2128940872"/>\n' + ' <c:axId val="-2129643912"/>\n' + " </c:scatterChart>\n" + " <c:valAx>\n" + ' <c:axId val="-2128940872"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="b"/>\n' + ' <c:numFmt formatCode="General" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2129643912"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:crossBetween val="midCat"/>\n' + " </c:valAx>\n" + " <c:valAx>\n" + ' <c:axId val="-2129643912"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="l"/>\n' + " <c:majorGridlines/>\n" + ' <c:numFmt formatCode="General" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2128940872"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:crossBetween val="midCat"/>\n' + " </c:valAx>\n" + " </c:plotArea>\n" + " <c:legend>\n" + ' <c:legendPos val="r"/>\n' + " <c:layout/>\n" + ' <c:overlay val="0"/>\n' + " </c:legend>\n" + ' <c:plotVisOnly val="1"/>\n' + ' <c:dispBlanksAs val="gap"/>\n' + ' <c:showDLblsOverMax val="0"/>\n' + " </c:chart>\n" + " <c:txPr>\n" + " <a:bodyPr/>\n" + " <a:lstStyle/>\n" + " <a:p>\n" + " <a:pPr>\n" + ' <a:defRPr sz="1800"/>\n' + " </a:pPr>\n" + ' <a:endParaRPr lang="en-US"/>\n' + " </a:p>\n" + " </c:txPr>\n" + "</c:chartSpace>\n" + ) % (self._scatterStyle_val, self._ser_xml) + return xml + + @property + def _marker_xml(self): + no_marker_types = ( + XL_CHART_TYPE.XY_SCATTER_LINES_NO_MARKERS, + XL_CHART_TYPE.XY_SCATTER_SMOOTH_NO_MARKERS, + ) + if self._chart_type in no_marker_types: + return ( + " <c:marker>\n" + ' <c:symbol val="none"/>\n' + " </c:marker>\n" + ) + return "" + + @property + def _scatterStyle_val(self): + smooth_types = ( + XL_CHART_TYPE.XY_SCATTER_SMOOTH, + XL_CHART_TYPE.XY_SCATTER_SMOOTH_NO_MARKERS, + ) + if self._chart_type in smooth_types: + return "smoothMarker" + return "lineMarker" + + @property + def _ser_xml(self): + xml = "" + for series in self._chart_data: + xml_writer = _XySeriesXmlWriter(series) + xml += ( + " <c:ser>\n" + ' <c:idx val="{ser_idx}"/>\n' + ' <c:order val="{ser_order}"/>\n' + "{tx_xml}" + "{spPr_xml}" + "{marker_xml}" + "{xVal_xml}" + "{yVal_xml}" + ' <c:smooth val="0"/>\n' + " </c:ser>\n" + ).format( + **{ + "ser_idx": series.index, + "ser_order": series.index, + "tx_xml": xml_writer.tx_xml, + "spPr_xml": self._spPr_xml, + "marker_xml": self._marker_xml, + "xVal_xml": xml_writer.xVal_xml, + "yVal_xml": xml_writer.yVal_xml, + } + ) + return xml + + @property + def _spPr_xml(self): + if self._chart_type == XL_CHART_TYPE.XY_SCATTER: + return ( + " <c:spPr>\n" + ' <a:ln w="47625">\n' + " <a:noFill/>\n" + " </a:ln>\n" + " </c:spPr>\n" + ) + return "" + + +class _BubbleChartXmlWriter(_XyChartXmlWriter): + """ + Provides specialized methods particular to the ``<c:bubbleChart>`` + element. + """ + + @property + def xml(self): + xml = ( + "<?xml version='1.0' encoding='UTF-8' standalone='yes'?>\n" + '<c:chartSpace xmlns:c="http://schemas.openxmlformats.org/drawin' + 'gml/2006/chart" xmlns:a="http://schemas.openxmlformats.org/draw' + 'ingml/2006/main" xmlns:r="http://schemas.openxmlformats.org/off' + 'iceDocument/2006/relationships">\n' + " <c:chart>\n" + ' <c:autoTitleDeleted val="0"/>\n' + " <c:plotArea>\n" + " <c:layout/>\n" + " <c:bubbleChart>\n" + ' <c:varyColors val="0"/>\n' + "%s" + " <c:dLbls>\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:dLbls>\n" + ' <c:bubbleScale val="100"/>\n' + ' <c:showNegBubbles val="0"/>\n' + ' <c:axId val="-2115720072"/>\n' + ' <c:axId val="-2115723560"/>\n' + " </c:bubbleChart>\n" + " <c:valAx>\n" + ' <c:axId val="-2115720072"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="b"/>\n' + ' <c:numFmt formatCode="General" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2115723560"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:crossBetween val="midCat"/>\n' + " </c:valAx>\n" + " <c:valAx>\n" + ' <c:axId val="-2115723560"/>\n' + " <c:scaling>\n" + ' <c:orientation val="minMax"/>\n' + " </c:scaling>\n" + ' <c:delete val="0"/>\n' + ' <c:axPos val="l"/>\n' + " <c:majorGridlines/>\n" + ' <c:numFmt formatCode="General" sourceLinked="1"/>\n' + ' <c:majorTickMark val="out"/>\n' + ' <c:minorTickMark val="none"/>\n' + ' <c:tickLblPos val="nextTo"/>\n' + ' <c:crossAx val="-2115720072"/>\n' + ' <c:crosses val="autoZero"/>\n' + ' <c:crossBetween val="midCat"/>\n' + " </c:valAx>\n" + " </c:plotArea>\n" + " <c:legend>\n" + ' <c:legendPos val="r"/>\n' + " <c:layout/>\n" + ' <c:overlay val="0"/>\n' + " </c:legend>\n" + ' <c:plotVisOnly val="1"/>\n' + ' <c:dispBlanksAs val="gap"/>\n' + ' <c:showDLblsOverMax val="0"/>\n' + " </c:chart>\n" + " <c:txPr>\n" + " <a:bodyPr/>\n" + " <a:lstStyle/>\n" + " <a:p>\n" + " <a:pPr>\n" + ' <a:defRPr sz="1800"/>\n' + " </a:pPr>\n" + ' <a:endParaRPr lang="en-US"/>\n' + " </a:p>\n" + " </c:txPr>\n" + "</c:chartSpace>\n" + ) % self._ser_xml + return xml + + @property + def _bubble3D_val(self): + if self._chart_type == XL_CHART_TYPE.BUBBLE_THREE_D_EFFECT: + return "1" + return "0" + + @property + def _ser_xml(self): + xml = "" + for series in self._chart_data: + xml_writer = _BubbleSeriesXmlWriter(series) + xml += ( + " <c:ser>\n" + ' <c:idx val="{ser_idx}"/>\n' + ' <c:order val="{ser_order}"/>\n' + "{tx_xml}" + ' <c:invertIfNegative val="0"/>\n' + "{xVal_xml}" + "{yVal_xml}" + "{bubbleSize_xml}" + ' <c:bubble3D val="{bubble3D_val}"/>\n' + " </c:ser>\n" + ).format( + **{ + "ser_idx": series.index, + "ser_order": series.index, + "tx_xml": xml_writer.tx_xml, + "xVal_xml": xml_writer.xVal_xml, + "yVal_xml": xml_writer.yVal_xml, + "bubbleSize_xml": xml_writer.bubbleSize_xml, + "bubble3D_val": self._bubble3D_val, + } + ) + return xml + + +class _CategorySeriesXmlWriter(_BaseSeriesXmlWriter): + """ + Generates XML snippets particular to a category chart series. + """ + + @property + def cat(self): + """ + Return the ``<c:cat>`` element XML for this series, as an oxml + element. + """ + categories = self._series.categories + + if categories.are_numeric: + return parse_xml( + self._numRef_cat_tmpl.format( + **{ + "wksht_ref": self._series.categories_ref, + "number_format": categories.number_format, + "cat_count": categories.leaf_count, + "cat_pt_xml": self._cat_num_pt_xml, + "nsdecls": " %s" % nsdecls("c"), + } + ) + ) + + if categories.depth == 1: + return parse_xml( + self._cat_tmpl.format( + **{ + "wksht_ref": self._series.categories_ref, + "cat_count": categories.leaf_count, + "cat_pt_xml": self._cat_pt_xml, + "nsdecls": " %s" % nsdecls("c"), + } + ) + ) + + return parse_xml( + self._multiLvl_cat_tmpl.format( + **{ + "wksht_ref": self._series.categories_ref, + "cat_count": categories.leaf_count, + "lvl_xml": self._lvl_xml(categories), + "nsdecls": " %s" % nsdecls("c"), + } + ) + ) + + @property + def cat_xml(self): + """ + The unicode XML snippet for the ``<c:cat>`` element for this series, + containing the category labels and spreadsheet reference. + """ + categories = self._series.categories + + if categories.are_numeric: + return self._numRef_cat_tmpl.format( + **{ + "wksht_ref": self._series.categories_ref, + "number_format": categories.number_format, + "cat_count": categories.leaf_count, + "cat_pt_xml": self._cat_num_pt_xml, + "nsdecls": "", + } + ) + + if categories.depth == 1: + return self._cat_tmpl.format( + **{ + "wksht_ref": self._series.categories_ref, + "cat_count": categories.leaf_count, + "cat_pt_xml": self._cat_pt_xml, + "nsdecls": "", + } + ) + + return self._multiLvl_cat_tmpl.format( + **{ + "wksht_ref": self._series.categories_ref, + "cat_count": categories.leaf_count, + "lvl_xml": self._lvl_xml(categories), + "nsdecls": "", + } + ) + + @property + def val(self): + """ + The ``<c:val>`` XML for this series, as an oxml element. + """ + xml = self._val_tmpl.format( + **{ + "nsdecls": " %s" % nsdecls("c"), + "values_ref": self._series.values_ref, + "number_format": self._series.number_format, + "val_count": len(self._series), + "val_pt_xml": self._val_pt_xml, + } + ) + return parse_xml(xml) + + @property + def val_xml(self): + """ + Return the unicode XML snippet for the ``<c:val>`` element describing + this series, containing the series values and their spreadsheet range + reference. + """ + return self._val_tmpl.format( + **{ + "nsdecls": "", + "values_ref": self._series.values_ref, + "number_format": self._series.number_format, + "val_count": len(self._series), + "val_pt_xml": self._val_pt_xml, + } + ) + + @property + def _cat_num_pt_xml(self): + """ + The unicode XML snippet for the ``<c:pt>`` elements when category + labels are numeric (including date type). + """ + xml = "" + for idx, category in enumerate(self._series.categories): + xml += ( + ' <c:pt idx="{cat_idx}">\n' + " <c:v>{cat_lbl_str}</c:v>\n" + " </c:pt>\n" + ).format( + **{ + "cat_idx": idx, + "cat_lbl_str": category.numeric_str_val(self._date_1904), + } + ) + return xml + + @property + def _cat_pt_xml(self): + """ + The unicode XML snippet for the ``<c:pt>`` elements containing the + category names for this series. + """ + xml = "" + for idx, category in enumerate(self._series.categories): + xml += ( + ' <c:pt idx="{cat_idx}">\n' + " <c:v>{cat_label}</c:v>\n" + " </c:pt>\n" + ).format(**{"cat_idx": idx, "cat_label": escape(str(category.label))}) + return xml + + @property + def _cat_tmpl(self): + """ + The template for the ``<c:cat>`` element for this series, containing + the category labels and spreadsheet reference. + """ + return ( + " <c:cat{nsdecls}>\n" + " <c:strRef>\n" + " <c:f>{wksht_ref}</c:f>\n" + " <c:strCache>\n" + ' <c:ptCount val="{cat_count}"/>\n' + "{cat_pt_xml}" + " </c:strCache>\n" + " </c:strRef>\n" + " </c:cat>\n" + ) + + def _lvl_xml(self, categories): + """ + The unicode XML snippet for the ``<c:lvl>`` elements containing + multi-level category names. + """ + + def lvl_pt_xml(level): + xml = "" + for idx, name in level: + xml += ( + ' <c:pt idx="%d">\n' + " <c:v>%s</c:v>\n" + " </c:pt>\n" + ) % (idx, escape("%s" % name)) + return xml + + xml = "" + for level in categories.levels: + xml += (" <c:lvl>\n" "{lvl_pt_xml}" " </c:lvl>\n").format( + **{"lvl_pt_xml": lvl_pt_xml(level)} + ) + return xml + + @property + def _multiLvl_cat_tmpl(self): + """ + The template for the ``<c:cat>`` element for this series when there + are multi-level (nested) categories. + """ + return ( + " <c:cat{nsdecls}>\n" + " <c:multiLvlStrRef>\n" + " <c:f>{wksht_ref}</c:f>\n" + " <c:multiLvlStrCache>\n" + ' <c:ptCount val="{cat_count}"/>\n' + "{lvl_xml}" + " </c:multiLvlStrCache>\n" + " </c:multiLvlStrRef>\n" + " </c:cat>\n" + ) + + @property + def _numRef_cat_tmpl(self): + """ + The template for the ``<c:cat>`` element for this series when the + labels are numeric (or date) values. + """ + return ( + " <c:cat{nsdecls}>\n" + " <c:numRef>\n" + " <c:f>{wksht_ref}</c:f>\n" + " <c:numCache>\n" + " <c:formatCode>{number_format}</c:formatCode>\n" + ' <c:ptCount val="{cat_count}"/>\n' + "{cat_pt_xml}" + " </c:numCache>\n" + " </c:numRef>\n" + " </c:cat>\n" + ) + + @property + def _val_pt_xml(self): + """ + The unicode XML snippet containing the ``<c:pt>`` elements containing + the values for this series. + """ + xml = "" + for idx, value in enumerate(self._series.values): + if value is None: + continue + xml += ( + ' <c:pt idx="{val_idx:d}">\n' + " <c:v>{value}</c:v>\n" + " </c:pt>\n" + ).format(**{"val_idx": idx, "value": value}) + return xml + + @property + def _val_tmpl(self): + """ + The template for the ``<c:val>`` element for this series, containing + the series values and their spreadsheet range reference. + """ + return ( + " <c:val{nsdecls}>\n" + " <c:numRef>\n" + " <c:f>{values_ref}</c:f>\n" + " <c:numCache>\n" + " <c:formatCode>{number_format}</c:formatCode>\n" + ' <c:ptCount val="{val_count}"/>\n' + "{val_pt_xml}" + " </c:numCache>\n" + " </c:numRef>\n" + " </c:val>\n" + ) + + +class _XySeriesXmlWriter(_BaseSeriesXmlWriter): + """ + Generates XML snippets particular to an XY series. + """ + + @property + def xVal(self): + """ + Return the ``<c:xVal>`` element for this series as an oxml element. + This element contains the X values for this series. + """ + xml = self._xVal_tmpl.format( + **{ + "nsdecls": " %s" % nsdecls("c"), + "numRef_xml": self.numRef_xml( + self._series.x_values_ref, + self._series.number_format, + self._series.x_values, + ), + } + ) + return parse_xml(xml) + + @property + def xVal_xml(self): + """ + Return the ``<c:xVal>`` element for this series as unicode text. This + element contains the X values for this series. + """ + return self._xVal_tmpl.format( + **{ + "nsdecls": "", + "numRef_xml": self.numRef_xml( + self._series.x_values_ref, + self._series.number_format, + self._series.x_values, + ), + } + ) + + @property + def yVal(self): + """ + Return the ``<c:yVal>`` element for this series as an oxml element. + This element contains the Y values for this series. + """ + xml = self._yVal_tmpl.format( + **{ + "nsdecls": " %s" % nsdecls("c"), + "numRef_xml": self.numRef_xml( + self._series.y_values_ref, + self._series.number_format, + self._series.y_values, + ), + } + ) + return parse_xml(xml) + + @property + def yVal_xml(self): + """ + Return the ``<c:yVal>`` element for this series as unicode text. This + element contains the Y values for this series. + """ + return self._yVal_tmpl.format( + **{ + "nsdecls": "", + "numRef_xml": self.numRef_xml( + self._series.y_values_ref, + self._series.number_format, + self._series.y_values, + ), + } + ) + + @property + def _xVal_tmpl(self): + """ + The template for the ``<c:xVal>`` element for this series, containing + the X values and their spreadsheet range reference. + """ + return " <c:xVal{nsdecls}>\n" "{numRef_xml}" " </c:xVal>\n" + + @property + def _yVal_tmpl(self): + """ + The template for the ``<c:yVal>`` element for this series, containing + the Y values and their spreadsheet range reference. + """ + return " <c:yVal{nsdecls}>\n" "{numRef_xml}" " </c:yVal>\n" + + +class _BubbleSeriesXmlWriter(_XySeriesXmlWriter): + """ + Generates XML snippets particular to a bubble chart series. + """ + + @property + def bubbleSize(self): + """ + Return the ``<c:bubbleSize>`` element for this series as an oxml + element. This element contains the bubble size values for this + series. + """ + xml = self._bubbleSize_tmpl.format( + **{ + "nsdecls": " %s" % nsdecls("c"), + "numRef_xml": self.numRef_xml( + self._series.bubble_sizes_ref, + self._series.number_format, + self._series.bubble_sizes, + ), + } + ) + return parse_xml(xml) + + @property + def bubbleSize_xml(self): + """ + Return the ``<c:bubbleSize>`` element for this series as unicode + text. This element contains the bubble size values for all the + data points in the chart. + """ + return self._bubbleSize_tmpl.format( + **{ + "nsdecls": "", + "numRef_xml": self.numRef_xml( + self._series.bubble_sizes_ref, + self._series.number_format, + self._series.bubble_sizes, + ), + } + ) + + @property + def _bubbleSize_tmpl(self): + """ + The template for the ``<c:bubbleSize>`` element for this series, + containing the bubble size values and their spreadsheet range + reference. + """ + return " <c:bubbleSize{nsdecls}>\n" "{numRef_xml}" " </c:bubbleSize>\n" + + +class _BubbleSeriesXmlRewriter(_BaseSeriesXmlRewriter): + """ + A series rewriter suitable for bubble charts. + """ + + def _rewrite_ser_data(self, ser, series_data, date_1904): + """ + Rewrite the ``<c:tx>``, ``<c:cat>`` and ``<c:val>`` child elements + of *ser* based on the values in *series_data*. + """ + ser._remove_tx() + ser._remove_xVal() + ser._remove_yVal() + ser._remove_bubbleSize() + + xml_writer = _BubbleSeriesXmlWriter(series_data) + + ser._insert_tx(xml_writer.tx) + ser._insert_xVal(xml_writer.xVal) + ser._insert_yVal(xml_writer.yVal) + ser._insert_bubbleSize(xml_writer.bubbleSize) + + +class _CategorySeriesXmlRewriter(_BaseSeriesXmlRewriter): + """ + A series rewriter suitable for category charts. + """ + + def _rewrite_ser_data(self, ser, series_data, date_1904): + """ + Rewrite the ``<c:tx>``, ``<c:cat>`` and ``<c:val>`` child elements + of *ser* based on the values in *series_data*. + """ + ser._remove_tx() + ser._remove_cat() + ser._remove_val() + + xml_writer = _CategorySeriesXmlWriter(series_data, date_1904) + + ser._insert_tx(xml_writer.tx) + ser._insert_cat(xml_writer.cat) + ser._insert_val(xml_writer.val) + + +class _XySeriesXmlRewriter(_BaseSeriesXmlRewriter): + """ + A series rewriter suitable for XY (aka. scatter) charts. + """ + + def _rewrite_ser_data(self, ser, series_data, date_1904): + """ + Rewrite the ``<c:tx>``, ``<c:xVal>`` and ``<c:yVal>`` child elements + of *ser* based on the values in *series_data*. + """ + ser._remove_tx() + ser._remove_xVal() + ser._remove_yVal() + + xml_writer = _XySeriesXmlWriter(series_data) + + ser._insert_tx(xml_writer.tx) + ser._insert_xVal(xml_writer.xVal) + ser._insert_yVal(xml_writer.yVal) |