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