]>
git.saurik.com Git - wxWidgets.git/blob - utils/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
25 # Not everybody will have Numeric, so let's be cool about it...
30 d
= wx
.wxMessageDialog(wx
.NULL
,
31 """This module requires the Numeric module, which could not be imported.
32 It probably is not installed (it's not part of the standard Python
33 distribution). See the Python site (http://www.python.org) for
34 information on downloading source or binaries.""",
36 if d
.ShowModal() == wx
.wxID_CANCEL
:
37 d
= wx
.wxMessageDialog(wx
.NULL
, "I kid you not! Pressing Cancel won't help you!", "Not a joke", wx
.wxOK
)
47 def __init__(self
, points
, attr
):
48 self
.points
= Numeric
.array(points
)
49 self
.scaled
= self
.points
51 for name
, value
in self
._attributes
.items():
55 self
.attributes
[name
] = value
57 def boundingBox(self
):
58 return Numeric
.minimum
.reduce(self
.points
), \
59 Numeric
.maximum
.reduce(self
.points
)
61 def scaleAndShift(self
, scale
=1, shift
=0):
62 self
.scaled
= scale
*self
.points
+shift
65 class PolyLine(PolyPoints
):
67 def __init__(self
, points
, **attr
):
68 PolyPoints
.__init
__(self
, points
, attr
)
70 _attributes
= {'color': 'black',
74 color
= self
.attributes
['color']
75 width
= self
.attributes
['width']
77 dc
.SetPen(wx
.wxPen(wx
.wxNamedColour(color
), width
))
78 dc
.DrawLines(map(tuple,self
.scaled
))
81 class PolyMarker(PolyPoints
):
83 def __init__(self
, points
, **attr
):
85 PolyPoints
.__init
__(self
, points
, attr
)
87 _attributes
= {'color': 'black',
91 'fillstyle': wx
.wxSOLID
,
96 color
= self
.attributes
['color']
97 width
= self
.attributes
['width']
98 size
= self
.attributes
['size']
99 fillcolor
= self
.attributes
['fillcolor']
100 fillstyle
= self
.attributes
['fillstyle']
101 marker
= self
.attributes
['marker']
103 dc
.SetPen(wx
.wxPen(wx
.wxNamedColour(color
),width
))
105 dc
.SetBrush(wx
.wxBrush(wx
.wxNamedColour(fillcolor
),fillstyle
))
107 dc
.SetBrush(wx
.wxBrush(wx
.wxNamedColour('black'), wx
.wxTRANSPARENT
))
109 self
._drawmarkers
(dc
, self
.scaled
, marker
, size
)
111 def _drawmarkers(self
, dc
, coords
, marker
,size
=1):
112 f
= eval('self._' +marker
)
113 for xc
, yc
in coords
:
116 def _circle(self
, dc
, xc
, yc
, size
=1):
117 dc
.DrawEllipse(xc
-2.5*size
,yc
-2.5*size
,5.*size
,5.*size
)
119 def _dot(self
, dc
, xc
, yc
, size
=1):
122 def _square(self
, dc
, xc
, yc
, size
=1):
123 dc
.DrawRectangle(xc
-2.5*size
,yc
-2.5*size
,5.*size
,5.*size
)
125 def _triangle(self
, dc
, xc
, yc
, size
=1):
126 dc
.DrawPolygon([(-0.5*size
*5,0.2886751*size
*5),
127 (0.5*size
*5,0.2886751*size
*5),
128 (0.0,-0.577350*size
*5)],xc
,yc
)
130 def _triangle_down(self
, dc
, xc
, yc
, size
=1):
131 dc
.DrawPolygon([(-0.5*size
*5,-0.2886751*size
*5),
132 (0.5*size
*5,-0.2886751*size
*5),
133 (0.0,0.577350*size
*5)],xc
,yc
)
135 def _cross(self
, dc
, xc
, yc
, size
=1):
136 dc
.DrawLine(xc
-2.5*size
,yc
-2.5*size
,xc
+2.5*size
,yc
+2.5*size
)
137 dc
.DrawLine(xc
-2.5*size
,yc
+2.5*size
,xc
+2.5*size
,yc
-2.5*size
)
139 def _plus(self
, dc
, xc
, yc
, size
=1):
140 dc
.DrawLine(xc
-2.5*size
,yc
,xc
+2.5*size
,yc
)
141 dc
.DrawLine(xc
,yc
-2.5*size
,xc
,yc
+2.5*size
)
145 def __init__(self
, objects
):
146 self
.objects
= objects
148 def boundingBox(self
):
149 p1
, p2
= self
.objects
[0].boundingBox()
150 for o
in self
.objects
[1:]:
151 p1o
, p2o
= o
.boundingBox()
152 p1
= Numeric
.minimum(p1
, p1o
)
153 p2
= Numeric
.maximum(p2
, p2o
)
156 def scaleAndShift(self
, scale
=1, shift
=0):
157 for o
in self
.objects
:
158 o
.scaleAndShift(scale
, shift
)
160 def draw(self
, canvas
):
161 for o
in self
.objects
:
165 return len(self
.objects
)
167 def __getitem__(self
, item
):
168 return self
.objects
[item
]
171 class PlotCanvas(wx
.wxWindow
):
173 def __init__(self
, parent
, id = -1):
174 wx
.wxWindow
.__init
__(self
, parent
, id, wx
.wxPyDefaultPosition
, wx
.wxPyDefaultSize
)
176 self
.SetClientSizeWH(400,400)
177 self
.SetBackgroundColour(wx
.wxNamedColour("white"))
179 wx
.EVT_SIZE(self
,self
.reconfigure
)
181 self
.last_draw
= None
182 # self.font = self._testFont(font)
184 def OnPaint(self
, event
):
185 pdc
= wx
.wxPaintDC(self
)
186 if self
.last_draw
is not None:
187 apply(self
.draw
, self
.last_draw
+ (pdc
,))
189 def reconfigure(self
, event
):
190 (new_width
,new_height
) = self
.GetClientSizeTuple()
191 if new_width
== self
.width
and new_height
== self
.height
:
196 def _testFont(self
, font
):
198 bg
= self
.canvas
.cget('background')
200 item
= CanvasText(self
.canvas
, 0, 0, anchor
=NW
,
201 text
='0', fill
=bg
, font
=font
)
202 self
.canvas
.delete(item
)
208 (self
.width
,self
.height
) = self
.GetClientSizeTuple();
209 self
.plotbox_size
= 0.97*Numeric
.array([self
.width
, -self
.height
])
210 xo
= 0.5*(self
.width
-self
.plotbox_size
[0])
211 yo
= self
.height
-0.5*(self
.height
+self
.plotbox_size
[1])
212 self
.plotbox_origin
= Numeric
.array([xo
, yo
])
214 def draw(self
, graphics
, xaxis
= None, yaxis
= None, dc
= None):
215 if dc
== None: dc
= wx
.wxClientDC(self
)
218 self
.last_draw
= (graphics
, xaxis
, yaxis
)
219 p1
, p2
= graphics
.boundingBox()
220 xaxis
= self
._axisInterval
(xaxis
, p1
[0], p2
[0])
221 yaxis
= self
._axisInterval
(yaxis
, p1
[1], p2
[1])
222 text_width
= [0., 0.]
223 text_height
= [0., 0.]
224 if xaxis
is not None:
227 xticks
= self
._ticks
(xaxis
[0], xaxis
[1])
228 bb
= dc
.GetTextExtent(xticks
[0][1])
229 text_height
[1] = bb
[1]
230 text_width
[0] = 0.5*bb
[0]
231 bb
= dc
.GetTextExtent(xticks
[-1][1])
232 text_width
[1] = 0.5*bb
[0]
235 if yaxis
is not None:
238 yticks
= self
._ticks
(yaxis
[0], yaxis
[1])
240 bb
= dc
.GetTextExtent(y
[1])
241 text_width
[0] = max(text_width
[0],bb
[0])
244 text_height
[1] = max(text_height
[1], h
)
247 text1
= Numeric
.array([text_width
[0], -text_height
[1]])
248 text2
= Numeric
.array([text_width
[1], -text_height
[0]])
249 scale
= (self
.plotbox_size
-text1
-text2
) / (p2
-p1
)
250 shift
= -p1
*scale
+ self
.plotbox_origin
+ text1
251 self
._drawAxes
(dc
, xaxis
, yaxis
, p1
, p2
,
252 scale
, shift
, xticks
, yticks
)
253 graphics
.scaleAndShift(scale
, shift
)
257 def _axisInterval(self
, spec
, lower
, upper
):
260 if spec
== 'minimal':
262 return lower
-0.5, upper
+0.5
265 if spec
== 'automatic':
268 return lower
-0.5, upper
+0.5
269 log
= Numeric
.log10(range)
270 power
= Numeric
.floor(log
)
275 lower
= lower
- lower
% grid
278 upper
= upper
- mod
+ grid
280 if type(spec
) == type(()):
286 raise ValueError, str(spec
) + ': illegal axis specification'
288 def _drawAxes(self
, dc
, xaxis
, yaxis
,
289 bb1
, bb2
, scale
, shift
, xticks
, yticks
):
290 dc
.SetPen(wx
.wxPen(wx
.wxNamedColour('BLACK'),1))
291 if xaxis
is not None:
294 for y
, d
in [(bb1
[1], -3), (bb2
[1], 3)]:
295 p1
= scale
*Numeric
.array([lower
, y
])+shift
296 p2
= scale
*Numeric
.array([upper
, y
])+shift
297 dc
.DrawLine(p1
[0],p1
[1],p2
[0],p2
[1])
298 for x
, label
in xticks
:
299 p
= scale
*Numeric
.array([x
, y
])+shift
300 dc
.DrawLine(p
[0],p
[1],p
[0],p
[1]+d
)
302 dc
.DrawText(label
,p
[0],p
[1])
305 if yaxis
is not None:
308 h
= dc
.GetCharHeight()
309 for x
, d
in [(bb1
[0], -3), (bb2
[0], 3)]:
310 p1
= scale
*Numeric
.array([x
, lower
])+shift
311 p2
= scale
*Numeric
.array([x
, upper
])+shift
312 dc
.DrawLine(p1
[0],p1
[1],p2
[0],p2
[1])
313 for y
, label
in yticks
:
314 p
= scale
*Numeric
.array([x
, y
])+shift
315 dc
.DrawLine(p
[0],p
[1],p
[0]-d
,p
[1])
317 dc
.DrawText(label
,p
[0]-dc
.GetTextExtent(label
)[0],
321 def _ticks(self
, lower
, upper
):
322 ideal
= (upper
-lower
)/7.
323 log
= Numeric
.log10(ideal
)
324 power
= Numeric
.floor(log
)
328 for f
, lf
in self
._multiples
:
329 e
= Numeric
.fabs(fraction
-lf
)
333 grid
= factor
* 10.**power
334 if power
> 3 or power
< -3:
337 digits
= max(1, int(power
))
338 format
= '%' + `digits`
+'.0f'
341 format
= '%'+`digits
+2`
+'.'+`digits`
+'f'
343 t
= -grid
*Numeric
.floor(-lower
/grid
)
345 ticks
.append(t
, format
% (t
,))
349 _multiples
= [(2., Numeric
.log10(2.)), (5., Numeric
.log10(5.))]
351 def redraw(self
,dc
=None):
352 if self
.last_draw
is not None:
353 apply(self
.draw
, self
.last_draw
+ (dc
,))
356 self
.canvas
.delete('all')
359 # Now a sample implementation using the above...
362 if __name__
== '__main__':
364 class AppFrame(wx
.wxFrame
):
365 def __init__(self
, parent
, id, title
):
366 wx
.wxFrame
.__init
__(self
, parent
, id, title
,
367 wx
.wxPyDefaultPosition
, wx
.wxSize(400, 400))
369 # Now Create the menu bar and items
370 self
.mainmenu
= wx
.wxMenuBar()
373 menu
.Append(200, '&Print...', 'Print the current plot')
374 wx
.EVT_MENU(self
, 200, self
.OnFilePrint
)
375 menu
.Append(209, 'E&xit', 'Enough of this already!')
376 wx
.EVT_MENU(self
, 209, self
.OnFileExit
)
377 self
.mainmenu
.Append(menu
, '&File')
380 menu
.Append(210, '&Draw', 'Draw plots')
381 wx
.EVT_MENU(self
,210,self
.OnPlotDraw
)
382 menu
.Append(211, '&Redraw', 'Redraw plots')
383 wx
.EVT_MENU(self
,211,self
.OnPlotRedraw
)
384 menu
.Append(212, '&Clear', 'Clear canvas')
385 wx
.EVT_MENU(self
,212,self
.OnPlotClear
)
386 self
.mainmenu
.Append(menu
, '&Plot')
389 menu
.Append(220, '&About', 'About this thing...')
390 wx
.EVT_MENU(self
, 220, self
.OnHelpAbout
)
391 self
.mainmenu
.Append(menu
, '&Help')
393 self
.SetMenuBar(self
.mainmenu
)
395 # A status bar to tell people what's happening
396 self
.CreateStatusBar(1)
398 self
.client
= PlotCanvas(self
)
400 def OnFilePrint(self
, event
):
401 d
= wx
.wxMessageDialog(self
,
402 """As of this writing, printing support in wxPython is shaky at best.
403 Are you sure you want to do this?""", "Danger!", wx
.wxYES_NO
)
404 if d
.ShowModal() == wx
.wxID_YES
:
405 psdc
= wx
.wxPostScriptDC("out.ps", wx
.TRUE
, self
)
406 self
.client
.redraw(psdc
)
408 def OnFileExit(self
, event
):
411 def OnPlotDraw(self
, event
):
412 self
.client
.draw(InitObjects(),'automatic','automatic');
414 def OnPlotRedraw(self
,event
):
417 def OnPlotClear(self
,event
):
418 self
.client
.last_draw
= None
419 dc
= wx
.wxClientDC(self
.client
)
422 def OnHelpAbout(self
, event
):
423 about
= wx
.wxMessageDialog(self
, __doc__
, "About...", wx
.wxOK
)
426 def OnCloseWindow(self
, event
):
430 # 100 points sin function, plotted as green circles
431 data1
= 2.*Numeric
.pi
*Numeric
.arange(200)/200.
432 data1
.shape
= (100, 2)
433 data1
[:,1] = Numeric
.sin(data1
[:,0])
434 markers1
= PolyMarker(data1
, color
='green', marker
='circle',size
=1)
436 # 50 points cos function, plotted as red line
437 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
439 data1
[:,1] = Numeric
.cos(data1
[:,0])
440 lines
= PolyLine(data1
, color
='red')
442 # A few more points...
444 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
445 (3.*pi
/4., -1)], color
='blue',
446 fillcolor
='green', marker
='cross')
448 return PlotGraphics([markers1
, lines
, markers2
])
451 class MyApp(wx
.wxApp
):
453 frame
= AppFrame(wx
.NULL
, -1, "wxPlotCanvas")
455 self
.SetTopWindow(frame
)