source: trunk/python/asaplotbase.py @ 1153

Last change on this file since 1153 was 1153, checked in by mar637, 18 years ago

lots of changes to support soft refresh, for things like text overlays, linecatlogs etc. reworked plot_lines to to auto-peak detection. added forwarding functions to matplotlib.axes. drawing functions

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