source: trunk/python/asaplot.py @ 111

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

Initial revision

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