]>
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
 
  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
)