source: trunk/python/asaplot.py @ 118

Last change on this file since 118 was 118, checked in by cal103, 19 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
Line 
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, \
15        FigureManagerTkAgg
16from matplotlib.figure import Figure, Text
17from matplotlib.numerix import sqrt
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
30    def __init__(self, rows=1, cols=0, title='', size=(8,6), buffering=False):
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)
38
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)
42
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')
46
47        self.set_title(title)
48        self.subplots = []
49        if rows > 0:
50            self.set_panels(rows, cols)
51
52
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
57
58        matplotlib.interactive = True
59        self.buffering = buffering
60
61        self.canvas.show()
62
63
64    def clear(self):
65        """
66        Delete all lines from the plot.  Line numbering will restart from 1.
67        """
68
69        for i in range(1,len(self.lines)+1):
70           self.delete(i)
71
72        self.axes.clear()
73        self.colours[0] = 1
74        self.lines = []
75       
76
77    def delete(self, numbers=None):
78        """
79        Delete the 0-relative line number, default is to delete the last.
80        The remaining lines are NOT renumbered.
81        """
82
83        if numbers is None: numbers = [len(self.lines)-1]
84
85        if not hasattr(numbers, '__iter__'):
86            numbers = [numbers]
87
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
94
95        self.show()
96
97
98    def get_line(self):
99        """
100        Get the current default line attributes.
101        """
102        return self.attributes
103
104
105    def hold(self, hold=True):
106        """
107        Buffer graphics until subsequently released.
108        """
109        self.buffering = hold
110
111
112    def legend(self, loc=1):
113        """
114        Add a legend to the plot.
115
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
127
128        """
129        if 1 > loc > 10: loc = 0
130        self.loc = loc
131        self.show()
132
133
134    def map(self):
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()
141
142
143    def palette(self, pen=None, colours=None):
144        """
145        Redefine the colour sequence.
146
147        pen is the pen number to use for the next plot; this will be auto-
148        incremented.
149
150        colours is the list of pen colours.  Colour may be specified via
151        the single letter values understood by matplotlib:
152
153            b: blue
154            g: green
155            r: red
156            c: cyan
157            m: magenta
158            y: yellow
159            k: black
160            w: white
161
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        """
166
167        if pen is None and colours is None:
168            self.colours = []
169            return
170
171        if pen is None:
172            if not len(self.colours):
173                self.colours = [1]
174        else:
175            self.colours[0] = pen
176
177        if colours is None:
178            return
179
180        cols = []
181        for col in colours:
182            cols.append(get_colour(col))
183
184        self.colours[1:] = cols
185
186        if 0 > self.colours[0] > len(self.colours):
187            self.colours[0] = 1
188
189
190    def plot(self, x=None, y=None, mask=None, fmt=None, add=None):
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.
194
195        The argument list works a bit like the matlab plot() function.
196        """
197
198        if x is None:
199            if y is None: return
200            x = range(len(y))
201
202        elif y is None:
203            y = x
204            x = range(len(y))
205
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 = []
213
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)
222
223                segments.append(x[i:j])
224                segments.append(y[i:j])
225
226                i = j
227
228            line = self.axes.plot(*segments)
229
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)
238
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)
245
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]])
249
250            self.colours[0] += 1
251            if self.colours[0] >= len(self.colours):
252                self.colours[0] = 1
253
254        self.show()
255
256
257    def quit(self):
258        """
259        Destroy the ASAPlot graphics window.
260        """
261        self.window.destroy()
262
263
264    def release(self):
265        """
266        Release buffered graphics.
267        """
268        self.buffering = False
269        self.show()
270
271
272    def set_axes(self, what=None, *args, **kwargs):
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        """
278
279        if what is None: return
280        if what[-6:] == 'colour': what = what[:-6] + 'color'
281
282        newargs = {}
283        for k, v in kwargs.iteritems():
284            k = k.lower()
285            if k == 'colour': k = 'color'
286
287            if k == 'color':
288                v = get_colour(v)
289
290            newargs[k] = v
291
292        getattr(self.axes, "set_%s"%what)(*args, **newargs)
293        self.show()
294
295
296    def set_figure(self, what=None, *args, **kwargs):
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        """
302
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]),)
307
308        newargs = {}
309        for k, v in kwargs.iteritems():
310            k = k.lower()
311            if k == 'colour': k = 'color'
312
313            if k == 'color':
314                v = get_colour(v)
315
316            newargs[k] = v
317
318        getattr(self.figure, "set_%s"%what)(*args, **newargs)
319        self.show()
320
321
322    def set_line(self, number=None, **kwargs):
323        """
324        Set attributes for the specified line, or else the next line(s)
325        to be plotted.
326
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.
330
331        Keyword arguments specify Line2D attributes, e.g. color='r'.  Do
332
333            import matplotlib
334            help(matplotlib.lines)
335
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".
339
340        Set the value to None to delete an attribute.
341
342        Colour translation is done as described in the doctext for palette().
343        """
344
345        redraw = False
346        for k, v in kwargs.iteritems():
347            k = k.lower()
348            if k == 'colour': k = 'color'
349
350            if k == 'color':
351                v = get_colour(v)
352
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
363
364        if redraw: self.show()
365
366
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
432    def show(self):
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)
449
450                if len(lines):
451                    self.axes.legend(tuple(lines), tuple(labels), self.loc)
452                else:
453                    self.axes.legend((' '))
454
455            self.window.wm_deiconify()
456            self.canvas.show()
457
458
459    def subplot(self, i=0):
460        """
461        Set the subplot to the 0-relative panel number as defined by one or
462        more invokations of set_panels().
463        """
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']
469
470
471    def terminate(self):
472        """
473        Clear the figure.
474        """
475        self.window.destroy()
476
477
478    def text(self, *args, **kwargs):
479        """
480        Add text to the figure.
481        """
482        self.figure.text(*args, **kwargs)
483        self.show()
484
485
486    def unmap(self):
487        """
488        Hide the ASAPlot graphics window.
489        """
490        self.window.wm_withdraw()
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():
512        if name.lower() == colour:
513            return colours[name]
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:
528        print colours[name], name
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:
539        line = rgb.readline()
540        if line == '': break
541        tmp = line.split()
542
543        if len(tmp) == 4:
544            if tmp[3][:4] == 'gray': continue
545            if tmp[3].lower().find('gray') != -1: continue
546
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.