diff options
Diffstat (limited to '.venv/lib/python3.12/site-packages/pptx/chart/axis.py')
-rw-r--r-- | .venv/lib/python3.12/site-packages/pptx/chart/axis.py | 523 |
1 files changed, 523 insertions, 0 deletions
diff --git a/.venv/lib/python3.12/site-packages/pptx/chart/axis.py b/.venv/lib/python3.12/site-packages/pptx/chart/axis.py new file mode 100644 index 00000000..a9b87703 --- /dev/null +++ b/.venv/lib/python3.12/site-packages/pptx/chart/axis.py @@ -0,0 +1,523 @@ +"""Axis-related chart objects.""" + +from __future__ import annotations + +from pptx.dml.chtfmt import ChartFormat +from pptx.enum.chart import ( + XL_AXIS_CROSSES, + XL_CATEGORY_TYPE, + XL_TICK_LABEL_POSITION, + XL_TICK_MARK, +) +from pptx.oxml.ns import qn +from pptx.oxml.simpletypes import ST_Orientation +from pptx.shared import ElementProxy +from pptx.text.text import Font, TextFrame +from pptx.util import lazyproperty + + +class _BaseAxis(object): + """Base class for chart axis objects. All axis objects share these properties.""" + + def __init__(self, xAx): + super(_BaseAxis, self).__init__() + self._element = xAx # axis element, c:catAx or c:valAx + self._xAx = xAx + + @property + def axis_title(self): + """An |AxisTitle| object providing access to title properties. + + Calling this property is destructive in the sense that it adds an + axis title element (`c:title`) to the axis XML if one is not already + present. Use :attr:`has_title` to test for presence of axis title + non-destructively. + """ + return AxisTitle(self._element.get_or_add_title()) + + @lazyproperty + def format(self): + """ + The |ChartFormat| object providing access to the shape formatting + properties of this axis, such as its line color and fill. + """ + return ChartFormat(self._element) + + @property + def has_major_gridlines(self): + """ + Read/write boolean value specifying whether this axis has gridlines + at its major tick mark locations. Assigning |True| to this property + causes major gridlines to be displayed. Assigning |False| causes them + to be removed. + """ + if self._element.majorGridlines is None: + return False + return True + + @has_major_gridlines.setter + def has_major_gridlines(self, value): + if bool(value) is True: + self._element.get_or_add_majorGridlines() + else: + self._element._remove_majorGridlines() + + @property + def has_minor_gridlines(self): + """ + Read/write boolean value specifying whether this axis has gridlines + at its minor tick mark locations. Assigning |True| to this property + causes minor gridlines to be displayed. Assigning |False| causes them + to be removed. + """ + if self._element.minorGridlines is None: + return False + return True + + @has_minor_gridlines.setter + def has_minor_gridlines(self, value): + if bool(value) is True: + self._element.get_or_add_minorGridlines() + else: + self._element._remove_minorGridlines() + + @property + def has_title(self): + """Read/write boolean specifying whether this axis has a title. + + |True| if this axis has a title, |False| otherwise. Assigning |True| + causes an axis title to be added if not already present. Assigning + |False| causes any existing title to be deleted. + """ + if self._element.title is None: + return False + return True + + @has_title.setter + def has_title(self, value): + if bool(value) is True: + self._element.get_or_add_title() + else: + self._element._remove_title() + + @lazyproperty + def major_gridlines(self): + """ + The |MajorGridlines| object representing the major gridlines for + this axis. + """ + return MajorGridlines(self._element) + + @property + def major_tick_mark(self): + """ + Read/write :ref:`XlTickMark` value specifying the type of major tick + mark to display on this axis. + """ + majorTickMark = self._element.majorTickMark + if majorTickMark is None: + return XL_TICK_MARK.CROSS + return majorTickMark.val + + @major_tick_mark.setter + def major_tick_mark(self, value): + self._element._remove_majorTickMark() + if value is XL_TICK_MARK.CROSS: + return + self._element._add_majorTickMark(val=value) + + @property + def maximum_scale(self): + """ + Read/write float value specifying the upper limit of the value range + for this axis, the number at the top or right of the vertical or + horizontal value scale, respectively. The value |None| indicates the + upper limit should be determined automatically based on the range of + data point values associated with the axis. + """ + return self._element.scaling.maximum + + @maximum_scale.setter + def maximum_scale(self, value): + scaling = self._element.scaling + scaling.maximum = value + + @property + def minimum_scale(self): + """ + Read/write float value specifying lower limit of value range, the + number at the bottom or left of the value scale. |None| if no minimum + scale has been set. The value |None| indicates the lower limit should + be determined automatically based on the range of data point values + associated with the axis. + """ + return self._element.scaling.minimum + + @minimum_scale.setter + def minimum_scale(self, value): + scaling = self._element.scaling + scaling.minimum = value + + @property + def minor_tick_mark(self): + """ + Read/write :ref:`XlTickMark` value specifying the type of minor tick + mark for this axis. + """ + minorTickMark = self._element.minorTickMark + if minorTickMark is None: + return XL_TICK_MARK.CROSS + return minorTickMark.val + + @minor_tick_mark.setter + def minor_tick_mark(self, value): + self._element._remove_minorTickMark() + if value is XL_TICK_MARK.CROSS: + return + self._element._add_minorTickMark(val=value) + + @property + def reverse_order(self): + """Read/write bool value specifying whether to reverse plotting order for axis. + + For a category axis, this reverses the order in which the categories are + displayed. This may be desired, for example, on a (horizontal) bar-chart where + by default the first category appears at the bottom. Since we read from + top-to-bottom, many viewers may find it most natural for the first category to + appear on top. + + For a value axis, it reverses the direction of increasing value from + bottom-to-top to top-to-bottom. + """ + return self._element.orientation == ST_Orientation.MAX_MIN + + @reverse_order.setter + def reverse_order(self, value): + self._element.orientation = ( + ST_Orientation.MAX_MIN if bool(value) is True else ST_Orientation.MIN_MAX + ) + + @lazyproperty + def tick_labels(self): + """ + The |TickLabels| instance providing access to axis tick label + formatting properties. Tick labels are the numbers appearing on + a value axis or the category names appearing on a category axis. + """ + return TickLabels(self._element) + + @property + def tick_label_position(self): + """ + Read/write :ref:`XlTickLabelPosition` value specifying where the tick + labels for this axis should appear. + """ + tickLblPos = self._element.tickLblPos + if tickLblPos is None: + return XL_TICK_LABEL_POSITION.NEXT_TO_AXIS + if tickLblPos.val is None: + return XL_TICK_LABEL_POSITION.NEXT_TO_AXIS + return tickLblPos.val + + @tick_label_position.setter + def tick_label_position(self, value): + tickLblPos = self._element.get_or_add_tickLblPos() + tickLblPos.val = value + + @property + def visible(self): + """ + Read/write. |True| if axis is visible, |False| otherwise. + """ + delete = self._element.delete_ + if delete is None: + return False + return False if delete.val else True + + @visible.setter + def visible(self, value): + if value not in (True, False): + raise ValueError("assigned value must be True or False, got: %s" % value) + delete = self._element.get_or_add_delete_() + delete.val = not value + + +class AxisTitle(ElementProxy): + """Provides properties for manipulating axis title.""" + + def __init__(self, title): + super(AxisTitle, self).__init__(title) + self._title = title + + @lazyproperty + def format(self): + """|ChartFormat| object providing access to shape formatting. + + Return the |ChartFormat| object providing shape formatting properties + for this axis title, such as its line color and fill. + """ + return ChartFormat(self._element) + + @property + def has_text_frame(self): + """Read/write Boolean specifying presence of a text frame. + + Return |True| if this axis title has a text frame, and |False| + otherwise. Assigning |True| causes a text frame to be added if not + already present. Assigning |False| causes any existing text frame to + be removed along with any text contained in the text frame. + """ + if self._title.tx_rich is None: + return False + return True + + @has_text_frame.setter + def has_text_frame(self, value): + if bool(value) is True: + self._title.get_or_add_tx_rich() + else: + self._title._remove_tx() + + @property + def text_frame(self): + """|TextFrame| instance for this axis title. + + Return a |TextFrame| instance allowing read/write access to the text + of this axis title and its text formatting properties. Accessing this + property is destructive as it adds a new text frame if not already + present. + """ + rich = self._title.get_or_add_tx_rich() + return TextFrame(rich, self) + + +class CategoryAxis(_BaseAxis): + """A category axis of a chart.""" + + @property + def category_type(self): + """ + A member of :ref:`XlCategoryType` specifying the scale type of this + axis. Unconditionally ``CATEGORY_SCALE`` for a |CategoryAxis| object. + """ + return XL_CATEGORY_TYPE.CATEGORY_SCALE + + +class DateAxis(_BaseAxis): + """A category axis with dates as its category labels. + + This axis-type has some special display behaviors such as making length of equal + periods equal and normalizing month start dates despite unequal month lengths. + """ + + @property + def category_type(self): + """ + A member of :ref:`XlCategoryType` specifying the scale type of this + axis. Unconditionally ``TIME_SCALE`` for a |DateAxis| object. + """ + return XL_CATEGORY_TYPE.TIME_SCALE + + +class MajorGridlines(ElementProxy): + """Provides access to the properties of the major gridlines appearing on an axis.""" + + def __init__(self, xAx): + super(MajorGridlines, self).__init__(xAx) + self._xAx = xAx # axis element, catAx or valAx + + @lazyproperty + def format(self): + """ + The |ChartFormat| object providing access to the shape formatting + properties of this data point, such as line and fill. + """ + majorGridlines = self._xAx.get_or_add_majorGridlines() + return ChartFormat(majorGridlines) + + +class TickLabels(object): + """A service class providing access to formatting of axis tick mark labels.""" + + def __init__(self, xAx_elm): + super(TickLabels, self).__init__() + self._element = xAx_elm + + @lazyproperty + def font(self): + """ + The |Font| object that provides access to the text properties for + these tick labels, such as bold, italic, etc. + """ + defRPr = self._element.defRPr + font = Font(defRPr) + return font + + @property + def number_format(self): + """ + Read/write string (e.g. "$#,##0.00") specifying the format for the + numbers on this axis. The syntax for these strings is the same as it + appears in the PowerPoint or Excel UI. Returns 'General' if no number + format has been set. Note that this format string has no effect on + rendered tick labels when :meth:`number_format_is_linked` is |True|. + Assigning a format string to this property automatically sets + :meth:`number_format_is_linked` to |False|. + """ + numFmt = self._element.numFmt + if numFmt is None: + return "General" + return numFmt.formatCode + + @number_format.setter + def number_format(self, value): + numFmt = self._element.get_or_add_numFmt() + numFmt.formatCode = value + self.number_format_is_linked = False + + @property + def number_format_is_linked(self): + """ + Read/write boolean specifying whether number formatting should be + taken from the source spreadsheet rather than the value of + :meth:`number_format`. + """ + numFmt = self._element.numFmt + if numFmt is None: + return False + souceLinked = numFmt.sourceLinked + if souceLinked is None: + return True + return numFmt.sourceLinked + + @number_format_is_linked.setter + def number_format_is_linked(self, value): + numFmt = self._element.get_or_add_numFmt() + numFmt.sourceLinked = value + + @property + def offset(self): + """ + Read/write int value in range 0-1000 specifying the spacing between + the tick mark labels and the axis as a percentange of the default + value. 100 if no label offset setting is present. + """ + lblOffset = self._element.lblOffset + if lblOffset is None: + return 100 + return lblOffset.val + + @offset.setter + def offset(self, value): + if self._element.tag != qn("c:catAx"): + raise ValueError("only a category axis has an offset") + self._element._remove_lblOffset() + if value == 100: + return + lblOffset = self._element._add_lblOffset() + lblOffset.val = value + + +class ValueAxis(_BaseAxis): + """An axis having continuous (as opposed to discrete) values. + + The vertical axis is generally a value axis, however both axes of an XY-type chart + are value axes. + """ + + @property + def crosses(self): + """ + Member of :ref:`XlAxisCrosses` enumeration specifying the point on + this axis where the other axis crosses, such as auto/zero, minimum, + or maximum. Returns `XL_AXIS_CROSSES.CUSTOM` when a specific numeric + crossing point (e.g. 1.5) is defined. + """ + crosses = self._cross_xAx.crosses + if crosses is None: + return XL_AXIS_CROSSES.CUSTOM + return crosses.val + + @crosses.setter + def crosses(self, value): + cross_xAx = self._cross_xAx + if value == XL_AXIS_CROSSES.CUSTOM: + if cross_xAx.crossesAt is not None: + return + cross_xAx._remove_crosses() + cross_xAx._remove_crossesAt() + if value == XL_AXIS_CROSSES.CUSTOM: + cross_xAx._add_crossesAt(val=0.0) + else: + cross_xAx._add_crosses(val=value) + + @property + def crosses_at(self): + """ + Numeric value on this axis at which the perpendicular axis crosses. + Returns |None| if no crossing value is set. + """ + crossesAt = self._cross_xAx.crossesAt + if crossesAt is None: + return None + return crossesAt.val + + @crosses_at.setter + def crosses_at(self, value): + cross_xAx = self._cross_xAx + cross_xAx._remove_crosses() + cross_xAx._remove_crossesAt() + if value is None: + return + cross_xAx._add_crossesAt(val=value) + + @property + def major_unit(self): + """ + The float number of units between major tick marks on this value + axis. |None| corresponds to the 'Auto' setting in the UI, and + specifies the value should be calculated by PowerPoint based on the + underlying chart data. + """ + majorUnit = self._element.majorUnit + if majorUnit is None: + return None + return majorUnit.val + + @major_unit.setter + def major_unit(self, value): + self._element._remove_majorUnit() + if value is None: + return + self._element._add_majorUnit(val=value) + + @property + def minor_unit(self): + """ + The float number of units between minor tick marks on this value + axis. |None| corresponds to the 'Auto' setting in the UI, and + specifies the value should be calculated by PowerPoint based on the + underlying chart data. + """ + minorUnit = self._element.minorUnit + if minorUnit is None: + return None + return minorUnit.val + + @minor_unit.setter + def minor_unit(self, value): + self._element._remove_minorUnit() + if value is None: + return + self._element._add_minorUnit(val=value) + + @property + def _cross_xAx(self): + """ + The axis element in the same group (primary/secondary) that crosses + this axis. + """ + crossAx_id = self._element.crossAx.val + expr = '(../c:catAx | ../c:valAx | ../c:dateAx)/c:axId[@val="%d"]' % crossAx_id + cross_axId = self._element.xpath(expr)[0] + return cross_axId.getparent() |