source: branches/alma/python/asaplotbase.py @ 1515

Last change on this file since 1515 was 1446, checked in by TakTsutsumi, 16 years ago

Merged recent updates (since 2007) from nrao-asap

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 26.7 KB
Line 
1"""
2ASAP plotting class based on matplotlib.
3"""
4
5import sys
6from re import match
7
8import matplotlib
9
10from matplotlib.figure import Figure, Text
11from matplotlib.font_manager import FontProperties as FP
12from matplotlib.numerix import sqrt
13from matplotlib import rc, rcParams
14from asap import rcParams as asaprcParams
15from matplotlib.ticker import OldScalarFormatter
16from matplotlib.ticker import NullLocator
17from matplotlib.transforms import blend_xy_sep_transform
18
19if int(matplotlib.__version__.split(".")[1]) < 87:
20    print "Warning: matplotlib version < 0.87. This might cause errors. Please upgrade."
21
22#class MyFormatter(OldScalarFormatter):
23#    def __call__(self, x, pos=None):
24#        last = len(self.locs)-2
25#        if pos==0:
26#            return ''
27#        else: return OldScalarFormatter.__call__(self, x, pos)
28
29class asaplotbase:
30    """
31    ASAP plotting base class based on matplotlib.
32    """
33
34    def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False):
35        """
36        Create a new instance of the ASAPlot plotting class.
37
38        If rows < 1 then a separate call to set_panels() is required to define
39        the panel layout; refer to the doctext for set_panels().
40        """
41        self.is_dead = False
42        self.figure = Figure(figsize=size, facecolor='#ddddee')
43        self.canvas = None
44
45        self.set_title(title)
46        self.subplots = []
47        if rows > 0:
48            self.set_panels(rows, cols)
49
50        # Set matplotlib default colour sequence.
51        self.colormap = "green red black cyan magenta orange blue purple yellow pink".split()
52
53        c = asaprcParams['plotter.colours']
54        if isinstance(c,str) and len(c) > 0:
55            self.colormap = c.split()
56
57        self.lsalias = {"line":  [1,0],
58                        "dashdot": [4,2,1,2],
59                        "dashed" : [4,2,4,2],
60                        "dotted" : [1,2],
61                        "dashdotdot": [4,2,1,2,1,2],
62                        "dashdashdot": [4,2,4,2,1,2]
63                        }
64
65        styles = "line dashed dotted dashdot".split()
66        c = asaprcParams['plotter.linestyles']
67        if isinstance(c,str) and len(c) > 0:
68            styles = c.split()
69        s = []
70        for ls in styles:
71            if self.lsalias.has_key(ls):
72                s.append(self.lsalias.get(ls))
73            else:
74                s.append('-')
75        self.linestyles = s
76
77        self.color = 0;
78        self.linestyle = 0;
79        self.attributes = {}
80        self.loc = 0
81
82        self.buffering = buffering
83
84    def clear(self):
85        """
86        Delete all lines from the plot.  Line numbering will restart from 0.
87        """
88
89        for i in range(len(self.lines)):
90           self.delete(i)
91        self.axes.clear()
92        self.color = 0
93        self.lines = []
94
95    def palette(self, color, colormap=None, linestyle=0, linestyles=None):
96        if colormap:
97            if isinstance(colormap,list):
98                self.colormap = colormap
99            elif isinstance(colormap,str):
100                self.colormap = colormap.split()
101        if 0 <= color < len(self.colormap):
102            self.color = color
103        if linestyles:
104            self.linestyles = []
105            if isinstance(linestyles,list):
106                styles = linestyles
107            elif isinstance(linestyles,str):
108                styles = linestyles.split()
109            for ls in styles:
110                if self.lsalias.has_key(ls):
111                    self.linestyles.append(self.lsalias.get(ls))
112                else:
113                    self.linestyles.append(self.lsalias.get('line'))
114        if 0 <= linestyle < len(self.linestyles):
115            self.linestyle = linestyle
116
117    def delete(self, numbers=None):
118        """
119        Delete the 0-relative line number, default is to delete the last.
120        The remaining lines are NOT renumbered.
121        """
122
123        if numbers is None: numbers = [len(self.lines)-1]
124
125        if not hasattr(numbers, '__iter__'):
126            numbers = [numbers]
127
128        for number in numbers:
129            if 0 <= number < len(self.lines):
130                if self.lines[number] is not None:
131                    for line in self.lines[number]:
132                        line.set_linestyle('None')
133                        self.lines[number] = None
134        self.show()
135
136    def get_line(self):
137        """
138        Get the current default line attributes.
139        """
140        return self.attributes
141
142
143    def hist(self, x=None, y=None, fmt=None, add=None):
144        """
145        Plot a histogram.  N.B. the x values refer to the start of the
146        histogram bin.
147
148        fmt is the line style as in plot().
149        """
150        from matplotlib.numerix import array
151        from matplotlib.numerix.ma import MaskedArray
152        if x is None:
153            if y is None: return
154            x = range(len(y))
155
156        if len(x) != len(y):
157            return
158        l2 = 2*len(x)
159        x2 = range(l2)
160        y2 = range(12)
161        y2 = range(l2)
162        m2 = range(l2)
163        #ymsk = y.raw_mask()
164        #ydat = y.raw_data()
165        ymsk = y.mask
166        ydat = y.data
167        for i in range(l2):
168            x2[i] = x[i/2]
169            m2[i] = ymsk[i/2]
170
171        y2[0] = 0.0
172        for i in range(1,l2):
173            y2[i] = ydat[(i-1)/2]
174
175        self.plot(x2, MaskedArray(y2,mask=m2,copy=0), fmt, add)
176
177
178    def hold(self, hold=True):
179        """
180        Buffer graphics until subsequently released.
181        """
182        self.buffering = hold
183
184
185    def legend(self, loc=None):
186        """
187        Add a legend to the plot.
188
189        Any other value for loc else disables the legend:
190             1: upper right
191             2: upper left
192             3: lower left
193             4: lower right
194             5: right
195             6: center left
196             7: center right
197             8: lower center
198             9: upper center
199            10: center
200
201        """
202        if isinstance(loc, int):
203            self.loc = None
204            if 0 <= loc <= 10: self.loc = loc
205        else:
206            self.loc = None
207        #self.show()
208
209
210    def plot(self, x=None, y=None, fmt=None, add=None):
211        """
212        Plot the next line in the current frame using the current line
213        attributes.  The ASAPlot graphics window will be mapped and raised.
214
215        The argument list works a bit like the matlab plot() function.
216        """
217        if x is None:
218            if y is None: return
219            x = range(len(y))
220
221        elif y is None:
222            y = x
223            x = range(len(y))
224        if fmt is None:
225            line = self.axes.plot(x, y)
226        else:
227            line = self.axes.plot(x, y, fmt)
228
229        # Add to an existing line?
230        i = None
231        if add is None or len(self.lines) < add < 0:
232            # Don't add.
233            self.lines.append(line)
234            i = len(self.lines) - 1
235        else:
236            if add == 0: add = len(self.lines)
237            i = add - 1
238            self.lines[i].extend(line)
239
240        # Set/reset attributes for the line.
241        gotcolour = False
242        for k, v in self.attributes.iteritems():
243            if k == 'color': gotcolour = True
244            for segment in self.lines[i]:
245                getattr(segment, "set_%s"%k)(v)
246
247        if not gotcolour and len(self.colormap):
248            for segment in self.lines[i]:
249                getattr(segment, "set_color")(self.colormap[self.color])
250                if len(self.colormap)  == 1:
251                    getattr(segment, "set_dashes")(self.linestyles[self.linestyle])
252
253            self.color += 1
254            if self.color >= len(self.colormap):
255                self.color = 0
256
257            if len(self.colormap) == 1:
258                self.linestyle += 1
259            if self.linestyle >= len(self.linestyles):
260                self.linestyle = 0
261
262        self.show()
263
264
265    def position(self):
266        """
267        Use the mouse to get a position from a graph.
268        """
269
270        def position_disable(event):
271            self.register('button_press', None)
272            print '%.4f, %.4f' % (event.xdata, event.ydata)
273
274        print 'Press any mouse button...'
275        self.register('button_press', position_disable)
276
277
278    def region(self):
279        """
280        Use the mouse to get a rectangular region from a plot.
281
282        The return value is [x0, y0, x1, y1] in world coordinates.
283        """
284
285        def region_start(event):
286            height = self.canvas.figure.bbox.height()
287            self.rect = {'fig': None, 'height': height,
288                         'x': event.x, 'y': height - event.y,
289                         'world': [event.xdata, event.ydata,
290                                   event.xdata, event.ydata]}
291            self.register('button_press', None)
292            self.register('motion_notify', region_draw)
293            self.register('button_release', region_disable)
294
295        def region_draw(event):
296            self.canvas._tkcanvas.delete(self.rect['fig'])
297            self.rect['fig'] = self.canvas._tkcanvas.create_rectangle(
298                                self.rect['x'], self.rect['y'],
299                                event.x, self.rect['height'] - event.y)
300
301        def region_disable(event):
302            self.register('motion_notify', None)
303            self.register('button_release', None)
304
305            self.canvas._tkcanvas.delete(self.rect['fig'])
306
307            self.rect['world'][2:4] = [event.xdata, event.ydata]
308            print '(%.2f, %.2f)  (%.2f, %.2f)' % (self.rect['world'][0],
309                self.rect['world'][1], self.rect['world'][2],
310                self.rect['world'][3])
311
312        self.register('button_press', region_start)
313
314        # This has to be modified to block and return the result (currently
315        # printed by region_disable) when that becomes possible in matplotlib.
316
317        return [0.0, 0.0, 0.0, 0.0]
318
319
320    def register(self, type=None, func=None):
321        """
322        Register, reregister, or deregister events of type 'button_press',
323        'button_release', or 'motion_notify'.
324
325        The specified callback function should have the following signature:
326
327            def func(event)
328
329        where event is an MplEvent instance containing the following data:
330
331            name                # Event name.
332            canvas              # FigureCanvas instance generating the event.
333            x      = None       # x position - pixels from left of canvas.
334            y      = None       # y position - pixels from bottom of canvas.
335            button = None       # Button pressed: None, 1, 2, 3.
336            key    = None       # Key pressed: None, chr(range(255)), shift,
337                                  win, or control
338            inaxes = None       # Axes instance if cursor within axes.
339            xdata  = None       # x world coordinate.
340            ydata  = None       # y world coordinate.
341
342        For example:
343
344            def mouse_move(event):
345                print event.xdata, event.ydata
346
347            a = asaplot()
348            a.register('motion_notify', mouse_move)
349
350        If func is None, the event is deregistered.
351
352        Note that in TkAgg keyboard button presses don't generate an event.
353        """
354
355        if not self.events.has_key(type): return
356
357        if func is None:
358            if self.events[type] is not None:
359                # It's not clear that this does anything.
360                self.canvas.mpl_disconnect(self.events[type])
361                self.events[type] = None
362
363                # It seems to be necessary to return events to the toolbar.
364                if type == 'motion_notify':
365                    self.canvas.mpl_connect(type + '_event',
366                        self.figmgr.toolbar.mouse_move)
367                elif type == 'button_press':
368                    self.canvas.mpl_connect(type + '_event',
369                        self.figmgr.toolbar.press)
370                elif type == 'button_release':
371                    self.canvas.mpl_connect(type + '_event',
372                        self.figmgr.toolbar.release)
373
374        else:
375            self.events[type] = self.canvas.mpl_connect(type + '_event', func)
376
377
378    def release(self):
379        """
380        Release buffered graphics.
381        """
382        self.buffering = False
383        self.show()
384
385
386    def save(self, fname=None, orientation=None, dpi=None, papertype=None):
387        """
388        Save the plot to a file.
389
390        fname is the name of the output file.  The image format is determined
391        from the file suffix; 'png', 'ps', and 'eps' are recognized.  If no
392        file name is specified 'yyyymmdd_hhmmss.png' is created in the current
393        directory.
394        """
395        from asap import rcParams
396        if papertype is None:
397            papertype = rcParams['plotter.papertype']
398        if fname is None:
399            from datetime import datetime
400            dstr = datetime.now().strftime('%Y%m%d_%H%M%S')
401            fname = 'asap'+dstr+'.png'
402
403        d = ['png','.ps','eps']
404
405        from os.path import expandvars
406        fname = expandvars(fname)
407
408        if fname[-3:].lower() in d:
409            try:
410                if fname[-3:].lower() == ".ps":
411                    from matplotlib import __version__ as mv
412                    w = self.figure.figwidth.get()
413                    h = self.figure.figheight.get()
414
415                    if orientation is None:
416                        # oriented
417                        if w > h:
418                            orientation = 'landscape'
419                        else:
420                            orientation = 'portrait'
421                    from matplotlib.backends.backend_ps import papersize
422                    pw,ph = papersize[papertype.lower()]
423                    ds = None
424                    if orientation == 'landscape':
425                        ds = min(ph/w, pw/h)
426                    else:
427                        ds = min(pw/w, ph/h)
428                    ow = ds * w
429                    oh = ds * h
430                    self.figure.set_figsize_inches((ow, oh))
431                    self.figure.savefig(fname, orientation=orientation,
432                                        papertype=papertype.lower())
433                    self.figure.set_figsize_inches((w, h))
434                    print 'Written file %s' % (fname)
435                else:
436                    if dpi is None:
437                        dpi =150
438                    self.figure.savefig(fname,dpi=dpi)
439                    print 'Written file %s' % (fname)
440            except IOError, msg:
441                print 'Failed to save %s: Error msg was\n\n%s' % (fname, err)
442                return
443        else:
444            print "Invalid image type. Valid types are:"
445            print "'ps', 'eps', 'png'"
446
447
448    def set_axes(self, what=None, *args, **kwargs):
449        """
450        Set attributes for the axes by calling the relevant Axes.set_*()
451        method.  Colour translation is done as described in the doctext
452        for palette().
453        """
454
455        if what is None: return
456        if what[-6:] == 'colour': what = what[:-6] + 'color'
457
458        key = "colour"
459        if kwargs.has_key(key):
460            val = kwargs.pop(key)
461            kwargs["color"] = val
462
463        getattr(self.axes, "set_%s"%what)(*args, **kwargs)
464
465        self.show(hardrefresh=False)
466
467
468    def set_figure(self, what=None, *args, **kwargs):
469        """
470        Set attributes for the figure by calling the relevant Figure.set_*()
471        method.  Colour translation is done as described in the doctext
472        for palette().
473        """
474
475        if what is None: return
476        if what[-6:] == 'colour': what = what[:-6] + 'color'
477        #if what[-5:] == 'color' and len(args):
478        #    args = (get_colour(args[0]),)
479
480        newargs = {}
481        for k, v in kwargs.iteritems():
482            k = k.lower()
483            if k == 'colour': k = 'color'
484            newargs[k] = v
485
486        getattr(self.figure, "set_%s"%what)(*args, **newargs)
487        self.show(hardrefresh=False)
488
489
490    def set_limits(self, xlim=None, ylim=None):
491        """
492        Set x-, and y-limits for each subplot.
493
494        xlim = [xmin, xmax] as in axes.set_xlim().
495        ylim = [ymin, ymax] as in axes.set_ylim().
496        """
497        for s in self.subplots:
498            self.axes  = s['axes']
499            self.lines = s['lines']
500            oldxlim =  list(self.axes.get_xlim())
501            oldylim =  list(self.axes.get_ylim())
502            if xlim is not None:
503                for i in range(len(xlim)):
504                    if xlim[i] is not None:
505                        oldxlim[i] = xlim[i]
506            if ylim is not None:
507                for i in range(len(ylim)):
508                    if ylim[i] is not None:
509                        oldylim[i] = ylim[i]
510            self.axes.set_xlim(oldxlim)
511            self.axes.set_ylim(oldylim)
512        return
513
514
515    def set_line(self, number=None, **kwargs):
516        """
517        Set attributes for the specified line, or else the next line(s)
518        to be plotted.
519
520        number is the 0-relative number of a line that has already been
521        plotted.  If no such line exists, attributes are recorded and used
522        for the next line(s) to be plotted.
523
524        Keyword arguments specify Line2D attributes, e.g. color='r'.  Do
525
526            import matplotlib
527            help(matplotlib.lines)
528
529        The set_* methods of class Line2D define the attribute names and
530        values.  For non-US usage, "colour" is recognized as synonymous with
531        "color".
532
533        Set the value to None to delete an attribute.
534
535        Colour translation is done as described in the doctext for palette().
536        """
537
538        redraw = False
539        for k, v in kwargs.iteritems():
540            k = k.lower()
541            if k == 'colour': k = 'color'
542
543            if 0 <= number < len(self.lines):
544                if self.lines[number] is not None:
545                    for line in self.lines[number]:
546                        getattr(line, "set_%s"%k)(v)
547                    redraw = True
548            else:
549                if v is None:
550                    del self.attributes[k]
551                else:
552                    self.attributes[k] = v
553
554        if redraw: self.show(hardrefresh=False)
555
556
557    def set_panels(self, rows=1, cols=0, n=-1, nplots=-1, ganged=True):
558        """
559        Set the panel layout.
560
561        rows and cols, if cols != 0, specify the number of rows and columns in
562        a regular layout.   (Indexing of these panels in matplotlib is row-
563        major, i.e. column varies fastest.)
564
565        cols == 0 is interpreted as a retangular layout that accomodates
566        'rows' panels, e.g. rows == 6, cols == 0 is equivalent to
567        rows == 2, cols == 3.
568
569        0 <= n < rows*cols is interpreted as the 0-relative panel number in
570        the configuration specified by rows and cols to be added to the
571        current figure as its next 0-relative panel number (i).  This allows
572        non-regular panel layouts to be constructed via multiple calls.  Any
573        other value of n clears the plot and produces a rectangular array of
574        empty panels.  The number of these may be limited by nplots.
575        """
576        if n < 0 and len(self.subplots):
577            self.figure.clear()
578            self.set_title()
579
580        if rows < 1: rows = 1
581
582        if cols <= 0:
583            i = int(sqrt(rows))
584            if i*i < rows: i += 1
585            cols = i
586
587            if i*(i-1) >= rows: i -= 1
588            rows = i
589
590        if 0 <= n < rows*cols:
591            i = len(self.subplots)
592            self.subplots.append({})
593
594            self.subplots[i]['axes']  = self.figure.add_subplot(rows,
595                                            cols, n+1)
596            self.subplots[i]['lines'] = []
597
598            if i == 0: self.subplot(0)
599
600            self.rows = 0
601            self.cols = 0
602
603        else:
604            self.subplots = []
605
606            if nplots < 1 or rows*cols < nplots:
607                nplots = rows*cols
608            if ganged:
609                hsp,wsp = None,None
610                if rows > 1: hsp = 0.0001
611                if cols > 1: wsp = 0.0001
612                self.figure.subplots_adjust(wspace=wsp,hspace=hsp)
613            for i in range(nplots):
614                self.subplots.append({})
615                self.subplots[i]['lines'] = []
616                if not ganged:
617                    self.subplots[i]['axes'] = self.figure.add_subplot(rows,
618                                                cols, i+1)
619                    self.subplots[i]['axes'].xaxis.set_major_formatter(OldScalarFormatter())
620                else:
621                    if i == 0:
622                        self.subplots[i]['axes'] = self.figure.add_subplot(rows,
623                                                cols, i+1)
624                        self.subplots[i]['axes'].xaxis.set_major_formatter(OldScalarFormatter())
625                    else:
626                        self.subplots[i]['axes'] = self.figure.add_subplot(rows,
627                                                cols, i+1,
628                                                sharex=self.subplots[0]['axes'],
629                                                sharey=self.subplots[0]['axes'])
630
631                    # Suppress tick labelling for interior subplots.
632                    if i <= (rows-1)*cols - 1:
633                        if i+cols < nplots:
634                            # Suppress x-labels for frames width
635                            # adjacent frames
636                            for tick in self.subplots[i]['axes'].xaxis.majorTicks:
637                                tick.label1On = False
638                            self.subplots[i]['axes'].xaxis.label.set_visible(False)
639                    if i%cols:
640                        # Suppress y-labels for frames not in the left column.
641                        for tick in self.subplots[i]['axes'].yaxis.majorTicks:
642                            tick.label1On = False
643                        self.subplots[i]['axes'].yaxis.label.set_visible(False)
644                    # disable the first tick of [1:ncol-1] of the last row
645                    #if i+1 < nplots:
646                    #    self.subplots[i]['axes'].xaxis.majorTicks[0].label1On = False
647                self.rows = rows
648                self.cols = cols
649            self.subplot(0)
650
651    def tidy(self):
652        # this needs to be exceuted after the first "refresh"
653        nplots = len(self.subplots)
654        if nplots == 1: return
655        for i in xrange(nplots):
656            ax = self.subplots[i]['axes']
657            if i%self.cols:
658                ax.xaxis.majorTicks[0].label1On = False
659            else:
660                if i != 0:
661                    ax.yaxis.majorTicks[-1].label1On = False
662
663
664    def set_title(self, title=None):
665        """
666        Set the title of the plot window.  Use the previous title if title is
667        omitted.
668        """
669        if title is not None:
670            self.title = title
671
672        self.figure.text(0.5, 0.95, self.title, horizontalalignment='center')
673
674
675    def show(self, hardrefresh=True):
676        """
677        Show graphics dependent on the current buffering state.
678        """
679        if not hardrefresh: return
680        if not self.buffering:
681            if self.loc is not None:
682                for sp in self.subplots:
683                    lines  = []
684                    labels = []
685                    i = 0
686                    for line in sp['lines']:
687                        i += 1
688                        if line is not None:
689                            lines.append(line[0])
690                            lbl = line[0].get_label()
691                            if lbl == '':
692                                lbl = str(i)
693                            labels.append(lbl)
694
695                    if len(lines):
696                        fp = FP(size=rcParams['legend.fontsize'])
697                        fsz = fp.get_size_in_points() - len(lines)
698                        fp.set_size(max(fsz,6))
699                        sp['axes'].legend(tuple(lines), tuple(labels),
700                                          self.loc, prop=fp)
701                    else:
702                        sp['axes'].legend((' '))
703
704            from matplotlib.artist import setp
705            fp = FP(size=rcParams['xtick.labelsize'])
706            xts = fp.get_size_in_points()- (self.cols)/2
707            fp = FP(size=rcParams['ytick.labelsize'])
708            yts = fp.get_size_in_points() - (self.rows)/2
709            for sp in self.subplots:
710                ax = sp['axes']
711                s = rcParams['axes.titlesize']
712                tsize = s-(self.cols+self.rows-1)
713                ax.title.set_size(max(tsize,9))
714                fp = FP(size=rcParams['axes.labelsize'])
715                setp(ax.get_xticklabels(), fontsize=xts)
716                setp(ax.get_yticklabels(), fontsize=yts)
717                origx =  fp.get_size_in_points()
718                origy = origx
719                off = 0
720                if self.cols > 1: off = self.cols
721                xfsize = origx-off
722                ax.xaxis.label.set_size(xfsize)
723                off = 0
724                if self.rows > 1: off = self.rows
725                yfsize = origy-off
726                ax.yaxis.label.set_size(yfsize)
727
728    def subplot(self, i=None, inc=None):
729        """
730        Set the subplot to the 0-relative panel number as defined by one or
731        more invokations of set_panels().
732        """
733        l = len(self.subplots)
734        if l:
735            if i is not None:
736                self.i = i
737
738            if inc is not None:
739                self.i += inc
740
741            self.i %= l
742            self.axes  = self.subplots[self.i]['axes']
743            self.lines = self.subplots[self.i]['lines']
744
745    def text(self, *args, **kwargs):
746        """
747        Add text to the figure.
748        """
749        self.figure.text(*args, **kwargs)
750        self.show()
751
752    def vline_with_label(self, x, y, label,
753                         location='bottom', rotate=0.0, **kwargs):
754        """
755        Plot a vertical line with label.
756        It takes "world" values fo x and y.
757        """
758        ax = self.axes
759        # need this to suppress autoscaling during this function
760        self.axes.set_autoscale_on(False)
761        ymin = 0.0
762        ymax = 1.0
763        valign = 'center'
764        if location.lower() == 'top':
765            y = max(0.0, y)
766        elif location.lower() == 'bottom':
767            y = min(0.0, y)
768        lbloffset = 0.06
769        # a rough estimate for the bb of the text
770        if rotate > 0.0: lbloffset = 0.03*len(label)
771        peakoffset = 0.01
772        xy0 = ax.transData.xy_tup((x,y))
773        # get relative coords
774        xy = ax.transAxes.inverse_xy_tup(xy0)
775        if location.lower() == 'top':
776            ymax = 1.0-lbloffset
777            ymin = xy[1]+peakoffset
778            valign = 'bottom'
779            ylbl = ymax+0.01
780        elif location.lower() == 'bottom':
781            ymin = lbloffset
782            ymax = xy[1]-peakoffset
783            valign = 'top'
784            ylbl = ymin-0.01
785        trans = blend_xy_sep_transform(ax.transData, ax.transAxes)
786        l = ax.axvline(x, ymin, ymax, color='black', **kwargs)
787        t = ax.text(x, ylbl ,label, verticalalignment=valign,
788                                    horizontalalignment='center',
789                    rotation=rotate,transform = trans)
790        self.axes.set_autoscale_on(True)
Note: See TracBrowser for help on using the repository browser.