]> git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/wxPlotCanvas.py
More agressive cleanup. Now also removes temp items that are created
[wxWidgets.git] / wxPython / wx / lib / wxPlotCanvas.py
1 """
2 This is a port of Konrad Hinsen's tkPlotCanvas.py plotting module.
3 After thinking long and hard I came up with the name "wxPlotCanvas.py".
4
5 This file contains two parts; first the re-usable library stuff, then, after
6 a "if __name__=='__main__'" test, a simple frame and a few default plots
7 for testing.
8
9 Harm van der Heijden, feb 1999
10
11 Original comment follows below:
12 # This module defines a plot widget for Tk user interfaces.
13 # It supports only elementary line plots at the moment.
14 # See the example at the end for documentation...
15 #
16 # Written by Konrad Hinsen <hinsen@cnrs-orleans.fr>
17 # With contributions from RajGopal Srinivasan <raj@cherubino.med.jhmi.edu>
18 # Last revision: 1998-7-28
19 #
20 """
21 # 12/13/2003 - Jeff Grimmett (grimmtooth@softhome.net)
22 #
23 # o Updated for V2.5 compatability
24 # o wx.SpinCtl has some issues that cause the control to
25 #   lock up. Noted in other places using it too, it's not this module
26 #   that's at fault.
27 # o Added deprecation warning.
28
29
30 import  warnings
31 import  wx
32
33 warningmsg = r"""\
34
35 THIS MODULE IS NOW DEPRECATED
36
37 This module has been replaced by wxPyPlot, which in wxPython
38 can be found in wx.lib.plot.py.
39
40 """
41
42 warnings.warn(warningmsg, DeprecationWarning, stacklevel=2)
43
44 # Not everybody will have Numeric, so let's be cool about it...
45 try:
46     import Numeric
47 except:
48     # bummer!
49     msg = """This module requires the Numeric module, which could not be
50 imported.  It probably is not installed (it's not part of the standard
51 Python distribution). See the Python site (http://www.python.org) for
52 information on downloading source or binaries."""
53
54     if wx.Platform == '__WXMSW__':
55         d = wx.MessageDialog(None, msg, "Numeric not found")
56         if d.ShowModal() == wx.ID_CANCEL:
57             d = wx.MessageDialog(None, "I kid you not! Pressing Cancel won't help you!", "Not a joke", wx.OK)
58             d.ShowModal()
59     else:
60         print msg
61     raise ImportError
62
63 #
64 # Plotting classes...
65 #
66 class PolyPoints:
67
68     def __init__(self, points, attr):
69         self.points = Numeric.array(points)
70         self.scaled = self.points
71         self.attributes = {}
72         for name, value in self._attributes.items():
73             try:
74                 value = attr[name]
75             except KeyError: pass
76             self.attributes[name] = value
77
78     def boundingBox(self):
79         return Numeric.minimum.reduce(self.points), \
80                Numeric.maximum.reduce(self.points)
81
82     def scaleAndShift(self, scale=1, shift=0):
83         self.scaled = scale*self.points+shift
84
85
86 class PolyLine(PolyPoints):
87
88     def __init__(self, points, **attr):
89         PolyPoints.__init__(self, points, attr)
90
91     _attributes = {'color': 'black',
92                    'width': 1}
93
94     def draw(self, dc):
95         color = self.attributes['color']
96         width = self.attributes['width']
97         arguments = []
98         dc.SetPen(wx.Pen(wx.NamedColour(color), width))
99         dc.DrawLines(map(tuple,self.scaled))
100
101
102 class PolyMarker(PolyPoints):
103
104     def __init__(self, points, **attr):
105
106         PolyPoints.__init__(self, points, attr)
107
108     _attributes = {'color': 'black',
109                    'width': 1,
110                    'fillcolor': None,
111                    'size': 2,
112                    'fillstyle': wx.SOLID,
113                    'outline': 'black',
114                    'marker': 'circle'}
115
116     def draw(self, dc):
117         color = self.attributes['color']
118         width = self.attributes['width']
119         size = self.attributes['size']
120         fillcolor = self.attributes['fillcolor']
121         fillstyle = self.attributes['fillstyle']
122         marker = self.attributes['marker']
123
124         dc.SetPen(wx.Pen(wx.NamedColour(color),width))
125         if fillcolor:
126             dc.SetBrush(wx.Brush(wx.NamedColour(fillcolor),fillstyle))
127         else:
128             dc.SetBrush(wx.Brush(wx.NamedColour('black'), wx.TRANSPARENT))
129
130         self._drawmarkers(dc, self.scaled, marker, size)
131
132     def _drawmarkers(self, dc, coords, marker,size=1):
133         f = eval('self._' +marker)
134         for xc, yc in coords:
135             f(dc, xc, yc, size)
136
137     def _circle(self, dc, xc, yc, size=1):
138         dc.DrawEllipse((xc-2.5*size,yc-2.5*size), (5.*size,5.*size))
139
140     def _dot(self, dc, xc, yc, size=1):
141         dc.DrawPoint(xc,yc)
142
143     def _square(self, dc, xc, yc, size=1):
144         dc.DrawRectangle(xc-2.5*size,yc-2.5*size,5.*size,5.*size)
145
146     def _triangle(self, dc, xc, yc, size=1):
147         dc.DrawPolygon([(-0.5*size*5,0.2886751*size*5),
148                        (0.5*size*5,0.2886751*size*5),
149                        (0.0,-0.577350*size*5)],xc,yc)
150
151     def _triangle_down(self, dc, xc, yc, size=1):
152         dc.DrawPolygon([(-0.5*size*5,-0.2886751*size*5),
153                        (0.5*size*5,-0.2886751*size*5),
154                        (0.0,0.577350*size*5)],xc,yc)
155
156     def _cross(self, dc, xc, yc, size=1):
157         dc.DrawLine((xc-2.5*size, yc-2.5*size), (xc+2.5*size,yc+2.5*size))
158         dc.DrawLine((xc-2.5*size,yc+2.5*size), (xc+2.5*size,yc-2.5*size))
159
160     def _plus(self, dc, xc, yc, size=1):
161         dc.DrawLine((xc-2.5*size,yc), (xc+2.5*size,yc))
162         dc.DrawLine((xc,yc-2.5*size,xc), (yc+2.5*size))
163
164 class PlotGraphics:
165
166     def __init__(self, objects):
167         self.objects = objects
168
169     def boundingBox(self):
170         p1, p2 = self.objects[0].boundingBox()
171         for o in self.objects[1:]:
172             p1o, p2o = o.boundingBox()
173             p1 = Numeric.minimum(p1, p1o)
174             p2 = Numeric.maximum(p2, p2o)
175         return p1, p2
176
177     def scaleAndShift(self, scale=1, shift=0):
178         for o in self.objects:
179             o.scaleAndShift(scale, shift)
180
181     def draw(self, canvas):
182         for o in self.objects:
183             o.draw(canvas)
184
185     def __len__(self):
186         return len(self.objects)
187
188     def __getitem__(self, item):
189         return self.objects[item]
190
191
192 class PlotCanvas(wx.Window):
193
194     def __init__(self, parent, id=-1,
195                  pos = wx.DefaultPosition, size = wx.DefaultSize,
196                  style = 0, name = 'plotCanvas'):
197         wx.Window.__init__(self, parent, id, pos, size, style, name)
198         self.border = (1,1)
199         self.SetClientSize((400,400))
200         self.SetBackgroundColour("white")
201
202         self.Bind(wx.EVT_SIZE,self.reconfigure)
203         self.Bind(wx.EVT_PAINT, self.OnPaint)
204         self._setsize()
205         self.last_draw = None
206 #       self.font = self._testFont(font)
207
208     def OnPaint(self, event):
209         pdc = wx.PaintDC(self)
210         if self.last_draw is not None:
211             apply(self.draw, self.last_draw + (pdc,))
212
213     def reconfigure(self, event):
214         (new_width,new_height) = self.GetClientSize()
215         if new_width == self.width and new_height == self.height:
216             return
217         self._setsize()
218         # self.redraw()
219
220     def _testFont(self, font):
221         if font is not None:
222             bg = self.canvas.cget('background')
223             try:
224                 item = CanvasText(self.canvas, 0, 0, anchor=NW,
225                                   text='0', fill=bg, font=font)
226                 self.canvas.delete(item)
227             except TclError:
228                 font = None
229         return font
230
231     def _setsize(self):
232         (self.width,self.height) = self.GetClientSize();
233         self.plotbox_size = 0.97*Numeric.array([self.width, -self.height])
234         xo = 0.5*(self.width-self.plotbox_size[0])
235         yo = self.height-0.5*(self.height+self.plotbox_size[1])
236         self.plotbox_origin = Numeric.array([xo, yo])
237
238     def draw(self, graphics, xaxis = None, yaxis = None, dc = None):
239         if dc == None: dc = wx.ClientDC(self)
240         dc.BeginDrawing()
241         dc.Clear()
242         self.last_draw = (graphics, xaxis, yaxis)
243         p1, p2 = graphics.boundingBox()
244         xaxis = self._axisInterval(xaxis, p1[0], p2[0])
245         yaxis = self._axisInterval(yaxis, p1[1], p2[1])
246         text_width = [0., 0.]
247         text_height = [0., 0.]
248         if xaxis is not None:
249             p1[0] = xaxis[0]
250             p2[0] = xaxis[1]
251             xticks = self._ticks(xaxis[0], xaxis[1])
252             bb = dc.GetTextExtent(xticks[0][1])
253             text_height[1] = bb[1]
254             text_width[0] = 0.5*bb[0]
255             bb = dc.GetTextExtent(xticks[-1][1])
256             text_width[1] = 0.5*bb[0]
257         else:
258             xticks = None
259         if yaxis is not None:
260             p1[1] = yaxis[0]
261             p2[1] = yaxis[1]
262             yticks = self._ticks(yaxis[0], yaxis[1])
263             for y in yticks:
264                 bb = dc.GetTextExtent(y[1])
265                 text_width[0] = max(text_width[0],bb[0])
266             h = 0.5*bb[1]
267             text_height[0] = h
268             text_height[1] = max(text_height[1], h)
269         else:
270             yticks = None
271         text1 = Numeric.array([text_width[0], -text_height[1]])
272         text2 = Numeric.array([text_width[1], -text_height[0]])
273         scale = (self.plotbox_size-text1-text2) / (p2-p1)
274         shift = -p1*scale + self.plotbox_origin + text1
275         self._drawAxes(dc, xaxis, yaxis, p1, p2,
276                        scale, shift, xticks, yticks)
277         graphics.scaleAndShift(scale, shift)
278         graphics.draw(dc)
279         dc.EndDrawing()
280
281     def _axisInterval(self, spec, lower, upper):
282         if spec is None:
283             return None
284         if spec == 'minimal':
285             if lower == upper:
286                 return lower-0.5, upper+0.5
287             else:
288                 return lower, upper
289         if spec == 'automatic':
290             range = upper-lower
291             if range == 0.:
292                 return lower-0.5, upper+0.5
293             log = Numeric.log10(range)
294             power = Numeric.floor(log)
295             fraction = log-power
296             if fraction <= 0.05:
297                 power = power-1
298             grid = 10.**power
299             lower = lower - lower % grid
300             mod = upper % grid
301             if mod != 0:
302                 upper = upper - mod + grid
303             return lower, upper
304         if type(spec) == type(()):
305             lower, upper = spec
306             if lower <= upper:
307                 return lower, upper
308             else:
309                 return upper, lower
310         raise ValueError, str(spec) + ': illegal axis specification'
311
312     def _drawAxes(self, dc, xaxis, yaxis,
313                   bb1, bb2, scale, shift, xticks, yticks):
314         dc.SetPen(wx.Pen(wx.NamedColour('BLACK'),1))
315         if xaxis is not None:
316             lower, upper = xaxis
317             text = 1
318             for y, d in [(bb1[1], -3), (bb2[1], 3)]:
319                 p1 = scale*Numeric.array([lower, y])+shift
320                 p2 = scale*Numeric.array([upper, y])+shift
321                 dc.DrawLine((p1[0],p1[1]), (p2[0],p2[1]))
322                 for x, label in xticks:
323                     p = scale*Numeric.array([x, y])+shift
324                     dc.DrawLine((p[0],p[1]), (p[0],p[1]+d))
325                     if text:
326                         dc.DrawText(label, (p[0],p[1]))
327                 text = 0
328
329         if yaxis is not None:
330             lower, upper = yaxis
331             text = 1
332             h = dc.GetCharHeight()
333             for x, d in [(bb1[0], -3), (bb2[0], 3)]:
334                 p1 = scale*Numeric.array([x, lower])+shift
335                 p2 = scale*Numeric.array([x, upper])+shift
336                 dc.DrawLine((p1[0],p1[1]), (p2[0],p2[1]))
337                 for y, label in yticks:
338                     p = scale*Numeric.array([x, y])+shift
339                     dc.DrawLine((p[0],p[1]), (p[0]-d,p[1]))
340                     if text:
341                         dc.DrawText(label, 
342                                     (p[0]-dc.GetTextExtent(label)[0], p[1]-0.5*h))
343                 text = 0
344
345     def _ticks(self, lower, upper):
346         ideal = (upper-lower)/7.
347         log = Numeric.log10(ideal)
348         power = Numeric.floor(log)
349         fraction = log-power
350         factor = 1.
351         error = fraction
352         for f, lf in self._multiples:
353             e = Numeric.fabs(fraction-lf)
354             if e < error:
355                 error = e
356                 factor = f
357         grid = factor * 10.**power
358         if power > 3 or power < -3:
359             format = '%+7.0e'
360         elif power >= 0:
361             digits = max(1, int(power))
362             format = '%' + `digits`+'.0f'
363         else:
364             digits = -int(power)
365             format = '%'+`digits+2`+'.'+`digits`+'f'
366         ticks = []
367         t = -grid*Numeric.floor(-lower/grid)
368         while t <= upper:
369             ticks.append( (t, format % (t,)) )
370             t = t + grid
371         return ticks
372
373     _multiples = [(2., Numeric.log10(2.)), (5., Numeric.log10(5.))]
374
375     def redraw(self,dc=None):
376         if self.last_draw is not None:
377             apply(self.draw, self.last_draw + (dc,))
378
379     def clear(self):
380         self.canvas.delete('all')
381
382 #---------------------------------------------------------------------------
383 # if running standalone...
384 #
385 #     ...a sample implementation using the above
386 #
387
388
389 if __name__ == '__main__':
390     def _InitObjects():
391         # 100 points sin function, plotted as green circles
392         data1 = 2.*Numeric.pi*Numeric.arange(200)/200.
393         data1.shape = (100, 2)
394         data1[:,1] = Numeric.sin(data1[:,0])
395         markers1 = PolyMarker(data1, color='green', marker='circle',size=1)
396
397         # 50 points cos function, plotted as red line
398         data1 = 2.*Numeric.pi*Numeric.arange(100)/100.
399         data1.shape = (50,2)
400         data1[:,1] = Numeric.cos(data1[:,0])
401         lines = PolyLine(data1, color='red')
402
403         # A few more points...
404         pi = Numeric.pi
405         markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
406                               (3.*pi/4., -1)], color='blue',
407                               fillcolor='green', marker='cross')
408
409         return PlotGraphics([markers1, lines, markers2])
410
411
412     class AppFrame(wx.Frame):
413         def __init__(self, parent, id, title):
414             wx.Frame.__init__(self, parent, id, title,
415                                 wx.DefaultPosition, (400, 400))
416
417             # Now Create the menu bar and items
418             self.mainmenu = wx.MenuBar()
419
420             menu = wx.Menu()
421             menu.Append(200, '&Print...', 'Print the current plot')
422             self.Bind(wx.EVT_MENU, self.OnFilePrint, id=200)
423             menu.Append(209, 'E&xit', 'Enough of this already!')
424             self.Bind(wx.EVT_MENU, self.OnFileExit, id=209)
425             self.mainmenu.Append(menu, '&File')
426
427             menu = wx.Menu()
428             menu.Append(210, '&Draw', 'Draw plots')
429             self.Bind(wx.EVT_MENU,self.OnPlotDraw, id=210)
430             menu.Append(211, '&Redraw', 'Redraw plots')
431             self.Bind(wx.EVT_MENU,self.OnPlotRedraw, id=211)
432             menu.Append(212, '&Clear', 'Clear canvas')
433             self.Bind(wx.EVT_MENU,self.OnPlotClear, id=212)
434             self.mainmenu.Append(menu, '&Plot')
435
436             menu = wx.Menu()
437             menu.Append(220, '&About', 'About this thing...')
438             self.Bind(wx.EVT_MENU, self.OnHelpAbout, id=220)
439             self.mainmenu.Append(menu, '&Help')
440
441             self.SetMenuBar(self.mainmenu)
442
443             # A status bar to tell people what's happening
444             self.CreateStatusBar(1)
445
446             self.client = PlotCanvas(self)
447
448         def OnFilePrint(self, event):
449             d = wx.MessageDialog(self,
450 """As of this writing, printing support in wxPython is shaky at best.
451 Are you sure you want to do this?""", "Danger!", wx.YES_NO)
452             if d.ShowModal() == wx.ID_YES:
453                 psdc = wx.PostScriptDC("out.ps", True, self)
454                 self.client.redraw(psdc)
455
456         def OnFileExit(self, event):
457             self.Close()
458
459         def OnPlotDraw(self, event):
460             self.client.draw(_InitObjects(),'automatic','automatic');
461
462         def OnPlotRedraw(self,event):
463             self.client.redraw()
464
465         def OnPlotClear(self,event):
466             self.client.last_draw = None
467             dc = wx.ClientDC(self.client)
468             dc.Clear()
469
470         def OnHelpAbout(self, event):
471             about = wx.MessageDialog(self, __doc__, "About...", wx.OK)
472             about.ShowModal()
473
474
475
476     class MyApp(wx.App):
477         def OnInit(self):
478             frame = AppFrame(None, -1, "wxPlotCanvas")
479             frame.Show(True)
480             self.SetTopWindow(frame)
481             return True
482
483
484     app = MyApp(0)
485     app.MainLoop()
486
487
488
489
490 #----------------------------------------------------------------------------