]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wx/lib/wxPlotCanvas.py
2 This is a port of Konrad Hinsen's tkPlotCanvas.py plotting module.
3 After thinking long and hard I came up with the name "wxPlotCanvas.py".
5 This file contains two parts; first the re-usable library stuff, then, after
6 a "if __name__=='__main__'" test, a simple frame and a few default plots
9 Harm van der Heijden, feb 1999
11 Original 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...
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
21 # 12/13/2003 - Jeff Grimmett (grimmtooth@softhome.net)
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
27 # o Added deprecation warning.
35 THIS MODULE IS NOW DEPRECATED
37 This module has been replaced by wxPyPlot, which in wxPython
38 can be found in wx.lib.plot.py.
42 warnings
.warn(warningmsg
, DeprecationWarning, stacklevel
=2)
44 # Not everybody will have Numeric, so let's be cool about it...
49 msg
= """This module requires the Numeric module, which could not be
50 imported. It probably is not installed (it's not part of the standard
51 Python distribution). See the Python site (http://www.python.org) for
52 information on downloading source or binaries."""
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
)
68 def __init__(self
, points
, attr
):
69 self
.points
= Numeric
.array(points
)
70 self
.scaled
= self
.points
72 for name
, value
in self
._attributes
.items():
76 self
.attributes
[name
] = value
78 def boundingBox(self
):
79 return Numeric
.minimum
.reduce(self
.points
), \
80 Numeric
.maximum
.reduce(self
.points
)
82 def scaleAndShift(self
, scale
=1, shift
=0):
83 self
.scaled
= scale
*self
.points
+shift
86 class PolyLine(PolyPoints
):
88 def __init__(self
, points
, **attr
):
89 PolyPoints
.__init
__(self
, points
, attr
)
91 _attributes
= {'color': 'black',
95 color
= self
.attributes
['color']
96 width
= self
.attributes
['width']
98 dc
.SetPen(wx
.Pen(wx
.NamedColour(color
), width
))
99 dc
.DrawLines(map(tuple,self
.scaled
))
102 class PolyMarker(PolyPoints
):
104 def __init__(self
, points
, **attr
):
106 PolyPoints
.__init
__(self
, points
, attr
)
108 _attributes
= {'color': 'black',
112 'fillstyle': wx
.SOLID
,
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']
124 dc
.SetPen(wx
.Pen(wx
.NamedColour(color
),width
))
126 dc
.SetBrush(wx
.Brush(wx
.NamedColour(fillcolor
),fillstyle
))
128 dc
.SetBrush(wx
.Brush(wx
.NamedColour('black'), wx
.TRANSPARENT
))
130 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
132 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
133 f
= eval('self._' +marker
)
134 for xc
, yc
in coords
:
137 def _circle(self
, dc
, xc
, yc
, size
=1):
138 dc
.DrawEllipse(xc
-2.5*size
,yc
-2.5*size
, 5.*size
,5.*size
)
140 def _dot(self
, dc
, xc
, yc
, size
=1):
143 def _square(self
, dc
, xc
, yc
, size
=1):
144 dc
.DrawRectangle(xc
-2.5*size
,yc
-2.5*size
,5.*size
,5.*size
)
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
)
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
)
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
)
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
)
166 def __init__(self
, objects
):
167 self
.objects
= objects
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
)
177 def scaleAndShift(self
, scale
=1, shift
=0):
178 for o
in self
.objects
:
179 o
.scaleAndShift(scale
, shift
)
181 def draw(self
, canvas
):
182 for o
in self
.objects
:
186 return len(self
.objects
)
188 def __getitem__(self
, item
):
189 return self
.objects
[item
]
192 class PlotCanvas(wx
.Window
):
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
)
199 self
.SetClientSize((400,400))
200 self
.SetBackgroundColour("white")
202 self
.Bind(wx
.EVT_SIZE
,self
.reconfigure
)
203 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
205 self
.last_draw
= None
206 # self.font = self._testFont(font)
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
,))
213 def reconfigure(self
, event
):
214 (new_width
,new_height
) = self
.GetClientSize()
215 if new_width
== self
.width
and new_height
== self
.height
:
220 def _testFont(self
, font
):
222 bg
= self
.canvas
.cget('background')
224 item
= CanvasText(self
.canvas
, 0, 0, anchor
=NW
,
225 text
='0', fill
=bg
, font
=font
)
226 self
.canvas
.delete(item
)
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
])
238 def draw(self
, graphics
, xaxis
= None, yaxis
= None, dc
= None):
239 if dc
== None: dc
= wx
.ClientDC(self
)
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:
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]
259 if yaxis
is not None:
262 yticks
= self
._ticks
(yaxis
[0], yaxis
[1])
264 bb
= dc
.GetTextExtent(y
[1])
265 text_width
[0] = max(text_width
[0],bb
[0])
268 text_height
[1] = max(text_height
[1], h
)
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
)
281 def _axisInterval(self
, spec
, lower
, upper
):
284 if spec
== 'minimal':
286 return lower
-0.5, upper
+0.5
289 if spec
== 'automatic':
292 return lower
-0.5, upper
+0.5
293 log
= Numeric
.log10(range)
294 power
= Numeric
.floor(log
)
299 lower
= lower
- lower
% grid
302 upper
= upper
- mod
+ grid
304 if type(spec
) == type(()):
310 raise ValueError, str(spec
) + ': illegal axis specification'
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:
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
)
326 dc
.DrawText(label
, p
[0],p
[1])
329 if yaxis
is not None:
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])
342 p
[0]-dc
.GetTextExtent(label
)[0], p
[1]-0.5*h
)
345 def _ticks(self
, lower
, upper
):
346 ideal
= (upper
-lower
)/7.
347 log
= Numeric
.log10(ideal
)
348 power
= Numeric
.floor(log
)
352 for f
, lf
in self
._multiples
:
353 e
= Numeric
.fabs(fraction
-lf
)
357 grid
= factor
* 10.**power
358 if power
> 3 or power
< -3:
361 digits
= max(1, int(power
))
362 format
= '%' + `digits`
+'.0f'
365 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
367 t
= -grid
*Numeric
.floor(-lower
/grid
)
369 ticks
.append( (t
, format
% (t
,)) )
373 _multiples
= [(2., Numeric
.log10(2.)), (5., Numeric
.log10(5.))]
375 def redraw(self
,dc
=None):
376 if self
.last_draw
is not None:
377 apply(self
.draw
, self
.last_draw
+ (dc
,))
380 self
.canvas
.delete('all')
382 #---------------------------------------------------------------------------
383 # if running standalone...
385 # ...a sample implementation using the above
389 if __name__
== '__main__':
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)
397 # 50 points cos function, plotted as red line
398 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
400 data1
[:,1] = Numeric
.cos(data1
[:,0])
401 lines
= PolyLine(data1
, color
='red')
403 # A few more points...
405 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
406 (3.*pi
/4., -1)], color
='blue',
407 fillcolor
='green', marker
='cross')
409 return PlotGraphics([markers1
, lines
, markers2
])
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))
417 # Now Create the menu bar and items
418 self
.mainmenu
= wx
.MenuBar()
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')
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')
437 menu
.Append(220, '&About', 'About this thing...')
438 self
.Bind(wx
.EVT_MENU
, self
.OnHelpAbout
, id=220)
439 self
.mainmenu
.Append(menu
, '&Help')
441 self
.SetMenuBar(self
.mainmenu
)
443 # A status bar to tell people what's happening
444 self
.CreateStatusBar(1)
446 self
.client
= PlotCanvas(self
)
448 def OnFilePrint(self
, event
):
449 d
= wx
.MessageDialog(self
,
450 """As of this writing, printing support in wxPython is shaky at best.
451 Are 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
)
456 def OnFileExit(self
, event
):
459 def OnPlotDraw(self
, event
):
460 self
.client
.draw(_InitObjects(),'automatic','automatic');
462 def OnPlotRedraw(self
,event
):
465 def OnPlotClear(self
,event
):
466 self
.client
.last_draw
= None
467 dc
= wx
.ClientDC(self
.client
)
470 def OnHelpAbout(self
, event
):
471 about
= wx
.MessageDialog(self
, __doc__
, "About...", wx
.OK
)
478 frame
= AppFrame(None, -1, "wxPlotCanvas")
480 self
.SetTopWindow(frame
)
490 #----------------------------------------------------------------------------