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