Skip to content

Commit

Permalink
Fix inversion of 3d axis.
Browse files Browse the repository at this point in the history
... which was broken because 3d axises stored and accessed their viewlim
info sometimes in xy_viewLim/zz_viewLim and sometimes in v_interval (and
likewise for datalim), leading to out-of-sync info...

Not deprecating d_interval/v_interval for now both because this needs to
go to 3.1.1 to fix the breakage and because I think storing the info in
axis attributes actually makes *more* sense than in viewLim/dataLim --
but fully switching to d_interval/v_interval needs bigger surgery.
  • Loading branch information
anntzer committed Jun 20, 2019
1 parent 63d96d2 commit 0e41317
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 83 deletions.
97 changes: 38 additions & 59 deletions lib/matplotlib/axis.py
Original file line number Diff line number Diff line change
Expand Up @@ -1890,6 +1890,36 @@ def get_minpos(self):
raise NotImplementedError()


def _make_getset_interval(method_name, lim_name, attr_name):
"""
Helper to generate ``get_{data,view}_interval`` and
``set_{data,view}_interval`` implementations.
"""

def getter(self):
# docstring inherited.
return getattr(getattr(self.axes, lim_name), attr_name)

def setter(self, vmin, vmax, ignore=False):
# docstring inherited.
if ignore:
setattr(getattr(self.axes, lim_name), attr_name, (vmin, vmax))
else:
oldmin, oldmax = getter(self)
if oldmin < oldmax:
setter(self, min(vmin, vmax, oldmin), max(vmin, vmax, oldmax),
ignore=True)
else:
setter(self, max(vmin, vmax, oldmax), min(vmin, vmax, oldmin),
ignore=True)
self.stale = True

getter.__name__ = f"get_{method_name}_interval"
setter.__name__ = f"set_{method_name}_interval"

return getter, setter


class XAxis(Axis):
__name__ = 'xaxis'
axis_name = 'x'
Expand Down Expand Up @@ -2133,39 +2163,14 @@ def get_ticks_position(self):
"default": "default", "unknown": "unknown"}[
self._get_ticks_position()]

def get_view_interval(self):
# docstring inherited
return self.axes.viewLim.intervalx

def set_view_interval(self, vmin, vmax, ignore=False):
# docstring inherited
if ignore:
self.axes.viewLim.intervalx = vmin, vmax
else:
Vmin, Vmax = self.get_view_interval()
if Vmin < Vmax:
self.axes.viewLim.intervalx = (min(vmin, vmax, Vmin),
max(vmin, vmax, Vmax))
else:
self.axes.viewLim.intervalx = (max(vmin, vmax, Vmin),
min(vmin, vmax, Vmax))
get_view_interval, set_view_interval = _make_getset_interval(
"view", "viewLim", "intervalx")
get_data_interval, set_data_interval = _make_getset_interval(
"data", "dataLim", "intervalx")

def get_minpos(self):
return self.axes.dataLim.minposx

def get_data_interval(self):
# docstring inherited
return self.axes.dataLim.intervalx

def set_data_interval(self, vmin, vmax, ignore=False):
# docstring inherited
if ignore:
self.axes.dataLim.intervalx = vmin, vmax
else:
Vmin, Vmax = self.get_data_interval()
self.axes.dataLim.intervalx = min(vmin, Vmin), max(vmax, Vmax)
self.stale = True

def set_default_intervals(self):
# docstring inherited
xmin, xmax = 0., 1.
Expand Down Expand Up @@ -2460,40 +2465,14 @@ def get_ticks_position(self):
"default": "default", "unknown": "unknown"}[
self._get_ticks_position()]

def get_view_interval(self):
# docstring inherited
return self.axes.viewLim.intervaly

def set_view_interval(self, vmin, vmax, ignore=False):
# docstring inherited
if ignore:
self.axes.viewLim.intervaly = vmin, vmax
else:
Vmin, Vmax = self.get_view_interval()
if Vmin < Vmax:
self.axes.viewLim.intervaly = (min(vmin, vmax, Vmin),
max(vmin, vmax, Vmax))
else:
self.axes.viewLim.intervaly = (max(vmin, vmax, Vmin),
min(vmin, vmax, Vmax))
self.stale = True
get_view_interval, set_view_interval = _make_getset_interval(
"view", "viewLim", "intervaly")
get_data_interval, set_data_interval = _make_getset_interval(
"data", "dataLim", "intervaly")

def get_minpos(self):
return self.axes.dataLim.minposy

def get_data_interval(self):
# docstring inherited
return self.axes.dataLim.intervaly

def set_data_interval(self, vmin, vmax, ignore=False):
# docstring inherited
if ignore:
self.axes.dataLim.intervaly = vmin, vmax
else:
Vmin, Vmax = self.get_data_interval()
self.axes.dataLim.intervaly = min(vmin, Vmin), max(vmax, Vmax)
self.stale = True

def set_default_intervals(self):
# docstring inherited
ymin, ymax = 0., 1.
Expand Down
56 changes: 32 additions & 24 deletions lib/mpl_toolkits/mplot3d/axis3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,6 @@ def __init__(self, adir, v_intervalx, d_intervalx, axes, *args,
rotate_label=None, **kwargs):
# adir identifies which axes this is
self.adir = adir
# data and viewing intervals for this direction
self.d_interval = d_intervalx
self.v_interval = v_intervalx

# This is a temporary member variable.
# Do not depend on this existing in future releases!
Expand Down Expand Up @@ -105,6 +102,10 @@ def __init__(self, adir, v_intervalx, d_intervalx, axes, *args,
})

maxis.XAxis.__init__(self, axes, *args, **kwargs)

# data and viewing intervals for this direction
self.d_interval = d_intervalx
self.v_interval = v_intervalx
self.set_rotate_label(rotate_label)

def init3d(self):
Expand Down Expand Up @@ -420,42 +421,49 @@ def draw(self, renderer):
renderer.close_group('axis3d')
self.stale = False

def get_view_interval(self):
# docstring inherited
return self.v_interval

def set_view_interval(self, vmin, vmax, ignore=False):
# docstring inherited
if ignore:
self.v_interval = vmin, vmax
else:
Vmin, Vmax = self.get_view_interval()
self.v_interval = min(vmin, Vmin), max(vmax, Vmax)

# TODO: Get this to work properly when mplot3d supports
# the transforms framework.
def get_tightbbox(self, renderer):
# Currently returns None so that Axis.get_tightbbox
# doesn't return junk info.
return None

@property
def d_interval(self):
return self.get_data_interval()

@d_interval.setter
def d_interval(self, minmax):
return self.set_data_interval(*minmax)

@property
def v_interval(self):
return self.get_view_interval()

@d_interval.setter
def v_interval(self, minmax):
return self.set_view_interval(*minmax)


# Use classes to look at different data limits


class XAxis(Axis):
def get_data_interval(self):
# docstring inherited
return self.axes.xy_dataLim.intervalx
get_view_interval, set_view_interval = maxis._make_getset_interval(
"view", "xy_viewLim", "intervalx")
get_data_interval, set_data_interval = maxis._make_getset_interval(
"data", "xy_dataLim", "intervalx")


class YAxis(Axis):
def get_data_interval(self):
# docstring inherited
return self.axes.xy_dataLim.intervaly
get_view_interval, set_view_interval = maxis._make_getset_interval(
"view", "xy_viewLim", "intervaly")
get_data_interval, set_data_interval = maxis._make_getset_interval(
"data", "xy_dataLim", "intervaly")


class ZAxis(Axis):
def get_data_interval(self):
# docstring inherited
return self.axes.zz_dataLim.intervalx
get_view_interval, set_view_interval = maxis._make_getset_interval(
"view", "zz_viewLim", "intervalx")
get_data_interval, set_data_interval = maxis._make_getset_interval(
"data", "zz_dataLim", "intervalx")
12 changes: 12 additions & 0 deletions lib/mpl_toolkits/tests/test_mplot3d.py
Original file line number Diff line number Diff line change
Expand Up @@ -801,6 +801,18 @@ def test_line3d_set_get_data_3d():
np.testing.assert_array_equal((x2, y2, z2), line.get_data_3d())


@check_figures_equal(extensions=["png"])
def test_inverted(fig_test, fig_ref):
# Plot then invert.
ax = fig_test.add_subplot(projection="3d")
ax.plot([1, 1, 10, 10], [1, 10, 10, 10], [1, 1, 1, 10])
ax.invert_yaxis()
# Invert then plot.
ax = fig_ref.add_subplot(projection="3d")
ax.invert_yaxis()
ax.plot([1, 1, 10, 10], [1, 10, 10, 10], [1, 1, 1, 10])


def test_inverted_cla():
# Github PR #5450. Setting autoscale should reset
# axes to be non-inverted.
Expand Down

0 comments on commit 0e41317

Please sign in to comment.