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