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