Skip to content

Commit 5b0624e

Browse files
committed
cht: add _BaseAxis.reverse_order getter
1 parent 7e48ef2 commit 5b0624e

File tree

6 files changed

+76
-8
lines changed

6 files changed

+76
-8
lines changed

features/cht-axis-props.feature

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,6 @@ Feature: Axis properties
164164
Then axis.major_gridlines is a MajorGridlines object
165165

166166

167-
@wip
168167
Scenario Outline: Get Axis.reverse_order
169168
Given an axis having reverse-order turned <status>
170169
Then axis.reverse_order is <expected-value>

pptx/chart/axis.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
XL_TICK_MARK,
1111
)
1212
from pptx.oxml.ns import qn
13+
from pptx.oxml.simpletypes import ST_Orientation
1314
from pptx.shared import ElementProxy
1415
from pptx.text.text import Font, TextFrame
1516
from pptx.util import lazyproperty
@@ -175,6 +176,21 @@ def minor_tick_mark(self, value):
175176
return
176177
self._element._add_minorTickMark(val=value)
177178

179+
@property
180+
def reverse_order(self):
181+
"""Read/write bool value specifying whether to reverse plotting order for axis.
182+
183+
For a category axis, this reverses the order in which the categories are
184+
displayed. This may be desired, for example, on a (horizontal) bar-chart where
185+
by default the first category appears at the bottom. Since we read from
186+
top-to-bottom, many viewers may find it most natural for the first category to
187+
appear on top.
188+
189+
For a value axis, it reverses the direction of increasing value from
190+
bottom-to-top to top-to-bottom.
191+
"""
192+
return self._element.orientation == ST_Orientation.MAX_MIN
193+
178194
@lazyproperty
179195
def tick_labels(self):
180196
"""

pptx/oxml/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ def register_element_cls(nsptagname, cls):
6565
CT_Crosses,
6666
CT_DateAx,
6767
CT_LblOffset,
68+
CT_Orientation,
6869
CT_Scaling,
6970
CT_TickLblPos,
7071
CT_TickMark,
@@ -80,6 +81,7 @@ def register_element_cls(nsptagname, cls):
8081
register_element_cls("c:majorUnit", CT_AxisUnit)
8182
register_element_cls("c:minorTickMark", CT_TickMark)
8283
register_element_cls("c:minorUnit", CT_AxisUnit)
84+
register_element_cls("c:orientation", CT_Orientation)
8385
register_element_cls("c:scaling", CT_Scaling)
8486
register_element_cls("c:tickLblPos", CT_TickLblPos)
8587
register_element_cls("c:valAx", CT_ValAx)

pptx/oxml/chart/axis.py

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
from pptx.enum.chart import XL_AXIS_CROSSES, XL_TICK_LABEL_POSITION, XL_TICK_MARK
88
from pptx.oxml.chart.shared import CT_Title
9-
from pptx.oxml.simpletypes import ST_AxisUnit, ST_LblOffset
9+
from pptx.oxml.simpletypes import ST_AxisUnit, ST_LblOffset, ST_Orientation
1010
from pptx.oxml.text import CT_TextBody
1111
from pptx.oxml.xmlchemy import (
1212
BaseOxmlElement,
@@ -30,6 +30,18 @@ def defRPr(self):
3030
defRPr = txPr.defRPr
3131
return defRPr
3232

33+
@property
34+
def orientation(self):
35+
"""Value of `val` attribute of `c:scaling/c:orientation` grandchild element.
36+
37+
Defaults to `ST_Orientation.MIN_MAX` if attribute or any ancestors are not
38+
present.
39+
"""
40+
orientation = self.scaling.orientation
41+
if orientation is None:
42+
return ST_Orientation.MIN_MAX
43+
return orientation.val
44+
3345
def _new_title(self):
3446
return CT_Title.new_title()
3547

@@ -155,13 +167,25 @@ class CT_LblOffset(BaseOxmlElement):
155167
val = OptionalAttribute("val", ST_LblOffset, default=100)
156168

157169

170+
class CT_Orientation(BaseOxmlElement):
171+
"""`c:xAx/c:scaling/c:orientation` element, defining category order.
172+
173+
Used to reverse the order categories appear in on a bar chart so they start at the
174+
top rather than the bottom. Because we read top-to-bottom, the default way looks odd
175+
to many and perhaps most folks. Also applicable to value and date axes.
176+
"""
177+
178+
val = OptionalAttribute("val", ST_Orientation, default=ST_Orientation.MIN_MAX)
179+
180+
158181
class CT_Scaling(BaseOxmlElement):
159182
"""`c:scaling` element.
160183
161184
Defines axis scale characteristics such as maximum value, log vs. linear, etc.
162185
"""
163186

164187
_tag_seq = ("c:logBase", "c:orientation", "c:max", "c:min", "c:extLst")
188+
orientation = ZeroOrOne("c:orientation", successors=_tag_seq[2:])
165189
max = ZeroOrOne("c:max", successors=_tag_seq[3:])
166190
min = ZeroOrOne("c:min", successors=_tag_seq[4:])
167191
del _tag_seq

pptx/oxml/simpletypes.py

Lines changed: 17 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
# encoding: utf-8
22

3-
"""
4-
Simple type classes, providing validation and format translation for values
5-
stored in XML element attributes. Naming generally corresponds to the simple
6-
type in the associated XML schema.
7-
"""
3+
"""Simple-type classes.
84
9-
from __future__ import absolute_import, print_function
5+
A "simple-type" is a scalar type, generally serving as an XML attribute. This is in
6+
contrast to a "complex-type" which would specify an XML element.
7+
8+
These objects providing validation and format translation for values stored in XML
9+
element attributes. Naming generally corresponds to the simple type in the associated
10+
XML schema.
11+
"""
1012

1113
import numbers
1214

@@ -480,6 +482,15 @@ def validate(cls, value):
480482
cls.validate_int_in_range(value, 2, 72)
481483

482484

485+
class ST_Orientation(XsdStringEnumeration):
486+
"""Valid values for `val` attribute on c:orientation (CT_Orientation)."""
487+
488+
MAX_MIN = "maxMin"
489+
MIN_MAX = "minMax"
490+
491+
_members = (MAX_MIN, MIN_MAX)
492+
493+
483494
class ST_Overlap(BaseIntType):
484495
"""
485496
String value is an integer in range -100..100, representing a percent,

tests/chart/test_axis.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ def it_can_change_its_minor_tick_mark(self, minor_tick_set_fixture):
112112
axis.minor_tick_mark = new_value
113113
assert axis._element.xml == expected_xml
114114

115+
def it_knows_whether_it_renders_in_reverse_order(self, reverse_order_get_fixture):
116+
xAx, expected_value = reverse_order_get_fixture
117+
assert _BaseAxis(xAx).reverse_order == expected_value
118+
115119
def it_knows_its_tick_label_position(self, tick_lbl_pos_get_fixture):
116120
axis, expected_value = tick_lbl_pos_get_fixture
117121
assert axis.tick_label_position == expected_value
@@ -475,6 +479,18 @@ def minor_tick_set_fixture(self, request):
475479
expected_xml = xml(expected_xAx_cxml)
476480
return axis, new_value, expected_xml
477481

482+
@pytest.fixture(
483+
params=[
484+
("c:catAx/c:scaling", False),
485+
("c:valAx/c:scaling/c:orientation", False),
486+
("c:catAx/c:scaling/c:orientation{val=minMax}", False),
487+
("c:valAx/c:scaling/c:orientation{val=maxMin}", True),
488+
]
489+
)
490+
def reverse_order_get_fixture(self, request):
491+
xAx_cxml, expected_value = request.param
492+
return element(xAx_cxml), expected_value
493+
478494
@pytest.fixture(params=["c:catAx", "c:dateAx", "c:valAx"])
479495
def tick_labels_fixture(self, request, TickLabels_, tick_labels_):
480496
xAx_cxml = request.param

0 commit comments

Comments
 (0)