clipping and linewidth/circle radius

I'm trying to deal nicely with the clipping that happens when, for example, a line has data that is inside its clipping box, but the linewidth forces part of the line to be drawn outside of the clipping box. This is visible on the spine placement demo at http://matplotlib.sourceforge.net/examples/pylab_examples/spine_placement_demo.html, for example (the clipping at the top and bottom of most of the sine waves). This has been brought up here (or on the -devel list) before, and it was suggested to just set the clipping off, but that won't work in my case because sometimes I want to use that clipping box to limit the data shown.

The best solution I can think of is to expand the clipping box by padding it with the line width. For something like a scatter plot, I would also be okay with expanding the clipping box by padding by the max radius of a circle in the circle collection. However, I can't quite figure out how to do this with the transform framework. If I just pad the clip box using the padded() method, it seems to make it a static Bbox instead of a TransformedBbox, and my line disappears. Can someone help?

Here is the example code I'm using. I don't know what to put in for the ???, which is all that I think I need.

import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
ax.plot([0,1],[1,1],lw=20)
ax.spines['right'].set_color('none')
ax.spines['top'].set_color('none')
ax.set_ylim([0,1])
line=ax.lines[0]

# I still want clipping for things that are outside of the original clip box+linewidth padding
# so I don't want to take off clipping; I guess it doesn't matter for this example, but it does for
# more complicated examples.
#line.set_clip_on(False)

bb=line.get_clip_box()

# How can I pad bb so that it is padded by linewidth/2*1.0/72*fig.dpi_scale_trans.transform_point([1,1]) dots?
padded_bb=???
line.set_clip_box(padded_bb)
fig.savefig('test.png')

Another option I thought of was separating out masking the data from clipping the graphics. I suppose if I could get the data for the line and mask it to be within the clipbox, but then just set clipping off, I would still have the benefit of clipping things that were way outside of my bounding box, but letting things like an extra bit of linewidth through. However, this requires doing things like computing intersections of the line and the bounding box so that I can insert an extra point for the "end" of the line at the bounding box. This gets harder when the line is a spline or something like that.

Thanks for being patient with me while I learn my way around matplotlib's excellent transformation framework.

Jason

···

--
Jason Grout

I'm trying to deal nicely with the clipping that happens when, for example, a line has data that is inside its clipping box, but the linewidth forces part of the line to be drawn outside of the clipping box. This is visible on the spine placement demo at http://matplotlib.sourceforge.net/examples/pylab_examples/spine_placement_demo.html, for example (the clipping at the top and bottom of most of the sine waves). This has been brought up here (or on the -devel list) before, and it was suggested to just set the clipping off, but that won't work in my case because sometimes I want to use that clipping box to limit the data shown.

The best solution I can think of is to expand the clipping box by padding it with the line width. For something like a scatter plot, I would also be okay with expanding the clipping box by padding by the max radius of a circle in the circle collection. However, I can't quite figure out how to do this with the transform framework. If I just pad the clip box using the padded() method, it seems to make it a static Bbox instead of a TransformedBbox, and my line disappears. Can someone help?
  

Yeah -- that sounds rather painful. We don't currently have a way to dynamically grow a bbox like this and have it updated on zooming/panning etc. I think what ultimately needs to happen is that clipping is made aware of the spine placement (which is a relatively new feature) and automatically deal with these small adjustments of the clipping box. I'm thinking of the Axes clip_box becoming something dynamically calculated based on the placement of the spines. But I think it will be much harder to do it from the outside. Of course, it's hard to say if this is the right thing to do in the general case -- we'll always end up clipping some marker or another if the limits of the axes are anything but the limits of the actual data. Hmm... I'll have to think on this some more.

Another option I thought of was separating out masking the data from clipping the graphics. I suppose if I could get the data for the line and mask it to be within the clipbox, but then just set clipping off, I would still have the benefit of clipping things that were way outside of my bounding box, but letting things like an extra bit of linewidth through. However, this requires doing things like computing intersections of the line and the bounding box so that I can insert an extra point for the "end" of the line at the bounding box. This gets harder when the line is a spline or something like that.
  

Do you care about panning and zooming, or are you just creating static plots? If static, then you can just pass in a masked array as the data and all of these things should be handled automatically (with the exception of splines). Something like:

In [1]: X = np.arange(0, np.pi * 2.0, 0.001)

In [3]: Y = np.sin(X)

In [4]: X = ma.masked_where(X > np.pi * 1.5, X)

In [5]: Y = ma.masked_where((Y < -0.5) | (Y > 0.5), Y)

In [6]: plot(X, Y)

Cheers,
Mike

···

jason-sage@...2130... wrote:

--
Michael Droettboom
Science Software Branch
Operations and Engineering Division
Space Telescope Science Institute
Operated by AURA for NASA

Michael Droettboom wrote:

I'm trying to deal nicely with the clipping that happens when, for example, a line has data that is inside its clipping box, but the linewidth forces part of the line to be drawn outside of the clipping box. This is visible on the spine placement demo at http://matplotlib.sourceforge.net/examples/pylab_examples/spine_placement_demo.html, for example (the clipping at the top and bottom of most of the sine waves). This has been brought up here (or on the -devel list) before, and it was suggested to just set the clipping off, but that won't work in my case because sometimes I want to use that clipping box to limit the data shown.

The best solution I can think of is to expand the clipping box by padding it with the line width. For something like a scatter plot, I would also be okay with expanding the clipping box by padding by the max radius of a circle in the circle collection. However, I can't quite figure out how to do this with the transform framework. If I just pad the clip box using the padded() method, it seems to make it a static Bbox instead of a TransformedBbox, and my line disappears. Can someone help?
  

Yeah -- that sounds rather painful. We don't currently have a way to dynamically grow a bbox like this and have it updated on zooming/panning etc. I think what ultimately needs to happen is that clipping is made aware of the spine placement (which is a relatively new feature) and automatically deal with these small adjustments of the clipping box. I'm thinking of the Axes clip_box becoming something dynamically calculated based on the placement of the spines. But I think it will be much harder to do it from the outside. Of course, it's hard to say if this is the right thing to do in the general case -- we'll always end up clipping some marker or another if the limits of the axes are anything but the limits of the actual data. Hmm... I'll have to think on this some more.

Thanks for thinking about this. My use-case (matplotlib in Sage) is just static pictures (at least until maybe the html5 canvas backend comes around!)

Another option I thought of was separating out masking the data from clipping the graphics. I suppose if I could get the data for the line and mask it to be within the clipbox, but then just set clipping off, I would still have the benefit of clipping things that were way outside of my bounding box, but letting things like an extra bit of linewidth through. However, this requires doing things like computing intersections of the line and the bounding box so that I can insert an extra point for the "end" of the line at the bounding box. This gets harder when the line is a spline or something like that.
  

Do you care about panning and zooming, or are you just creating static plots? If static, then you can just pass in a masked array as the data and all of these things should be handled automatically (with the exception of splines). Something like:

In [1]: X = np.arange(0, np.pi * 2.0, 0.001)

In [3]: Y = np.sin(X)

In [4]: X = ma.masked_where(X > np.pi * 1.5, X)

In [5]: Y = ma.masked_where((Y < -0.5) | (Y > 0.5), Y)

In [6]: plot(X, Y)

I thought about this, but often, in Sage, we adjust the axes limits *after* we have plotted the data. So I would have to find all lines and circles and whatever else in the graph, get the data out, and mask it. Would I then have to redraw it too?

also, if it is a long, straight line defined by two points that goes out of the clip box, I probably want to insert a point at the intersection so it looks like the line is just being clipped. Between these two problems, it seemed like the better solution was just padding the clip box by the line width.

I'm still trying to understand the transformation system. In linear algebra, to make the box grow by just a bit, I would translate the clip box to the origin, scale by a certain amount (probably based on fig.dpi_scale_something_or_other), then translate the clip box back. Would this work? On the other hand, what if I hooked into the on_draw method and padded the clip box there, similar to the FAQ example of making really long text labels fit?

Thanks,

Jason

···

jason-sage@...2130... wrote: