]>
git.saurik.com Git - wxWidgets.git/blob - wxPython/wxPython/lib/wxPlotCanvas.py
926333c92d05eb1efb869365e5f38d1b686b3c74
   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     msg 
= """This module requires the Numeric module, which could not be 
  31 imported.  It probably is not installed (it's not part of the standard 
  32 Python distribution). See the Python site (http://www.python.org) for 
  33 information on downloading source or binaries.""" 
  35     if wxPlatform 
== '__WXMSW__': 
  36         d 
= wx
.wxMessageDialog(wx
.NULL
, msg
, "Numeric not found") 
  37         if d
.ShowModal() == wx
.wxID_CANCEL
: 
  38             d 
= wx
.wxMessageDialog(wx
.NULL
, "I kid you not! Pressing Cancel won't help you!", "Not a joke", wx
.wxOK
) 
  49     def __init__(self
, points
, attr
): 
  50         self
.points 
= Numeric
.array(points
) 
  51         self
.scaled 
= self
.points
 
  53         for name
, value 
in self
._attributes
.items(): 
  57             self
.attributes
[name
] = value
 
  59     def boundingBox(self
): 
  60         return Numeric
.minimum
.reduce(self
.points
), \
 
  61                Numeric
.maximum
.reduce(self
.points
) 
  63     def scaleAndShift(self
, scale
=1, shift
=0): 
  64         self
.scaled 
= scale
*self
.points
+shift
 
  67 class PolyLine(PolyPoints
): 
  69     def __init__(self
, points
, **attr
): 
  70         PolyPoints
.__init
__(self
, points
, attr
) 
  72     _attributes 
= {'color': 'black', 
  76         color 
= self
.attributes
['color'] 
  77         width 
= self
.attributes
['width'] 
  79         dc
.SetPen(wx
.wxPen(wx
.wxNamedColour(color
), width
)) 
  80         dc
.DrawLines(map(tuple,self
.scaled
)) 
  83 class PolyMarker(PolyPoints
): 
  85     def __init__(self
, points
, **attr
): 
  87         PolyPoints
.__init
__(self
, points
, attr
) 
  89     _attributes 
= {'color': 'black', 
  93                    'fillstyle': wx
.wxSOLID
, 
  98         color 
= self
.attributes
['color'] 
  99         width 
= self
.attributes
['width'] 
 100         size 
= self
.attributes
['size'] 
 101         fillcolor 
= self
.attributes
['fillcolor'] 
 102         fillstyle 
= self
.attributes
['fillstyle'] 
 103         marker 
= self
.attributes
['marker'] 
 105         dc
.SetPen(wx
.wxPen(wx
.wxNamedColour(color
),width
)) 
 107             dc
.SetBrush(wx
.wxBrush(wx
.wxNamedColour(fillcolor
),fillstyle
)) 
 109             dc
.SetBrush(wx
.wxBrush(wx
.wxNamedColour('black'), wx
.wxTRANSPARENT
)) 
 111         self
._drawmarkers
(dc
, self
.scaled
, marker
, size
) 
 113     def _drawmarkers(self
, dc
, coords
, marker
,size
=1): 
 114         f 
= eval('self._' +marker
) 
 115         for xc
, yc 
in coords
: 
 118     def _circle(self
, dc
, xc
, yc
, size
=1): 
 119         dc
.DrawEllipse(xc
-2.5*size
,yc
-2.5*size
,5.*size
,5.*size
) 
 121     def _dot(self
, dc
, xc
, yc
, size
=1): 
 124     def _square(self
, dc
, xc
, yc
, size
=1): 
 125         dc
.DrawRectangle(xc
-2.5*size
,yc
-2.5*size
,5.*size
,5.*size
) 
 127     def _triangle(self
, dc
, xc
, yc
, size
=1): 
 128         dc
.DrawPolygon([(-0.5*size
*5,0.2886751*size
*5), 
 129                        (0.5*size
*5,0.2886751*size
*5), 
 130                        (0.0,-0.577350*size
*5)],xc
,yc
) 
 132     def _triangle_down(self
, dc
, xc
, yc
, size
=1): 
 133         dc
.DrawPolygon([(-0.5*size
*5,-0.2886751*size
*5), 
 134                        (0.5*size
*5,-0.2886751*size
*5), 
 135                        (0.0,0.577350*size
*5)],xc
,yc
) 
 137     def _cross(self
, dc
, xc
, yc
, size
=1): 
 138         dc
.DrawLine(xc
-2.5*size
,yc
-2.5*size
,xc
+2.5*size
,yc
+2.5*size
) 
 139         dc
.DrawLine(xc
-2.5*size
,yc
+2.5*size
,xc
+2.5*size
,yc
-2.5*size
) 
 141     def _plus(self
, dc
, xc
, yc
, size
=1): 
 142         dc
.DrawLine(xc
-2.5*size
,yc
,xc
+2.5*size
,yc
) 
 143         dc
.DrawLine(xc
,yc
-2.5*size
,xc
,yc
+2.5*size
) 
 147     def __init__(self
, objects
): 
 148         self
.objects 
= objects
 
 150     def boundingBox(self
): 
 151         p1
, p2 
= self
.objects
[0].boundingBox() 
 152         for o 
in self
.objects
[1:]: 
 153             p1o
, p2o 
= o
.boundingBox() 
 154             p1 
= Numeric
.minimum(p1
, p1o
) 
 155             p2 
= Numeric
.maximum(p2
, p2o
) 
 158     def scaleAndShift(self
, scale
=1, shift
=0): 
 159         for o 
in self
.objects
: 
 160             o
.scaleAndShift(scale
, shift
) 
 162     def draw(self
, canvas
): 
 163         for o 
in self
.objects
: 
 167         return len(self
.objects
) 
 169     def __getitem__(self
, item
): 
 170         return self
.objects
[item
] 
 173 class PlotCanvas(wx
.wxWindow
): 
 175     def __init__(self
, parent
, id = -1): 
 176         wx
.wxWindow
.__init
__(self
, parent
, id, wx
.wxPyDefaultPosition
, wx
.wxPyDefaultSize
) 
 178         self
.SetClientSizeWH(400,400) 
 179         self
.SetBackgroundColour(wx
.wxNamedColour("white")) 
 181         wx
.EVT_SIZE(self
,self
.reconfigure
) 
 182         wx
.EVT_PAINT(self
, self
.OnPaint
) 
 184         self
.last_draw 
= None 
 185 #       self.font = self._testFont(font) 
 187     def OnPaint(self
, event
): 
 188         pdc 
= wx
.wxPaintDC(self
) 
 189         if self
.last_draw 
is not None: 
 190             apply(self
.draw
, self
.last_draw 
+ (pdc
,)) 
 192     def reconfigure(self
, event
): 
 193         (new_width
,new_height
) = self
.GetClientSizeTuple() 
 194         if new_width 
== self
.width 
and new_height 
== self
.height
: 
 199     def _testFont(self
, font
): 
 201             bg 
= self
.canvas
.cget('background') 
 203                 item 
= CanvasText(self
.canvas
, 0, 0, anchor
=NW
, 
 204                                   text
='0', fill
=bg
, font
=font
) 
 205                 self
.canvas
.delete(item
) 
 211         (self
.width
,self
.height
) = self
.GetClientSizeTuple(); 
 212         self
.plotbox_size 
= 0.97*Numeric
.array([self
.width
, -self
.height
]) 
 213         xo 
= 0.5*(self
.width
-self
.plotbox_size
[0]) 
 214         yo 
= self
.height
-0.5*(self
.height
+self
.plotbox_size
[1]) 
 215         self
.plotbox_origin 
= Numeric
.array([xo
, yo
]) 
 217     def draw(self
, graphics
, xaxis 
= None, yaxis 
= None, dc 
= None): 
 218         if dc 
== None: dc 
= wx
.wxClientDC(self
) 
 221         self
.last_draw 
= (graphics
, xaxis
, yaxis
) 
 222         p1
, p2 
= graphics
.boundingBox() 
 223         xaxis 
= self
._axisInterval
(xaxis
, p1
[0], p2
[0]) 
 224         yaxis 
= self
._axisInterval
(yaxis
, p1
[1], p2
[1]) 
 225         text_width 
= [0., 0.] 
 226         text_height 
= [0., 0.] 
 227         if xaxis 
is not None: 
 230             xticks 
= self
._ticks
(xaxis
[0], xaxis
[1]) 
 231             bb 
= dc
.GetTextExtent(xticks
[0][1]) 
 232             text_height
[1] = bb
[1] 
 233             text_width
[0] = 0.5*bb
[0] 
 234             bb 
= dc
.GetTextExtent(xticks
[-1][1]) 
 235             text_width
[1] = 0.5*bb
[0] 
 238         if yaxis 
is not None: 
 241             yticks 
= self
._ticks
(yaxis
[0], yaxis
[1]) 
 243                 bb 
= dc
.GetTextExtent(y
[1]) 
 244                 text_width
[0] = max(text_width
[0],bb
[0]) 
 247             text_height
[1] = max(text_height
[1], h
) 
 250         text1 
= Numeric
.array([text_width
[0], -text_height
[1]]) 
 251         text2 
= Numeric
.array([text_width
[1], -text_height
[0]]) 
 252         scale 
= (self
.plotbox_size
-text1
-text2
) / (p2
-p1
) 
 253         shift 
= -p1
*scale 
+ self
.plotbox_origin 
+ text1
 
 254         self
._drawAxes
(dc
, xaxis
, yaxis
, p1
, p2
, 
 255                        scale
, shift
, xticks
, yticks
) 
 256         graphics
.scaleAndShift(scale
, shift
) 
 260     def _axisInterval(self
, spec
, lower
, upper
): 
 263         if spec 
== 'minimal': 
 265                 return lower
-0.5, upper
+0.5 
 268         if spec 
== 'automatic': 
 271                 return lower
-0.5, upper
+0.5 
 272             log 
= Numeric
.log10(range) 
 273             power 
= Numeric
.floor(log
) 
 278             lower 
= lower 
- lower 
% grid
 
 281                 upper 
= upper 
- mod 
+ grid
 
 283         if type(spec
) == type(()): 
 289         raise ValueError, str(spec
) + ': illegal axis specification' 
 291     def _drawAxes(self
, dc
, xaxis
, yaxis
, 
 292                   bb1
, bb2
, scale
, shift
, xticks
, yticks
): 
 293         dc
.SetPen(wx
.wxPen(wx
.wxNamedColour('BLACK'),1)) 
 294         if xaxis 
is not None: 
 297             for y
, d 
in [(bb1
[1], -3), (bb2
[1], 3)]: 
 298                 p1 
= scale
*Numeric
.array([lower
, y
])+shift
 
 299                 p2 
= scale
*Numeric
.array([upper
, y
])+shift
 
 300                 dc
.DrawLine(p1
[0],p1
[1],p2
[0],p2
[1]) 
 301                 for x
, label 
in xticks
: 
 302                     p 
= scale
*Numeric
.array([x
, y
])+shift
 
 303                     dc
.DrawLine(p
[0],p
[1],p
[0],p
[1]+d
) 
 305                         dc
.DrawText(label
,p
[0],p
[1]) 
 308         if yaxis 
is not None: 
 311             h 
= dc
.GetCharHeight() 
 312             for x
, d 
in [(bb1
[0], -3), (bb2
[0], 3)]: 
 313                 p1 
= scale
*Numeric
.array([x
, lower
])+shift
 
 314                 p2 
= scale
*Numeric
.array([x
, upper
])+shift
 
 315                 dc
.DrawLine(p1
[0],p1
[1],p2
[0],p2
[1]) 
 316                 for y
, label 
in yticks
: 
 317                     p 
= scale
*Numeric
.array([x
, y
])+shift
 
 318                     dc
.DrawLine(p
[0],p
[1],p
[0]-d
,p
[1]) 
 320                         dc
.DrawText(label
,p
[0]-dc
.GetTextExtent(label
)[0], 
 324     def _ticks(self
, lower
, upper
): 
 325         ideal 
= (upper
-lower
)/7. 
 326         log 
= Numeric
.log10(ideal
) 
 327         power 
= Numeric
.floor(log
) 
 331         for f
, lf 
in self
._multiples
: 
 332             e 
= Numeric
.fabs(fraction
-lf
) 
 336         grid 
= factor 
* 10.**power
 
 337         if power 
> 3 or power 
< -3: 
 340             digits 
= max(1, int(power
)) 
 341             format 
= '%' + `digits`
+'.0f' 
 344             format 
= '%'+`digits
+2`
+'.'+`digits`
+'f' 
 346         t 
= -grid
*Numeric
.floor(-lower
/grid
) 
 348             ticks
.append( (t
, format 
% (t
,)) ) 
 352     _multiples 
= [(2., Numeric
.log10(2.)), (5., Numeric
.log10(5.))] 
 354     def redraw(self
,dc
=None): 
 355         if self
.last_draw 
is not None: 
 356             apply(self
.draw
, self
.last_draw 
+ (dc
,)) 
 359         self
.canvas
.delete('all') 
 361 #--------------------------------------------------------------------------- 
 362 # if running standalone... 
 364 #     ...a sample implementation using the above 
 368 if __name__ 
== '__main__': 
 370         # 100 points sin function, plotted as green circles 
 371         data1 
= 2.*Numeric
.pi
*Numeric
.arange(200)/200. 
 372         data1
.shape 
= (100, 2) 
 373         data1
[:,1] = Numeric
.sin(data1
[:,0]) 
 374         markers1 
= PolyMarker(data1
, color
='green', marker
='circle',size
=1) 
 376         # 50 points cos function, plotted as red line 
 377         data1 
= 2.*Numeric
.pi
*Numeric
.arange(100)/100. 
 379         data1
[:,1] = Numeric
.cos(data1
[:,0]) 
 380         lines 
= PolyLine(data1
, color
='red') 
 382         # A few more points... 
 384         markers2 
= PolyMarker([(0., 0.), (pi
/4., 1.), (pi
/2, 0.), 
 385                               (3.*pi
/4., -1)], color
='blue', 
 386                               fillcolor
='green', marker
='cross') 
 388         return PlotGraphics([markers1
, lines
, markers2
]) 
 391     class AppFrame(wx
.wxFrame
): 
 392         def __init__(self
, parent
, id, title
): 
 393             wx
.wxFrame
.__init
__(self
, parent
, id, title
, 
 394                                 wx
.wxPyDefaultPosition
, wx
.wxSize(400, 400)) 
 396             # Now Create the menu bar and items 
 397             self
.mainmenu 
= wx
.wxMenuBar() 
 400             menu
.Append(200, '&Print...', 'Print the current plot') 
 401             wx
.EVT_MENU(self
, 200, self
.OnFilePrint
) 
 402             menu
.Append(209, 'E&xit', 'Enough of this already!') 
 403             wx
.EVT_MENU(self
, 209, self
.OnFileExit
) 
 404             self
.mainmenu
.Append(menu
, '&File') 
 407             menu
.Append(210, '&Draw', 'Draw plots') 
 408             wx
.EVT_MENU(self
,210,self
.OnPlotDraw
) 
 409             menu
.Append(211, '&Redraw', 'Redraw plots') 
 410             wx
.EVT_MENU(self
,211,self
.OnPlotRedraw
) 
 411             menu
.Append(212, '&Clear', 'Clear canvas') 
 412             wx
.EVT_MENU(self
,212,self
.OnPlotClear
) 
 413             self
.mainmenu
.Append(menu
, '&Plot') 
 416             menu
.Append(220, '&About', 'About this thing...') 
 417             wx
.EVT_MENU(self
, 220, self
.OnHelpAbout
) 
 418             self
.mainmenu
.Append(menu
, '&Help') 
 420             self
.SetMenuBar(self
.mainmenu
) 
 422             # A status bar to tell people what's happening 
 423             self
.CreateStatusBar(1) 
 425             self
.client 
= PlotCanvas(self
) 
 427         def OnFilePrint(self
, event
): 
 428             d 
= wx
.wxMessageDialog(self
, 
 429 """As of this writing, printing support in wxPython is shaky at best. 
 430 Are you sure you want to do this?""", "Danger!", wx
.wxYES_NO
) 
 431             if d
.ShowModal() == wx
.wxID_YES
: 
 432                 psdc 
= wx
.wxPostScriptDC("out.ps", wx
.TRUE
, self
) 
 433                 self
.client
.redraw(psdc
) 
 435         def OnFileExit(self
, event
): 
 438         def OnPlotDraw(self
, event
): 
 439             self
.client
.draw(_InitObjects(),'automatic','automatic'); 
 441         def OnPlotRedraw(self
,event
): 
 444         def OnPlotClear(self
,event
): 
 445             self
.client
.last_draw 
= None 
 446             dc 
= wx
.wxClientDC(self
.client
) 
 449         def OnHelpAbout(self
, event
): 
 450             about 
= wx
.wxMessageDialog(self
, __doc__
, "About...", wx
.wxOK
) 
 455     class MyApp(wx
.wxApp
): 
 457             frame 
= AppFrame(wx
.NULL
, -1, "wxPlotCanvas") 
 459             self
.SetTopWindow(frame
) 
 469 #----------------------------------------------------------------------------