From: C M <cmp...@gm...> - 2010-02-04 02:59:28
|
I'm using autoscale_view for the y axis, but find with a marker size > about 10, it will autoscale the graphs such that some markers are bisected by the edges of the frame. I already have it set to: self.subplot.autoscale_view(tight=False, scalex=False, scaley=True) so I'd basically like "tight" here to be "even less tight". For example, for a graph of time in minutes along the y axis, I'd like the bottom of the graph to actually be a bit below zero to catch events that are 0.5 min, etc., without them being half-buried under the edge of the graph. Can autoscale_view be altered a bit to allow for a more generous view? Thanks, Che |
From: Stan W. <sta...@nr...> - 2010-02-12 22:10:52
|
> From: C M [mailto:cmp...@gm...] > Sent: Wednesday, February 03, 2010 21:59 > > I'm using autoscale_view for the y axis, but find with a marker size > > about 10, it will autoscale the graphs such that some markers are > bisected by the edges of the frame. I already have it set to: > > self.subplot.autoscale_view(tight=False, scalex=False, > scaley=True) > > so I'd basically like "tight" here to be "even less tight". For > example, for a graph of time in minutes along the y axis, I'd like the > bottom of the graph to actually be a bit below zero to catch events > that are 0.5 min, etc., without them being half-buried under the edge > of the graph. > > Can autoscale_view be altered a bit to allow for a more generous view? For a similar requirement, I made the following custom locator: ---- import numpy as np import matplotlib as mpl import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms class LooseMaxNLocator(mticker.MaxNLocator): def __init__(self, margin = 0.05, **kwargs): mticker.MaxNLocator.__init__(self, **kwargs) self._margin = margin def autoscale(self): dmin, dmax = self.axis.get_data_interval() if self._symmetric: maxabs = max(abs(dmin), abs(dmax)) dmin = -maxabs dmax = maxabs dmin, dmax = mtransforms.nonsingular(dmin, dmax, expander = 0.05) margin = self._margin * (dmax - dmin) vmin = dmin - margin vmax = dmax + margin bin_boundaries = self.bin_boundaries(vmin, vmax) vmin = min(vmin, max(bin_boundaries[bin_boundaries <= dmin])) vmax = max(vmax, min(bin_boundaries[bin_boundaries >= dmax])) return np.array([vmin, vmax]) ---- The *margin* argument controls the looseness. For a given axis *ax*, you instantiate and apply the locator with something like ax.xaxis.set_major_locator(LooseMaxNLocator(nbins=7, steps=[1, 2, 5, 10])) and likewise for the Y axis. I believe that if the plot has already been drawn, you have to somehow force an autoscaling. I wrote that about 1.5 years ago for an earlier version of matplotlib, and I don't know how compatible it is with the current ticker.py code. In particular, you might need to override *view_limits* instead of *autoscale*. Anyway, I hope it's useful to you. |
From: C M <cmp...@gm...> - 2010-02-12 22:15:23
|
On Fri, Feb 12, 2010 at 2:24 PM, Stan West <sta...@nr...> wrote: >> From: C M [mailto:cmp...@gm...] >> Sent: Wednesday, February 03, 2010 21:59 >> >> I'm using autoscale_view for the y axis, but find with a marker size > >> about 10, it will autoscale the graphs such that some markers are >> bisected by the edges of the frame. I already have it set to: >> >> self.subplot.autoscale_view(tight=False, scalex=False, >> scaley=True) >> >> so I'd basically like "tight" here to be "even less tight". For >> example, for a graph of time in minutes along the y axis, I'd like the >> bottom of the graph to actually be a bit below zero to catch events >> that are 0.5 min, etc., without them being half-buried under the edge >> of the graph. >> >> Can autoscale_view be altered a bit to allow for a more generous view? > > For a similar requirement, I made the following custom locator: Thank you. I've been playing around with a way to do this, and may have something working now from my attempt to modify the autoscale_view function in axes.py. My own re-write is below. It's barely different from what is in the original function. It just gets the two edges of the bounding box that contains all the lines and moves them out a bit. I would like to understand your approach better. So far, I can't get your code to produce the "margins" indicated--but I'm probably applying it wrongly. I don't know how to force an autoscale, for example. Your code is tough for me to understand because there are a number of things you make use of that I'm not familiar with yet. I could ask a number of questions but don't want to burden the list with that unless people are up for it. Thanks, Che # autoscale_view function that allows looser edges. def loose_autoscale_view(self, subplot, margin, tight=False, scalex=True, scaley=True): """ autoscale the view limits using the data limits. You can selectively autoscale only a single axis, eg, the xaxis by setting *scaley* to *False*. The autoscaling preserves any axis direction reversal that has already been done. """ # if image data only just use the datalim if not self.subplot._autoscaleon: return if scalex: xshared = self.subplot._shared_x_axes.get_siblings(self.subplot) dl = [ax.dataLim for ax in xshared] bb = mtransforms.BboxBase.union(dl) xdiff = bb.intervalx[1] - bb.intervalx[0] x0 = bb.intervalx[0]-xdiff * margin x1 = bb.intervalx[1]+xdiff * margin if scaley: yshared = self.subplot._shared_y_axes.get_siblings(self.subplot) dl = [ax.dataLim for ax in yshared] bb = mtransforms.BboxBase.union(dl) y0 = bb.intervaly[0]-(bb.intervaly[1]* margin) y1 = bb.intervaly[1]* (1+margin) if (tight or (len(self.subplot.images)>0 and len(self.subplot.lines)==0 and len(self.subplot.patches)==0)): if scalex: self.subplot.set_xbound(x0, x1) if scaley: self.subplot.set_ybound(y0, y1) return if scalex: XL = self.subplot.xaxis.get_major_locator().view_limits(x0, x1) self.subplot.set_xbound(XL) if scaley: YL = self.subplot.yaxis.get_major_locator().view_limits(y0, y1) self.subplot.set_ybound(YL) #Then it would be called with: self.loose_autoscale_view(self.subplot, 0.02, tight=False, scalex=True, scaley=True) #where self.subplot is my axes object. (self is a panel class) |
From: Stan W. <sta...@nr...> - 2010-02-18 21:48:38
|
> From: C M [mailto:cmp...@gm...] > Sent: Friday, February 12, 2010 17:15 > > I would like to understand your approach better. So far, I can't get > your code to produce the "margins" indicated--but I'm probably > applying it wrongly. I don't know how to force an autoscale, for > example. Your code is tough for me to understand because there are a > number of things you make use of that I'm not familiar with yet. I > could ask a number of questions but don't want to burden the list with > that unless people are up for it. Okay. The basic idea is that axes.autoscale_view(tight=False) already has the capacity to obtain whatever view limits are returned by each axis' major tick locator, so we don't need to alter the autoscale_view code within matplotlib; we just implement a locator that yields limits we like. I based my locator on MaxNLocator, but you could use a different base. Only two methods inherited from MaxNLocator need to be modified -- the __init__ method to store the margin, and the view_limits method to implement the looser limits. We attach an instance of the locator to an axis using the axis' set_major_locator method, and if we've already plotted and need to autoscale, we invoke axes.autoscale_view. To later change the margin (or the parameters handled by MaxNLocator), we can attach a new instance. I updated my code for matplotlib 0.99.1, added some comments and examples, and have attached it below. I hope it's helpful as an example. ---- import numpy as np import matplotlib as mpl import matplotlib.ticker as mticker import matplotlib.transforms as mtransforms class LooseMaxNLocator(mticker.MaxNLocator): """ Select no more than N intervals at nice locations with view limits loosely fitted to the data. Unlike MaxNLocator, the view limits do not necessarily coincide with tick locations. """ def __init__(self, margin = 0.0, **kwargs): """ Keyword arguments: *margin* Specifies the minimum size of both the lower and upper margins (between the view limits and the data limits) as a fraction of the data range. Must be non-negative. Remaining keyword arguments are passed to MaxNLocator. """ mticker.MaxNLocator.__init__(self, **kwargs) if margin < 0: raise ValueError('The margin must be non-negative.') self._margin = margin def view_limits(self, dmin, dmax): # begin partial duplication of MaxNLocator.view_limits if self._symmetric: maxabs = max(abs(dmin), abs(dmax)) dmin = -maxabs dmax = maxabs dmin, dmax = mtransforms.nonsingular(dmin, dmax, expander=0.05) # end duplication margin = self._margin * (dmax - dmin) # fraction of data range vmin = dmin - margin # expand the view vmax = dmax + margin bin_boundaries = self.bin_boundaries(vmin, vmax) # locate ticks with MaxNLocator # Note: If the lines below change vmin or vmax, the bin boundaries # later calculated by MaxNLocator.__call__ may differ from those # calculated here. vmin = min(vmin, max(bin_boundaries[bin_boundaries <= dmin])) # expand view to the highest tick below or touching the data vmax = max(vmax, min(bin_boundaries[bin_boundaries >= dmax])) # expand view to the lowest tick above or touching the data return np.array([vmin, vmax]) # Examples import matplotlib.pyplot as plt fig1 = plt.figure() ax1 = fig1.add_subplot(1, 1, 1) ax1.set_xlabel('default locator') ax1.set_ylabel('LooseMaxNLocator') ax1.yaxis.set_major_locator( LooseMaxNLocator(nbins=9, steps=[1, 2, 5, 10], margin=0.125)) # Set our locator before we plot. ax1.plot([0, 0.95], [0, 0.95]) # Our locator's view limits are used. fig2 = plt.figure() ax2 = fig2.add_subplot(1, 1, 1) ax2.set_xlabel('default locator') ax2.set_ylabel('LooseMaxNLocator') ax2.plot([0, 1.05], [0, 1.05]) # The default locator's view limits are used. ax2.yaxis.set_major_locator( LooseMaxNLocator(nbins=9, steps=[1, 2, 5, 10], margin=0.125)) # Now set our locator. ax2.autoscale_view() # Autoscale activates our locator's view limits. |