]>
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."""
55 if wx
.Platform
== '__WXMSW__' and wx
.GetApp() is not None:
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
)
67 def __init__(self
, points
, attr
):
68 self
.points
= Numeric
.array(points
)
69 self
.scaled
= self
.points
71 for name
, value
in self
._attributes
.items():
75 self
.attributes
[name
] = value
77 def boundingBox(self
):
78 return Numeric
.minimum
.reduce(self
.points
), \
79 Numeric
.maximum
.reduce(self
.points
)
81 def scaleAndShift(self
, scale
=1, shift
=0):
82 self
.scaled
= scale
*self
.points
+shift
85 class PolyLine(PolyPoints
):
87 def __init__(self
, points
, **attr
):
88 PolyPoints
.__init
__(self
, points
, attr
)
90 _attributes
= {'color': 'black',
94 color
= self
.attributes
['color']
95 width
= self
.attributes
['width']
97 dc
.SetPen(wx
.Pen(wx
.NamedColour(color
), width
))
98 dc
.DrawLines(map(tuple,self
.scaled
))
101 class PolyMarker(PolyPoints
):
103 def __init__(self
, points
, **attr
):
105 PolyPoints
.__init
__(self
, points
, attr
)
107 _attributes
= {'color': 'black',
111 'fillstyle': wx
.SOLID
,
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']
123 dc
.SetPen(wx
.Pen(wx
.NamedColour(color
),width
))
125 dc
.SetBrush(wx
.Brush(wx
.NamedColour(fillcolor
),fillstyle
))
127 dc
.SetBrush(wx
.Brush(wx
.NamedColour('black'), wx
.TRANSPARENT
))
129 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
131 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
132 f
= eval('self._' +marker
)
133 for xc
, yc
in coords
:
136 def _circle(self
, dc
, xc
, yc
, size
=1):
137 dc
.DrawEllipse(xc
-2.5*size
,yc
-2.5*size
, 5.*size
,5.*size
)
139 def _dot(self
, dc
, xc
, yc
, size
=1):
142 def _square(self
, dc
, xc
, yc
, size
=1):
143 dc
.DrawRectangle(xc
-2.5*size
,yc
-2.5*size
,5.*size
,5.*size
)
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
)
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
)
155 def _cross(self
, dc
, xc
, yc
, size
=1):
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
)
159 def _plus(self
, dc
, xc
, yc
, size
=1):
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
)
165 def __init__(self
, objects
):
166 self
.objects
= objects
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
)
176 def scaleAndShift(self
, scale
=1, shift
=0):
177 for o
in self
.objects
:
178 o
.scaleAndShift(scale
, shift
)
180 def draw(self
, canvas
):
181 for o
in self
.objects
:
185 return len(self
.objects
)
187 def __getitem__(self
, item
):
188 return self
.objects
[item
]
191 class PlotCanvas(wx
.Window
):
193 def __init__(self
, parent
, id=-1,
194 pos
= wx
.DefaultPosition
, size
= wx
.DefaultSize
,
195 style
= 0, name
= 'plotCanvas'):
196 wx
.Window
.__init
__(self
, parent
, id, pos
, size
, style
, name
)
198 self
.SetClientSize((400,400))
199 self
.SetBackgroundColour("white")
201 self
.Bind(wx
.EVT_SIZE
,self
.reconfigure
)
202 self
.Bind(wx
.EVT_PAINT
, self
.OnPaint
)
204 self
.last_draw
= None
205 # self.font = self._testFont(font)
207 def OnPaint(self
, event
):
208 pdc
= wx
.PaintDC(self
)
209 if self
.last_draw
is not None:
210 apply(self
.draw
, self
.last_draw
+ (pdc
,))
212 def reconfigure(self
, event
):
213 (new_width
,new_height
) = self
.GetClientSize()
214 if new_width
== self
.width
and new_height
== self
.height
:
219 def _testFont(self
, font
):
221 bg
= self
.canvas
.cget('background')
223 item
= CanvasText(self
.canvas
, 0, 0, anchor
=NW
,
224 text
='0', fill
=bg
, font
=font
)
225 self
.canvas
.delete(item
)
231 (self
.width
,self
.height
) = self
.GetClientSize();
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
])
237 def draw(self
, graphics
, xaxis
= None, yaxis
= None, dc
= None):
238 if dc
== None: dc
= wx
.ClientDC(self
)
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:
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]
258 if yaxis
is not None:
261 yticks
= self
._ticks
(yaxis
[0], yaxis
[1])
263 bb
= dc
.GetTextExtent(y
[1])
264 text_width
[0] = max(text_width
[0],bb
[0])
267 text_height
[1] = max(text_height
[1], h
)
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
)
280 def _axisInterval(self
, spec
, lower
, upper
):
283 if spec
== 'minimal':
285 return lower
-0.5, upper
+0.5
288 if spec
== 'automatic':
291 return lower
-0.5, upper
+0.5
292 log
= Numeric
.log10(range)
293 power
= Numeric
.floor(log
)
298 lower
= lower
- lower
% grid
301 upper
= upper
- mod
+ grid
303 if type(spec
) == type(()):
309 raise ValueError, str(spec
) + ': illegal axis specification'
311 def _drawAxes(self
, dc
, xaxis
, yaxis
,
312 bb1
, bb2
, scale
, shift
, xticks
, yticks
):
313 dc
.SetPen(wx
.Pen(wx
.NamedColour('BLACK'),1))
314 if xaxis
is not None:
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
320 dc
.DrawLine(p1
[0],p1
[1], p2
[0],p2
[1])
321 for x
, label
in xticks
:
322 p
= scale
*Numeric
.array([x
, y
])+shift
323 dc
.DrawLine(p
[0],p
[1], p
[0],p
[1]+d
)
325 dc
.DrawText(label
, p
[0],p
[1])
328 if yaxis
is not None:
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
335 dc
.DrawLine(p1
[0],p1
[1], p2
[0],p2
[1])
336 for y
, label
in yticks
:
337 p
= scale
*Numeric
.array([x
, y
])+shift
338 dc
.DrawLine(p
[0],p
[1], p
[0]-d
,p
[1])
341 p
[0]-dc
.GetTextExtent(label
)[0], p
[1]-0.5*h
)
344 def _ticks(self
, lower
, upper
):
345 ideal
= (upper
-lower
)/7.
346 log
= Numeric
.log10(ideal
)
347 power
= Numeric
.floor(log
)
351 for f
, lf
in self
._multiples
:
352 e
= Numeric
.fabs(fraction
-lf
)
356 grid
= factor
* 10.**power
357 if power
> 3 or power
< -3:
360 digits
= max(1, int(power
))
361 format
= '%' + `digits`
+'.0f'
364 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
366 t
= -grid
*Numeric
.floor(-lower
/grid
)
368 ticks
.append( (t
, format
% (t
,)) )
372 _multiples
= [(2., Numeric
.log10(2.)), (5., Numeric
.log10(5.))]
374 def redraw(self
,dc
=None):
375 if self
.last_draw
is not None:
376 apply(self
.draw
, self
.last_draw
+ (dc
,))
379 self
.canvas
.delete('all')
381 #---------------------------------------------------------------------------
382 # if running standalone...
384 # ...a sample implementation using the above
388 if __name__
== '__main__':
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)
396 # 50 points cos function, plotted as red line
397 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
399 data1
[:,1] = Numeric
.cos(data1
[:,0])
400 lines
= PolyLine(data1
, color
='red')
402 # A few more points...
404 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
405 (3.*pi
/4., -1)], color
='blue',
406 fillcolor
='green', marker
='cross')
408 return PlotGraphics([markers1
, lines
, markers2
])
411 class AppFrame(wx
.Frame
):
412 def __init__(self
, parent
, id, title
):
413 wx
.Frame
.__init
__(self
, parent
, id, title
,
414 wx
.DefaultPosition
, (400, 400))
416 # Now Create the menu bar and items
417 self
.mainmenu
= wx
.MenuBar()
420 menu
.Append(200, '&Print...', 'Print the current plot')
421 self
.Bind(wx
.EVT_MENU
, self
.OnFilePrint
, id=200)
422 menu
.Append(209, 'E&xit', 'Enough of this already!')
423 self
.Bind(wx
.EVT_MENU
, self
.OnFileExit
, id=209)
424 self
.mainmenu
.Append(menu
, '&File')
427 menu
.Append(210, '&Draw', 'Draw plots')
428 self
.Bind(wx
.EVT_MENU
,self
.OnPlotDraw
, id=210)
429 menu
.Append(211, '&Redraw', 'Redraw plots')
430 self
.Bind(wx
.EVT_MENU
,self
.OnPlotRedraw
, id=211)
431 menu
.Append(212, '&Clear', 'Clear canvas')
432 self
.Bind(wx
.EVT_MENU
,self
.OnPlotClear
, id=212)
433 self
.mainmenu
.Append(menu
, '&Plot')
436 menu
.Append(220, '&About', 'About this thing...')
437 self
.Bind(wx
.EVT_MENU
, self
.OnHelpAbout
, id=220)
438 self
.mainmenu
.Append(menu
, '&Help')
440 self
.SetMenuBar(self
.mainmenu
)
442 # A status bar to tell people what's happening
443 self
.CreateStatusBar(1)
445 self
.client
= PlotCanvas(self
)
447 def OnFilePrint(self
, event
):
448 d
= wx
.MessageDialog(self
,
449 """As of this writing, printing support in wxPython is shaky at best.
450 Are 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
)
453 self
.client
.redraw(psdc
)
455 def OnFileExit(self
, event
):
458 def OnPlotDraw(self
, event
):
459 self
.client
.draw(_InitObjects(),'automatic','automatic');
461 def OnPlotRedraw(self
,event
):
464 def OnPlotClear(self
,event
):
465 self
.client
.last_draw
= None
466 dc
= wx
.ClientDC(self
.client
)
469 def OnHelpAbout(self
, event
):
470 about
= wx
.MessageDialog(self
, __doc__
, "About...", wx
.OK
)
477 frame
= AppFrame(None, -1, "wxPlotCanvas")
479 self
.SetTopWindow(frame
)
489 #----------------------------------------------------------------------------