]>
git.saurik.com Git - wxWidgets.git/blob - utils/wxPython/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
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
)
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')
357 #---------------------------------------------------------------------------
358 # if running standalone...
360 # ...a sample implementation using the above
364 if __name__
== '__main__':
366 # 100 points sin function, plotted as green circles
367 data1
= 2.*Numeric
.pi
*Numeric
.arange(200)/200.
368 data1
.shape
= (100, 2)
369 data1
[:,1] = Numeric
.sin(data1
[:,0])
370 markers1
= PolyMarker(data1
, color
='green', marker
='circle',size
=1)
372 # 50 points cos function, plotted as red line
373 data1
= 2.*Numeric
.pi
*Numeric
.arange(100)/100.
375 data1
[:,1] = Numeric
.cos(data1
[:,0])
376 lines
= PolyLine(data1
, color
='red')
378 # A few more points...
380 markers2
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.),
381 (3.*pi
/4., -1)], color
='blue',
382 fillcolor
='green', marker
='cross')
384 return PlotGraphics([markers1
, lines
, markers2
])
387 class AppFrame(wx
.wxFrame
):
388 def __init__(self
, parent
, id, title
):
389 wx
.wxFrame
.__init
__(self
, parent
, id, title
,
390 wx
.wxPyDefaultPosition
, wx
.wxSize(400, 400))
392 # Now Create the menu bar and items
393 self
.mainmenu
= wx
.wxMenuBar()
396 menu
.Append(200, '&Print...', 'Print the current plot')
397 wx
.EVT_MENU(self
, 200, self
.OnFilePrint
)
398 menu
.Append(209, 'E&xit', 'Enough of this already!')
399 wx
.EVT_MENU(self
, 209, self
.OnFileExit
)
400 self
.mainmenu
.Append(menu
, '&File')
403 menu
.Append(210, '&Draw', 'Draw plots')
404 wx
.EVT_MENU(self
,210,self
.OnPlotDraw
)
405 menu
.Append(211, '&Redraw', 'Redraw plots')
406 wx
.EVT_MENU(self
,211,self
.OnPlotRedraw
)
407 menu
.Append(212, '&Clear', 'Clear canvas')
408 wx
.EVT_MENU(self
,212,self
.OnPlotClear
)
409 self
.mainmenu
.Append(menu
, '&Plot')
412 menu
.Append(220, '&About', 'About this thing...')
413 wx
.EVT_MENU(self
, 220, self
.OnHelpAbout
)
414 self
.mainmenu
.Append(menu
, '&Help')
416 self
.SetMenuBar(self
.mainmenu
)
418 # A status bar to tell people what's happening
419 self
.CreateStatusBar(1)
421 self
.client
= PlotCanvas(self
)
423 def OnFilePrint(self
, event
):
424 d
= wx
.wxMessageDialog(self
,
425 """As of this writing, printing support in wxPython is shaky at best.
426 Are you sure you want to do this?""", "Danger!", wx
.wxYES_NO
)
427 if d
.ShowModal() == wx
.wxID_YES
:
428 psdc
= wx
.wxPostScriptDC("out.ps", wx
.TRUE
, self
)
429 self
.client
.redraw(psdc
)
431 def OnFileExit(self
, event
):
434 def OnPlotDraw(self
, event
):
435 self
.client
.draw(_InitObjects(),'automatic','automatic');
437 def OnPlotRedraw(self
,event
):
440 def OnPlotClear(self
,event
):
441 self
.client
.last_draw
= None
442 dc
= wx
.wxClientDC(self
.client
)
445 def OnHelpAbout(self
, event
):
446 about
= wx
.wxMessageDialog(self
, __doc__
, "About...", wx
.wxOK
)
449 def OnCloseWindow(self
, event
):
453 class MyApp(wx
.wxApp
):
455 frame
= AppFrame(wx
.NULL
, -1, "wxPlotCanvas")
457 self
.SetTopWindow(frame
)
467 #----------------------------------------------------------------------------