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