Changeset 7847:957568a68c03 in orange


Ignore:
Timestamp:
04/15/11 13:35:38 (3 years ago)
Author:
ales_erjavec <ales.erjavec@…>
Branch:
default
Convert:
cf0818b241b672ec26182650251c213e40f506ad
Message:

Added DendrogramWidget, DendrogramLayout, CutoffLine class (will replace HierarchicalClusterItem in the future).

File:
1 edited

Legend:

Unmodified
Added
Removed
  • orange/OrangeWidgets/OWClustering.py

    r6561 r7847  
    11from OWWidget import * 
     2from OWColorPalette import ColorPaletteHSV 
    23from functools import partial 
    34 
     
    1213        self._selected = False 
    1314        self._highlight = False 
    14         self.orientation = kwargs.get("orientation", Qt.Horizontal) 
    15         self.highlightPen = QPen(Qt.black, 2) 
     15        self.highlightPen = QPen(Qt.blue, 2) 
    1616        self.highlightPen.setCosmetic(True) 
    1717        self.standardPen = QPen(Qt.blue, 1) 
     
    3333        self.setPen(self.standardPen) 
    3434        self.setBrush(QBrush(Qt.white, Qt.SolidPattern)) 
    35 ##        self.setAcceptHoverEvents(True) 
     35#        self.setAcceptHoverEvents(True) 
    3636         
    3737        if self.isTopLevel(): ## top level cluster 
     
    7676    def setSize(self, width, height): 
    7777        if self.isTopLevel(): 
    78             scaleY = float(height) / self.cluster.height 
     78            scaleY = (float(height) / self.cluster.height) if self.cluster.height else 0.0 
    7979            scaleX = float(width) / len(self.cluster)   
    8080            self.clusterGeometryReset(scaleX, scaleY) 
     
    129129        """ 
    130130        return len(self.cluster) 
    131  
    132          
     131     
     132    def hoverEnterEvent(self, event): 
     133        self.setHighlight(True) 
     134         
     135    def hoverLeaveEvent(self, event): 
     136        self.setHighlight(False) 
     137         
     138from Orange.clustering import hierarchical 
     139 
     140class DendrogramItem(QGraphicsRectItem): 
     141    """ A Graphics item representing a cluster in a DendrogramWidget. 
     142    """ 
     143    def __init__(self, cluster=None, orientation=Qt.Vertical, parent=None, scene=None): 
     144        QGraphicsRectItem.__init__(self, parent) 
     145#        self.setCacheMode(QGraphicsItem.NoCache) 
     146        self._highlight = False 
     147        self._path = QPainterPath() 
     148        self.setFlag(QGraphicsItem.ItemIsSelectable, True) 
     149        self.setAcceptHoverEvents(True) 
     150        self.orientation = orientation 
     151        self.set_cluster(cluster) 
     152        if scene is not None: 
     153            scene.addItem(self) 
     154             
     155    def set_cluster(self, cluster): 
     156        """ Set the cluster for this item. 
     157        """ 
     158        self.cluster = cluster 
     159        self.setToolTip("Height: %f" % cluster.height) 
     160        self.updatePath() 
     161        self.update() 
     162         
     163    def set_highlight(self, state): 
     164        """ Set highlight state for this item. Highlighted items are drawn 
     165        with a wider pen. 
     166         
     167        """ 
     168        for cl in hierarchical.preorder(self): 
     169            cl._highlight = state 
     170            cl.update()  
     171         
     172    @property 
     173    def highlight(self): 
     174        return self._highlight 
     175         
     176    @property     
     177    def branches(self): 
     178        """ Branch items. 
     179        """ 
     180        parent = self.parentWidget() 
     181        if self.cluster.branches and isinstance(parent, DendrogramWidget): 
     182            return [parent.item(branch) for branch in self.cluster.branches] 
     183        else: 
     184            return [] 
     185     
     186    def setGeometry(self, rect): 
     187        self.setRect(rect) 
     188        self.updatePath() 
     189         
     190    def setRect(self, rect): 
     191        QGraphicsRectItem.setRect(self, rect) 
     192        self.updatePath() 
     193         
     194    def sizeHint(self, which, constraint=QRectF()): 
     195        # Called by GraphicsRectLayout 
     196        if self.cluster: 
     197            parent = self.parentWidget() 
     198            font = parent.font() if parent is not None else QFont() 
     199            metrics = QFontMetrics(font) 
     200            spacing = metrics.lineSpacing() 
     201            if self.orientation == Qt.Vertical: 
     202                return QSizeF(self.cluster.height, spacing) 
     203            else: 
     204                return QSizeF(spacing, self.cluster.height) 
     205        else: 
     206            return QSizeF(0.0, 0.0) 
     207         
     208    def shape(self): 
     209        path = QPainterPath() 
     210        path.addRect(self.rect()) 
     211        return path 
     212     
     213    def boundingRect(self): 
     214        return self.rect().adjusted(-2, -2, 2, 2) 
     215 
     216    def paint(self, painter, option, widget=0): 
     217        painter.save() 
     218        path = self._path 
     219         
     220        if self.highlight: 
     221            color = QColor(Qt.blue) 
     222            pen_w = 2 
     223        else: 
     224            color = QColor(Qt.blue) 
     225            pen_w = 1 
     226             
     227        pen = QPen(color, pen_w) 
     228        pen.setCosmetic(True) 
     229        pen.setCapStyle(Qt.FlatCap) 
     230        pen.setJoinStyle(Qt.RoundJoin) 
     231        painter.setPen(pen) 
     232        painter.drawPath(path) 
     233        painter.restore() 
     234         
     235    def hoverEnterEvent(self, event): 
     236        parent = self.parentWidget() 
     237        if isinstance(parent, DendrogramWidget): 
     238            parent.set_highlighted_item(self) 
     239         
     240    def hoverLeaveEvent(self, event): 
     241        parent = self.parentWidget() 
     242        if isinstance(parent, DendrogramWidget): 
     243            parent.set_highlighted_item(None) 
     244         
     245    def updatePath(self): 
     246        path = QPainterPath() 
     247         
     248        rect = self.rect() 
     249        branches = self.branches 
     250        if branches: 
     251            if self.orientation == Qt.Vertical: 
     252                leftrect = branches[0].rect() 
     253                rightrect = branches[-1].rect() 
     254                path.moveTo(QPointF(leftrect.left(), rect.top())) 
     255                path.lineTo(rect.topLeft()) 
     256                path.lineTo(rect.bottomLeft()) 
     257                path.lineTo(QPointF(rightrect.left(), rect.bottom())) 
     258            else: 
     259                leftrect = branches[0].rect() 
     260                rightrect = branches[-1].rect() 
     261                path.moveTo(QPointF(rect.left(), leftrect.bottom())) 
     262                path.lineTo(rect.bottomLeft()) 
     263                path.lineTo(rect.bottomRight()) 
     264                path.lineTo(QPointF(rect.right(), rightrect.bottom())) 
     265        else: 
     266            if self.orientation == Qt.Vertical: 
     267                path.moveTo(rect.topRight()) 
     268                path.lineTo(rect.topLeft()) 
     269                path.lineTo(rect.bottomLeft()) 
     270                path.lineTo(rect.bottomRight()) 
     271            else: 
     272                path.moveTo(rect.topLeft()) 
     273                path.lineTo(rect.bottomLeft()) 
     274                path.lineTo(rect.bottomRight()) 
     275                path.lineTo(rect.topRight()) 
     276#        stroke = QPainterPathStroker() 
     277#        path = stroke.createStroke(path) 
     278        self._path = path 
     279#        self.setPath(path) 
     280     
     281    def itemChange(self, change, value): 
     282        if change == QGraphicsItem.ItemSelectedHasChanged:  
     283            widget = self.parentWidget() 
     284            if isinstance(widget, DendrogramWidget): 
     285                # Notify the DendrogramWidget of the change in the selection 
     286                return QVariant(widget.item_selection(self, value.toBool())) 
     287        return value 
     288             
     289         
     290class GraphicsRectLayoutItem(QGraphicsLayoutItem): 
     291    """ A wrapper for a QGraphicsRectItem allowing the item to 
     292    be managed by a QGraphicsLayout. 
     293      
     294    """ 
     295     
     296    def __init__(self, item, parent=None): 
     297        QGraphicsLayoutItem.__init__(self, parent) 
     298        self.item = item 
     299        self.setGraphicsItem(item) 
     300         
     301    def setGeometry(self, rect): 
     302        self.item.setRect(rect) 
     303         
     304    def sizeHint(self, which, constraint=QRectF()): 
     305        if hasattr(self.item, "sizeHint"): 
     306            return self.item.sizeHint(which, constraint) 
     307        else: 
     308            return self.item.rect() 
     309     
     310    def __getattr__(self, name): 
     311        if hasattr(self.item, name): 
     312            return getattr(self.item, name) 
     313        else: 
     314            raise AttributeError(name) 
     315     
     316class DendrogramLayout(QGraphicsLayout): 
     317    """ A graphics layout managing the DendrogramItem's in a DendrogramWidget. 
     318    """ 
     319    def __init__(self, widget, orientation=Qt.Horizontal): 
     320        assert(isinstance(widget, DendrogramWidget)) 
     321        QGraphicsLayout.__init__(self, widget) 
     322        self.widget = widget 
     323        self.orientation = orientation 
     324        self._root = None 
     325        self._items = [] 
     326        self._clusters = [] 
     327        self._selection_poly_adjust = 0 
     328     
     329    def setDendrogram(self, root, items): 
     330        """ Set the dendrogram items for layout. 
     331         
     332        :param root: a root HierarchicalCluster instance 
     333        :param item: a list of DendrogramItems to layout 
     334          
     335        """ 
     336        self._root = root 
     337        self._items = items 
     338        self._clusters = [item.cluster for item in items] 
     339        self._layout = hierarchical.dendrogram_layout(root, False) 
     340        self._layout_dict = dict(self._layout) 
     341        self._cached_geometry = {} 
     342         
     343        self.invalidate() 
     344         
     345    def do_layout(self): 
     346        if self._items and self._root: 
     347            leaf_item_count = len([item for item in self._items 
     348                                   if not item.cluster.branches]) 
     349            cluster_width = float(leaf_item_count - 1) 
     350            root_height = self._root.height 
     351            c_rect = self.contentsRect() 
     352             
     353            if self.orientation == Qt.Vertical: 
     354                height_scale = c_rect.width() / root_height 
     355                width_scale =  c_rect.height() / cluster_width 
     356                x_offset = self._selection_poly_adjust + c_rect.left()  
     357                y_offset = c_rect.top() #width_scale / 2.0 
     358            else: 
     359                height_scale = c_rect.height() / root_height 
     360                width_scale =  c_rect.width() / cluster_width 
     361                x_offset = c_rect.left() # width_scale / 2.0  
     362                y_offset = self._selection_poly_adjust + c_rect.top() 
     363                 
     364            for item, cluster in zip(self._items, self._clusters): 
     365                start, center, end = self._layout_dict[cluster] 
     366                if self.orientation == Qt.Vertical: 
     367                    # Should this be translated so all items have positive x coordinates 
     368                    rect = QRectF(-cluster.height * height_scale, start * width_scale, 
     369                                  cluster.height * height_scale, (end - start) * width_scale) 
     370                    rect.translate(c_rect.width() + x_offset, 0 + y_offset) 
     371                else: 
     372                    rect = QRectF(start * width_scale, 0.0, 
     373                                  (end - start) * width_scale, cluster.height * height_scale) 
     374                    rect.translate(0 + x_offset,  y_offset) 
     375                     
     376                if rect.isEmpty(): 
     377                    rect.setSize(QSizeF(max(rect.width(), 0.001), max(rect.height(), 0.001))) 
     378                     
     379                item.setGeometry(rect) 
     380                item.setZValue(root_height - cluster.height) 
     381                 
     382            self.widget._update_selection_items() 
     383     
     384    def setGeometry(self, geometry): 
     385        QGraphicsLayout.setGeometry(self, geometry) 
     386        self.do_layout() 
     387         
     388    def sizeHint(self, which, constraint=QSizeF()): 
     389        if self._root and which == Qt.PreferredSize: 
     390            leaf_items = [item for item in self._items 
     391                          if not item.cluster.branches] 
     392            hints = [item.sizeHint(which) for item in leaf_items] 
     393            if self.orientation == Qt.Vertical: 
     394                height = sum([hint.height() for hint in hints] + [0]) 
     395                width = 100 
     396            else: 
     397                height = 100 
     398                width = sum([hint.width() for hint in hints] + [0]) 
     399            return QSizeF(width, height) 
     400        else: 
     401            return QSizeF() 
     402     
     403    def count(self): 
     404        return len(self._items) 
     405     
     406    def itemAt(self, index): 
     407        return self._items[index] 
     408     
     409    def removeItem(self, index): 
     410        del self._items[index] 
     411         
     412    def widgetEvent(self, event): 
     413        if event.type() == QEvent.FontChange: 
     414            self.invalidate() 
     415        return QGraphicsLayout.widgetEvent(self, event) 
     416     
     417     
     418class SelectionPolygon(QGraphicsPolygonItem): 
     419    """ A Selection polygon covering the selected dendrogram sub tree. 
     420    """ 
     421    def __init__(self, polygon, parent=None): 
     422        QGraphicsPolygonItem.__init__(self, polygon, parent) 
     423        self.setBrush(QBrush(QColor(255, 0, 0, 100))) 
     424         
     425     
     426def selection_polygon_from_item(item, adjust=3): 
     427    """ Construct a polygon covering the dendrogram rooted at item. 
     428    """ 
     429    polygon = QPolygonF() 
     430    for item in hierarchical.preorder(item): 
     431        adjusted = item.rect().adjusted(-adjust, -adjust, adjust, adjust) 
     432        polygon = polygon.united(QPolygonF(adjusted)) 
     433    return polygon 
     434 
     435     
     436class DendrogramWidget(QGraphicsWidget): 
     437    """ A Graphics Widget displaying a dendrogram.  
     438    """ 
     439    def __init__(self, root=None, parent=None, orientation=Qt.Vertical, scene=None): 
     440        QGraphicsWidget.__init__(self, parent) 
     441        self.setLayout(DendrogramLayout(self, orientation=orientation)) 
     442        self.orientation = orientation 
     443        self._highlighted_item = None 
     444        if scene is not None: 
     445            scene.addItem(self) 
     446        self.set_root(root) 
     447         
     448    def clear(self): 
     449        pass 
     450     
     451    def set_root(self, root): 
     452        """ Set the root cluster. 
     453         
     454        :param root: Root cluster. 
     455        :type root: :class:`Orange.clustering.hierarchical.HierarchicalCluster` 
     456          
     457        """ 
     458        self.clear() 
     459        self.root_cluster = root 
     460        self.dendrogram_items = {} 
     461        self.cluster_parent = {} 
     462        self.selected_items = {} 
     463        if root: 
     464            items = [] 
     465            for cluster in hierarchical.postorder(self.root_cluster): 
     466                item = DendrogramItem(cluster, parent=self, orientation=self.orientation) 
     467                  
     468                for branch in cluster.branches or []: 
     469                    branch_item = self.dendrogram_items[branch]  
     470#                    branch_item.setParentItem(item) 
     471                    self.cluster_parent[branch] = cluster 
     472                items.append(GraphicsRectLayoutItem(item)) 
     473                self.dendrogram_items[cluster] = item 
     474                 
     475            self.layout().setDendrogram(root, items) 
     476#            self.dendrogram_items[root].setParentItem(self) 
     477             
     478            self.resize(self.layout().sizeHint(Qt.PreferredSize)) 
     479            self.layout().activate() 
     480             
     481    def item(self, cluster): 
     482        """ Return the DendrogramItem instance representing the cluster. 
     483         
     484        :type cluster: :class:`Orange.clustering.hierarchical.HierarchicalCluster` 
     485         
     486        """ 
     487        return self.dendrogram_items.get(cluster) 
     488     
     489    def height_at(self, point): 
     490        """ Return the cluster height at the point in local coordinates. 
     491        """ 
     492        root_item = self.item(self.root_cluster) 
     493        rect = root_item.rect() 
     494        root_height = self.root_cluster.height 
     495        if self.orientation == Qt.Vertical: 
     496            return  (root_height - 0) / (rect.left() - rect.right()) * point.x() + root_height 
     497        else: 
     498            return (root_height - 0) / (rect.bottom() - rect.top()) * point.y() + root_height 
     499             
     500    def set_labels(self, labels): 
     501        """ Set the cluster leaf labels. 
     502        """ 
     503        for label, item in zip(labels, self.leaf_items()): 
     504            old_text = getattr(item, "_label_text", None) 
     505            if old_text is not None: 
     506                old_text.setParent(None) 
     507                if self.scene(): 
     508                    self.scene().removeItem(old_text) 
     509            text = QGraphicsTextItem(label, item) 
     510            if self.orientation == Qt.Vertical: 
     511                text.translate(5, - text.boundingRect().height() / 2.0) 
     512            else: 
     513                text.translate(- text.boundingRect().height() / 2.0, 5) 
     514                text.rotate(-90) 
     515                 
     516    def set_highlighted_item(self, item): 
     517        """ Set the currently highlighted item. 
     518        """ 
     519        if self._highlighted_item == item: 
     520            return 
     521         
     522        if self._highlighted_item: 
     523            self._highlighted_item.set_highlight(False) 
     524        if item: 
     525            item.set_highlight(True) 
     526        self._highlighted_item = item 
     527         
     528    def leaf_items(self): 
     529        """ Iterate over the dendrogram leaf items (instances of :class:`DendrogramItem`). 
     530        """ 
     531        if self.root_cluster: 
     532            clusters = hierarchical.postorder(self.root_cluster) 
     533        else: 
     534            clusters = [] 
     535        for cluster in clusters: 
     536            if not cluster.branches: 
     537                yield self.dendrogram_items[cluster]  
     538     
     539    def leaf_anchors(self): 
     540        """ Iterate over the dendrogram leaf anchor points (:class:`QPointF`). 
     541        The points are in the widget (as well as item) local coordinates. 
     542         
     543        """ 
     544        for item in self.leaf_items(): 
     545            if self.orientation == Qt.Vertical: 
     546                yield QPointF(item.rect().right(), item.rect().center().y()) 
     547            else: 
     548                yield QPointF(item.rect().center().x(), item.rect().top()) 
     549         
     550    def selected_clusters(self): 
     551        """ Return the selected clusters. 
     552        """ 
     553        return [item.cluster for item in self.selected_items] 
     554         
     555    def set_selected_items(self, items): 
     556        """ Force item selection. 
     557         
     558        :param items: List of `DendrogramItem`s to select . 
     559          
     560        """ 
     561        for sel in list(self.selected_items): 
     562            self._remove_selection(sel) 
     563             
     564        for item in items: 
     565            self._add_selection(item, reenumerate=False) 
     566             
     567        self._re_enumerate_selections() 
     568         
     569    def set_selected_clusters(self, clusters): 
     570        """ Force cluster selection. 
     571         
     572        :param items: List of `Orange.clustering.hierarchical.HierarchicalCluster`s to select . 
     573          
     574        """ 
     575        self.set_selected_items(map(self.item, clusters)) 
     576         
     577    def item_selection(self, item, select_state): 
     578        """ Update item selection. 
     579         
     580        :param item: DendrogramItem. 
     581        :param select_state: New selection state for item. 
     582        """ 
     583        modifiers = QApplication.instance().keyboardModifiers() 
     584        extended_selection = modifiers & Qt.ControlModifier 
     585         
     586        if select_state == False and item not in self.selected_items: 
     587            # Already removed 
     588            return select_state 
     589        if not extended_selection: 
     590            selected_items = list(self.selected_items) 
     591            for selected in selected_items: 
     592                self._remove_selection(selected) 
     593             
     594        if item in self.selected_items: 
     595            if select_state == False: 
     596                self._remove_selection(item) 
     597        else: 
     598            # If item is already inside another selected item, 
     599            # remove that selection 
     600            super_selection = self._selected_super_item(item) 
     601            if super_selection: 
     602                self._remove_selection(super_selection) 
     603            # Remove selections this selection will override. 
     604            sub_selections = self._selected_sub_items(item) 
     605            for sub in sub_selections: 
     606                self._remove_selection(sub) 
     607             
     608            if select_state == True: 
     609                self._add_selection(item) 
     610            elif item in self.selected_items: 
     611                self._remove_selection(item) 
     612             
     613        return select_state 
     614                 
     615    def _re_enumerate_selections(self): 
     616        """ Re enumerate the selection items and update the colors. 
     617        """  
     618        items = sorted(self.selected_items.items(), key=lambda item: item[1][0]) 
     619        palette = ColorPaletteHSV(len(items)) 
     620        for new_i, (item, (i, selection_item)) in enumerate(items): 
     621            self.selected_items[item] = new_i, selection_item 
     622            color = palette[new_i] 
     623            color.setAlpha(150) 
     624            selection_item.setBrush(QColor(color)) 
     625             
     626    def _remove_selection(self, item): 
     627        """ Remove selection rooted at item. 
     628        """ 
     629        i, selection_poly = self.selected_items[item] 
     630        selection_poly.hide() 
     631        selection_poly.setParentItem(None) 
     632        if self.scene(): 
     633            self.scene().removeItem(selection_poly) 
     634        del self.selected_items[item] 
     635        item.setSelected(False) 
     636        self._re_enumerate_selections() 
     637        self.emit(SIGNAL("selectionChanged()")) 
     638         
     639    def _add_selection(self, item, reenumerate=True): 
     640        """ Add selection rooted at item 
     641        """ 
     642        selection_item = self.selection_item_constructor(item) 
     643        self.selected_items[item] = len(self.selected_items), selection_item 
     644        if reenumerate: 
     645            self._re_enumerate_selections() 
     646        self.emit(SIGNAL("selectionChanged()")) 
     647         
     648    def _selected_sub_items(self, item): 
     649        """ Return all selected subclusters under item. 
     650        """ 
     651        res = [] 
     652        for item in hierarchical.preorder(item)[1:]: 
     653            if item in self.selected_items: 
     654                res.append(item) 
     655        return res 
     656     
     657    def _selected_super_item(self, item): 
     658        """ Return the selected super item if it exists  
     659        """ 
     660        for selected_item in self.selected_items: 
     661            if item in hierarchical.preorder(selected_item): 
     662                return selected_item 
     663        return None 
     664     
     665    def selection_item_constructor(self, item): 
     666        """ Return an selection item covering the selection rooted at item. 
     667        """ 
     668        poly = selection_polygon_from_item(item) 
     669        selection_poly = SelectionPolygon(poly, self) 
     670        return selection_poly 
     671     
     672    def _update_selection_items(self): 
     673        """ Update the shapes of selection items after a layout change. 
     674        """ 
     675        for item, (i, selection_item) in self.selected_items.items(): 
     676            selection_item.setPolygon(selection_polygon_from_item(item)) 
     677         
     678    def paint(self, painter, options, widget=0): 
     679        rect =  self.geometry() 
     680        rect.translate(-self.pos()) 
     681        painter.drawRect(rect) 
     682     
     683     
     684class CutoffLine(QGraphicsLineItem): 
     685    """ A dragable cutoff line for selection of clusters in a DendrogramWidget 
     686    based in their height. 
     687     
     688    """ 
     689    def __init__(self, widget, scene=None): 
     690        assert(isinstance(widget, DendrogramWidget)) 
     691        QGraphicsLineItem.__init__(self, widget) 
     692        self.setAcceptedMouseButtons(Qt.LeftButton) 
     693        pen = QPen(Qt.black, 2) 
     694        pen.setCosmetic(True) 
     695        self.setPen(pen) 
     696        geom = widget.geometry() 
     697        if widget.orientation == Qt.Vertical: 
     698            self.setLine(0, 0, 0, geom.height()) 
     699            self.setCursor(Qt.SizeHorCursor) 
     700        else: 
     701            self.setLine(0, geom.height(), geom.width(), geom.height()) 
     702            self.setCursor(Qt.SizeVerCursor) 
     703        self.setZValue(widget.item(widget.root_cluster).zValue() + 10) 
     704         
     705    def mousePressEvent(self, event): 
     706        pass  
     707     
     708    def mouseMoveEvent(self, event): 
     709        widget = self.parentWidget() 
     710        dpos = event.pos() - event.lastPos() 
     711        line = self.line() 
     712        if widget.orientation == Qt.Vertical: 
     713            line = line.translated(dpos.x(), 0) 
     714        else: 
     715            line = line.translated(0, dpos.y()) 
     716        self.setLine(line) 
     717        height = widget.height_at(event.pos()) 
     718        self.cutoff_selection(height) 
     719         
     720    def mouseReleaseEvent(self, event): 
     721        pass 
     722     
     723    def cutoff_selection(self, height): 
     724        widget = self.parentWidget() 
     725        clusters = clusters_at_height(widget.root_cluster, height) 
     726        items = [widget.item(cl) for cl in clusters] 
     727        widget.set_selected_items(items) 
     728         
     729         
     730def clusters_at_height(root_cluster, height): 
     731    """ Return a list of clusters by cutting the clustering at height. 
     732    """ 
     733    lower = set() 
     734    cluster_list = [] 
     735    for cl in hierarchical.preorder(root_cluster): 
     736        if cl in lower: 
     737            continue 
     738        if cl.height < height: 
     739            cluster_list.append(cl) 
     740            lower.update(hierarchical.preorder(cl)) 
     741    return cluster_list 
     742     
     743     
     744class RadialDendrogramLayout(DendrogramLayout): 
     745    """ Layout the RadialDendrogramItems 
     746    """ 
     747    def __init__(self, parent=None, span=340): 
     748        DendrogramLayout.__init__(self, parent) 
     749        self.span = span 
     750         
     751        raise NotImplementedError 
     752         
     753         
     754    def do_layout(self): 
     755        if self._items and self._root: 
     756            leaf_items = [item for item in self._items if item.cluster.branches] 
     757            leaf_item_count = len([item for item in self._items 
     758                                   if item.cluster.branches]) 
     759            cluster_width = float(leaf_item_count) 
     760            root_height = self._root.height 
     761            c_rect = self.contentsRect() 
     762            radius = min([c_rect.height(), c_rect.width()]) / 2.0 
     763            center_offset = 5 
     764            height_scale = (radius - center_offset) / root_height 
     765            width_scale = self.span / cluster_width  
     766#            if self.orientation == Qt.Vertical: 
     767#                height_scale = c_rect.width() / root_height 
     768#                width_scale =  c_rect.height() / cluster_width 
     769#            else: 
     770#                height_scale = c_rect.height() / root_height 
     771#                width_scale =  c_rect.width() / cluster_width 
     772                 
     773            for item, cluster in zip(self._items, self._clusters): 
     774                start, center, end = self._layout_dict[cluster] 
     775#                if self.orientation == Qt.Vertical: 
     776#                    # Should this be translated so all items have positive x coordinates 
     777#                    rect = QRectF(-cluster.height * height_scale, start * width_scale, 
     778#                                  cluster.height * height_scale, (end - start) * width_scale) 
     779#                else: 
     780#                    rect = QRectF(start * width_scale, 0.0, #cluster.height * height_scale, 
     781#                                  (end - start) * width_scale, cluster.height * height_scale) 
     782                 
     783                rect.translate(c_rect.topLeft()) 
     784                item.setGeometry(rect) 
     785     
     786class RadialDendrogramWidget(DendrogramWidget): 
     787    def __init__(self, root=None, parent=None): 
     788        DendrogramWidget.__init__(self, parent=parent) 
     789        self.setLayout(CirclarDendrogramLayout()) 
     790        self.setRoot(root) 
     791         
     792        raise NotImplementedError 
     793         
     794    def set_root(self, root): 
     795        """ Set the root cluster. 
     796         
     797        :param root: Root cluster. 
     798        :type root: :class:`Orange.clustering.hierarchical.HierarchicalCluster` 
     799          
     800        """ 
     801        self.clear() 
     802        self.root_cluster = root 
     803        self.dendrogram_items = {} 
     804        self.cluster_parent = {} 
     805        if root: 
     806            items = [] 
     807            for cluster in hierarchical.postorder(self.root_cluster): 
     808                item = RadialDendrogramItem(cluster) 
     809                for branch in cluster.branches or []: 
     810                    branch_item = self.dendrogram_items[branch]  
     811                    self.cluster_parent[branch] = cluster 
     812                items.append(GraphicsRectLayoutItem(item)) 
     813                self.dendrogram_items[cluster] = item 
     814                 
     815            self.layout().setDendrogram(root, items) 
     816             
     817            self.layout().activate() 
     818 
     819def test(): 
     820    app = QApplication([]) 
     821    scene = QGraphicsScene() 
     822    view = QGraphicsView() 
     823    view.setScene(scene) 
     824    view.show() 
     825    import Orange 
     826    data = Orange.data.Table("../doc/datasets/iris.tab") 
     827    root = hierarchical.clustering(data) 
     828#    print hierarchical.cophenetic_correlation(root, hierarchical.instance_distance_matrix(data)) 
     829#    widget = DendrogramWidget(hierarchical.pruned(root, level=4))#, orientation=Qt.Horizontal) 
     830    widget = DendrogramWidget(root) 
     831    scene.addItem(widget) 
     832    line = CutoffLine(widget) 
     833#    widget.layout().setMaximumHeight(400) 
     834     
     835    app.exec_() 
     836     
     837if __name__ == "__main__": 
     838    test() 
Note: See TracChangeset for help on using the changeset viewer.