about summary refs log tree commit diff
path: root/.venv/lib/python3.12/site-packages/openpyxl/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/openpyxl/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/openpyxl/chart')
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py105
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py19
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py199
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py106
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/axis.py401
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py144
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py67
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py195
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py246
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py43
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py62
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/label.py127
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/layout.py74
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/legend.py75
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py129
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/marker.py90
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/picture.py35
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py177
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py65
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py162
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py57
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py55
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/reader.py32
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/reference.py124
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py53
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/series.py197
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py41
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py89
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py54
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py119
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/text.py78
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/title.py76
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py98
-rw-r--r--.venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py31
34 files changed, 3625 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py
new file mode 100644
index 00000000..1651a993
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/_3d.py
@@ -0,0 +1,105 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors import Typed, Alias
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedInteger,
+    NestedMinMax,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from .marker import PictureOptions
+from .shapes import GraphicalProperties
+
+
+class View3D(Serialisable):
+
+    tagname = "view3D"
+
+    rotX = NestedMinMax(min=-90, max=90, allow_none=True)
+    x_rotation = Alias('rotX')
+    hPercent = NestedMinMax(min=5, max=500, allow_none=True)
+    height_percent = Alias('hPercent')
+    rotY = NestedInteger(min=-90, max=90, allow_none=True)
+    y_rotation = Alias('rotY')
+    depthPercent = NestedInteger(allow_none=True)
+    rAngAx = NestedBool(allow_none=True)
+    right_angle_axes = Alias('rAngAx')
+    perspective = NestedInteger(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('rotX', 'hPercent', 'rotY', 'depthPercent', 'rAngAx',
+                    'perspective',)
+
+    def __init__(self,
+                 rotX=15,
+                 hPercent=None,
+                 rotY=20,
+                 depthPercent=None,
+                 rAngAx=True,
+                 perspective=None,
+                 extLst=None,
+                ):
+        self.rotX = rotX
+        self.hPercent = hPercent
+        self.rotY = rotY
+        self.depthPercent = depthPercent
+        self.rAngAx = rAngAx
+        self.perspective = perspective
+
+
+class Surface(Serialisable):
+
+    tagname = "surface"
+
+    thickness = NestedInteger(allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('thickness', 'spPr', 'pictureOptions',)
+
+    def __init__(self,
+                 thickness=None,
+                 spPr=None,
+                 pictureOptions=None,
+                 extLst=None,
+                ):
+        self.thickness = thickness
+        self.spPr = spPr
+        self.pictureOptions = pictureOptions
+
+
+class _3DBase(Serialisable):
+
+    """
+    Base class for 3D charts
+    """
+
+    tagname = "ChartBase"
+
+    view3D = Typed(expected_type=View3D, allow_none=True)
+    floor = Typed(expected_type=Surface, allow_none=True)
+    sideWall = Typed(expected_type=Surface, allow_none=True)
+    backWall = Typed(expected_type=Surface, allow_none=True)
+
+    def __init__(self,
+                 view3D=None,
+                 floor=None,
+                 sideWall=None,
+                 backWall=None,
+                 ):
+        if view3D is None:
+            view3D = View3D()
+        self.view3D = view3D
+        if floor is None:
+            floor = Surface()
+        self.floor = floor
+        if sideWall is None:
+            sideWall = Surface()
+        self.sideWall = sideWall
+        if backWall is None:
+            backWall = Surface()
+        self.backWall = backWall
+        super(_3DBase, self).__init__()
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py
new file mode 100644
index 00000000..ecc4d8bf
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/__init__.py
@@ -0,0 +1,19 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from .area_chart import AreaChart, AreaChart3D
+from .bar_chart import BarChart, BarChart3D
+from .bubble_chart import BubbleChart
+from .line_chart import LineChart, LineChart3D
+from .pie_chart import (
+    PieChart,
+    PieChart3D,
+    DoughnutChart,
+    ProjectedPieChart
+)
+from .radar_chart import RadarChart
+from .scatter_chart import ScatterChart
+from .stock_chart import StockChart
+from .surface_chart import SurfaceChart, SurfaceChart3D
+
+from .series_factory import SeriesFactory as Series
+from .reference import Reference
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py
new file mode 100644
index 00000000..6a613546
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/_chart.py
@@ -0,0 +1,199 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from collections import OrderedDict
+from operator import attrgetter
+
+from openpyxl.descriptors import (
+    Typed,
+    Integer,
+    Alias,
+    MinMax,
+    Bool,
+    Set,
+)
+from openpyxl.descriptors.sequence import ValueSequence
+from openpyxl.descriptors.serialisable import Serialisable
+
+from ._3d import _3DBase
+from .data_source import AxDataSource, NumRef
+from .layout import Layout
+from .legend import Legend
+from .reference import Reference
+from .series_factory import SeriesFactory
+from .series import attribute_mapping
+from .shapes import GraphicalProperties
+from .title import TitleDescriptor
+
+class AxId(Serialisable):
+
+    val = Integer()
+
+    def __init__(self, val):
+        self.val = val
+
+
+def PlotArea():
+    from .chartspace import PlotArea
+    return PlotArea()
+
+
+class ChartBase(Serialisable):
+
+    """
+    Base class for all charts
+    """
+
+    legend = Typed(expected_type=Legend, allow_none=True)
+    layout = Typed(expected_type=Layout, allow_none=True)
+    roundedCorners = Bool(allow_none=True)
+    axId = ValueSequence(expected_type=int)
+    visible_cells_only = Bool(allow_none=True)
+    display_blanks = Set(values=['span', 'gap', 'zero'])
+    graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True)
+
+    _series_type = ""
+    ser = ()
+    series = Alias('ser')
+    title = TitleDescriptor()
+    anchor = "E15" # default anchor position
+    width = 15 # in cm, approx 5 rows
+    height = 7.5 # in cm, approx 14 rows
+    _id = 1
+    _path = "/xl/charts/chart{0}.xml"
+    style = MinMax(allow_none=True, min=1, max=48)
+    mime_type = "application/vnd.openxmlformats-officedocument.drawingml.chart+xml"
+    graphical_properties = Typed(expected_type=GraphicalProperties, allow_none=True) # mapped to chartspace
+
+    __elements__ = ()
+
+
+    def __init__(self, axId=(), **kw):
+        self._charts = [self]
+        self.title = None
+        self.layout = None
+        self.roundedCorners = None
+        self.legend = Legend()
+        self.graphical_properties = None
+        self.style = None
+        self.plot_area = PlotArea()
+        self.axId = axId
+        self.display_blanks = 'gap'
+        self.pivotSource = None
+        self.pivotFormats = ()
+        self.visible_cells_only = True
+        self.idx_base = 0
+        self.graphical_properties = None
+        super().__init__()
+
+
+    def __hash__(self):
+        """
+        Just need to check for identity
+        """
+        return id(self)
+
+    def __iadd__(self, other):
+        """
+        Combine the chart with another one
+        """
+        if not isinstance(other, ChartBase):
+            raise TypeError("Only other charts can be added")
+        self._charts.append(other)
+        return self
+
+
+    def to_tree(self, namespace=None, tagname=None, idx=None):
+        self.axId = [id for id in self._axes]
+        if self.ser is not None:
+            for s in self.ser:
+                s.__elements__ = attribute_mapping[self._series_type]
+        return super().to_tree(tagname, idx)
+
+
+    def _reindex(self):
+        """
+        Normalise and rebase series: sort by order and then rebase order
+
+        """
+        # sort data series in order and rebase
+        ds = sorted(self.series, key=attrgetter("order"))
+        for idx, s in enumerate(ds):
+            s.order = idx
+        self.series = ds
+
+
+    def _write(self):
+        from .chartspace import ChartSpace, ChartContainer
+        self.plot_area.layout = self.layout
+
+        idx_base = self.idx_base
+        for chart in self._charts:
+            if chart not in self.plot_area._charts:
+                chart.idx_base = idx_base
+                idx_base += len(chart.series)
+        self.plot_area._charts = self._charts
+
+        container = ChartContainer(plotArea=self.plot_area, legend=self.legend, title=self.title)
+        if isinstance(chart, _3DBase):
+            container.view3D = chart.view3D
+            container.floor = chart.floor
+            container.sideWall = chart.sideWall
+            container.backWall = chart.backWall
+        container.plotVisOnly = self.visible_cells_only
+        container.dispBlanksAs = self.display_blanks
+        container.pivotFmts = self.pivotFormats
+        cs = ChartSpace(chart=container)
+        cs.style = self.style
+        cs.roundedCorners = self.roundedCorners
+        cs.pivotSource = self.pivotSource
+        cs.spPr = self.graphical_properties
+        return cs.to_tree()
+
+
+    @property
+    def _axes(self):
+        x = getattr(self, "x_axis", None)
+        y = getattr(self, "y_axis", None)
+        z = getattr(self, "z_axis", None)
+        return OrderedDict([(axis.axId, axis) for axis in (x, y, z) if axis])
+
+
+    def set_categories(self, labels):
+        """
+        Set the categories / x-axis values
+        """
+        if not isinstance(labels, Reference):
+            labels = Reference(range_string=labels)
+        for s in self.ser:
+            s.cat = AxDataSource(numRef=NumRef(f=labels))
+
+
+    def add_data(self, data, from_rows=False, titles_from_data=False):
+        """
+        Add a range of data in a single pass.
+        The default is to treat each column as a data series.
+        """
+        if not isinstance(data, Reference):
+            data = Reference(range_string=data)
+
+        if from_rows:
+            values = data.rows
+
+        else:
+            values = data.cols
+
+        for ref in values:
+            series = SeriesFactory(ref, title_from_data=titles_from_data)
+            self.series.append(series)
+
+
+    def append(self, value):
+        """Append a data series to the chart"""
+        l = self.series[:]
+        l.append(value)
+        self.series = l
+
+
+    @property
+    def path(self):
+        return self._path.format(self._id)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py
new file mode 100644
index 00000000..d3d98085
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/area_chart.py
@@ -0,0 +1,106 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Set,
+    Bool,
+    Integer,
+    Sequence,
+    Alias,
+)
+
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedMinMax,
+    NestedSet,
+    NestedBool,
+)
+
+from ._chart import ChartBase
+from .descriptors import NestedGapAmount
+from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines
+from .label import DataLabelList
+from .series import Series
+
+
+class _AreaChartBase(ChartBase):
+
+    grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked']))
+    varyColors = NestedBool(nested=True, allow_none=True)
+    ser = Sequence(expected_type=Series, allow_none=True)
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    dataLabels = Alias("dLbls")
+    dropLines = Typed(expected_type=ChartLines, allow_none=True)
+
+    _series_type = "area"
+
+    __elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines')
+
+    def __init__(self,
+                 grouping="standard",
+                 varyColors=None,
+                 ser=(),
+                 dLbls=None,
+                 dropLines=None,
+                ):
+        self.grouping = grouping
+        self.varyColors = varyColors
+        self.ser = ser
+        self.dLbls = dLbls
+        self.dropLines = dropLines
+        super().__init__()
+
+
+class AreaChart(_AreaChartBase):
+
+    tagname = "areaChart"
+
+    grouping = _AreaChartBase.grouping
+    varyColors = _AreaChartBase.varyColors
+    ser = _AreaChartBase.ser
+    dLbls = _AreaChartBase.dLbls
+    dropLines = _AreaChartBase.dropLines
+
+    # chart properties actually used by containing classes
+    x_axis = Typed(expected_type=TextAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _AreaChartBase.__elements__ + ('axId',)
+
+    def __init__(self,
+                 axId=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+        super().__init__(**kw)
+
+
+class AreaChart3D(AreaChart):
+
+    tagname = "area3DChart"
+
+    grouping = _AreaChartBase.grouping
+    varyColors = _AreaChartBase.varyColors
+    ser = _AreaChartBase.ser
+    dLbls = _AreaChartBase.dLbls
+    dropLines = _AreaChartBase.dropLines
+
+    gapDepth = NestedGapAmount()
+
+    x_axis = Typed(expected_type=TextAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+    z_axis = Typed(expected_type=SeriesAxis, allow_none=True)
+
+    __elements__ = AreaChart.__elements__ + ('gapDepth', )
+
+    def __init__(self, gapDepth=None, **kw):
+        self.gapDepth = gapDepth
+        super(AreaChart3D, self).__init__(**kw)
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+        self.z_axis = SeriesAxis()
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/axis.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/axis.py
new file mode 100644
index 00000000..7e99416c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/axis.py
@@ -0,0 +1,401 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Float,
+    NoneSet,
+    Bool,
+    Integer,
+    MinMax,
+    NoneSet,
+    Set,
+    String,
+    Alias,
+)
+
+from openpyxl.descriptors.excel import (
+    ExtensionList,
+    Percentage,
+    _explicit_none,
+)
+from openpyxl.descriptors.nested import (
+    NestedValue,
+    NestedSet,
+    NestedBool,
+    NestedNoneSet,
+    NestedFloat,
+    NestedInteger,
+    NestedMinMax,
+)
+from openpyxl.xml.constants import CHART_NS
+
+from .descriptors import NumberFormatDescriptor
+from .layout import Layout
+from .text import Text, RichText
+from .shapes import GraphicalProperties
+from .title import Title, TitleDescriptor
+
+
+class ChartLines(Serialisable):
+
+    tagname = "chartLines"
+
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+
+    def __init__(self, spPr=None):
+        self.spPr = spPr
+
+
+class Scaling(Serialisable):
+
+    tagname = "scaling"
+
+    logBase = NestedFloat(allow_none=True)
+    orientation = NestedSet(values=(['maxMin', 'minMax']))
+    max = NestedFloat(allow_none=True)
+    min = NestedFloat(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('logBase', 'orientation', 'max', 'min',)
+
+    def __init__(self,
+                 logBase=None,
+                 orientation="minMax",
+                 max=None,
+                 min=None,
+                 extLst=None,
+                ):
+        self.logBase = logBase
+        self.orientation = orientation
+        self.max = max
+        self.min = min
+
+
+class _BaseAxis(Serialisable):
+
+    axId = NestedInteger(expected_type=int)
+    scaling = Typed(expected_type=Scaling)
+    delete = NestedBool(allow_none=True)
+    axPos = NestedSet(values=(['b', 'l', 'r', 't']))
+    majorGridlines = Typed(expected_type=ChartLines, allow_none=True)
+    minorGridlines = Typed(expected_type=ChartLines, allow_none=True)
+    title = TitleDescriptor()
+    numFmt = NumberFormatDescriptor()
+    number_format = Alias("numFmt")
+    majorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
+    minorTickMark = NestedNoneSet(values=(['cross', 'in', 'out']), to_tree=_explicit_none)
+    tickLblPos = NestedNoneSet(values=(['high', 'low', 'nextTo']))
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    textProperties = Alias('txPr')
+    crossAx = NestedInteger(expected_type=int) # references other axis
+    crosses = NestedNoneSet(values=(['autoZero', 'max', 'min']))
+    crossesAt = NestedFloat(allow_none=True)
+
+    # crosses & crossesAt are mutually exclusive
+
+    __elements__ = ('axId', 'scaling', 'delete', 'axPos', 'majorGridlines',
+                    'minorGridlines', 'title', 'numFmt', 'majorTickMark', 'minorTickMark',
+                    'tickLblPos', 'spPr', 'txPr', 'crossAx', 'crosses', 'crossesAt')
+
+    def __init__(self,
+                 axId=None,
+                 scaling=None,
+                 delete=None,
+                 axPos='l',
+                 majorGridlines=None,
+                 minorGridlines=None,
+                 title=None,
+                 numFmt=None,
+                 majorTickMark=None,
+                 minorTickMark=None,
+                 tickLblPos=None,
+                 spPr=None,
+                 txPr= None,
+                 crossAx=None,
+                 crosses=None,
+                 crossesAt=None,
+                ):
+        self.axId = axId
+        if scaling is None:
+            scaling = Scaling()
+        self.scaling = scaling
+        self.delete = delete
+        self.axPos = axPos
+        self.majorGridlines = majorGridlines
+        self.minorGridlines = minorGridlines
+        self.title = title
+        self.numFmt = numFmt
+        self.majorTickMark = majorTickMark
+        self.minorTickMark = minorTickMark
+        self.tickLblPos = tickLblPos
+        self.spPr = spPr
+        self.txPr = txPr
+        self.crossAx = crossAx
+        self.crosses = crosses
+        self.crossesAt = crossesAt
+
+
+class DisplayUnitsLabel(Serialisable):
+
+    tagname = "dispUnitsLbl"
+
+    layout = Typed(expected_type=Layout, allow_none=True)
+    tx = Typed(expected_type=Text, allow_none=True)
+    text = Alias("tx")
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias("spPr")
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    textPropertes = Alias("txPr")
+
+    __elements__ = ('layout', 'tx', 'spPr', 'txPr')
+
+    def __init__(self,
+                 layout=None,
+                 tx=None,
+                 spPr=None,
+                 txPr=None,
+                ):
+        self.layout = layout
+        self.tx = tx
+        self.spPr = spPr
+        self.txPr = txPr
+
+
+class DisplayUnitsLabelList(Serialisable):
+
+    tagname = "dispUnits"
+
+    custUnit = NestedFloat(allow_none=True)
+    builtInUnit = NestedNoneSet(values=(['hundreds', 'thousands',
+                                         'tenThousands', 'hundredThousands', 'millions', 'tenMillions',
+                                         'hundredMillions', 'billions', 'trillions']))
+    dispUnitsLbl = Typed(expected_type=DisplayUnitsLabel, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('custUnit', 'builtInUnit', 'dispUnitsLbl',)
+
+    def __init__(self,
+                 custUnit=None,
+                 builtInUnit=None,
+                 dispUnitsLbl=None,
+                 extLst=None,
+                ):
+        self.custUnit = custUnit
+        self.builtInUnit = builtInUnit
+        self.dispUnitsLbl = dispUnitsLbl
+
+
+class NumericAxis(_BaseAxis):
+
+    tagname = "valAx"
+
+    axId = _BaseAxis.axId
+    scaling = _BaseAxis.scaling
+    delete = _BaseAxis.delete
+    axPos = _BaseAxis.axPos
+    majorGridlines = _BaseAxis.majorGridlines
+    minorGridlines = _BaseAxis.minorGridlines
+    title = _BaseAxis.title
+    numFmt = _BaseAxis.numFmt
+    majorTickMark = _BaseAxis.majorTickMark
+    minorTickMark = _BaseAxis.minorTickMark
+    tickLblPos = _BaseAxis.tickLblPos
+    spPr = _BaseAxis.spPr
+    txPr = _BaseAxis.txPr
+    crossAx = _BaseAxis.crossAx
+    crosses = _BaseAxis.crosses
+    crossesAt = _BaseAxis.crossesAt
+
+    crossBetween = NestedNoneSet(values=(['between', 'midCat']))
+    majorUnit = NestedFloat(allow_none=True)
+    minorUnit = NestedFloat(allow_none=True)
+    dispUnits = Typed(expected_type=DisplayUnitsLabelList, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _BaseAxis.__elements__ + ('crossBetween', 'majorUnit',
+                                             'minorUnit', 'dispUnits',)
+
+
+    def __init__(self,
+                 crossBetween=None,
+                 majorUnit=None,
+                 minorUnit=None,
+                 dispUnits=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.crossBetween = crossBetween
+        self.majorUnit = majorUnit
+        self.minorUnit = minorUnit
+        self.dispUnits = dispUnits
+        kw.setdefault('majorGridlines', ChartLines())
+        kw.setdefault('axId', 100)
+        kw.setdefault('crossAx', 10)
+        super().__init__(**kw)
+
+
+    @classmethod
+    def from_tree(cls, node):
+        """
+        Special case value axes with no gridlines
+        """
+        self = super().from_tree(node)
+        gridlines = node.find("{%s}majorGridlines" % CHART_NS)
+        if gridlines is None:
+            self.majorGridlines = None
+        return self
+
+
+
+class TextAxis(_BaseAxis):
+
+    tagname = "catAx"
+
+    axId = _BaseAxis.axId
+    scaling = _BaseAxis.scaling
+    delete = _BaseAxis.delete
+    axPos = _BaseAxis.axPos
+    majorGridlines = _BaseAxis.majorGridlines
+    minorGridlines = _BaseAxis.minorGridlines
+    title = _BaseAxis.title
+    numFmt = _BaseAxis.numFmt
+    majorTickMark = _BaseAxis.majorTickMark
+    minorTickMark = _BaseAxis.minorTickMark
+    tickLblPos = _BaseAxis.tickLblPos
+    spPr = _BaseAxis.spPr
+    txPr = _BaseAxis.txPr
+    crossAx = _BaseAxis.crossAx
+    crosses = _BaseAxis.crosses
+    crossesAt = _BaseAxis.crossesAt
+
+    auto = NestedBool(allow_none=True)
+    lblAlgn = NestedNoneSet(values=(['ctr', 'l', 'r']))
+    lblOffset = NestedMinMax(min=0, max=1000)
+    tickLblSkip = NestedInteger(allow_none=True)
+    tickMarkSkip = NestedInteger(allow_none=True)
+    noMultiLvlLbl = NestedBool(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _BaseAxis.__elements__ + ('auto', 'lblAlgn', 'lblOffset',
+                                             'tickLblSkip', 'tickMarkSkip', 'noMultiLvlLbl')
+
+    def __init__(self,
+                 auto=None,
+                 lblAlgn=None,
+                 lblOffset=100,
+                 tickLblSkip=None,
+                 tickMarkSkip=None,
+                 noMultiLvlLbl=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.auto = auto
+        self.lblAlgn = lblAlgn
+        self.lblOffset = lblOffset
+        self.tickLblSkip = tickLblSkip
+        self.tickMarkSkip = tickMarkSkip
+        self.noMultiLvlLbl = noMultiLvlLbl
+        kw.setdefault('axId', 10)
+        kw.setdefault('crossAx', 100)
+        super().__init__(**kw)
+
+
+class DateAxis(TextAxis):
+
+    tagname = "dateAx"
+
+    axId = _BaseAxis.axId
+    scaling = _BaseAxis.scaling
+    delete = _BaseAxis.delete
+    axPos = _BaseAxis.axPos
+    majorGridlines = _BaseAxis.majorGridlines
+    minorGridlines = _BaseAxis.minorGridlines
+    title = _BaseAxis.title
+    numFmt = _BaseAxis.numFmt
+    majorTickMark = _BaseAxis.majorTickMark
+    minorTickMark = _BaseAxis.minorTickMark
+    tickLblPos = _BaseAxis.tickLblPos
+    spPr = _BaseAxis.spPr
+    txPr = _BaseAxis.txPr
+    crossAx = _BaseAxis.crossAx
+    crosses = _BaseAxis.crosses
+    crossesAt = _BaseAxis.crossesAt
+
+    auto = NestedBool(allow_none=True)
+    lblOffset = NestedInteger(allow_none=True)
+    baseTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
+    majorUnit = NestedFloat(allow_none=True)
+    majorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
+    minorUnit = NestedFloat(allow_none=True)
+    minorTimeUnit = NestedNoneSet(values=(['days', 'months', 'years']))
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _BaseAxis.__elements__ + ('auto', 'lblOffset',
+                                             'baseTimeUnit', 'majorUnit', 'majorTimeUnit', 'minorUnit',
+                                             'minorTimeUnit')
+
+    def __init__(self,
+                 auto=None,
+                 lblOffset=None,
+                 baseTimeUnit=None,
+                 majorUnit=None,
+                 majorTimeUnit=None,
+                 minorUnit=None,
+                 minorTimeUnit=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.auto = auto
+        self.lblOffset = lblOffset
+        self.baseTimeUnit = baseTimeUnit
+        self.majorUnit = majorUnit
+        self.majorTimeUnit = majorTimeUnit
+        self.minorUnit = minorUnit
+        self.minorTimeUnit = minorTimeUnit
+        kw.setdefault('axId', 500)
+        kw.setdefault('lblOffset', lblOffset)
+        super().__init__(**kw)
+
+
+class SeriesAxis(_BaseAxis):
+
+    tagname = "serAx"
+
+    axId = _BaseAxis.axId
+    scaling = _BaseAxis.scaling
+    delete = _BaseAxis.delete
+    axPos = _BaseAxis.axPos
+    majorGridlines = _BaseAxis.majorGridlines
+    minorGridlines = _BaseAxis.minorGridlines
+    title = _BaseAxis.title
+    numFmt = _BaseAxis.numFmt
+    majorTickMark = _BaseAxis.majorTickMark
+    minorTickMark = _BaseAxis.minorTickMark
+    tickLblPos = _BaseAxis.tickLblPos
+    spPr = _BaseAxis.spPr
+    txPr = _BaseAxis.txPr
+    crossAx = _BaseAxis.crossAx
+    crosses = _BaseAxis.crosses
+    crossesAt = _BaseAxis.crossesAt
+
+    tickLblSkip = NestedInteger(allow_none=True)
+    tickMarkSkip = NestedInteger(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _BaseAxis.__elements__ + ('tickLblSkip', 'tickMarkSkip')
+
+    def __init__(self,
+                 tickLblSkip=None,
+                 tickMarkSkip=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.tickLblSkip = tickLblSkip
+        self.tickMarkSkip = tickMarkSkip
+        kw.setdefault('axId', 1000)
+        kw.setdefault('crossAx', 10)
+        super().__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py
new file mode 100644
index 00000000..fa08e076
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/bar_chart.py
@@ -0,0 +1,144 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Bool,
+    Integer,
+    Sequence,
+    Alias,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedNoneSet,
+    NestedSet,
+    NestedBool,
+    NestedInteger,
+    NestedMinMax,
+)
+
+from .descriptors import (
+    NestedGapAmount,
+    NestedOverlap,
+)
+from ._chart import ChartBase
+from ._3d import _3DBase
+from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines
+from .shapes import GraphicalProperties
+from .series import Series
+from .legend import Legend
+from .label import DataLabelList
+
+
+class _BarChartBase(ChartBase):
+
+    barDir = NestedSet(values=(['bar', 'col']))
+    type = Alias("barDir")
+    grouping = NestedSet(values=(['percentStacked', 'clustered', 'standard',
+                                  'stacked']))
+    varyColors = NestedBool(nested=True, allow_none=True)
+    ser = Sequence(expected_type=Series, allow_none=True)
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    dataLabels = Alias("dLbls")
+
+    __elements__ = ('barDir', 'grouping', 'varyColors', 'ser', 'dLbls')
+
+    _series_type = "bar"
+
+    def __init__(self,
+                 barDir="col",
+                 grouping="clustered",
+                 varyColors=None,
+                 ser=(),
+                 dLbls=None,
+                 **kw
+                ):
+        self.barDir = barDir
+        self.grouping = grouping
+        self.varyColors = varyColors
+        self.ser = ser
+        self.dLbls = dLbls
+        super().__init__(**kw)
+
+
+class BarChart(_BarChartBase):
+
+    tagname = "barChart"
+
+    barDir = _BarChartBase.barDir
+    grouping = _BarChartBase.grouping
+    varyColors = _BarChartBase.varyColors
+    ser = _BarChartBase.ser
+    dLbls = _BarChartBase.dLbls
+
+    gapWidth = NestedGapAmount()
+    overlap = NestedOverlap()
+    serLines = Typed(expected_type=ChartLines, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    # chart properties actually used by containing classes
+    x_axis = Typed(expected_type=TextAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+
+    __elements__ = _BarChartBase.__elements__ + ('gapWidth', 'overlap', 'serLines', 'axId')
+
+    def __init__(self,
+                 gapWidth=150,
+                 overlap=None,
+                 serLines=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.gapWidth = gapWidth
+        self.overlap = overlap
+        self.serLines = serLines
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+        self.legend = Legend()
+        super().__init__(**kw)
+
+
+class BarChart3D(_BarChartBase, _3DBase):
+
+    tagname = "bar3DChart"
+
+    barDir = _BarChartBase.barDir
+    grouping = _BarChartBase.grouping
+    varyColors = _BarChartBase.varyColors
+    ser = _BarChartBase.ser
+    dLbls = _BarChartBase.dLbls
+
+    view3D = _3DBase.view3D
+    floor = _3DBase.floor
+    sideWall = _3DBase.sideWall
+    backWall = _3DBase.backWall
+
+    gapWidth = NestedGapAmount()
+    gapDepth = NestedGapAmount()
+    shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax']))
+    serLines = Typed(expected_type=ChartLines, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    x_axis = Typed(expected_type=TextAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+    z_axis = Typed(expected_type=SeriesAxis, allow_none=True)
+
+    __elements__ = _BarChartBase.__elements__ + ('gapWidth', 'gapDepth', 'shape', 'serLines', 'axId')
+
+    def __init__(self,
+                 gapWidth=150,
+                 gapDepth=150,
+                 shape=None,
+                 serLines=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.gapWidth = gapWidth
+        self.gapDepth = gapDepth
+        self.shape = shape
+        self.serLines = serLines
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+        self.z_axis = SeriesAxis()
+
+        super(BarChart3D, self).__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py
new file mode 100644
index 00000000..3fca043a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/bubble_chart.py
@@ -0,0 +1,67 @@
+#Autogenerated schema
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Set,
+    MinMax,
+    Bool,
+    Integer,
+    Alias,
+    Sequence,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedNoneSet,
+    NestedMinMax,
+    NestedBool,
+)
+
+from ._chart import ChartBase
+from .axis import TextAxis, NumericAxis
+from .series import XYSeries
+from .label import DataLabelList
+
+
+class BubbleChart(ChartBase):
+
+    tagname = "bubbleChart"
+
+    varyColors = NestedBool(allow_none=True)
+    ser = Sequence(expected_type=XYSeries, allow_none=True)
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    dataLabels = Alias("dLbls")
+    bubble3D = NestedBool(allow_none=True)
+    bubbleScale = NestedMinMax(min=0, max=300, allow_none=True)
+    showNegBubbles = NestedBool(allow_none=True)
+    sizeRepresents = NestedNoneSet(values=(['area', 'w']))
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    x_axis = Typed(expected_type=NumericAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+
+    _series_type = "bubble"
+
+    __elements__ = ('varyColors', 'ser', 'dLbls', 'bubble3D', 'bubbleScale',
+                    'showNegBubbles', 'sizeRepresents', 'axId')
+
+    def __init__(self,
+                 varyColors=None,
+                 ser=(),
+                 dLbls=None,
+                 bubble3D=None,
+                 bubbleScale=None,
+                 showNegBubbles=None,
+                 sizeRepresents=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.varyColors = varyColors
+        self.ser = ser
+        self.dLbls = dLbls
+        self.bubble3D = bubble3D
+        self.bubbleScale = bubbleScale
+        self.showNegBubbles = showNegBubbles
+        self.sizeRepresents = sizeRepresents
+        self.x_axis = NumericAxis(axId=10, crossAx=20)
+        self.y_axis = NumericAxis(axId=20, crossAx=10)
+        super().__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py
new file mode 100644
index 00000000..cba213c2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/chartspace.py
@@ -0,0 +1,195 @@
+
+# Copyright (c) 2010-2024 openpyxl
+
+"""
+Enclosing chart object. The various chart types are actually child objects.
+Will probably need to call this indirectly
+"""
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    String,
+    Alias,
+)
+from openpyxl.descriptors.excel import (
+    ExtensionList,
+    Relation
+)
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedNoneSet,
+    NestedString,
+    NestedMinMax,
+)
+from openpyxl.descriptors.sequence import NestedSequence
+from openpyxl.xml.constants import CHART_NS
+
+from openpyxl.drawing.colors import ColorMapping
+from .text import RichText
+from .shapes import GraphicalProperties
+from .legend import Legend
+from ._3d import _3DBase
+from .plotarea import PlotArea
+from .title import Title
+from .pivot import (
+    PivotFormat,
+    PivotSource,
+)
+from .print_settings import PrintSettings
+
+
+class ChartContainer(Serialisable):
+
+    tagname = "chart"
+
+    title = Typed(expected_type=Title, allow_none=True)
+    autoTitleDeleted = NestedBool(allow_none=True)
+    pivotFmts = NestedSequence(expected_type=PivotFormat)
+    view3D = _3DBase.view3D
+    floor = _3DBase.floor
+    sideWall = _3DBase.sideWall
+    backWall = _3DBase.backWall
+    plotArea = Typed(expected_type=PlotArea, )
+    legend = Typed(expected_type=Legend, allow_none=True)
+    plotVisOnly = NestedBool()
+    dispBlanksAs = NestedNoneSet(values=(['span', 'gap', 'zero']))
+    showDLblsOverMax = NestedBool(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('title', 'autoTitleDeleted', 'pivotFmts', 'view3D',
+                    'floor', 'sideWall', 'backWall', 'plotArea', 'legend', 'plotVisOnly',
+                    'dispBlanksAs', 'showDLblsOverMax')
+
+    def __init__(self,
+                 title=None,
+                 autoTitleDeleted=None,
+                 pivotFmts=(),
+                 view3D=None,
+                 floor=None,
+                 sideWall=None,
+                 backWall=None,
+                 plotArea=None,
+                 legend=None,
+                 plotVisOnly=True,
+                 dispBlanksAs="gap",
+                 showDLblsOverMax=None,
+                 extLst=None,
+                ):
+        self.title = title
+        self.autoTitleDeleted = autoTitleDeleted
+        self.pivotFmts = pivotFmts
+        self.view3D = view3D
+        self.floor = floor
+        self.sideWall = sideWall
+        self.backWall = backWall
+        if plotArea is None:
+            plotArea = PlotArea()
+        self.plotArea = plotArea
+        self.legend = legend
+        self.plotVisOnly = plotVisOnly
+        self.dispBlanksAs = dispBlanksAs
+        self.showDLblsOverMax = showDLblsOverMax
+
+
+class Protection(Serialisable):
+
+    tagname = "protection"
+
+    chartObject = NestedBool(allow_none=True)
+    data = NestedBool(allow_none=True)
+    formatting = NestedBool(allow_none=True)
+    selection = NestedBool(allow_none=True)
+    userInterface = NestedBool(allow_none=True)
+
+    __elements__ = ("chartObject", "data", "formatting", "selection", "userInterface")
+
+    def __init__(self,
+                 chartObject=None,
+                 data=None,
+                 formatting=None,
+                 selection=None,
+                 userInterface=None,
+                ):
+        self.chartObject = chartObject
+        self.data = data
+        self.formatting = formatting
+        self.selection = selection
+        self.userInterface = userInterface
+
+
+class ExternalData(Serialisable):
+
+    tagname = "externalData"
+
+    autoUpdate = NestedBool(allow_none=True)
+    id = String() # Needs namespace
+
+    def __init__(self,
+                 autoUpdate=None,
+                 id=None
+                ):
+        self.autoUpdate = autoUpdate
+        self.id = id
+
+
+class ChartSpace(Serialisable):
+
+    tagname = "chartSpace"
+
+    date1904 = NestedBool(allow_none=True)
+    lang = NestedString(allow_none=True)
+    roundedCorners = NestedBool(allow_none=True)
+    style = NestedMinMax(allow_none=True, min=1, max=48)
+    clrMapOvr = Typed(expected_type=ColorMapping, allow_none=True)
+    pivotSource = Typed(expected_type=PivotSource, allow_none=True)
+    protection = Typed(expected_type=Protection, allow_none=True)
+    chart = Typed(expected_type=ChartContainer)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphical_properties = Alias("spPr")
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    textProperties = Alias("txPr")
+    externalData = Typed(expected_type=ExternalData, allow_none=True)
+    printSettings = Typed(expected_type=PrintSettings, allow_none=True)
+    userShapes = Relation()
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('date1904', 'lang', 'roundedCorners', 'style',
+                    'clrMapOvr', 'pivotSource', 'protection', 'chart', 'spPr', 'txPr',
+                    'externalData', 'printSettings', 'userShapes')
+
+    def __init__(self,
+                 date1904=None,
+                 lang=None,
+                 roundedCorners=None,
+                 style=None,
+                 clrMapOvr=None,
+                 pivotSource=None,
+                 protection=None,
+                 chart=None,
+                 spPr=None,
+                 txPr=None,
+                 externalData=None,
+                 printSettings=None,
+                 userShapes=None,
+                 extLst=None,
+                ):
+        self.date1904 = date1904
+        self.lang = lang
+        self.roundedCorners = roundedCorners
+        self.style = style
+        self.clrMapOvr = clrMapOvr
+        self.pivotSource = pivotSource
+        self.protection = protection
+        self.chart = chart
+        self.spPr = spPr
+        self.txPr = txPr
+        self.externalData = externalData
+        self.printSettings = printSettings
+        self.userShapes = userShapes
+
+
+    def to_tree(self, tagname=None, idx=None, namespace=None):
+        tree = super().to_tree()
+        tree.set("xmlns", CHART_NS)
+        return tree
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py
new file mode 100644
index 00000000..c38eafb2
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/data_source.py
@@ -0,0 +1,246 @@
+"""
+Collection of utility primitives for charts.
+"""
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Bool,
+    Typed,
+    Alias,
+    String,
+    Integer,
+    Sequence,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedString,
+    NestedText,
+    NestedInteger,
+)
+
+
+class NumFmt(Serialisable):
+
+    formatCode = String()
+    sourceLinked = Bool()
+
+    def __init__(self,
+                 formatCode=None,
+                 sourceLinked=False
+                ):
+        self.formatCode = formatCode
+        self.sourceLinked = sourceLinked
+
+
+class NumberValueDescriptor(NestedText):
+    """
+    Data should be numerical but isn't always :-/
+    """
+
+    allow_none = True
+
+    def __set__(self, instance, value):
+        if value == "#N/A":
+            self.expected_type = str
+        else:
+            self.expected_type = float
+        super().__set__(instance, value)
+
+
+class NumVal(Serialisable):
+
+    idx = Integer()
+    formatCode = NestedText(allow_none=True, expected_type=str)
+    v = NumberValueDescriptor()
+
+    def __init__(self,
+                 idx=None,
+                 formatCode=None,
+                 v=None,
+                ):
+        self.idx = idx
+        self.formatCode = formatCode
+        self.v = v
+
+
+class NumData(Serialisable):
+
+    formatCode = NestedText(expected_type=str, allow_none=True)
+    ptCount = NestedInteger(allow_none=True)
+    pt = Sequence(expected_type=NumVal)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('formatCode', 'ptCount', 'pt')
+
+    def __init__(self,
+                 formatCode=None,
+                 ptCount=None,
+                 pt=(),
+                 extLst=None,
+                ):
+        self.formatCode = formatCode
+        self.ptCount = ptCount
+        self.pt = pt
+
+
+class NumRef(Serialisable):
+
+    f = NestedText(expected_type=str)
+    ref = Alias('f')
+    numCache = Typed(expected_type=NumData, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('f', 'numCache')
+
+    def __init__(self,
+                 f=None,
+                 numCache=None,
+                 extLst=None,
+                ):
+        self.f = f
+        self.numCache = numCache
+
+
+class StrVal(Serialisable):
+
+    tagname = "strVal"
+
+    idx = Integer()
+    v = NestedText(expected_type=str)
+
+    def __init__(self,
+                 idx=0,
+                 v=None,
+                ):
+        self.idx = idx
+        self.v = v
+
+
+class StrData(Serialisable):
+
+    tagname = "strData"
+
+    ptCount = NestedInteger(allow_none=True)
+    pt = Sequence(expected_type=StrVal)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('ptCount', 'pt')
+
+    def __init__(self,
+                 ptCount=None,
+                 pt=(),
+                 extLst=None,
+                ):
+        self.ptCount = ptCount
+        self.pt = pt
+
+
+class StrRef(Serialisable):
+
+    tagname = "strRef"
+
+    f = NestedText(expected_type=str, allow_none=True)
+    strCache = Typed(expected_type=StrData, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('f', 'strCache')
+
+    def __init__(self,
+                 f=None,
+                 strCache=None,
+                 extLst=None,
+                ):
+        self.f = f
+        self.strCache = strCache
+
+
+class NumDataSource(Serialisable):
+
+    numRef = Typed(expected_type=NumRef, allow_none=True)
+    numLit = Typed(expected_type=NumData, allow_none=True)
+
+
+    def __init__(self,
+                 numRef=None,
+                 numLit=None,
+                 ):
+        self.numRef = numRef
+        self.numLit = numLit
+
+
+class Level(Serialisable):
+
+    tagname = "lvl"
+
+    pt = Sequence(expected_type=StrVal)
+
+    __elements__ = ('pt',)
+
+    def __init__(self,
+                 pt=(),
+                ):
+        self.pt = pt
+
+
+class MultiLevelStrData(Serialisable):
+
+    tagname = "multiLvlStrData"
+
+    ptCount = Integer(allow_none=True)
+    lvl = Sequence(expected_type=Level)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('ptCount', 'lvl',)
+
+    def __init__(self,
+                 ptCount=None,
+                 lvl=(),
+                 extLst=None,
+                ):
+        self.ptCount = ptCount
+        self.lvl = lvl
+
+
+class MultiLevelStrRef(Serialisable):
+
+    tagname = "multiLvlStrRef"
+
+    f = NestedText(expected_type=str)
+    multiLvlStrCache = Typed(expected_type=MultiLevelStrData, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('multiLvlStrCache', 'f')
+
+    def __init__(self,
+                 f=None,
+                 multiLvlStrCache=None,
+                 extLst=None,
+                ):
+        self.f = f
+        self.multiLvlStrCache = multiLvlStrCache
+
+
+class AxDataSource(Serialisable):
+
+    tagname = "cat"
+
+    numRef = Typed(expected_type=NumRef, allow_none=True)
+    numLit = Typed(expected_type=NumData, allow_none=True)
+    strRef = Typed(expected_type=StrRef, allow_none=True)
+    strLit = Typed(expected_type=StrData, allow_none=True)
+    multiLvlStrRef = Typed(expected_type=MultiLevelStrRef, allow_none=True)
+
+    def __init__(self,
+                 numRef=None,
+                 numLit=None,
+                 strRef=None,
+                 strLit=None,
+                 multiLvlStrRef=None,
+                 ):
+        if not any([numLit, numRef, strRef, strLit, multiLvlStrRef]):
+            raise TypeError("A data source must be provided")
+        self.numRef = numRef
+        self.numLit = numLit
+        self.strRef = strRef
+        self.strLit = strLit
+        self.multiLvlStrRef = multiLvlStrRef
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py
new file mode 100644
index 00000000..6bc94348
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/descriptors.py
@@ -0,0 +1,43 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+
+from openpyxl.descriptors.nested import (
+    NestedMinMax
+    )
+
+from openpyxl.descriptors import Typed
+
+from .data_source import NumFmt
+
+"""
+Utility descriptors for the chart module.
+For convenience but also clarity.
+"""
+
+class NestedGapAmount(NestedMinMax):
+
+    allow_none = True
+    min = 0
+    max = 500
+
+
+class NestedOverlap(NestedMinMax):
+
+    allow_none = True
+    min = -100
+    max = 100
+
+
+class NumberFormatDescriptor(Typed):
+    """
+    Allow direct assignment of format code
+    """
+
+    expected_type = NumFmt
+    allow_none = True
+
+    def __set__(self, instance, value):
+        if isinstance(value, str):
+            value = NumFmt(value)
+        super().__set__(instance, value)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py
new file mode 100644
index 00000000..6ae24451
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/error_bar.py
@@ -0,0 +1,62 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Float,
+    Set,
+    Alias
+)
+
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedNoneSet,
+    NestedSet,
+    NestedBool,
+    NestedFloat,
+)
+
+from .data_source import NumDataSource
+from .shapes import GraphicalProperties
+
+
+class ErrorBars(Serialisable):
+
+    tagname = "errBars"
+
+    errDir = NestedNoneSet(values=(['x', 'y']))
+    direction = Alias("errDir")
+    errBarType = NestedSet(values=(['both', 'minus', 'plus']))
+    style = Alias("errBarType")
+    errValType = NestedSet(values=(['cust', 'fixedVal', 'percentage', 'stdDev', 'stdErr']))
+    size = Alias("errValType")
+    noEndCap = NestedBool(nested=True, allow_none=True)
+    plus = Typed(expected_type=NumDataSource, allow_none=True)
+    minus = Typed(expected_type=NumDataSource, allow_none=True)
+    val = NestedFloat(allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias("spPr")
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('errDir','errBarType', 'errValType', 'noEndCap','minus', 'plus', 'val', 'spPr')
+
+
+    def __init__(self,
+                 errDir=None,
+                 errBarType="both",
+                 errValType="fixedVal",
+                 noEndCap=None,
+                 plus=None,
+                 minus=None,
+                 val=None,
+                 spPr=None,
+                 extLst=None,
+                ):
+        self.errDir = errDir
+        self.errBarType = errBarType
+        self.errValType = errValType
+        self.noEndCap = noEndCap
+        self.plus = plus
+        self.minus = minus
+        self.val = val
+        self.spPr = spPr
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/label.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/label.py
new file mode 100644
index 00000000..d6eacb16
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/label.py
@@ -0,0 +1,127 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Sequence,
+    Alias,
+    Typed
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedNoneSet,
+    NestedBool,
+    NestedString,
+    NestedInteger,
+    )
+
+from .shapes import GraphicalProperties
+from .text import RichText
+
+
+class _DataLabelBase(Serialisable):
+
+    numFmt = NestedString(allow_none=True, attribute="formatCode")
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    textProperties = Alias('txPr')
+    dLblPos = NestedNoneSet(values=['bestFit', 'b', 'ctr', 'inBase', 'inEnd',
+                                    'l', 'outEnd', 'r', 't'])
+    position = Alias('dLblPos')
+    showLegendKey = NestedBool(allow_none=True)
+    showVal = NestedBool(allow_none=True)
+    showCatName = NestedBool(allow_none=True)
+    showSerName = NestedBool(allow_none=True)
+    showPercent = NestedBool(allow_none=True)
+    showBubbleSize = NestedBool(allow_none=True)
+    showLeaderLines = NestedBool(allow_none=True)
+    separator = NestedString(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ("numFmt", "spPr", "txPr", "dLblPos", "showLegendKey",
+                    "showVal", "showCatName", "showSerName", "showPercent", "showBubbleSize",
+                    "showLeaderLines", "separator")
+
+    def __init__(self,
+                 numFmt=None,
+                 spPr=None,
+                 txPr=None,
+                 dLblPos=None,
+                 showLegendKey=None,
+                 showVal=None,
+                 showCatName=None,
+                 showSerName=None,
+                 showPercent=None,
+                 showBubbleSize=None,
+                 showLeaderLines=None,
+                 separator=None,
+                 extLst=None,
+                 ):
+        self.numFmt = numFmt
+        self.spPr = spPr
+        self.txPr = txPr
+        self.dLblPos = dLblPos
+        self.showLegendKey = showLegendKey
+        self.showVal = showVal
+        self.showCatName = showCatName
+        self.showSerName = showSerName
+        self.showPercent = showPercent
+        self.showBubbleSize = showBubbleSize
+        self.showLeaderLines = showLeaderLines
+        self.separator = separator
+
+
+class DataLabel(_DataLabelBase):
+
+    tagname = "dLbl"
+
+    idx = NestedInteger()
+
+    numFmt = _DataLabelBase.numFmt
+    spPr = _DataLabelBase.spPr
+    txPr = _DataLabelBase.txPr
+    dLblPos = _DataLabelBase.dLblPos
+    showLegendKey = _DataLabelBase.showLegendKey
+    showVal = _DataLabelBase.showVal
+    showCatName = _DataLabelBase.showCatName
+    showSerName = _DataLabelBase.showSerName
+    showPercent = _DataLabelBase.showPercent
+    showBubbleSize = _DataLabelBase.showBubbleSize
+    showLeaderLines = _DataLabelBase.showLeaderLines
+    separator = _DataLabelBase.separator
+    extLst = _DataLabelBase.extLst
+
+    __elements__ = ("idx",)  + _DataLabelBase.__elements__
+
+    def __init__(self, idx=0, **kw ):
+        self.idx = idx
+        super().__init__(**kw)
+
+
+class DataLabelList(_DataLabelBase):
+
+    tagname = "dLbls"
+
+    dLbl = Sequence(expected_type=DataLabel, allow_none=True)
+
+    delete = NestedBool(allow_none=True)
+    numFmt = _DataLabelBase.numFmt
+    spPr = _DataLabelBase.spPr
+    txPr = _DataLabelBase.txPr
+    dLblPos = _DataLabelBase.dLblPos
+    showLegendKey = _DataLabelBase.showLegendKey
+    showVal = _DataLabelBase.showVal
+    showCatName = _DataLabelBase.showCatName
+    showSerName = _DataLabelBase.showSerName
+    showPercent = _DataLabelBase.showPercent
+    showBubbleSize = _DataLabelBase.showBubbleSize
+    showLeaderLines = _DataLabelBase.showLeaderLines
+    separator = _DataLabelBase.separator
+    extLst = _DataLabelBase.extLst
+
+    __elements__ = ("delete", "dLbl",) + _DataLabelBase.__elements__
+
+    def __init__(self, dLbl=(), delete=None,  **kw):
+        self.dLbl = dLbl
+        self.delete = delete
+        super().__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/layout.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/layout.py
new file mode 100644
index 00000000..f2f65530
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/layout.py
@@ -0,0 +1,74 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    NoneSet,
+    Float,
+    Typed,
+    Alias,
+)
+
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedNoneSet,
+    NestedSet,
+    NestedMinMax,
+)
+
+class ManualLayout(Serialisable):
+
+    tagname = "manualLayout"
+
+    layoutTarget = NestedNoneSet(values=(['inner', 'outer']))
+    xMode = NestedNoneSet(values=(['edge', 'factor']))
+    yMode = NestedNoneSet(values=(['edge', 'factor']))
+    wMode = NestedSet(values=(['edge', 'factor']))
+    hMode = NestedSet(values=(['edge', 'factor']))
+    x = NestedMinMax(min=-1, max=1, allow_none=True)
+    y = NestedMinMax(min=-1, max=1, allow_none=True)
+    w = NestedMinMax(min=0, max=1, allow_none=True)
+    width = Alias('w')
+    h = NestedMinMax(min=0, max=1,  allow_none=True)
+    height = Alias('h')
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('layoutTarget', 'xMode', 'yMode', 'wMode', 'hMode', 'x',
+                    'y', 'w', 'h')
+
+    def __init__(self,
+                 layoutTarget=None,
+                 xMode=None,
+                 yMode=None,
+                 wMode="factor",
+                 hMode="factor",
+                 x=None,
+                 y=None,
+                 w=None,
+                 h=None,
+                 extLst=None,
+                ):
+        self.layoutTarget = layoutTarget
+        self.xMode = xMode
+        self.yMode = yMode
+        self.wMode = wMode
+        self.hMode = hMode
+        self.x = x
+        self.y = y
+        self.w = w
+        self.h = h
+
+
+class Layout(Serialisable):
+
+    tagname = "layout"
+
+    manualLayout = Typed(expected_type=ManualLayout, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('manualLayout',)
+
+    def __init__(self,
+                 manualLayout=None,
+                 extLst=None,
+                ):
+        self.manualLayout = manualLayout
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/legend.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/legend.py
new file mode 100644
index 00000000..1f7c802b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/legend.py
@@ -0,0 +1,75 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Integer,
+    Alias,
+    Sequence,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedSet,
+    NestedInteger
+)
+
+from .layout import Layout
+from .shapes import GraphicalProperties
+from .text import RichText
+
+
+class LegendEntry(Serialisable):
+
+    tagname = "legendEntry"
+
+    idx = NestedInteger()
+    delete = NestedBool()
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('idx', 'delete', 'txPr')
+
+    def __init__(self,
+                 idx=0,
+                 delete=False,
+                 txPr=None,
+                 extLst=None,
+                ):
+        self.idx = idx
+        self.delete = delete
+        self.txPr = txPr
+
+
+class Legend(Serialisable):
+
+    tagname = "legend"
+
+    legendPos = NestedSet(values=(['b', 'tr', 'l', 'r', 't']))
+    position = Alias('legendPos')
+    legendEntry = Sequence(expected_type=LegendEntry)
+    layout = Typed(expected_type=Layout, allow_none=True)
+    overlay = NestedBool(allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    textProperties = Alias('txPr')
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('legendPos', 'legendEntry', 'layout', 'overlay', 'spPr', 'txPr',)
+
+    def __init__(self,
+                 legendPos="r",
+                 legendEntry=(),
+                 layout=None,
+                 overlay=None,
+                 spPr=None,
+                 txPr=None,
+                 extLst=None,
+                ):
+        self.legendPos = legendPos
+        self.legendEntry = legendEntry
+        self.layout = layout
+        self.overlay = overlay
+        self.spPr = spPr
+        self.txPr = txPr
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py
new file mode 100644
index 00000000..0aa3ad5b
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/line_chart.py
@@ -0,0 +1,129 @@
+#Autogenerated schema
+from openpyxl.descriptors import (
+    Typed,
+    Sequence,
+    Alias,
+    )
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedSet,
+    NestedBool,
+)
+
+from ._chart import ChartBase
+from .updown_bars import UpDownBars
+from .descriptors import NestedGapAmount
+from .axis import TextAxis, NumericAxis, SeriesAxis, ChartLines, _BaseAxis
+from .label import DataLabelList
+from .series import Series
+
+
+class _LineChartBase(ChartBase):
+
+    grouping = NestedSet(values=(['percentStacked', 'standard', 'stacked']))
+    varyColors = NestedBool(allow_none=True)
+    ser = Sequence(expected_type=Series, allow_none=True)
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    dataLabels = Alias("dLbls")
+    dropLines = Typed(expected_type=ChartLines, allow_none=True)
+
+    _series_type = "line"
+
+    __elements__ = ('grouping', 'varyColors', 'ser', 'dLbls', 'dropLines')
+
+    def __init__(self,
+                 grouping="standard",
+                 varyColors=None,
+                 ser=(),
+                 dLbls=None,
+                 dropLines=None,
+                 **kw
+                ):
+        self.grouping = grouping
+        self.varyColors = varyColors
+        self.ser = ser
+        self.dLbls = dLbls
+        self.dropLines = dropLines
+        super().__init__(**kw)
+
+
+class LineChart(_LineChartBase):
+
+    tagname = "lineChart"
+
+    grouping = _LineChartBase.grouping
+    varyColors = _LineChartBase.varyColors
+    ser = _LineChartBase.ser
+    dLbls = _LineChartBase.dLbls
+    dropLines =_LineChartBase.dropLines
+
+    hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
+    upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
+    marker = NestedBool(allow_none=True)
+    smooth = NestedBool(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    x_axis = Typed(expected_type=_BaseAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+
+    __elements__ = _LineChartBase.__elements__ + ('hiLowLines', 'upDownBars', 'marker', 'smooth', 'axId')
+
+    def __init__(self,
+                 hiLowLines=None,
+                 upDownBars=None,
+                 marker=None,
+                 smooth=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.hiLowLines = hiLowLines
+        self.upDownBars = upDownBars
+        self.marker = marker
+        self.smooth = smooth
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+
+        super().__init__(**kw)
+
+
+class LineChart3D(_LineChartBase):
+
+    tagname = "line3DChart"
+
+    grouping = _LineChartBase.grouping
+    varyColors = _LineChartBase.varyColors
+    ser = _LineChartBase.ser
+    dLbls = _LineChartBase.dLbls
+    dropLines =_LineChartBase.dropLines
+
+    gapDepth = NestedGapAmount()
+    hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
+    upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
+    marker = NestedBool(allow_none=True)
+    smooth = NestedBool(allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    x_axis = Typed(expected_type=TextAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+    z_axis = Typed(expected_type=SeriesAxis)
+
+    __elements__ = _LineChartBase.__elements__ + ('gapDepth', 'hiLowLines',
+                                                  'upDownBars', 'marker', 'smooth', 'axId')
+
+    def __init__(self,
+                 gapDepth=None,
+                 hiLowLines=None,
+                 upDownBars=None,
+                 marker=None,
+                 smooth=None,
+                 **kw
+                ):
+        self.gapDepth = gapDepth
+        self.hiLowLines = hiLowLines
+        self.upDownBars = upDownBars
+        self.marker = marker
+        self.smooth = smooth
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+        self.z_axis = SeriesAxis()
+        super(LineChart3D, self).__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/marker.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/marker.py
new file mode 100644
index 00000000..61e2641d
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/marker.py
@@ -0,0 +1,90 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Alias,
+)
+
+from openpyxl.descriptors.excel import(
+    ExtensionList,
+    _explicit_none,
+)
+
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedInteger,
+    NestedMinMax,
+    NestedNoneSet,
+)
+
+from .layout import Layout
+from .picture import PictureOptions
+from .shapes import *
+from .text import *
+from .error_bar import *
+
+
+class Marker(Serialisable):
+
+    tagname = "marker"
+
+    symbol = NestedNoneSet(values=(['circle', 'dash', 'diamond', 'dot', 'picture',
+                              'plus', 'square', 'star', 'triangle', 'x', 'auto']),
+                           to_tree=_explicit_none)
+    size = NestedMinMax(min=2, max=72, allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('symbol', 'size', 'spPr')
+
+    def __init__(self,
+                 symbol=None,
+                 size=None,
+                 spPr=None,
+                 extLst=None,
+                ):
+        self.symbol = symbol
+        self.size = size
+        if spPr is None:
+            spPr = GraphicalProperties()
+        self.spPr = spPr
+
+
+class DataPoint(Serialisable):
+
+    tagname = "dPt"
+
+    idx = NestedInteger()
+    invertIfNegative = NestedBool(allow_none=True)
+    marker = Typed(expected_type=Marker, allow_none=True)
+    bubble3D = NestedBool(allow_none=True)
+    explosion = NestedInteger(allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('idx', 'invertIfNegative', 'marker', 'bubble3D',
+                    'explosion', 'spPr', 'pictureOptions')
+
+    def __init__(self,
+                 idx=None,
+                 invertIfNegative=None,
+                 marker=None,
+                 bubble3D=None,
+                 explosion=None,
+                 spPr=None,
+                 pictureOptions=None,
+                 extLst=None,
+                ):
+        self.idx = idx
+        self.invertIfNegative = invertIfNegative
+        self.marker = marker
+        self.bubble3D = bubble3D
+        self.explosion = explosion
+        if spPr is None:
+            spPr = GraphicalProperties()
+        self.spPr = spPr
+        self.pictureOptions = pictureOptions
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/picture.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/picture.py
new file mode 100644
index 00000000..8c917d8c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/picture.py
@@ -0,0 +1,35 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedFloat,
+    NestedMinMax,
+    NestedNoneSet,
+)
+
+class PictureOptions(Serialisable):
+
+    tagname = "pictureOptions"
+
+    applyToFront = NestedBool(allow_none=True, nested=True)
+    applyToSides = NestedBool(allow_none=True, nested=True)
+    applyToEnd = NestedBool(allow_none=True, nested=True)
+    pictureFormat = NestedNoneSet(values=(['stretch', 'stack', 'stackScale']), nested=True)
+    pictureStackUnit = NestedFloat(allow_none=True, nested=True)
+
+    __elements__ = ('applyToFront', 'applyToSides', 'applyToEnd', 'pictureFormat', 'pictureStackUnit')
+
+    def __init__(self,
+                 applyToFront=None,
+                 applyToSides=None,
+                 applyToEnd=None,
+                 pictureFormat=None,
+                 pictureStackUnit=None,
+                ):
+        self.applyToFront = applyToFront
+        self.applyToSides = applyToSides
+        self.applyToEnd = applyToEnd
+        self.pictureFormat = pictureFormat
+        self.pictureStackUnit = pictureStackUnit
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py
new file mode 100644
index 00000000..6bb67e1e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/pie_chart.py
@@ -0,0 +1,177 @@
+#Autogenerated schema
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Bool,
+    MinMax,
+    Integer,
+    NoneSet,
+    Float,
+    Alias,
+    Sequence,
+)
+from openpyxl.descriptors.excel import ExtensionList, Percentage
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedMinMax,
+    NestedInteger,
+    NestedFloat,
+    NestedNoneSet,
+    NestedSet,
+)
+from openpyxl.descriptors.sequence import ValueSequence
+
+from ._chart import ChartBase
+from .axis import ChartLines
+from .descriptors import NestedGapAmount
+from .series import Series
+from .label import DataLabelList
+
+
+class _PieChartBase(ChartBase):
+
+    varyColors = NestedBool(allow_none=True)
+    ser = Sequence(expected_type=Series, allow_none=True)
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    dataLabels = Alias("dLbls")
+
+    _series_type = "pie"
+
+    __elements__ = ('varyColors', 'ser', 'dLbls')
+
+    def __init__(self,
+                 varyColors=True,
+                 ser=(),
+                 dLbls=None,
+                ):
+        self.varyColors = varyColors
+        self.ser = ser
+        self.dLbls = dLbls
+        super().__init__()
+
+
+
+class PieChart(_PieChartBase):
+
+    tagname = "pieChart"
+
+    varyColors = _PieChartBase.varyColors
+    ser = _PieChartBase.ser
+    dLbls = _PieChartBase.dLbls
+
+    firstSliceAng = NestedMinMax(min=0, max=360)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _PieChartBase.__elements__ + ('firstSliceAng', )
+
+    def __init__(self,
+                 firstSliceAng=0,
+                 extLst=None,
+                 **kw
+                ):
+        self.firstSliceAng = firstSliceAng
+        super().__init__(**kw)
+
+
+class PieChart3D(_PieChartBase):
+
+    tagname = "pie3DChart"
+
+    varyColors = _PieChartBase.varyColors
+    ser = _PieChartBase.ser
+    dLbls = _PieChartBase.dLbls
+
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _PieChartBase.__elements__
+
+
+class DoughnutChart(_PieChartBase):
+
+    tagname = "doughnutChart"
+
+    varyColors = _PieChartBase.varyColors
+    ser = _PieChartBase.ser
+    dLbls = _PieChartBase.dLbls
+
+    firstSliceAng = NestedMinMax(min=0, max=360)
+    holeSize = NestedMinMax(min=1, max=90, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _PieChartBase.__elements__ + ('firstSliceAng', 'holeSize')
+
+    def __init__(self,
+                 firstSliceAng=0,
+                 holeSize=10,
+                 extLst=None,
+                 **kw
+                ):
+        self.firstSliceAng = firstSliceAng
+        self.holeSize = holeSize
+        super().__init__(**kw)
+
+
+class CustomSplit(Serialisable):
+
+    tagname = "custSplit"
+
+    secondPiePt = ValueSequence(expected_type=int)
+
+    __elements__ = ('secondPiePt',)
+
+    def __init__(self,
+                 secondPiePt=(),
+                ):
+        self.secondPiePt = secondPiePt
+
+
+class ProjectedPieChart(_PieChartBase):
+
+    """
+    From the spec 21.2.2.126
+
+    This element contains the pie of pie or bar of pie series on this
+    chart. Only the first series shall be displayed. The splitType element
+    shall determine whether the splitPos and custSplit elements apply.
+    """
+
+    tagname = "ofPieChart"
+
+    varyColors = _PieChartBase.varyColors
+    ser = _PieChartBase.ser
+    dLbls = _PieChartBase.dLbls
+
+    ofPieType = NestedSet(values=(['pie', 'bar']))
+    type = Alias('ofPieType')
+    gapWidth = NestedGapAmount()
+    splitType = NestedNoneSet(values=(['auto', 'cust', 'percent', 'pos', 'val']))
+    splitPos = NestedFloat(allow_none=True)
+    custSplit = Typed(expected_type=CustomSplit, allow_none=True)
+    secondPieSize = NestedMinMax(min=5, max=200, allow_none=True)
+    serLines = Typed(expected_type=ChartLines, allow_none=True)
+    join_lines = Alias('serLines')
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = _PieChartBase.__elements__ + ('ofPieType', 'gapWidth',
+                                                 'splitType', 'splitPos', 'custSplit', 'secondPieSize', 'serLines')
+
+    def __init__(self,
+                 ofPieType="pie",
+                 gapWidth=None,
+                 splitType="auto",
+                 splitPos=None,
+                 custSplit=None,
+                 secondPieSize=75,
+                 serLines=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.ofPieType = ofPieType
+        self.gapWidth = gapWidth
+        self.splitType = splitType
+        self.splitPos = splitPos
+        self.custSplit = custSplit
+        self.secondPieSize = secondPieSize
+        if serLines is None:
+            self.serLines = ChartLines()
+        super().__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py
new file mode 100644
index 00000000..937fd294
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/pivot.py
@@ -0,0 +1,65 @@
+
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Alias,
+    Typed,
+)
+from openpyxl.descriptors.nested import NestedInteger, NestedText
+from openpyxl.descriptors.excel import ExtensionList
+
+from .label import DataLabel
+from .marker import Marker
+from .shapes import GraphicalProperties
+from .text import RichText
+
+
+class PivotSource(Serialisable):
+
+    tagname = "pivotSource"
+
+    name = NestedText(expected_type=str)
+    fmtId = NestedInteger(expected_type=int)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('name', 'fmtId')
+
+    def __init__(self,
+                 name=None,
+                 fmtId=None,
+                 extLst=None,
+                ):
+        self.name = name
+        self.fmtId = fmtId
+
+
+class PivotFormat(Serialisable):
+
+    tagname = "pivotFmt"
+
+    idx = NestedInteger(nested=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias("spPr")
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    TextBody = Alias("txPr")
+    marker = Typed(expected_type=Marker, allow_none=True)
+    dLbl = Typed(expected_type=DataLabel, allow_none=True)
+    DataLabel = Alias("dLbl")
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('idx', 'spPr', 'txPr', 'marker', 'dLbl')
+
+    def __init__(self,
+                 idx=0,
+                 spPr=None,
+                 txPr=None,
+                 marker=None,
+                 dLbl=None,
+                 extLst=None,
+                ):
+        self.idx = idx
+        self.spPr = spPr
+        self.txPr = txPr
+        self.marker = marker
+        self.dLbl = dLbl
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py
new file mode 100644
index 00000000..268bfbc4
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/plotarea.py
@@ -0,0 +1,162 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Alias,
+)
+from openpyxl.descriptors.excel import (
+    ExtensionList,
+)
+from openpyxl.descriptors.sequence import (
+    MultiSequence,
+    MultiSequencePart,
+)
+from openpyxl.descriptors.nested import (
+    NestedBool,
+)
+
+from ._3d import _3DBase
+from .area_chart import AreaChart, AreaChart3D
+from .bar_chart import BarChart, BarChart3D
+from .bubble_chart import BubbleChart
+from .line_chart import LineChart, LineChart3D
+from .pie_chart import PieChart, PieChart3D, ProjectedPieChart, DoughnutChart
+from .radar_chart import RadarChart
+from .scatter_chart import ScatterChart
+from .stock_chart import StockChart
+from .surface_chart import SurfaceChart, SurfaceChart3D
+from .layout import Layout
+from .shapes import GraphicalProperties
+from .text import RichText
+
+from .axis import (
+    NumericAxis,
+    TextAxis,
+    SeriesAxis,
+    DateAxis,
+)
+
+
+class DataTable(Serialisable):
+
+    tagname = "dTable"
+
+    showHorzBorder = NestedBool(allow_none=True)
+    showVertBorder = NestedBool(allow_none=True)
+    showOutline = NestedBool(allow_none=True)
+    showKeys = NestedBool(allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('showHorzBorder', 'showVertBorder', 'showOutline',
+                    'showKeys', 'spPr', 'txPr')
+
+    def __init__(self,
+                 showHorzBorder=None,
+                 showVertBorder=None,
+                 showOutline=None,
+                 showKeys=None,
+                 spPr=None,
+                 txPr=None,
+                 extLst=None,
+                ):
+        self.showHorzBorder = showHorzBorder
+        self.showVertBorder = showVertBorder
+        self.showOutline = showOutline
+        self.showKeys = showKeys
+        self.spPr = spPr
+        self.txPr = txPr
+
+
+class PlotArea(Serialisable):
+
+    tagname = "plotArea"
+
+    layout = Typed(expected_type=Layout, allow_none=True)
+    dTable = Typed(expected_type=DataTable, allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias("spPr")
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    # at least one chart
+    _charts = MultiSequence()
+    areaChart = MultiSequencePart(expected_type=AreaChart, store="_charts")
+    area3DChart = MultiSequencePart(expected_type=AreaChart3D, store="_charts")
+    lineChart = MultiSequencePart(expected_type=LineChart, store="_charts")
+    line3DChart = MultiSequencePart(expected_type=LineChart3D, store="_charts")
+    stockChart = MultiSequencePart(expected_type=StockChart, store="_charts")
+    radarChart = MultiSequencePart(expected_type=RadarChart, store="_charts")
+    scatterChart = MultiSequencePart(expected_type=ScatterChart, store="_charts")
+    pieChart = MultiSequencePart(expected_type=PieChart, store="_charts")
+    pie3DChart = MultiSequencePart(expected_type=PieChart3D, store="_charts")
+    doughnutChart = MultiSequencePart(expected_type=DoughnutChart, store="_charts")
+    barChart = MultiSequencePart(expected_type=BarChart, store="_charts")
+    bar3DChart = MultiSequencePart(expected_type=BarChart3D, store="_charts")
+    ofPieChart = MultiSequencePart(expected_type=ProjectedPieChart, store="_charts")
+    surfaceChart = MultiSequencePart(expected_type=SurfaceChart, store="_charts")
+    surface3DChart = MultiSequencePart(expected_type=SurfaceChart3D, store="_charts")
+    bubbleChart = MultiSequencePart(expected_type=BubbleChart, store="_charts")
+
+    # axes
+    _axes = MultiSequence()
+    valAx = MultiSequencePart(expected_type=NumericAxis, store="_axes")
+    catAx = MultiSequencePart(expected_type=TextAxis, store="_axes")
+    dateAx = MultiSequencePart(expected_type=DateAxis, store="_axes")
+    serAx = MultiSequencePart(expected_type=SeriesAxis, store="_axes")
+
+    __elements__ = ('layout', '_charts', '_axes', 'dTable', 'spPr')
+
+    def __init__(self,
+                 layout=None,
+                 dTable=None,
+                 spPr=None,
+                 _charts=(),
+                 _axes=(),
+                 extLst=None,
+                ):
+        self.layout = layout
+        self.dTable = dTable
+        self.spPr = spPr
+        self._charts = _charts
+        self._axes = _axes
+
+
+    def to_tree(self, tagname=None, idx=None, namespace=None):
+        axIds = {ax.axId for ax in self._axes}
+        for chart in self._charts:
+            for id, axis in chart._axes.items():
+                if id not in axIds:
+                    setattr(self, axis.tagname, axis)
+                    axIds.add(id)
+
+        return super().to_tree(tagname)
+
+
+    @classmethod
+    def from_tree(cls, node):
+        self = super().from_tree(node)
+        axes = dict((axis.axId, axis) for axis in self._axes)
+        for chart in self._charts:
+            if isinstance(chart, (ScatterChart, BubbleChart)):
+                x, y = (axes[axId] for axId in chart.axId)
+                chart.x_axis = x
+                chart.y_axis = y
+                continue
+
+            for axId in chart.axId:
+                axis = axes.get(axId)
+                if axis is None and isinstance(chart, _3DBase):
+                    # Series Axis can be optional
+                    chart.z_axis = None
+                    continue
+                if axis.tagname in ("catAx", "dateAx"):
+                    chart.x_axis = axis
+                elif axis.tagname == "valAx":
+                    chart.y_axis = axis
+                elif axis.tagname == "serAx":
+                    chart.z_axis = axis
+
+        return self
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py
new file mode 100644
index 00000000..65137310
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/print_settings.py
@@ -0,0 +1,57 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Float,
+    Typed,
+    Alias,
+)
+
+from openpyxl.worksheet.page import PrintPageSetup
+from openpyxl.worksheet.header_footer import HeaderFooter
+
+
+class PageMargins(Serialisable):
+    """
+    Identical to openpyxl.worksheet.page.Pagemargins but element names are different :-/
+    """
+    tagname = "pageMargins"
+
+    l = Float()
+    left = Alias('l')
+    r = Float()
+    right = Alias('r')
+    t = Float()
+    top = Alias('t')
+    b = Float()
+    bottom = Alias('b')
+    header = Float()
+    footer = Float()
+
+    def __init__(self, l=0.75, r=0.75, t=1, b=1, header=0.5, footer=0.5):
+        self.l = l
+        self.r = r
+        self.t = t
+        self.b = b
+        self.header = header
+        self.footer = footer
+
+
+class PrintSettings(Serialisable):
+
+    tagname = "printSettings"
+
+    headerFooter = Typed(expected_type=HeaderFooter, allow_none=True)
+    pageMargins = Typed(expected_type=PageMargins, allow_none=True)
+    pageSetup = Typed(expected_type=PrintPageSetup, allow_none=True)
+
+    __elements__ = ("headerFooter", "pageMargins", "pageMargins")
+
+    def __init__(self,
+                 headerFooter=None,
+                 pageMargins=None,
+                 pageSetup=None,
+                ):
+        self.headerFooter = headerFooter
+        self.pageMargins = pageMargins
+        self.pageSetup = pageSetup
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py
new file mode 100644
index 00000000..fa3aa0da
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/radar_chart.py
@@ -0,0 +1,55 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Sequence,
+    Typed,
+    Alias,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedInteger,
+    NestedSet
+)
+
+from ._chart import ChartBase
+from .axis import TextAxis, NumericAxis
+from .series import Series
+from .label import DataLabelList
+
+
+class RadarChart(ChartBase):
+
+    tagname = "radarChart"
+
+    radarStyle = NestedSet(values=(['standard', 'marker', 'filled']))
+    type = Alias("radarStyle")
+    varyColors = NestedBool(nested=True, allow_none=True)
+    ser = Sequence(expected_type=Series, allow_none=True)
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    dataLabels = Alias("dLbls")
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    _series_type = "radar"
+
+    x_axis = Typed(expected_type=TextAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+
+    __elements__ = ('radarStyle', 'varyColors', 'ser', 'dLbls', 'axId')
+
+    def __init__(self,
+                 radarStyle="standard",
+                 varyColors=None,
+                 ser=(),
+                 dLbls=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.radarStyle = radarStyle
+        self.varyColors = varyColors
+        self.ser = ser
+        self.dLbls = dLbls
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+        super().__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/reader.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/reader.py
new file mode 100644
index 00000000..0ef719f9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/reader.py
@@ -0,0 +1,32 @@
+# Copyright (c) 2010-2024 openpyxl
+
+"""
+Read a chart
+"""
+
+def read_chart(chartspace):
+    cs = chartspace
+    plot = cs.chart.plotArea
+
+    chart = plot._charts[0]
+    chart._charts = plot._charts
+
+    chart.title = cs.chart.title
+    chart.display_blanks = cs.chart.dispBlanksAs
+    chart.visible_cells_only = cs.chart.plotVisOnly
+    chart.layout = plot.layout
+    chart.legend = cs.chart.legend
+
+    # 3d attributes
+    chart.floor = cs.chart.floor
+    chart.sideWall = cs.chart.sideWall
+    chart.backWall = cs.chart.backWall
+    chart.pivotSource = cs.pivotSource
+    chart.pivotFormats = cs.chart.pivotFmts
+    chart.idx_base = min((s.idx for s in chart.series), default=0)
+    chart._reindex()
+
+    # Border, fill, etc.
+    chart.graphical_properties = cs.graphical_properties
+
+    return chart
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/reference.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/reference.py
new file mode 100644
index 00000000..dc102791
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/reference.py
@@ -0,0 +1,124 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from itertools import chain
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    MinMax,
+    Typed,
+    String,
+    Strict,
+)
+from openpyxl.worksheet.worksheet import Worksheet
+from openpyxl.utils import (
+    get_column_letter,
+    range_to_tuple,
+    quote_sheetname
+)
+
+
+class DummyWorksheet:
+
+
+    def __init__(self, title):
+        self.title = title
+
+
+class Reference(Strict):
+
+    """
+    Normalise cell range references
+    """
+
+    min_row = MinMax(min=1, max=1000000, expected_type=int)
+    max_row = MinMax(min=1, max=1000000, expected_type=int)
+    min_col = MinMax(min=1, max=16384, expected_type=int)
+    max_col = MinMax(min=1, max=16384, expected_type=int)
+    range_string = String(allow_none=True)
+
+    def __init__(self,
+                 worksheet=None,
+                 min_col=None,
+                 min_row=None,
+                 max_col=None,
+                 max_row=None,
+                 range_string=None
+                 ):
+        if range_string is not None:
+            sheetname, boundaries = range_to_tuple(range_string)
+            min_col, min_row, max_col, max_row = boundaries
+            worksheet = DummyWorksheet(sheetname)
+
+        self.worksheet = worksheet
+        self.min_col = min_col
+        self.min_row = min_row
+        if max_col is None:
+            max_col = min_col
+        self.max_col = max_col
+        if max_row is None:
+            max_row = min_row
+        self.max_row = max_row
+
+
+    def __repr__(self):
+        return str(self)
+
+
+    def __str__(self):
+        fmt = u"{0}!${1}${2}:${3}${4}"
+        if (self.min_col == self.max_col
+            and self.min_row == self.max_row):
+            fmt = u"{0}!${1}${2}"
+        return fmt.format(self.sheetname,
+                          get_column_letter(self.min_col), self.min_row,
+                          get_column_letter(self.max_col), self.max_row
+                          )
+
+
+    __str__ = __str__
+
+
+
+    def __len__(self):
+        if self.min_row == self.max_row:
+            return 1 + self.max_col - self.min_col
+        return 1 + self.max_row - self.min_row
+
+
+    def __eq__(self, other):
+        return str(self) == str(other)
+
+
+    @property
+    def rows(self):
+        """
+        Return all rows in the range
+        """
+        for row in range(self.min_row, self.max_row+1):
+            yield Reference(self.worksheet, self.min_col, row, self.max_col, row)
+
+
+    @property
+    def cols(self):
+        """
+        Return all columns in the range
+        """
+        for col in range(self.min_col, self.max_col+1):
+            yield Reference(self.worksheet, col, self.min_row, col, self.max_row)
+
+
+    def pop(self):
+        """
+        Return and remove the first cell
+        """
+        cell = "{0}{1}".format(get_column_letter(self.min_col), self.min_row)
+        if self.min_row == self.max_row:
+            self.min_col += 1
+        else:
+            self.min_row += 1
+        return cell
+
+
+    @property
+    def sheetname(self):
+        return quote_sheetname(self.worksheet.title)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py
new file mode 100644
index 00000000..2699239e
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/scatter_chart.py
@@ -0,0 +1,53 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Sequence,
+    Alias
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedNoneSet,
+    NestedBool,
+)
+
+from ._chart import ChartBase
+from .axis import NumericAxis, TextAxis
+from .series import XYSeries
+from .label import DataLabelList
+
+
+class ScatterChart(ChartBase):
+
+    tagname = "scatterChart"
+
+    scatterStyle = NestedNoneSet(values=(['line', 'lineMarker', 'marker', 'smooth', 'smoothMarker']))
+    varyColors = NestedBool(allow_none=True)
+    ser = Sequence(expected_type=XYSeries, allow_none=True)
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    dataLabels = Alias("dLbls")
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    x_axis = Typed(expected_type=(NumericAxis, TextAxis))
+    y_axis = Typed(expected_type=NumericAxis)
+
+    _series_type = "scatter"
+
+    __elements__ = ('scatterStyle', 'varyColors', 'ser', 'dLbls', 'axId',)
+
+    def __init__(self,
+                 scatterStyle=None,
+                 varyColors=None,
+                 ser=(),
+                 dLbls=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.scatterStyle = scatterStyle
+        self.varyColors = varyColors
+        self.ser = ser
+        self.dLbls = dLbls
+        self.x_axis = NumericAxis(axId=10, crossAx=20)
+        self.y_axis = NumericAxis(axId=20, crossAx=10)
+        super().__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/series.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/series.py
new file mode 100644
index 00000000..f1403a6c
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/series.py
@@ -0,0 +1,197 @@
+# Copyright (c) 2010-2024 openpyxl
+
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    String,
+    Integer,
+    Bool,
+    Alias,
+    Sequence,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedInteger,
+    NestedBool,
+    NestedNoneSet,
+    NestedText,
+)
+
+from .shapes import GraphicalProperties
+from .data_source import (
+    AxDataSource,
+    NumDataSource,
+    NumRef,
+    StrRef,
+)
+from .error_bar import ErrorBars
+from .label import DataLabelList
+from .marker import DataPoint, PictureOptions, Marker
+from .trendline import Trendline
+
+attribute_mapping = {
+    'area': ('idx', 'order', 'tx', 'spPr', 'pictureOptions', 'dPt', 'dLbls', 'errBars',
+             'trendline', 'cat', 'val',),
+    'bar':('idx', 'order','tx', 'spPr', 'invertIfNegative', 'pictureOptions', 'dPt',
+           'dLbls', 'trendline', 'errBars', 'cat', 'val', 'shape'),
+    'bubble':('idx','order', 'tx', 'spPr', 'invertIfNegative', 'dPt', 'dLbls',
+              'trendline', 'errBars', 'xVal', 'yVal', 'bubbleSize', 'bubble3D'),
+    'line':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline',
+            'errBars', 'cat', 'val', 'smooth'),
+    'pie':('idx', 'order', 'tx', 'spPr', 'explosion', 'dPt', 'dLbls', 'cat', 'val'),
+    'radar':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'cat', 'val'),
+    'scatter':('idx', 'order', 'tx', 'spPr', 'marker', 'dPt', 'dLbls', 'trendline',
+               'errBars', 'xVal', 'yVal', 'smooth'),
+    'surface':('idx', 'order', 'tx', 'spPr', 'cat', 'val'),
+                     }
+
+
+class SeriesLabel(Serialisable):
+
+    tagname = "tx"
+
+    strRef = Typed(expected_type=StrRef, allow_none=True)
+    v = NestedText(expected_type=str, allow_none=True)
+    value = Alias('v')
+
+    __elements__ = ('strRef', 'v')
+
+    def __init__(self,
+                 strRef=None,
+                 v=None):
+        self.strRef = strRef
+        self.v = v
+
+
+class Series(Serialisable):
+
+    """
+    Generic series object. Should not be instantiated directly.
+    User the chart.Series factory instead.
+    """
+
+    tagname = "ser"
+
+    idx = NestedInteger()
+    order = NestedInteger()
+    tx = Typed(expected_type=SeriesLabel, allow_none=True)
+    title = Alias('tx')
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+
+    # area chart
+    pictureOptions = Typed(expected_type=PictureOptions, allow_none=True)
+    dPt = Sequence(expected_type=DataPoint, allow_none=True)
+    data_points = Alias("dPt")
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    labels = Alias("dLbls")
+    trendline = Typed(expected_type=Trendline, allow_none=True)
+    errBars = Typed(expected_type=ErrorBars, allow_none=True)
+    cat = Typed(expected_type=AxDataSource, allow_none=True)
+    identifiers = Alias("cat")
+    val = Typed(expected_type=NumDataSource, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    #bar chart
+    invertIfNegative = NestedBool(allow_none=True)
+    shape = NestedNoneSet(values=(['cone', 'coneToMax', 'box', 'cylinder', 'pyramid', 'pyramidToMax']))
+
+    #bubble chart
+    xVal = Typed(expected_type=AxDataSource, allow_none=True)
+    yVal = Typed(expected_type=NumDataSource, allow_none=True)
+    bubbleSize = Typed(expected_type=NumDataSource, allow_none=True)
+    zVal = Alias("bubbleSize")
+    bubble3D = NestedBool(allow_none=True)
+
+    #line chart
+    marker = Typed(expected_type=Marker, allow_none=True)
+    smooth = NestedBool(allow_none=True)
+
+    #pie chart
+    explosion = NestedInteger(allow_none=True)
+
+    __elements__ = ()
+
+
+    def __init__(self,
+                 idx=0,
+                 order=0,
+                 tx=None,
+                 spPr=None,
+                 pictureOptions=None,
+                 dPt=(),
+                 dLbls=None,
+                 trendline=None,
+                 errBars=None,
+                 cat=None,
+                 val=None,
+                 invertIfNegative=None,
+                 shape=None,
+                 xVal=None,
+                 yVal=None,
+                 bubbleSize=None,
+                 bubble3D=None,
+                 marker=None,
+                 smooth=None,
+                 explosion=None,
+                 extLst=None,
+                ):
+        self.idx = idx
+        self.order = order
+        self.tx = tx
+        if spPr is None:
+            spPr = GraphicalProperties()
+        self.spPr = spPr
+        self.pictureOptions = pictureOptions
+        self.dPt = dPt
+        self.dLbls = dLbls
+        self.trendline = trendline
+        self.errBars = errBars
+        self.cat = cat
+        self.val = val
+        self.invertIfNegative = invertIfNegative
+        self.shape = shape
+        self.xVal = xVal
+        self.yVal = yVal
+        self.bubbleSize = bubbleSize
+        self.bubble3D = bubble3D
+        if marker is None:
+            marker = Marker()
+        self.marker = marker
+        self.smooth = smooth
+        self.explosion = explosion
+
+
+    def to_tree(self, tagname=None, idx=None):
+        """The index can need rebasing"""
+        if idx is not None:
+            if self.order == self.idx:
+                self.order = idx # rebase the order if the index has been rebased
+            self.idx = idx
+        return super().to_tree(tagname)
+
+
+class XYSeries(Series):
+
+    """Dedicated series for charts that have x and y series"""
+
+    idx = Series.idx
+    order = Series.order
+    tx = Series.tx
+    spPr = Series.spPr
+
+    dPt = Series.dPt
+    dLbls = Series.dLbls
+    trendline = Series.trendline
+    errBars = Series.errBars
+    xVal = Series.xVal
+    yVal = Series.yVal
+
+    invertIfNegative = Series.invertIfNegative
+
+    bubbleSize = Series.bubbleSize
+    bubble3D = Series.bubble3D
+
+    marker = Series.marker
+    smooth = Series.smooth
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py
new file mode 100644
index 00000000..90b368d9
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/series_factory.py
@@ -0,0 +1,41 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from .data_source import NumDataSource, NumRef, AxDataSource
+from .reference import Reference
+from .series import Series, XYSeries, SeriesLabel, StrRef
+from  openpyxl.utils import rows_from_range, quote_sheetname
+
+
+def SeriesFactory(values, xvalues=None, zvalues=None, title=None, title_from_data=False):
+    """
+    Convenience Factory for creating chart data series.
+    """
+
+    if not isinstance(values, Reference):
+        values = Reference(range_string=values)
+
+    if title_from_data:
+        cell = values.pop()
+        title = u"{0}!{1}".format(values.sheetname, cell)
+        title = SeriesLabel(strRef=StrRef(title))
+    elif title is not None:
+        title = SeriesLabel(v=title)
+
+    source = NumDataSource(numRef=NumRef(f=values))
+    if xvalues is not None:
+        if not isinstance(xvalues, Reference):
+            xvalues = Reference(range_string=xvalues)
+        series = XYSeries()
+        series.yVal = source
+        series.xVal = AxDataSource(numRef=NumRef(f=xvalues))
+        if zvalues is not None:
+            if not isinstance(zvalues, Reference):
+                zvalues = Reference(range_string=zvalues)
+            series.zVal = NumDataSource(NumRef(f=zvalues))
+    else:
+        series = Series()
+        series.val = source
+
+    if title is not None:
+        series.title = title
+    return series
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py
new file mode 100644
index 00000000..7736c1ad
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/shapes.py
@@ -0,0 +1,89 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Alias
+)
+from openpyxl.descriptors.nested import (
+    EmptyTag
+)
+from openpyxl.drawing.colors import ColorChoiceDescriptor
+from openpyxl.drawing.fill import *
+from openpyxl.drawing.line import LineProperties
+from openpyxl.drawing.geometry import (
+    Shape3D,
+    Scene3D,
+    Transform2D,
+    CustomGeometry2D,
+    PresetGeometry2D,
+)
+
+
+class GraphicalProperties(Serialisable):
+
+    """
+    Somewhat vaguely 21.2.2.197 says this:
+
+    This element specifies the formatting for the parent chart element. The
+    custGeom, prstGeom, scene3d, and xfrm elements are not supported. The
+    bwMode attribute is not supported.
+
+    This doesn't leave much. And the element is used in different places.
+    """
+
+    tagname = "spPr"
+
+    bwMode = NoneSet(values=(['clr', 'auto', 'gray', 'ltGray', 'invGray',
+                          'grayWhite', 'blackGray', 'blackWhite', 'black', 'white', 'hidden']
+                         )
+                 )
+
+    xfrm = Typed(expected_type=Transform2D, allow_none=True)
+    transform = Alias('xfrm')
+    custGeom = Typed(expected_type=CustomGeometry2D, allow_none=True) # either or
+    prstGeom = Typed(expected_type=PresetGeometry2D, allow_none=True)
+
+    # fills one of
+    noFill = EmptyTag(namespace=DRAWING_NS)
+    solidFill = ColorChoiceDescriptor()
+    gradFill = Typed(expected_type=GradientFillProperties, allow_none=True)
+    pattFill = Typed(expected_type=PatternFillProperties, allow_none=True)
+
+    ln = Typed(expected_type=LineProperties, allow_none=True)
+    line = Alias('ln')
+    scene3d = Typed(expected_type=Scene3D, allow_none=True)
+    sp3d = Typed(expected_type=Shape3D, allow_none=True)
+    shape3D = Alias('sp3d')
+    extLst = Typed(expected_type=OfficeArtExtensionList, allow_none=True)
+
+    __elements__ = ('xfrm', 'prstGeom', 'noFill', 'solidFill', 'gradFill', 'pattFill',
+                    'ln', 'scene3d', 'sp3d')
+
+    def __init__(self,
+                 bwMode=None,
+                 xfrm=None,
+                 noFill=None,
+                 solidFill=None,
+                 gradFill=None,
+                 pattFill=None,
+                 ln=None,
+                 scene3d=None,
+                 custGeom=None,
+                 prstGeom=None,
+                 sp3d=None,
+                 extLst=None,
+                ):
+        self.bwMode = bwMode
+        self.xfrm = xfrm
+        self.noFill = noFill
+        self.solidFill = solidFill
+        self.gradFill = gradFill
+        self.pattFill = pattFill
+        if ln is None:
+            ln = LineProperties()
+        self.ln = ln
+        self.custGeom = custGeom
+        self.prstGeom = prstGeom
+        self.scene3d = scene3d
+        self.sp3d = sp3d
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py
new file mode 100644
index 00000000..119c7901
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/stock_chart.py
@@ -0,0 +1,54 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Sequence,
+    Alias,
+)
+from openpyxl.descriptors.excel import ExtensionList
+
+from ._chart import ChartBase
+from .axis import TextAxis, NumericAxis, ChartLines
+from .updown_bars import UpDownBars
+from .label import DataLabelList
+from .series import Series
+
+
+class StockChart(ChartBase):
+
+    tagname = "stockChart"
+
+    ser = Sequence(expected_type=Series) #min 3, max4
+    dLbls = Typed(expected_type=DataLabelList, allow_none=True)
+    dataLabels = Alias('dLbls')
+    dropLines = Typed(expected_type=ChartLines, allow_none=True)
+    hiLowLines = Typed(expected_type=ChartLines, allow_none=True)
+    upDownBars = Typed(expected_type=UpDownBars, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    x_axis = Typed(expected_type=TextAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+
+    _series_type = "line"
+
+    __elements__ = ('ser', 'dLbls', 'dropLines', 'hiLowLines', 'upDownBars',
+                    'axId')
+
+    def __init__(self,
+                 ser=(),
+                 dLbls=None,
+                 dropLines=None,
+                 hiLowLines=None,
+                 upDownBars=None,
+                 extLst=None,
+                 **kw
+                ):
+        self.ser = ser
+        self.dLbls = dLbls
+        self.dropLines = dropLines
+        self.hiLowLines = hiLowLines
+        self.upDownBars = upDownBars
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+        super().__init__(**kw)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py
new file mode 100644
index 00000000..5f388e14
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/surface_chart.py
@@ -0,0 +1,119 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Integer,
+    Bool,
+    Alias,
+    Sequence,
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedInteger,
+    NestedBool,
+)
+
+from ._chart import ChartBase
+from ._3d import _3DBase
+from .axis import TextAxis, NumericAxis, SeriesAxis
+from .shapes import GraphicalProperties
+from .series import Series
+
+
+class BandFormat(Serialisable):
+
+    tagname = "bandFmt"
+
+    idx = NestedInteger()
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias("spPr")
+
+    __elements__ = ('idx', 'spPr')
+
+    def __init__(self,
+                 idx=0,
+                 spPr=None,
+                ):
+        self.idx = idx
+        self.spPr = spPr
+
+
+class BandFormatList(Serialisable):
+
+    tagname = "bandFmts"
+
+    bandFmt = Sequence(expected_type=BandFormat, allow_none=True)
+
+    __elements__ = ('bandFmt',)
+
+    def __init__(self,
+                 bandFmt=(),
+                ):
+        self.bandFmt = bandFmt
+
+
+class _SurfaceChartBase(ChartBase):
+
+    wireframe = NestedBool(allow_none=True)
+    ser = Sequence(expected_type=Series, allow_none=True)
+    bandFmts = Typed(expected_type=BandFormatList, allow_none=True)
+
+    _series_type = "surface"
+
+    __elements__ = ('wireframe', 'ser', 'bandFmts')
+
+    def __init__(self,
+                 wireframe=None,
+                 ser=(),
+                 bandFmts=None,
+                 **kw
+                ):
+        self.wireframe = wireframe
+        self.ser = ser
+        self.bandFmts = bandFmts
+        super().__init__(**kw)
+
+
+class SurfaceChart3D(_SurfaceChartBase, _3DBase):
+
+    tagname = "surface3DChart"
+
+    wireframe = _SurfaceChartBase.wireframe
+    ser = _SurfaceChartBase.ser
+    bandFmts = _SurfaceChartBase.bandFmts
+
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    x_axis = Typed(expected_type=TextAxis)
+    y_axis = Typed(expected_type=NumericAxis)
+    z_axis = Typed(expected_type=SeriesAxis)
+
+    __elements__ = _SurfaceChartBase.__elements__ + ('axId',)
+
+    def __init__(self, **kw):
+        self.x_axis = TextAxis()
+        self.y_axis = NumericAxis()
+        self.z_axis = SeriesAxis()
+        super(SurfaceChart3D, self).__init__(**kw)
+
+
+class SurfaceChart(SurfaceChart3D):
+
+    tagname = "surfaceChart"
+
+    wireframe = _SurfaceChartBase.wireframe
+    ser = _SurfaceChartBase.ser
+    bandFmts = _SurfaceChartBase.bandFmts
+
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = SurfaceChart3D.__elements__
+
+    def __init__(self, **kw):
+        super().__init__(**kw)
+        self.y_axis.delete = True
+        self.view3D.x_rotation = 90
+        self.view3D.y_rotation = 0
+        self.view3D.perspective = False
+        self.view3D.right_angle_axes = False
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/text.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/text.py
new file mode 100644
index 00000000..bd034c24
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/text.py
@@ -0,0 +1,78 @@
+# Copyright (c) 2010-2024 openpyxl
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Alias,
+    Sequence,
+)
+
+
+from openpyxl.drawing.text import (
+    RichTextProperties,
+    ListStyle,
+    Paragraph,
+)
+
+from .data_source import StrRef
+
+
+class RichText(Serialisable):
+
+    """
+    From the specification: 21.2.2.216
+
+    This element specifies text formatting. The lstStyle element is not supported.
+    """
+
+    tagname = "rich"
+
+    bodyPr = Typed(expected_type=RichTextProperties)
+    properties = Alias("bodyPr")
+    lstStyle = Typed(expected_type=ListStyle, allow_none=True)
+    p = Sequence(expected_type=Paragraph)
+    paragraphs = Alias('p')
+
+    __elements__ = ("bodyPr", "lstStyle", "p")
+
+    def __init__(self,
+                 bodyPr=None,
+                 lstStyle=None,
+                 p=None,
+                ):
+        if bodyPr is None:
+            bodyPr = RichTextProperties()
+        self.bodyPr = bodyPr
+        self.lstStyle = lstStyle
+        if p is None:
+            p = [Paragraph()]
+        self.p = p
+
+
+class Text(Serialisable):
+
+    """
+    The value can be either a cell reference or a text element
+    If both are present then the reference will be used.
+    """
+
+    tagname = "tx"
+
+    strRef = Typed(expected_type=StrRef, allow_none=True)
+    rich = Typed(expected_type=RichText, allow_none=True)
+
+    __elements__ = ("strRef", "rich")
+
+    def __init__(self,
+                 strRef=None,
+                 rich=None
+                 ):
+        self.strRef = strRef
+        if rich is None:
+            rich = RichText()
+        self.rich = rich
+
+
+    def to_tree(self, tagname=None, idx=None, namespace=None):
+        if self.strRef and self.rich:
+            self.rich = None # can only have one
+        return super().to_tree(tagname, idx, namespace)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/title.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/title.py
new file mode 100644
index 00000000..10f79d7a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/title.py
@@ -0,0 +1,76 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    Alias,
+)
+
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import NestedBool
+
+from .text import Text, RichText
+from .layout import Layout
+from .shapes import GraphicalProperties
+
+from openpyxl.drawing.text import (
+    Paragraph,
+    RegularTextRun,
+    LineBreak,
+    ParagraphProperties,
+    CharacterProperties,
+)
+
+
+class Title(Serialisable):
+    tagname = "title"
+
+    tx = Typed(expected_type=Text, allow_none=True)
+    text = Alias('tx')
+    layout = Typed(expected_type=Layout, allow_none=True)
+    overlay = NestedBool(allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    body = Alias('txPr')
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('tx', 'layout', 'overlay', 'spPr', 'txPr')
+
+    def __init__(self,
+                 tx=None,
+                 layout=None,
+                 overlay=None,
+                 spPr=None,
+                 txPr=None,
+                 extLst=None,
+                ):
+        if tx is None:
+            tx = Text()
+        self.tx = tx
+        self.layout = layout
+        self.overlay = overlay
+        self.spPr = spPr
+        self.txPr = txPr
+
+
+
+def title_maker(text):
+    title = Title()
+    paraprops = ParagraphProperties()
+    paraprops.defRPr = CharacterProperties()
+    paras = [Paragraph(r=[RegularTextRun(t=s)], pPr=paraprops) for s in text.split("\n")]
+
+    title.tx.rich.paragraphs = paras
+    return title
+
+
+class TitleDescriptor(Typed):
+
+    expected_type = Title
+    allow_none = True
+
+    def __set__(self, instance, value):
+        if isinstance(value, str):
+            value = title_maker(value)
+        super().__set__(instance, value)
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py
new file mode 100644
index 00000000..bf6d2366
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/trendline.py
@@ -0,0 +1,98 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import (
+    Typed,
+    String,
+    Alias
+)
+from openpyxl.descriptors.excel import ExtensionList
+from openpyxl.descriptors.nested import (
+    NestedBool,
+    NestedInteger,
+    NestedFloat,
+    NestedSet
+)
+
+from .data_source import NumFmt
+from .shapes import GraphicalProperties
+from .text import RichText, Text
+from .layout import Layout
+
+
+class TrendlineLabel(Serialisable):
+
+    tagname = "trendlineLbl"
+
+    layout = Typed(expected_type=Layout, allow_none=True)
+    tx = Typed(expected_type=Text, allow_none=True)
+    numFmt = Typed(expected_type=NumFmt, allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias("spPr")
+    txPr = Typed(expected_type=RichText, allow_none=True)
+    textProperties = Alias("txPr")
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('layout', 'tx', 'numFmt', 'spPr', 'txPr')
+
+    def __init__(self,
+                 layout=None,
+                 tx=None,
+                 numFmt=None,
+                 spPr=None,
+                 txPr=None,
+                 extLst=None,
+                ):
+        self.layout = layout
+        self.tx = tx
+        self.numFmt = numFmt
+        self.spPr = spPr
+        self.txPr = txPr
+
+
+class Trendline(Serialisable):
+
+    tagname = "trendline"
+
+    name = String(allow_none=True)
+    spPr = Typed(expected_type=GraphicalProperties, allow_none=True)
+    graphicalProperties = Alias('spPr')
+    trendlineType = NestedSet(values=(['exp', 'linear', 'log', 'movingAvg', 'poly', 'power']))
+    order = NestedInteger(allow_none=True)
+    period = NestedInteger(allow_none=True)
+    forward = NestedFloat(allow_none=True)
+    backward = NestedFloat(allow_none=True)
+    intercept = NestedFloat(allow_none=True)
+    dispRSqr = NestedBool(allow_none=True)
+    dispEq = NestedBool(allow_none=True)
+    trendlineLbl = Typed(expected_type=TrendlineLabel, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('spPr', 'trendlineType', 'order', 'period', 'forward',
+                    'backward', 'intercept', 'dispRSqr', 'dispEq', 'trendlineLbl')
+
+    def __init__(self,
+                 name=None,
+                 spPr=None,
+                 trendlineType='linear',
+                 order=None,
+                 period=None,
+                 forward=None,
+                 backward=None,
+                 intercept=None,
+                 dispRSqr=None,
+                 dispEq=None,
+                 trendlineLbl=None,
+                 extLst=None,
+                ):
+        self.name = name
+        self.spPr = spPr
+        self.trendlineType = trendlineType
+        self.order = order
+        self.period = period
+        self.forward = forward
+        self.backward = backward
+        self.intercept = intercept
+        self.dispRSqr = dispRSqr
+        self.dispEq = dispEq
+        self.trendlineLbl = trendlineLbl
diff --git a/.venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py b/.venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py
new file mode 100644
index 00000000..6de7ab8a
--- /dev/null
+++ b/.venv/lib/python3.12/site-packages/openpyxl/chart/updown_bars.py
@@ -0,0 +1,31 @@
+# Copyright (c) 2010-2024 openpyxl
+
+from openpyxl.descriptors.serialisable import Serialisable
+from openpyxl.descriptors import Typed
+from openpyxl.descriptors.excel import ExtensionList
+
+from .shapes import GraphicalProperties
+from .axis import ChartLines
+from .descriptors import NestedGapAmount
+
+
+class UpDownBars(Serialisable):
+
+    tagname = "upbars"
+
+    gapWidth = NestedGapAmount()
+    upBars = Typed(expected_type=ChartLines, allow_none=True)
+    downBars = Typed(expected_type=ChartLines, allow_none=True)
+    extLst = Typed(expected_type=ExtensionList, allow_none=True)
+
+    __elements__ = ('gapWidth', 'upBars', 'downBars')
+
+    def __init__(self,
+                 gapWidth=150,
+                 upBars=None,
+                 downBars=None,
+                 extLst=None,
+                ):
+        self.gapWidth = gapWidth
+        self.upBars = upBars
+        self.downBars = downBars