]> git.saurik.com Git - wxWidgets.git/blame - utils/wxPython/lib/wxPlotCanvas.py
fixed somebody's poorly done StreamSize-->GetSize transition
[wxWidgets.git] / utils / 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!
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()
ab9bc19b 39 raise ImportError
cf694132
RD
40
41#
42# Plotting classes...
43#
44class 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
64class 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
80class 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
142class 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
170class 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# if running standalone...
359#
360# ...a sample implementation using the above
361#
362
363
364if __name__ == '__main__':
365 def _InitObjects():
366 # 100 points sin function, plotted as green circles
367 data1 = 2.*Numeric.pi*Numeric.arange(200)/200.
368 data1.shape = (100, 2)
369 data1[:,1] = Numeric.sin(data1[:,0])
370 markers1 = PolyMarker(data1, color='green', marker='circle',size=1)
371
372 # 50 points cos function, plotted as red line
373 data1 = 2.*Numeric.pi*Numeric.arange(100)/100.
374 data1.shape = (50,2)
375 data1[:,1] = Numeric.cos(data1[:,0])
376 lines = PolyLine(data1, color='red')
377
378 # A few more points...
379 pi = Numeric.pi
380 markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
381 (3.*pi/4., -1)], color='blue',
382 fillcolor='green', marker='cross')
383
384 return PlotGraphics([markers1, lines, markers2])
385
386
387 class AppFrame(wx.wxFrame):
388 def __init__(self, parent, id, title):
389 wx.wxFrame.__init__(self, parent, id, title,
390 wx.wxPyDefaultPosition, wx.wxSize(400, 400))
391
392 # Now Create the menu bar and items
393 self.mainmenu = wx.wxMenuBar()
394
395 menu = wx.wxMenu()
396 menu.Append(200, '&Print...', 'Print the current plot')
397 wx.EVT_MENU(self, 200, self.OnFilePrint)
398 menu.Append(209, 'E&xit', 'Enough of this already!')
399 wx.EVT_MENU(self, 209, self.OnFileExit)
400 self.mainmenu.Append(menu, '&File')
401
402 menu = wx.wxMenu()
403 menu.Append(210, '&Draw', 'Draw plots')
404 wx.EVT_MENU(self,210,self.OnPlotDraw)
405 menu.Append(211, '&Redraw', 'Redraw plots')
406 wx.EVT_MENU(self,211,self.OnPlotRedraw)
407 menu.Append(212, '&Clear', 'Clear canvas')
408 wx.EVT_MENU(self,212,self.OnPlotClear)
409 self.mainmenu.Append(menu, '&Plot')
410
411 menu = wx.wxMenu()
412 menu.Append(220, '&About', 'About this thing...')
413 wx.EVT_MENU(self, 220, self.OnHelpAbout)
414 self.mainmenu.Append(menu, '&Help')
415
416 self.SetMenuBar(self.mainmenu)
417
418 # A status bar to tell people what's happening
419 self.CreateStatusBar(1)
420
421 self.client = PlotCanvas(self)
422
423 def OnFilePrint(self, event):
424 d = wx.wxMessageDialog(self,
425"""As of this writing, printing support in wxPython is shaky at best.
426Are you sure you want to do this?""", "Danger!", wx.wxYES_NO)
427 if d.ShowModal() == wx.wxID_YES:
428 psdc = wx.wxPostScriptDC("out.ps", wx.TRUE, self)
429 self.client.redraw(psdc)
430
431 def OnFileExit(self, event):
432 self.Close()
433
434 def OnPlotDraw(self, event):
435 self.client.draw(_InitObjects(),'automatic','automatic');
436
437 def OnPlotRedraw(self,event):
438 self.client.redraw()
439
440 def OnPlotClear(self,event):
441 self.client.last_draw = None
442 dc = wx.wxClientDC(self.client)
443 dc.Clear()
444
445 def OnHelpAbout(self, event):
446 about = wx.wxMessageDialog(self, __doc__, "About...", wx.wxOK)
447 about.ShowModal()
448
449 def OnCloseWindow(self, event):
450 self.Destroy()
451
452
453 class MyApp(wx.wxApp):
454 def OnInit(self):
455 frame = AppFrame(wx.NULL, -1, "wxPlotCanvas")
456 frame.Show(wx.TRUE)
457 self.SetTopWindow(frame)
458 return wx.TRUE
459
460
461 app = MyApp(0)
462 app.MainLoop()
463
464
465
466
467#----------------------------------------------------------------------------