]> git.saurik.com Git - wxWidgets.git/blob - wxPython/tests/wxPlotCanvas.py
corrections for compilation under Mac OS X
[wxWidgets.git] / wxPython / tests / 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
22 from wxPython import wx
23
24 # Not everybody will have Numeric, so let's be cool about it...
25 try:
26 import Numeric
27 except:
28 # bummer!
29 d = wx.wxMessageDialog(wx.NULL,
30 """This module requires the Numeric module, which could not be imported.
31 It probably is not installed (it's not part of the standard Python
32 distribution). See the Python site (http://www.python.org) for
33 information on downloading source or binaries.""",
34 "Numeric not found")
35 if d.ShowModal() == wx.wxID_CANCEL:
36 d = wx.wxMessageDialog(wx.NULL, "I kid you not! Pressing Cancel won't help you!", "Not a joke", wx.wxOK)
37 d.ShowModal()
38 import sys
39 sys.exit()
40
41 #
42 # Plotting classes...
43 #
44 class PolyPoints:
45
46 def __init__(self, points, attr):
47 self.points = Numeric.array(points)
48 self.scaled = self.points
49 self.attributes = {}
50 for name, value in self._attributes.items():
51 try:
52 value = attr[name]
53 except KeyError: pass
54 self.attributes[name] = value
55
56 def boundingBox(self):
57 return Numeric.minimum.reduce(self.points), \
58 Numeric.maximum.reduce(self.points)
59
60 def scaleAndShift(self, scale=1, shift=0):
61 self.scaled = scale*self.points+shift
62
63
64 class PolyLine(PolyPoints):
65
66 def __init__(self, points, **attr):
67 PolyPoints.__init__(self, points, attr)
68
69 _attributes = {'color': 'black',
70 'width': 1}
71
72 def draw(self, dc):
73 color = self.attributes['color']
74 width = self.attributes['width']
75 arguments = []
76 dc.SetPen(wx.wxPen(wx.wxNamedColour(color), width))
77 dc.DrawLines(map(tuple,self.scaled))
78
79
80 class PolyMarker(PolyPoints):
81
82 def __init__(self, points, **attr):
83
84 PolyPoints.__init__(self, points, attr)
85
86 _attributes = {'color': 'black',
87 'width': 1,
88 'fillcolor': None,
89 'size': 2,
90 'fillstyle': wx.wxSOLID,
91 'outline': 'black',
92 'marker': 'circle'}
93
94 def draw(self, dc):
95 color = self.attributes['color']
96 width = self.attributes['width']
97 size = self.attributes['size']
98 fillcolor = self.attributes['fillcolor']
99 fillstyle = self.attributes['fillstyle']
100 marker = self.attributes['marker']
101
102 dc.SetPen(wx.wxPen(wx.wxNamedColour(color),width))
103 if fillcolor:
104 dc.SetBrush(wx.wxBrush(wx.wxNamedColour(fillcolor),fillstyle))
105 else:
106 dc.SetBrush(wx.wxBrush(wx.wxNamedColour('black'), wx.wxTRANSPARENT))
107
108 self._drawmarkers(dc, self.scaled, marker, size)
109
110 def _drawmarkers(self, dc, coords, marker,size=1):
111 f = eval('self._' +marker)
112 for xc, yc in coords:
113 f(dc, xc, yc, size)
114
115 def _circle(self, dc, xc, yc, size=1):
116 dc.DrawEllipse(xc-2.5*size,yc-2.5*size,5.*size,5.*size)
117
118 def _dot(self, dc, xc, yc, size=1):
119 dc.DrawPoint(xc,yc)
120
121 def _square(self, dc, xc, yc, size=1):
122 dc.DrawRectangle(xc-2.5*size,yc-2.5*size,5.*size,5.*size)
123
124 def _triangle(self, dc, xc, yc, size=1):
125 dc.DrawPolygon([(-0.5*size*5,0.2886751*size*5),
126 (0.5*size*5,0.2886751*size*5),
127 (0.0,-0.577350*size*5)],xc,yc)
128
129 def _triangle_down(self, dc, xc, yc, size=1):
130 dc.DrawPolygon([(-0.5*size*5,-0.2886751*size*5),
131 (0.5*size*5,-0.2886751*size*5),
132 (0.0,0.577350*size*5)],xc,yc)
133
134 def _cross(self, dc, xc, yc, size=1):
135 dc.DrawLine(xc-2.5*size,yc-2.5*size,xc+2.5*size,yc+2.5*size)
136 dc.DrawLine(xc-2.5*size,yc+2.5*size,xc+2.5*size,yc-2.5*size)
137
138 def _plus(self, dc, xc, yc, size=1):
139 dc.DrawLine(xc-2.5*size,yc,xc+2.5*size,yc)
140 dc.DrawLine(xc,yc-2.5*size,xc,yc+2.5*size)
141
142 class PlotGraphics:
143
144 def __init__(self, objects):
145 self.objects = objects
146
147 def boundingBox(self):
148 p1, p2 = self.objects[0].boundingBox()
149 for o in self.objects[1:]:
150 p1o, p2o = o.boundingBox()
151 p1 = Numeric.minimum(p1, p1o)
152 p2 = Numeric.maximum(p2, p2o)
153 return p1, p2
154
155 def scaleAndShift(self, scale=1, shift=0):
156 for o in self.objects:
157 o.scaleAndShift(scale, shift)
158
159 def draw(self, canvas):
160 for o in self.objects:
161 o.draw(canvas)
162
163 def __len__(self):
164 return len(self.objects)
165
166 def __getitem__(self, item):
167 return self.objects[item]
168
169
170 class PlotCanvas(wx.wxWindow):
171
172 def __init__(self, parent, id = -1):
173 wx.wxWindow.__init__(self, parent, id, wx.wxPyDefaultPosition, wx.wxPyDefaultSize)
174 self.border = (1,1)
175 self.SetClientSizeWH(400,400)
176 self.SetBackgroundColour(wx.wxNamedColour("white"))
177
178 wx.EVT_SIZE(self,self.reconfigure)
179 self._setsize()
180 self.last_draw = None
181 # self.font = self._testFont(font)
182
183 def OnPaint(self, event):
184 pdc = wx.wxPaintDC(self)
185 if self.last_draw is not None:
186 apply(self.draw, self.last_draw + (pdc,))
187
188 def reconfigure(self, event):
189 (new_width,new_height) = self.GetClientSizeTuple()
190 if new_width == self.width and new_height == self.height:
191 return
192 self._setsize()
193 # self.redraw()
194
195 def _testFont(self, font):
196 if font is not None:
197 bg = self.canvas.cget('background')
198 try:
199 item = CanvasText(self.canvas, 0, 0, anchor=NW,
200 text='0', fill=bg, font=font)
201 self.canvas.delete(item)
202 except TclError:
203 font = None
204 return font
205
206 def _setsize(self):
207 (self.width,self.height) = self.GetClientSizeTuple();
208 self.plotbox_size = 0.97*Numeric.array([self.width, -self.height])
209 xo = 0.5*(self.width-self.plotbox_size[0])
210 yo = self.height-0.5*(self.height+self.plotbox_size[1])
211 self.plotbox_origin = Numeric.array([xo, yo])
212
213 def draw(self, graphics, xaxis = None, yaxis = None, dc = None):
214 if dc == None: dc = wx.wxClientDC(self)
215 dc.BeginDrawing()
216 dc.Clear()
217 self.last_draw = (graphics, xaxis, yaxis)
218 p1, p2 = graphics.boundingBox()
219 xaxis = self._axisInterval(xaxis, p1[0], p2[0])
220 yaxis = self._axisInterval(yaxis, p1[1], p2[1])
221 text_width = [0., 0.]
222 text_height = [0., 0.]
223 if xaxis is not None:
224 p1[0] = xaxis[0]
225 p2[0] = xaxis[1]
226 xticks = self._ticks(xaxis[0], xaxis[1])
227 bb = dc.GetTextExtent(xticks[0][1])
228 text_height[1] = bb[1]
229 text_width[0] = 0.5*bb[0]
230 bb = dc.GetTextExtent(xticks[-1][1])
231 text_width[1] = 0.5*bb[0]
232 else:
233 xticks = None
234 if yaxis is not None:
235 p1[1] = yaxis[0]
236 p2[1] = yaxis[1]
237 yticks = self._ticks(yaxis[0], yaxis[1])
238 for y in yticks:
239 bb = dc.GetTextExtent(y[1])
240 text_width[0] = max(text_width[0],bb[0])
241 h = 0.5*bb[1]
242 text_height[0] = h
243 text_height[1] = max(text_height[1], h)
244 else:
245 yticks = None
246 text1 = Numeric.array([text_width[0], -text_height[1]])
247 text2 = Numeric.array([text_width[1], -text_height[0]])
248 scale = (self.plotbox_size-text1-text2) / (p2-p1)
249 shift = -p1*scale + self.plotbox_origin + text1
250 self._drawAxes(dc, xaxis, yaxis, p1, p2,
251 scale, shift, xticks, yticks)
252 graphics.scaleAndShift(scale, shift)
253 graphics.draw(dc)
254 dc.EndDrawing()
255
256 def _axisInterval(self, spec, lower, upper):
257 if spec is None:
258 return None
259 if spec == 'minimal':
260 if lower == upper:
261 return lower-0.5, upper+0.5
262 else:
263 return lower, upper
264 if spec == 'automatic':
265 range = upper-lower
266 if range == 0.:
267 return lower-0.5, upper+0.5
268 log = Numeric.log10(range)
269 power = Numeric.floor(log)
270 fraction = log-power
271 if fraction <= 0.05:
272 power = power-1
273 grid = 10.**power
274 lower = lower - lower % grid
275 mod = upper % grid
276 if mod != 0:
277 upper = upper - mod + grid
278 return lower, upper
279 if type(spec) == type(()):
280 lower, upper = spec
281 if lower <= upper:
282 return lower, upper
283 else:
284 return upper, lower
285 raise ValueError, str(spec) + ': illegal axis specification'
286
287 def _drawAxes(self, dc, xaxis, yaxis,
288 bb1, bb2, scale, shift, xticks, yticks):
289 dc.SetPen(wx.wxPen(wx.wxNamedColour('BLACK'),1))
290 if xaxis is not None:
291 lower, upper = xaxis
292 text = 1
293 for y, d in [(bb1[1], -3), (bb2[1], 3)]:
294 p1 = scale*Numeric.array([lower, y])+shift
295 p2 = scale*Numeric.array([upper, y])+shift
296 dc.DrawLine(p1[0],p1[1],p2[0],p2[1])
297 for x, label in xticks:
298 p = scale*Numeric.array([x, y])+shift
299 dc.DrawLine(p[0],p[1],p[0],p[1]+d)
300 if text:
301 dc.DrawText(label,p[0],p[1])
302 text = 0
303
304 if yaxis is not None:
305 lower, upper = yaxis
306 text = 1
307 h = dc.GetCharHeight()
308 for x, d in [(bb1[0], -3), (bb2[0], 3)]:
309 p1 = scale*Numeric.array([x, lower])+shift
310 p2 = scale*Numeric.array([x, upper])+shift
311 dc.DrawLine(p1[0],p1[1],p2[0],p2[1])
312 for y, label in yticks:
313 p = scale*Numeric.array([x, y])+shift
314 dc.DrawLine(p[0],p[1],p[0]-d,p[1])
315 if text:
316 dc.DrawText(label,p[0]-dc.GetTextExtent(label)[0],
317 p[1]-0.5*h)
318 text = 0
319
320 def _ticks(self, lower, upper):
321 ideal = (upper-lower)/7.
322 log = Numeric.log10(ideal)
323 power = Numeric.floor(log)
324 fraction = log-power
325 factor = 1.
326 error = fraction
327 for f, lf in self._multiples:
328 e = Numeric.fabs(fraction-lf)
329 if e < error:
330 error = e
331 factor = f
332 grid = factor * 10.**power
333 if power > 3 or power < -3:
334 format = '%+7.0e'
335 elif power >= 0:
336 digits = max(1, int(power))
337 format = '%' + `digits`+'.0f'
338 else:
339 digits = -int(power)
340 format = '%'+`digits+2`+'.'+`digits`+'f'
341 ticks = []
342 t = -grid*Numeric.floor(-lower/grid)
343 while t <= upper:
344 ticks.append(t, format % (t,))
345 t = t + grid
346 return ticks
347
348 _multiples = [(2., Numeric.log10(2.)), (5., Numeric.log10(5.))]
349
350 def redraw(self,dc=None):
351 if self.last_draw is not None:
352 apply(self.draw, self.last_draw + (dc,))
353
354 def clear(self):
355 self.canvas.delete('all')
356
357 #
358 # Now a sample implementation using the above...
359 #
360
361 if __name__ == '__main__':
362
363 class AppFrame(wx.wxFrame):
364 def __init__(self, parent, id, title):
365 wx.wxFrame.__init__(self, parent, id, title,
366 wx.wxPyDefaultPosition, wx.wxSize(400, 400))
367
368 # Now Create the menu bar and items
369 self.mainmenu = wx.wxMenuBar()
370
371 menu = wx.wxMenu()
372 menu.Append(200, '&Print...', 'Print the current plot')
373 wx.EVT_MENU(self, 200, self.OnFilePrint)
374 menu.Append(209, 'E&xit', 'Enough of this already!')
375 wx.EVT_MENU(self, 209, self.OnFileExit)
376 self.mainmenu.Append(menu, '&File')
377
378 menu = wx.wxMenu()
379 menu.Append(210, '&Draw', 'Draw plots')
380 wx.EVT_MENU(self,210,self.OnPlotDraw)
381 menu.Append(211, '&Redraw', 'Redraw plots')
382 wx.EVT_MENU(self,211,self.OnPlotRedraw)
383 menu.Append(212, '&Clear', 'Clear canvas')
384 wx.EVT_MENU(self,212,self.OnPlotClear)
385 self.mainmenu.Append(menu, '&Plot')
386
387 menu = wx.wxMenu()
388 menu.Append(220, '&About', 'About this thing...')
389 wx.EVT_MENU(self, 220, self.OnHelpAbout)
390 self.mainmenu.Append(menu, '&Help')
391
392 self.SetMenuBar(self.mainmenu)
393
394 # A status bar to tell people what's happening
395 self.CreateStatusBar(1)
396
397 self.client = PlotCanvas(self)
398
399 def OnFilePrint(self, event):
400 d = wx.wxMessageDialog(self,
401 """As of this writing, printing support in wxPython is shaky at best.
402 Are you sure you want to do this?""", "Danger!", wx.wxYES_NO)
403 if d.ShowModal() == wx.wxID_YES:
404 psdc = wx.wxPostScriptDC("out.ps", wx.TRUE, self)
405 self.client.redraw(psdc)
406
407 def OnFileExit(self, event):
408 self.Close()
409
410 def OnPlotDraw(self, event):
411 self.client.draw(InitObjects(),'automatic','automatic');
412
413 def OnPlotRedraw(self,event):
414 self.client.redraw()
415
416 def OnPlotClear(self,event):
417 self.client.last_draw = None
418 dc = wx.wxClientDC(self.client)
419 dc.Clear()
420
421 def OnHelpAbout(self, event):
422 about = wx.wxMessageDialog(self, __doc__, "About...", wx.wxOK)
423 about.ShowModal()
424
425 def OnCloseWindow(self, event):
426 self.Destroy()
427
428 def InitObjects():
429 # 100 points sin function, plotted as green circles
430 data1 = 2.*Numeric.pi*Numeric.arange(200)/200.
431 data1.shape = (100, 2)
432 data1[:,1] = Numeric.sin(data1[:,0])
433 markers1 = PolyMarker(data1, color='green', marker='circle',size=1)
434
435 # 50 points cos function, plotted as red line
436 data1 = 2.*Numeric.pi*Numeric.arange(100)/100.
437 data1.shape = (50,2)
438 data1[:,1] = Numeric.cos(data1[:,0])
439 lines = PolyLine(data1, color='red')
440
441 # A few more points...
442 pi = Numeric.pi
443 markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
444 (3.*pi/4., -1)], color='blue',
445 fillcolor='green', marker='cross')
446
447 return PlotGraphics([markers1, lines, markers2])
448
449
450 class MyApp(wx.wxApp):
451 def OnInit(self):
452 frame = AppFrame(wx.NULL, -1, "wxPlotCanvas")
453 frame.Show(wx.TRUE)
454 self.SetTopWindow(frame)
455 return wx.TRUE
456
457
458 app = MyApp(0)
459 app.MainLoop()