Source code for pptx.chart.plot

# 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


[docs]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. """
[docs]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
[docs]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