text: # encoding: utf-8
  
  """
  Plot-related objects. A plot is known as a chart group in the MS API. A chart
  can have more than one plot overlayed on each other, such as a line plot
  layered over a bar plot.
  """
  
  from __future__ import absolute_import, print_function, unicode_literals
  
  from .category import Categories
  from .datalabel import DataLabels
  from ..enum.chart import XL_CHART_TYPE as XL
  from ..oxml.ns import qn
  from ..oxml.simpletypes import ST_BarDir, ST_Grouping
  from .series import SeriesCollection
  from ..util import lazyproperty
  
  
  class _BasePlot(object):
      """
      A distinct plot that appears in the plot area of a chart. A chart may
      have more than one plot, in which case they appear as superimposed
      layers, such as a line plot appearing on top of a bar chart.
      """
  
      def __init__(self, xChart, chart):
          super(_BasePlot, self).__init__()
          self._element = xChart
          self._chart = chart
  
      @lazyproperty
      def categories(self):
          """
          Returns a |category.Categories| sequence object containing
          a |category.Category| object for each of the category labels
          associated with this plot. The |category.Category| class derives from
          ``str``, so the returned value can be treated as a simple sequence of
          strings for the common case where all you need is the labels in the
          order they appear on the chart. |category.Categories| provides
          additional properties for dealing with hierarchical categories when
          required.
          """
          return Categories(self._element)
  
      @property
      def chart(self):
          """
          The |Chart| object containing this plot.
          """
          return self._chart
  
      @property
      def data_labels(self):
          """
          |DataLabels| instance providing properties and methods on the
          collection of data labels associated with this plot.
          """
          dLbls = self._element.dLbls
          if dLbls is None:
              raise ValueError(
                  "plot has no data labels, set has_data_labels = True first"
              )
          return DataLabels(dLbls)
  
      @property
      def has_data_labels(self):
          """
          Read/write boolean, |True| if the series has data labels. Assigning
          |True| causes data labels to be added to the plot. Assigning False
          removes any existing data labels.
          """
          return self._element.dLbls is not None
  
      @has_data_labels.setter
      def has_data_labels(self, value):
          """
          Add, remove, or leave alone the ``<c:dLbls>`` child element depending
          on current state and assigned *value*. If *value* is |True| and no
          ``<c:dLbls>`` element is present, a new default element is added with
          default child elements and settings. When |False|, any existing dLbls
          element is removed.
          """
          if bool(value) is False:
              self._element._remove_dLbls()
          else:
              if self._element.dLbls is None:
                  dLbls = self._element._add_dLbls()
                  dLbls.showVal.val = True
  
      @lazyproperty
      def series(self):
          """
          A sequence of |Series| objects representing the series in this plot,
          in the order they appear in the plot.
          """
          return SeriesCollection(self._element)
  
      @property
      def vary_by_categories(self):
          """
          Read/write boolean value specifying whether to use a different color
          for each of the points in this plot. Only effective when there is
          a single series; PowerPoint automatically varies color by series when
          more than one series is present.
          """
          varyColors = self._element.varyColors
          if varyColors is None:
              return True
          return varyColors.val
  
      @vary_by_categories.setter
      def vary_by_categories(self, value):
          self._element.get_or_add_varyColors().val = bool(value)
  
  
  class AreaPlot(_BasePlot):
      """
      An area plot.
      """
  
  
  class Area3DPlot(_BasePlot):
      """
      A 3-dimensional area plot.
      """
  
  
  class BarPlot(_BasePlot):
      """
      A bar chart-style plot.
      """
  
      @property
      def gap_width(self):
          """
          Width of gap between bar(s) of each category, as an integer
          percentage of the bar width. The default value for a new bar chart is
          150, representing 150% or 1.5 times the width of a single bar.
          """
          gapWidth = self._element.gapWidth
          if gapWidth is None:
              return 150
          return gapWidth.val
  
      @gap_width.setter
      def gap_width(self, value):
          gapWidth = self._element.get_or_add_gapWidth()
          gapWidth.val = value
  
      @property
      def overlap(self):
          """
          Read/write int value in range -100..100 specifying a percentage of
          the bar width by which to overlap adjacent bars in a multi-series bar
          chart. Default is 0. A setting of -100 creates a gap of a full bar
          width and a setting of 100 causes all the bars in a category to be
          superimposed. A stacked bar plot has overlap of 100 by default.
          """
          overlap = self._element.overlap
          if overlap is None:
              return 0
          return overlap.val
  
      @overlap.setter
      def overlap(self, value):
          """
          Set the value of the ``<c:overlap>`` child element to *int_value*,
          or remove the overlap element if *int_value* is 0.
          """
          if value == 0:
              self._element._remove_overlap()
              return
          self._element.get_or_add_overlap().val = value
  
  
  class BubblePlot(_BasePlot):
      """
      A bubble chart plot.
      """
  
      @property
      def bubble_scale(self):
          """
          An integer between 0 and 300 inclusive indicating the percentage of
          the default size at which bubbles should be displayed. Assigning
          |None| produces the same behavior as assigning `100`.
          """
          bubbleScale = self._element.bubbleScale
          if bubbleScale is None:
              return 100
          return bubbleScale.val
  
      @bubble_scale.setter
      def bubble_scale(self, value):
          bubbleChart = self._element
          bubbleChart._remove_bubbleScale()
          if value is None:
              return
          bubbleScale = bubbleChart._add_bubbleScale()
          bubbleScale.val = value
  
  
  class DoughnutPlot(_BasePlot):
      """
      An doughnut plot.
      """
  
  
  class LinePlot(_BasePlot):
      """
      A line chart-style plot.
      """
  
  
  class PiePlot(_BasePlot):
      """
      A pie chart-style plot.
      """
  
  
  class RadarPlot(_BasePlot):
      """
      A radar-style plot.
      """
  
  
  class XyPlot(_BasePlot):
      """
      An XY (scatter) plot.
      """
  
  
  def PlotFactory(xChart, chart):
      """
      Return an instance of the appropriate subclass of _BasePlot based on the
      tagname of *xChart*.
      """
      try:
          PlotCls = {
              qn("c:areaChart"): AreaPlot,
              qn("c:area3DChart"): Area3DPlot,
              qn("c:barChart"): BarPlot,
              qn("c:bubbleChart"): BubblePlot,
              qn("c:doughnutChart"): DoughnutPlot,
              qn("c:lineChart"): LinePlot,
              qn("c:pieChart"): PiePlot,
              qn("c:radarChart"): RadarPlot,
              qn("c:scatterChart"): XyPlot,
          }[xChart.tag]
      except KeyError:
          raise ValueError("unsupported plot type %s" % xChart.tag)
  
      return PlotCls(xChart, chart)
  
  
  class PlotTypeInspector(object):
      """
      "One-shot" service object that knows how to identify the type of a plot
      as a member of the XL_CHART_TYPE enumeration.
      """
  
      @classmethod
      def chart_type(cls, plot):
          """
          Return the member of :ref:`XlChartType` that corresponds to the chart
          type of *plot*.
          """
          try:
              chart_type_method = {
                  "AreaPlot": cls._differentiate_area_chart_type,
                  "Area3DPlot": cls._differentiate_area_3d_chart_type,
                  "BarPlot": cls._differentiate_bar_chart_type,
                  "BubblePlot": cls._differentiate_bubble_chart_type,
                  "DoughnutPlot": cls._differentiate_doughnut_chart_type,
                  "LinePlot": cls._differentiate_line_chart_type,
                  "PiePlot": cls._differentiate_pie_chart_type,
                  "RadarPlot": cls._differentiate_radar_chart_type,
                  "XyPlot": cls._differentiate_xy_chart_type,
              }[plot.__class__.__name__]
          except KeyError:
              raise NotImplementedError(
                  "chart_type() not implemented for %s" % plot.__class__.__name__
              )
          return chart_type_method(plot)
  
      @classmethod
      def _differentiate_area_3d_chart_type(cls, plot):
          return {
              ST_Grouping.STANDARD: XL.THREE_D_AREA,
              ST_Grouping.STACKED: XL.THREE_D_AREA_STACKED,
              ST_Grouping.PERCENT_STACKED: XL.THREE_D_AREA_STACKED_100,
          }[plot._element.grouping_val]
  
      @classmethod
      def _differentiate_area_chart_type(cls, plot):
          return {
              ST_Grouping.STANDARD: XL.AREA,
              ST_Grouping.STACKED: XL.AREA_STACKED,
              ST_Grouping.PERCENT_STACKED: XL.AREA_STACKED_100,
          }[plot._element.grouping_val]
  
      @classmethod
      def _differentiate_bar_chart_type(cls, plot):
          barChart = plot._element
          if barChart.barDir.val == ST_BarDir.BAR:
              return {
                  ST_Grouping.CLUSTERED: XL.BAR_CLUSTERED,
                  ST_Grouping.STACKED: XL.BAR_STACKED,
                  ST_Grouping.PERCENT_STACKED: XL.BAR_STACKED_100,
              }[barChart.grouping_val]
          if barChart.barDir.val == ST_BarDir.COL:
              return {
                  ST_Grouping.CLUSTERED: XL.COLUMN_CLUSTERED,
                  ST_Grouping.STACKED: XL.COLUMN_STACKED,
                  ST_Grouping.PERCENT_STACKED: XL.COLUMN_STACKED_100,
              }[barChart.grouping_val]
          raise ValueError("invalid barChart.barDir value '%s'" % barChart.barDir.val)
  
      @classmethod
      def _differentiate_bubble_chart_type(cls, plot):
          def first_bubble3D(bubbleChart):
              results = bubbleChart.xpath("c:ser/c:bubble3D")
              return results[0] if results else None
  
          bubbleChart = plot._element
          bubble3D = first_bubble3D(bubbleChart)
  
          if bubble3D is None:
              return XL.BUBBLE
          if bubble3D.val:
              return XL.BUBBLE_THREE_D_EFFECT
          return XL.BUBBLE
  
      @classmethod
      def _differentiate_doughnut_chart_type(cls, plot):
          doughnutChart = plot._element
          explosion = doughnutChart.xpath("./c:ser/c:explosion")
          return XL.DOUGHNUT_EXPLODED if explosion else XL.DOUGHNUT
  
      @classmethod
      def _differentiate_line_chart_type(cls, plot):
          lineChart = plot._element
  
          def has_line_markers():
              matches = lineChart.xpath('c:ser/c:marker/c:symbol[@val="none"]')
              if matches:
                  return False
              return True
  
          if has_line_markers():
              return {
                  ST_Grouping.STANDARD: XL.LINE_MARKERS,
                  ST_Grouping.STACKED: XL.LINE_MARKERS_STACKED,
                  ST_Grouping.PERCENT_STACKED: XL.LINE_MARKERS_STACKED_100,
              }[plot._element.grouping_val]
          else:
              return {
                  ST_Grouping.STANDARD: XL.LINE,
                  ST_Grouping.STACKED: XL.LINE_STACKED,
                  ST_Grouping.PERCENT_STACKED: XL.LINE_STACKED_100,
              }[plot._element.grouping_val]
  
      @classmethod
      def _differentiate_pie_chart_type(cls, plot):
          pieChart = plot._element
          explosion = pieChart.xpath("./c:ser/c:explosion")
          return XL.PIE_EXPLODED if explosion else XL.PIE
  
      @classmethod
      def _differentiate_radar_chart_type(cls, plot):
          radarChart = plot._element
          radar_style = radarChart.xpath("c:radarStyle")[0].get("val")
  
          def noMarkers():
              matches = radarChart.xpath("c:ser/c:marker/c:symbol")
              if matches and matches[0].get("val") == "none":
                  return True
              return False
  
          if radar_style is None:
              return XL.RADAR
          if radar_style == "filled":
              return XL.RADAR_FILLED
          if noMarkers():
              return XL.RADAR
          return XL.RADAR_MARKERS
  
      @classmethod
      def _differentiate_xy_chart_type(cls, plot):
          scatterChart = plot._element
  
          def noLine():
              return bool(scatterChart.xpath("c:ser/c:spPr/a:ln/a:noFill"))
  
          def noMarkers():
              symbols = scatterChart.xpath("c:ser/c:marker/c:symbol")
              if symbols and symbols[0].get("val") == "none":
                  return True
              return False
  
          scatter_style = scatterChart.xpath("c:scatterStyle")[0].get("val")
  
          if scatter_style == "lineMarker":
              if noLine():
                  return XL.XY_SCATTER
              if noMarkers():
                  return XL.XY_SCATTER_LINES_NO_MARKERS
              return XL.XY_SCATTER_LINES
  
          if scatter_style == "smoothMarker":
              if noMarkers():
                  return XL.XY_SCATTER_SMOOTH_NO_MARKERS
              return XL.XY_SCATTER_SMOOTH
  
          return XL.XY_SCATTER
  