]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/tests/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
22 from wxPython
import wx
24 # Not everybody will have Numeric, so let's be cool about it...
29 d
= wx
.wxMessageDialog(wx
.NULL
,
30 """This module requires the Numeric module, which could not be imported.
31 It probably is not installed (it's not part of the standard Python
32 distribution). See the Python site (http://www.python.org) for
33 information on downloading source or binaries.""",
35 if d
.ShowModal() == wx
.wxID_CANCEL
:
36 d
= wx
.wxMessageDialog(wx
.NULL
, "I kid you not! Pressing Cancel won't help you!", "Not a joke", wx
.wxOK
)
46 def __init__(self
, points
, attr
):
47 self
.points
= Numeric
.array(points
)
48 self
.scaled
= self
.points
50 for name
, value
in self
._attributes
.items():
54 self
.attributes
[name
] = value
56 def boundingBox(self
):
57 return Numeric
.minimum
.reduce(self
.points
), \
58 Numeric
.maximum
.reduce(self
.points
)
60 def scaleAndShift(self
, scale
=1, shift
=0):
61 self
.scaled
= scale
*self
.points
+shift
64 class PolyLine(PolyPoints
):
66 def __init__(self
, points
, **attr
):
67 PolyPoints
.__init
__(self
, points
, attr
)
69 _attributes
= {'color': 'black',
73 color
= self
.attributes
['color']
74 width
= self
.attributes
['width']
76 dc
.SetPen(wx
.wxPen(wx
.wxNamedColour(color
), width
))
77 dc
.DrawLines(map(tuple,self
.scaled
))
80 class PolyMarker(PolyPoints
):
82 def __init__(self
, points
, **attr
):
84 PolyPoints
.__init
__(self
, points
, attr
)
86 _attributes
= {'color': 'black',
90 'fillstyle': wx
.wxSOLID
,
95 color
= self
.attributes
['color']
96 width
= self
.attributes
['width']
97 size
= self
.attributes
['size']
98 fillcolor
= self
.attributes
['fillcolor']
99 fillstyle
= self
.attributes
['fillstyle']
100 marker
= self
.attributes
['marker']
102 dc
.SetPen(wx
.wxPen(wx
.wxNamedColour(color
),width
))
104 dc
.SetBrush(wx
.wxBrush(wx
.wxNamedColour(fillcolor
),fillstyle
))
106 dc
.SetBrush(wx
.wxBrush(wx
.wxNamedColour('black'), wx
.wxTRANSPARENT
))
108 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
110 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
111 f
= eval('self._' +marker
)
112 for xc
, yc
in coords
:
115 def _circle(self
, dc
, xc
, yc
, size
=1):
116 dc
.DrawEllipse(xc
-2.5*size
,yc
-2.5*size
,5.*size
,5.*size
)
118 def _dot(self
, dc
, xc
, yc
, size
=1):
121 def _square(self
, dc
, xc
, yc
, size
=1):
122 dc
.DrawRectangle(xc
-2.5*size
,yc
-2.5*size
,5.*size
,5.*size
)
124 def _triangle(self
, dc
, xc
, yc
, size
=1):
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
)
129 def _triangle_down(self
, dc
, xc
, yc
, size
=1):
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
)
134 def _cross(self
, dc
, xc
, yc
, size
=1):
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
)
138 def _plus(self
, dc
, xc
, yc
, size
=1):
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
)
144 def __init__(self
, objects
):
145 self
.objects
= objects
147 def boundingBox(self
):
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
)
155 def scaleAndShift(self
, scale
=1, shift
=0):
156 for o
in self
.objects
:
157 o
.scaleAndShift(scale
, shift
)
159 def draw(self
, canvas
):
160 for o
in self
.objects
:
164 return len(self
.objects
)
166 def __getitem__(self
, item
):
167 return self
.objects
[item
]
170 class PlotCanvas(wx
.wxWindow
):
172 def __init__(self
, parent
, id = -1):
173 wx
.wxWindow
.__init
__(self
, parent
, id, wx
.wxPyDefaultPosition
, wx
.wxPyDefaultSize
)
175 self
.SetClientSizeWH(400,400)
176 self
.SetBackgroundColour(wx
.wxNamedColour("white"))
178 wx
.EVT_SIZE(self
,self
.reconfigure
)
180 self
.last_draw
= None
181 # self.font = self._testFont(font)
183 def OnPaint(self
, event
):
184 pdc
= wx
.wxPaintDC(self
)
185 if self
.last_draw
is not None:
186 apply(self
.draw
, self
.last_draw
+ (pdc
,))
188 def reconfigure(self
, event
):
189 (new_width
,new_height
) = self
.GetClientSizeTuple()
190 if new_width
== self
.width
and new_height
== self
.height
:
195 def _testFont(self
, font
):
197 bg
= self
.canvas
.cget('background')
199 item
= CanvasText(self
.canvas
, 0, 0, anchor
=NW
,
200 text
='0', fill
=bg
, font
=font
)
201 self
.canvas
.delete(item
)
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
])
213 def draw(self
, graphics
, xaxis
= None, yaxis
= None, dc
= None):
214 if dc
== None: dc
= wx
.wxClientDC(self
)
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:
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]
234 if yaxis
is not None:
237 yticks
= self
._ticks
(yaxis
[0], yaxis
[1])
239 bb
= dc
.GetTextExtent(y
[1])
240 text_width
[0] = max(text_width
[0],bb
[0])
243 text_height
[1] = max(text_height
[1], h
)
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
,
251 scale
, shift
, xticks
, yticks
)
252 graphics
.scaleAndShift(scale
, shift
)
256 def _axisInterval(self
, spec
, lower
, upper
):
259 if spec
== 'minimal':
261 return lower
-0.5, upper
+0.5
264 if spec
== 'automatic':
267 return lower
-0.5, upper
+0.5
268 log
= Numeric
.log10(range)
269 power
= Numeric
.floor(log
)
274 lower
= lower
- lower
% grid
277 upper
= upper
- mod
+ grid
279 if type(spec
) == type(()):
285 raise ValueError, str(spec
) + ': illegal axis specification'
287 def _drawAxes(self
, dc
, xaxis
, yaxis
,
288 bb1
, bb2
, scale
, shift
, xticks
, yticks
):
289 dc
.SetPen(wx
.wxPen(wx
.wxNamedColour('BLACK'),1))
290 if xaxis
is not None:
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
)
301 dc
.DrawText(label
,p
[0],p
[1])
304 if yaxis
is not None:
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])
316 dc
.DrawText(label
,p
[0]-dc
.GetTextExtent(label
)[0],
320 def _ticks(self
, lower
, upper
):
321 ideal
= (upper
-lower
)/7.
322 log
= Numeric
.log10(ideal
)
323 power
= Numeric
.floor(log
)
327 for f
, lf
in self
._multiples
:
328 e
= Numeric
.fabs(fraction
-lf
)
332 grid
= factor
* 10.**power
333 if power
> 3 or power
< -3:
336 digits
= max(1, int(power
))
337 format
= '%' + `digits`
+'.0f'
340 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
342 t
= -grid
*Numeric
.floor(-lower
/grid
)
344 ticks
.append(t
, format
% (t
,))
348 _multiples
= [(2., Numeric
.log10(2.)), (5., Numeric
.log10(5.))]
350 def redraw(self
,dc
=None):
351 if self
.last_draw
is not None:
352 apply(self
.draw
, self
.last_draw
+ (dc
,))
355 self
.canvas
.delete('all')
358 # Now a sample implementation using the above...
361 if __name__
== '__main__':
363 class AppFrame(wx
.wxFrame
):
364 def __init__(self
, parent
, id, title
):
365 wx
.wxFrame
.__init
__(self
, parent
, id, title
,
366 wx
.wxPyDefaultPosition
, wx
.wxSize(400, 400))
368 # Now Create the menu bar and items
369 self
.mainmenu
= wx
.wxMenuBar()
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')
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')
388 menu
.Append(220, '&About', 'About this thing...')
389 wx
.EVT_MENU(self
, 220, self
.OnHelpAbout
)
390 self
.mainmenu
.Append(menu
, '&Help')
392 self
.SetMenuBar(self
.mainmenu
)
394 # A status bar to tell people what's happening
395 self
.CreateStatusBar(1)
397 self
.client
= PlotCanvas(self
)
399 def OnFilePrint(self
, event
):
400 d
= wx
.wxMessageDialog(self
,
401 """As of this writing, printing support in wxPython is shaky at best.
402 Are you sure you want to do this?""", "Danger!", wx
.wxYES_NO
)
403 if d
.ShowModal() == wx
.wxID_YES
:
404 psdc
= wx
.wxPostScriptDC("out.ps", wx
.TRUE
, self
)
405 self
.client
.redraw(psdc
)
407 def OnFileExit(self
, event
):
410 def OnPlotDraw(self
, event
):
411 self
.client
.draw(InitObjects(),'automatic','automatic');
413 def OnPlotRedraw(self
,event
):
416 def OnPlotClear(self
,event
):
417 self
.client
.last_draw
= None
418 dc
= wx
.wxClientDC(self
.client
)
421 def OnHelpAbout(self
, event
):
422 about
= wx
.wxMessageDialog(self
, __doc__
, "About...", wx
.wxOK
)
425 def OnCloseWindow(self
, event
):
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)
435 # 50 points cos function, plotted as red line
436 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
438 data1
[:,1] = Numeric
.cos(data1
[:,0])
439 lines
= PolyLine(data1
, color
='red')
441 # A few more points...
443 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
444 (3.*pi
/4., -1)], color
='blue',
445 fillcolor
='green', marker
='cross')
447 return PlotGraphics([markers1
, lines
, markers2
])
450 class MyApp(wx
.wxApp
):
452 frame
= AppFrame(wx
.NULL
, -1, "wxPlotCanvas")
454 self
.SetTopWindow(frame
)