ML.Cluster.ClusterVis

Package ML :: Package Cluster :: Module ClusterVis |
[hide private]
[frames] | no frames]

# Source Code for Module ML.Cluster.ClusterVis

      1  # $Id: ClusterVis.py 778 2008-07-30 09:51:55Z glandrum $
      2  #
      3  # Copyright (C) 2001-2006  greg Landrum and Rational Discovery LLC
      4  #
      5  #   @@ All Rights Reserved  @@
      6  #
      7  """Cluster tree visualization using Sping
      8
      9  """
     10
     11  try:
     12    from sping import pid
     13    piddle = pid
     14  except ImportError:
     15    from piddle import piddle
     16  import ClusterUtils
     17
     18  import numpy
     19
     20 -class VisOpts(object):
     21    """ stores visualization options for cluster viewing
     22
     23      **Instance variables**
     24
     25        - x/yOffset: amount by which the drawing is offset from the edges of the canvas
     26
     27        - lineColor: default color for drawing the cluster tree
     28
     29        - lineWidth: the width of the lines used to draw the tree
     30
     31    """
     32    xOffset = 20
     33    yOffset = 20
     34    lineColor = piddle.Color(0,0,0)
     35    hideColor = piddle.Color(.8,.8,.8)
     36    terminalColors = [piddle.Color(1,0,0),piddle.Color(0,0,1),piddle.Color(1,1,0),
     37                      piddle.Color(0,.5,.5),piddle.Color(0,.8,0),piddle.Color(.5,.5,.5),
     38                      piddle.Color(.8,.3,.3),piddle.Color(.3,.3,.8),piddle.Color(.8,.8,.3),
     39                      piddle.Color(.3,.8,.8)]
     40    lineWidth = 2
     41    hideWidth = 1.1
     42    nodeRad=15
     43    nodeColor = piddle.Color(1.,.4,.4)
     44    highlightColor = piddle.Color(1.,1.,.4)
     45    highlightRad = 10
     46
     47 -def _scaleMetric(val,power=2,min=1e-4):
     48    val = float(val)
     49    nval = pow(val,power)
     50    if nval < min:
     51      return 0.0
     52    else:
     53      return numpy.log(nval/min)
     54
     55 -class ClusterRenderer(object):
     56 -  def __init__(self,canvas,size,
     57                 ptColors=[],lineWidth=None,
     58                 showIndices=0,
     59                 showNodes=1,
     60                 stopAtCentroids=0,
     61                 logScale=0,
     62                 tooClose=-1):
     63      self.canvas = canvas
     64      self.size = size
     65      self.ptColors = ptColors
     66      self.lineWidth = lineWidth
     67      self.showIndices = showIndices
     68      self.showNodes = showNodes
     69      self.stopAtCentroids = stopAtCentroids
     70      self.logScale = logScale
     71      self.tooClose = tooClose
     72
     73 -  def _AssignPointLocations(self,cluster,terminalOffset=4):
     74      self.pts = cluster.GetPoints()
     75      self.nPts = len(self.pts)
     76      self.xSpace = float(self.size[0]-2*VisOpts.xOffset)/float(self.nPts-1)
     77      ySize = self.size[1]
     78      for i in xrange(self.nPts):
     79        pt = self.pts[i]
     80        if self.logScale > 0:
     81          v = _scaleMetric(pt.GetMetric(), self.logScale)
     82        else:
     83          v = float(pt.GetMetric())
     84        pt._drawPos = (VisOpts.xOffset+i*self.xSpace,
     85                       ySize-(v*self.ySpace+VisOpts.yOffset)+terminalOffset)
     86
     87 -  def _AssignClusterLocations(self,cluster):
     88      # first get the search order (top down)
     89      toDo = [cluster]
     90      examine = cluster.GetChildren()[:]
     91      while len(examine):
     92        node = examine.pop(0)
     93        children = node.GetChildren()
     94        if len(children):
     95          toDo.append(node)
     96          for child in children:
     97            if not child.IsTerminal():
     98              examine.append(child)
     99      # and reverse it (to run from bottom up)
    100      toDo.reverse()
    101      for node in toDo:
    102        if self.logScale > 0:
    103          v = _scaleMetric(node.GetMetric(), self.logScale)
    104        else:
    105          v = float(node.GetMetric())
    106        # average our children's x positions
    107        childLocs = [x._drawPos[0] for x in node.GetChildren()]
    108        if len(childLocs):
    109          xp = sum(childLocs)/float(len(childLocs))
    110          yp = self.size[1] - (v*self.ySpace+VisOpts.yOffset)
    111          node._drawPos = (xp,yp)
    112
    113 -  def _DrawToLimit(self,cluster):
    114      """
    115        we assume that _drawPos settings have been done already
    116      """
    117      if self.lineWidth is None:
    118        lineWidth = VisOpts.lineWidth
    119      else:
    120        lineWidth = self.lineWidth
    121
    122      examine = [cluster]
    123      while len(examine):
    124        node = examine.pop(0)
    125        xp,yp = node._drawPos
    126        children = node.GetChildren()
    127        if abs(children[1]._drawPos[0]-children[0]._drawPos[0])>self.tooClose:
    128          # draw the horizontal line connecting things
    129          drawColor = VisOpts.lineColor
    130          self.canvas.drawLine(children[0]._drawPos[0],yp,
    131                               children[-1]._drawPos[0],yp,
    132                               drawColor,lineWidth)
    133          # and draw the lines down to the children
    134          for child in children:
    135            if self.ptColors and child.GetData() is not None:
    136              drawColor = self.ptColors[child.GetData()]
    137            else:
    138              drawColor = VisOpts.lineColor
    139            cxp,cyp = child._drawPos
    140            self.canvas.drawLine(cxp,yp,cxp,cyp,drawColor,lineWidth)
    141            if not child.IsTerminal():
    142              examine.append(child)
    143            else:
    144              if self.showIndices and not self.stopAtCentroids:
    145                try:
    146                  txt = str(child.GetName())
    147                except:
    148                  txt = str(child.GetIndex())
    149                self.canvas.drawString(txt,
    150                                       cxp-self.canvas.stringWidth(txt)/2,
    151                                       cyp)
    152
    153        else:
    154          # draw a "hidden" line to the bottom
    155          self.canvas.drawLine(xp,yp,xp,self.size[1]-VisOpts.yOffset,
    156                               VisOpts.hideColor,lineWidth)
    157
    158
    159 -  def DrawTree(self,cluster,minHeight=2.0):
    160      if self.logScale > 0:
    161        v = _scaleMetric(cluster.GetMetric(), self.logScale)
    162      else:
    163        v = float(cluster.GetMetric())
    164      if v <= 0:
    165        v = minHeight
    166      self.ySpace = float(self.size[1]-2*VisOpts.yOffset)/v
    167
    168      self._AssignPointLocations(cluster)
    169      self._AssignClusterLocations(cluster)
    170      if not self.stopAtCentroids:
    171        self._DrawToLimit(cluster)
    172      else:
    173        raise NotImplementedError,'stopAtCentroids drawing not yet implemented'
    174
    175
    176 -def DrawClusterTree(cluster,canvas,size,
    177                      ptColors=[],lineWidth=None,
    178                      showIndices=0,
    179                      showNodes=1,
    180                      stopAtCentroids=0,
    181                      logScale=0,
    182                      tooClose=-1):
    183    """ handles the work of drawing a cluster tree on a Sping canvas
    184
    185      **Arguments**
    186
    187        - cluster: the cluster tree to be drawn
    188
    189        - canvas:  the Sping canvas on which to draw
    190
    191        - size: the size of _canvas_
    192
    193        - ptColors: if this is specified, the _colors_ will be used to color
    194          the terminal nodes of the cluster tree.  (color == _pid.Color_)
    195
    196        - lineWidth: if specified, it will be used for the widths of the lines
    197          used to draw the tree
    198
    199     **Notes**
    200
    201       - _Canvas_ is neither _save_d nor _flush_ed at the end of this
    202
    203       - if _ptColors_ is the wrong length for the number of possible terminal
    204         node types, this will throw an IndexError
    205
    206       - terminal node types are determined using their _GetData()_ methods
    207
    208    """
    209    renderer = ClusterRenderer(canvas,size,ptColors,lineWidth,showIndices,showNodes,stopAtCentroids,
    210                               logScale,tooClose)
    211    renderer.DrawTree(cluster)
    212 -def _DrawClusterTree(cluster,canvas,size,
    213                      ptColors=[],lineWidth=None,
    214                      showIndices=0,
    215                      showNodes=1,
    216                      stopAtCentroids=0,
    217                      logScale=0,
    218                      tooClose=-1):
    219    """ handles the work of drawing a cluster tree on a Sping canvas
    220
    221      **Arguments**
    222
    223        - cluster: the cluster tree to be drawn
    224
    225        - canvas:  the Sping canvas on which to draw
    226
    227        - size: the size of _canvas_
    228
    229        - ptColors: if this is specified, the _colors_ will be used to color
    230          the terminal nodes of the cluster tree.  (color == _pid.Color_)
    231
    232        - lineWidth: if specified, it will be used for the widths of the lines
    233          used to draw the tree
    234
    235     **Notes**
    236
    237       - _Canvas_ is neither _save_d nor _flush_ed at the end of this
    238
    239       - if _ptColors_ is the wrong length for the number of possible terminal
    240         node types, this will throw an IndexError
    241
    242       - terminal node types are determined using their _GetData()_ methods
    243
    244    """
    245    if lineWidth is None:
    246      lineWidth = VisOpts.lineWidth
    247    pts = cluster.GetPoints()
    248    nPts = len(pts)
    249    if nPts <= 1: return
    250    xSpace = float(size[0]-2*VisOpts.xOffset)/float(nPts-1)
    251    if logScale > 0:
    252      v = _scaleMetric(cluster.GetMetric(), logScale)
    253    else:
    254      v = float(cluster.GetMetric())
    255    ySpace = float(size[1]-2*VisOpts.yOffset)/v
    256
    257    for i in xrange(nPts):
    258      pt = pts[i]
    259      if logScale > 0:
    260        v = _scaleMetric(pt.GetMetric(), logScale)
    261      else:
    262        v = float(pt.GetMetric())
    263      pt._drawPos = (VisOpts.xOffset+i*xSpace,
    264                     size[1]-(v*ySpace+VisOpts.yOffset))
    265      if not stopAtCentroids or not hasattr(pt,'_isCentroid'):
    266        allNodes.remove(pt)
    267
    268    if not stopAtCentroids:
    269      allNodes=ClusterUtils.GetNodeList(cluster)
    270    else:
    271      allNodes=ClusterUtils.GetNodesDownToCentroids(cluster)
    272
    273    while len(allNodes):
    274      node = allNodes.pop(0)
    275      children = node.GetChildren()
    276      if len(children):
    277        if logScale > 0:
    278          v = _scaleMetric(node.GetMetric(), logScale)
    279        else:
    280          v = float(node.GetMetric())
    281        yp = size[1]-(v*ySpace+VisOpts.yOffset)
    282        childLocs = [x._drawPos[0] for x in children]
    283        xp = sum(childLocs)/float(len(childLocs))
    284        node._drawPos = (xp,yp)
    285        if not stopAtCentroids or node._aboveCentroid > 0:
    286          for child in children:
    287            if ptColors != [] and child.GetData() is not None:
    288              drawColor = ptColors[child.GetData()]
    289            else:
    290              drawColor = VisOpts.lineColor
    291            if showNodes and hasattr(child,'_isCentroid'):
    292              canvas.drawLine(child._drawPos[0],child._drawPos[1]-VisOpts.nodeRad/2,
    293                              child._drawPos[0],node._drawPos[1],
    294                              drawColor,lineWidth)
    295            else:
    296              canvas.drawLine(child._drawPos[0],child._drawPos[1],
    297                              child._drawPos[0],node._drawPos[1],
    298                              drawColor,lineWidth)
    299          canvas.drawLine(children[0]._drawPos[0],node._drawPos[1],
    300                          children[-1]._drawPos[0],node._drawPos[1],
    301                          VisOpts.lineColor,lineWidth)
    302        else:
    303          for child in children:
    304            drawColor = VisOpts.hideColor
    305            canvas.drawLine(child._drawPos[0],child._drawPos[1],
    306                            child._drawPos[0],node._drawPos[1],
    307                            drawColor,VisOpts.hideWidth)
    308          canvas.drawLine(children[0]._drawPos[0],node._drawPos[1],
    309                          children[-1]._drawPos[0],node._drawPos[1],
    310                          VisOpts.hideColor,VisOpts.hideWidth)
    311
    312      if showIndices and (not stopAtCentroids or node._aboveCentroid >= 0):
    313        txt = str(node.GetIndex())
    314        if hasattr(node,'_isCentroid'):
    315          txtColor = piddle.Color(1,.2,.2)
    316        else:
    317          txtColor = piddle.Color(0,0,0)
    318
    319        canvas.drawString(txt,
    320                          node._drawPos[0]-canvas.stringWidth(txt)/2,
    321                          node._drawPos[1]+canvas.fontHeight()/4,
    322                          color=txtColor)
    323
    324      if showNodes and hasattr(node,'_isCentroid'):
    325        rad = VisOpts.nodeRad
    326        canvas.drawEllipse(node._drawPos[0]-rad/2,node._drawPos[1]-rad/2,
    327                         node._drawPos[0]+rad/2,node._drawPos[1]+rad/2,
    328                         piddle.transparent,
    329                         fillColor=VisOpts.nodeColor)
    330        txt = str(node._clustID)
    331        canvas.drawString(txt,
    332                          node._drawPos[0]-canvas.stringWidth(txt)/2,
    333                          node._drawPos[1]+canvas.fontHeight()/4,
    334                          color=piddle.Color(0,0,0))
    335
    336    if showIndices and not stopAtCentroids:
    337      for pt in pts:
    338        txt = str(pt.GetIndex())
    339        canvas.drawString(str(pt.GetIndex()),
    340                          pt._drawPos[0]-canvas.stringWidth(txt)/2,
    341                          pt._drawPos[1])
    342
    343 -def ClusterToPDF(cluster,fileName,size=(300,300),ptColors=[],lineWidth=None,
    344                   showIndices=0,stopAtCentroids=0,logScale=0):
    345    """ handles the work of drawing a cluster tree to an PDF file
    346
    347      **Arguments**
    348
    349        - cluster: the cluster tree to be drawn
    350
    351        - fileName: the name of the file to be created
    352
    353        - size: the size of output canvas
    354
    355        - ptColors: if this is specified, the _colors_ will be used to color
    356          the terminal nodes of the cluster tree.  (color == _pid.Color_)
    357
    358        - lineWidth: if specified, it will be used for the widths of the lines
    359          used to draw the tree
    360
    361     **Notes**
    362
    363       - if _ptColors_ is the wrong length for the number of possible terminal
    364         node types, this will throw an IndexError
    365
    366       - terminal node types are determined using their _GetData()_ methods
    367
    368    """
    369    try:
    370      from sping.PDF import pidPDF
    371    except ImportError:
    372      from piddle import piddlePDF
    373      pidPDF = piddlePDF
    374
    375    canvas = pidPDF.PDFCanvas(size,fileName)
    376    if lineWidth is None:
    377      lineWidth = VisOpts.lineWidth
    378    DrawClusterTree(cluster,canvas,size,ptColors=ptColors,lineWidth=lineWidth,
    379                    showIndices=showIndices,stopAtCentroids=stopAtCentroids,
    380                    logScale=logScale)
    381    if fileName:
    382      canvas.save()
    383    return canvas
    384
    385 -def ClusterToSVG(cluster,fileName,size=(300,300),ptColors=[],lineWidth=None,
    386                   showIndices=0,stopAtCentroids=0,logScale=0):
    387    """ handles the work of drawing a cluster tree to an SVG file
    388
    389      **Arguments**
    390
    391        - cluster: the cluster tree to be drawn
    392
    393        - fileName: the name of the file to be created
    394
    395        - size: the size of output canvas
    396
    397        - ptColors: if this is specified, the _colors_ will be used to color
    398          the terminal nodes of the cluster tree.  (color == _pid.Color_)
    399
    400        - lineWidth: if specified, it will be used for the widths of the lines
    401          used to draw the tree
    402
    403     **Notes**
    404
    405       - if _ptColors_ is the wrong length for the number of possible terminal
    406         node types, this will throw an IndexError
    407
    408       - terminal node types are determined using their _GetData()_ methods
    409
    410    """
    411    try:
    412      from sping.SVG import pidSVG
    413    except ImportError:
    414      from piddle.piddleSVG import piddleSVG
    415      pidSVG = piddleSVG
    416
    417    canvas = pidSVG.SVGCanvas(size,fileName)
    418
    419    if lineWidth is None:
    420      lineWidth = VisOpts.lineWidth
    421    DrawClusterTree(cluster,canvas,size,ptColors=ptColors,lineWidth=lineWidth,
    422                    showIndices=showIndices,stopAtCentroids=stopAtCentroids,
    423                    logScale=logScale)
    424    if fileName:
    425      canvas.save()
    426    return canvas
    427
    428 -def ClusterToImg(cluster,fileName,size=(300,300),ptColors=[],lineWidth=None,
    429                   showIndices=0,stopAtCentroids=0,logScale=0):
    430    """ handles the work of drawing a cluster tree to an image file
    431
    432      **Arguments**
    433
    434        - cluster: the cluster tree to be drawn
    435
    436        - fileName: the name of the file to be created
    437
    438        - size: the size of output canvas
    439
    440        - ptColors: if this is specified, the _colors_ will be used to color
    441          the terminal nodes of the cluster tree.  (color == _pid.Color_)
    442
    443        - lineWidth: if specified, it will be used for the widths of the lines
    444          used to draw the tree
    445
    446     **Notes**
    447
    448       - The extension on  _fileName_ determines the type of image file created.
    449         All formats supported by PIL can be used.
    450
    451       - if _ptColors_ is the wrong length for the number of possible terminal
    452         node types, this will throw an IndexError
    453
    454       - terminal node types are determined using their _GetData()_ methods
    455
    456    """
    457    try:
    458      from sping.PIL import pidPIL
    459    except ImportError:
    460      from piddle import piddlePIL
    461      pidPIL = piddlePIL
    462    canvas = pidPIL.PILCanvas(size,fileName)
    463    if lineWidth is None:
    464      lineWidth = VisOpts.lineWidth
    465    DrawClusterTree(cluster,canvas,size,ptColors=ptColors,lineWidth=lineWidth,
    466                    showIndices=showIndices,stopAtCentroids=stopAtCentroids,
    467                    logScale=logScale)
    468    if fileName:
    469      canvas.save()
    470    return canvas
    471