Apparent bug in Data limits with LineCollections

Hi all,

I'm having problems with the data limits when I use a LineCollection

Here is the relevant code. A VectorLinecollection is a subclass of LineCollection. ax is an Axes instance.

         ax.clear()
         VLC = VectorLineCollection(t, y, theta, self.Figure, ax)
         ax.add_collection(VLC)
         ax.plot(t, y, 'o')
         print "The lines are:", ax.get_lines()
         print "AutoScaleOn:", ax._autoscaleon
         print ax.dataLim.intervaly()
         print ax.dataLim.intervaly().get_bounds()
         ax.autoscale_view()

This all works fine when I call it the first time, but when I call it again, with a new LineCollection, dataLim is not re-set. If I comment out the add_collection call, and just do the plot, then dataLim does get reset when clear is called, so autoscale_view obviously doesn't work.

any ideas what's wrong? How would I force a re-set of dataLim?

This is buried in a wxmpl based program, but if I need to, I'll make a small stand-alone script that demonstrates the problem.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                         
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@...259...

Chris,

Christopher Barker wrote:

Hi all,

I'm having problems with the data limits when I use a LineCollection

Here is the relevant code. A VectorLinecollection is a subclass of LineCollection. ax is an Axes instance.

        ax.clear()
        VLC = VectorLineCollection(t, y, theta, self.Figure, ax)
        ax.add_collection(VLC)
        ax.plot(t, y, 'o')
        print "The lines are:", ax.get_lines()
        print "AutoScaleOn:", ax._autoscaleon
        print ax.dataLim.intervaly()
        print ax.dataLim.intervaly().get_bounds()
        ax.autoscale_view()

This all works fine when I call it the first time, but when I call it again, with a new LineCollection, dataLim is not re-set. If I comment out the add_collection call, and just do the plot, then dataLim does get reset when clear is called, so autoscale_view obviously doesn't work.

any ideas what's wrong? How would I force a re-set of dataLim?

It is not entirely clear to me what you mean by "call it again...", but note that autoscale_view() updates the viewLim from the dataLim; it does not update the dataLim. That is done by add_line, which is called by plot, but it is not done by add_collection, as far as I can see. The axes methods for updating dataLim are:

     def update_datalim(self, xys):
         'Update the data lim bbox with seq of xy tups'
         # if no data is set currently, the bbox will ignore it's
         # limits and set the bound to be the bounds of the xydata.
         # Otherwise, it will compute the bounds of it's current data
         # and the data in xydata
         self.dataLim.update(xys, not self.has_data())

     def update_datalim_numerix(self, x, y):
         'Update the data lim bbox with seq of xy tups'
         # if no data is set currently, the bbox will ignore it's
         # limits and set the bound to be the bounds of the xydata.
         # Otherwise, it will compute the bounds of it's current data
         # and the data in xydata
         #print type(x), type(y)
         self.dataLim.update_numerix(x, y, not self.has_data())

Eric

Eric Firing wrote:

I'm having problems with the data limits when I use a LineCollection

Here is the relevant code. A VectorLinecollection is a subclass of LineCollection. ax is an Axes instance.

        ax.clear()
        VLC = VectorLineCollection(t, y, theta, self.Figure, ax)
        ax.add_collection(VLC)
        ax.plot(t, y, 'o')
        print "The lines are:", ax.get_lines()
        print "AutoScaleOn:", ax._autoscaleon
        print ax.dataLim.intervaly()
        print ax.dataLim.intervaly().get_bounds()
        ax.autoscale_view()

It is not entirely clear to me what you mean by "call it again...",

Sorry about that. this code is in a method, and when I call that method again, it gets re-run, but with different data. What I want to happen is for the axis to auto-scale to the new data.

but note that autoscale_view() updates the viewLim from the dataLim; it does not update the dataLim.

exactly. That's why I've printed ax.dataLim.intervaly().get_bounds(). dataLim is not getting updated each time this code is run.

That is done by add_line, which is called by plot,

It looks like it should first get updated by the axes,.clear() call, then updated again each time you add something to the axes.

but it is not done by add_collection, as far as I can see.

except that it works fine the first time this code is run: add_collection seems to update dataLim, but the clear call doesn't clear it out again.

In this case, the problem is that I run this code with large data, the axis limits get set to large values. then I run it again with smaller values data, and the axis limits stay large, even after calling clear() and autoscale_view().

axes methods for updating dataLim are:

I saw those, but I don't know where they are getting called. most importantly, I don't know what is happening to dataLim in a clear() call, but it doesn't seem to be working right.

Maybe I'll try to make a small sample that demonstrates the issue. stay tuned.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                         
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@...259...

Christopher Barker wrote:

Maybe I'll try to make a small sample that demonstrates the issue. stay tuned.

OK, read the previous posts for background, or ignore them, and just read this one. I made a small sample, and found that using a LineCollection with axes.add_collection does something weird to the data limits that axes sets. I've enclosed a small sample that demonstrates the problem.

Here is the plotting code itself:

# Do some plotting
ax.plot(offsets[:,0], offsets[:,1], 'o')

# clear the axes, and try again, with different data.
ax.clear()
offsets *= .1
coll = LineCollection(segments, offsets=offsets,
                       transOffset=ax.transData, # transforms the x,y offsets
                       )
# points/72.*dpi = pixels -- see matplotlib.transforms
trans = scale_transform(fig.dpi/Value(72.), fig.dpi/Value(72.))
coll.set_transform(trans) # the points to pixels transform

ax.add_collection(coll)
ax.plot(offsets[:,0], offsets[:,1], 'o')
ax.autoscale_view()

pylab.show()

running this, you get the axes set to data limits appropriate to the first ax.plot call, before the ax.clear() call. (the second has values 1/10 as large.

However, if you don't do the first plot call, it all scales correctly. Now it's even weirder. If you do the first plot call (with the larger data), but comment out add_collection() call, it scales correctly. So it seems that using add_collection someone fixes the data limits to the previous value, even after the ax.clear() call. AFAICT, looking at the axes class code, this shouldn't happen. add_collection doesn't do much at all.

Also, after axes.clear() is called, ax.has_data() returns False, so when
axes.update_datalim is called, the old dataLim should be ignored.

by the way, it looks like axes.cla() should perhaps have this in it:

self.dataLim = unit_bbox()

or maybe:

self._set_lim_and_transforms()

I've gotten a bit lost in all this: HELP!

-Chris

test.py (1.03 KB)

···

--
Christopher Barker, Ph.D.
Oceanographer
                                         
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@...259...

Just for the record, I've solved all this, for the moment, by clearing the entire figure, and starting again with a brand new axes. It does seem like there's a bug in the axes.clear() method, however, I should be able to just .clear() and add stuff again, shouldn't I?

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                         
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@...259...

Christopher Barker wrote:

Christopher Barker wrote:

Maybe I'll try to make a small sample that demonstrates the issue. stay tuned.

OK, read the previous posts for background, or ignore them, and just read this one. I made a small sample, and found that using a LineCollection with axes.add_collection does something weird to the data limits that axes sets. I've enclosed a small sample that demonstrates the problem.

Chris,

Attached is a slight modification of your test case, with annotations. I think it does what you want--sort of. But I agree that there is a bug. After chasing references backwards and forwards through axes.py, axis.py, ticker.py, and _transforms.cpp, I don't quite understand how everything works, but I think this is part of the problem:

axes.has_data() is being used to determine whether to update the dataLim from scratch, or to start with previous values. axes.has_data() is True if any drawing artist (Line, Collection, etc.) has been added to the axes. So, after clearing the axes, adding a LineCollection tells the dataLim update method (called by the subsequent plot()) to use the old information, even though Collections (unlike Lines) do not change the dataLim. In other words, in your original script, I think that adding the collection was preventing plot() from setting the axes to a smaller value.

Workarounds: either set the dataLim explicitly, or add the collection after the plot command (as in the attached modified script). The latter only works if the plot command sets the dataLim to be large enough to cover everything in the collection.

I would like to make a genuine bugfix, but I do not yet understand all this well enough to do so right now. Maybe John will chime in with a good solution.

Eric

CBtest.py (1.49 KB)

Eric Firing wrote:

Attached is a slight modification of your test case, with annotations.

Thanks Eric, that does work. I'd rather the Z-order be set with the LineCollection under the plot, but it doesn't matter much, and I can probably manipulate that by hand if need be.

think it does what you want--sort of. But I agree that there is a bug. After chasing references backwards and forwards through axes.py, axis.py, ticker.py, and _transforms.cpp, I don't quite understand how everything works,

You got father than me.

Workarounds: either set the dataLim explicitly, or add the collection after the plot command (as in the attached modified script). The latter only works if the plot command sets the dataLim to be large enough to cover everything in the collection.

Yes, I may need to set dataLim myself after all.

I would like to make a genuine bugfix, but I do not yet understand all this well enough to do so right now. Maybe John will chime in with a good solution.

I think the relevant questions are these:

Is there a reason that add_collection doesn't reset the dataLim?

Is there a reason that axes.cla() doesn't reset the dataLim?

The fix will depend on those answers. I'd like to see both done.

John?

Thanks for your help, Eric.

-Chris

···

--
Christopher Barker, Ph.D.
Oceanographer
                                         
NOAA/OR&R/HAZMAT (206) 526-6959 voice
7600 Sand Point Way NE (206) 526-6329 fax
Seattle, WA 98115 (206) 526-6317 main reception

Chris.Barker@...259...

Chris,

I will forward to matplotlib-users a message from John including text that he had intended to send to that list and to you, and that answers some of your questions. I hope to have the problem fixed in cvs this weekend.

Eric

Christopher Barker wrote:
[.......]

···

I think the relevant questions are these:

Is there a reason that add_collection doesn't reset the dataLim?

Is there a reason that axes.cla() doesn't reset the dataLim?

The fix will depend on those answers. I'd like to see both done.

John?

Thanks for your help, Eric.

-Chris