about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/pptx/chart/xmlwriter.py
diff options
context:
space:
mode:
authorS. Solomon Darnell2025-03-28 21:52:21 -0500
committerS. Solomon Darnell2025-03-28 21:52:21 -0500
commit4a52a71956a8d46fcb7294ac71734504bb09bcc2 (patch)
treeee3dc5af3b6313e921cd920906356f5d4febc4ed /.venv/lib/python3.12/site-packages/pptx/chart/xmlwriter.py
parentcc961e04ba734dd72309fb548a2f97d67d578813 (diff)
downloadgn-ai-master.tar.gz
two version of R2R are here HEAD master
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.py1840
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)