imageTools.py :  » Media-Sound-Audio » athenaCL » athenaCL » libATH » Python Open Source

Home
Python Open Source
1.3.1.2 Python
2.Ajax
3.Aspect Oriented
4.Blog
5.Build
6.Business Application
7.Chart Report
8.Content Management Systems
9.Cryptographic
10.Database
11.Development
12.Editor
13.Email
14.ERP
15.Game 2D 3D
16.GIS
17.GUI
18.IDE
19.Installer
20.IRC
21.Issue Tracker
22.Language Interface
23.Log
24.Math
25.Media Sound Audio
26.Mobile
27.Network
28.Parser
29.PDF
30.Project Management
31.RSS
32.Search
33.Security
34.Template Engines
35.Test
36.UML
37.USB Serial
38.Web Frameworks
39.Web Server
40.Web Services
41.Web Unit
42.Wiki
43.Windows
44.XML
Python Open Source » Media Sound Audio » athenaCL 
athenaCL » athenaCL » libATH » imageTools.py
#-----------------------------------------------------------------||||||||||||--
# Name:         imageTools.py
# Purpose:      General image processing and drawing tools using PIL, Tk, and 
#               vector graphics
#
# Authors:      Christopher Ariza
#
# Copyright:    (c) 2004-2006 Christopher Ariza
# License:      GPL
#-----------------------------------------------------------------||||||||||||--

import os, random, string, copy, time, array
from athenaCL.libATH import drawer
from athenaCL.libATH import interpolate
from athenaCL.libATH import fontLibrary
# dialog import imageTools.py for finding dlg visual formats
from athenaCL.libATH import osTools

_MOD = 'imageTools.py'


#-----------------------------------------------------------------||||||||||||--
# note: this imports must match those tested in drawer.imageFormats
try:
   import Image, ImageDraw, ImageFont #, ImageFilter ImageTk
   PIL = 1
except ImportError:
   PIL = 0
try:
   import Tkinter
   TK = 1
except ImportError:
   TK = 0
#-----------------------------------------------------------------||||||||||||--
FONTPATH = os.path.join(os.path.expanduser('~'), 'Library', 'FontsPIL')
if not os.path.exists(FONTPATH):
   FONTPATH = None
   # load the better than nothing default
   # print _MOD, 'no PIL fonts available'

   
#-----------------------------------------------------------------||||||||||||--
# these methods borrowed from:
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/266466/index_txt

def RGBToHTMLColor(rgb_tuple):
   """ convert an (R, G, B) tuple to #RRGGBB """
   if drawer.isStr(rgb_tuple):
      if rgb_tuple[0] == '#': # already a string
         return rgb_tuple
   for x in rgb_tuple:
      assert x < 256 and x >= 0
   hexcolor = '#%02x%02x%02x' % rgb_tuple
   # that's it! '%02x' means zero-padded, 2-digit hex values
   hexcolor = hexcolor.upper()
   return hexcolor

def HTMLColorToRGB(colorstring):
   """ convert #RRGGBB to an (R, G, B) tuple """
   colorstring = colorstring.strip()
   if colorstring[0] == '#': colorstring = colorstring[1:]
   if len(colorstring) != 6:
      raise ValueError, "input #%s is not in #RRGGBB format" % colorstring
   r, g, b = colorstring[:2], colorstring[2:4], colorstring[4:]
   r, g, b = [int(n, 16) for n in (r, g, b)]
   return (r, g, b)
   
def HTMLColorToUnitRGB(colorstring):
   """ converts #RRGGBB to postScript style float values
   assume that there are 256 values"""
   max = 255.0
   r, g, b = HTMLColorToRGB(colorstring)
   r = r / max
   g = g / max
   b = b / max
   return r, g, b

def HTMLColorToPILColor(colorstring):
   """ converts #RRGGBB to PIL-compatible integers"""
   colorstring = colorstring.strip()
   while colorstring[0] == '#': colorstring = colorstring[1:]
   # get bytes in reverse order to deal with PIL quirk
   colorstring = colorstring[-2:] + colorstring[2:4] + colorstring[:2]
   # finally, make it numeric
   color = int(colorstring, 16)
   return color

def PILColorToRGB(pil_color):
   """ convert a PIL-compatible integer into an (r, g, b) tuple """
   hexstr = '%06x' % pil_color
   # reverse byte order
   r, g, b = hexstr[4:], hexstr[2:4], hexstr[:2]
   r, g, b = [int(n, 16) for n in (r, g, b)]
   return (r, g, b)

def PILColorToHTMLColor(pil_integer):
   return RGBToHTMLColor(PILColorToRGB(pil_integer))

def RGBToPILColor(rgb_tuple):
   return HTMLColorToPILColor(RGBToHTMLColor(rgb_tuple))

# added for easy processing
def FloatToRGB(cellValue):
   """translate a cell value to a color or gray shade
   positive shift will make that much brighter
   negative shift will make darker"""
   # must invert cell value, so 1 is black, or 0
   cellValue = 1-cellValue
   # if not float return after reversing, could be a decimal
   return (float(str(cellValue)) * 255, float(str(cellValue)) * 255,
          float(str(cellValue)) * 255)

#    if not drawer.isInt(cellValue):
#       print _MOD, 'converting non int'
# 
#    cellMax = 255 * max
#    if cellMax >= 255: cellMax = 255
#    cellMin = 255 * min
#    if cellMin <= 0: cellMin = 0
#    cellRange = cellMax - cellMin
#    shade = int(round(cellMin + (cellRange * cellValue))) 
#    tk_rgb = (shade,shade,shade) # (shade, shade, shade) 
#    return tk_rgb


#-----------------------------------------------------------------||||||||||||--
# tools for determining image format types
# vetor here referes to a trademarked name
# dynamic testing of available formats in drawer.imageFormats

imageFormatNames = ['text', 'eps', 'tk', 'png', 'jpg']

def imageFormatParser(usrStr):
   """provide backward compat to older names"""
   ref = {
      'jpg' : ['pil', 'jpeg', 'jpg', 'j'],
      'png' : ['file', 'png', 'p'],
      'tk'  : ['tk', 'tkinter', 'k'], # t reserved for text
      'eps' : ['eps', 'ps', 'e'],
      'text': ['text', 't'],
         }
   usrStr = drawer.selectionParse(usrStr, ref)
   return usrStr # may be None
   

      
   
#-----------------------------------------------------------------||||||||||||--
# designed for use with PIL fonts found here
# http://effbot.org/downloads/
fontLibPil = {
   'char' : ('charI', 'charR', ),
   'courier' : ('courB', 'courO', 'courR'),
   'helvetica' : ('helvR', 'helvO', 'helvB'),
   'lub' : ('lubB', 'luBIS', 'lubI', 'luBS', 'lubR', 
            'luIS', 'luRS', 'lutBS', 'lutRS'),
   'ncen' : ('ncenB', 'ncenBI', 'ncenI', 'ncenR'),
   'symbol' : ('symb', ),
   'tech' : ('tech', 'techB',),
   'term' : ('termB', 'term',),
   'times' : ('timB', 'timBI', 'timI', 'timR'),
   }

def getFontPath((face, style, size)): # dont check for errrs
   for n in fontLibPil[face]:
      if style in n or style == n:
         group = n
         break
   if size <= 9:
      sizeStr = '0%s' % size
   else:
      sizeStr = str(size)
   name = '%s%s.pil' % (group, sizeStr)
   return os.path.join(FONTPATH, name)
   
   
   
#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
# note: these objects require PIL
class Thumb:
   def __init__(self, fileList, size=256, fmt='.jpg'):
      "create a thumbnail; if size is none, dont resize"
      self.sizeW = size
      self.sizeH = size
      self.fmt = fmt
      # check if file list is a dir, if so, get a listing of image files
      if drawer.isList(fileList):
         self.fileList = fileList
      else:
         self.fileList = [fileList, ] # its a single file

   def reduce(self):
      import Image
      procFiles = [] # stores tuples of src, dst file
      nameOutFiles = []
      for infile in self.fileList:
         if self.sizeW == None:
            outfile = os.path.splitext(infile)[0] + self.fmt
         else:
            outfile = os.path.splitext(infile)[0] + ".th%s%s" % (
                                               self.sizeW, self.fmt)
         if infile.endswith('.gif'):
            print _MOD, 'cannot process gif files: %s' % infile
         elif outfile in nameOutFiles:
            print _MOD, 'file already generated: %s' % outfile
         elif infile != outfile:
            try:
               im = Image.open(infile)
               if self.sizeW != None: # only resize if size is not None
                  print _MOD, 'reducing size to %s' % self.sizeW
                  im.thumbnail((self.sizeW, self.sizeH), Image.ANTIALIAS)
               #im.save(outfile, "JPEG")
               im.save(outfile)
               procFiles.append([infile, outfile])
               nameOutFiles.append(outfile)
            except IOError, msg:
               print _MOD, "cannot create thumbnail: %s" % infile, msg 
         else:
            print _MOD, 'name conflict: %s' % infile
      return procFiles




#-----------------------------------------------------------------||||||||||||--
class GridGraphic:
   """uess a list of lists as matrix for generating images"""
   def __init__(self, gridData=None, dataType='float', transp=None):
      """if dataType == float values are interpreted as gray scale
      if dataType == hex, html hex a decimal strings expected
      if dataType == rgb, assume rgb triples
      rot is rotatioin in degrees
      transparency: None is off, value is color that is transparent?
      """
      self.dataType = dataType
      self.transp = transp
      if gridData == None: # use demo
         self.gridData = [] # a list of lists
         for x in range(20):
            self.gridData.append([1,1,1,1,.5,.5,.5,.5,1,1,1,1,
                                  0,0,0,1,1,1,1,0,0,0,0])
      else:
         self.gridData = gridData
      self.noCols = len(self.gridData[0])
      self.noRows = len(self.gridData)
        
      self.RGBData = self._convertToRGB() 

      self.imageObj = Image.new("RGB", (self.noCols, self.noRows))
      self.imageObj.putdata(self.RGBData)

      # not needed for transparency create mask
      #self.imageObj = self.imageObj.convert("RGBA") 
      #mask = Image.new("1", (self.noCols, self.noRows))
      #self.imageObj.putalpha(mask)

      # putalpha(band)
      # To add an alpha (transparency) band to an "RGBA"-mode image, 
      # call this method and pass it an
      # image of the same size having mode "L" or "1". The pixels of the band
      # image replace the alpha
      # band of the original image in place

   def _convertToRGB(self):
      "translate a list of cells to RGB"
      inputList = [] # self.gridData
      outputList = []
      # flatten grid into one long list
      for row in self.gridData:
         for col in row:
            inputList.append(col)
            
      for cell in inputList:
         if self.dataType == 'float':
            outputList.append(FloatToRGB(cell))
         elif self.dataType == 'hex':
            outputList.append(HTMLColorToRGB(cell))
         elif self.dataType == 'rgb':
            outputList.append(cell)
      #for each in self.gridData._flatten(self.gridData):
      return outputList

   def scale(self, xMult, yMult):
      self.imageObj = self.imageObj.resize((self.noCols*xMult, 
                         self.noRows*yMult))

   def rotate(self, angle):
      if angle != 0:
         self.imageObj = self.imageObj.rotate(angle)

   def write(self, filePath, openMedia=1):
      "Saves the Image; .png and .jpg both work; format based on extensions"
      self.filePath = filePath
      if self.transp != None:
         self.imageObj.save(self.filePath, transparency=self.transp)
      else: #determine format from name
         self.imageObj.save(self.filePath)

   def show(self, dir=None, prefDict=None):
      "Opens default image viewer and shows picture"
      self.imageObj.show(dir, prefDict)

   def getData(self):
      bundle = []
      seq = list(self.imageObj.getdata())
      row = []
      for pix in seq:
         if len(row) == self.noCols:
            bundle.append(row)
            row = []
         row.append(pix)
      return bundle




class BarCode(GridGraphic):
   def __init__(self, src=None, width=120, foreColor='#000000', 
                    backColor='#aaaaaa', xMult=1, yMult=1, horizontal=1):
      if width == None and src == None:
         raise ValueError, 'not enough arguments'
      #foreColor can be a list of colors that are chosen from at random
      if not drawer.isList(foreColor):
         foreColor = [foreColor,]
      if not drawer.isList(backColor):
         backColor = [backColor,]
      if backColor[0] == None: # if none, back it transparent    
         self.transp = 0 # black
         backColor[0] = '#000000' # make it black
      else:
         self.transp = None # none is no transp

      if width == None:
         width = len(src)

      # do not apply xmult yet, but account for it in the lineGraph
      lineGraph = []
      if src == None: # creates a random distribution
         for i in range(0,width):
            pixel = random.choice((0,1)) # choose on or off
            lineGraph.append(pixel)
      else: # wrap src to fill width
         for i in range(0,width):
            pixel = src[i % len(src)] # wrap
            lineGraph.append(pixel)

      gridRow = []
      for pixel in lineGraph:
         #choose random color
         if pixel: # if 1
            color = random.choice(foreColor)
         else:
            color = random.choice(backColor)
         for x in range(0, xMult):
            gridRow.append(color)
      gridData = [gridRow]

      GridGraphic.__init__(self, gridData, 'hex', self.transp)
      self.scale(xMult, yMult)
      if not horizontal: # rotate 90 degrees    
         self.rotate(90)
      

class GridText(GridGraphic):
   def __init__(self, msg='test', foreColor='#000000', 
                backColor='#aaaaaa', font='micro', kernBit=1, 
                vertBit=None, xMult=1, yMult=1, horizontal=1, 
                charWrapWidth=None):

      #foreColor can be a list of colors that are chosen from at random
      if not drawer.isList(foreColor):
         foreColor = [foreColor,]
      if not drawer.isList(backColor):
         backColor = [backColor,]
      if backColor[0] == None: # if none, back it transparent    
         self.transp = 0 # black
         backColor[0] = '#000000' # make it black
      else:
         self.transp = None # none is no transp

      kernEqual = 0 # treat as equla spaced?
      vertBit = None # use default
      obj = fontLibrary.FontBitMap(msg, font, kernEqual, kernBit, vertBit, 
            charWrapWidth)
      gridData = obj.encode()
      for row in range(0, len(gridData)):
         rowData = gridData[row]
         for col in range(0, len(rowData)):
            if gridData[row][col] == 1:
               gridData[row][col] = random.choice(foreColor)
            else: # its 0
               gridData[row][col] = random.choice(backColor)

      GridGraphic.__init__(self, gridData, 'hex', self.transp)
      self.scale(xMult, yMult)
      if not horizontal: # rotate 90 degrees    
         self.rotate(90)



class GridBlend(GridGraphic):
   def __init__(self, colorStart='#000000', colorEnd='#333333', length=20, 
                direction='x', width=40):
      if direction == 'y':
         horizontal = 0
      else:
         horizontal = 1

      colorListStart = HTMLColorToRGB(colorStart)
      colorListEnd = HTMLColorToRGB(colorEnd)


      colorArray = [[], [], []]

      for i in range(3): # for r g b
         s = colorListStart[i]
         e = colorListEnd[i]
         interpObj = interpolate.OneDimensionalLinear(s, e)
         for val in interpObj.discrete(length, 0):
            colorArray[i].append(int(val))

      gridData = []
      for row in range(length):
         rowData = []
         code = RGBToHTMLColor((colorArray[0][row], 
                                colorArray[1][row], colorArray[2][row]))
         for col in range(width):
            rowData.append(code)
         gridData.append(rowData)

      GridGraphic.__init__(self, gridData, 'hex', None)
      if not horizontal: # rotate 90 degrees    
         self.rotate(90)


#-----------------------------------------------------------------||||||||||||--
class FontText:
   """used for creating text chunk images for web pages"""
   def __init__(self, text=None, fontInfo=None, size=None, 
                bg="#000000", fg="#ffffff", xMult=1, yMult=1, 
                anti=0):
      if text == None:
         text = string.ascii_letters
      if fontInfo == None: # default font
         fontInfo = ('helvetica', 'R', 12)
      if FONTPATH == None:
         font = ImageFont.load_default() # get basic
      else:
         font = ImageFont.load(getFontPath(fontInfo))

      # should be based on size of text
      if size == None: # area of background
         width = len(text) * fontInfo[2]
         height= fontInfo[2] * 2
      else:
         width, height = size

      if bg == None: # assume transparent
         bg = '#000000'
         self.transp = 0 # trans color is black?
      else:
         self.transp = None # no transparency

      bg = HTMLColorToRGB(bg)
      fg = HTMLColorToRGB(fg)

      self.im = Image.new("RGB", (width, height), bg)
      draw = ImageDraw.Draw(self.im)
      draw.text((0, 0), text, font=font, fill=fg)

      #im = im.resize((width, height), Image.ANTIALIAS)
      if anti:
         self.im = self.im.resize((width*xMult, height*yMult), Image.ANTIALIAS)
      else:
         self.im = self.im.resize((width*xMult, height*yMult))

      #self.im.show()

   def write(self, filePath, openMedia=1):
      "Saves the Image; .png and .jpg both work; format based on extensions"
      self.filePath = filePath
      if self.transp != None:
         self.im.save(self.filePath, transparency=self.transp)
      else: #determine format from name
         self.im.save(self.filePath)


#-----------------------------------------------------------------||||||||||||--
class WebText:
   """writes a text image with special styles and mods on a transparent bkg"""
   def __init__(self, text, fg, style):
      if style in ['miniLargeA', 'macro']:
         self.image = GridText(text, fg, None, 'macro', 4, None, 1, 1)

         #text = text.upper() # make all caps
         #newText = []
         #for char in text:
         #   newText.append('%s ' % char)
         #fontInfo = ('helvetica', 'R', 8)
         #Text.__init__(self, ''.join(newText), fontInfo, None, None, fg, 2, 1)

      elif style in ['miniSmallA', 'micro']:
         wrapW = 60 # chars
         self.image = GridText(text, fg, None, 'micro', 2, None, 1, 1, 1, wrapW)

         #fontInfo = ('helvetica', 'R', 8)
         #Text.__init__(self, text, fontInfo, None, None, fg, 1, 1)

      elif style in ['poster']:
         wrapW = 60 # chars
         self.image = GridText(text, fg, None, 'poster', 2, None, 
                               1, 1, 1, wrapW)
      elif style in ['strong']:
         wrapW = 60 # chars
         self.image = GridText(text, fg, None, 'strong', 2, None, 
                               1, 1, 1, wrapW)
      elif style in ['strongDouble']:
         wrapW = 60 # chars
         self.image = GridText(text, fg, None, 'strong', 2, None, 
                               2, 2, 1, wrapW)
      elif style in ['capitalSingle', 'capital']:
         wrapW = 60 # chars
         self.image = GridText(text, fg, None, 'capital', 2, None, 
                               1, 1, 1, wrapW)
      elif style in ['capitalDouble']:
         wrapW = 60 # chars
         self.image = GridText(text, fg, None, 'capital', 2, None, 
                               2, 2, 1, wrapW)
      elif style in ['capitalQuad']:
         wrapW = 60 # chars
         self.image = GridText(text, fg, None, 'capital', 2, None, 
                               4, 4, 1, wrapW)

      else:
         raise KeyError, 'no such style name %r' % style

   def write(self, filePath):
      self.image.write(filePath)





#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
# note: these objects use PIL and TK, and only use pil

class _CanvasBase:
   """abstract convas object to allow for drawing in both PIL and tk
   all colors are in html style hex"""

   def __init__(self, w, h, bg, fmt='png', name='image', transp=None):
      self.w = w
      self.h = h
      self.bg = bg
      self.fmt = fmt
      self.name = name
      self.transp = transp # transparancy only works w/ pil

   def _gridCenter(self, point):
      """to find the center of a bitMap dimensions an off by 1 error
      occurs in the standard case. a 1 shift must be applied"""
      return int(round((point * .5))) - 1 

   def _widthRound(self, width):
      """vector can hadle floating point widths
      here, round; if 0 or None, keep"""
      if width == 0 or width == None:
         return width
      width = int(round(width)) # vector may have fractional widths
      if width == 0: # .2, rounded down, for example
         width = 1
      return width 

   def gridText(self, x, y, anchor, msg, font='micro', fill='#ffffff', 
          kernBit=1, vertBit=None, xMult=1, yMult=1, 
          horizontal=1, charWrapWidth=None):
      """create bit map font; same method for Tk and PIL, as these use
      the bitmap method
      a postscript version of this actually uses fonts
      anchor can be: nw, ne, nc; sw, se, sc
      """
      kernEqual = 0 # treat as equal spaced?
      vertBit = None # use default
      obj = fontLibrary.FontBitMap(msg, font, kernEqual, kernBit, vertBit, 
            charWrapWidth)
      gridData = obj.encode()
      xLen = len(gridData[0]) # get first row
      yLen = len(gridData)
      if anchor == 'nw': # default for most applications: uppr left
         xShift = 0
         yShift = 0
      elif anchor == 'ne': #
         xShift = -xLen
         yShift = 0
      elif anchor == 'nc': #
         xShift = -self._gridCenter(xLen)
         yShift = 0
         
      # vertical centering
      elif anchor == 'cw': #
         xShift = 0
         yShift = -self._gridCenter(yLen)
      elif anchor == 'ce': #
         xShift = -xLen
         yShift = -self._gridCenter(yLen)
      elif anchor == 'cc': #
         xShift = -self._gridCenter(xLen)
         yShift = -self._gridCenter(yLen)

      elif anchor == 'sw':
         xShift = 0
         yShift = -yLen + 1 # neg shift (reduce nw anchor by vert length)
      elif anchor == 'se':
         xShift = -xLen
         yShift = -yLen + 1
      elif anchor == 'sc':
         xShift = -self._gridCenter(xLen)
         yShift = -yLen + 1
      # move bitmap points (they assume a 'nw' origin'
      # with shift values calculated based on anchor points
      self.bitmap(x+xShift, y+yShift, gridData, fill) # bitmap method in child

   def rotate(self, degrees=90):
      pass # only PIL
   
   def write(self, filePath=None, openMedia=1):
      pass # PIL, PostScript

   def destroy(self):
      pass # only TK


#-----------------------------------------------------------------||||||||||||--
class TkCanvas(_CanvasBase):

   def __init__(self, w, h, bg, name, master):
      if not TK:
         raise ImportError, 'Tkinter not loaded'
      _CanvasBase.__init__(self, w, h, bg, 'tk', name)
      if master == None:
         self.master = Tkinter.Tk()
      else:
         self.master = master
      # not sure this is needed
      #self.top = Tkinter.Toplevel() # create a top level?
      self.frame = Tkinter.Frame(self.master, width=self.w, height=self.h, bd=0,
                            bg=self.bg, relief='flat')
      self.frame.pack(fill='both', pady=0, padx=0)
      # create base canvas
      self.c = Tkinter.Canvas(self.frame, bg=self.bg, bd=0, width=self.w, 
               height=self.h, highlightthickness=0, 
               closeenough=1, confine=1)
      self.c.pack(fill='both', pady=0, padx=0) 

   def rectangle(self, x1, y1, x2, y2, fill, outline, width=1):
      width = self._widthRound(width) # vector may have fractional widths
      # attempt to fix off by one error
      # this creates rectangles that better match PIL model
      self.c.create_rectangle(x1,y1,x2+1,y2+1, fill=fill, outline=outline, 
                              width=width)

   def line(self, x1, y1, x2, y2, fill, width=1):
      width = self._widthRound(width) # vector may have fractional widths
      self.c.create_line(x1, y1, x2, y2, fill=fill, width=width)

   def fontText(self, x, y, anchor, text, font, fill):
      """anchors supirted: 'nw', 'sw', 'ne', 'se'
      default is nw (upper left corner)
      """
      self.c.create_text(x, y, text, font=font, fill=fill, anchor=anchor)

   def oval(self, x1, y1, x2, y2, fill, outline, width=1):
      width = self._widthRound(width) # vector may have fractional widths
      self.c.create_oval(x1, y1, x2, y2, fill=fill, outline=outline, 
                         width=width)

   def bitmap(self, xShift, yShift, gridData, color=None):
      """if color==None, use data value in grid data as color value
      note: drawing a pixel image is not uniform b/n tk implementations
      on win nothing resuls from equal points: must add a single point"""

      # get constant shift values
      if os.name == 'mac': xBuf = 0
      elif os.name == 'posix': xBuf = 0
      else: xBuf = 1 # win tk requires extra pixel 

      x = 0
      y = 0
      for row in gridData:
         for col in row:
            if color != None:
               if col == 0: # nothing here, and not a color value
                  colorUse = None # do not draw
               else: # if 1, binary but mapp
                  colorUse = color
            elif color == None: # color not given, use values
               colorUse = col
            if colorUse != None:
               self.c.create_line((xShift+x, yShift+y, xShift+x+xBuf, yShift+y),
                                fill=colorUse, width=1, capstyle="round")
               # capstyle may not be the same on differnt tks, must specify
            x = x + 1
         x = 0
         y = y + 1

   def show(self, dir=None, prefDict=None):
      """dir and prefDict do nothing here"""
      # may need to pack again?
      #frame.pack(expand=1, fill='both', pady=0, padx=0)
      self.master.title(self.name)
      self.master.iconname(self.name)
      geometryStr = "%dx%d%+d%+d" % (self.w, self.h, 100, 100) # upLeft offset
      self.master.geometry(geometryStr)
      self.master.maxsize(width=self.w, height=self.h)
      self.master.minsize(width=self.w, height=self.h)
      self.master.resizable(width=0, height=0) # prohibit resizing
      # not calling these allows the window to be detached 
      #self.master.mainloop()
      # this should freeze the window
      #self.master.wait_window(self.c)

   def destroy(self):
      # might want to use self.frame instead?
      self.master.withdraw()
      self.master.destroy()
      del self.master


#-----------------------------------------------------------------||||||||||||--
class PilCanvas(_CanvasBase):
   def __init__(self, w, h, bg, name, transp, subFmt):
      _CanvasBase.__init__(self, w, h, bg, 'png', name, transp)
      self.subFmt = subFmt # may be 'jpg' or 'png'
      self.imageObj = Image.new("RGB", (self.w, self.h))
      self.c = ImageDraw.Draw(self.imageObj)
      # add background color if not None
      if self.bg == None or transp != None: pass
      else:
         self.c.rectangle((0,0,self.w, self.h), self.bg, outline=None)

   def rectangle(self, x1, y1, x2, y2, fill, outline, width=1):
      width = self._widthRound(width) # vector may have fractional widths
      self.c.rectangle((x1, y1, x2, y2), fill=fill, outline=outline)
      if outline == None:
         width = 0
      if width > 1: # draw rectangles inside to create width on outline
         for i in range(1, width): # one pixel is laready drawin
            self.c.rectangle((x1+i, y1+i, x2-i, y2-i), 
                            fill=None, outline=outline)

   def line(self, x1, y1, x2, y2, fill, width=1):
      """lines in pil do not have a widht attribute, which forces
      drawing multiple lines to simulate width"""
      width = self._widthRound(width) # vector may have fractional widths
      if width == 1:
         self.c.line((x1, y1, x2, y2), fill=fill)
      elif width > 1: # even is drawn + 1 (down)
         #for i in range(1, width):
         for i in range(1, width):
            if x1 == x2: # its a vertical line
               self.c.line((x1+i, y1, x2+i, y2), fill=fill)
            elif y1 == y2: # its a vertical line
               self.c.line((x1, y1+i, x2, y2+i), fill=fill)
            else: # diagonal: draw a rectangle? this doesnt work well
               if i % 2 == 0: # of even
                  self.c.line((x1+i, y1, x2+i, y2), fill=fill)
               else: # odd
                  self.c.line((x1, y1+i, x2, y2+i), fill=fill)

   def fontText(self, x, y, anchor, text, font, fill):
      # default anchord position is 'ne' # position is upper right
      fontObj = ImageFont.load_default() # gets default font
      self.c.text((x, y), text, fill=fill, font=fontObj)

   def oval(self, x1, y1, x2, y2, fill, outline, width=1):
      width = self._widthRound(width) # vector may have fractional widths
      self.c.ellipse((x1, y1, x2, y2), fill=fill, outline=outline)
      if outline == None:
         width = 0
      if width > 1:
         for i in range(1, width):
            self.c.ellipse((x1+i, y1+i, x2-i, y2-i), 
                            fill=None, outline=outline)

   def bitmap(self, xShift, yShift, gridData, color=None):
      """if color==None, use data value in grid data as color value"""
      x = 0
      y = 0
      for row in gridData:
         for col in row:
            if color != None:
               if col == 0: # nothing here, and not a color value
                  colorUse = None # do not draw
               else: # if 1, binary but mapp
                  colorUse = color
            elif color == None: # color not given, use values
               colorUse = col
            if colorUse != None:
               self.c.point((xShift+x, yShift+y), fill=colorUse)
            x = x + 1
         x = 0
         y = y + 1

   def rotate(self, angle=90):
      self.imageObj = self.imageObj.rotate(angle)
   
   def write(self, filePath=None, openMedia=1, prefDict=None):
      """transparency is stored at init"""
      #del self.c # not sure if this is necessary
      if filePath == None:
         ext = '.%s' % self.subFmt
         filePath = osTools.tempFile(ext) # jpg/png       
      if self.transp != None: # transparency not working
         self.imageObj.save(filePath, transparency=self.transp)
      else: #determine format from name
         self.imageObj.save(filePath)
      if openMedia:
         osTools.openMedia(filePath, prefDict) #open for user
      self.filePath = filePath

   def show(self, dir=None, prefDict=None):
      """dir provides a directory to write a file w/ auto-gen name"""
      if dir != None: # get path
         ext = '.%s' % self.subFmt
         filePath = os.path.join(dir, osTools.tempFileName(ext))
      else:
         ext = '.%s' % self.subFmt
         filePath = osTools.tempFile(ext) # jpg/png       
      self.write(filePath, 1, prefDict)
      #if self.subFmt == 'jpg': # gets defaul pil format, which is jpeg
      #   self.imageObj.show() # used to use show, but sometimes fails
      #elif self.subFmt == 'png': # write a file
      #   self.write()


#-----------------------------------------------------------------||||||||||||--
class EpsCanvas(_CanvasBase):
   """write a postscript vector graphics file"""
   def __init__(self, w, h, bg=None, name='untitled'):
      # increase size of w/h by to handle ob1 error
      _CanvasBase.__init__(self, w, h, bg, 'eps', name)
      self.title = name
      self.creator = 'athenaCL'
      self.forStr = 'athenaCL'
      self.date = time.asctime(time.localtime())
      self.defIndex = 0
      # canvas as a list of strings; append 
      self.c = []
      self.c.append(self._headStr())
      # add background color if not None
      if self.bg == None: pass
      else: # will append to c
         self.rectangle(0,0,self.w, self.h, self.bg, None, 0)
         
   def _headStr(self):
      """the head string for an eps/ps file is very sensitive
      changing this can result in cropping or other strangness
      do not define page size or anything else"""
      msg = []
      #msg.append('%!PS-Adobe-2.0\n')
      msg.append('%!PS-Adobe-3.1 EPSF-3.0\n') # for eps
      # % image or page area: lower left x y: upper right x y % 
      msg.append('%%BoundingBox: ')
      # as LLx, LLy, URx, URy
      msg.append(" %s %s %s %s " % (0, 0, self.w, self.h))
      msg.append("""
%%Title: %s 
%%Creator: %s  
%%For: %s
%%CreationDate: %s 
%%EndComments
/defaults save def
""" % (self.title, self.creator, self.forStr, self.date))
      return ''.join(msg)
  
   def _tailStr(self):
      """showpage actual prints ont pages
      returns a string: does not append to c"""
      msg = []
      msg.append("""
%%EndPage 
%%Trailer
grestore showpage defaults restore
%%EOF""")
      return ''.join(msg)
  
   #------------------------------------------------------------------------||--
   # conversion utilities
   def _convertCoordSquare(self, x1, y1, x2, y2):
      """convert xy pairs to four points necessary for drawing
      assume xy pars are upper left, lower right
      but all measurements from ll, ur
      
      NOTE: there is an error in coordinates from lower left to upper right
      the sometimes are reversed (rotated 180 degrees)
      """      
      #assert y1 <= self.h
      #assert y2 <= self.h
      if y1 >= y2:
         yMin = y2
         yMax = y1
      else:
         yMin = y1
         yMax = y2
      # this was recently added; may cause errors...         
      if x1 >= x2:
         xMin = x2
         xMax = x1
      else:
         xMin = x1
         xMax = x2
         
      ll = (xMin, self.h - yMax) # lower left
      ul = (xMin, self.h - yMin) # upper left
      lr = (xMax, self.h - yMax) # lower right
      ur = (xMax, self.h - yMin) # upper right
      # return points measured from bottom
      return ll, ul, ur, lr

   def _convertCoordLine(self, x1, y1, x2, y2):
      # using square conversion causes errors when points are specified
      # from lower left to upper right; need to just return the appropriate
      # sides
      ll, ul, ur, lr = self._convertCoordSquare(x1, y1, x2, y2)
      if y1 > y2 and x1 < x2: # lower left to upper right
         return ll, ur
      elif y1 > y2 and x1 > x2: # lower right to upper left
         return lr, ul
      elif y1 <= y2 and x1 >= x2: # upper right to lower left
         return ur, ll 
      else: # standard case: upper left to lower right
         return ul, lr

   def _convertCoordArc(self, x1, y1, x2, y2):
      # find center point: add smaller size, plus half point
      x = x1 + ((x2 - x1) * .5)
      y = y1 + ((y2 - y1) * .5)
      # readius is simply mid-point: assume this is a circle
      radius = (x2 - x1) * .5 # assume its a circle
      # all points are the same
      ll, ul, ur, lr = self._convertCoordSquare(x, y, x, y)
      return ll[0], ll[1], radius
      
   def _getDefName(self):
      # get object def name
      strDef = 'obj%s' % self.defIndex     
      self.defIndex = self.defIndex + 1 # increment def index
      return strDef
         
   #------------------------------------------------------------------------||-- 
   
   def rectangle(self, x1, y1, x2, y2, fill=None, 
                 outline=None, width=1):
      """closepath: enclose shape, which can be filled
      grey's vary from 0 to 1 
      set color w/ "setrgbcolor
      gsave and grestone necessary to wrap all definitions from 
         global namespace
         
      rectangles, to correspond to bit-map coordds, will add 1 to x and y 
      coords...
      """
      # if x or y are equal, draw a line instead of a rectangle
      # give the line a 1 point width
#       if x1 == x2 and y1 == y2:
#          self.line(x1, y1, x2, y2, fill, 1)
#          return None
#       elif x1 == x2 or y1 == y2:
#          self.line(x1, y1, x2, y2, fill, 1)      
#          return None
         
      ll, ul, ur, lr = self._convertCoordSquare(x1, y1, x2+1, y2+1) 
      # convert coords                        
      if fill == None: strFill = ''
      else:
         r, g, b = HTMLColorToUnitRGB(fill)
         strFill = '%s %s %s setrgbcolor fill' % (r, g, b) 
      if outline == None: strOutline = ''
      else:
         r, g, b = HTMLColorToUnitRGB(outline)
         strOutline = '%s setlinewidth %s %s %s setrgbcolor stroke' % (width,
                        r, g, b)
      strDefName = self._getDefName() # get a unique def identifier      
      msg = []
      msg.append("""
/%s { newpath %s %s moveto %s %s lineto %s %s lineto %s %s lineto closepath gsave %s grestore gsave %s grestore} def """ % (strDefName, 
      ll[0], ll[1], ul[0], ul[1], ur[0], ur[1], lr[0], lr[1], 
      strFill, strOutline))
      msg.append("""
gsave %s 1 1 scale grestore
""" % strDefName)
      self.c.append(''.join(msg))

   def line(self, x1, y1, x2, y2, fill, width=.5):
      # there may be an ob1 error as in rectangle here; but cant fix
      # as would make straight lines crooked...
      pairL, pairR = self._convertCoordLine(x1, y1, x2, y2) # convert coords                        
      if fill == None: fill = ''
      else:
         r, g, b = HTMLColorToUnitRGB(fill)
         # linecap 2 sets projecting caps, extending to width
         # this does not yet seem to work
         strFill = '%s setlinewidth 2 setlinecap %s %s %s setrgbcolor stroke'%( 
            width, r, g, b)
      strDefName = self._getDefName() # get a unique def identifier      
      msg = []
      msg.append("""
/%s { newpath %s %s moveto %s %s lineto closepath gsave %s grestore} def 
""" % (strDefName, 
      pairL[0], pairL[1], pairR[0], pairR[1], 
      strFill))
      msg.append("""
gsave %s 1 1 scale grestore
""" % strDefName)
      self.c.append(''.join(msg))


   def _charFilter(self, usrStr):
      """replace characters in a usrStr w/ necssary escape chars"""
      msg = []
      for char in usrStr:
         if char in ['(', ')',]: # not sure if back-slash should be here
            msg.append('\%s' % char) # escape
         else:
            msg.append(char)
      return ''.join(msg) 

   def fontText(self, x, y, anchor='nw', text='test', font=None,
                fill="#342396"):
      """vector font text defines 6 acnhors:
      nw, ne, nc; sw, se, sc"""
      # fontInfo = ('helvetica', 'R', 12)
      # for postscript, default anchor is 'sw'; need to translate

      # thought these were standard ps fonts: these do not seem to work for all
      # times-(roman/bold/italic/bolditalic)
      # helvetica-(bold/oblique/boldoblique)
      # courier-(bold/oblique/boldoblique)
      # symbol
      text = self._charFilter(text) # escape necessary characters      
      ll, ul, ur, lr = self._convertCoordSquare(x, y, x, y) # convert coords                        

      if font == None:
         fontFace = 'Times-Roman' 
         fontSize = '12' 
      else:
         # note: hyphenated font names do not work w/ eps
         # it may be that the style was not in the right case
         #if font[1] != None:
         #   fontFace = '%s-%s' % (font[0], font[1])
         #else:
         fontFace = '%s' % font[0]
         fontSize = str(font[2])
         
      # find the fontHeight, not the same as fontSize
      # this is found experimentally w/ courier
      # want upper left corner of font
      fontHeight = round((int(fontSize) * .55), 1)
      # this adjustment should be added to vertical positon 
      # (shifted upward) to account for strange spacing, on
      x = ll[0]   
      y = ll[1]
      
      if fill == None:
         strFill = ''
      else:
         r, g, b = HTMLColorToUnitRGB(fill)
         strFill = '%s %s %s setrgbcolor' % (r, g, b)
      if anchor == 'sw': # default is lower left
         strMove = '%s %s moveto' % (x, y)
      elif anchor == 'se': # pop removes y coord
         strMove = '%s (%s) stringwidth pop sub %s moveto' % (
                              x, text, y)
      elif anchor == 'sc': # pop removes y coord
         strMove = '%s (%s) stringwidth pop 2 div sub %s moveto' % (
                              x, text, y)
      elif anchor == 'cw': 
         strMove = '%s %s %s 2 div sub moveto' % (
                              x, y, fontHeight)  
      elif anchor == 'ce': # pop removes y coord
         strMove = '%s (%s) stringwidth pop sub %s %s 2 div sub moveto' % (
                              x, text, y, fontHeight)
      elif anchor == 'cc': # pop removes y coord
         strMove = '%s (%s) stringwidth pop 2 div sub %s 2 div moveto' % (
                              x, text, fontHeight)                              
      elif anchor == 'nw': 
         strMove = '%s %s %s sub moveto' % (x, y, fontHeight)
      # first put x coord on stack, then measure, remove y, then subtract
      elif anchor == 'ne': # pop removes y coord
         strMove = '%s (%s) stringwidth pop sub %s %s sub moveto' % (
                              x, text, y, fontHeight)
      elif anchor == 'nc': 
         strMove = '%s (%s) stringwidth pop 2 div sub %s %s sub moveto' % (
                              x, text, y, fontHeight)                             
      msg = []
      msg.append("""
/%s findfont 
%s scalefont setfont 
newpath 
%s
%s 
(%s) show
""" % (fontFace, fontSize, strMove, strFill, text))
      self.c.append(''.join(msg))
      
   def oval(self, x1, y1, x2, y2, fill, outline, width=1):
      xCenter, yCenter, radius = self._convertCoordArc(x1, y1, x2, y2)
      if fill == None: strFill = ''
      else:
         r, g, b = HTMLColorToUnitRGB(fill)
         strFill = '%s %s %s setrgbcolor fill' % (r, g, b) 
      if outline == None: strOutline = ''
      else:
         r, g, b = HTMLColorToUnitRGB(outline)
         strOutline = '%s setlinewidth %s %s %s setrgbcolor stroke' % (width,
                        r, g, b)
      strDefName = self._getDefName() # get a unique def identifier      
      msg = []
      msg.append("""
/%s { newpath %s %s %s %s %s arc closepath gsave %s grestore gsave %s grestore} def """ % (
      strDefName, 
      xCenter, yCenter, radius, 0, 360,
      strFill, strOutline))
      msg.append("""
gsave %s 1 1 scale grestore
""" % strDefName)
      self.c.append(''.join(msg))

   #------------------------------------------------------------------------||--   
   def gridText(self, x, y, anchor='nw', msg='test', font='micro', 
            fill='#ffffff', kernBit=1,
            vertBit=None, xMult=1, yMult=1, horizontal=1, charWrapWidth=None):
      """over-ride this method to provide font support wihtout using 
      bitmap; get anchor from normal gridText calls"""      
      # there will be a size that fits similar to a real gridText
      if font == 'micro':
         # keep case
         fontSize = 7 * yMult
         fontInfo = ('Courier', 'bold', fontSize)
      elif font == 'macro': # make caps
         msg = msg.upper()
         fontSize = 14 * yMult
         fontInfo = ('Courier', 'bold', fontSize)
      # will append to self.c
      self.fontText(x, y, anchor, msg, fontInfo, fill)


#    alternative method; but does not work properly 
#    def bitmap(self, xShift, yShift, gridData, color=None):
#       """if color==None, use data value in grid data as color value
#       """
#       pairL, pairR = self._convertCoordLine(xShift, yShift, xShift, yShift) 
#       pixSize = 1
#       strDefName = self._getDefName() # get a unique def identifier      
#       msg = []
#       msg.append("""
# /%s %s %s translate %s %s scale { 1 1 1 [1 0 0 1 0 0] {<c936>} image } def 
# """ % (strDefName, pairL[0], pairL[1], pixSize, pixSize)
#       )
#       msg.append("""
# gsave %s 1 1 scale grestore
# """ % strDefName)
#       self.c.append(''.join(msg))

   def bitmap(self, xShift, yShift, gridData, color=None):
      """this method draws rectangle for every bit; this works but is
      very slow"""
      x = 0
      y = 0
      for row in gridData:
         for col in row:
            if color != None:
               if col == 0: # nothing here, and not a color value
                  colorUse = None # do not draw
               else: # if 1, binary but mapp
                  colorUse = color
            elif color == None: # color not given, use values
               colorUse = col
            if colorUse != None:
               self.rectangle(xShift+x, yShift+y, xShift+x, yShift+y,
                               colorUse, None, 0)
               # capstyle may not be the same on differnt tks, must specify
            x = x + 1
         x = 0
         y = y + 1
      
   #------------------------------------------------------------------------||--   
   
   def write(self, filePath=None, openMedia=1, prefDict=None):
      if filePath == None:
         filePath = osTools.tempFile('.eps')            
      f = open(filePath, 'w')
      f.writelines(''.join(self.c) + self._tailStr())
      f.close
      if openMedia:
         osTools.openMedia(filePath, prefDict)         
      self.filePath = filePath

   def show(self, dir=None, prefDict=None):
      """show action is to write file"""
      if dir != None: # get path
         ext = '.eps'
         filePath = os.path.join(dir, osTools.tempFileName(ext))
      else:
         filePath = osTools.tempFile('.eps') # jpg/png    
      self.write(filePath, 1, prefDict)


#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
# factory returns an object
def Canvas(fmt, w, h, bg='#000000', name='image', master=None, transp=None):
   # this needs to be changed: jpg, png 
   if fmt in ['jpg','png']:
      obj = PilCanvas(w, h, bg, name, transp, fmt)
   elif fmt == 'tk':
      obj = TkCanvas(w, h, bg, name, master)
   elif fmt == 'eps':
      obj = EpsCanvas(w, h, bg, name)
   else:
      raise ValueError, 'bad canvas format type: %s' % fmt
   return obj









#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--
class ProcessGraphCoordData:
   """process data into a bit map data format
   assumes that data is set of numerical coordinants
   designed to process multiple streams of data, w/ same rez
   finds all real-world values w/n an integer space"""

   def __init__(self, dataLib=None, xRez=100, yRez=100, 
                      dataSigDig=(2,2), axisSigDig=(0,0),):
      """dataLib is a dictionary of (x,y) coordinants
      key is symbol to be used, value is a list of coords
      max and min are calculated based on data
      dataLib may contain four coords: (x,y,x,y) for rectangular segments
      """
      self.xRez = xRez
      self.yRez = yRez
      self.dataSigDig = dataSigDig
      self.axisSigDig = axisSigDig
      if dataLib != None:
         self.dataLib = dataLib
      else: # generate a random dataLib
         self.dataLib = {}
         self.dataLib[1] = []
         for x in range(1, 101): # fill w/ random values
            #self.dataLib[1].append((x, random.random()))
            self.dataLib[1].append((x, x, x+3, x))
      # check if these are 4-point coords
      if len(self.dataLib[1][0]) == 4:
         self.dataMode = 'line' # two coords
      else:
         self.dataMode = 'point' # one coord

   def _findRange(self):
      """find min, max x values
      check the data for anything that we cannot process
      like strings...
      """
      self.xRangeLib = {}
      self.yRangeLib = {}
      for key in self.dataLib.keys():
         xMax = self.dataLib[key][0][0] # seed w/ values from data
         xMin = self.dataLib[key][0][0]
         yMax = self.dataLib[key][0][1]
         yMin = self.dataLib[key][0][1]         
         if self.dataMode == 'point':
            allPointList = self.dataLib[key]
         elif self.dataMode == 'line':
            allPointList = []
            for x, y, p, q in self.dataLib[key]:
               allPointList.append((x,y)) # stack all pairs of coords
               allPointList.append((p,q))
         else:
            raise ValueError
         for x, y in allPointList:
            if x >= xMax: xMax = x
            if x <= xMin: xMin = x
            if y >= yMax: yMax = y
            if y <= yMin: yMin = y
         # special case for constant values
         if xMax == xMin: # if the same
            xMax = xMax + 1
            xMin = xMin - 1
         if yMax == yMin: # if the same
            yMax = yMax + 1
            yMin = yMin - 1
         self.xRangeLib[key] = (xMin, xMax)
         self.yRangeLib[key] = (yMin, yMax)              

   def _genAxis(self, min, max, steps, side):
      """reverses y axis, so returns from largest to smallest
      seems to be bug here, as max has to be manually added; is not
      reached through increment addition
      float axis values must have higher specificity than sigDig"""
      axis = []
      if side == 'x':
         sigDig = self.axisSigDig[0]
      elif side == 'y':
         sigDig = self.axisSigDig[1]
      
      if sigDig != 0:
         if drawer.isInt(min): rMin = min
         else: rMin = round(min, sigDig)
         if drawer.isInt(max): rMax = max         
         else: rMax = round(max, sigDig)
      else: # make into an int
         if not drawer.isInt(min):
            rMin = int(round(min, sigDig))
         else: rMin = min
         if not drawer.isInt(max):
            rMax = int(round(max, sigDig))
         else: rMax = max

      incRaw = float(abs(rMax - rMin)) / (steps)
      if sigDig != 0:
         inc = round(incRaw, sigDig+4) # add extra resolution
      else:
         inc = int(round(incRaw, sigDig))

      axis.append(rMin) # add first
      i = copy.copy(rMin)
      for x in range(1, steps-1): # first already added, last to be added
         i = i + inc
         axis.append(i)
      axis.append(rMax) # add first

      if side == 'y': # y should go from higher to lower
         axis.reverse()
      #print _MOD, min, max, rMin, rMax, axis, len(axis)
      return axis

   def _findAxis(self):
      self.xAxisLib = {}
      self.yAxisLib = {}
      for key in self.dataLib.keys():
         self.xAxisLib[key] = self._genAxis(self.xRangeLib[key][0], 
                              self.xRangeLib[key][1], self.xRez, 'x')
         self.yAxisLib[key] = self._genAxis(self.yRangeLib[key][0], 
                              self.yRangeLib[key][1], self.yRez, 'y')

   def _findIndexInAxis(self, axis, value, side):
      """given an axis, value, and side, determine which point
      on the axis the value should be assigned to
      axis are already rounded"""
      # round value w/ data from sigDig local attribute
      # for sig dig, x is 0, y is 1
      if side == 'x':
         value = round(value, self.dataSigDig[0])
      elif side == 'y':
         value = round(value, self.dataSigDig[1])
      else:
         raise ValueError

      for i in range(0, len(axis)):
         if side == 'x':
            if value == axis[i]:
               return i
            elif i >= len(axis) - 1: # the last point of axis
               return i
            elif value > axis[i] and value < axis[i+1]:
               lower = abs(value - axis[i])
               upper = abs(value - axis[i+1])
               if lower < upper: # closer to lower:
                  return i
               elif upper < lower: # closer to upper
                  return i + 1
               elif upper == lower: # equal?
                  return i # round to lower?
               else: print _MOD, 'x error'
         elif side == 'y': # greater to lesser values
            if value == axis[i]:
               return i
            elif i >= len(axis) - 1: # the last point of axis
               return i
            elif value < axis[i] and value > axis[i+1]:
               lower = abs(value - axis[i])
               upper = abs(value - axis[i+1])
               if lower < upper: # closer to lower:
                  return i
               elif upper < lower: # closer to upper
                  return i + 1
               elif upper == lower: # equal?
                  return i # round to lower?
               else: print _MOD, 'y error'

   def _findPlot(self):
      """create a bit map based on resolution
      this used to store a double array of e entire but map
      now this converts the floating point values into integer values
      within the defined resultion
      these values are stored in self.plotLib; provide additional
      method to get values out in old format"""
      self.plotLib = [] # used to be a dictionary
      # each key represents a color/symbol/fill-pattern group
      for key in self.dataLib.keys():
         for data in self.dataLib[key]:
            if self.dataMode == 'point':
               x, y = data
               # determine which axis position this point is closer to?
               xPos = self._findIndexInAxis(self.xAxisLib[key], x, 'x')
               yPos = self._findIndexInAxis(self.yAxisLib[key], y, 'y')
               #self.plotLib[key][yPos][xPos] = key # key is symbol in this pos
               self.plotLib.append((key, xPos, yPos))
               #print _MOD, x, y, xSig, ySig, xPos, yPos
            elif self.dataMode == 'line':
               x1, y1, x2, y2 = data
               x1Pos = self._findIndexInAxis(self.xAxisLib[key], x1, 'x')
               y1Pos = self._findIndexInAxis(self.yAxisLib[key], y1, 'y')
               x2Pos = self._findIndexInAxis(self.xAxisLib[key], x2, 'x')
               y2Pos = self._findIndexInAxis(self.yAxisLib[key], y2, 'y')
               #self.plotLib[key][yPos][xPos] = key # key is symbol in this pos
               self.plotLib.append((key, x1Pos, y1Pos, x2Pos, y2Pos))
            else:
               raise ValueError
               
   def getBitLib(self):
      """after calling _findPlt aboive, this method will convert self.plotLib
      into an old format bit-map, a list of nested lists, w/ each value in 
      the list being the integer value of the necessary key/fromat/colo 
      indicator
      this may be very inefficient; only use if abs necessary
      """
      self.bitLib = {} # this is like the old plotLib format
      # build empty double array
      for key in self.dataLib.keys():
         self.bitLib[key] = []
         for y in range(self.yRez): # replace w/ array?
            row = array.array('i')
            for x in range(self.xRez):
               row.append(0) # fill with zeros
            self.bitLib[key].append(row)
      # fill w/ key, non-zero values
      for data in self.plotLib: # plotLib is a list now
         if self.dataMode == 'point':
            key, x, y = data
            # y comes before x because of nested lists
            self.bitLib[key][y][x] = key
         elif self.dataMode == 'line': # this is likely not used for line graphs
            # connect points b/n lines
            key, x1, y1, x2, y2 = data
            # just store first point
            self.bitLib[key][y1][x1] = key
         else:
            raise ValueError
      #print self.bitLib
      return self.bitLib
            
   def getPlotLib(self):
      """plotlib is a raw data point value list
      leading values are id symbols (ints0
      than any number of xy pairs"""
      return self.plotLib
   
   def keys(self):
      return self.dataLib.keys()
   
   def gridKey(self, key, side='x', lines=3):
      """returns a grid key for this axis
      lines are number of lines, not number of spaces
      NOT reversed for y axis"""
      if side == 'y':
         axis = self.yAxisLib[key]
      else:
         axis = self.xAxisLib[key]
      gridKey = [0] * len(axis)
      partitions = lines + 1
      step = int(round((len(axis) / float(partitions)), 0))
      pos = 0
      for i in range(0, lines): # always one less
         pos = pos + step
         #print _MOD, pos, i, lines, len(gridKey)
         if pos < len(gridKey):
            gridKey[pos] = 1 # indicate grid line w/ 1
         else:
            pass 
            #print _MOD, 'grid (%s:%s) line out of range' % (side, lines)
      if side == 'y':
         gridKey.reverse() # must reverse y axis
      return gridKey

   def unitKey(self, key, side, gridKey, forceExtreme='min'):
      """uses grid key to get a unit key (label each grid)
      fore range adds first and last values
      axis values are not rounded; need to be for units"""
      if side == 'x':
         sigDig = self.axisSigDig[0]
      elif side == 'y':
         sigDig = self.axisSigDig[1]

      if side == 'y':
         axis = self.yAxisLib[key] # alrady reversed
      else:
         axis = self.xAxisLib[key]
      unitKey = [None] * len(axis)
      for i in range(0, len(axis)):
         if gridKey[i] > 0: # if grid active
            if sigDig != 0:
               unitKey[i] = round(axis[i], sigDig)
            else:
               unitKey[i] = int(round(axis[i], sigDig))
      if side == 'x':
         if forceExtreme in ['min', 'range']:
            if sigDig != 0:
               unitKey[0] = round(axis[0], sigDig)
            else:
               unitKey[0] = int(round(axis[0], sigDig))
         if forceExtreme in ['max','range']:
            j = len(axis) - 1 # last value
            if sigDig != 0:
               unitKey[j] = round(axis[j], sigDig)
            else:
               unitKey[j] = int(round(axis[j], sigDig))
      if side == 'y':
         if forceExtreme in ['min', 'range']:
            j = len(axis) - 1 # last value
            if sigDig != 0:
               unitKey[j] = round(axis[j], sigDig)
            else:
               unitKey[j] = int(round(axis[j], sigDig))
         if forceExtreme in ['max','range']:
            if sigDig != 0:
               unitKey[0] = round(axis[0], sigDig)
            else:
               unitKey[0] = int(round(axis[0], sigDig))
      return unitKey

   def __call__(self):
      self._findRange()
      self._findAxis()
      self._findPlot()
      #self.display()






#-----------------------------------------------------------------||||||||||||--
class _GraphBase:
   def __init__(self, xRez, yRez, plotLib, origin=(0,0)):
      """canvas is only provided through update method
      only call update once after setting parameters
      xRez and yRez are integer resolution sizes (w/h of a bit map)
      plotLib is a list of points, each a list: key, x, y, x, y
      """
      # self.bitData
      self.plotLib = plotLib
      self.xRez = xRez
      self.yRez = yRez
      
      self.xOrigin, self.yOrigin = origin
      self.master = None
      # cells must be an odd number (so that there is a center
      self.wIcon = 9
      self.hIcon = 9
      # widht of horizontal and vertical margins
      # applied to one side
      self.wIconGutter = 18
      self.hIconGutter = 18

      self.lMargin = 40 # left
      self.rMargin = 2   # right
      self.tMargin = 2   # top   
      self.bMargin = 40  # bottom

      #grid binary key for each angle: determines if grid line is drawn
      # may want to add other symbols for variation
      self.rowGridKey = [0,1,1]
      self.colGridKey = [0,1,0,1]

      #ticks extend into margin, separate color
      # value determines length
      # NB: problem w/ 1 pixel horizontal ticks
      self.lTickKey = [1,5,2]   # left
      self.rTickKey = [2,1] # right
      self.tTickKey = [2,1,2,0] # top 
      self.bTickKey = [3,1,1,4] # bottom

      # give string for each necessary unit marker
      # None translated to no unit
      self.colUnitKey = ['a', 'b', 'c', None, 3, 4, 5]
      self.rowUnitKey = ['a', 'b', 'c', None, 3, 4, 5]
      self.unitGutter = 2 # pixel spacing from tick mark, or edge if none

      # labels for x y axis
      self.xLabel = 'events'
      self.yLabel = 'db'
      self.labelGutter = 6 # pixels spaciing from unit gutter +tick

      # colors
      self.colorBkg    = '#000000'
      self.colorGrid   = '#000033'
      self.colorTick   = '#CC6600'
      self.colorMargin = '#333333'
      self.colorUnits  = '#333399'
      self.colorLabel  = '#000000'
      self.colorTitle  = '#333333'

      self.bitColorDict = {1 : '#336633',
                           2 : '#003366',
                           3 : '#003366', }
      self.transp = None # transparancy default off, passed to canvas
      # stores methods for drawing icons
      self.bitDict = {}

      self.font = 'micro'
      self.kernEqual = 0
      self.kernBit = 1
      self.title = ''
      self.titlePos = 'bl' # pick a corner, bottom left here
      self.titleGutterVert = 2 # pix from vertical edges
      self.titleGutterHorz = 2 # pix from horiz edges
      # update values
      # self.update(canvas)

   def update(self, canvas='png'):
      """ canvas can be an object or format string
      update all calculated size values
      regenerates Canvas, and this bkg color and self.tranp make a dif
      must be done manually from out side object after loading all necessary
      data formats
      if canvas is == None, does not create a canvas, just calc values"""
      # size of data
      self.graphCol = self.xRez
      self.graphRow = self.yRez
      # width of graph = icon width, and margins plus 1
      self.wGraph = ((self.wIcon * self.graphCol) + 
                     (self.wIconGutter * (self.graphCol + 1)))
      self.hGraph = ((self.hIcon * self.graphRow) + 
                     (self.hIconGutter * (self.graphRow + 1)))
      #print _MOD, self.wGraph, self.hGraph, self.graphCol, self.graphRow
      self.wTotal = self.wGraph + (self.lMargin + self.rMargin)
      self.hTotal = self.hGraph + (self.tMargin + self.bMargin)
      # create canvas if given a string, othewise use existing canvas
      if drawer.isStr(canvas): # its a format string
         self.fmt = canvas
         self.canvas = Canvas(self.fmt, self.wTotal, self.hTotal, 
                       self.colorBkg, 'graph', self.master, self.transp)
      elif canvas != None: # its canvas already
         self.fmt = canvas.fmt
         self.canvas = canvas
      else: # used only to calculate size
         self.canvas = None
      # upper left corner of where graph starts
      self.graphOrigin = (self.xOrigin+self.lMargin, self.yOrigin+self.tMargin)

      if self.canvas != None:
         self.canvas.rectangle(self.xOrigin, self.yOrigin, 
                    self.xOrigin+self.wTotal-1, self.yOrigin+self.hTotal-1,
                    self.colorBkg, None)
      # create dumy char to measure size
      obj = fontLibrary.FontBitMap('a', self.font)
      self.wFont = obj.wFont
      self.hFont = obj.hFont

   def destroy(self):
      """useful for tk windows only"""
      self.canvas.destroy()
      del self.canvas

   #------------------------------------------------------------------------||--
   def setIconSize(self, wIcon, hIcon, wIconGutter, hIconGutter):
      # reinit w/ the format string, not w/ the canvas
      self.wIcon = wIcon
      self.hIcon = hIcon
      self.wIconGutter = wIconGutter
      self.hIconGutter = hIconGutter

   def setMargin(self, lMargin, rMargin, tMargin, bMargin):
      self.lMargin = lMargin
      self.rMargin = rMargin
      self.tMargin = tMargin
      self.bMargin = bMargin

   def setLabel(self, xLabel, yLabel, labelGutter):
      self.xLabel = xLabel
      self.yLabel = yLabel
      self.labelGutter = labelGutter # must include any unit size

   def setGrid(self, rowGridKey, colGridKey, rowUnitKey, colUnitKey):
      self.rowGridKey = rowGridKey
      self.colGridKey = colGridKey
      self.rowUnitKey = rowUnitKey
      self.colUnitKey = colUnitKey

   def setTitle(self, title, titlePos='br', 
                titleGutterVert=2, titleGutterHorz=None):
      self.title = title
      self.titlePos = titlePos
      self.titleGutterVert = titleGutterVert
      if titleGutterHorz != None:
         self.titleGutterHorz = titleGutterHorz
      else: # make same as vert
         self.titleGutterHorz = titleGutterVert
      # no update necessary

   def setColors(self, bkg, grid, tick, margin, units, label, title,
                 bitColorDict):
      self.colorBkg = bkg
      self.colorGrid = grid
      self.colorTick = tick
      self.colorMargin = margin
      self.colorUnits = units
      self.colorLabel = label
      self.colorTitle = title
      self.bitColorDict = bitColorDict
      # no update necessary

   #------------------------------------------------------------------------||--
   def _iconA(self, args):
      # x and y do not include margin; are icon's upper left and right corner
      # if x1 == x2, this is a width of 1 (in a bit map relm)
      x, y = args
      if self.hIcon == 1: # draw a line
         self.canvas.rectangle(x, y, x+self.wIcon-1, y,
                            self.bitColorDict[1], None, 0)
         #self.canvas.line(x, y, x+self.wIcon, y, color, None, 1)
      else:
         self.canvas.rectangle(x, y, x+self.wIcon-1, y+self.hIcon-1,
                            self.bitColorDict[1], None, 0)
   def _iconB(self, args):
      x, y = args
      self.canvas.rectangle(x, y, x+self.wIcon-1, y+self.hIcon-1,
                            self.bitColorDict[2], None, 1)
   def _iconC(self, args):
      x, y = args
      self.canvas.rectangle(x, y, x+self.wIcon-1, y+self.hIcon-1,
                            self.bitColorDict[3], None, 0)
                            
   def _updateBitDict(self):
      self.bitDict = {1 : '_iconA',
                      2 : '_iconB',
                      3 : '_iconC',
                     } # map integers to icon methods


   #------------------------------------------------------------------------||--
   def _drawMargin(self):
      # draw top
      if self.tMargin != 0:
         self.canvas.rectangle(self.xOrigin, self.yOrigin, 
                   self.wTotal+self.xOrigin-1, self.yOrigin+self.tMargin-1, 
                   self.colorMargin, None, 0)
      # draw left
      if self.lMargin != 0:
         self.canvas.rectangle(self.xOrigin, self.yOrigin,
                    self.xOrigin+self.lMargin-1, self.yOrigin+self.hTotal-1, 
                    self.colorMargin, None, 0)
      # draw right
      if self.rMargin != 0:
         self.canvas.rectangle((self.xOrigin+self.lMargin+self.wGraph),
                               self.yOrigin, 
                     self.wTotal+self.xOrigin-1, self.yOrigin+self.hTotal-1, 
                     self.colorMargin, None, 0)
      # draw bottom
      if self.bMargin != 0:
         self.canvas.rectangle(self.xOrigin, 
                              (self.yOrigin+self.tMargin+self.hGraph), 
                   self.wTotal+self.xOrigin-1, self.hTotal+self.yOrigin-1, 
                   self.colorMargin, None, 0)


   #------------------------------------------------------------------------||--
   def _yGraphPosList(self):
      """gets a list of all necessary graph positions
      these are absoult points based on origin values
      gets real x/y points as integers?"""
      points = []
      x = copy.copy(self.graphOrigin[0])
      y = copy.copy(self.graphOrigin[1])
      i = 0
      for row in range(self.yRez):
         y = y + self.hIconGutter # upper left of icon
         yPos = y + (self.hIcon/2) # center of icon
         points.append((x, y, yPos, i)) # add data
         # incr data
         y = y + self.hIcon
         i = i + 1
      return points

   def _xGraphPosList(self):
      """gets a list of all necessary graph positions
      values are in pixels"""
      points = []
      x = copy.copy(self.graphOrigin[0])
      y = copy.copy(self.graphOrigin[1])
      j = 0
      for col in range(self.xRez): # read col events from first row of data
         x = x + self.wIconGutter
         xPos = x + (self.wIcon/2)
         points.append((x, y, xPos, j)) # add data
         # incr data
         x = x + self.wIcon
         j = j + 1 # j is an index of values
      #print _MOD, 'xGraphPosList', points
      return points

   #------------------------------------------------------------------------||--
   def _drawGrid(self):
      """to deal with error, add to length of horizontal grid"""
      if self.rowGridKey != []:
         for x, y, yPos, i in self._yGraphPosList():
            if self.rowGridKey[i % len(self.rowGridKey)] != 0:
               # x was reduced by one to account for some error
               # this introduced another off by one error in the vector version
               # not backgrounded to try to remove problems
               self.canvas.line(x, yPos, x+self.wGraph, yPos, self.colorGrid)
               #self.canvas.line(x-1, yPos, x+self.wGraph, 
               #                yPos, self.colorGrid)
      if self.colGridKey != []:
         for x, y, xPos, j in self._xGraphPosList():
            if self.colGridKey[j % len(self.colGridKey)] != 0: 
               # pre vector version
               self.canvas.line(xPos, y, xPos, y+self.hGraph, self.colorGrid)
               #self.canvas.line(xPos, y-1, xPos, y+self.hGraph, self.colorGrid)

   def _drawTick(self):
      for x, y, yPos, i in self._yGraphPosList():
         # do left
         if self.lTickKey != []: # not empty
            pix = self.lTickKey[i % len(self.lTickKey)]
            if pix != 0:
               self.canvas.line(x-pix, yPos, 
                                x-1, yPos, self.colorTick)
         # do right
         if self.rTickKey != []: # not empty
            pix = self.rTickKey[i % len(self.rTickKey)]
            if pix != 0:
               self.canvas.line(x+self.wGraph, yPos, 
                          x+self.wGraph+pix-1, yPos, self.colorTick)

      for x, y, xPos, j in self._xGraphPosList():
         # do bottom
         if self.bTickKey != []: # not empty
            pix =  self.bTickKey[j % len(self.bTickKey)]
            if pix != 0:   
               self.canvas.line(xPos, y + self.hGraph, 
                                xPos, y + self.hGraph + pix, self.colorTick)
         # do top
         if self.tTickKey != []: # not empty
            pix = self.tTickKey[j % len(self.tTickKey)]
            if pix != 0:   
               self.canvas.line(xPos, y-pix, 
                                xPos, y, self.colorTick)

   def _getUnitGutter(self, side):
      if side in ['col', 'b', 'x']:
         tick = self.bTickKey
      elif side in ['row', 'l', 'y']:
         tick = self.lTickKey
      max = 0
      for pix in tick:
         if pix >= max:
            max = pix
      return max + self.unitGutter

   def _drawUnits(self):
      """draw units on grid line spacings
      """
      rowGutter = self._getUnitGutter('row')
      colGutter = self._getUnitGutter('col')
      #self.yUnitMaxShift = 0
      self.yUnitOuterIndex = self.yRez #len(self.bitData)
      if self.rowUnitKey != []:
         for x, y, yPos, i in self._yGraphPosList():
            char = self.rowUnitKey[i % len(self.rowUnitKey)]
            if char != None: # should be 'ce'
               self.canvas.gridText(x-rowGutter, yPos, 'ce', str(char), 
                  self.font, self.colorUnits, self.kernBit)
               if i <= self.yUnitOuterIndex:
                  self.yUnitOuterIndex = i
      #self.xUnitMaxShift = 0
      self.xUnitOuterIndex = 0
      if self.colUnitKey != []:
         for x, y, xPos, j in self._xGraphPosList():
            char = self.colUnitKey[j % len(self.colUnitKey)]
            if char != None:  
               self.canvas.gridText(xPos, y+self.hGraph+colGutter, 'nc', 
                  str(char), self.font, self.colorUnits, self.kernBit)
               if j >= self.xUnitOuterIndex:
                  self.xUnitOuterIndex = j

   def _getLabelGutter(self, side):
      if side == 'col' or side == 'b' or side == 'x':
         tick = self.bTickKey
         #fontShift = self.hFont # need only char height here
      elif side == 'row' or side == 'l' or side == 'y':
         tick = self.lTickKey
         #fontShift = self.yUnitMaxShift # need max word length, not char
      max = 0 # get tick size
      for pix in tick:
         if pix >= max:
            max = pix
      return max + self.unitGutter + self.labelGutter #+ fontShift

   def _drawLabels(self):
      # these values may no be incorrect; double check
      rowGutter = self._getLabelGutter('row')
      colGutter = self._getLabelGutter('col')      
      if self.yLabel != '':
         for x, y, yPos, i in self._yGraphPosList():
            if i == self.yUnitOuterIndex: # 0 gets first first
               # used to be center of y; now use north anchor
               self.canvas.gridText(x-rowGutter, yPos, 'ce', self.yLabel, 
                  self.font, self.colorLabel, self.kernBit)
               break               
      if self.xLabel != '':
         for x, y, xPos, j in self._xGraphPosList():
            if j == self.xUnitOuterIndex: #len(self.bitData[0]) - 1: # 
               self.canvas.gridText(xPos, y+self.hGraph+colGutter,
                  'ne', self.xLabel, self.font, self.colorLabel, self.kernBit)
               break

   def _drawTitle(self):
      if self.title == '': return None # bail   
      x = copy.copy(self.xOrigin)
      y = copy.copy(self.yOrigin)
      xPos = x
      yPos = y
      # can be 'bl' or 'rt' or the like
      if 'b' in self.titlePos:
         #yPos = y + self.hTotal - hText - self.titleGutterVert
         yPos = (y + self.hTotal) - self.titleGutterVert
         anchorVertical = 's'
      if 'l' in self.titlePos:
         xPos = x + self.titleGutterHorz
         anchorHorizontal = 'w'
      if 'r' in self.titlePos:
         #xPos = x + self.wTotal - (wText + self.titleGutterHorz)
         xPos = x + self.wTotal - self.titleGutterHorz
         anchorHorizontal = 'e'
      if 't' in self.titlePos:
         #yPos = y + hText + self.titleGutterVert
         yPos = y + self.titleGutterVert
         anchorVertical = 'n'
      anchor = anchorVertical + anchorHorizontal # combine anchor notation
      self.canvas.gridText(xPos, yPos, anchor, self.title, 
         self.font, self.colorTitle, self.kernBit)
      #self.canvas.bitmap(xPos, yPos, gridData, self.colorTitle)

   #------------------------------------------------------------------------||--
   def _xGridToPixel(self, xRaw):
      """returns upper left corner for an integer grid bit map
      translated into pixel positions"""
      # always a margin (at 0) so add 1
      # no width at zero, so leave as is
      x = (self.graphOrigin[0] + ((xRaw + 1) * self.wIconGutter) +
           (xRaw * self.wIcon))
      return x

   def _yGridToPixel(self, yRaw):
      """returns upper left corner for an integer grid bit map
      translated into pixel positions"""
      # always a margin (at 0) so add 1
      # no width at zero, so leave as is
      y = (self.graphOrigin[1] + ((yRaw + 1) * self.hIconGutter) +
           (yRaw * self.hIcon))
      return y

# old version did things in order
#    def _drawIcon(self):
#       """draw each icon
#       point based to icon method is the upper left point"""
#       x = copy.copy(self.graphOrigin[0])
#       y = copy.copy(self.graphOrigin[1])
#       for row in self.bitData:
#          y = y + self.hIconGutter
#          for col in row:
#             x = x + self.wIconGutter
#             if col != 0:
#                meth = getattr(self, self.bitDict[col])
#                meth((x, y))
#             x = x + self.wIcon
#          # reset x
#          x = copy.copy(self.graphOrigin[0])
#          # add horizontal 
#          y = y + self.hIcon

   def _drawIcon(self):
      """alternative method: just look at data points, calc shift and pos"""
      for data in self.plotLib:
         if len(data) == 3:
            # three element data points are used for point graphs
            key, xRaw, yRaw = data # these are raw data points, starting from 0
            x = self._xGridToPixel(xRaw)
            y = self._yGridToPixel(yRaw)
            args = (x,y)
         elif len(data) == 5: 
            # 5 element args used for bar graphs
            key, x1Raw, y1Raw, x2Raw, y2Raw = data 
            x1 = self._xGridToPixel(x1Raw)
            y1 = self._yGridToPixel(y1Raw)
            x2 = self._xGridToPixel(x2Raw)
            y2 = self._yGridToPixel(y2Raw)

            # use raw integer x values to get widht, tn convert to pixel
            widthStep = x2Raw-x1Raw # number of 'cells' to span 
            width = ((widthStep * self.wIcon) + 
                    ((widthStep+1) * self.wIconGutter)) # one more gutter  
            args = (x1, y1, x1+width, y1)
         
         meth = getattr(self, self.bitDict[key])
         meth(args)


   #------------------------------------------------------------------------||--
   def draw(self):
      self._updateBitDict()
      self._drawGrid() # covers horizontal error introduced for vector version
      self._drawMargin()
      self._drawTick()
      self._drawUnits()
      self._drawIcon()
      self._drawLabels()
      self._drawTitle()

   def show(self, dir=None, prefDict=None):
      """some canvases use show to write files; a dir can be provided
      to use w/ automatically generated files names"""
      self.canvas.show(dir, prefDict)

   def write(self, filePath=None):
      """Saves the Image; .png and .jpg both work; format based on extensions
      None automatically gets a temp file name"""
      self.canvas.write(filePath)
      self.filePath = self.canvas.filePath # get from lower canvas


#-----------------------------------------------------------------||||||||||||--
class NumericalPointGraph(_GraphBase):
   """this graph is designed for points on a lone
   it assumes a fixed icon size
   this graph is used for TImap and TPmap displays"""
   def __init__(self, dataObj=None, origin=(0,0)):
   
      groupKey = dataObj.keys()[0] # take first key, will be 1
      # graph base wants a bit-map of enter data to be written
      _GraphBase.__init__(self, dataObj.xRez, dataObj.yRez, 
                          dataObj.getPlotLib(), origin)
      # set preferences

      # cells must be an odd number (so that there is a center
      self.wIcon = 3
      self.hIcon = 3
      # widht of horizontal and vertical margins
      # applied to one sides
      self.wIconGutter = 0
      self.hIconGutter = 0

      self.lMargin = 60 # left
      self.rMargin = 1   # right
      self.tMargin = 1   # top   
      self.bMargin = 60  # bottom

      #grid binary key for each angle: determines if grid line is drawn
      # may want to add other symbols for variation
      self.rowGridKey = dataObj.gridKey(groupKey, 'y', 8)
      self.colGridKey = dataObj.gridKey(groupKey, 'x', 8)

      #ticks extend into margin, separate color
      # value determines length
      # NB: problem w/ 1 pixel horizontal ticks
      self.lTickKey = []   # left
      self.rTickKey = [] # right
      self.tTickKey = [] # top 
      self.bTickKey = [] # bottom

      # give string for each necessary unit marker
      # None translated to no unit
      self.rowUnitKey = dataObj.unitKey(groupKey, 'y', self.rowGridKey)
      self.colUnitKey = dataObj.unitKey(groupKey, 'x', self.colGridKey)
      self.unitGutter = 2 # pixel spacing from tick mark, or edge if none

      # labels for x y axis
      self.xLabel = 'x'
      self.yLabel = 'y'
      self.labelGutter = 4 # pixels spaciing from unit+tick

      # colors
      self.colorBkg  = '#000000'
      self.colorGrid = '#333333'
      self.colorTick = "#CC6600"
      self.colorMargin = '#333333'
      self.colorUnits  = '#333399'
      self.colorLabel  = '#000000'
      self.bitColorDict = {1 : '#aaaaaa',}

      # update calculated values

   def _iconA(self, args):
      x, y = args
      # x and y do not include margin; are icon's upper left and right corner
      self.canvas.rectangle(x, y, x+self.wIcon-1, y+self.hIcon-1,
                            self.bitColorDict[1], None, 0)

   def _iconB(self, args):
      x, y = args
      # x and y do not include margin; are icon's upper left and right corner
      self.canvas.rectangle(x, y, x+self.wIcon-1, y+self.hIcon-1,
                            self.bitColorDict[2], None, 0)

   def _updateBitDict(self):
      self.bitDict = {1 : '_iconA',
                      2 : '_iconB',
                     } # map integers to icon methods

#-----------------------------------------------------------------||||||||||||--
class NumericalBarGraph(_GraphBase):
   """this graph is designed for points on a lone
   it assumes a fixed icon size
   this graph is used for TImap and TPmap displays
   dataObj is a raw dictionary of values organized by symbol (all are 1 for 
   now; may be real-value x,y pairs or x,y,x,y quads"""
   def __init__(self, dataObj=None, origin=(0,0)):
   
      groupKey = dataObj.keys()[0] # take first key, will be 1
      # will assign self.plotLib
      _GraphBase.__init__(self, dataObj.xRez, dataObj.yRez, 
                          dataObj.getPlotLib(), origin)
      # set preferences

      # cells must be an odd number (so that there is a center
      self.wIcon = 3 # used for relative grid size
      self.hIcon = 3
      # widht of horizontal and vertical margins
      # applied to one sides
      self.wIconGutter = 0
      self.hIconGutter = 0

      self.lMargin = 60 # left
      self.rMargin = 1   # right
      self.tMargin = 1   # top   
      self.bMargin = 60  # bottom

      #grid binary key for each angle: determines if grid line is drawn
      # may want to add other symbols for variation
      self.rowGridKey = dataObj.gridKey(groupKey, 'y', 8)
      self.colGridKey = dataObj.gridKey(groupKey, 'x', 8)

      #ticks extend into margin, separate color
      # value determines length
      # NB: problem w/ 1 pixel horizontal ticks
      self.lTickKey = []   # left
      self.rTickKey = [] # right
      self.tTickKey = [] # top 
      self.bTickKey = [] # bottom

      # give string for each necessary unit marker
      # None translated to no unit
      self.rowUnitKey = dataObj.unitKey(groupKey, 'y', self.rowGridKey)
      self.colUnitKey = dataObj.unitKey(groupKey, 'x', self.colGridKey)
      self.unitGutter = 2 # pixel spacing from tick mark, or edge if none

      # labels for x y axis
      self.xLabel = 'x'
      self.yLabel = 'y'
      self.labelGutter = 4 # pixels spaciing from unit+tick

      # colors
      self.colorBkg  = '#000000'
      self.colorGrid = '#333333'
      self.colorTick = "#CC6600"
      self.colorMargin = '#333333'
      self.colorUnits  = '#333399'
      self.colorLabel  = '#000000'

      self.bitColorDict = {1 : '#aaaaaa',
                           }
      # update calculated values

   def _iconA(self, args):
      x1, y1, x2, y2 = args
      # x and y do not include margin; are icon's upper left and right
      # corner
      # x2+self.wIcon-1
      self.canvas.rectangle(x1, y1, x2-1, y2+self.hIcon-1,
                            self.bitColorDict[1], None, 0)

   def _iconB(self, args):
      x1, y1, x2, y2 = args
      self.canvas.rectangle(x1, y1, x2-1, y2+self.hIcon-1,
                            self.bitColorDict[2], None, 0)


   def _updateBitDict(self):
      self.bitDict = {1 : '_iconA',
                      2 : '_iconB',
                     } # map integers to icon methods




#-----------------------------------------------------------------||||||||||||--
class TextGraph(_GraphBase):
   """create text in a graph 
   same interface as GridText object"""
   def __init__(self, msg=None, fg='#666633', style='dotMediumA',  
                backColor=None,
                font='micro', kernBit=1, vertBit=None, charWrapWidth=None):
      if msg == None:
         msg = 'algorithmic.net'
      # process data
      obj = fontLibrary.FontBitMap(msg, font, 0, kernBit, 
            vertBit, charWrapWidth)
      gridData = obj.encode()
      gridData = self._addNoise(gridData)
      xRez, yRez, plotLib = self._gridToPlotLib(gridData)
      # call base class
      canvas ='png'
      _GraphBase.__init__(self, xRez, yRez, plotLib)
      # set preferences

      # cells must be an odd number (so that there is a center
      self.wIcon = 2
      self.hIcon = 1
      # widht of horizontal and vertical margins
      # applied to one sides
      self.wIconGutter = 1
      self.hIconGutter = 1
      #grid binary key for each angle: determines if grid line is drawn
      # may want to add other symbols for variation
      self.rowGridKey = []
      self.colGridKey = []
      # colors
      self.colorBkg  = '#000000'
      self.colorGrid = '#333333'
      self.colorTick = "#CC6600"
      self.colorMargin = '#333333'
      self.colorUnits  = '#333399'
      self.colorLabel  = '#000000'

      self.bitColorDict = {1 : fg,
                           2 : fg,
                           }
      # other image settings
      self.transp = 1

      # not used so much in font
      self.lTickKey = []   # left
      self.rTickKey = [] # right
      self.tTickKey = [] # top 
      self.bTickKey = [] # bottom
      self.lMargin = 0 # left
      self.rMargin = 0   # right
      self.tMargin = 0   # top   
      self.bMargin = 0  # bottom
      # give string for each necessary unit marker
      # None translated to no unit
      self.rowUnitKey = []
      self.colUnitKey = []
      self.unitGutter = 2 # pixel spacing from tick mark, or edge if none
      # labels for x y axis
      self.xLabel = ''
      self.yLabel = ''
      self.labelGutter = 4 # pixels spaciing from unit+tick
      # update calculated values
      self.update(canvas)

   def _gridToPlotLib(self, gridData):
      """translate a full bit-map grid into a plotLib"""
      plotLib = []
      for row in range(len(gridData)):
         for col in range(len(gridData[0])):
            key = gridData[row][col]
            if key == 0: pass
            plotLib.append((key, col, row))
      yRez = len(gridData)
      xRez = len(gridData[0])
      return xRez, yRez, plotLib

   def _addNoise(self, gridData, distribution=[1,1,1,1,1,2]):
      for row in range(len(gridData)):
         for col in range(len(gridData[0])):
            if gridData[row][col] == 0:
               pass
            else:
               gridData[row][col] = random.choice(distribution)
      return gridData

   def _iconA(self, args):
      x, y = args   
      # x and y do not include margin; are icon's upper left and right
      # corner
      self.canvas.rectangle(x, y, x+self.wIcon-1, y+self.hIcon-1,
                            self.bitColorDict[1], None, 1)

   def _iconB(self, args):
      x, y = args
      self.canvas.rectangle(x, y, x+self.wIcon-1, y+self.hIcon-1,
                            self.bitColorDict[2], None, 1)









#-----------------------------------------------------------------||||||||||||--
#-----------------------------------------------------------------||||||||||||--

class Test:
   def __init__(self):
      self.testGraph()
      self.testGraphText()
      self.testCanvas()
      self.testProcGraphCoord()
      
   def testBarCode(self):
      bg = '#000000'
      fg = ['#666633','#333366', '#336633']
      a = BarCode(None, 50, fg, bg, 1, 50, 1)
      a.show()

   def testText(self):
      bg = '#000000'
      fg = ['#666633','#333366', '#336633']
      #a = TextWebTitle('testingTESTNGIN', '#333366', 'algoNetLarge')
      #a = TextWebTitle('testingTESTNGIN', '#333366', 'algoNetSmall')
      a = GridText('testing a long sample of text!', fg, bg, 'micro', 6, None,
                   2, 2, 1, 10)
      a.show()

   def testCanvas(self):
      fmtList = ['tk', 'png', 'eps']
      import time
      for fmt in fmtList:
         print _MOD, 'testing format', fmt
         a = Canvas(fmt, 200, 200)
         a.rectangle(10, 10, 120, 120, '#333333', '#ffffff', 5)
         a.rectangle(150, 150, 180, 180, None, '#ffffff', 2)
         a.line(10, 130, 200, 130, '#663399', 3)
         a.line(10, 180, 10, 200, '#663399', 1)         
         a.oval(50, 50, 150, 150, '#999933', '#cccccc', 3)
         a.oval(75, 75, 125, 125, '#99ff33', None, 0)
         a.oval(90, 90, 110, 110, None, '#ff3333', 1)
         
         a.line(0, 100, 200, 100, '#999999', .25)
         a.line(100, 0, 100, 200, '#999999', .25)
         # problem case
         a.line(0, 200, 200, 0, '#999999', .25)

         a.gridText(0, 0, 'nw', 'testing some large text', 'micro', 
            '#33ff99', 2)
         a.gridText(20, 180, 'nw', 'a big title', 'macro', '#cccc33',  2)

         a.gridText(100, 100, 'ne', 'northEast', 'micro', '#000000')
         a.gridText(100, 100, 'nw', 'northWest', 'micro', '#333333')
         a.gridText(100, 25,  'nc', 'northCenter', 'micro', '#cccccc')
         a.gridText(25, 100,   'ce', 'centerEast', 'micro', '#cccccc')

         a.gridText(100, 100, 'se', 'southEast', 'micro', '#666666')
         a.gridText(100, 100, 'sw', 'southWest', 'micro', '#999999')
         a.gridText(100, 175, 'sc', 'southCenter', 'micro', '#aaaaaa')
         a.gridText(175, 100, 'cw', 'centerWest', 'micro', '#aaaaaa')
         a.show()
         del a
         time.sleep(1) # wait a second
         
         
# out of date
#    def testGraph(self):
#       fmtList = ['tk', 'png', 'eps']
#       import time
#       for fmt in fmtList:
#          print _MOD, 'testing format', fmt
#          canvas = Canvas(fmt, 400, 600, '#666666')
#          a = _GraphBase(None, (30,30))
#          a.update(canvas)
#          a.draw()
#    
#          b = _GraphBase(None, (30,30+a.hTotal+2))
#          b.setTitle('testTitle')
#          b.update(canvas)
#          b.draw()   
#          b.show()


   def testGraphText(self):
      #d = TextGraph()
      #d.draw()
      #d.show()

      obj = TextGraph('test', '#333366', 'dotMediumA')
      obj.draw()
      path = '/Volumes/xdisc/_scratch/a.gif'
      obj.write(path)
      os.system('open %s' % path)


   def testProcGraphCoord(self):
      a = ProcessGraphCoordData()
      a()

if __name__ == '__main__':
   Test()




www.java2java.com | Contact Us
Copyright 2009 - 12 Demo Source and Support. All rights reserved.
All other trademarks are property of their respective owners.