Esempio n. 1
0
class DecompositionView(QtGui.QDialog):
    """ A view where the decomposition of the system of constraints is visualised as a directed acyclic graph"""
    def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None):
        """ Initialization of the CompositionView class
            
        Parameters:
            viewportMngr - the manager of the viewports where the composition view can reside in
            prototypeMngr - the manager of the prototypes is used to obtain the results of the solver
        """
        QtGui.QDialog.__init__(self, parent)
        self.prototypeManager = prototypeMngr
        self.viewport = viewport
        self.viewportManager = viewportMngr
        self.settings = Settings()
        self.setWindowFlags(QtCore.Qt.Window)
        self.timer = QtCore.QObject()

        """map GeometricDecomposition to CVCluster"""
        self.map = {}
        
        self.ui = Ui_compositionView()
        self.ui.setupUi(self)
        self.ui.graphicsView.setupViewport(QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers|QtOpenGL.QGL.DoubleBuffer)))
        self.ui.graphicsView.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
        
        self.currentTool = None
        self.viewportType = vpType
        self.orientation = TreeOrientation.BOTTOM

        self.overConstrainedColor = QtGui.QColor(0,0,255)
        self.underConstrainedColor = QtGui.QColor(255,0,0)
        self.wellConstrainedColor = QtGui.QColor(0,255,0)
        self.unsolvedColor = QtGui.QColor(125,124,255)
        
        self.createScene()
        self.createTriggers()
        
    def createTriggers(self):
        """ Create the triggers for the components in the graphical window """ 
        QtCore.QObject.connect(self.ui.zoomInButton,QtCore.SIGNAL("clicked()"),self.zoomIn)
        QtCore.QObject.connect(self.ui.zoomOutButton,QtCore.SIGNAL("clicked()"),self.zoomOut)
        QtCore.QObject.connect(self.ui.fitButton, QtCore.SIGNAL("clicked()"), self.fit)
        #QtCore.QObject.connect(self.ui.collapseButton, QtCore.SIGNAL("clicked()"), self.collapse)
        QtCore.QObject.connect(self.ui.graphicsScene, QtCore.SIGNAL("changed(const QList<QRectF> & )"), self.updateSceneRect)
        QtCore.QObject.connect(self.ui.verticalSlider,QtCore.SIGNAL("valueChanged(int)"),self.setupMatrix)
        #QtCore.QObject.connect(self.settings.dvData,QtCore.SIGNAL("treeOrientationChanged()"), self.updateTreeOrientation)
             
    def getViewportType(self):
        return self.viewportType
        
    def updateGL(self):
        self.update()
    
    def createDecomposition(self):
        """ Create a new decomposition. If an older one exists it will be removed. """ 
        self.clearScene()
        self.createScene()
       
    def clearScene(self):
        self.map = {}
        if self.ui.graphicsScene != None:
            for item in self.ui.graphicsView.items():
                item.hide()
                if item.parentItem() == None:
                    self.ui.graphicsScene.removeItem(item)
            
    def createScene(self):
        """ Updating the view with new data and nodes for the visualisation of the tree """
        if self.prototypeManager.result != None:
            # get all clusters from result
            new = [self.prototypeManager.result]
            clusters = set()
            while len(new) > 0:
                c = new.pop()
                clusters.add(c)
                for child in c.subs:
                    if child not in clusters:
                        new.append(child)
            # create N layers for clusters with 1-N variables
            N = len(self.prototypeManager.result.variables)
            layers = []
            for n in range(0,N+1):
                layers.append([])
            # add clusters to layers 
            for c in clusters:
                n = len(c.variables)
                layers[n].append(c)
            # sort clusters in layers
            # start from layer N (largest clusters)
            # clusters are initially ordered according to the order in which sub-clusters appear in the previous (n+1) layer             
            for n in reversed(range(1,N)):
                print "ordering layers",n
                # find subiable pseudo-ordering in previous layers
                subordervalue = {}
                clusterindex = 0
                for cluster in layers[n+1]:
                    clusterindex  = clusterindex+1 
                    for sub in cluster.subs:
                        # order by first appearence in cluster from left to right
                        if sub not in subordervalue:
                            subordervalue[sub] = clusterindex
                # determine pseudo-order clusters in this layers: sum subordervalues per cluster
                clusterordervalue = {}
                for cluster in layers[n]:
                    clusterordervalue[cluster] = 0
                    for sub in cluster.subs:
                        if sub in subordervalue:
                            clusterordervalue[cluster] += subordervalue[sub]
                # sort clusters in layers
                layers[n].sort(lambda x,y:clusterordervalue[x]<clusterordervalue[y])
            
            # map GeometricDecompositions to CVClusters
            for n in range(0,N+1):
                layer = layers[n]
                for k in range(0,len(layer)):
                    c = layer[k]
                    y = n * 50.0
                    x = (k - len(layer)/2.0) * 50.0 * n
                    cvcluster = CVCluster(self, c, x,y)
                    self.ui.graphicsScene.addItem(cvcluster)
                    self.map[c] = cvcluster
                    self.map[cvcluster] = c
            # add CVConnections
            for c in clusters:
                for child in c.subs:
                    self.ui.graphicsScene.addItem(CVConnection(self, self.map[c], self.map[child]))
            # iteratively improve graph layout
            self.optimiseGraphLayout()

    def optimiseGraphLayout(self):
        print "optimising graph layout..."
        # force due to overlapping overlaps 
        force_cluster = 0.2
        # force due to connection length
        force_connection = 0.025
        # force due to clusters overlapping connections
        force_cluster_connection = 0.05

        # create a graph of clusters and connections
        graph = geosolver.graph.Graph()
        if self.ui.graphicsScene != None:
            for item in self.ui.graphicsView.items():
                if isinstance(item, CVCluster):
                    graph.add_vertex(item)
                    item.force = numpy.array([0.0,0.0])
                elif isinstance(item, CVConnection):
                    graph.add_edge(item.nodeFrom, item.nodeTo, item)   
       
        l = list(graph.vertices())

        # iteratively improve layout 
        for i in range(100):
            # clear forces 
            for c in l:
                c.force = numpy.array([random.random()*0, random.random()*0])

            # determine forces due to overlapping cluster boxes
            n = len(l)
            for i in range(n):
                for j in range(i+1,n):
                    c1 = l[i]     
                    c2 = l[j] 
                    box1 = c1.boundingRect().translated(c1.position)
                    box1.setWidth(2*box1.width())
                    box1.setHeight(2*box1.height())
                    box2 = c2.boundingRect().translated(c2.position)
                    box2.setWidth(2*box2.width())
                    box2.setHeight(2*box2.height())
                    #print "box 1", box1 
                    #print "box 2", box2 
                    if box1.intersects(box2):
                        #print "intersects"
                        force = box1.intersected(box2).width() + box1.intersected(box2).height() 
                        centerdiff = box2.center()-box1.center()
                        direction = numpy.array([centerdiff.x(),centerdiff.y()])
                        norm =  numpy.linalg.norm(direction)
                        if norm != 0:
                            direction = direction / numpy.linalg.norm(direction)
                        else:
                            direction = numpy.array([0,0])
                        #direction[1] = 0.0
                        c1.force += -force*direction * force_cluster;
                        c2.force += force*direction * force_cluster;
                        #print "force 1", c1.force
                        #print "force 2", c2.force
            
            # determine forces due to connections
            for e in graph.edges():
                c1 = e[0]     
                c2 = e[1] 
                box1 = c1.boundingRect().translated(c1.position)
                box2 = c2.boundingRect().translated(c2.position)
                # force 1: pull together on x
                centerdiff = box2.center()-box1.center()
                direction = numpy.array([centerdiff.x(),0])
                norm =  numpy.linalg.norm(direction)
                if norm != 0:
                    direction = direction / numpy.linalg.norm(direction)
                else:
                    direction = numpy.array([0,0])
                goal = 0 
                force = (norm - goal) * force_connection;
                c1.force += +force*direction;
                c2.force += -force*direction;
                # force 2: keep y at distance and in layer order
                direction = numpy.array([0, centerdiff.y()])
                norm =  numpy.linalg.norm(direction)
                if norm != 0:
                    direction = direction / numpy.linalg.norm(direction)
                else:
                    direction = numpy.array([0,0])
                goal = box1.height() + box2.height()
                force = (norm - goal) * force_connection;
                c1.force += +force*direction;
                c2.force += -force*direction;
                #print "force ", force
                

            # determine forces due to clusters overlapping connections
            n = len(l)
            for c in graph.vertices():
                for e in graph.edges():
                    box1 = c.boundingRect().translated(c.position)
                    box1.setWidth(0.5*box1.width())
                    box1.setHeight(0.5*box1.height())
                    con = graph.get(e[0],e[1])
                    box2 = con.boundingRect()
                    box2.setWidth(0.5*box2.width())
                    box2.setHeight(0.5*box2.height())
                    #print "box 1", box1 
                    #print "box 2", box2 
                    if box1.intersects(box2):
                        #print "intersects"
                        force = box1.intersected(box2).width() + box1.intersected(box2).height() 
                        centerdiff = box2.center()-box1.center()
                        direction = numpy.array([centerdiff.x(),centerdiff.y()])
                        norm =  numpy.linalg.norm(direction)
                        if norm != 0:
                            direction = direction / numpy.linalg.norm(direction)
                        else:
                            direction = numpy.array([0,0])
                        c.force += -force*direction * force_cluster_connection;
                        e[0].force += force*direction * force_cluster_connection;
                        e[1].force += force*direction * force_cluster_connection;
                        #print "force 1", c1.force
                        #print "force 2", c2.force

            # apply forces 
            for c in l:
                move = QtCore.QPointF(c.force[0],c.force[1])
                c.position += move
                c.translate(move.x(), move.y())

            # uppate connectors
            for e in graph.edges():
                connector = graph.get(e[0],e[1])
                connector.determinePath()

        # done iterating
        print "done"

    def updateViewports(self):
        self.viewportManager.updateViewports()
    
    def updateSceneRect(self, rectList=None):
        self.ui.graphicsScene.setSceneRect(self.ui.graphicsScene.itemsBoundingRect())
    
    def zoomIn(self):
        """ Zoom in the graphics view, by updating the vertical slider """
        self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1)
    
    def zoomOut(self):
        """ Zoom out the graphics view, by updating the vertical slider """
        self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1)
    
    def fit(self):
        """ Fits the tree exactly in the graphics view """
        self.ui.graphicsView.fitInView(0.0, 0.0, self.ui.graphicsScene.width(), self.ui.graphicsScene.height(), QtCore.Qt.KeepAspectRatio)
        """ Update the slider """
        value = (math.log(self.ui.graphicsView.matrix().m11(),2)*50) + 250.0
        self.ui.verticalSlider.setValue(value)
    
    def setupMatrix(self, value):
        """ Zoom in/out the graphics view, depending on the value of the slider 
        
        Parameters
            value    -    value of the updated slider
        """
        scale = math.pow(2.0, (self.ui.verticalSlider.value()-250.0)/50.0)
        matrix = QtGui.QMatrix()
        matrix.scale(scale,scale)

        self.ui.graphicsView.setMatrix(matrix)
Esempio n. 2
0
class CompositionView(QtGui.QDialog):
    """ A view where the decomposition of the system of constraints is visualised as a tree """
    def __init__(self,
                 viewport,
                 viewportMngr,
                 vpType,
                 prototypeMngr,
                 parent=None):
        """ Initialization of the CompositionView class
			
		Parameters:
			viewportMngr - the manager of the viewports where the composition view can reside in
			prototypeMngr - the manager of the prototypes is used to obtain the results of the solver
		"""
        QtGui.QDialog.__init__(self, parent)
        self.prototypeManager = prototypeMngr
        self.viewport = viewport
        self.viewportManager = viewportMngr
        self.settings = Settings()
        self.setWindowFlags(QtCore.Qt.Window)
        self.timer = QtCore.QObject()
        #QtCore.qsrand(QtCore.QTime(0,0,0).secsTo(QtCore.QTime.currentTime()))

        self.tree = Tree(None)
        self.infoOverlay = CVInfoOverlay(self)
        self.connections = []
        self.ui = Ui_compositionView()
        self.ui.setupUi(self)
        self.ui.graphicsView.setupViewport(
            QtOpenGL.QGLWidget(
                QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers
                                   | QtOpenGL.QGL.DoubleBuffer)))
        #self.ui.graphicsView.setViewport(QtGui.QWidget())
        self.ui.graphicsView.setRenderHints(
            QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)

        self.collapsed = False
        self.currentTool = None
        self.viewportType = vpType
        self.first = False
        self.nodeId = 0

        self.overConstrainedColor = QtGui.QColor(0, 0, 255)
        self.underConstrainedColor = QtGui.QColor(255, 0, 0)
        self.wellConstrainedColor = QtGui.QColor(0, 255, 0)
        self.unsolvedColor = QtGui.QColor(125, 124, 255)

        self.setScene()
        self.createTriggers()

    def createTriggers(self):
        """ Create the triggers for the components in the graphical window """
        QtCore.QObject.connect(self.ui.zoomInButton,
                               QtCore.SIGNAL("clicked()"), self.zoomIn)
        QtCore.QObject.connect(self.ui.zoomOutButton,
                               QtCore.SIGNAL("clicked()"), self.zoomOut)
        QtCore.QObject.connect(self.ui.fitButton, QtCore.SIGNAL("clicked()"),
                               self.fit)
        QtCore.QObject.connect(self.ui.collapseButton,
                               QtCore.SIGNAL("clicked()"), self.collapse)
        QtCore.QObject.connect(
            self.ui.graphicsScene,
            QtCore.SIGNAL("changed(const QList<QRectF> & )"),
            self.updateSceneRect)
        QtCore.QObject.connect(self.ui.verticalSlider,
                               QtCore.SIGNAL("valueChanged(int)"),
                               self.setupMatrix)
        QtCore.QObject.connect(self.settings.dvData,
                               QtCore.SIGNAL("treeOrientationChanged()"),
                               self.updateTreeOrientation)

    def setScene(self):
        """ The scene where the tree is visualised in, will be created and set """
        self.initView()

    def getViewportType(self):
        return self.viewportType

    def updateGL(self):
        self.update()

    def createDecomposition(self):
        """ Create a new decomposition. If an older one exists it will be removed. """
        if self.ui.graphicsScene != None:
            for item in self.ui.graphicsView.items():
                item.hide()
                if item.parentItem() == None:
                    self.ui.graphicsScene.removeItem(item)
            if self.tree.root != None:
                self.tree.clear(self.tree.root)
            del self.connections[:]
            del self.settings.dvData.fixedClusterIds[:]
            self.initView()

    def initView(self):
        """ Updating the view with new data and nodes for the visualisation of the tree """
        if self.prototypeManager.result != None:
            self.nodeId = 0
            self.tree.root = self.populateTree(
                self.prototypeManager.result.subs, None,
                self.prototypeManager.result)
            # return    # Rick 20090522 debug
            self.drawTree(self.ui.graphicsScene, self.tree.root,
                          self.tree.root.children)
            self.drawConnections(self.ui.graphicsScene)
            self.determineCollapse(self.tree.root)
            self.showConnections()
            self.tree.root.showChildren()
            self.updateTree()
            self.addInfoOverlay()
            self.initFixStates()

    def updateViewports(self):
        self.viewportManager.updateViewports()

    def updateTree(self):
        """ Update the tree, where the node positions and connections between the nodes are updated """
        self.tree.clear(self.tree.root)
        self.tree.updateTree()
        self.updateNodePositions(self.tree.root)
        self.showConnections()

    def populateTree(self, nodes, rootNode, currentNode, id=0):
        """ Recursive function to populate a tree, from the results of the solver to finally display it in the Decomposition View. 
		The population is depth first.
			
		Parameters:
			nodes 		- the childnodes
			rootNode 	- root node of the (partial) tree
			currentNode	- the current node of the result obtained from the constraints solver
		"""
        if len(currentNode.variables) == 1:
            self.createLeafPoint(rootNode, currentNode.variables[0],
                                 self.nodeId)

        else:
            newNode = CVCluster(self, rootNode, self.nodeId)
            newNode.flag = currentNode.flag
            newNode.variables = currentNode.variables

            needCollapse = False
            """ Add children to the rootNode to create the full tree """
            if rootNode != None:
                #self.setCollapse(newNode)

                rootNode.children += [newNode]
                """ Create a connection between the nodes if the current node has a rootnode"""
                newConnection = CVConnection(self, rootNode, newNode)
                self.connections += [newConnection]
            """ get the leaf nodes """
            if len(nodes) == 0:
                for variable in newNode.variables:
                    self.createLeafPoint(newNode, variable, self.nodeId)

        for node in nodes:
            self.nodeId += 1
            self.populateTree(node.subs, newNode, node, self.nodeId)
        """ To return the whole tree, a check will be performed for the rootnode """
        if rootNode == None:
            return newNode

    def initFixStates(self):
        """ Initialize the fix states from another view if available """
        for fixedId in self.settings.dvData.fixedClusterIds:
            self.updateState(fixedId, True, self.tree.root)

    def stateChange(self, id, fixed):
        """ Change the state of the cluster which might be fixed and report it 
			to the other decomposition views.
		
		Paramaters: 
			id - unique id of the cluster 
			fixed - should the clusters be fixed or not 
		"""
        self.viewportManager.updateDecompositionFixed(id, fixed)
        if fixed:
            self.settings.dvData.fixedClusterIds += [id]
        elif not fixed:
            self.settings.dvData.fixedClusterIds = filter(
                lambda x: x != id, self.settings.dvData.fixedClusterIds)

    def updateState(self, id, fixed, rootNode):
        """ Update the fixed cluster, with the visuals. 
		Parameters:
			id - unique id of the cluster
			fixed - should the cluster be fixed or not
			rootNode - recursive funcion to walk the tree
		"""
        if rootNode.identifier == id:
            if fixed and rootNode.isVisible():
                rootNode.fixGraphic.show()
                rootNode.clusterActive = True
            elif fixed and not rootNode.isVisible():
                rootNode.fixGraphic.hide()
                rootNode.clusterActive = True
            else:
                rootNode.fixGraphic.hide()
                rootNode.clusterActive = False
                self.prototypeManager.removeClusterObjects(
                    [rootNode.permCluster], True, True)
                rootNode.permCluster = None
            return True

        for node in rootNode.children:
            found = self.updateState(id, fixed, node)
            if found:
                break

        return False

    def createLeafPoint(self, node, variable, id):
        cvPoint = CVPoint(self, node, id)
        cvPoint.setWidthAndHeight(20, 20)
        cvPoint.prtRef = self.prototypeManager.getObjectByKey(variable)
        cvPoint.isCollapsed = False
        cvPoint.canCollapse = False
        #cvPoint.setInfoOverlay()
        node.children += [cvPoint]
        newConnection = CVConnection(self, node, cvPoint)
        self.connections += [newConnection]

    def setCollapse(self, node):
        node.isCollapsed = False
        if not isinstance(node, CVPoint):
            if not node.collapseFromResult():
                node.updateCluster()

    def determineCollapse(self, node):
        self.setCollapse(node)
        for child in node.children:
            self.determineCollapse(child)

    def addInfoOverlay(self):
        self.ui.graphicsScene.addItem(self.infoOverlay)
        self.infoOverlay.hide()

    def drawTree(self, scene, root, childNodes):
        """ The different nodes are added to the scene and will automatically be drawn 
			
		Parameters:
			scene		- the scene where the rootnode has to be drawn in
			root		- the rootnode of the (sub-) tree
			childNode	- the children of this root node
		"""
        root.setPos(root.position)
        scene.addItem(root)
        #print "#nodes: ", len(childNodes), " position: ",root.position.x(), " " , root.position.y()

        for node in childNodes:
            self.drawTree(scene, node, node.children)

    def drawConnections(self, scene):
        """ The connections between the nodes are added to the scene and will automatically be drawn 
			
		Parameters:
			scene	- the scene where the connections has to be drawn in
		"""
        for connection in self.connections:
            #connection.setPos()
            scene.addItem(connection)

    def showConnections(self):
        """ Show/hide the connections between the nodes, this depends if the node is collapsed """
        for connection in self.connections:
            connection.setPos(connection.nodeTo.position)
            if connection.nodeFrom.isCollapsed or (
                    connection.nodeTo.isVisible() == False):
                connection.hide()
            else:
                connection.show()

    def updateConnections(self):
        for connection in self.connections:
            connection.update()

    def nrVisibleConnections(self):
        number = 0
        #for connection in self.connections:
        #print "x, y: " , connection.x(), connection.y()
        #print "nr of visible connections: " , number

    def updateNodePositions(self, node):
        """ Map the position of the nodes in the tree on the graphical view 
			
		Parameters:
			node	- a node in the tree for which the position is set
		"""
        node.setPos(node.position)

        for childNode in node.children:
            self.updateNodePositions(childNode)

    def updateSceneRect(self, rectList=None):
        self.ui.graphicsScene.setSceneRect(
            self.ui.graphicsScene.itemsBoundingRect())

    def updateTreeOrientation(self):
        self.tree.orientation = self.settings.dvData.treeAlignment

    def zoomIn(self):
        """ Zoom in the graphics view, by updating the vertical slider """
        self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1)

    def zoomOut(self):
        """ Zoom out the graphics view, by updating the vertical slider """
        self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1)

    def fit(self):
        """ Fits the tree exactly in the graphics view """
        self.ui.graphicsView.fitInView(0.0, 0.0, self.ui.graphicsScene.width(),
                                       self.ui.graphicsScene.height(),
                                       QtCore.Qt.KeepAspectRatio)
        """ Update the slider """
        value = (math.log(self.ui.graphicsView.matrix().m11(), 2) * 50) + 250.0
        self.ui.verticalSlider.setValue(value)

    def collapseAll(self, node):
        if node.canCollapse:
            node.collapse()
            for childNode in node.children:
                self.collapseAll(childNode)

    def expandAll(self, node):
        node.expand()
        for childNode in node.children:
            self.expandAll(childNode)

    def collapse(self):
        if self.collapsed:
            self.collapsed = False
            self.collapseAll(self.tree.root)
        else:
            self.collapsed = True
            self.expandAll(self.tree.root)

        self.updateTree()
        self.update()

    def setupMatrix(self, value):
        """ Zoom in/out the graphics view, depending on the value of the slider 
		
		Parameters
			value	-	value of the updated slider
		"""
        scale = math.pow(2.0, (self.ui.verticalSlider.value() - 250.0) / 50.0)
        matrix = QtGui.QMatrix()
        matrix.scale(scale, scale)

        self.ui.graphicsView.setMatrix(matrix)
Esempio n. 3
0
class CompositionView(QtGui.QDialog):
    """ A view where the decomposition of the system of constraints is visualised as a tree """
    def __init__(self, viewport, viewportMngr, vpType, prototypeMngr, parent=None):
        """ Initialization of the CompositionView class
            
        Parameters:
            viewportMngr - the manager of the viewports where the composition view can reside in
            prototypeMngr - the manager of the prototypes is used to obtain the results of the solver
        """
        QtGui.QDialog.__init__(self, parent)
        self.prototypeManager = prototypeMngr
        self.viewport = viewport
        self.viewportManager = viewportMngr
        self.settings = Settings()
        self.setWindowFlags(QtCore.Qt.Window)
        self.timer = QtCore.QObject()
        #QtCore.qsrand(QtCore.QTime(0,0,0).secsTo(QtCore.QTime.currentTime()))
        
        self.tree = Tree(None)
        self.infoOverlay = CVInfoOverlay(self)
        self.connections = []
        self.ui = Ui_compositionView()
        self.ui.setupUi(self)
        self.ui.graphicsView.setupViewport(QtOpenGL.QGLWidget(QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers|QtOpenGL.QGL.DoubleBuffer)))
        #self.ui.graphicsView.setViewport(QtGui.QWidget())
        self.ui.graphicsView.setRenderHints(QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)
        
        self.collapsed = False
        self.currentTool = None
        self.viewportType = vpType
        self.first = False
        self.nodeId = 0

        self.overConstrainedColor = QtGui.QColor(0,0,255)
        self.underConstrainedColor = QtGui.QColor(255,0,0)
        self.wellConstrainedColor = QtGui.QColor(0,255,0)
        self.unsolvedColor = QtGui.QColor(125,124,255)
        
        self.setScene()
        self.createTriggers()
        
    def createTriggers(self):
        """ Create the triggers for the components in the graphical window """ 
        QtCore.QObject.connect(self.ui.zoomInButton,QtCore.SIGNAL("clicked()"),self.zoomIn)
        QtCore.QObject.connect(self.ui.zoomOutButton,QtCore.SIGNAL("clicked()"),self.zoomOut)
        QtCore.QObject.connect(self.ui.fitButton, QtCore.SIGNAL("clicked()"), self.fit)
        QtCore.QObject.connect(self.ui.collapseButton, QtCore.SIGNAL("clicked()"), self.collapse)
        QtCore.QObject.connect(self.ui.graphicsScene, QtCore.SIGNAL("changed(const QList<QRectF> & )"), self.updateSceneRect)
        QtCore.QObject.connect(self.ui.verticalSlider,QtCore.SIGNAL("valueChanged(int)"),self.setupMatrix)
        QtCore.QObject.connect(self.settings.dvData,QtCore.SIGNAL("treeOrientationChanged()"), self.updateTreeOrientation)
            
    def setScene(self):
        """ The scene where the tree is visualised in, will be created and set """
        self.initView()
    
    def getViewportType(self):
        return self.viewportType
        
    def updateGL(self):
        self.update()
    
    def createDecomposition(self):
        """ Create a new decomposition. If an older one exists it will be removed. """ 
        if self.ui.graphicsScene != None:
            for item in self.ui.graphicsView.items():
                item.hide()
                if item.parentItem() == None:
                    self.ui.graphicsScene.removeItem(item)
            if self.tree.root != None:
                self.tree.clear(self.tree.root)
            del self.connections[:]
            del self.settings.dvData.fixedClusterIds[:]
            self.initView()
            
    def initView(self):
        """ Updating the view with new data and nodes for the visualisation of the tree """
        if self.prototypeManager.result != None:
            self.nodeId = 0
            self.tree.root = self.populateTree(self.prototypeManager.result.subs, None, self.prototypeManager.result)
            self.drawTree(self.ui.graphicsScene, self.tree.root, self.tree.root.children)
            self.drawConnections(self.ui.graphicsScene)
            self.determineCollapse(self.tree.root)
            self.showConnections()
            self.tree.root.showChildren()
            self.updateTree()
            self.addInfoOverlay()
            self.initFixStates()
                
    def updateViewports(self):
        self.viewportManager.updateViewports()
    
    def updateTree(self):
        """ Update the tree, where the node positions and connections between the nodes are updated """
        self.tree.clear(self.tree.root)
        self.tree.updateTree()
        self.updateNodePositions(self.tree.root)        
        self.showConnections()
        
    def populateTree(self, nodes, rootNode, currentNode, id=0):
        """ Recursive function to populate a tree, from the results of the solver to finally display it in the Decomposition View. 
        The population is depth first.
            
        Parameters:
            nodes         - the childnodes
            rootNode     - root node of the (partial) tree
            currentNode    - the current node of the result obtained from the constraints solver
        """    
        if len(currentNode.variables) == 1:
            self.createLeafPoint(rootNode, currentNode.variables[0], self.nodeId)
            
        else:
            newNode = CVCluster(self, rootNode, self.nodeId)
            newNode.flag = currentNode.flag
            newNode.variables = currentNode.variables
            
            needCollapse = False        
            """ Add children to the rootNode to create the full tree """
            if rootNode != None:
                #self.setCollapse(newNode)
                
                rootNode.children += [newNode]
    
                """ Create a connection between the nodes if the current node has a rootnode"""
                newConnection = CVConnection(self, rootNode, newNode)
                self.connections += [newConnection]        
        
            """ get the leaf nodes """
            if len(nodes) == 0:
                for variable in newNode.variables:
                    self.createLeafPoint(newNode, variable, self.nodeId)
       
        # Rick 20091116 - skip this for debug
        # for node in nodes:
        #    self.nodeId += 1
        #    self.populateTree(node.subs, newNode, node, self.nodeId)
        
        """ To return the whole tree, a check will be performed for the rootnode """
        if rootNode == None:
            return newNode
    
    def initFixStates(self):
        """ Initialize the fix states from another view if available """
        for fixedId in self.settings.dvData.fixedClusterIds:
            self.updateState(fixedId, True, self.tree.root)
    
    def stateChange(self, id, fixed):
        """ Change the state of the cluster which might be fixed and report it 
            to the other decomposition views.
        
        Paramaters: 
            id - unique id of the cluster 
            fixed - should the clusters be fixed or not 
        """
        self.viewportManager.updateDecompositionFixed(id, fixed)
        if fixed:
            self.settings.dvData.fixedClusterIds += [id]
        elif not fixed:
            self.settings.dvData.fixedClusterIds = filter(lambda x:x!=id, self.settings.dvData.fixedClusterIds)
            
    def updateState(self, id, fixed, rootNode):
        """ Update the fixed cluster, with the visuals. 
        Parameters:
            id - unique id of the cluster
            fixed - should the cluster be fixed or not
            rootNode - recursive funcion to walk the tree
        """
        if rootNode.identifier == id:
            if fixed and rootNode.isVisible():
                rootNode.fixGraphic.show()
                rootNode.clusterActive = True
            elif fixed and not rootNode.isVisible():
                rootNode.fixGraphic.hide()
                rootNode.clusterActive = True
            else:
                rootNode.fixGraphic.hide()
                rootNode.clusterActive = False
                self.prototypeManager.removeClusterObjects([rootNode.permCluster], True, True)
                rootNode.permCluster = None
            return True
        
        for node in rootNode.children:
            found = self.updateState(id, fixed, node)
            if found:
                break
            
        return False
    
    def createLeafPoint(self, node, variable, id):
        cvPoint = CVPoint(self, node, id)
        cvPoint.setWidthAndHeight(20, 20)
        cvPoint.prtRef = self.prototypeManager.getObjectByKey(variable) 
        cvPoint.isCollapsed = False
        cvPoint.canCollapse = False
        #cvPoint.setInfoOverlay()
        node.children += [cvPoint]
        newConnection = CVConnection(self, node, cvPoint)
        self.connections += [newConnection]
    
    def setCollapse(self, node):
        node.isCollapsed = False
        if not isinstance(node, CVPoint):
            if not node.collapseFromResult():
                node.updateCluster()
    
    def determineCollapse(self, node):
        self.setCollapse(node)
        for child in node.children:
            self.determineCollapse(child)
        
    def addInfoOverlay(self):
        self.ui.graphicsScene.addItem(self.infoOverlay)
        self.infoOverlay.hide()
            
    def drawTree(self, scene, root, childNodes):
        """ The different nodes are added to the scene and will automatically be drawn 
            
        Parameters:
            scene        - the scene where the rootnode has to be drawn in
            root        - the rootnode of the (sub-) tree
            childNode    - the children of this root node
        """
        root.setPos(root.position)
        scene.addItem(root)
        #print "#nodes: ", len(childNodes), " position: ",root.position.x(), " " , root.position.y()

        for node in childNodes:    
            self.drawTree(scene, node, node.children)
    
    def drawConnections(self, scene):
        """ The connections between the nodes are added to the scene and will automatically be drawn 
            
        Parameters:
            scene    - the scene where the connections has to be drawn in
        """
        for connection in self.connections:
            #connection.setPos()
            scene.addItem(connection)
    
    def showConnections(self):
        """ Show/hide the connections between the nodes, this depends if the node is collapsed """
        for connection in self.connections:
            connection.setPos(connection.nodeTo.position)
            if connection.nodeFrom.isCollapsed or (connection.nodeTo.isVisible() == False):
                connection.hide()
            else:
                connection.show()
    
    def updateConnections(self):
        for connection in self.connections:
            connection.update()
    
    def nrVisibleConnections(self):
        number = 0
        #for connection in self.connections:
            #print "x, y: " , connection.x(), connection.y()
        #print "nr of visible connections: " , number 
    
    def updateNodePositions(self, node):
        """ Map the position of the nodes in the tree on the graphical view 
            
        Parameters:
            node    - a node in the tree for which the position is set
        """
        node.setPos(node.position)

        for childNode in node.children:
            self.updateNodePositions(childNode)

    def updateSceneRect(self, rectList=None):
        self.ui.graphicsScene.setSceneRect(self.ui.graphicsScene.itemsBoundingRect())
    
    def updateTreeOrientation(self):
        self.tree.orientation = self.settings.dvData.treeAlignment
            
    def zoomIn(self):
        """ Zoom in the graphics view, by updating the vertical slider """
        self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1)
    
    def zoomOut(self):
        """ Zoom out the graphics view, by updating the vertical slider """
        self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1)
    
    def fit(self):
        """ Fits the tree exactly in the graphics view """
        self.ui.graphicsView.fitInView(0.0, 0.0, self.ui.graphicsScene.width(), self.ui.graphicsScene.height(), QtCore.Qt.KeepAspectRatio)
        """ Update the slider """
        value = (math.log(self.ui.graphicsView.matrix().m11(),2)*50) + 250.0
        self.ui.verticalSlider.setValue(value)
    
    def collapseAll(self, node):
        if node.canCollapse:
            node.collapse()
            for childNode in node.children:
                self.collapseAll(childNode)
        
    def expandAll(self, node):
        node.expand()
        for childNode in node.children:
            self.expandAll(childNode)
        
    def collapse(self):
        if self.collapsed:
            self.collapsed = False
            self.collapseAll(self.tree.root)
        else:
            self.collapsed = True
            self.expandAll(self.tree.root)
            
        self.updateTree()
        self.update()
            
    def setupMatrix(self, value):
        """ Zoom in/out the graphics view, depending on the value of the slider 
        
        Parameters
            value    -    value of the updated slider
        """
        scale = math.pow(2.0, (self.ui.verticalSlider.value()-250.0)/50.0)
        matrix = QtGui.QMatrix()
        matrix.scale(scale,scale)

        self.ui.graphicsView.setMatrix(matrix)
class DecompositionView(QtGui.QDialog):
    """ A view where the decomposition of the system of constraints is visualised as a directed acyclic graph"""
    def __init__(self,
                 viewport,
                 viewportMngr,
                 vpType,
                 prototypeMngr,
                 parent=None):
        """ Initialization of the CompositionView class
            
        Parameters:
            viewportMngr - the manager of the viewports where the composition view can reside in
            prototypeMngr - the manager of the prototypes is used to obtain the results of the solver
        """
        QtGui.QDialog.__init__(self, parent)
        self.prototypeManager = prototypeMngr
        self.viewport = viewport
        self.viewportManager = viewportMngr
        self.settings = Settings()
        self.setWindowFlags(QtCore.Qt.Window)
        self.timer = QtCore.QObject()
        """map GeometricDecomposition to CVCluster"""
        self.map = {}

        self.ui = Ui_compositionView()
        self.ui.setupUi(self)
        self.ui.graphicsView.setupViewport(
            QtOpenGL.QGLWidget(
                QtOpenGL.QGLFormat(QtOpenGL.QGL.SampleBuffers
                                   | QtOpenGL.QGL.DoubleBuffer)))
        self.ui.graphicsView.setRenderHints(
            QtGui.QPainter.Antialiasing | QtGui.QPainter.SmoothPixmapTransform)

        self.currentTool = None
        self.viewportType = vpType
        self.orientation = TreeOrientation.BOTTOM

        self.overConstrainedColor = QtGui.QColor(0, 0, 255)
        self.underConstrainedColor = QtGui.QColor(255, 0, 0)
        self.wellConstrainedColor = QtGui.QColor(0, 255, 0)
        self.unsolvedColor = QtGui.QColor(125, 124, 255)

        self.createScene()
        self.createTriggers()

    def createTriggers(self):
        """ Create the triggers for the components in the graphical window """
        QtCore.QObject.connect(self.ui.zoomInButton,
                               QtCore.SIGNAL("clicked()"), self.zoomIn)
        QtCore.QObject.connect(self.ui.zoomOutButton,
                               QtCore.SIGNAL("clicked()"), self.zoomOut)
        QtCore.QObject.connect(self.ui.fitButton, QtCore.SIGNAL("clicked()"),
                               self.fit)
        #QtCore.QObject.connect(self.ui.collapseButton, QtCore.SIGNAL("clicked()"), self.collapse)
        QtCore.QObject.connect(
            self.ui.graphicsScene,
            QtCore.SIGNAL("changed(const QList<QRectF> & )"),
            self.updateSceneRect)
        QtCore.QObject.connect(self.ui.verticalSlider,
                               QtCore.SIGNAL("valueChanged(int)"),
                               self.setupMatrix)
        #QtCore.QObject.connect(self.settings.dvData,QtCore.SIGNAL("treeOrientationChanged()"), self.updateTreeOrientation)

    def getViewportType(self):
        return self.viewportType

    def updateGL(self):
        self.update()

    def createDecomposition(self):
        """ Create a new decomposition. If an older one exists it will be removed. """
        self.clearScene()
        self.createScene()

    def clearScene(self):
        self.map = {}
        if self.ui.graphicsScene != None:
            for item in self.ui.graphicsView.items():
                item.hide()
                if item.parentItem() == None:
                    self.ui.graphicsScene.removeItem(item)

    def createScene(self):
        """ Updating the view with new data and nodes for the visualisation of the tree """
        if self.prototypeManager.result != None:
            # get all clusters from result
            new = [self.prototypeManager.result]
            clusters = set()
            while len(new) > 0:
                c = new.pop()
                clusters.add(c)
                for child in c.subs:
                    if child not in clusters:
                        new.append(child)
            # create N layers for clusters with 1-N variables
            N = len(self.prototypeManager.result.variables)
            layers = []
            for n in range(0, N + 1):
                layers.append([])
            # add clusters to layers
            for c in clusters:
                n = len(c.variables)
                layers[n].append(c)
            # sort clusters in layers
            # start from layer N (largest clusters)
            # clusters are initially ordered according to the order in which sub-clusters appear in the previous (n+1) layer
            for n in reversed(range(1, N)):
                print "ordering layers", n
                # find subiable pseudo-ordering in previous layers
                subordervalue = {}
                clusterindex = 0
                for cluster in layers[n + 1]:
                    clusterindex = clusterindex + 1
                    for sub in cluster.subs:
                        # order by first appearence in cluster from left to right
                        if sub not in subordervalue:
                            subordervalue[sub] = clusterindex
                # determine pseudo-order clusters in this layers: sum subordervalues per cluster
                clusterordervalue = {}
                for cluster in layers[n]:
                    clusterordervalue[cluster] = 0
                    for sub in cluster.subs:
                        if sub in subordervalue:
                            clusterordervalue[cluster] += subordervalue[sub]
                # sort clusters in layers
                layers[n].sort(
                    lambda x, y: clusterordervalue[x] < clusterordervalue[y])

            # map GeometricDecompositions to CVClusters
            for n in range(0, N + 1):
                layer = layers[n]
                for k in range(0, len(layer)):
                    c = layer[k]
                    y = n * 50.0
                    x = (k - len(layer) / 2.0) * 50.0 * n
                    cvcluster = CVCluster(self, c, x, y)
                    self.ui.graphicsScene.addItem(cvcluster)
                    self.map[c] = cvcluster
                    self.map[cvcluster] = c
            # add CVConnections
            for c in clusters:
                for child in c.subs:
                    self.ui.graphicsScene.addItem(
                        CVConnection(self, self.map[c], self.map[child]))
            # iteratively improve graph layout
            self.optimiseGraphLayout()

    def optimiseGraphLayout(self):
        print "optimising graph layout..."
        # force due to overlapping overlaps
        force_cluster = 0.2
        # force due to connection length
        force_connection = 0.025
        # force due to clusters overlapping connections
        force_cluster_connection = 0.05

        # create a graph of clusters and connections
        graph = geosolver.graph.Graph()
        if self.ui.graphicsScene != None:
            for item in self.ui.graphicsView.items():
                if isinstance(item, CVCluster):
                    graph.add_vertex(item)
                    item.force = numpy.array([0.0, 0.0])
                elif isinstance(item, CVConnection):
                    graph.add_edge(item.nodeFrom, item.nodeTo, item)

        l = list(graph.vertices())

        # iteratively improve layout
        for i in range(100):
            # clear forces
            for c in l:
                c.force = numpy.array(
                    [random.random() * 0,
                     random.random() * 0])

            # determine forces due to overlapping cluster boxes
            n = len(l)
            for i in range(n):
                for j in range(i + 1, n):
                    c1 = l[i]
                    c2 = l[j]
                    box1 = c1.boundingRect().translated(c1.position)
                    box1.setWidth(2 * box1.width())
                    box1.setHeight(2 * box1.height())
                    box2 = c2.boundingRect().translated(c2.position)
                    box2.setWidth(2 * box2.width())
                    box2.setHeight(2 * box2.height())
                    #print "box 1", box1
                    #print "box 2", box2
                    if box1.intersects(box2):
                        #print "intersects"
                        force = box1.intersected(
                            box2).width() + box1.intersected(box2).height()
                        centerdiff = box2.center() - box1.center()
                        direction = numpy.array(
                            [centerdiff.x(), centerdiff.y()])
                        norm = numpy.linalg.norm(direction)
                        if norm != 0:
                            direction = direction / numpy.linalg.norm(
                                direction)
                        else:
                            direction = numpy.array([0, 0])
                        #direction[1] = 0.0
                        c1.force += -force * direction * force_cluster
                        c2.force += force * direction * force_cluster
                        #print "force 1", c1.force
                        #print "force 2", c2.force

            # determine forces due to connections
            for e in graph.edges():
                c1 = e[0]
                c2 = e[1]
                box1 = c1.boundingRect().translated(c1.position)
                box2 = c2.boundingRect().translated(c2.position)
                # force 1: pull together on x
                centerdiff = box2.center() - box1.center()
                direction = numpy.array([centerdiff.x(), 0])
                norm = numpy.linalg.norm(direction)
                if norm != 0:
                    direction = direction / numpy.linalg.norm(direction)
                else:
                    direction = numpy.array([0, 0])
                goal = 0
                force = (norm - goal) * force_connection
                c1.force += +force * direction
                c2.force += -force * direction
                # force 2: keep y at distance and in layer order
                direction = numpy.array([0, centerdiff.y()])
                norm = numpy.linalg.norm(direction)
                if norm != 0:
                    direction = direction / numpy.linalg.norm(direction)
                else:
                    direction = numpy.array([0, 0])
                goal = box1.height() + box2.height()
                force = (norm - goal) * force_connection
                c1.force += +force * direction
                c2.force += -force * direction
                #print "force ", force

            # determine forces due to clusters overlapping connections
            n = len(l)
            for c in graph.vertices():
                for e in graph.edges():
                    box1 = c.boundingRect().translated(c.position)
                    box1.setWidth(0.5 * box1.width())
                    box1.setHeight(0.5 * box1.height())
                    con = graph.get(e[0], e[1])
                    box2 = con.boundingRect()
                    box2.setWidth(0.5 * box2.width())
                    box2.setHeight(0.5 * box2.height())
                    #print "box 1", box1
                    #print "box 2", box2
                    if box1.intersects(box2):
                        #print "intersects"
                        force = box1.intersected(
                            box2).width() + box1.intersected(box2).height()
                        centerdiff = box2.center() - box1.center()
                        direction = numpy.array(
                            [centerdiff.x(), centerdiff.y()])
                        norm = numpy.linalg.norm(direction)
                        if norm != 0:
                            direction = direction / numpy.linalg.norm(
                                direction)
                        else:
                            direction = numpy.array([0, 0])
                        c.force += -force * direction * force_cluster_connection
                        e[0].force += force * direction * force_cluster_connection
                        e[1].force += force * direction * force_cluster_connection
                        #print "force 1", c1.force
                        #print "force 2", c2.force

            # apply forces
            for c in l:
                move = QtCore.QPointF(c.force[0], c.force[1])
                c.position += move
                c.translate(move.x(), move.y())

            # uppate connectors
            for e in graph.edges():
                connector = graph.get(e[0], e[1])
                connector.determinePath()

        # done iterating
        print "done"

    def updateViewports(self):
        self.viewportManager.updateViewports()

    def updateSceneRect(self, rectList=None):
        self.ui.graphicsScene.setSceneRect(
            self.ui.graphicsScene.itemsBoundingRect())

    def zoomIn(self):
        """ Zoom in the graphics view, by updating the vertical slider """
        self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() + 1)

    def zoomOut(self):
        """ Zoom out the graphics view, by updating the vertical slider """
        self.ui.verticalSlider.setValue(self.ui.verticalSlider.value() - 1)

    def fit(self):
        """ Fits the tree exactly in the graphics view """
        self.ui.graphicsView.fitInView(0.0, 0.0, self.ui.graphicsScene.width(),
                                       self.ui.graphicsScene.height(),
                                       QtCore.Qt.KeepAspectRatio)
        """ Update the slider """
        value = (math.log(self.ui.graphicsView.matrix().m11(), 2) * 50) + 250.0
        self.ui.verticalSlider.setValue(value)

    def setupMatrix(self, value):
        """ Zoom in/out the graphics view, depending on the value of the slider 
        
        Parameters
            value    -    value of the updated slider
        """
        scale = math.pow(2.0, (self.ui.verticalSlider.value() - 250.0) / 50.0)
        matrix = QtGui.QMatrix()
        matrix.scale(scale, scale)

        self.ui.graphicsView.setMatrix(matrix)