text: # Copyright (c) 2010-2020 openpyxlzip
  
  from collections import OrderedDict
  
  from openpyxlzip.descriptors import (
      Typed,
      Integer,
      Alias,
      MinMax,
      Bool,
      Set,
  )
  from openpyxlzip.descriptors.sequence import ValueSequence
  from openpyxlzip.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'])
  
      _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)
  
      __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
          super(ChartBase, self).__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, elem_type=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(ChartBase, self).to_tree(tagname, idx, elem_type=elem_type)
  
  
      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
          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)
  