Bug in Axes.relim when the first line is y_isdata=False and possible fix #854

Closed
wants to merge 1,135 commits into
from

Conversation

Projects
None yet
Owner

mdboom commented May 28, 2013

You can reproduce the bug with:

from matplotlib import pyplot
ax = pyplot.axes()
ax.axvline(0.5)
ax.plot([-0.1, 0, 0.2, 0.1])
ax.relim()
ax.autoscale()
(ymin, ymax) = ax.get_ylim()
print ymin, ymax
pyplot.show()

Printed ylim is -0.2 1.0 where the upper bound should be something like 0.25. You will see that there is big space between data line and the top bound.

To workaround this problem, I defined the following functions:

def _update_line_limits(self, line, updatex=True, updatey=True):
    p = line.get_path()
    if p.vertices.size > 0:
        self.dataLim.update_from_path(p, self.ignore_existing_data_limits,
                                      updatex=updatex and line.x_isdata,
                                      updatey=updatey and line.y_isdata)
        if (updatex and line.x_isdata) or (updatey and line.y_isdata):
            self.ignore_existing_data_limits = False


def relim(self):
    self.dataLim.ignore(True)
    for (udatex, updatey) in [[True, False], [False, True]]:
        self.ignore_existing_data_limits = True
        for line in self.lines:
            _update_line_limits(self, line, udatex, updatey)

    for p in self.patches:
        self._update_patch_limits(p)

If you call relim(ax) instead of ax.relim(), printed ylim is -0.1 0.25. I just simply separate the update of the line limits for x and y axis. I think you can simply replace the two functions with Axes.relim and Ax._update_line_limits.

I have currently have no time for preparing executable development version of matplotlib and its test environment. I will send a pull request at some point when I am ready, but if somebody can "pull" the snippet above and test it, please do that.

@ghost ghost assigned pelson Aug 19, 2012

Member

pelson commented Aug 19, 2012

This is still broken even with #731. Once that is in, I will take a closer look at this.

Member

pelson commented Sep 4, 2012

There is a deeper problem here. Notice how the following code results in different results:

>>> import matplotlib.pyplot as plt
>>> ax = plt.axes()
>>> plt.axvline(0.5)
>>> plt.plot([-0.1, 0, 0.2, 0.1])
>>> print ax.viewLim
Bbox('array([[ 0. , -0.2],\n       [ 3. ,  1. ]])')
>>> import matplotlib.pyplot as plt
>>> ax = plt.axes()
>>> plt.plot([-0.1, 0, 0.2, 0.1])
>>> plt.axvline(0.5)
>>> print ax.viewLim
Bbox('array([[ 0.  , -0.1 ],\n       [ 3.  ,  0.25]])')

Essentially, the problem comes down to the fact that the axes.ignore_existing_data_limits is not split into ignore x and ignore_y components. Unfortunately I don't think we can get a solution to this before the 1.2.x rc1, not least because it will need a change to some of the underlying cpp code in _path.cpp.

I should add, I don't think your ax.relim(); ax.autoscale() are strictly necessary. This should be what you get in the first place (at least, I can't get it to make any difference).

EDIT: Fixed attached code.

Member

dmcdougall commented Sep 4, 2012

@pelson I'm struggling to see the difference in those two pieces of code.

Member

pelson commented Sep 4, 2012

Doh! I have edited it now.

mdboom and others added some commits Apr 30, 2013

Fixes issue #1960. Account for right/top spine data offset on transfo…
…rm when doing spine.set_position(). Also includes a testcase for the data locations.
Switch agg backend to use pixfmt_rgba32_plain instead of pixfmt_rgba3…
…2, which fixes a problem with alpha-blending partially transparent backgrounds.
Merge pull request #1868 from pelson/alpha_background
Fixed background colour of PNGs saved with a non-zero opacity.
Merge pull request #1968 from mdboom/rotated_text
Rotated text element misalignment in Agg
Merge pull request #1929 from pelson/bbox_inches_contour_problem
Fixed failing bbox_inches='tight' case when a contour collection is empty
Fixes bug where if alpha is set and edgecolor='none', edges would sti…
…ll be rendered as black.

Also fixes test case result that hit this bug
Changes backends to support of independent face and edge alphas
Specifically, alters the base, AGG, PDF, PGF, SVG, Cairo, and Mac OS X
backends to better enable the use of RGBA color values for both fills
and edges. An explicit alpha attribute, if set, will override the
alpha channels of the color values.

Updates test results, which are now what would be expected.

Also fixes a couple bugs with handling of linestyles.
Merge pull request #1956 from Westacular/imsave_alpha
Imsave now preserves the alpha channel.
Merge remote-tracking branch 'upstream/v1.2.x'
Conflicts:
	.travis.yml
	lib/matplotlib/tests/baseline_images/test_png/pngsuite.png
	lib/matplotlib/tests/test_axes.py
Replace usage of Lena image in the gallery.
Note that lena.npy was created in PR #1924 before I realized matplotlib could read png images without PIL. That file wasn't used anywhere else, so I remove it here.
Fix `backend_driver.py`
A couple of files were removed in PR #1918, but were accidentally re-added to `backend_driver.py` by PR #1924.
Merge pull request #1976 from tonysyu/replace-lena-in-gallery
Replace usage of Lena image in the gallery.
Fix bug in SpanSelector, introduced in commit #dd325759
The useblit attribute was being set after calling new_axes,
but new_axes needs that attribute.  By setting useblit before
calling new_axes, we set it based on the initial canvas; even
though new_axes might change to a different canvas, we assume
it will be the same type of canvas (same backend), so its
supports_blit attribute will not change.
Merge pull request #1982 from efiring/SpanSelector_blit_bug
Fix bug in SpanSelector, introduced in commit dd32575

mdboom and others added some commits May 28, 2013

Merge pull request #1813 from mdboom/gtk3-version-check
GTK segfault with GTK3 and mpl_toolkits
use plt.subplots() in examples as much as possible
At the recent LBL Software Carpentry Workshop, it was pointed out that there's
an inconsistency within our documentation for how to create new figures with
subplots.

Indeed, most examples were using the old way, something like:

    fig = plt.figure()
    ax = plt.subplot(111) # or plt.add_subplot(111)

This patch changes a whole bunch of instances like the above to:

    fig, ax = plt.subplots()

We should strive to have a minimal amount of constants in our code,
especially unusual ones like `111`, which only make sense to Matlab
refugees.

I have left unchanged examples which were using axes keywords passed to
subplot() and add_subplot(), since those end up transforming things like:

    figure()
    subplot(111, axisbg='w')

to

    plt.subplots(subplot_kw=dict(axisbg='w'))

which isn't necessarily better.

I also did not touch most of the user_interfaces examples, since those did not
involve using plt, but instead explicitly imported Figure, and used the OO
approach on Figure instances.

Also updated instaces where the old "import pylab as p" convention was used to
use our standard "import matplotlib.pyplot as plt"

I have also updated some, but not all uses of subplot(121) etc, but I'm a bit
exhausted after doing all of these.
Merge pull request #454 from mdboom/xdg_config
Use a subdirectory of $XDG_CONFIG_HOME instead of ~/.matplotlibrc on Linux
Merge pull request #2050 from pelson/colormap_api
Added the from_levels_and_colors function.
Owner

mdboom commented May 28, 2013

I think the problem is deeper than @pelson suggests. _path.update_path_extents is already able to ignore an axis simply by putting the values out of order (i.e. (max, min) rather than (min, max)) for that axis. This works as designed (even if it's not the cleanest way to do things).

The problem here is that the "default" data range is (0, 1) on both axes. So when you start with a vline, it (correctly) ignores the y axis, and sets the xrange to (0.49, 0.51) and leaves the yrange as (0, 1). The subsequent auto limit operation on the line plot does not know that the previous y limits aren't meaningful, and therefore keeps them as they are (since they don't need to be expanded to show the data).

I think the fix here is to update relim and the Axes.__init__ constructor to start with the null data range rather than the unit data range. Then we know when a range isn't meaningful because it hasn't already been set to something finite.

I've attached a patch against 1.2.x (since this bug exists there, too), but I'm not entirely sure it isn't too big/risky for 1.2. I'd like to get this in for 1.3, however.

The attached PR also fixes #2061.

lib/matplotlib/transforms.py
@@ -779,6 +779,16 @@ def unit():
"""
return Bbox(Bbox._unit_values.copy())
+ _null_values = np.array([[np.inf, np.inf], [-np.inf, -np.inf]], np.float_)
@pelson

pelson May 29, 2013

Member

I can't see much point in defining this outside of the null staticmethod. Is there a performance reason?
Is there a reason for np.float_ rather than np.float or the more explicit np.float64?

@mdboom

mdboom May 29, 2013

Owner

Probably not -- just being symmetrical with unit.

lib/matplotlib/axes.py
@@ -8309,6 +8312,7 @@ def hist(self, x, bins=10, range=None, normed=False, weights=None,
self.set_autoscalex_on(_saved_autoscalex)
self.set_autoscaley_on(_saved_autoscaley)
+ self.relim()
@pelson

pelson May 29, 2013

Member

I have no idea what impact this will have. It concerns me a little - relim has some limitations which would be inherited by this change (e.g. an axes with a collection instance which then adds a hist could cut out the collection)...

@mdboom

mdboom May 29, 2013

Owner

That's true... let me see if I can find another approach...

Member

pelson commented May 29, 2013

I've attached a patch against 1.2.x (since this bug exists there, too), but I'm not entirely sure it isn't too big/risky for 1.2. I'd like to get this in for 1.3, however.

I don't think this can go into v1.2.x, the change is functional (admittedly it was unintentional behaviour in the first place).

The log scale test result is very different - its hard to know which one is expected, but it is without a doubt different behaviour. Would you mind providing your analysis on the test and why it was wrong?

Other than that, this is a nice solution - though I have to admit I'm not too fussed whether it makes it into the v1.3.x series or not.

Nice work @mdboom!

Owner

mdboom commented May 29, 2013

The log scale test adds a axvline and axhline, both at a position 24.1. In the old version, the resulting limits were (0, 100), since the initial limits on a plot were (0, 1), and the ax?line call added to it. In the new version, the resulting limits are (10, 100), since it should only include the decades around the given value. I think this is the correct behavior.

Yeah -- I think I agree about not including this in 1.2.x. I'll rebase and create a new PR for 1.3.x, also incorporating your other comments.

Member

pelson commented May 29, 2013

Yeah -- I think I agree about not including this in 1.2.x. I'll rebase and create a new PR for 1.3.x, also incorporating your other comments.

Great. And thanks for the explanation - I'm more comfortable with the change now. The only remaining work is to add an API change entry for the change.

Cheers,

@mdboom mdboom referenced this pull request May 29, 2013

Merged

Data limits (on 1.3.x) #2082

Owner

mdboom commented May 29, 2013

Closing in favor of #2082.

@mdboom mdboom closed this May 29, 2013

Contributor

megies commented on c39d9dc Sep 15, 2013

@mdboom, while debugging way too low RMS values in our own project's image tests (seems to happen for a combination of matplotlib 1.2 and numpy 1.7) I had a look at this. The injected code is without effect, rms is overwritten right afterwards in line 339.

Member

pelson replied Sep 16, 2013

Looks to be true. If you want to submit a PR deleting this code I'd happily merge it :-)

P.S. From memory, the RMS values have changed in magnitude between v1.2 and v1.3.

Contributor

megies replied Sep 16, 2013

Yes, the whole RMS calculation was turned upside down between 1.2 and 1.3.
I would have opened a PR already had I known what to make of this piece of code.. Since it was only introduced with the third-to-last change to the file it felt like it should be in there but just wasn't included in the right way. It looks like it was cherry-picked in here (?) and might have gotten garbled up in the process (?)..

Owner

mdboom replied Sep 16, 2013

I've made a PR in #2426. (Disappointingly, it doesn't seem to have a measurable impact on the time required to run the tests...)

@mdboom mdboom deleted the mdboom:data_limits branch Aug 7, 2014

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment