Package SimPy :: Module SimPlot |
[hide private]
[frames] | no frames]

# Source Code for Module SimPy.SimPlot

      1  #!/usr / bin / env python
      2  # $Revision: 229 $ $Date: 2009-01-26 16:17:21 +0100 (Mo, 26 Jan 2009) $ kgm
      3  """ SimPlot 2.0  Provides basic plotting services based on Tk / Tkinter.
      4
      5  LICENSE:
      6  Copyright (C) 2002, 2005, 2006, 2007, 2008  Klaus G. Muller, Tony Vignaux
      7  mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz
      8
      9      This library is free software; you can redistribute it and / or
     10      modify it under the terms of the GNU Lesser General Public
     11      License as published by the Free Software Foundation; either
     12      version 2.1 of the License, or (at your option) any later version.
     13
     14      This library is distributed in the hope that it will be useful,
     15      but WITHOUT ANY WARRANTY; without even the implied warranty of
     16      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
     17      Lesser General Public License for more details.
     18
     19      You should have received a copy of the GNU Lesser General Public
     20      License along with this library; if not, write to the Free Software
     21      Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111 - 1307  USA
     22  END OF LICENSE
     23
     24  Derived from plotting package in Grayson's Tkinter book.
     25  The idea to use this package came from Prof. Simon Frost
     26  of U of California, San Diego who also strongly contributed
     27  to the design and implementation of SimPlot.
     28
     29
     30  Change history:
     31
     32      Nov 2, 2003 :  Combined utils.py (also from Grayson) with plotting package.
     33      Nov 11, 2003: Made totally OO
     34      Dec 16, 2003: Completion of SimPlot 1.4alpha
     35      Feb 2004:       Release with SimPy 1.4
     36      Aug 27, 2005: Added tests for empty point sets to plotXXX functions.
     37      Sep 15, 2008: Adjusted to SimPy 2.0 changes
     38
     39  """
     40  __version__ = '2.0 $Revision: 229 $ $Date: 2009-01-26 16:17:21 +0100 (Mo, 26 Jan 2009) $'
     41  from Tkinter import *
     42  from Canvas import Line, CanvasText, Rectangle
     43  from tkMessageBox import *
     44  from tkSimpleDialog import askinteger, askstring, askfloat
     45  from tkFileDialog import *
     46  import string, math
     47  from math import pi
     48  from SimPy.Simulation import Monitor
     49
     50 -def minCoordinate(clist):
     51      if len(clist) < 2: return clist[0]
     52      try:
     53          x,  y  = clist[0]
     54          for x1, y1 in clist[1:]:
     55              if x1 <= x or y1 <= y:
     56                  x, y = x1, y1
     57      except:
     58          x, y = 0, 0
     59
     60      return x, y
     61
     62 -def maxCoordinate(clist):
     63      if len(clist) < 2: return clist[0]
     64      try:
     65          x,  y  = clist[0]
     66          for x1, y1 in clist[1:]:
     67              if x1 >= x or y1 >= y:
     68                  x, y = x1, y1
     69      except:
     70          x, y = 0, 0
     71
     72      return x, y
     73
     74 -def minBound(clist):
     75      x = 10000000
     76      y = 10000000
     77      for x1, y1 in clist:
     78          if x1 < x: x = x1
     79          if y1 < y: y = y1
     80      return x, y
     81
     82 -def maxBound(clist):
     83      x = -10000000
     84      y = -10000000
     85      for x1, y1 in clist:
     86          if x1 > x: x = x1
     87          if y1 > y: y = y1
     88      return x, y
     89
     90 -class SimPlot(object):
     91 -    def __init__(self, root = Tk()):
     92          self.root = root
     93          pass
     94
     95 -    def mainloop(self):
     96          self.root.mainloop()
     97
     98 -    def makeLine(self, points,**attr):
     99          return GraphLine(points, **attr)
    100 -    def makeStep(self, points, **attr):
    101          #convert data list to steps
    102          step0 = points[:]
    103          step1 = [[0, 0]] * 2*len(step0)
    104          prev = [step0[0][0],0]
    105          for x in range(len(step0)):
    106              step1[2 * x] = [step0[x][0],prev[1]]
    107              step1[2 * x + 1] = step0[x]
    108              prev = step0[x]
    109          #draw the line
    110          return self.makeLine(step1, smooth = False, **attr)
    111
    112 -    def makeHistogram(self, points,**attr):
    113          """Makes a histogram graph. 'points' must be a Histogram - like
    114          object.
    115          """
    116          #convert data list to bars
    117          step0 = points[:]
    118          step1 = [[0, 0]] * 3*len(step0)
    119          prev = [step0[0][0],0]
    120          for x in range(len(step0)):
    121              step1[3 * x] = [step0[x][0],prev[1]]
    122              step1[3 * x + 1] = [step0[x][0],0.0]
    123              step1[3 * x + 2] = step0[x]
    124              prev = step0[x]
    125          deltax = step0[1][0] - step0[0][0]
    126          step1.append([prev[0] + deltax, prev[1]])
    127          step1.append([prev[0] + deltax, 0])
    128          #make the line
    129          return self.makeLine(step1, smooth = False,
    130                               xaxis = (step1[0][0],step1[-1][0]),
    131                               **attr)
    132
    133 -    def makeSymbols(self, points,**attr):
    134          return GraphSymbols(points,**attr)
    135 -    def makeBars(self, points,**attr):
    136          return GraphBars(points,**attr)
    137 -    def makeGraphObjects(self, objects):
    138          return GraphObjects(objects)
    139 -    def makeGraphBase(self, master, width, height,
    140                   background = 'white', title = '', xtitle = '', ytitle = '', **kw):
    141          return GraphBase(master, width, height,
    142                   background, title, xtitle, ytitle,**kw)
    143
    144 -    def graphMenu(self, root, graph):
    145          """To provide a File menu (postscript output, more to come)
    146          to the plotxxxx plots"""
    147          mainMenu = Menu(root)
    148          root.config(menu = mainMenu)
    149          def postscriptout():
    150              graph.postscr()
    151          file = Menu(mainMenu)
    152          file.add_command(label = 'Postscript', command = postscriptout)
    153          mainMenu.add_cascade(label = 'File', menu = file, underline = 0)
    154
    155 -    def plotLine(self, points, windowsize = (500, 300),title = '', width = 1, color = 'black',
    156                   smooth = 0, background = 'white', xlab = 'x', ylab = 'y',
    157                   xaxis = 'automatic', yaxis = 'automatic'):
    158          """Generates a line chart, with menu to save as Postscript file.
    159          'points' can be a Monitor instance.
    160          """
    161          if points != []:
    162              root = Toplevel()
    163              f = Frame(root)
    164              try: #if it is like a Monitor, take xlab, ylab from it
    165                  ylab = points.ylab
    166                  xlab = points.tlab
    167                  if not title: title = points.name
    168              except:
    169                  pass
    170              line = self.makeLine(points, width = width, color = color, smooth = smooth)
    171              gr = self.makeGraphObjects([line])
    172              graph = self.makeGraphBase(f, windowsize[0], windowsize[1],
    173                                     title = title, xtitle = xlab,
    174                                     ytitle = ylab, background = background)
    175              graph.pack(side = LEFT, fill = BOTH, expand = YES)
    176              graph.draw(gr, xaxis = xaxis, yaxis = yaxis)
    177              #File menu
    178              self.graphMenu(root, graph)
    179              f.pack()
    180              return graph
    181          else:
    182              print 'SimPlot.plotline: dataset empty, no plot.'
    183              return None
    184
    185 -    def plotStep(self, points, windowsize = (500, 300),title = '', width = 1, color = 'black',
    186                   background = 'white', xlab = 'x', ylab = 'y',
    187                   xaxis = 'automatic', yaxis = 'automatic'):
    188          """Generates a step chart, with menu to save as Postscript file.
    189          'points' can be a Monitor instance.
    190          """
    191          if points != []:
    192              #convert data list to steps
    193              step0 = points[:]
    194              step1 = [[0, 0]] * 2*len(step0)
    195              prev = [step0[0][0],0]
    196              for x in range(len(step0)):
    197                  step1[2 * x] = [step0[x][0],prev[1]]
    198                  step1[2 * x + 1] = step0[x]
    199                  prev = step0[x]
    200              #treat monitor case
    201              try: #if it is like a Monitor, take xlab, ylab from it
    202                  ylab = points.ylab
    203                  xlab = points.tlab
    204                  if not title: title = points.name
    205              except:
    206                  pass
    207              #draw the line
    208              smooth = False
    209              return self.plotLine(step1, windowsize, title, width, color,
    210                   smooth, background, xlab, ylab,
    211                   xaxis, yaxis)
    212          else:
    213              print 'SimPlot.plotStep: dataset empty, no plot.'
    214              return None
    215
    216 -    def plotHistogram(self, points, windowsize = (500, 300),title = '', width = 1, color = 'black',
    217                   background = 'white', xlab = 'x', ylab = 'y',
    218                   xaxis = 'automatic', yaxis = 'automatic'):
    219          """Makes a histogram plot. 'points' can be a Monitor  instance.
    220          """
    221          if points != []:
    222              #convert data list to bars
    223              step0 = points[:]
    224              step1 = [[0, 0]] * 3*len(step0)
    225              prev = [step0[0][0],0]
    226              for x in range(len(step0)):
    227                  step1[3 * x] = [step0[x][0],prev[1]]
    228                  step1[3 * x + 1] = [step0[x][0],0.0]
    229                  step1[3 * x + 2] = step0[x]
    230                  prev = step0[x]
    231              deltax = step0[1][0] - step0[0][0]
    232              step1.append([prev[0] + deltax, prev[1]])
    233              step1.append([prev[0] + deltax, 0])
    234              #treat monitor case
    235              try: #if it is like a Monitor, take xlab, ylab from it
    236                  ylab = points.ylab
    237                  xlab = points.tlab
    238                  if not title: title = points.name
    239              except:
    240                  pass
    241              #draw the line
    242              smooth = False
    243              return self.plotLine(step1, windowsize = windowsize, title = title, width = width,
    244                               color = color, smooth = smooth, background = background,
    245                               xlab = xlab, ylab = ylab, xaxis = (step1[0][0],step1[-1][0]),
    246                               yaxis = yaxis)
    247          else:
    248              print 'SimPlot.plotHistogram: dataset empty, no plot.'
    249              return None
    250
    251 -    def plotBars(self, points, windowsize = (500, 300),title = '', color = 'black',
    252                   width = 1, size = 3, fillcolor = 'black', fillstyle = '',
    253                   outline = 'black', background = 'white', xlab = 'x', ylab = 'y',
    254                   xaxis = 'automatic', yaxis = 'automatic', anchor = 0.0):
    255          """Generates a bar chart, with menu to save as Postscript file.
    256          'points' can be a Monitor instance.
    257          """
    258          if points != []:
    259              root = Toplevel()
    260              f = Frame(root)
    261              try: #if it is like a Monitor, take xlab, ylab from it
    262                  ylab = points.ylab
    263                  xlab = points.tlab
    264                  if not title: title = points.name
    265              except:
    266                  pass
    267              bars = self.makeBars(points, width = width, size = size, color = color,
    268                             fillcolor = fillcolor, fillstyle = fillstyle,
    269                             outline = outline, anchor = anchor)
    270              gr = self.makeGraphObjects([bars])
    271              graph = self.makeGraphBase(f, windowsize[0],windowsize[1],
    272                                     title = title, xtitle = xlab,
    273                                     ytitle = ylab, background = background)
    274              graph.pack(side = LEFT, fill = BOTH, expand = YES)
    275              graph.draw(gr, xaxis = xaxis, yaxis = yaxis)
    276              #File menu
    277              self.graphMenu(root, graph)
    278              f.pack()
    279              return graph
    280          else:
    281              print 'SimPlot.plotBars dataset empty, no plot.'
    282              return None
    283
    284 -    def plotScatter(self, points, windowsize = (500, 300),title = '', width = 1, color = 'black',
    285                      fillcolor = 'black', size = 2, fillstyle = '',
    286                      outline = 'black', marker = 'circle',
    287                      background = 'white', xlab = 'x', ylab = 'y',
    288                      xaxis = 'automatic', yaxis = 'automatic'):
    289          if points != []:
    290              root = Toplevel()
    291              f = Frame(root)
    292              try: #if it is like a Monitor, take xlab, ylab from it
    293                  ylab = points.ylab
    294                  xlab = points.tlab
    295                  if not title: title = points.name
    296              except:
    297                  pass
    298              scat = self.makeSymbols(points, width = width, color = color, size = size,
    299                                marker = marker, fillcolor = fillcolor,
    300                                fillstyle = fillstyle, outline = outline)
    301              gr = self.makeGraphObjects([scat])
    302              graph = self.makeGraphBase(f, windowsize[0],windowsize[1],
    303                                     title = title, xtitle = xlab,
    304                                     ytitle = ylab, background = background)
    305              graph.pack(side = LEFT, fill = BOTH, expand = YES)
    306              graph.draw(gr, xaxis = xaxis, yaxis = yaxis)
    307              #File menu
    308              self.graphMenu(root, graph)
    309              f.pack()
    310              return graph
    311          else:
    312              print 'SimPlot.plotScatter: dataset empty, no plot.'
    313              return None
    314
    315 -    def mainloop(self):
    316          self.root.mainloop()
    317
    318 -class GraphPoints:
    319 -    def __init__(self, points, attr):
    320          self.points = points
    321          self.scaled = self.points
    322          self.attributes = {}
    323          for name, value in self._attributes.items():
    324              try:
    325                  value = attr[name]
    326              except KeyError: pass
    327              self.attributes[name] = value
    328
    329 -    def boundingBox(self):
    330          return minBound(self.points),  maxBound(self.points)
    331
    332 -    def fitToScale(self, scale = (1, 1), shift = (0, 0)):
    333          self.scaled = []
    334          for x, y in self.points:
    335              self.scaled.append(((scale[0] * x) + shift[0],\
    336                                 (scale[1] * y) + shift[1]))
    337              self.attributes.get('anchor', 0.0)
    338          self.anchor = scale[1] * self.attributes.get('anchor', 0.0)+\
    339                        shift[1]
    340
    341 -class GraphLine(GraphPoints):
    342 -    def __init__(self, points, **attr):
    343          GraphPoints.__init__(self, points, attr)
    344
    345      _attributes = {'color':       'black',
    346                     'width':        1,
    347                     'smooth':       0,
    348                     'splinesteps': 12}
    349
    350 -    def draw(self, canvas):
    351          color  = self.attributes['color']
    352          width  = self.attributes['width']
    353          smooth = self.attributes['smooth']
    354          steps  = self.attributes['splinesteps']
    355          arguments = (canvas,)
    356          if smooth:
    357              for i in range(len(self.points)):
    358                  x1, y1 = self.scaled[i]
    359                  arguments = arguments + (x1, y1)
    360          else:
    361              for i in range(len(self.points) - 1):
    362                  x1, y1 = self.scaled[i]
    363                  x2, y2 = self.scaled[i + 1]
    364                  arguments = arguments + (x1, y1, x2, y2)
    365          apply(Line, arguments, {'fill': color, 'width': width,
    366                                  'smooth': smooth, 'splinesteps':steps})
    367
    368 -class GraphSymbols(GraphPoints):
    369 -    def __init__(self, points, **attr):
    370          GraphPoints.__init__(self, points, attr)
    371
    372      _attributes = {'color': 'black',
    373                     'width': 1,
    374                     'fillcolor': 'black',
    375                     'size': 2,
    376                     'fillstyle': '',
    377                     'outline': 'black',
    378                     'marker': 'circle'}
    379
    380 -    def draw(self, canvas):
    381          color     = self.attributes['color']
    382          size      = self.attributes['size']
    383          fillcolor = self.attributes['fillcolor']
    384          marker    = self.attributes['marker']
    385          fillstyle = self.attributes['fillstyle']
    386
    387          self._drawmarkers(canvas, self.scaled, marker, color,
    388                            fillstyle, fillcolor, size)
    389
    390 -    def _drawmarkers(self, c, coords, marker = 'circle', color = 'black',
    391                       fillstyle = '', fillcolor = '', size = 2):
    392          l = []
    393          f = eval('self._' + marker)
    394          for xc, yc in coords:
    395              id = f(c, xc, yc, outline = color, size = size,
    396                     fill = fillcolor, fillstyle = fillstyle)
    397              if type(id) is type(()):
    398                  for item in id: l.append(item)
    399              else:
    400                  l.append(id)
    401          return l
    402
    403 -    def _circle(self, c, xc, yc, size = 1, fill = '', outline = 'black',
    404                  fillstyle = ''):
    405          id = c.create_oval(xc - 0.5, yc - 0.5, xc + 0.5, yc + 0.5,
    406                             fill = fill, outline = outline,
    407                             stipple = fillstyle)
    408          c.scale(id, xc, yc, size * 5, size * 5)
    409          return id
    410
    411 -    def _dot(self, c, xc, yc, size = 1, fill = '', outline = 'black',
    412               fillstyle = ''):
    413          id = c.create_oval(xc - 0.5, yc - 0.5, xc + 0.5, yc + 0.5,
    414                             fill = fill, outline = outline,
    415                             stipple = fillstyle)
    416          c.scale(id, xc, yc, size * 2.5, size * 2.5)
    417          return id
    418
    419 -    def _square(self, c, xc, yc, size = 1, fill = '', outline = 'black',
    420                  fillstyle = ''):
    421          id = c.create_rectangle(xc - 0.5, yc - 0.5, xc + 0.5, yc + 0.5,
    422                                  fill = fill, outline = outline,
    423                                  stipple = fillstyle)
    424          c.scale(id, xc, yc, size * 5, size * 5)
    425          return id
    426
    427 -    def _triangle(self, c, xc, yc, size = 1, fill = '', outline = 'black',
    428                    fillstyle = ''):
    429          id = c.create_polygon(-0.5, 0.288675134595,
    430                                0.5, 0.288675134595,
    431                                0.0, -0.577350269189, fill = fill,
    432                                outline = outline, stipple = fillstyle)
    433          c.move(id, xc, yc)
    434          c.scale(id, xc, yc, size * 5, size * 5)
    435          return id
    436
    437 -    def _triangle_down(self, c, xc, yc, size = 1, fill = '',
    438                         outline = 'black', fillstyle = ''):
    439          id = c.create_polygon(-0.5, -0.288675134595,
    440                                0.5, -0.288675134595,
    441                                0.0, 0.577350269189, fill = fill,
    442                                outline = outline, stipple = fillstyle)
    443          c.move(id, xc, yc)
    444          c.scale(id, xc, yc, size * 5, size * 5)
    445          return id
    446
    447 -    def _cross(self, c, xc, yc, size = 1, fill = 'black', outline = None,
    448                 fillstyle = ''):
    449          if outline: fill = outline
    450          id1 = c.create_line(xc - 0.5, yc - 0.5, xc + 0.5, yc + 0.5,
    451                              fill = fill)
    452          id2 = c.create_line(xc - 0.5, yc + 0.5, xc + 0.5, yc - 0.5,
    453                              fill = fill)
    454          c.scale(id1, xc, yc, size * 5, size * 5)
    455          c.scale(id2, xc, yc, size * 5, size * 5)
    456          return id1, id2
    457
    458 -    def _plus(self, c, xc, yc, size = 1, fill = 'black', outline = None,
    459                fillstyle = ''):
    460          if outline: fill = outline
    461          id1 = c.create_line(xc - 0.5, yc, xc + 0.5, yc, fill = fill)
    462          id2 = c.create_line(xc, yc + 0.5, xc, yc - 0.5, fill = fill)
    463          c.scale(id1, xc, yc, size * 5, size * 5)
    464          c.scale(id2, xc, yc, size * 5, size * 5)
    465          return id1, id2
    466
    467 -class GraphBars(GraphPoints):
    468 -    def __init__(self, points, **attr):
    469          GraphPoints.__init__(self, points, attr)
    470
    471      _attributes = {'color': 'black',
    472                     'width': 1,
    473                     'fillcolor': 'black',
    474                     'size': 3,
    475                     'fillstyle': '',
    476                     'outline': 'black'}
    477
    478 -    def draw(self, canvas):
    479          color     = self.attributes['color']
    480          width     = self.attributes['width']
    481          fillstyle = self.attributes['fillstyle']
    482          outline   = self.attributes['outline']
    483          spread    = self.attributes['size']
    484          arguments = (canvas,)
    485          p1, p2    = self.boundingBox()
    486          for i in range(len(self.points)):
    487              x1, y1 = self.scaled[i]
    488              canvas.create_rectangle(x1 - spread, y1, x1 + spread,
    489                                      self.anchor, fill = color,
    490                                      width = width, outline = outline,
    491                                      stipple = fillstyle)
    492
    493 -class GraphObjects:
    494 -    def __init__(self, objects):
    495          self.objects = objects
    496
    497 -    def boundingBox(self):
    498          c1, c2 = self.objects[0].boundingBox()
    499          for object in self.objects[1:]:
    500              c1o, c2o = object.boundingBox()
    501              c1 = minBound([c1, c1o])
    502
    503              c2 = maxBound([c2, c2o])
    504          return c1, c2
    505
    506 -    def fitToScale(self, scale = (1, 1), shift = (0, 0)):
    507          for object in self.objects:
    508              object.fitToScale(scale, shift)
    509
    510 -    def draw(self, canvas):
    511          for object in self.objects:
    512              object.draw(canvas)
    513
    514 -class GraphBase(Frame):
    515 -    def __init__(self, master, width, height,
    516                   background = 'white', title = '', xtitle = '', ytitle = '', **kw):
    517          apply(Frame.__init__, (self, master), kw)
    518          self.title = title
    519          self.xtitle = xtitle
    520          self.ytitle = ytitle
    521          self.canvas = Canvas(self, width = width, height = height,
    522                               background = background)
    523          self.canvas.pack(fill = BOTH, expand = YES)
    524          border_w = self.canvas.winfo_reqwidth() - \
    525                     string.atoi(self.canvas.cget('width'))
    526          border_h = self.canvas.winfo_reqheight() - \
    527                     string.atoi(self.canvas.cget('height'))
    528          self.border = (border_w, border_h)
    529          self.canvas.bind('<Configure>', self.configure)
    530          self.plotarea_size = [None, None]
    531          self._setsize()
    532          self.last_drawn = None
    533          self.font = ('Verdana', 10)
    534
    535 -    def configure(self, event):
    536          new_width = event.width - self.border[0]
    537          new_height = event.height - self.border[1]
    538          width = string.atoi(self.canvas.cget('width'))
    539          height = string.atoi(self.canvas.cget('height'))
    540          if new_width == width and new_height == height:
    541              return
    542          self.canvas.configure(width = new_width, height = new_height)
    543          self._setsize()
    544          self.clear()
    545          self.replot()
    546
    547 -    def bind(self, *args):
    548          apply(self.canvas.bind, args)
    549
    550 -    def _setsize(self):
    551          self.width = string.atoi(self.canvas.cget('width'))
    552          self.height = string.atoi(self.canvas.cget('height'))
    553          #self.plotarea_size[0] = 0.90 * self.width
    554          #self.plotarea_size[1] = 0.90 * -self.height
    555          self.plotarea_size[0] = 0.90 * self.width
    556          self.plotarea_size[1] = 0.90 * -self.height
    557          xo = 0.5 * (self.width - self.plotarea_size[0])
    558          yo = self.height - 0.5 * (self.height + self.plotarea_size[1])
    559          self.plotarea_origin = (xo, yo)
    560
    561 -    def draw(self, graphics, xaxis = 'automatic', yaxis = 'automatic'):
    562
    563          self.last_drawn = (graphics, xaxis, yaxis)
    564          p1, p2 = graphics.boundingBox()
    565          xaxis = self._axisInterval(xaxis, p1[0], p2[0])
    566          yaxis = self._axisInterval(yaxis, p1[1], p2[1])
    567          text_width = [0., 0.]
    568          text_height = [0., 0.]
    569          if xaxis is not None:
    570              p1 = xaxis[0], p1[1]
    571              p2 = xaxis[1], p2[1]
    572              xticks = self._ticks(xaxis[0], xaxis[1])
    573              bb = self._textBoundingBox(xticks[0][1])
    574              text_height[1] = bb[3] - bb[1]
    575              text_width[0] = 0.5 * (bb[2] - bb[0])
    576              bb = self._textBoundingBox(xticks[-1][1])
    577              text_width[1] = 0.5 * (bb[2] - bb[0])
    578          else:
    579              xticks = None
    580          if yaxis is not None:
    581              p1 = p1[0], yaxis[0]
    582              p2 = p2[0], yaxis[1]
    583              yticks = self._ticks(yaxis[0], yaxis[1])
    584              for y in yticks:
    585                  bb = self._textBoundingBox(y[1])
    586                  w = bb[2] - bb[0]
    587                  text_width[0] = max(text_width[0], w)
    588              h = 0.5 * (bb[3] - bb[1])
    589              text_height[0] = h
    590              text_height[1] = max(text_height[1], h)
    591          else:
    592              yticks = None
    593          text1 = [text_width[0], -text_height[1]]
    594          text2 = [text_width[1], -text_height[0]]
    595          scale = ((self.plotarea_size[0] - text1[0] - text2[0]) / \
    596                   (p2[0] - p1[0]),
    597                   (self.plotarea_size[1] - text1[1] - text2[1]) / \
    598                   (p2[1] - p1[1]))
    599          shift = ((-p1[0] * scale[0]) + self.plotarea_origin[0] + \
    600                   text1[0],
    601                   (-p1[1] * scale[1]) + self.plotarea_origin[1] + \
    602                   text1[1])
    603          self._drawAxes(self.canvas, xaxis, yaxis, p1, p2,
    604                          scale, shift, xticks, yticks)
    605          graphics.fitToScale(scale, shift)
    606          graphics.draw(self.canvas)
    607
    608 -    def _axisInterval(self, spec, lower, upper):
    609          if spec is None:
    610              return None
    611          if spec == 'minimal':
    612              if lower == upper:
    613                  return lower - 0.5, upper + 0.5
    614              else:
    615                  return lower, upper
    616          if spec == 'automatic':
    617              range = upper - lower
    618              if range == 0.:
    619                  return lower - 0.5, upper + 0.5
    620              log = math.log10(range)
    621              power = math.floor(log)
    622              fraction = log - power
    623              if fraction <= 0.05:
    624                  power = power - 1
    625              grid = 10.**power
    626              lower = lower - lower % grid
    627              mod = upper % grid
    628              if mod != 0:
    629                  upper = upper - mod + grid
    630              return lower, upper
    631          if type(spec) == type(()):
    632              lower, upper = spec
    633              if lower <= upper:
    634                  return lower, upper
    635              else:
    636                  return upper, lower
    637          raise ValueError, str(spec) + ': illegal axis specification'
    638
    639 -    def _drawAxes(self, canvas, xaxis, yaxis,
    640                    bb1, bb2, scale, shift, xticks, yticks):
    641          dict = {'anchor': N, 'fill': 'black'}
    642          if self.font is not None:
    643              dict['font'] = self.font
    644          if xaxis is not None:
    645          #draw x - axis
    646              lower, upper = xaxis
    647              text = 1
    648              once = 1
    649              for y, d in [(bb1[1], -3), (bb2[1], 3)]:
    650              #d=.5 of tick - length
    651                  p1 = (scale[0] * lower) + shift[0], (scale[1] * y) + shift[1]
    652                  if once: pp1 = p1
    653                  p2 = (scale[0] * upper) + shift[0], (scale[1] * y) + shift[1]
    654                  if once: pp2 = p2
    655                  once = 0
    656                  Line(self.canvas, p1[0], p1[1], p2[0], p2[1],
    657                       fill = 'black', width = 1)
    658                  if xticks:
    659                      for x, label in xticks:
    660                          p = (scale[0] * x) + shift[0], \
    661                              (scale[1] * y) + shift[1]
    662                          Line(self.canvas, p[0], p[1], p[0], p[1] + d,
    663                               fill = 'black', width = 1)
    664                          if text:
    665                              dict['text'] = label
    666                              apply(CanvasText, (self.canvas, p[0],
    667                                                 p[1] + 2), dict)    ##KGM 14 Aug 03
    668                  text = 0
    669              #write x - axis title
    670              CanvasText(self.canvas,(pp2[0] - pp1[0]) / 2.+pp1[0],pp1[1] + 22, text = self.xtitle)
    671          #write graph title
    672          CanvasText(self.canvas,(pp2[0] - pp1[0]) / 2.+pp1[0],7, text = self.title)
    673          dict['anchor'] = E
    674          if yaxis is not None:
    675              #draw y - axis
    676              lower, upper = yaxis
    677              text = 1
    678              once = 1
    679              for x, d in [(bb1[0], -3), (bb2[0], 3)]:
    680                  p1 = (scale[0] * x) + shift[0], (scale[1] * lower) + shift[1]
    681                  p2 = (scale[0] * x) + shift[0], (scale[1] * upper) + shift[1]
    682                  if once: pp1 = p1 ;pp2 = p2
    683                  once = 0
    684                  Line(self.canvas, p1[0], p1[1], p2[0], p2[1],
    685                       fill = 'black', width = 1)
    686                  if yticks:
    687                      for y, label in yticks:
    688                          p = (scale[0] * x) + shift[0], \
    689                              (scale[1] * y) + shift[1]
    690                          Line(self.canvas, p[0], p[1], p[0] - d, p[1],
    691                               fill = 'black', width = 1)
    692                          if text:
    693                              dict['text'] = label
    694                              apply(CanvasText,(self.canvas,
    695                                                p[0] - 4, p[1] + 2), dict)
    696                  text = 0
    697              #write y - axis title
    698              CanvasText(self.canvas, pp2[0],pp2[1] - 10, text = self.ytitle)
    699
    700 -    def _ticks(self, lower, upper):
    701          ideal = (upper - lower) / 7.
    702          log = math.log10(ideal)
    703          power = math.floor(log)
    704          fraction = log - power
    705          factor = 1.
    706          error = fraction
    707          for f, lf in self._multiples:
    708              e = math.fabs(fraction - lf)
    709              if e < error:
    710                  error = e
    711                  factor = f
    712          grid = factor * 10.**power
    713          if power > 3 or power < -3:
    714              format = '%+7.0e'
    715          elif power >= 0:
    716              digits = max(1, int(power))
    717              format = '%' + `digits`+'.0f'
    718          else:
    719              digits = -int(power)
    720              format = '%'+`digits + 2`+'.'+`digits`+'f'
    721          ticks = []
    722          t = -grid * math.floor(-lower / grid)
    723          while t <= upper and len(ticks) < 200:
    724              ticks.append((t, format % (t,)))
    725              t = t + grid
    726          return ticks
    727
    728      _multiples = [(2., math.log10(2.)), (5., math.log10(5.))]
    729
    730 -    def _textBoundingBox(self, text):
    731          bg = self.canvas.cget('background')
    732          dict = {'anchor': NW, 'text': text, 'fill': bg}
    733          if self.font is not None:
    734              dict['font'] = self.font
    735          item = apply(CanvasText, (self.canvas, 0., 0.), dict)
    736          bb = self.canvas.bbox(item)
    737          self.canvas.delete(item)
    738          return bb
    739
    740 -    def replot(self):
    741          if self.last_drawn is not None:
    742              apply(self.draw, self.last_drawn)
    743
    744 -    def clear(self):
    745          self.canvas.delete('all')
    746
    747 -    def postscr(self, filename = None):
    748          """Write to Postscript file given by 'filename'. If none provided,
    749          ask user.
    750          """
    751          from tkFileDialog import asksaveasfilename
    752          if not filename:
    753              filename = asksaveasfilename()
    754          if filename:
    755              if not filename[-3:] == '.ps':
    756                  filename += '.ps'
    757              self.canvas.postscript(width = self.width, height = self.height, file = filename)
    758
    759 -class TextBox(Frame):
    760 -    def __init__(self, master, width, height,
    761                   background = 'white', boxtext = '', **kw):
    762          apply(Frame.__init__, (self, master), kw)
    763          self.width = width
    764          self.height = height
    765          self.canvas = Canvas(self, width = width, height = height,
    766                             background = background)
    767          self.canvas.pack(fill = BOTH, expand = YES)
    768          #CanvasText(self.canvas, text = boxtext)
    769
    770 -    def postscr(self):
    771          #select output file
    772          #from tkFileDialog import asksaveasfilename
    773          filename = asksaveasfilename()
    774          if filename:
    775              if not filename[-3:] == '.ps':
    776                  filename += '.ps'
    777              self.canvas.postscript(width = self.width, height = self.height, file = filename)
    778
    779  if __name__ == '__main__':
    780      print 'SimPlot.py %s'%__version__
    781      root = Tk()
    782      plt = SimPlot()
    783      root.title('SimPlot example - First frame')
    784
    785      root1 = Tk()
    786      root1.title('SimPlot example - Second frame')
    787
    788      """PARAMETER DEFAULTS:
    789      GraphBase
    790      ---------
    791      background = 'white',
    792      title = '',
    793      xtitle = '',
    794      ytitle = ''
    795
    796      GraphBase.draw
    797      --------------
    798      xaxis = 'automatic',
    799      yaxis = 'automatic')
    800
    801      GraphLine
    802      ---------
    803      color:       'black',
    804      width:        1,
    805      smooth:       0,
    806      splinesteps: 12
    807
    808      GraphSymbols:
    809      -------------
    810      color: 'black',
    811      width: 1,
    812      fillcolor: 'black',
    813      size: 2,
    814      fillstyle: '',
    815      outline: 'black',
    816      marker: 'circle'}
    817
    818      GraphBars
    819      ---------
    820      color: 'black',
    821      width: 1,
    822      fillcolor: 'black',
    823      size: 3,
    824      fillstyle: '',
    825      outline: 'black'
    826      """
    827      # Plot 1 -- smooth line + filled bars
    828      di = 5.0 * pi / 40.
    829      data = []
    830      for i in range(40):
    831          data.append((float(i) * di,
    832                       (math.sin(float(i) * di) - math.cos(float(i) * di))))
    833      line1  = plt.makeLine(data, color = 'black', width = 1,
    834                        smooth = 1)
    835      line1a = plt.makeBars(data[1:], color = 'blue', fillstyle = 'gray25',
    836                        anchor = 0.0)
    837
    838
    839      graphObject = plt.makeGraphObjects([line1a, line1])
    840      #Second panel -- Narrow bars
    841      line2 = plt.makeBars([(0, 0),(1, 145),(2,-90),(3, 147),(4, 22),(5, 31),
    842                          (6, 77),(7, 125),(8, 220),(9, 550),(10, 560),(11, 0)],
    843                         outline = 'green', color = 'red', size = 7)
    844
    845
    846      graphObject2 = plt.makeGraphObjects([line2])
    847
    848      # Third plot -- Smooth line and unsmoothed line
    849      line3 = plt.makeLine([(1, 145 + 100),(2, 151 + 100),(3, 147 + 100),(4, 22 + 100),(5, 31 + 100),
    850                          (6, 77 + 100),(7, 125 + 100),(8, 220 + 100),(9, 550 + 100),(10, 560 + 100)],
    851                         color = 'blue', width = 2, smooth = 1)
    852      line3a = plt.makeLine([(1, 145),(2, 151),(3, 147),(4, 22),(5, 31),
    853                          (6, 77),(7, 125),(8, 220),(9, 550),(10, 560)],
    854                         color = 'green', width = 2, smooth = 0)
    855      line3b = plt.makeStep([(1, 145 + 100),(2, 151 + 100),(3, 147 + 100),(4, 22 + 100),(5, 31 + 100),
    856                          (6, 77 + 100),(7, 125 + 100),(8, 220 + 100),(9, 550 + 100),(10, 560 + 100)],
    857                         color = 'red', width = 2)
    858
    859      graphObject3 = plt.makeGraphObjects([line3, line3a, line3b])
    860
    861      # Fourth plot -- lines with all available symbols with different
    862      #                outline colors / fill colors / sizes
    863
    864      line4 = plt.makeSymbols([(1, 100),(2, 100),(3, 100),(4, 100),(5, 100),
    865                          (6, 100),(7, 100),(8, 100),(9, 100),(10, 100)],
    866                         color = 'black', fillcolor = 'red', width = 2, marker = 'triangle')
    867      line5 = plt.makeSymbols([(1, 200),(2, 200),(3, 200),(4, 200),(5, 200),
    868                          (6, 200),(7, 200),(8, 200),(9, 200),(10, 200)],
    869                         color = 'red', width = 2, marker = 'circle')
    870      line6 = plt.makeSymbols([(1, 300),(2, 300),(3, 300),(4, 300),(5, 300),
    871                          (6, 300),(7, 300),(8, 300),(9, 300),(10, 300)],
    872                         color = 'green', width = 2, marker = 'dot')
    873      line7 = plt.makeSymbols([(1, 400),(2, 400),(3, 400),(4, 400),(5, 400),
    874                          (6, 400),(7, 400),(8, 400),(9, 400),(10, 400)],
    875                         color = 'blue', fillcolor = 'white',
    876                           size = 2, width = 2, marker = 'square')
    877      line8 = plt.makeSymbols([(1, 500),(2, 500),(3, 500),(4, 500),(5, 500),
    878                          (6, 500),(7, 500),(8, 500),(9, 500),(10, 500)],
    879                         color = 'yellow', width = 2, marker = 'triangle')
    880      line9 = plt.makeSymbols([(1, 600),(2, 600),(3, 600),(4, 600),(5, 600),
    881                          (6, 600),(7, 600),(8, 600),(9, 600),(10, 600)],
    882                         color = 'magenta', width = 2, marker = 'cross')
    883      line10 = plt.makeSymbols([(1, 700),(2, 700),(3, 700),(4, 700),(5, 700),
    884                          (6, 700),(7, 700),(8, 700),(9, 700),(10, 700)],
    885                         color = 'brown', width = 2, marker = 'plus')
    886      line11 = plt.makeSymbols([(1, 800),(2, 800),(3, 800),(4, 800),(5, 800),
    887                          (6, 800),(7, 800),(8, 800),(9, 800),(10, 800)],
    888                         color = 'black', fillcolor = 'orange',
    889                            width = 2, marker = 'triangle_down')
    890
    891
    892      graphObject4 = GraphObjects([line4, line5, line6, line7, line8,
    893                                   line9, line10, line11])
    894
    895      # Two panels
    896      f1 = Frame(root)
    897      f2 = Frame(root1)
    898
    899      graph={}
    900      # Plots 1 and 2 in panel f1, side by side
    901      graph[1] = plt.makeGraphBase(f1, 500, 300, title = 'Plot 1: 1 makeLine call, 1 makeBars call',
    902                           xtitle = 'the x-axis', ytitle = 'the y-axis')
    903      graph[1].pack(side = LEFT, fill = BOTH, expand = YES)
    904      graph[1].draw(graphObject, xaxis = 'minimal', yaxis = 'minimal')
    905
    906      graph[2]  = plt.makeGraphBase(f1, 500, 300, title = 'Plot 2: 1 makeBars call',
    907                          xtitle = 'time', ytitle = 'pulse [volt]')
    908      # Set side - by - side plots
    909      graph[2].pack(side = LEFT, fill = BOTH, expand = YES)
    910      graph[2].draw(graphObject2, 'minimal', 'automatic')
    911
    912      # Pack panel 1 to make it visible
    913      f1.pack()
    914
    915      # Plots 2 and 3 in panel f2, one under the other
    916      graph[3]  = plt.makeGraphBase(f2, 500, 300,
    917                          title = 'Plot 3: 2 makeLine call (smooth, not smooth); 1 makeStep call')
    918      graph[3].pack(side = TOP, fill = BOTH, expand = YES)
    919      graph[3].draw(graphObject3)
    920
    921      graph[4]  = plt.makeGraphBase(f2, 500, 300, border = 3, title = 'Plot 4: 8 makeSymbols calls')
    922      # Set one - over - other configuration of plots
    923      graph[4].pack(side = TOP, fill = BOTH, expand = YES)
    924      graph[4].draw(graphObject4)
    925
    926      # Pack panel 2 to make it visible
    927      f2.pack()
    928
    929      # Save graph[1] to Postscript file (user selects filename)
    930      graph[1].postscr()
    931
    932      # end plotting stuff
    933
    934      #### Very Important -- get Tk going by starting event loop
    935      plt.mainloop()
    936