source: trunk/python/asaplot.py @ 119

Last change on this file since 119 was 119, checked in by cal103, 19 years ago

Added a histogram plotting function hist(); modified subplot() so that an
increment may be specified.

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