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