source: trunk/python/asaplot.py @ 118

Last change on this file since 118 was 118, checked in by cal103, 20 years ago

Added general control of the panel layout.

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 11.9 KB
RevLine 
[111]1"""
2ASAP plotting class based on matplotlib.
3"""
4
5import sys
6from re import match
7import Tkinter as Tk
8
9print "Importing matplotlib with TkAgg backend."
10import matplotlib
11matplotlib.use("TkAgg")
12
13from matplotlib.backends import new_figure_manager, show
14from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, \
[117]15        FigureManagerTkAgg
[111]16from matplotlib.figure import Figure, Text
[118]17from matplotlib.numerix import sqrt
[111]18
19# Force use of the newfangled toolbar.
20matplotlib.rcParams['toolbar'] = 'toolbar2'
21
22# Colour dictionary.
23colours = {}
24
25class ASAPlot:
26    """
27    ASAP plotting class based on matplotlib.
28    """
29
[118]30    def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False):
[117]31        """
32        Create a new instance of the ASAPlot plotting class.
33        """
34        self.window = Tk.Tk()
35       
36        self.frame1 = Tk.Frame(self.window, relief=Tk.RIDGE, borderwidth=4)
37        self.frame1.pack(fill=Tk.BOTH)
[111]38
[117]39        self.figure = Figure(figsize=size, facecolor='#ddddee')
40        self.canvas = FigureCanvasTkAgg(self.figure, master=self.frame1)
41        self.canvas.get_tk_widget().pack(side=Tk.TOP, fill=Tk.BOTH, expand=1)
[111]42
[117]43        # Simply instantiating this is enough to get a working toolbar.
44        self.figmgr = FigureManagerTkAgg(self.canvas, 1, self.window)
45        self.window.wm_title('ASAPlot graphics window')
[111]46
[118]47        self.set_title(title)
[117]48        self.subplots = []
[118]49        if rows > 0:
50            self.set_panels(rows, cols)
[111]51
52
[117]53        # Set matplotlib default colour sequence.
54        self.colours = [1, 'b', 'g', 'r', 'c', 'm', 'y', 'k', 'w']
55        self.attributes = {}
56        self.loc = 1
[111]57
[117]58        matplotlib.interactive = True
59        self.buffering = buffering
[111]60
[117]61        self.canvas.show()
[111]62
63
[117]64    def clear(self):
65        """
66        Delete all lines from the plot.  Line numbering will restart from 1.
67        """
[111]68
[117]69        for i in range(1,len(self.lines)+1):
70           self.delete(i)
[111]71
[117]72        self.axes.clear()
73        self.colours[0] = 1
74        self.lines = []
75       
[111]76
77    def delete(self, numbers=None):
[117]78        """
79        Delete the 0-relative line number, default is to delete the last.
80        The remaining lines are NOT renumbered.
81        """
[111]82
[117]83        if numbers is None: numbers = [len(self.lines)-1]
[111]84
[117]85        if not hasattr(numbers, '__iter__'):
86            numbers = [numbers]
[111]87
[117]88        for number in numbers:
89            if 0 <= number < len(self.lines):
90                if self.lines[number] is not None:
91                    for line in self.lines[number]:
92                        line.set_linestyle('None')
93                        self.lines[number] = None
[111]94
[117]95        self.show()
[111]96
97
98    def get_line(self):
[117]99        """
100        Get the current default line attributes.
101        """
102        return self.attributes
[111]103
104
105    def hold(self, hold=True):
[117]106        """
107        Buffer graphics until subsequently released.
108        """
109        self.buffering = hold
[111]110
111
112    def legend(self, loc=1):
[117]113        """
114        Add a legend to the plot.
[111]115
[117]116        Any other value for loc else disables the legend:
117             1: upper right
118             2: upper left
119             3: lower left
120             4: lower right
121             5: right
122             6: center left
123             7: center right
124             8: lower center
125             9: upper center
126            10: center
[111]127
[117]128        """
129        if 1 > loc > 10: loc = 0
130        self.loc = loc
131        self.show()
[111]132
133
134    def map(self):
[117]135        """
136        Reveal the ASAPlot graphics window and bring it to the top of the
137        window stack.
138        """
139        self.window.wm_deiconify()
140        self.window.lift()
[111]141
[117]142
[111]143    def palette(self, pen=None, colours=None):
[117]144        """
145        Redefine the colour sequence.
[111]146
[117]147        pen is the pen number to use for the next plot; this will be auto-
148        incremented.
[111]149
[117]150        colours is the list of pen colours.  Colour may be specified via
151        the single letter values understood by matplotlib:
[111]152
[117]153            b: blue
154            g: green
155            r: red
156            c: cyan
157            m: magenta
158            y: yellow
159            k: black
160            w: white
[111]161
[117]162        or via the full name as listed in the colour dictionary which is
163        loaded by default by load_colours() from rgb.txt and listed by
164        list_colours().
165        """
[111]166
[117]167        if pen is None and colours is None:
168            self.colours = []
169            return
[111]170
[117]171        if pen is None:
172            if not len(self.colours):
173                self.colours = [1]
174        else:
175            self.colours[0] = pen
[111]176
[117]177        if colours is None:
178            return
[111]179
[117]180        cols = []
181        for col in colours:
182            cols.append(get_colour(col))
[111]183
[117]184        self.colours[1:] = cols
[111]185
[117]186        if 0 > self.colours[0] > len(self.colours):
187            self.colours[0] = 1
[111]188
189
190    def plot(self, x=None, y=None, mask=None, fmt=None, add=None):
[117]191        """
192        Plot the next line in the current frame using the current line
193        attributes.  The ASAPlot graphics window will be mapped and raised.
[111]194
[117]195        The argument list works a bit like the matlab plot() function.
196        """
[111]197
[117]198        if x is None:
199            if y is None: return
200            x = range(len(y))
[111]201
[117]202        elif y is None:
203            y = x
204            x = range(len(y))
[111]205
[117]206        if mask is None:
207            if fmt is None:
208                line = self.axes.plot(x, y)
209            else:
210                line = self.axes.plot(x, y, fmt)
211        else:
212            segments = []
[111]213
[117]214            mask = list(mask)
215            i = 0
216            while mask[i:].count(1):
217                i += mask[i:].index(1)
218                if mask[i:].count(0):
219                    j = i + mask[i:].index(0)
220                else:
221                    j = len(mask)
[111]222
[117]223                segments.append(x[i:j])
224                segments.append(y[i:j])
[111]225
[117]226                i = j
[111]227
[117]228            line = self.axes.plot(*segments)
[111]229
[117]230        # Add to an existing line?
231        if add is None or len(self.lines) < add < 0:
232            self.lines.append(line)
233            i = len(self.lines) - 1
234        else:
235            if add == 0: add = len(self.lines)
236            i = add - 1
237            self.lines[i].extend(line)
[111]238
[117]239        # Set/reset attributes for the line.
240        gotcolour = False
241        for k, v in self.attributes.iteritems():
242            if k == 'color': gotcolour = True
243            for segment in self.lines[i]:
244                getattr(segment, "set_%s"%k)(v)
[111]245
[117]246        if not gotcolour and len(self.colours):
247            for segment in self.lines[i]:
248                getattr(segment, "set_color")(self.colours[self.colours[0]])
[111]249
[117]250            self.colours[0] += 1
251            if self.colours[0] >= len(self.colours):
252                self.colours[0] = 1
[111]253
[117]254        self.show()
[111]255
256
257    def quit(self):
[117]258        """
259        Destroy the ASAPlot graphics window.
260        """
261        self.window.destroy()
[111]262
263
264    def release(self):
[117]265        """
266        Release buffered graphics.
267        """
268        self.buffering = False
269        self.show()
[111]270
271
272    def set_axes(self, what=None, *args, **kwargs):
[117]273        """
274        Set attributes for the axes by calling the relevant Axes.set_*()
275        method.  Colour translation is done as described in the doctext
276        for palette().
277        """
[111]278
[117]279        if what is None: return
280        if what[-6:] == 'colour': what = what[:-6] + 'color'
[111]281
[117]282        newargs = {}
283        for k, v in kwargs.iteritems():
284            k = k.lower()
285            if k == 'colour': k = 'color'
[111]286
[117]287            if k == 'color':
288                v = get_colour(v)
[111]289
[117]290            newargs[k] = v
[111]291
[117]292        getattr(self.axes, "set_%s"%what)(*args, **newargs)
293        self.show()
[111]294
295
296    def set_figure(self, what=None, *args, **kwargs):
[117]297        """
298        Set attributes for the figure by calling the relevant Figure.set_*()
299        method.  Colour translation is done as described in the doctext
300        for palette().
301        """
[111]302
[117]303        if what is None: return
304        if what[-6:] == 'colour': what = what[:-6] + 'color'
305        if what[-5:] == 'color' and len(args):
306            args = (get_colour(args[0]),)
[111]307
[117]308        newargs = {}
309        for k, v in kwargs.iteritems():
310            k = k.lower()
311            if k == 'colour': k = 'color'
[111]312
[117]313            if k == 'color':
314                v = get_colour(v)
[111]315
[117]316            newargs[k] = v
[111]317
[117]318        getattr(self.figure, "set_%s"%what)(*args, **newargs)
319        self.show()
[111]320
321
322    def set_line(self, number=None, **kwargs):
[117]323        """
324        Set attributes for the specified line, or else the next line(s)
325        to be plotted.
[111]326
[117]327        number is the 0-relative number of a line that has already been
328        plotted.  If no such line exists, attributes are recorded and used
329        for the next line(s) to be plotted.
[111]330
[117]331        Keyword arguments specify Line2D attributes, e.g. color='r'.  Do
[111]332
[117]333            import matplotlib
334            help(matplotlib.lines)
[111]335
[117]336        The set_* methods of class Line2D define the attribute names and
337        values.  For non-US usage, "colour" is recognized as synonymous with
338        "color".
[111]339
[117]340        Set the value to None to delete an attribute.
[111]341
[117]342        Colour translation is done as described in the doctext for palette().
343        """
[111]344
[117]345        redraw = False
346        for k, v in kwargs.iteritems():
347            k = k.lower()
348            if k == 'colour': k = 'color'
[111]349
[117]350            if k == 'color':
351                v = get_colour(v)
[111]352
[117]353            if 0 <= number < len(self.lines):
354                if self.lines[number] is not None:
355                    for line in self.lines[number]:
356                        getattr(line, "set_%s"%k)(v)
357                    redraw = True
358            else:
359                if v is None:
360                    del self.attributes[k]
361                else:
362                    self.attributes[k] = v
[111]363
[117]364        if redraw: self.show()
[111]365
366
[118]367    def set_panels(self, rows=1, cols=0, n=-1):
368        """
369        Set the panel layout.
370       
371        rows and cols, if cols != 0, specify the number of rows and columns in
372        a regular layout.   (Indexing of these panels in matplotlib is row-
373        major, i.e. column varies fastest.)
374
375        cols == 0 is interpreted as a retangular layout that accomodates
376        'rows' panels, e.g. rows == 6, cols == 0 is equivalent to
377        rows == 2, cols == 3.
378
379        0 <= n < rows*cols is interpreted as the 0-relative panel number in
380        the configuration specified by rows and cols to be added to the
381        current figure as its next 0-relative panel number (i).  This allows
382        non-regular panel layouts to be constructed via multiple calls.  Any
383        other value of n clears the plot and produces a rectangular array of
384        empty panels.
385        """
386        if n < 0 and len(self.subplots):
387            self.figure.clear()
388            self.set_title()
389
390        if rows < 1:
391            rows = 1
392
393        if cols == 0:
394            i = int(sqrt(rows))
395            if i*i < rows: i += 1
396            cols = i
397
398            if i*(i-1) >= rows: i -= 1
399            rows = i
400
401        if 0 <= n < rows*cols:
402            i = len(self.subplots)
403            self.subplots.append({})
404            self.subplots[i]['axes']  = self.figure.add_subplot(rows,
405                                            cols, n+1)
406            self.subplots[i]['lines'] = []
407
408            if i == 0: self.subplot()
409
410        else:
411            self.subplots = []
412            for i in range(0,rows*cols):
413                self.subplots.append({})
414                self.subplots[i]['axes']  = self.figure.add_subplot(rows,
415                                                cols, i+1)
416                self.subplots[i]['lines'] = []
417
418            self.subplot()
419
420
421    def set_title(self, title=None):
422        """
423        Set the title of the plot window.  Use the previous title if title is
424        omitted.
425        """
426        if title is not None:
427            self.title = title
428
429        self.figure.text(0.5, 0.95, self.title, horizontalalignment='center')
430
431
[111]432    def show(self):
[117]433        """
434        Show graphics dependent on the current buffering state.
435        """
436        if not self.buffering:
437            if self.loc:
438                lines  = []
439                labels = []
440                i = 0
441                for line in self.lines:
442                    i += 1
443                    if line is not None:
444                        lines.append(line[0])
445                        lbl = line[0].get_label()
446                        if lbl == '':
447                            lbl = str(i)
448                        labels.append(lbl)
[111]449
[117]450                if len(lines):
451                    self.axes.legend(tuple(lines), tuple(labels), self.loc)
452                else:
453                    self.axes.legend((' '))
[111]454
[117]455            self.window.wm_deiconify()
456            self.canvas.show()
[111]457
458
[118]459    def subplot(self, i=0):
[117]460        """
[118]461        Set the subplot to the 0-relative panel number as defined by one or
462        more invokations of set_panels().
[117]463        """
[118]464        l = len(self.subplots)
465        if l:
466            i = i%l
467            self.axes  = self.subplots[i]['axes']
468            self.lines = self.subplots[i]['lines']
[111]469
[117]470
[111]471    def terminate(self):
[117]472        """
473        Clear the figure.
474        """
475        self.window.destroy()
[111]476
477
478    def text(self, *args, **kwargs):
[117]479        """
480        Add text to the figure.
481        """
482        self.figure.text(*args, **kwargs)
483        self.show()
[111]484
485
486    def unmap(self):
[117]487        """
488        Hide the ASAPlot graphics window.
489        """
490        self.window.wm_withdraw()
[111]491
492
493def get_colour(colour='black'):
494    """
495    Look up a colour by name in the colour dictionary.  Matches are
496    case-insensitive, insensitive to blanks, and 'gray' matches 'grey'.
497    """
498
499    if colour is None: return None
500
501    if match('[rgbcmykw]$', colour): return colour
502    if match('#[\da-fA-F]{6}$', colour): return colour
503
504    if len(colours) == 0: load_colours()
505
506    # Try a quick match.
507    if colours.has_key(colour): return colours[colour]
508
509    colour = colour.replace(' ','').lower()
510    colour = colour.replace('gray','grey')
511    for name in colours.keys():
[117]512        if name.lower() == colour:
513            return colours[name]
[111]514
515    return '#000000'
516
517
518def list_colours():
519    """
520    List the contents of the colour dictionary sorted by name.
521    """
522
523    if len(colours) == 0: load_colours()
524
525    names = colours.keys()
526    names.sort()
527    for name in names:
[117]528        print colours[name], name
[111]529
530
531def load_colours(file='/usr/local/lib/rgb.txt'):
532    """
533    Load the colour dictionary from the specified file.
534    """
535    print 'Loading colour dictionary from', file
536    rgb = open(file, 'r')
537
538    while True:
[117]539        line = rgb.readline()
540        if line == '': break
541        tmp = line.split()
[111]542
[117]543        if len(tmp) == 4:
544            if tmp[3][:4] == 'gray': continue
545            if tmp[3].lower().find('gray') != -1: continue
[111]546
[117]547            name = tmp[3][0].upper() + tmp[3][1:]
548            r, g, b = int(tmp[0]), int(tmp[1]), int(tmp[2])
549            colours[name] = '#%2.2x%2.2x%2.2x' % (r, g, b)
Note: See TracBrowser for help on using the repository browser.