]> git.saurik.com Git - wxWidgets.git/blame - wxPython/tests/wxPlotCanvas.py
Added stub for wxIcon(const char **xpm)
[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
7d255c9c
HH
23
24# Not everybody will have Numeric, so let's be cool about it...
25try:
26 import Numeric
27except:
28 # bummer!
1e4a197e 29 d = wx.wxMessageDialog(wx.NULL,
7d255c9c
HH
30 """This module requires the Numeric module, which could not be imported.
31It probably is not installed (it's not part of the standard Python
1e4a197e
RD
32distribution). See the Python site (http://www.python.org) for
33information on downloading source or binaries.""",
7d255c9c
HH
34 "Numeric not found")
35 if d.ShowModal() == wx.wxID_CANCEL:
1e4a197e
RD
36 d = wx.wxMessageDialog(wx.NULL, "I kid you not! Pressing Cancel won't help you!", "Not a joke", wx.wxOK)
37 d.ShowModal()
7d255c9c
HH
38 import sys
39 sys.exit()
40
41#
42# Plotting classes...
43#
44class PolyPoints:
45
46 def __init__(self, points, attr):
47 self.points = Numeric.array(points)
1e4a197e 48 self.scaled = self.points
7d255c9c
HH
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):
1e4a197e
RD
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))
7d255c9c
HH
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,
1e4a197e 90 'fillstyle': wx.wxSOLID,
7d255c9c
HH
91 'outline': 'black',
92 'marker': 'circle'}
93
94 def draw(self, dc):
1e4a197e
RD
95 color = self.attributes['color']
96 width = self.attributes['width']
7d255c9c
HH
97 size = self.attributes['size']
98 fillcolor = self.attributes['fillcolor']
99 fillstyle = self.attributes['fillstyle']
100 marker = self.attributes['marker']
101
1e4a197e
RD
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))
7d255c9c 107
1e4a197e 108 self._drawmarkers(dc, self.scaled, marker, size)
7d255c9c
HH
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):
1e4a197e 116 dc.DrawEllipse(xc-2.5*size,yc-2.5*size,5.*size,5.*size)
7d255c9c
HH
117
118 def _dot(self, dc, xc, yc, size=1):
1e4a197e 119 dc.DrawPoint(xc,yc)
7d255c9c
HH
120
121 def _square(self, dc, xc, yc, size=1):
1e4a197e
RD
122 dc.DrawRectangle(xc-2.5*size,yc-2.5*size,5.*size,5.*size)
123
7d255c9c 124 def _triangle(self, dc, xc, yc, size=1):
1e4a197e
RD
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)
7d255c9c
HH
128
129 def _triangle_down(self, dc, xc, yc, size=1):
1e4a197e
RD
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)
7d255c9c
HH
133
134 def _cross(self, dc, xc, yc, size=1):
1e4a197e
RD
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)
7d255c9c
HH
137
138 def _plus(self, dc, xc, yc, size=1):
1e4a197e
RD
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)
7d255c9c
HH
141
142class PlotGraphics:
143
144 def __init__(self, objects):
145 self.objects = objects
146
147 def boundingBox(self):
1e4a197e
RD
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
7d255c9c
HH
154
155 def scaleAndShift(self, scale=1, shift=0):
1e4a197e
RD
156 for o in self.objects:
157 o.scaleAndShift(scale, shift)
7d255c9c
HH
158
159 def draw(self, canvas):
1e4a197e
RD
160 for o in self.objects:
161 o.draw(canvas)
7d255c9c
HH
162
163 def __len__(self):
1e4a197e 164 return len(self.objects)
7d255c9c
HH
165
166 def __getitem__(self, item):
1e4a197e 167 return self.objects[item]
7d255c9c
HH
168
169
170class PlotCanvas(wx.wxWindow):
171
172 def __init__(self, parent, id = -1):
1e4a197e
RD
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"))
7d255c9c 177
1e4a197e
RD
178 wx.EVT_SIZE(self,self.reconfigure)
179 self._setsize()
180 self.last_draw = None
181# self.font = self._testFont(font)
7d255c9c
HH
182
183 def OnPaint(self, event):
1e4a197e
RD
184 pdc = wx.wxPaintDC(self)
185 if self.last_draw is not None:
186 apply(self.draw, self.last_draw + (pdc,))
7d255c9c
HH
187
188 def reconfigure(self, event):
1e4a197e 189 (new_width,new_height) = self.GetClientSizeTuple()
7d255c9c
HH
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):
1e4a197e
RD
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
7d255c9c
HH
205
206 def _setsize(self):
1e4a197e
RD
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])
7d255c9c
HH
212
213 def draw(self, graphics, xaxis = None, yaxis = None, dc = None):
1e4a197e
RD
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,
7d255c9c 251 scale, shift, xticks, yticks)
1e4a197e
RD
252 graphics.scaleAndShift(scale, shift)
253 graphics.draw(dc)
254 dc.EndDrawing()
7d255c9c
HH
255
256 def _axisInterval(self, spec, lower, upper):
1e4a197e
RD
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'
7d255c9c
HH
286
287 def _drawAxes(self, dc, xaxis, yaxis,
288 bb1, bb2, scale, shift, xticks, yticks):
1e4a197e
RD
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
7d255c9c
HH
319
320 def _ticks(self, lower, upper):
1e4a197e
RD
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
7d255c9c
HH
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'
1e4a197e
RD
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
7d255c9c
HH
347
348 _multiples = [(2., Numeric.log10(2.)), (5., Numeric.log10(5.))]
349
350 def redraw(self,dc=None):
1e4a197e
RD
351 if self.last_draw is not None:
352 apply(self.draw, self.last_draw + (dc,))
7d255c9c
HH
353
354 def clear(self):
355 self.canvas.delete('all')
356
357#
358# Now a sample implementation using the above...
359#
360
361if __name__ == '__main__':
362
363 class AppFrame(wx.wxFrame):
1e4a197e
RD
364 def __init__(self, parent, id, title):
365 wx.wxFrame.__init__(self, parent, id, title,
366 wx.wxPyDefaultPosition, wx.wxSize(400, 400))
367
368 # Now Create the menu bar and items
369 self.mainmenu = wx.wxMenuBar()
370
371 menu = wx.wxMenu()
372 menu.Append(200, '&Print...', 'Print the current plot')
373 wx.EVT_MENU(self, 200, self.OnFilePrint)
374 menu.Append(209, 'E&xit', 'Enough of this already!')
375 wx.EVT_MENU(self, 209, self.OnFileExit)
376 self.mainmenu.Append(menu, '&File')
377
378 menu = wx.wxMenu()
379 menu.Append(210, '&Draw', 'Draw plots')
380 wx.EVT_MENU(self,210,self.OnPlotDraw)
381 menu.Append(211, '&Redraw', 'Redraw plots')
382 wx.EVT_MENU(self,211,self.OnPlotRedraw)
383 menu.Append(212, '&Clear', 'Clear canvas')
384 wx.EVT_MENU(self,212,self.OnPlotClear)
385 self.mainmenu.Append(menu, '&Plot')
386
387 menu = wx.wxMenu()
388 menu.Append(220, '&About', 'About this thing...')
389 wx.EVT_MENU(self, 220, self.OnHelpAbout)
390 self.mainmenu.Append(menu, '&Help')
391
392 self.SetMenuBar(self.mainmenu)
393
394 # A status bar to tell people what's happening
395 self.CreateStatusBar(1)
396
397 self.client = PlotCanvas(self)
398
399 def OnFilePrint(self, event):
400 d = wx.wxMessageDialog(self,
7d255c9c
HH
401"""As of this writing, printing support in wxPython is shaky at best.
402Are you sure you want to do this?""", "Danger!", wx.wxYES_NO)
403 if d.ShowModal() == wx.wxID_YES:
1e4a197e
RD
404 psdc = wx.wxPostScriptDC("out.ps", wx.TRUE, self)
405 self.client.redraw(psdc)
7d255c9c 406
1e4a197e
RD
407 def OnFileExit(self, event):
408 self.Close()
7d255c9c 409
1e4a197e
RD
410 def OnPlotDraw(self, event):
411 self.client.draw(InitObjects(),'automatic','automatic');
7d255c9c 412
1e4a197e
RD
413 def OnPlotRedraw(self,event):
414 self.client.redraw()
7d255c9c 415
1e4a197e
RD
416 def OnPlotClear(self,event):
417 self.client.last_draw = None
418 dc = wx.wxClientDC(self.client)
419 dc.Clear()
7d255c9c 420
1e4a197e
RD
421 def OnHelpAbout(self, event):
422 about = wx.wxMessageDialog(self, __doc__, "About...", wx.wxOK)
423 about.ShowModal()
7d255c9c 424
1e4a197e
RD
425 def OnCloseWindow(self, event):
426 self.Destroy()
7d255c9c
HH
427
428 def InitObjects():
1e4a197e
RD
429 # 100 points sin function, plotted as green circles
430 data1 = 2.*Numeric.pi*Numeric.arange(200)/200.
431 data1.shape = (100, 2)
432 data1[:,1] = Numeric.sin(data1[:,0])
433 markers1 = PolyMarker(data1, color='green', marker='circle',size=1)
7d255c9c 434
1e4a197e
RD
435 # 50 points cos function, plotted as red line
436 data1 = 2.*Numeric.pi*Numeric.arange(100)/100.
437 data1.shape = (50,2)
438 data1[:,1] = Numeric.cos(data1[:,0])
439 lines = PolyLine(data1, color='red')
7d255c9c 440
1e4a197e
RD
441 # A few more points...
442 pi = Numeric.pi
443 markers2 = PolyMarker([(0., 0.), (pi/4., 1.), (pi/2, 0.),
444 (3.*pi/4., -1)], color='blue',
445 fillcolor='green', marker='cross')
7d255c9c 446
1e4a197e 447 return PlotGraphics([markers1, lines, markers2])
7d255c9c
HH
448
449
450 class MyApp(wx.wxApp):
1e4a197e
RD
451 def OnInit(self):
452 frame = AppFrame(wx.NULL, -1, "wxPlotCanvas")
453 frame.Show(wx.TRUE)
454 self.SetTopWindow(frame)
455 return wx.TRUE
7d255c9c
HH
456
457
458 app = MyApp(0)
459 app.MainLoop()