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