make autoscale_view even less tight?

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: C M [mailto:cmpython@…287…]
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.

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)

···

On Fri, Feb 12, 2010 at 2:24 PM, Stan West <stan.west@...706...> wrote:

From: C M [mailto:cmpython@…287…]
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:

From: C M [mailto:cmpython@…287…]
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.