#!/usr/bin/env python
# $Revision: 1.1.1.14 $ $Date: 2007/01/08 14:47:12 $ kgm
""" SimPlot 1.8  Provides basic plotting services based on Tk/Tkinter.

LICENSE:
Copyright (C) 2002, 2005,2006,2007  Klaus G. Muller, Tony Vignaux
mailto: kgmuller@xs4all.nl and Tony.Vignaux@vuw.ac.nz

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
END OF LICENSE

Derived from plotting package in Grayson's Tkinter book.
The idea to use this package came from Prof. Simon Frost
of U of California, San Diego who also strongly contributed
to the design and implementation of SimPlot.


Change history:

    Nov 2, 2003 :  Combined utils.py (also from Grayson) with plotting package.
    Nov 11, 2003: Made totally OO
    Dec 16, 2003: Completion of SimPlot 1.4alpha
    Feb 2004:       Release with SimPy 1.4
    Aug 27, 2005: Added tests for empty point sets to plotXXX functions.

"""
__version__= '1.8 $Revision: 1.1.1.14 $ $Date: 2007/01/08 14:47:12 $'
from Tkinter import *
from Canvas import Line, CanvasText, Rectangle
from tkMessageBox import *
from tkSimpleDialog import askinteger,askstring,askfloat
from tkFileDialog import *
import string, math
from math import pi
from SimPy.Simulation import Monitor

def minCoordinate(clist):
    if len(clist) < 2: return clist[0]
    try:
        x,  y  = clist[0]
        for x1, y1 in clist[1:]:
            if x1 <= x or y1 <= y:
                x, y = x1, y1
    except:
        x, y = 0, 0

    return x,y

def maxCoordinate(clist):
    if len(clist) < 2: return clist[0]
    try:
        x,  y  = clist[0]
        for x1, y1 in clist[1:]:
            if x1 >= x or y1 >= y:
                x, y = x1, y1
    except:
        x, y = 0, 0

    return x,y

def minBound(clist):
    x = 10000000
    y = 10000000
    for x1, y1 in clist:
        if x1 < x: x = x1
        if y1 < y: y = y1        
    return x,y

def maxBound(clist):
    x = -10000000
    y = -10000000
    for x1, y1 in clist:
        if x1 > x: x = x1
        if y1 > y: y = y1        
    return x,y

class SimPlot(object):
    def __init__(self,root=Tk()):
        self.root=root
        pass

    def mainloop(self):
        self.root.mainloop()

    def makeLine(self,points,**attr):
        return GraphLine(points, **attr)
    def makeStep(self,points, **attr):
        #convert data list to steps
        step0=points[:]
        step1=[[0,0]]*2*len(step0)
        prev=[step0[0][0],0]
        for x in range(len(step0)):
            step1[2*x]=[step0[x][0],prev[1]]
            step1[2*x+1]=step0[x]
            prev=step0[x]
        #draw the line
        return self.makeLine(step1,smooth=False, **attr)
    
    def makeHistogram(self,points,**attr):
        """Makes a histogram graph. 'points' must be a Histogram-like
        object.
        """
        #convert data list to bars
        step0=points[:]
        step1=[[0,0]]*3*len(step0)
        prev=[step0[0][0],0]
        for x in range(len(step0)):
            step1[3*x]=[step0[x][0],prev[1]]
            step1[3*x+1]=[step0[x][0],0.0]
            step1[3*x+2]=step0[x]
            prev=step0[x]
        deltax=step0[1][0]-step0[0][0]
        step1.append([prev[0]+deltax,prev[1]])
        step1.append([prev[0]+deltax,0])
        #make the line
        return self.makeLine(step1,smooth=False,
                             xaxis=(step1[0][0],step1[-1][0]),
                             **attr)        
    
    def makeSymbols(self,points,**attr):
        return GraphSymbols(points,**attr)
    def makeBars(self,points,**attr):
        return GraphBars(points,**attr)
    def makeGraphObjects(self,objects):
        return GraphObjects(objects)
    def makeGraphBase(self, master, width, height,
                 background='white',title="",xtitle='',ytitle='', **kw):
        return GraphBase(master, width, height,
                 background,title,xtitle,ytitle,**kw)

    def graphMenu(self,root,graph):
        """To provide a File menu (postscript output, more to come)
        to the plotxxxx plots"""
        mainMenu=Menu(root)
        root.config(menu=mainMenu)
        def postscriptout():
            graph.postscr()
        file=Menu(mainMenu)
        file.add_command(label="Postscript",command=postscriptout)
        mainMenu.add_cascade(label='File',menu=file,underline=0)
        
    def plotLine(self,points,windowsize=(500,300),title='',width=1,color='black',
                 smooth=0,background='white',xlab='x',ylab='y',
                 xaxis='automatic',yaxis='automatic'):
        """Generates a line chart, with menu to save as Postscript file.
        'points' can be a Monitor instance.
        """
        if points!=[]:
            root=Toplevel()
            f=Frame(root)
            try: #if it is like a Monitor, take xlab,ylab from it
                ylab=points.ylab
                xlab=points.tlab
                if not title: title=points.name 
            except:
                pass
            line=self.makeLine(points, width=width,color=color,smooth=smooth)
            gr=self.makeGraphObjects([line])
            graph = self.makeGraphBase(f, windowsize[0], windowsize[1],
                                   title=title,xtitle=xlab,
                                   ytitle=ylab,background=background)
            graph.pack(side=LEFT, fill=BOTH, expand=YES)
            graph.draw(gr,xaxis=xaxis, yaxis=yaxis)
            #File menu
            self.graphMenu(root,graph)
            f.pack()
            return graph
        else:
            print "SimPlot.plotline: dataset empty, no plot."
            return None
    
    def plotStep(self,points,windowsize=(500,300),title='',width=1,color='black',
                 background='white',xlab='x',ylab='y',
                 xaxis='automatic',yaxis='automatic'):
        """Generates a step chart, with menu to save as Postscript file.
        'points' can be a Monitor instance.
        """
        if points!=[]:
            #convert data list to steps
            step0=points[:]
            step1=[[0,0]]*2*len(step0)
            prev=[step0[0][0],0]
            for x in range(len(step0)):
                step1[2*x]=[step0[x][0],prev[1]]
                step1[2*x+1]=step0[x]
                prev=step0[x]
            #treat monitor case
            try: #if it is like a Monitor, take xlab,ylab from it
                ylab=points.ylab
                xlab=points.tlab
                if not title: title=points.name 
            except:
                pass
            #draw the line
            smooth=False
            return self.plotLine(step1,windowsize,title,width,color,
                 smooth,background,xlab,ylab,
                 xaxis,yaxis)
        else:
            print "SimPlot.plotStep: dataset empty, no plot."
            return None

    def plotHistogram(self,points,windowsize=(500,300),title='',width=1,color='black',
                 background='white',xlab='x',ylab='y',
                 xaxis='automatic',yaxis='automatic'):
        """Makes a histogram plot. 'points' can be a Monitor  instance.
        """
        if points!=[]:
            #convert data list to bars
            step0=points[:]
            step1=[[0,0]]*3*len(step0)
            prev=[step0[0][0],0]
            for x in range(len(step0)):
                step1[3*x]=[step0[x][0],prev[1]]
                step1[3*x+1]=[step0[x][0],0.0]
                step1[3*x+2]=step0[x]
                prev=step0[x]
            deltax=step0[1][0]-step0[0][0]
            step1.append([prev[0]+deltax,prev[1]])
            step1.append([prev[0]+deltax,0])
            #treat monitor case
            try: #if it is like a Monitor, take xlab,ylab from it
                ylab=points.ylab
                xlab=points.tlab
                if not title: title=points.name 
            except:
                pass
            #draw the line
            smooth=False
            return self.plotLine(step1,windowsize=windowsize,title=title,width=width,
                             color=color,smooth=smooth,background=background,
                             xlab=xlab,ylab=ylab,xaxis=(step1[0][0],step1[-1][0]),
                             yaxis=yaxis)
        else:
            print "SimPlot.plotHistogram: dataset empty, no plot."
            return None
    
    def plotBars(self,points,windowsize=(500,300),title='',color='black',
                 width=1,size=3,fillcolor='black',fillstyle='',
                 outline='black',background='white',xlab='x',ylab='y',
                 xaxis='automatic',yaxis='automatic',anchor=0.0):
        """Generates a bar chart, with menu to save as Postscript file.
        'points' can be a Monitor instance.
        """
        if points!=[]:
            root=Toplevel()
            f=Frame(root)
            try: #if it is like a Monitor, take xlab,ylab from it
                ylab=points.ylab
                xlab=points.tlab
                if not title: title=points.name 
            except:
                pass
            bars=self.makeBars(points, width=width,size=size,color=color,
                           fillcolor=fillcolor,fillstyle=fillstyle,
                           outline=outline,anchor=anchor)
            gr=self.makeGraphObjects([bars])
            graph = self.makeGraphBase(f, windowsize[0],windowsize[1],
                                   title=title,xtitle=xlab,
                                   ytitle=ylab,background=background)
            graph.pack(side=LEFT, fill=BOTH, expand=YES)
            graph.draw(gr,xaxis=xaxis, yaxis=yaxis)
            #File menu
            self.graphMenu(root,graph)
            f.pack()
            return graph
        else:
            print "SimPlot.plotBars dataset empty, no plot."
            return None

    def plotScatter(self,points,windowsize=(500,300),title='',width=1,color='black',
                    fillcolor='black',size=2,fillstyle='',
                    outline='black',marker='circle',
                    background='white',xlab='x',ylab='y',
                    xaxis='automatic',yaxis='automatic'):
        if points!=[]:
            root=Toplevel()
            f=Frame(root)
            try: #if it is like a Monitor, take xlab,ylab from it
                ylab=points.ylab
                xlab=points.tlab
                if not title: title=points.name 
            except:
                pass
            scat=self.makeSymbols(points, width=width,color=color,size=size,
                              marker=marker,fillcolor=fillcolor,
                              fillstyle=fillstyle,outline=outline)
            gr=self.makeGraphObjects([scat])
            graph = self.makeGraphBase(f, windowsize[0],windowsize[1],
                                   title=title,xtitle=xlab,
                                   ytitle=ylab,background=background)
            graph.pack(side=LEFT, fill=BOTH, expand=YES)
            graph.draw(gr,xaxis=xaxis, yaxis=yaxis)
            #File menu
            self.graphMenu(root,graph)
            f.pack()
            return graph
        else:
            print "SimPlot.plotScatter: dataset empty, no plot."
            return None

    def mainloop(self):
        self.root.mainloop()
    
class GraphPoints:
    def __init__(self, points, attr):
        self.points = points
        self.scaled = self.points
        self.attributes = {}
        for name, value in self._attributes.items():
            try:
                value = attr[name]
            except KeyError: pass
            self.attributes[name] = value

    def boundingBox(self):
        return minBound(self.points),  maxBound(self.points)

    def fitToScale(self, scale=(1,1), shift=(0,0)):
        self.scaled = []
        for x,y in self.points:
            self.scaled.append(((scale[0]*x)+shift[0],\
                               (scale[1]*y)+shift[1]))
            self.attributes.get('anchor', 0.0)
        self.anchor = scale[1]*self.attributes.get('anchor', 0.0)+\
                      shift[1]

class GraphLine(GraphPoints):
    def __init__(self, points, **attr):
        GraphPoints.__init__(self, points, attr)

    _attributes = {'color':       'black',
                   'width':        1,
                   'smooth':       0,
                   'splinesteps': 12}

    def draw(self, canvas):
        color  = self.attributes['color']
        width  = self.attributes['width']
        smooth = self.attributes['smooth']
        steps  = self.attributes['splinesteps']
        arguments = (canvas,)
        if smooth:
            for i in range(len(self.points)):
                x1, y1 = self.scaled[i]
                arguments = arguments + (x1, y1)
        else:
            for i in range(len(self.points)-1):
                x1, y1 = self.scaled[i]
                x2, y2 = self.scaled[i+1]
                arguments = arguments + (x1, y1, x2, y2)
        apply(Line, arguments, {'fill': color, 'width': width,
                                'smooth': smooth, 'splinesteps':steps})

class GraphSymbols(GraphPoints):
    def __init__(self, points, **attr):
        GraphPoints.__init__(self, points, attr)

    _attributes = {'color': 'black',
                   'width': 1,
                   'fillcolor': 'black',
                   'size': 2,
                   'fillstyle': '',
                   'outline': 'black',
                   'marker': 'circle'}

    def draw(self, canvas):
        color     = self.attributes['color']
        size      = self.attributes['size']
        fillcolor = self.attributes['fillcolor']
        marker    = self.attributes['marker']
        fillstyle = self.attributes['fillstyle']

        self._drawmarkers(canvas, self.scaled, marker, color,
                          fillstyle, fillcolor, size)

    def _drawmarkers(self, c, coords, marker='circle', color='black',
                     fillstyle='', fillcolor='', size=2):
        l = []
        f = eval('self._' +marker)
        for xc, yc in coords:
            id = f(c, xc, yc, outline=color, size=size,
                   fill=fillcolor, fillstyle=fillstyle)
            if type(id) is type(()):
                for item in id: l.append(item)
            else:
                l.append(id)
        return l
    
    def _circle(self, c, xc, yc, size=1, fill='', outline='black',
                fillstyle=''):
        id = c.create_oval(xc-0.5, yc-0.5, xc+0.5, yc+0.5, 
                           fill=fill, outline=outline,
                           stipple=fillstyle)
        c.scale(id, xc, yc, size*5, size*5)
        return id

    def _dot(self, c, xc, yc, size=1, fill='', outline='black',
             fillstyle=''):
        id = c.create_oval(xc-0.5, yc-0.5, xc+0.5, yc+0.5, 
                           fill=fill, outline=outline,
                           stipple=fillstyle)
        c.scale(id, xc, yc, size*2.5, size*2.5)
        return id

    def _square(self, c, xc, yc, size=1, fill='', outline='black',
                fillstyle=''):
        id = c.create_rectangle(xc-0.5, yc-0.5, xc+0.5, yc+0.5,
                                fill=fill, outline=outline,
                                stipple=fillstyle)
        c.scale(id, xc, yc, size*5, size*5)
        return id
    
    def _triangle(self, c, xc, yc, size=1, fill='', outline='black',
                  fillstyle=''):
        id = c.create_polygon(-0.5, 0.288675134595,
                              0.5, 0.288675134595,
                              0.0, -0.577350269189, fill=fill,
                              outline=outline, stipple=fillstyle)
        c.move(id, xc, yc)
        c.scale(id, xc, yc, size*5, size*5)
        return id

    def _triangle_down(self, c, xc, yc, size=1, fill='',
                       outline='black', fillstyle=''):
        id = c.create_polygon(-0.5, -0.288675134595,
                              0.5, -0.288675134595,
                              0.0, 0.577350269189, fill=fill,
                              outline=outline, stipple=fillstyle)
        c.move(id, xc, yc)
        c.scale(id, xc, yc, size*5, size*5)
        return id

    def _cross(self, c, xc, yc, size=1, fill='black', outline=None,
               fillstyle=''):
        if outline: fill=outline
        id1 = c.create_line(xc-0.5, yc-0.5, xc+0.5, yc+0.5,
                            fill=fill)
        id2 = c.create_line(xc-0.5, yc+0.5, xc+0.5, yc-0.5,
                            fill=fill)
        c.scale(id1, xc, yc, size*5, size*5)
        c.scale(id2, xc, yc, size*5, size*5)
        return id1, id2

    def _plus(self, c, xc, yc, size=1, fill='black', outline=None,
              fillstyle=''):
        if outline: fill=outline
        id1 = c.create_line(xc-0.5, yc, xc+0.5, yc, fill=fill)
        id2 = c.create_line(xc, yc+0.5, xc, yc-0.5, fill=fill)
        c.scale(id1, xc, yc, size*5, size*5)
        c.scale(id2, xc, yc, size*5, size*5)
        return id1, id2

class GraphBars(GraphPoints):
    def __init__(self, points, **attr):
        GraphPoints.__init__(self, points, attr)

    _attributes = {'color': 'black',
                   'width': 1,
                   'fillcolor': 'black',
                   'size': 3,
                   'fillstyle': '', 
                   'outline': 'black'}

    def draw(self, canvas):
        color     = self.attributes['color']
        width     = self.attributes['width']
        fillstyle = self.attributes['fillstyle']
        outline   = self.attributes['outline']
        spread    = self.attributes['size']
        arguments = (canvas,)
        p1, p2    = self.boundingBox()
        for i in range(len(self.points)):
            x1, y1 = self.scaled[i]
            canvas.create_rectangle(x1-spread, y1, x1+spread,
                                    self.anchor, fill=color,
                                    width=width, outline=outline,
                                    stipple=fillstyle)

class GraphObjects:
    def __init__(self, objects):
        self.objects = objects

    def boundingBox(self):
        c1, c2 = self.objects[0].boundingBox()
        for object in self.objects[1:]:
            c1o, c2o = object.boundingBox()
            c1 = minBound([c1, c1o])

            c2 = maxBound([c2, c2o])
        return c1, c2

    def fitToScale(self, scale=(1,1), shift=(0,0)):
        for object in self.objects:
            object.fitToScale(scale, shift)

    def draw(self, canvas):
        for object in self.objects:
            object.draw(canvas)

class GraphBase(Frame):
    def __init__(self, master, width, height,
                 background='white',title="",xtitle='',ytitle='', **kw):
        apply(Frame.__init__, (self, master), kw)
        self.title=title                                        
        self.xtitle=xtitle
        self.ytitle=ytitle
        self.canvas = Canvas(self, width=width, height=height,
                             background=background)
        self.canvas.pack(fill=BOTH, expand=YES)
        border_w = self.canvas.winfo_reqwidth() - \
                   string.atoi(self.canvas.cget('width'))
        border_h = self.canvas.winfo_reqheight() - \
                   string.atoi(self.canvas.cget('height'))
        self.border = (border_w, border_h)
        self.canvas.bind('<Configure>', self.configure)
        self.plotarea_size = [None, None]
        self._setsize()
        self.last_drawn = None
        self.font = ('Verdana', 10)

    def configure(self, event):
        new_width = event.width-self.border[0]
        new_height = event.height-self.border[1]
        width = string.atoi(self.canvas.cget('width'))
        height = string.atoi(self.canvas.cget('height'))
        if new_width == width and new_height == height:
            return
        self.canvas.configure(width=new_width, height=new_height)
        self._setsize()
        self.clear()
        self.replot()

    def bind(self, *args):
        apply(self.canvas.bind, args)

    def _setsize(self):
        self.width = string.atoi(self.canvas.cget('width'))
        self.height = string.atoi(self.canvas.cget('height'))
        #self.plotarea_size[0] = 0.90 * self.width
        #self.plotarea_size[1] = 0.90 * -self.height
        self.plotarea_size[0] = 0.90 * self.width
        self.plotarea_size[1] = 0.90 * -self.height      
        xo = 0.5*(self.width-self.plotarea_size[0])
        yo = self.height-0.5*(self.height+self.plotarea_size[1])
        self.plotarea_origin = (xo, yo)
        
    def draw(self, graphics, xaxis = 'automatic', yaxis = 'automatic'):
        
        self.last_drawn = (graphics, xaxis, yaxis)
        p1, p2 = graphics.boundingBox()
        xaxis = self._axisInterval(xaxis, p1[0], p2[0])
        yaxis = self._axisInterval(yaxis, p1[1], p2[1])
        text_width = [0., 0.]
        text_height = [0., 0.]
        if xaxis is not None:
            p1 = xaxis[0], p1[1]
            p2 = xaxis[1], p2[1]
            xticks = self._ticks(xaxis[0], xaxis[1])
            bb = self._textBoundingBox(xticks[0][1])
            text_height[1] = bb[3]-bb[1]
            text_width[0] = 0.5*(bb[2]-bb[0])
            bb = self._textBoundingBox(xticks[-1][1])
            text_width[1] = 0.5*(bb[2]-bb[0])
        else:
            xticks = None
        if yaxis is not None:
            p1 = p1[0], yaxis[0]
            p2 = p2[0], yaxis[1]
            yticks = self._ticks(yaxis[0], yaxis[1])
            for y in yticks:
                bb = self._textBoundingBox(y[1])
                w = bb[2]-bb[0]
                text_width[0] = max(text_width[0], w)
            h = 0.5*(bb[3]-bb[1])
            text_height[0] = h
            text_height[1] = max(text_height[1], h)
        else:
            yticks = None
        text1 = [text_width[0], -text_height[1]]
        text2 = [text_width[1], -text_height[0]]
        scale = ((self.plotarea_size[0]-text1[0]-text2[0]) / \
                 (p2[0]-p1[0]),
                 (self.plotarea_size[1]-text1[1]-text2[1]) / \
                 (p2[1]-p1[1]))
        shift = ((-p1[0]*scale[0]) + self.plotarea_origin[0] + \
                 text1[0],
                 (-p1[1]*scale[1]) + self.plotarea_origin[1] + \
                 text1[1])
        self._drawAxes(self.canvas, xaxis, yaxis, p1, p2,
                        scale, shift, xticks, yticks)
        graphics.fitToScale(scale, shift)
        graphics.draw(self.canvas)

    def _axisInterval(self, spec, lower, upper):
        if spec is None:
            return None
        if spec == 'minimal':
            if lower == upper:
                return lower-0.5, upper+0.5
            else:
                return lower, upper
        if spec == 'automatic':
            range = upper-lower
            if range == 0.:
                return lower-0.5, upper+0.5
            log = math.log10(range)
            power = math.floor(log)
            fraction = log-power
            if fraction <= 0.05:
                power = power-1
            grid = 10.**power
            lower = lower - lower % grid
            mod = upper % grid
            if mod != 0:
                upper = upper - mod + grid
            return lower, upper
        if type(spec) == type(()):
            lower, upper = spec
            if lower <= upper:
                return lower, upper
            else:
                return upper, lower
        raise ValueError, str(spec) + ': illegal axis specification'

    def _drawAxes(self, canvas, xaxis, yaxis,
                  bb1, bb2, scale, shift, xticks, yticks):
        dict = {'anchor': N, 'fill': 'black'}
        if self.font is not None:
            dict['font'] = self.font
        if xaxis is not None:
        #draw x-axis
            lower, upper = xaxis
            text = 1
            once=1
            for y, d in [(bb1[1], -3), (bb2[1], 3)]:
            #d=.5 of tick-length
                p1 = (scale[0]*lower)+shift[0], (scale[1]*y)+shift[1]
                if once: pp1=p1
                p2 = (scale[0]*upper)+shift[0], (scale[1]*y)+shift[1]
                if once: pp2=p2
                once = 0
                Line(self.canvas, p1[0], p1[1], p2[0], p2[1],
                     fill = 'black', width = 1)
                if xticks:
                    for x, label in xticks:
                        p = (scale[0]*x)+shift[0], \
                            (scale[1]*y)+shift[1]
                        Line(self.canvas, p[0], p[1], p[0], p[1]+d,
                             fill = 'black', width = 1)
                        if text:
                            dict['text'] = label
                            apply(CanvasText, (self.canvas, p[0],
                                               p[1]+2), dict)    ##KGM 14 Aug 03
                text = 0
            #write x-axis title
            CanvasText(self.canvas,(pp2[0]-pp1[0])/2.+pp1[0],pp1[1]+22,text=self.xtitle)
        #write graph title
        CanvasText(self.canvas,(pp2[0]-pp1[0])/2.+pp1[0],7,text=self.title) 
        dict['anchor'] = E
        if yaxis is not None:
            #draw y-axis
            lower, upper = yaxis
            text = 1
            once = 1
            for x, d in [(bb1[0], -3), (bb2[0], 3)]:
                p1 = (scale[0]*x)+shift[0], (scale[1]*lower)+shift[1]
                p2 = (scale[0]*x)+shift[0], (scale[1]*upper)+shift[1]
                if once: pp1=p1 ;pp2=p2
                once = 0
                Line(self.canvas, p1[0], p1[1], p2[0], p2[1],
                     fill = 'black', width = 1)
                if yticks:
                    for y, label in yticks:
                        p = (scale[0]*x)+shift[0], \
                            (scale[1]*y)+shift[1]
                        Line(self.canvas, p[0], p[1], p[0]-d, p[1],
                             fill = 'black', width = 1)
                        if text:
                            dict['text'] = label
                            apply(CanvasText,(self.canvas,
                                              p[0]-4,p[1]+2), dict)
                text = 0
            #write y-axis title
            CanvasText(self.canvas,pp2[0],pp2[1] - 10,text=self.ytitle)

    def _ticks(self, lower, upper):
        ideal = (upper-lower)/7.
        log = math.log10(ideal)
        power = math.floor(log)
        fraction = log-power
        factor = 1.
        error = fraction
        for f, lf in self._multiples:
            e = math.fabs(fraction-lf)
            if e < error:
                error = e
                factor = f
        grid = factor * 10.**power
        if power > 3 or power < -3:
            format = '%+7.0e'
        elif power >= 0:
            digits = max(1, int(power))
            format = '%' + `digits`+'.0f'
        else:
            digits = -int(power)
            format = '%'+`digits+2`+'.'+`digits`+'f'
        ticks = []
        t = -grid*math.floor(-lower/grid)
        while t <= upper and len(ticks) < 200:
            ticks.append((t, format % (t,)))
            t = t + grid
        return ticks

    _multiples = [(2., math.log10(2.)), (5., math.log10(5.))]

    def _textBoundingBox(self, text):
        bg = self.canvas.cget('background')
        dict = {'anchor': NW, 'text': text, 'fill': bg}
        if self.font is not None:
            dict['font'] = self.font
        item = apply(CanvasText, (self.canvas, 0., 0.), dict)
        bb = self.canvas.bbox(item)
        self.canvas.delete(item)
        return bb

    def replot(self):
        if self.last_drawn is not None:
            apply(self.draw, self.last_drawn)

    def clear(self):
        self.canvas.delete('all')

    def postscr(self,filename=None):
        """Write to Postscript file given by 'filename'. If none provided,
        ask user.
        """
        from tkFileDialog import asksaveasfilename
        if not filename:
            filename=asksaveasfilename()
        if filename:
            if not filename[-3:] == '.ps':
                filename+=".ps"
            self.canvas.postscript(width=self.width,height=self.height,file=filename)

class TextBox(Frame):
    def __init__(self, master, width, height,
                 background='white',boxtext='', **kw):
        apply(Frame.__init__, (self, master), kw)
        self.width=width
        self.height=height
        self.canvas=Canvas(self, width=width, height=height,
                           background=background)
        self.canvas.pack(fill=BOTH, expand=YES)
        #CanvasText(self.canvas,text=boxtext)

    def postscr(self):
        #select output file
        #from tkFileDialog import asksaveasfilename
        filename=asksaveasfilename()
        if filename:
            if not filename[-3:] == '.ps':
                filename+=".ps"
            self.canvas.postscript(width=self.width,height=self.height,file=filename)
        
if __name__ == '__main__':
    print "SimPlot.py %s"%__version__
    root=Tk()
    plt=SimPlot()
    root.title('SimPlot example - First frame')

    root1 = Tk()
    root1.title('SimPlot example - Second frame')
    
    """PARAMETER DEFAULTS:
    GraphBase
    ---------
    background='white',
    title="",
    xtitle='',
    ytitle=''

    GraphBase.draw
    --------------
    xaxis = 'automatic',
    yaxis = 'automatic')
    
    GraphLine
    ---------
    color:       'black',
    width:        1,
    smooth:       0,
    splinesteps: 12

    GraphSymbols:
    -------------
    color: 'black',
    width: 1,
    fillcolor: 'black',
    size: 2,
    fillstyle: '',
    outline: 'black',
    marker: 'circle'}

    GraphBars
    ---------
    color: 'black',
    width: 1,
    fillcolor: 'black',
    size: 3,
    fillstyle: '', 
    outline: 'black'
    """
    # Plot 1 -- smooth line + filled bars
    di = 5.0*pi/40.
    data = []
    for i in range(40):
        data.append((float(i)*di,
                     (math.sin(float(i)*di)-math.cos(float(i)*di))))
    line1  = plt.makeLine(data, color='black', width=1,
                      smooth=1)
    line1a = plt.makeBars(data[1:], color='blue', fillstyle='gray25',
                      anchor=0.0)
    

    graphObject=plt.makeGraphObjects([line1a,line1])
    #Second panel -- Narrow bars
    line2 = plt.makeBars([(0,0),(1,145),(2,-90),(3,147),(4,22),(5,31),
                        (6,77),(7,125),(8,220),(9,550),(10,560),(11,0)],
                       outline='green',color='red', size=7)


    graphObject2=plt.makeGraphObjects([line2])

    # Third plot -- Smooth line and unsmoothed line 
    line3 = plt.makeLine([(1,145+100),(2,151+100),(3,147+100),(4,22+100),(5,31+100),
                        (6,77+100),(7,125+100),(8,220+100),(9,550+100),(10,560+100)],
                       color='blue', width=2, smooth=1)
    line3a = plt.makeLine([(1,145),(2,151),(3,147),(4,22),(5,31),
                        (6,77),(7,125),(8,220),(9,550),(10,560)],
                       color='green', width=2, smooth=0)
    line3b = plt.makeStep([(1,145+100),(2,151+100),(3,147+100),(4,22+100),(5,31+100),
                        (6,77+100),(7,125+100),(8,220+100),(9,550+100),(10,560+100)],
                       color='red', width=2)
    
    graphObject3 = plt.makeGraphObjects([line3, line3a, line3b])
    
    # Fourth plot -- lines with all available symbols with different
    #                outline colors/fill colors/sizes
    
    line4 = plt.makeSymbols([(1,100),(2,100),(3,100),(4,100),(5,100),
                        (6,100),(7,100),(8,100),(9,100),(10,100)],
                       color='black',fillcolor='red', width=2,marker='triangle')
    line5 = plt.makeSymbols([(1,200),(2,200),(3,200),(4,200),(5,200),
                        (6,200),(7,200),(8,200),(9,200),(10,200)],
                       color='red', width=2,marker='circle')
    line6 = plt.makeSymbols([(1,300),(2,300),(3,300),(4,300),(5,300),
                        (6,300),(7,300),(8,300),(9,300),(10,300)],
                       color='green', width=2,marker='dot')
    line7 = plt.makeSymbols([(1,400),(2,400),(3,400),(4,400),(5,400),
                        (6,400),(7,400),(8,400),(9,400),(10,400)],
                       color='blue', fillcolor='white',
                         size=2, width=2,marker='square')
    line8 = plt.makeSymbols([(1,500),(2,500),(3,500),(4,500),(5,500),
                        (6,500),(7,500),(8,500),(9,500),(10,500)],
                       color='yellow', width=2,marker='triangle')
    line9 = plt.makeSymbols([(1,600),(2,600),(3,600),(4,600),(5,600),
                        (6,600),(7,600),(8,600),(9,600),(10,600)],
                       color='magenta', width=2,marker='cross')
    line10 = plt.makeSymbols([(1,700),(2,700),(3,700),(4,700),(5,700),
                        (6,700),(7,700),(8,700),(9,700),(10,700)],
                       color='brown', width=2,marker='plus')
    line11 = plt.makeSymbols([(1,800),(2,800),(3,800),(4,800),(5,800),
                        (6,800),(7,800),(8,800),(9,800),(10,800)],
                       color='black', fillcolor='orange',
                          width=2,marker='triangle_down')
  

    graphObject4 = GraphObjects([line4, line5, line6, line7, line8,
                                 line9, line10, line11])

    # Two panels
    f1 = Frame(root)
    f2 = Frame(root1)
 
    graph={}
    # Plots 1 and 2 in panel f1, side by side
    graph[1]= plt.makeGraphBase(f1, 500, 300, title="Plot 1: 1 makeLine call, 1 makeBars call",
                         xtitle='the x-axis',ytitle='the y-axis')
    graph[1].pack(side=LEFT, fill=BOTH, expand=YES)
    graph[1].draw(graphObject,xaxis='minimal', yaxis='minimal')
    
    graph[2]  = plt.makeGraphBase(f1, 500, 300,title="Plot 2: 1 makeBars call",
                        xtitle='time',ytitle='pulse [volt]')
    # Set side-by-side plots
    graph[2].pack(side=LEFT, fill=BOTH, expand=YES)
    graph[2].draw(graphObject2, 'minimal', 'automatic')
       
    # Pack panel 1 to make it visible
    f1.pack()
    
    # Plots 2 and 3 in panel f2, one under the other
    graph[3]  = plt.makeGraphBase(f2, 500, 300,
                        title="Plot 3: 2 makeLine call (smooth, not smooth); 1 makeStep call")
    graph[3].pack(side=TOP, fill=BOTH, expand=YES)
    graph[3].draw(graphObject3)
    
    graph[4]  = plt.makeGraphBase(f2, 500, 300, border=3,title="Plot 4: 8 makeSymbols calls")
    # Set one-over-other configuration of plots
    graph[4].pack(side=TOP, fill=BOTH, expand=YES)
    graph[4].draw(graphObject4)

    # Pack panel 2 to make it visible
    f2.pack()

    # Save graph[1] to Postscript file (user selects filename)
    graph[1].postscr()
        
    # end plotting stuff

    #### Very Important -- get Tk going by starting event loop
    plt.mainloop()
