# **************************************************************************
# *
# * Authors: J.M. De la Rosa Trevin (jmdelarosa@cnb.csic.es)
# *
# * Unidad de Bioinformatica of Centro Nacional de Biotecnologia , CSIC
# *
# * This program is free software; you can redistribute it and/or modify
# * it under the terms of the GNU General Public License as published by
# * the Free Software Foundation; either version 3 of the License, or
# * (at your option) any later version.
# *
# * This program is distributed in the hope that it will be useful,
# * but WITHOUT ANY WARRANTY; without even the implied warranty of
# * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# * GNU General Public License for more details.
# *
# * You should have received a copy of the GNU General Public License
# * along with this program; if not, write to the Free Software
# * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
# * 02111-1307 USA
# *
# * All comments concerning this program package may be sent to the
# * e-mail address 'scipion@cnb.csic.es'
# *
# **************************************************************************
"""
This module implements a simple algorithm to display a graph(mainly a tree)
level by level, using only Tkinter.
"""
# TODO: all LevelTree code is DEPRECATED...remove it after cleaning
# and include all code from graph_layout
[docs]class LevelTree(object):
""" Class to render the Graph in a Canvas. """
def __init__(self, graph):
self.DY = 65
self.DX = 15
self.FONT = "sans-serif"
self.FONTSIZE = 9
self.graph = graph
self.canvas = None
[docs] def setCanvas(self, canvas):
self.canvas = canvas
[docs] def paint(self, createNode=None, createEdge=None, maxLevel=9999, usePositions=False):
""" Paint the Graph, nodes will be positioned by levels.
Params:
canvas: the canvas object to paint the graph.
createNode: function to build the Item that represents a Node.
the Item created should have the following methods:
getDimensions: return the width and height
moveTo: change the position of the Item
createEdge: function to build an Edge connection two Nodes
usePositions: if this is True, use the nodes positions without
recomputing them.
If createNode and createEdge are None, the default ones will be used,
that requires the setCanvas method had to be called first.
"""
self.createNode = createNode or self._defaultCreateNode
self.createEdge = createEdge or self._defaultCreateEdge
self.maxLevel = maxLevel
rootNode = self.graph.getRoot()
if usePositions:
self._paintNodeWithPosition(rootNode)
self._paintEdges(rootNode)
else:
self._setLevel(rootNode, 0, None)
self._paintNodeWithChilds(rootNode, 1)
m = 9999
for left, right in rootNode.hLimits:
m = min(m, left)
self._createEdges(rootNode, -m + self.DY)
def _setLevel(self, node, level, parent):
""" Set the level of the nodes. """
node.level = level
node.parent = parent
nextLevel = level + 1
if nextLevel > self.maxLevel:
return
for child in node.getChildren():
if nextLevel > getattr(child, 'level', 0):
self._setLevel(child, nextLevel, node)
def _paintNodeWithChilds(self, node, level):
y = level * self.DY
self._paintNode(node, y)
if level > self.maxLevel:
return
childs = [c for c in node.getChildren() if c.parent is node]
n = len(childs)
if n > 0:
# width = (xmax - xmin) / n
for c in childs:
self._paintNodeWithChilds(c, level + 1)
if n > 1:
offset = 0
for i in range(n - 1):
sep = self._getChildsSeparation(childs[i], childs[i + 1])
offset += sep
c = childs[i + 1]
c.offset = offset
total = childs[0].half + offset + childs[-1].half
half = total / 2
for c in childs:
c.offset -= half - childs[0].half
else:
childs[0].offset = 0
self._getHLimits(node)
def _defaultCreateNode(self, canvas, node, y):
""" If not createNode is specified, this one will be used
by default.
"""
if canvas is None:
raise Exception("method setCanvas should be called before using _defaultCreateNode")
nodeText = node.getLabel()
textColor = 'black'
return canvas.createTextbox(nodeText, 100, y, bgColor='light blue', textColor=textColor, margin=0)
def _defaultCreateEdge(self, srcItem, dstItem):
if self.canvas is None:
raise Exception("method setCanvas should be called before using _defaultCreateEdge")
self.canvas.createEdge(srcItem, dstItem)
def _paintNode(self, node, y):
""" Paint a node of the graph.
Params:
canvas: the canvas in which to paint.
node: the node of the graph to be painted.
y: level in the tree where to paint.
Returns:
the create item in the canvas.
"""
item = self.createNode(self.canvas, node, y)
node.width, node.height = item.getDimensions()
node.half = node.width / 2
node.hLimits = [[-node.half, node.half]]
node.y = item.y
node.offset = 0
# Create link from both sides to reach
node.item = item
item.node = node
return item
def _printHLimits(self, node, msg):
print("\n=====%s========" % msg)
print(" dd: %s" % node.t.text.replace('\n', '_'))
print(" offset: %d, width: %d" % (node.offset, node.width))
print(" hlimits:")
for l, r in node.hLimits:
print(" [%d, %d]" % (l, r))
def _getHLimits(self, node):
"""
This function will traverse the tree
from node to build the left and right profiles(hLimits)
for each level of the tree
"""
node.hLimits = [[-node.half, node.half]]
childs = [c for c in node.getChildren() if c.parent is node]
for child in childs:
count = 1
if not hasattr(child, 'hLimits'):
print("node %s has no hLimits" % child.label)
raise Exception()
for l, r in child.hLimits:
l += child.offset
r += child.offset
if count < len(node.hLimits):
if l < node.hLimits[count][0]:
node.hLimits[count][0] = l
if r > node.hLimits[count][1]:
node.hLimits[count][1] = r
else:
node.hLimits.append([l, r])
count += 1
def _getChildsSeparation(self, child1, child2):
""" Calculate separation between siblings
at each height level. """
sep = 0
hL1 = child1.hLimits
hL2 = child2.hLimits
n1 = len(hL1)
n2 = len(hL2)
h = min(n1, n2)
for i in range(h):
right = hL1[i][1]
left = hL2[i][0]
if left + sep < right:
sep = right - left
return sep + self.DX
def _createEdges(self, node, x):
""" Adjust the position of the nodes
and create the edges between them.
"""
nx = x + node.offset
node.item.moveTo(nx, node.y)
if node.level == self.maxLevel:
return
for c in node.getChildren():
if c.parent is node:
self._createEdges(c, nx)
self.createEdge(node.item, c.item)
def _paintNodeWithPosition(self, node):
""" Paint nodes using its position. """
self._paintNode(node, None)
for child in node.getChildren():
# parent = None for nodes that have been not traversed
parent = getattr(child, 'parent', None)
if parent is None:
child.parent = node
self._paintNodeWithPosition(child)
def _paintEdges(self, node):
""" Paint only the edges between nodes, assuming they are
already well positioned.
"""
for child in node.getChildren():
if child.parent is node:
self._paintEdges(child)
self.createEdge(node.item, child.item)