Changeset 7938:0f80e821b332 in orange


Ignore:
Timestamp:
05/25/11 12:12:28 (3 years ago)
Author:
ales_erjavec <ales.erjavec@…>
Branch:
default
Convert:
851eb64ee7186f0000941cfe96e17aa90b5f3ac0
Message:

Reworked Hierarchical Clustering widget to use OWClustering.DendrogramWidget.
Added functionality to OWClustering (cluster selection, cutoff selection ...), fixed some bugs.

Location:
orange/OrangeWidgets
Files:
2 edited

Legend:

Unmodified
Added
Removed
  • orange/OrangeWidgets/OWClustering.py

    r7855 r7938  
    55class HierarchicalClusterItem(QGraphicsRectItem): 
    66    """ An object used to draw orange.HierarchicalCluster on a QGraphicsScene 
     7     
     8    ..note:: deprecated use DendrogramWidget instead 
    79    """ 
    810    def __init__(self, cluster, *args, **kwargs): 
     
    1921        self.cluster = cluster 
    2022        self.branches = [] 
    21 #        if cluster.branches: 
    22 #            for branch in cluster.branches: 
    23 #                item = type(self)(branch, self) 
    24 #                item.setZValue(self.zValue()-1) 
    25 #                self.branches.append(item) 
    26 #            self.setRect(self.branches[0].rect().center().x(), 
    27 #                         0.0, #self.cluster.height, 
    28 #                         self.branches[-1].rect().center().x() - self.branches[0].rect().center().x(), 
    29 #                         self.cluster.height) 
    30 #        else: 
    31 #            self.setRect(cluster.first, 0, 0, 0) 
    3223        self.setFlags(QGraphicsItem.ItemIsSelectable) 
    3324        self.setPen(self.standardPen) 
    3425        self.setBrush(QBrush(Qt.white, Qt.SolidPattern)) 
    3526#        self.setAcceptHoverEvents(True) 
    36          
    37 #        if self.isTopLevel(): ## top level cluster 
    38 #            self.clusterGeometryReset() 
    3927             
    4028    @classmethod 
     
    153141        self.setHighlight(False) 
    154142         
     143DEBUG = False # Set to true to see widget geometries 
     144 
    155145from Orange.clustering import hierarchical 
    156  
    157146class DendrogramItem(QGraphicsRectItem): 
    158147    """ A Graphics item representing a cluster in a DendrogramWidget. 
     
    291280                path.lineTo(rect.bottomRight()) 
    292281                path.lineTo(rect.topRight()) 
    293 #        stroke = QPainterPathStroker() 
    294 #        path = stroke.createStroke(path) 
    295282        self._path = path 
    296 #        self.setPath(path) 
    297283     
    298284    def itemChange(self, change, value): 
     
    330316        else: 
    331317            raise AttributeError(name) 
     318     
    332319     
    333320class DendrogramLayout(QGraphicsLayout): 
     
    381368            for item, cluster in zip(self._items, self._clusters): 
    382369                start, center, end = self._layout_dict[cluster] 
     370                cluster_height = cluster.height 
    383371                if self.orientation == Qt.Vertical: 
    384372                    # Should this be translated so all items have positive x coordinates 
    385                     rect = QRectF(-cluster.height * height_scale, start * width_scale, 
    386                                   cluster.height * height_scale, (end - start) * width_scale) 
    387                     rect.translate(c_rect.width() + x_offset, 0 + y_offset) 
     373                    rect = QRectF(-cluster_height * height_scale, start * width_scale, 
     374                                  cluster_height * height_scale, (end - start) * width_scale) 
     375                    rect.translate(c_rect.width() + x_offset, y_offset) 
    388376                else: 
    389377                    rect = QRectF(start * width_scale, 0.0, 
    390                                   (end - start) * width_scale, cluster.height * height_scale) 
    391                     rect.translate(0 + x_offset,  y_offset) 
     378                                  (end - start) * width_scale, cluster_height * height_scale) 
     379                    rect.translate(x_offset,  y_offset) 
    392380                     
    393381                if rect.isEmpty(): 
     
    400388     
    401389    def setGeometry(self, geometry): 
     390        old = self.geometry() 
    402391        QGraphicsLayout.setGeometry(self, geometry) 
    403         self.do_layout() 
     392        if self.geometry() != old: 
     393            self.do_layout() 
    404394         
    405395    def sizeHint(self, which, constraint=QSizeF()): 
     
    415405                width = sum([hint.width() for hint in hints] + [0]) 
    416406            return QSizeF(width, height) 
     407        elif which == Qt.MinimumSize: 
     408            left, top, right, bottom = self.getContentsMargins() 
     409            return QSizeF(left + right, top + bottom) 
    417410        else: 
    418411            return QSizeF() 
     
    445438    """ 
    446439    polygon = QPolygonF() 
    447     for item in hierarchical.preorder(item): 
    448         adjusted = item.rect().adjusted(-adjust, -adjust, adjust, adjust) 
     440    # Selection spaning item itself 
     441    adjusted = item.rect().adjusted(-adjust, -adjust, adjust, adjust) 
     442    polygon = polygon.united(QPolygonF(adjusted)) 
     443     
     444    # Collect all left most tree branches 
     445    current = item 
     446    while current.branches: 
     447        current = current.branches[0] 
     448        adjusted = current.rect().adjusted(-adjust, -adjust, adjust, adjust) 
    449449        polygon = polygon.united(QPolygonF(adjusted)) 
     450     
     451    # Collect all right most tree branches 
     452    current = item 
     453    while current.branches: 
     454        current = current.branches[-1] 
     455        adjusted = current.rect().adjusted(-adjust, -adjust, adjust, adjust) 
     456        polygon = polygon.united(QPolygonF(adjusted)) 
     457     
    450458    return polygon 
    451459 
    452460     
    453461class DendrogramWidget(QGraphicsWidget): 
    454     """ A Graphics Widget displaying a dendrogram.  
     462    """ A Graphics Widget displaying a dendrogram. 
    455463    """ 
    456464    def __init__(self, root=None, parent=None, orientation=Qt.Vertical, scene=None): 
     
    485493                for branch in cluster.branches or []: 
    486494                    branch_item = self.dendrogram_items[branch]  
    487 #                    branch_item.setParentItem(item) 
    488495                    self.cluster_parent[branch] = cluster 
    489496                items.append(GraphicsRectLayoutItem(item)) 
     
    491498                 
    492499            self.layout().setDendrogram(root, items) 
    493 #            self.dendrogram_items[root].setParentItem(self) 
    494500             
    495501            self.resize(self.layout().sizeHint(Qt.PreferredSize)) 
    496502            self.layout().activate() 
     503            self.emit(SIGNAL("dendrogramLayoutChanged()")) 
    497504             
    498505    def item(self, cluster): 
     
    511518        root_height = self.root_cluster.height 
    512519        if self.orientation == Qt.Vertical: 
    513             return  (root_height - 0) / (rect.left() - rect.right()) * point.x() + root_height 
    514         else: 
    515             return (root_height - 0) / (rect.bottom() - rect.top()) * point.y() + root_height 
     520            return  (root_height - 0) / (rect.left() - rect.right()) * (point.x() - rect.left()) + root_height 
     521        else: 
     522            return (root_height - 0) / (rect.bottom() - rect.top()) * (point.y() - rect.top()) + root_height 
     523         
     524    def pos_at_height(self, height): 
     525        """ Return a point in local coordinates for `height` (in cluster 
     526        height scale). 
     527        """ 
     528        root_item = self.item(self.root_cluster) 
     529        rect = root_item.rect() 
     530        root_height = self.root_cluster.height 
     531        if self.orientation == Qt.Vertical: 
     532            x = (rect.right() - rect.left()) / root_height * (root_height - height) + rect.left() 
     533            y = 0.0 
     534        else: 
     535            x = 0.0 
     536            y = (rect.bottom() - rect.top()) / root_height * height + rect.top() 
     537             
     538        return QPointF(x, y) 
    516539             
    517540    def set_labels(self, labels): 
     
    576599          
    577600        """ 
    578         for sel in list(self.selected_items): 
    579             self._remove_selection(sel) 
    580              
    581         for item in items: 
    582             self._add_selection(item, reenumerate=False) 
    583              
    584         self._re_enumerate_selections() 
     601        to_remove = set(self.selected_items) - set(items) 
     602        to_add = set(items) - set(self.selected_items) 
     603         
     604        for sel in to_remove: 
     605            self._remove_selection(sel, emit_changed=False) 
     606        for sel in to_add: 
     607            self._add_selection(sel, reenumerate=False, emit_changed=False) 
     608         
     609        if to_add or to_remove: 
     610            self._re_enumerate_selections() 
     611            self.emit(SIGNAL("selectionChanged()")) 
    585612         
    586613    def set_selected_clusters(self, clusters): 
     
    593620         
    594621    def item_selection(self, item, select_state): 
    595         """ Update item selection. 
     622        """ Set the `item`s selection state to `select_state` 
    596623         
    597624        :param item: DendrogramItem. 
    598625        :param select_state: New selection state for item. 
    599626        """ 
    600         modifiers = QApplication.instance().keyboardModifiers() 
    601         extended_selection = modifiers & Qt.ControlModifier 
    602          
    603         if select_state == False and item not in self.selected_items: 
    604             # Already removed 
    605             return select_state 
    606         if not extended_selection: 
    607             selected_items = list(self.selected_items) 
    608             for selected in selected_items: 
    609                 self._remove_selection(selected) 
     627        if select_state == False and item not in self.selected_items or \ 
     628           select_state == True and item in self.selected_items: 
     629            return select_state # State unchanged 
    610630             
    611631        if item in self.selected_items: 
     
    629649             
    630650        return select_state 
    631                  
     651         
    632652    def _re_enumerate_selections(self): 
    633653        """ Re enumerate the selection items and update the colors. 
    634654        """  
    635         items = sorted(self.selected_items.items(), key=lambda item: item[1][0]) 
     655        items = sorted(self.selected_items.items(), key=lambda item: item[0].cluster.first) # Order the clusters 
    636656        palette = ColorPaletteHSV(len(items)) 
    637657        for new_i, (item, (i, selection_item)) in enumerate(items): 
     
    641661            selection_item.setBrush(QColor(color)) 
    642662             
    643     def _remove_selection(self, item): 
     663    def _remove_selection(self, item, emit_changed=True): 
    644664        """ Remove selection rooted at item. 
    645665        """ 
     
    652672        item.setSelected(False) 
    653673        self._re_enumerate_selections() 
    654         self.emit(SIGNAL("selectionChanged()")) 
    655          
    656     def _add_selection(self, item, reenumerate=True): 
     674        if emit_changed: 
     675            self.emit(SIGNAL("selectionChanged()")) 
     676         
     677    def _add_selection(self, item, reenumerate=True, emit_changed=True): 
    657678        """ Add selection rooted at item 
    658679        """ 
    659680        selection_item = self.selection_item_constructor(item) 
    660681        self.selected_items[item] = len(self.selected_items), selection_item 
     682        item.setSelected(True) 
    661683        if reenumerate: 
    662684            self._re_enumerate_selections() 
    663         self.emit(SIGNAL("selectionChanged()")) 
     685        if emit_changed: 
     686            self.emit(SIGNAL("selectionChanged()")) 
    664687         
    665688    def _selected_sub_items(self, item): 
     
    692715        for item, (i, selection_item) in self.selected_items.items(): 
    693716            selection_item.setPolygon(selection_polygon_from_item(item)) 
    694          
    695 #    def paint(self, painter, options, widget=0): 
    696 #        rect =  self.geometry() 
    697 #        rect.translate(-self.pos()) 
    698 #        painter.drawRect(rect) 
    699      
    700      
     717     
     718    def setGeometry(self, geometry): 
     719        QGraphicsWidget.setGeometry(self, geometry) 
     720        self.emit(SIGNAL("dendrogramGeometryChanged(QRectF)"), geometry) 
     721         
     722    def event(self, event): 
     723        ret = QGraphicsWidget.event(self, event) 
     724        if event.type() == QEvent.LayoutRequest: 
     725            self.emit(SIGNAL("dendrogramLayoutChanged()")) 
     726        return ret 
     727     
     728    if DEBUG: 
     729        def paint(self, painter, options, widget=0): 
     730            rect =  self.geometry() 
     731            rect.translate(-self.pos()) 
     732            painter.drawRect(rect) 
     733             
     734             
    701735class CutoffLine(QGraphicsLineItem): 
    702     """ A dragable cutoff line for selection of clusters in a DendrogramWidget 
    703     based in their height. 
    704      
     736    """ A dragable cutoff line for selection of clusters in a DendrogramWidget. 
    705737    """ 
     738    class emiter(QObject): 
     739        """ an empty QObject used by CuttofLine to emit signals 
     740        """ 
     741        pass 
     742     
    706743    def __init__(self, widget, scene=None): 
    707744        assert(isinstance(widget, DendrogramWidget)) 
    708745        QGraphicsLineItem.__init__(self, widget) 
    709746        self.setAcceptedMouseButtons(Qt.LeftButton) 
     747        self.emiter = self.emiter() 
    710748        pen = QPen(Qt.black, 2) 
    711749        pen.setCosmetic(True) 
     
    718756            self.setLine(0, geom.height(), geom.width(), geom.height()) 
    719757            self.setCursor(Qt.SizeVerCursor) 
     758        self.cutoff_height = widget.root_cluster.height 
    720759        self.setZValue(widget.item(widget.root_cluster).zValue() + 10) 
    721          
     760        widget.connect(widget, SIGNAL("dendrogramGeometryChanged(QRectF)"), self.on_geometry_changed) 
     761         
     762    def set_cutoff_at_height(self, height): 
     763        widget = self.parentWidget() 
     764        pos = widget.pos_at_height(height) 
     765        geom = widget.geometry() 
     766        if widget.orientation == Qt.Vertical: 
     767            self.setLine(pos.x(), 0, pos.x(), geom.height()) 
     768        else: 
     769            self.setLine(0, pos.y(), geom.width(), pos.y()) 
     770        self.cutoff_selection(height) 
     771             
     772    def cutoff_selection(self, height): 
     773        self.cutoff_height = height 
     774        widget = self.parentWidget() 
     775        clusters = clusters_at_height(widget.root_cluster, height) 
     776        items = [widget.item(cl) for cl in clusters] 
     777        self.emiter.emit(SIGNAL("cutoffValueChanged(float)"), height) 
     778        widget.set_selected_items(items) 
     779         
     780    def on_geometry_changed(self, geom): 
     781        widget = self.parentWidget() 
     782        height = self.cutoff_height 
     783        pos = widget.pos_at_height(height) 
     784                 
     785        if widget.orientation == Qt.Vertical: 
     786            self.setLine(pos.x(), 0, pos.x(), geom.height()) 
     787            self.setCursor(Qt.SizeHorCursor) 
     788        else: 
     789            self.setLine(0, pos.y(), geom.width(), pos.y()) 
     790            self.setCursor(Qt.SizeVerCursor) 
     791        self.setZValue(widget.item(widget.root_cluster).zValue() + 10) 
     792             
    722793    def mousePressEvent(self, event): 
    723794        pass  
     
    738809        pass 
    739810     
    740     def cutoff_selection(self, height): 
    741         widget = self.parentWidget() 
    742         clusters = clusters_at_height(widget.root_cluster, height) 
    743         items = [widget.item(cl) for cl in clusters] 
    744         widget.set_selected_items(items) 
    745          
     811    def mouseDoubleClickEvent(self, event): 
     812        pass 
    746813         
    747814def clusters_at_height(root_cluster, height): 
  • orange/OrangeWidgets/Unsupervised/OWHierarchicalClustering.py

    r7879 r7938  
    1010from OWWidget import * 
    1111from OWQCanvasFuncts import * 
     12import OWClustering 
    1213import OWGUI 
    1314import OWColorPalette 
    1415import math 
     16import numpy 
    1517import os 
     18 
     19import orange 
     20from Orange.clustering import hierarchical  
    1621 
    1722from OWDlgs import OWChooseImageSizeDlg 
     
    2025from PyQt4.QtGui import * 
    2126 
    22 try: 
    23     from OWDataFiles import DataFiles 
    24 except: 
    25     class DataFiles(object): 
    26         pass 
    27  
    28 class recursion_limit(object): 
    29     def __init__(self, limit=1000): 
    30         self.limit = limit 
    31          
    32     def __enter__(self): 
    33         self.old_limit = sys.getrecursionlimit() 
    34         sys.setrecursionlimit(self.limit) 
    35      
    36     def __exit__(self, exc_type, exc_val, exc_tb): 
    37         sys.setrecursionlimit(self.old_limit) 
    38  
    3927class OWHierarchicalClustering(OWWidget): 
    40     settingsList=["Linkage", "OverwriteMatrix", "Annotation", "Brightness", "PrintDepthCheck", 
    41                 "PrintDepth", "HDSize", "VDSize", "ManualHorSize","AutoResize", 
    42                 "TextSize", "LineSpacing", "ZeroOffset", "SelectionMode", "DisableHighlights", 
    43                 "DisableBubble", "ClassifySelected", "CommitOnChange", "ClassifyName", "addIdAs"] 
     28    settingsList = ["Linkage", "Annotation", "PrintDepthCheck", 
     29                    "PrintDepth", "HDSize", "VDSize", "ManualHorSize","AutoResize", 
     30                    "TextSize", "LineSpacing", "SelectionMode", 
     31                    "AppendClusters", "CommitOnChange", "ClassifyName", "addIdAs"] 
    4432     
    4533    contextHandlers={"":DomainContextHandler("", [ContextField("Annotation", DomainContextHandler.Required)])} 
    4634     
    4735    def __init__(self, parent=None, signalManager=None): 
    48         #OWWidget.__init__(self, parent, 'Hierarchical Clustering') 
    4936        OWWidget.__init__(self, parent, signalManager, 'Hierarchical Clustering', wantGraph=True) 
    50         self.inputs=[("Distance matrix", orange.SymMatrix, self.dataset)] 
    51         self.outputs=[("Selected Examples", ExampleTable), ("Unselected Examples", ExampleTable), ("Centroids", ExampleTable), ("Structured Data Files", DataFiles)] 
    52         self.linkage=[("Single linkage", orange.HierarchicalClustering.Single), 
     37         
     38        self.inputs = [("Distance matrix", orange.SymMatrix, self.set_matrix)] 
     39        self.outputs = [("Selected Examples", ExampleTable), ("Unselected Examples", ExampleTable), ("Centroids", ExampleTable)] 
     40        self.linkage = [("Single linkage", orange.HierarchicalClustering.Single), 
    5341                        ("Average linkage", orange.HierarchicalClustering.Average), 
    5442                        ("Ward's linkage", orange.HierarchicalClustering.Ward), 
    5543                        ("Complete linkage", orange.HierarchicalClustering.Complete), 
    56                      ] 
    57         self.Linkage=3 
    58         self.OverwriteMatrix=0 
    59         self.Annotation=0 
    60         self.Brightness=5 
    61         self.PrintDepthCheck=0 
    62         self.PrintDepth=100 
    63         self.HDSize=500         #initial horizontal and vertical dendrogram size 
    64         self.VDSize=800 
    65         self.ManualHorSize=0 
    66         self.AutoResize=0 
    67         self.TextSize=8 
    68         self.LineSpacing=4 
    69         self.SelectionMode=0 
    70         self.ZeroOffset=1 
    71         self.DisableHighlights=0 
    72         self.DisableBubble=0 
    73         self.ClassifySelected=0 
    74         self.CommitOnChange=0 
    75         self.ClassifyName="HC_class" 
     44                       ] 
     45        self.Linkage = 3 
     46        self.Annotation = 0 
     47        self.PrintDepthCheck = 0 
     48        self.PrintDepth = 10 
     49        self.HDSize = 500         #initial horizontal and vertical dendrogram size 
     50        self.VDSize = 800 
     51        self.ManualHorSize = 0 
     52        self.AutoResize = 0 
     53        self.TextSize = 8 
     54        self.LineSpacing = 4 
     55        self.SelectionMode = 0 
     56        self.AppendClusters = 0 
     57        self.CommitOnChange = 0 
     58        self.ClassifyName = "HC_class" 
     59        self.addIdAs = 0 
     60         
    7661        self.loadSettings() 
    77         self.AutoResize=False 
    78         self.inputMatrix=None 
    79         self.matrixSource="Unknown" 
    80         self.rootCluster=None 
    81         self.selectedExamples=None 
    82         self.ctrlPressed=FALSE 
    83         self.addIdAs = 0 
    84         self.settingsChanged = False 
    85  
    86         self.linkageMethods=[a[0] for a in self.linkage] 
     62         
     63        self.inputMatrix = None 
     64        self.matrixSource = "Unknown" 
     65        self.rootCluster = None 
     66        self.selectedExamples = None 
     67         
     68        self.selectionChanged = False 
     69 
     70        self.linkageMethods = [a[0] for a in self.linkage] 
    8771 
    8872        ################################# 
    8973        ##GUI 
    9074        ################################# 
    91  
    92         #Tabs 
    93 ##        self.tabs = OWGUI.tabWidget(self.controlArea) 
    94 ##        self.settingsTab = OWGUI.createTabPage(self.tabs, "Settings") 
    95 ##        self.selectionTab= OWGUI.createTabPage(self.tabs, "Selection") 
    96  
     75         
    9776        #HC Settings 
    9877        OWGUI.comboBox(self.controlArea, self, "Linkage", box="Linkage", 
    9978                items=self.linkageMethods, tooltip="Choose linkage method", 
    100                 callback=self.constructTree, addSpace = True) 
     79                callback=self.run_clustering, addSpace = True) 
    10180        #Label 
    10281        box = OWGUI.widgetBox(self.controlArea, "Annotation", addSpace = True) 
    103         self.labelCombo=OWGUI.comboBox(box, self, "Annotation", 
     82        self.labelCombo = OWGUI.comboBox(box, self, "Annotation", 
    10483                items=["None"],tooltip="Choose label attribute", 
    105                 callback=self.updateLabel) 
     84                callback=self.update_labels) 
    10685 
    10786        OWGUI.spin(box, self, "TextSize", label="Text size", 
    108                         min=5, max=15, step=1, callback=self.applySettings, controlWidth=40) 
    109         OWGUI.spin(box,self, "LineSpacing", label="Line spacing", 
    110                         min=2,max=8,step=1, callback=self.applySettings, controlWidth=40) 
    111          
    112 #        OWGUI.checkBox(box, self, "DisableBubble", "Disable bubble info") 
    113  
    114  
    115         #Dendrogram graphics settings 
    116         dendrogramBox=OWGUI.widgetBox(self.controlArea, "Limits", addSpace=True) 
    117         #OWGUI.spin(dendrogramBox, self, "Brightness", label="Brigthtness",min=1,max=9,step=1) 
     87                        min=5, max=15, step=1,  
     88                        callback=self.update_font, 
     89                        controlWidth=40, 
     90                        keyboardTracking=False) 
     91#        OWGUI.spin(box,self, "LineSpacing", label="Line spacing", 
     92#                        min=2,max=8,step=1,  
     93#                        callback=self.update_spacing,  
     94#                        controlWidth=40, 
     95#                        keyboardTracking=False) 
     96 
     97        # Dendrogram graphics settings 
     98        dendrogramBox = OWGUI.widgetBox(self.controlArea, "Limits",  
     99                                        addSpace=True) 
    118100         
    119101        form = QFormLayout() 
    120102        form.setLabelAlignment(Qt.AlignLeft) 
    121         sw = OWGUI.widgetBox(dendrogramBox, orientation="horizontal", addToLayout=False) #QWidget(dendrogramBox) 
    122         cw = OWGUI.widgetBox(dendrogramBox, orientation="horizontal", addToLayout=False) #QWidget(dendrogramBox) 
    123          
    124         sllp = OWGUI.hSlider(sw, self, "PrintDepth", minValue=1, maxValue=50, callback=self.applySettings) 
    125         cblp = OWGUI.checkBox(cw, self, "PrintDepthCheck", "Show to depth", callback = self.applySettings, disables=[sw]) 
     103         
     104        # Depth settings  
     105        sw = OWGUI.widgetBox(dendrogramBox, orientation="horizontal", 
     106                             addToLayout=False) 
     107        cw = OWGUI.widgetBox(dendrogramBox, orientation="horizontal",  
     108                             addToLayout=False) 
     109         
     110        sllp = OWGUI.hSlider(sw, self, "PrintDepth", minValue=1, maxValue=50,  
     111                             callback=self.on_depth_change) 
     112        cblp = OWGUI.checkBox(cw, self, "PrintDepthCheck", "Show to depth",  
     113                              callback = self.on_depth_change,  
     114                              disables=[sw]) 
    126115        form.addRow(cw, sw) 
    127 #        dendrogramBox.layout().addLayout(form) 
    128 #        box = OWGUI.widgetBox(dendrogramBox, orientation=form) 
    129          
    130         option = QStyleOptionButton() 
    131         cblp.initStyleOption(option) 
    132         checkWidth = cblp.style().subElementRect(QStyle.SE_CheckBoxContents, option, cblp).x() 
    133          
    134 #        ib = OWGUI.indentedBox(dendrogramBox, orientation = 0) 
    135 #        ib = OWGUI.widgetBox(dendrogramBox, margin=0) 
    136 #        ib.layout().setContentsMargins(checkWidth, 5, 5, 5) 
    137 #        OWGUI.widgetLabel(ib, "Depth"+ "  ") 
    138 #        slpd = OWGUI.hSlider(ib, self, "PrintDepth", minValue=1, maxValue=50, label="Depth  ", callback=self.applySettings) 
    139 #        cblp.disables.append(ib) 
    140 #        cblp.makeConsistent() 
    141          
    142 #        OWGUI.separator(dendrogramBox) 
    143         #OWGUI.spin(dendrogramBox, self, "VDSize", label="Vertical size", min=100, 
    144         #        max=10000, step=10) 
    145          
    146 #        form = QFormLayout() 
    147         sw = OWGUI.widgetBox(dendrogramBox, orientation="horizontal", addToLayout=False) #QWidget(dendrogramBox) 
    148         cw = OWGUI.widgetBox(dendrogramBox, orientation="horizontal", addToLayout=False) #QWidget(dendrogramBox) 
    149          
    150         hsb = OWGUI.spin(sw, self, "HDSize", min=200, max=10000, step=10, callback=self.applySettings, callbackOnReturn = False) 
    151         cbhs = OWGUI.checkBox(cw, self, "ManualHorSize", "Horizontal size", callback=self.applySettings, disables=[sw]) 
    152          
    153 #        ib = OWGUI.widgetBox(dendrogramBox, margin=0) 
    154 #        self.hSizeBox=OWGUI.spin(ib, self, "HDSize", label="Size"+"  ", min=200, 
    155 #                max=10000, step=10, callback=self.applySettings, callbackOnReturn = True, controlWidth=45) 
    156 #        self.hSizeBox.layout().setContentsMargins(checkWidth, 5, 5, 5) 
     116         
     117        checkWidth = OWGUI.checkButtonOffsetHint(cblp) 
     118         
     119        # Width settings 
     120        sw = OWGUI.widgetBox(dendrogramBox, orientation="horizontal",  
     121                             addToLayout=False) 
     122        cw = OWGUI.widgetBox(dendrogramBox, orientation="horizontal",  
     123                             addToLayout=False) 
     124         
     125        hsb = OWGUI.spin(sw, self, "HDSize", min=200, max=10000, step=10,  
     126                         callback=self.on_width_changed,  
     127                         callbackOnReturn = False, 
     128                         keyboardTracking=False) 
     129        cbhs = OWGUI.checkBox(cw, self, "ManualHorSize", "Horizontal size",  
     130                              callback=self.on_width_changed,  
     131                              disables=[sw]) 
     132         
    157133        self.hSizeBox = hsb 
    158134        form.addRow(cw, sw) 
    159135        dendrogramBox.layout().addLayout(form) 
    160          
    161          
    162 #        cbhs.disables.append(self.hSizeBox) 
    163 #        cbhs.makeConsistent() 
    164          
    165         #OWGUI.checkBox(dendrogramBox, self, "ManualHorSize", "Fit horizontal size") 
    166         #OWGUI.checkBox(dendrogramBox, self, "AutoResize", "Auto resize") 
    167  
     136 
     137        # Selection settings  
    168138        box = OWGUI.widgetBox(self.controlArea, "Selection") 
    169         OWGUI.checkBox(box, self, "SelectionMode", "Show cutoff line", callback=self.updateCutOffLine) 
    170         cb = OWGUI.checkBox(box, self, "ClassifySelected", "Append cluster IDs", callback=self.commitDataIf) 
    171 #        self.classificationBox = ib = OWGUI.indentedBox(box, sep=0) 
     139        OWGUI.checkBox(box, self, "SelectionMode", "Show cutoff line", 
     140                       callback=self.update_cutoff_line) 
     141        cb = OWGUI.checkBox(box, self, "AppendClusters", "Append cluster IDs", 
     142                            callback=self.commit_data_if) 
     143         
    172144        self.classificationBox = ib = OWGUI.widgetBox(box, margin=0) 
    173145         
    174146        form = QWidget() 
    175         le = OWGUI.lineEdit(form, self, "ClassifyName", None, callback=None, orientation="horizontal") 
    176         self.connect(le, SIGNAL("editingFinished()"), self.commitDataIf) 
    177 #        le_layout.addWidget(le.placeHolder) 
    178 #        OWGUI.separator(ib, height = 4) 
    179  
    180         aa = OWGUI.comboBox(form, self, "addIdAs", label = None, orientation = "horizontal", items = ["Class attribute", "Attribute", "Meta attribute"], callback=self.commitDataIf) 
     147        le = OWGUI.lineEdit(form, self, "ClassifyName", None, callback=None, 
     148                            orientation="horizontal") 
     149        self.connect(le, SIGNAL("editingFinished()"), self.commit_data_if) 
     150 
     151        aa = OWGUI.comboBox(form, self, "addIdAs", label=None, 
     152                            orientation="horizontal", 
     153                            items=["Class attribute", 
     154                                   "Attribute", 
     155                                   "Meta attribute"], 
     156                            callback=self.commit_data_if) 
    181157         
    182158        layout = QFormLayout() 
     
    184160        layout.setContentsMargins(0, 5, 0, 5) 
    185161        layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow) 
    186         layout.setLabelAlignment(Qt.AlignLeft)# | Qt.AlignJustify) 
     162        layout.setLabelAlignment(Qt.AlignLeft) 
    187163        layout.addRow("Name  ", le) 
    188164        layout.addRow("Place  ", aa) 
     
    198174        OWGUI.separator(box) 
    199175        cbAuto = OWGUI.checkBox(box, self, "CommitOnChange", "Commit on change") 
    200         btCommit = OWGUI.button(box, self, "&Commit", self.commitData) 
    201         OWGUI.setStopper(self, btCommit, cbAuto, "settingsChanged", self.commitData) 
     176        btCommit = OWGUI.button(box, self, "&Commit", self.commit_data, default=True) 
     177        OWGUI.setStopper(self, btCommit, cbAuto, "selectionChanged", self.commit_data) 
    202178         
    203179         
    204180        OWGUI.rubber(self.controlArea) 
    205 #        OWGUI.button(self.controlArea, self, "&Save Graph", self.saveGraph, debuggingEnabled = 0) 
    206181        self.connect(self.graphButton, SIGNAL("clicked()"), self.saveGraph) 
    207182 
    208         scale=QGraphicsScene(self) 
    209         self.headerView=ScaleView(self, scale, self.mainArea) 
    210         self.footerView=ScaleView(self, scale, self.mainArea) 
    211         self.dendrogram = Dendrogram(self) 
     183        self.scale_scene = scale = ScaleScene(self, self) 
     184        self.headerView = ScaleView(scale, self) 
     185        self.footerView = ScaleView(scale, self) 
     186         
     187        self.dendrogram = DendrogramScene(self) 
    212188        self.dendrogramView = DendrogramView(self.dendrogram, self.mainArea) 
    213189         
    214         self.connect(self.dendrogram, SIGNAL("sceneRectChanged(QRectF)"), self.footerView.sceneRectUpdated) 
     190        self.connect(self.dendrogram, SIGNAL("selectionChanged()"), self.on_selection_change) 
     191        self.connect(self.dendrogram, SIGNAL("sceneRectChanged(QRectF)"), scale.scene_rect_update) 
     192        self.connect(self.dendrogram, SIGNAL("dendrogramGeometryChanged(QRectF)"), self.on_dendrogram_geometry_change) 
     193        self.connect(self.dendrogram, SIGNAL("cutoffValueChanged(float)"), self.on_cuttof_value_changed) 
     194         
     195        self.connect(self.dendrogramView, SIGNAL("viewportResized(QSize)"), self.on_width_changed) 
     196        self.connect(self.dendrogramView, SIGNAL("transformChanged(QTransform)"), self.headerView.setTransform) 
     197        self.connect(self.dendrogramView, SIGNAL("transformChanged(QTransform)"), self.footerView.setTransform) 
    215198     
    216199        self.mainArea.layout().addWidget(self.headerView) 
     
    218201        self.mainArea.layout().addWidget(self.footerView) 
    219202         
    220         self.dendrogram.header=self.headerView 
    221         self.dendrogram.footer=self.footerView 
     203        self.dendrogram.header = self.headerView 
     204        self.dendrogram.footer = self.footerView 
    222205 
    223206        self.connect(self.dendrogramView.horizontalScrollBar(),SIGNAL("valueChanged(int)"), 
     
    229212        self.resize(800, 500) 
    230213         
     214        self.natural_dendrogram_width = 800  
     215        self.dendrogramView.set_fit_to_width(not self.ManualHorSize) 
     216         
    231217        self.matrix = None 
    232218        self.selectionList = [] 
     219        self.selected_clusters = []  
    233220 
    234221    def sendReport(self): 
     
    237224                             ("Annotation", self.labelCombo.currentText()), 
    238225                             self.PrintDepthCheck and ("Shown depth limited to", self.PrintDepth), 
    239                              self.SelectionMode and hasattr(self.dendrogram, "cutOffHeight") and ("Cutoff line at", self.dendrogram.cutOffHeight)]) 
     226                             self.SelectionMode and hasattr(self, "cutoff_height") and ("Cutoff line at", self.cutoff_height)]) 
    240227         
    241228        self.reportSection("Dendrogram") 
     
    250237        self.reportImage(lambda filename: buffer.save(filename, os.path.splitext(filename)[1][1:])) 
    251238         
    252     def dataset(self, data): 
    253         self.matrix=data 
     239    def set_matrix(self, data): 
     240        self.matrix = data 
    254241        self.closeContext() 
    255242        if not self.matrix: 
    256             self.rootCluster=None 
    257             self.selectedExamples=None 
     243            self.rootCluster = None 
     244            self.selectedExamples = None 
    258245            self.dendrogram.clear() 
    259             self.footerView.clear() 
    260246            self.labelCombo.clear() 
    261247            self.send("Selected Examples", None) 
     
    264250            return 
    265251 
    266         self.matrixSource="Unknown" 
    267         items=getattr(self.matrix, "items") 
     252        self.matrixSource = "Unknown" 
     253        items = getattr(self.matrix, "items") 
    268254        if isinstance(items, orange.ExampleTable): #Example Table from Example Distance 
    269255 
    270             self.labels=["None", "Default"]+ \ 
    271                          [a.name for a in items.domain.attributes] 
     256            self.labels = ["None", "Default"]+ \ 
     257                          [a.name for a in items.domain.attributes] 
    272258            if items.domain.classVar: 
    273259                self.labels.append(items.domain.classVar.name) 
    274260 
    275             self.labelInd=range(len(self.labels)-2) 
     261            self.labelInd = range(len(self.labels) - 2) 
    276262            self.labels.extend([m.name for m in items.domain.getmetas().values()]) 
    277263            self.labelInd.extend(items.domain.getmetas().keys()) 
    278             self.numMeta=len(items.domain.getmetas()) 
    279             self.metaLabels=items.domain.getmetas().values() 
    280             self.matrixSource="Example Distance" 
    281         elif isinstance(items, list): 
    282             self.labels=["None", "Default", "Label"] 
    283             self.Annotation=0 
    284             self.matrixSource="List" 
     264            self.numMeta = len(items.domain.getmetas()) 
     265            self.metaLabels = items.domain.getmetas().values() 
     266            self.matrixSource = "Example Distance" 
     267        elif isinstance(items, list):   # a list of item (most probably strings) 
     268            self.labels = ["None", "Default", "Name", "Strain"] 
     269            self.Annotation = 0 
     270            self.matrixSource = "Data Distance" 
    285271        else:   #From Attribute Distance 
    286             self.labels=["None", "Attribute Name"] 
    287             self.Annotation=1 
     272            self.labels = ["None", "Attribute Name"] 
     273            self.Annotation = 1 
    288274            self.matrixSource="Attribute Distance" 
     275             
    289276        self.labelCombo.clear() 
    290         for a in self.labels: 
    291             self.labelCombo.addItem(a) 
    292         if self.labelCombo.count()<self.Annotation-1: 
    293                 self.Annotation=0 
     277        self.labelCombo.addItems(self.labels) 
     278        if len(self.labels) < self.Annotation - 1: 
     279                self.Annotation = 0 
    294280        self.labelCombo.setCurrentIndex(self.Annotation) 
    295         if self.matrixSource=="Example Distance": 
     281        if self.matrixSource == "Example Distance": 
    296282            self.classificationBox.setDisabled(False) 
    297283        else: 
    298284            self.classificationBox.setDisabled(True) 
    299         if self.matrixSource=="Example Distance": 
     285        if self.matrixSource == "Example Distance": 
    300286            self.openContext("", items) 
    301287        self.error(0) 
    302288        try: 
    303             self.constructTree() 
     289            self.run_clustering() 
    304290        except orange.KernelException, ex: 
    305291            self.error(0, "Could not cluster data! %s" % ex.message) 
    306             self.dataset(None) 
    307              
    308  
    309     def updateLabel(self): 
    310      
     292            self.setMatrix(None) 
     293         
     294    def update_labels(self): 
     295        """ Change the labels in the scene. 
     296        """ 
     297         
    311298        if self.matrix is None: 
    312299            return 
    313         items=self.matrix.items 
    314         if self.Annotation==0: 
    315             self.rootCluster.mapping.setattr("objects", 
    316                 [" " for i in range(len(items))]) 
    317  
    318         elif self.Annotation==1: 
    319             if self.matrixSource=="Example Distance" or self.matrixSource=="List": 
    320                 self.rootCluster.mapping.setattr("objects", range(len(items))) 
    321             elif self.matrixSource=="Attribute Distance": 
    322                 self.rootCluster.mapping.setattr("objects", [getattr(a, "name", " ") for a in items]) 
    323         elif self.matrixSource=="Example Distance": 
    324             try: 
    325                 self.rootCluster.mapping.setattr("objects", 
    326                                 [str(e[self.labelInd[self.Annotation-2]]) for e in items]) 
    327             except IndexError: 
    328                 self.Annotation=0 
    329                 self.rootCluster.mapping.setattr("objects", [str(e[0]) for e in items]) 
    330         elif self.matrixSource=="List": 
    331             if self.Annotation==2: 
    332                 self.rootCluster.mapping.setattr("objects", [getattr(a, "name", str(a)) for a in items]) 
     300         
     301        items = getattr(self.matrix, "items", range(self.matrix.dim)) 
     302         
     303        if self.Annotation == 0: 
     304            labels = [""] * len(items) 
     305        elif self.Annotation == 1: 
     306            if isinstance(items, ExampleTable): 
     307                labels = [str(i) for i in range(len(items))] 
    333308            else: 
    334                 self.rootCluster.mapping.setattr("objects", [getattr(a, "strain", str(a)) for a in items]) 
    335         self.dendrogram.updateLabel() 
    336  
    337     def constructTree(self): 
     309                try: 
     310                    labels = [item.name for item in items] 
     311                except AttributeError: 
     312                    labels = [str(item) for item in items] 
     313        elif self.Annotation > 1 and isinstance(items, ExampleTable): 
     314            attr = self.labelInd[self.Annotation-2] 
     315            labels = [str(ex[attr]) for ex in items] 
     316        else: 
     317            labels = [str(item) for item in items] 
     318             
     319        self.dendrogram.set_labels(labels) 
     320        self.dendrogram.set_tool_tips(labels) 
     321 
     322    def run_clustering(self): 
    338323        if self.matrix: 
    339324            self.progressBarInit() 
    340             self.rootCluster=orange.HierarchicalClustering(self.matrix, 
     325            self.rootCluster = orange.HierarchicalClustering(self.matrix, 
    341326                linkage=self.linkage[self.Linkage][1], 
    342                 overwriteMatrix=self.OverwriteMatrix, 
    343                 progressCallback=self.progressBarSet) 
     327#                overwriteMatrix=self.OverwriteMatrix, 
     328                progressCallback=lambda value, a: self.progressBarSet(value*100)) 
    344329            self.progressBarFinished() 
    345             self.dendrogram.displayTree(self.rootCluster) 
    346             self.updateLabel() 
    347  
    348     def applySettings(self): 
    349         self.dendrogram.setSceneRect(0, 0, self.HDSize, self.VDSize) 
    350         self.dendrogram.displayTree(self.rootCluster) 
    351  
    352     def progressBarSet(self, value, a): 
    353         OWWidget.progressBarSet(self, value*100) 
    354  
    355     def keyPressEvent(self, key): 
    356         if key.key()==Qt.Key_Control: 
    357             self.ctrlPressed=TRUE 
     330            self.display_tree() 
     331 
     332    def display_tree(self): 
     333        root = self.rootCluster 
     334        if self.PrintDepthCheck: 
     335            root = hierarchical.pruned(root, level=self.PrintDepth) 
     336        self.display_tree1(root) 
     337         
     338    def display_tree1(self, tree): 
     339        self.dendrogram.clear() 
     340        self.update_font() 
     341        self.cutoff_height = tree.height * 0.95 
     342        self.dendrogram.set_cluster(tree) 
     343        self.update_labels() 
     344        self.update_cutoff_line() 
     345         
     346    def update_font(self): 
     347        font = self.font() 
     348        font.setPointSize(self.TextSize) 
     349        self.dendrogram.setFont(font) 
     350        if self.dendrogram.widget: 
     351            self.update_labels() 
     352             
     353    def update_spacing(self): 
     354        if self.dendrogram.labels_widget: 
     355            layout = self.dendrogram.labels_widget.layout() 
     356            layout.setSpacing(self.LineSpacing) 
     357         
     358    def on_width_changed(self, size=None): 
     359        if size is not None: 
     360            auto_width = size.width() - 20 
    358361        else: 
    359             OWWidget.keyPressEvent(self, key) 
    360  
    361     def keyReleaseEvent(self, key): 
    362         if key.key()==Qt.Key_Control: 
    363             self.ctrlPressed=FALSE 
     362            auto_width = self.natural_dendrogram_width 
     363             
     364        self.dendrogramView.set_fit_to_width(not self.ManualHorSize) 
     365        if self.ManualHorSize: 
     366            self.dendrogram.set_scene_width_hint(self.HDSize) 
     367            self.update_labels() 
    364368        else: 
    365             OWWidget.keyReleaseEvent(self, key) 
    366  
    367     def updateCutOffLine(self): 
     369            self.dendrogram.set_scene_width_hint(auto_width) 
     370            self.update_labels() 
     371             
     372    def on_dendrogram_geometry_change(self, geometry): 
     373        if self.rootCluster and self.dendrogram.widget: 
     374            widget = self.dendrogram.widget 
     375            left, top, right, bottom = widget.layout().getContentsMargins() 
     376            geometry = geometry.adjusted(left, top, -right, -bottom) 
     377            self.scale_scene.set_scale_bounds(geometry.left(), geometry.right()) 
     378            self.scale_scene.set_scale(self.rootCluster.height, 0.0) 
     379            pos = widget.pos_at_height(self.cutoff_height) 
     380            self.scale_scene.set_marker(pos) 
     381             
     382    def on_depth_change(self): 
     383        if self.rootCluster and self.dendrogram.widget: 
     384            selected = self.dendrogram.widget.selected_clusters() 
     385            selected = set([(c.first, c.last) for c in selected]) 
     386            root = self.rootCluster 
     387            if self.PrintDepthCheck: 
     388                root = hierarchical.pruned(root, level=self.PrintDepth) 
     389            self.display_tree1(root) 
     390             
     391            selected = [c for c in hierarchical.preorder(root) if (c.first, c.last) in selected] 
     392            self.dendrogram.widget.set_selected_clusters(selected)  
     393 
     394    def set_cuttof_position_from_scale(self, pos): 
     395        """ Cuttof position changed due to a mouse event in the scale scene. 
     396        """ 
     397        if self.rootCluster and self.dendrogram.cutoff_line: 
     398            height = self.dendrogram.widget.height_at(pos) 
     399            self.cutoff_height = height 
     400            line = self.dendrogram.cutoff_line.set_cutoff_at_height(height) 
     401             
     402    def on_cuttof_value_changed(self, height): 
     403        """ Cuttof value changed due to line drag in the dendrogram. 
     404        """ 
     405        if self.rootCluster and self.dendrogram.widget: 
     406            self.cutoff_height = height 
     407            widget = self.dendrogram.widget 
     408            pos = widget.pos_at_height(height) 
     409            self.scale_scene.set_marker(pos) 
     410             
     411    def update_cutoff_line(self): 
    368412        try: 
    369413            if self.SelectionMode: 
    370                 self.dendrogram.cutOffLine.show() 
    371                 self.footerView.scene().marker.show() 
     414                self.dendrogram.cutoff_line.show() 
     415                self.scale_scene.marker.show() 
    372416            else: 
    373                 self.dendrogram.cutOffLine.hide() 
    374                 self.footerView.scene().marker.hide() 
     417                self.dendrogram.cutoff_line.hide() 
     418                self.scale_scene.marker.hide() 
    375419        except RuntimeError: # underlying C/C++ object has been deleted 
    376420            pass 
    377         self.dendrogram.update() 
    378         self.footerView.scene().update() 
    379  
    380     def updateSelection(self, selection): 
    381         if self.matrixSource=="Attribute Distance": 
     421             
     422    def on_selection_change(self): 
     423        try: 
     424            if self.matrix: 
     425                items = self.dendrogram.selectedItems() 
     426                self.selected_clusters = [item.cluster for item in items] 
     427                self.commit_data_if() 
     428        except RuntimeError: 
     429            pass 
     430         
     431    def commit_data_if(self): 
     432        if self.CommitOnChange: 
     433            self.commit_data() 
     434        else: 
     435            self.selectionChanged = True 
     436 
     437    def commit_data(self): 
     438        items = getattr(self.matrix, "items") 
     439        if not items: 
     440            return # nothing to commit 
     441         
     442        self.selectionChanged = False 
     443        self.selectedExamples = None 
     444        selection = self.selected_clusters 
     445        maps = [list(self.rootCluster.mapping[c.first: c.last]) for c in selection] 
     446         
     447        from operator import add 
     448        selected_indices = sorted(reduce(add, maps, [])) 
     449        unselected_indices = sorted(set(self.rootCluster.mapping) - set(selected_indices)) 
     450         
     451        self.selection = selected = [items[k] for k in selected_indices] 
     452        unselected = [items[k] for k in unselected_indices] 
     453         
     454        if not selected: 
     455            self.send("Selected Examples", None) 
     456            self.send("Unselected Examples", None) 
     457            self.send("Centroids", None) 
    382458            return 
    383         self.selectionList=selection 
    384         if self.dendrogram.cutOffLineDragged==False: 
    385             self.commitDataIf() 
    386  
    387     def commitDataIf(self): 
    388         if self.CommitOnChange: 
    389             self.commitData() 
    390         else: 
    391             self.settingsChanged = True 
    392  
    393     def selectionChange(self): 
    394         if self.CommitOnChange: 
    395             self.commitData() 
    396  
    397     def commitData(self): 
    398         self.settingsChanged = False 
    399         self.selection=[] 
    400         selection=self.selectionList 
    401         maps=[self.rootCluster.mapping[c.first:c.last] for c in [e.rootCluster for e in selection]] 
    402         self.selection=[self.matrix.items[k] for k in [j for i in range(len(maps)) for j in maps[i]]] 
    403          
    404         if not self.selection: 
    405             self.send("Selected Examples",None) 
    406             self.send("Unselected Examples",None) 
    407             self.send("Structured Data Files", None) 
    408             return 
    409         items = getattr(self.matrix, "items") 
    410         if self.matrixSource == "Example Distance": 
    411             unselected = [item for item in items if item not in self.selection] 
    412             c=[i for i in range(len(maps)) for j in maps[i]] 
    413             aid = None 
    414             if self.ClassifySelected: 
    415                 clustVar=orange.EnumVariable(str(self.ClassifyName) , 
     459         
     460        if isinstance(items, ExampleTable):  
     461            c = [i for i in range(len(maps)) for j in maps[i]] 
     462            aid = clustVar = None 
     463            if self.AppendClusters: 
     464                clustVar = orange.EnumVariable(str(self.ClassifyName) , 
    416465                            values=["Cluster " + str(i) for i in range(len(maps))] + ["Other"]) 
    417466                origDomain = items.domain 
    418467                if self.addIdAs == 0: 
    419                     domain=orange.Domain(origDomain.attributes,clustVar) 
     468                    domain = orange.Domain(origDomain.attributes, clustVar) 
    420469                    if origDomain.classVar: 
    421470                        domain.addmeta(orange.newmetaid(), origDomain.classVar) 
    422471                    aid = -1 
    423472                elif self.addIdAs == 1: 
    424                     domain=orange.Domain(origDomain.attributes+[clustVar], origDomain.classVar) 
     473                    domain = orange.Domain(origDomain.attributes + [clustVar], origDomain.classVar) 
    425474                    aid = len(origDomain.attributes) 
    426475                else: 
    427                     domain=orange.Domain(origDomain.attributes, origDomain.classVar) 
    428                     aid=orange.newmetaid() 
     476                    domain = orange.Domain(origDomain.attributes, origDomain.classVar) 
     477                    aid = orange.newmetaid() 
    429478                    domain.addmeta(aid, clustVar) 
    430479 
    431480                domain.addmetas(origDomain.getmetas()) 
    432                 table1=orange.ExampleTable(domain) #orange.Domain(self.matrix.items.domain, classVar)) 
    433                 table1.extend(orange.ExampleTable(self.selection) if self.selection else []) 
    434                 for i in range(len(self.selection)): 
    435                     table1[i][aid] = clustVar("Cluster " + str(c[i])) 
    436  
    437                 table2 = orange.ExampleTable(domain) 
    438                 table2.extend(orange.ExampleTable(unselected) if unselected else []) 
    439                 for ex in table2: 
    440                     ex[aid] = clustVar("Other") 
    441  
    442                 self.selectedExamples=table1 
    443                 self.unselectedExamples=table2 
     481                table1 = table2 = None 
     482                if selected: 
     483                    table1 = orange.ExampleTable(domain, selected) 
     484                    for i in range(len(selected)): 
     485                        table1[i][clustVar] = clustVar("Cluster " + str(c[i])) 
     486                 
     487                if unselected: 
     488                    table2 = orange.ExampleTable(domain, unselected) 
     489                    for ex in table2: 
     490                        ex[clustVar] = clustVar("Other") 
     491 
     492                self.selectedExamples = table1 
     493                self.unselectedExamples = table2 
    444494            else: 
    445                 table1=orange.ExampleTable(self.selection) 
    446                 self.selectedExamples=table1 
     495                self.selectedExamples = orange.ExampleTable(selected) if selected else None 
    447496                self.unselectedExamples = orange.ExampleTable(unselected) if unselected else None 
    448             self.send("Selected Examples",self.selectedExamples) 
     497                 
     498            self.send("Selected Examples", self.selectedExamples) 
    449499            self.send("Unselected Examples", self.unselectedExamples) 
    450500 
    451             self.centroids = orange.ExampleTable(self.selectedExamples.domain) 
    452             for i in range(len(maps)): 
    453                 clusterEx = [ex for cluster, ex in zip(c, self.selectedExamples) if cluster == i] 
    454                 clusterEx = orange.ExampleTable(clusterEx) 
    455                 contstat = orange.DomainBasicAttrStat(clusterEx) 
    456                 discstat = orange.DomainDistributions(clusterEx, 0, 0, 1) 
    457                 ex = [cs.avg if cs else (ds.modus() if ds else "?") for cs, ds in zip(contstat, discstat)] 
    458                 example = orange.Example(self.centroids.domain, ex) 
    459                 if aid is not None and aid!=-1: 
    460                     example[aid] = i 
    461                 elif aid is not None: 
    462                     example.setclass(i) 
    463                 self.centroids.append(ex) 
    464             self.send("Centroids", self.centroids)     
    465              
    466         elif self.matrixSource=="List": 
     501            self.centroids = None 
     502            if self.selectedExamples: 
     503                self.centroids = orange.ExampleTable(self.selectedExamples.domain) 
     504                for i in range(len(maps)): 
     505                    clusterEx = [ex for cluster, ex in zip(c, self.selectedExamples) if cluster == i] 
     506                    clusterEx = orange.ExampleTable(clusterEx) 
     507                    contstat = orange.DomainBasicAttrStat(clusterEx) 
     508                    discstat = orange.DomainDistributions(clusterEx, 0, 0, 1) 
     509                    ex = [cs.avg if cs else (ds.modus() if ds else "?") for cs, ds in zip(contstat, discstat)] 
     510                    example = orange.Example(self.centroids.domain, ex) 
     511                    if clustVar is not None: 
     512                        example[clustVar] = clustVar(i) 
     513                    self.centroids.append(ex) 
     514            self.send("Centroids", self.centroids) 
     515             
     516        elif self.matrixSource=="Data Distance": 
    467517            names=list(set([d.strain for d in self.selection])) 
    468518            data=[(name, [d for d in filter(lambda a:a.strain==name, self.selection)]) for name in names] 
     
    481531 
    482532class DendrogramView(QGraphicsView): 
    483     def __init__(self,*args): 
    484         apply(QGraphicsView.__init__, (self,)+args) 
     533    def __init__(self, *args): 
     534        QGraphicsView.__init__(self, *args) 
    485535        self.viewport().setMouseTracking(True) 
    486         self.setAlignment(Qt.AlignLeft | Qt.AlignTop) 
     536        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
     537        self.setAlignment(Qt.AlignLeft | Qt.AlignCenter) 
    487538        self.setFocusPolicy(Qt.WheelFocus) 
     539#        self.setRenderHints(QPainter.Antialiasing | QPainter.TextAntialiasing) 
     540        self.setRenderHints(QPainter.TextAntialiasing) 
     541        self.fit_contents = True 
     542        self.connect(self, SIGNAL("viewportResized(QSize)"), self.updateTransform) 
     543        self.connect(self.scene(), SIGNAL("sceneRectChanged(QRectF)"), lambda: self.updateTransform(self.viewport().size())) 
    488544 
    489545    def resizeEvent(self, e): 
    490         QGraphicsView.resizeEvent(self,e) 
    491         if not self.scene().parent.ManualHorSize: 
    492             self.scene().displayTree(self.scene().rootCluster) 
    493         #self.updateContents() 
    494  
    495 class Dendrogram(QGraphicsScene): 
     546        QGraphicsView.resizeEvent(self, e) 
     547        self.emit(SIGNAL("viewportResized(QSize)"), e.size()) 
     548         
     549    def updateTransform(self, size): 
     550        return 
     551#        if self.fit_contents: 
     552#            scene_rect = self.scene().sceneRect() 
     553#            trans = QTransform() 
     554#            scale = size.width() / scene_rect.width() 
     555#            trans.scale(scale, scale) 
     556#            self.setTransform(trans) 
     557#        else: 
     558#            self.setTransform(QTransform()) 
     559#        self.emit(SIGNAL("transformChanged(QTransform)"), self.transform()) 
     560         
     561    def set_fit_to_width(self, state): 
     562        self.fit_contents = state 
     563        self.updateTransform(self.viewport().size()) 
     564 
     565from OWGraphics import GraphicsSimpleTextList 
     566 
     567class DendrogramScene(QGraphicsScene): 
    496568    def __init__(self, *args): 
    497         apply(QGraphicsScene.__init__, (self,)+args) 
    498         self.parent=args[0] 
    499         self.rootCluster=None 
    500         self.rootTree=None 
    501         self.highlighted=None #MyCanvasRect(None) 
    502         self.header=None 
    503         self.footer=None 
    504         self.cutOffLineDragged=False 
    505         self.selectionList=[] 
    506         self.pen=QPen(QColor("blue")) 
    507         self.selectedPen=QPen(QColor("red")) 
    508         self.highlightPen=QPen(QColor("blue"),2) 
    509         self.brush=QBrush(QColor("white")) 
    510         self.font=QFont() 
    511         self.rectObj=[] 
    512         self.textObj=[] 
    513         self.otherObj=[] 
    514         self.cutOffLine=QGraphicsLineItem(None, self) 
    515         self.cutOffLine.setPen( QPen(QColor("black"),2)) 
    516         #self.setDoubleBuffering(True) 
    517         self.holdoff=False 
    518         self.treeAreaWidth = 0 
    519         self.selectionList = [] 
    520         #self.setMouseTrackingEnabled(True) 
    521  
    522     def displayTree(self, root): 
     569        QGraphicsScene.__init__(self, *args) 
     570        self.root_cluster = None 
     571        self.header = None 
     572        self.footer = None 
     573         
     574        self.grid_widget = None 
     575        self.widget = None 
     576        self.labels_widget = None 
     577        self.scene_width_hint = 800 
     578        self.leaf_labels = {} 
     579 
     580    def set_cluster(self, root): 
     581        """ Set the cluster to display  
     582        """ 
    523583        self.clear() 
    524         self.rootCluster=root 
    525         if not self.rootCluster: 
     584        self.root_cluster = root 
     585        self.cutoff_line = None 
     586         
     587        if not self.root_cluster: 
    526588            return 
    527         if not self.parent.ManualHorSize: 
    528             width=self.parent.dendrogramView.size().width() 
     589         
     590        # the main widget containing the dendrogram and labels 
     591        self.grid_widget = QGraphicsWidget() 
     592        self.addItem(self.grid_widget) 
     593        layout = QGraphicsGridLayout() 
     594        self.grid_widget.setLayout(layout) 
     595         
     596        # dendrogram widget 
     597        self.widget = widget = OWClustering.DendrogramWidget(root=root, orientation=Qt.Vertical, parent=self.grid_widget) 
     598        self.connect(widget, SIGNAL("dendrogramGeometryChanged(QRectF)"),  
     599                     lambda rect: self.emit(SIGNAL("dendrogramGeometryChanged(QRectF)"), 
     600                                            rect)) 
     601         
     602        widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding) 
     603         
     604        layout.addItem(self.widget, 0, 0) 
     605        self.grid_widget.setMinimumWidth(self.scene_width_hint) 
     606        self.grid_widget.setMaximumWidth(self.scene_width_hint) 
     607         
     608        spacing = QFontMetrics(self.font()).lineSpacing() 
     609        left, top, right, bottom = widget.layout().getContentsMargins() 
     610        widget.layout().setContentsMargins(0.0, spacing / 2.0, 0.0, spacing / 2.0) 
     611         
     612        labels = [self.cluster_text(leaf.cluster) for leaf in widget.leaf_items()] 
     613         
     614        # Labels widget 
     615        labels = GraphicsSimpleTextList(labels, orientation=Qt.Vertical, parent=self.grid_widget) 
     616        labels.setAlignment(Qt.AlignLeft | Qt.AlignVCenter) 
     617        labels.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) 
     618        labels.setFont(self.font()) 
     619        layout.addItem(labels, 0, 1) 
     620         
     621        # Cutoff line     
     622        self.cutoff_line = OWClustering.CutoffLine(widget) 
     623        self.connect(self.cutoff_line.emiter, SIGNAL("cutoffValueChanged(float)"), 
     624                     lambda val: self.emit(SIGNAL("cutoffValueChanged(float)"), val)) 
     625        self.cutoff_line.set_cutoff_at_height(self.root_cluster.height * 0.95) 
     626             
     627        self.labels_widget = labels 
     628         
     629        layout.activate()  
     630        self._update_scene_rect() 
     631         
     632    def cluster_text(self, cluster): 
     633        """ Return the text to display next to the cluster. 
     634        """ 
     635        if cluster in self.leaf_labels: 
     636            return self.leaf_labels[cluster] 
     637        elif cluster.first == cluster.last - 1: 
     638            value = str(cluster.mapping[cluster.first]) 
     639            return value 
    529640        else: 
    530             width=self.parent.HDSize 
    531         self.setSceneRect(0, 0, width, self.height()) 
    532         self.textAreaWidth=100 
    533 ##        else: 
    534 ##            self.textSize=self.parent.TextSize 
    535 ##            self.textAreaWidth=100 #self.textSize*10 
    536         self.textSize=self.parent.TextSize 
    537         self.gTextPosInc=self.textSize+self.parent.LineSpacing 
    538         self.gTextPos=topMargin 
    539         self.gZPos=-1 
    540         self.treeHeight=root.height 
    541         self.treeAreaWidth=self.width()-leftMargin-self.textAreaWidth 
    542         self.font.setPointSize(self.textSize) 
    543         self.header.scene().setSceneRect(0, 0, self.width()+20, scaleHeight) 
    544         with recursion_limit(sys.getrecursionlimit() + len(self.rootCluster) + 1): 
    545             (self.rootGraphics,a)=self.drawTree(self.rootCluster,0) 
    546         self.updateLabel() 
    547         for old in self.oldSelection: 
    548             for new in self.rectObj: 
    549                 if new.cluster==old: 
    550                     self.addSelection(new) 
    551  
    552         self.header.drawScale(self.treeAreaWidth, root.height) 
    553          
    554 #        fix=max([a.boundingRect().width() for a in self.textObj]) 
    555 #        self.setSceneRect(0, 0, leftMargin+self.treeAreaWidth+fix+rightMargin, 2*topMargin+self.gTextPos) 
    556         self.setSceneRect(self.itemsBoundingRect().adjusted(-5, -5, 5, 5)) 
    557         self.cutOffLine.setLine(0,0,0,self.height()) 
    558         self.header.moveMarker(0) 
    559         self.update() 
    560  
    561     def drawTree(self, cluster, l): 
    562         level=l+1 
    563         if cluster.branches and (level<=self.parent.PrintDepth or \ 
    564                 not self.parent.PrintDepthCheck): 
    565             (leftR, hi)=self.drawTree(cluster.left, level) 
    566             (rightR,low)=self.drawTree(cluster.right, level) 
    567             top=leftMargin+self.treeAreaWidth- \ 
    568                 self.treeAreaWidth*cluster.height/(self.treeHeight or 1) 
    569             rectW=self.width()-self.textAreaWidth-top 
    570             rectH=low-hi 
    571             rect=MyCanvasRect(top, hi, rectW+2, rectH) 
    572             self.addItem(rect) 
    573             rect.left=leftR 
    574             rect.right=rightR 
    575             rect.cluster=cluster 
    576             rect.setBrush(self.brush) 
    577             rect.setPen(self.pen) 
    578             rect.setZValue(self.gZPos) 
    579             self.gZPos-=1 
    580             rect.show() 
    581             self.rectObj.append(rect) 
    582             return (rect, (hi+low)/2) 
     641            values = [str(cluster.mapping[i]) \ 
     642                      for i in range(cluster.first, cluster.last)] 
     643            return str(values[0]) + "..." 
     644 
     645    def set_labels(self, labels): 
     646        """ Set the item labels. 
     647        """ 
     648        assert(len(labels) == len(self.root_cluster.mapping)) 
     649        if self.labels_widget: 
     650            label_items = [] 
     651            for leaf in self.widget.leaf_items(): 
     652                cluster = leaf.cluster 
     653                indices = cluster.mapping[cluster.first: cluster.last] 
     654                text = [labels[i] for i in indices] 
     655                if len(text) > 1: 
     656                    text = text[0] + "..." 
     657                else: 
     658                    text = text[0] 
     659                label_items.append(text) 
     660                 
     661            self.labels_widget.set_labels(label_items) 
     662        self._update_scene_rect() 
     663 
     664    def set_tool_tips(self, tool_tips): 
     665        """ Set the item tool tips. 
     666        """ 
     667        assert(len(tool_tips) == len(self.root_cluster.mapping)) 
     668        if self.labels_widget: 
     669            for leaf, label in zip(self.widget.leaf_items(), self.labels_widget): 
     670                cluster = leaf.cluster 
     671                indices = cluster.mapping[cluster.first: cluster.last] 
     672                text = [tool_tips[i] for i in indices] 
     673                text = "<br>".join(text) 
     674                label.setToolTip(text) 
     675                 
     676    def set_scene_width_hint(self, width): 
     677        self.scene_width_hint = width 
     678        if self.grid_widget: 
     679            self.grid_widget.setMinimumWidth(self.scene_width_hint) 
     680            self.grid_widget.setMaximumWidth(self.scene_width_hint) 
     681             
     682    def clear(self): 
     683        self.widget = None 
     684        self.grid_widget = None 
     685        self.labels_widget = None 
     686        self.root_cluster = None 
     687        self.cutoff_line = None 
     688        QGraphicsScene.clear(self) 
     689         
     690    def setFont(self, font): 
     691        QGraphicsScene.setFont(self, font) 
     692        if self.labels_widget: 
     693            self.labels_widget.setFont(self.font()) 
     694        if self.widget: 
     695            # Fix widget top and bottom margins. 
     696            spacing = QFontMetrics(self.font()).lineSpacing() 
     697            left, top, right, bottom = self.widget.layout().getContentsMargins() 
     698            self.widget.layout().setContentsMargins(left, spacing / 2.0, right, spacing / 2.0) 
     699            self.grid_widget.resize(self.grid_widget.sizeHint(Qt.PreferredSize)) 
     700             
     701    def _update_scene_rect(self): 
     702        self.setSceneRect(reduce(QRectF.united, [item.sceneBoundingRect() for item in self.items()], QRectF()).adjusted(-10, -10, 10, 10)) 
     703         
     704     
     705class AxisScale(QGraphicsWidget): 
     706    """ A graphics widget for an axis scale 
     707    """ 
     708    # Defaults 
     709    orientation = Qt.Horizontal 
     710    tick_count=5 
     711    tick_align = Qt.AlignTop 
     712    text_align = Qt.AlignHCenter | Qt.AlignBottom 
     713    axis_scale = (0.0, 1.0) 
     714     
     715    def __init__(self, parent=None, orientation=Qt.Horizontal, tick_count=5, 
     716                 tick_align = Qt.AlignBottom, 
     717                 text_align = Qt.AlignHCenter | Qt.AlignBottom, 
     718                 axis_scale = (0.0, 1.0)): 
     719        QGraphicsWidget.__init__(self, parent)  
     720        self.orientation = orientation 
     721        self.tick_count = tick_count 
     722        self.tick_align = tick_align 
     723        self.text_align = text_align 
     724        self.axis_scale = axis_scale 
     725         
     726    def set_orientation(self, orientation): 
     727        self.orientation = orientation 
     728        if self.orientation == Qt.Horizontal: 
     729            self.setSizePolicy(QSizePolicy.MinimumExpanding, QSizePolicy.Fixed) 
    583730        else: 
    584             text=MyCanvasText(self, font=self.font, alignment=Qt.AlignLeft) 
    585             text.setPlainText(" ") 
    586             text.cluster=cluster 
    587             text.setPos(leftMargin+self.treeAreaWidth+5,math.ceil(self.gTextPos)) 
    588             text.setZValue(1) 
    589             self.textObj.append(text) 
    590             self.gTextPos+=self.gTextPosInc 
    591             return (None, self.gTextPos-self.gTextPosInc/2) 
    592  
    593     def removeItem(self, item): 
    594         if item.scene() is self: 
    595             QGraphicsScene.removeItem(self, item) 
    596  
    597     def clear(self): 
    598         for a in self.rectObj: 
    599             self.removeItem(a) 
    600         for a in self.textObj: 
    601             self.removeItem(a) 
    602         for a in self.otherObj: 
    603             self.removeItem(a) 
    604         self.rectObj=[] 
    605         self.textObj=[] 
    606         self.otherObj=[] 
    607         self.rootGraphics=None 
    608         self.rootCluster=None 
    609         self.cutOffLine.hide() 
    610         self.cutOffLine.setPos(0,0) 
    611         self.oldSelection=[a.rootCluster for a in self.selectionList] 
    612         self.clearSelection() 
    613         self.update() 
    614  
    615     def updateLabel(self): 
    616         if not self.rootCluster: 
    617             return 
    618         for a in self.textObj: 
    619             if len(a.cluster)>1 and not self.parent.Annotation==0: 
    620                 a.setPlainText("(%i items)" % len(a.cluster)) 
     731            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.MinimumExpanding) 
     732        self.updateGeometry() 
     733         
     734    def ticks(self): 
     735        minval, maxval = self.axis_scale 
     736        ticks = ["%.2f" % val for val in numpy.linspace(minval, maxval, self.tick_count)] 
     737        return ticks 
     738         
     739    def paint(self, painter, option, widget=0): 
     740        painter.setFont(self.font()) 
     741        size = self.geometry().size() 
     742        metrics = QFontMetrics(painter.font()) 
     743        minval, maxval = self.axis_scale 
     744        tick_count = self.tick_count 
     745         
     746        if self.orientation == Qt.Horizontal: 
     747            spanx, spany = size.width(), 0.0 
     748            xadv, yadv =  spanx / tick_count, 0.0 
     749            tick_w, tick_h = 0.0, -5.0 
     750            tick_offset = QPointF(0.0, tick_h) 
     751        else: 
     752            spanx, spany = 0.0, size.height() 
     753            xadv, yadv = 0.0, spany / tick_count 
     754            tick_w, tick_h = 5.0, 0.0 
     755            tick_func = lambda : (y / spany) 
     756            tick_offset = QPointF(tick_w + 1.0, metrics.ascent()/2) 
     757             
     758        ticks = self.ticks() 
     759             
     760        xstart, ystart = 0.0, 0.0 
     761         
     762        if self.orientation == Qt.Horizontal: 
     763            painter.translate(0.0, size.height()) 
     764             
     765        painter.drawLine(xstart, ystart, xstart + tick_count*xadv, ystart + tick_count*yadv) 
     766         
     767        linspacex = numpy.linspace(0.0, spanx, tick_count) 
     768        linspacey = numpy.linspace(0.0, spany, tick_count) 
     769         
     770        metrics = painter.fontMetrics() 
     771        for x, y, tick in zip(linspacex, linspacey, ticks): 
     772            painter.drawLine(x, y, x + tick_w, y + tick_h) 
     773            if self.orientation == Qt.Horizontal: 
     774                rect = QRectF(metrics.boundingRect(tick)) 
     775                rect.moveCenter(QPointF(x, y) + tick_offset + QPointF(0.0, -rect.height()/2.0)) 
     776                painter.drawText(rect, tick) 
    621777            else: 
    622                 a.setPlainText(str(a.cluster[0])) 
    623 #        self.setSceneRect(0, 0, leftMargin+self.treeAreaWidth+max([a.boundingRect().width() \ 
    624 #                for a in self.textObj])+rightMargin, self.height()) 
    625         self.setSceneRect(self.itemsBoundingRect().adjusted(-5, -5, 5, 5)) 
    626         self.update() 
    627  
    628     def highlight(self, objList): 
    629         if not self.rootCluster: 
    630             return 
    631         if self.parent.DisableHighlights: 
    632             if self.highlighted: 
    633                 self.highlighted.highlight(self.pen) 
    634                 self.highlighted=None 
    635                 self.update() 
    636             #return 
    637         if not objList or objList[0].__class__!=MyCanvasRect: 
    638             if self.highlighted: 
    639                 self.highlighted.highlight(self.pen) 
    640                 self.highlighted=None 
    641                 self.update() 
    642             return 
    643         i=0 
    644         if objList[i].__class__==SelectionPoly and len(objList)>1: 
    645             i+=1 
    646         if objList[i].__class__==MyCanvasRect and objList[i]!=self.highlighted: 
    647             if self.highlighted: 
    648                 self.highlighted.highlight(self.pen) 
    649             if not self.parent.DisableHighlights: 
    650                 objList[i].highlight(self.highlightPen) 
    651             self.highlighted=objList[i] 
    652             self.update() 
    653  
    654     def clearSelection(self): 
    655         for a in self.selectionList: 
    656             a.clearGraphics() 
    657         self.selectionList=[] 
    658         self.parent.updateSelection(self.selectionList) 
    659  
    660     def addSelection(self, obj): 
    661         vertList=[] 
    662         ptr=obj 
    663         while ptr:     #construct upper part of the polygon 
    664             rect=ptr.rect() 
    665             ptr=ptr.left 
    666             vertList.append(QPointF(rect.left()-polyOffset,rect.top()-polyOffset)) 
    667             if ptr: 
    668                 vertList.append(QPointF(ptr.rect().left()-polyOffset, rect.top()-polyOffset)) 
     778                painter.drawText(QPointF(x, y) + tick_offset, tick) 
     779                 
     780#        rect =  self.geometry() 
     781#        rect.translate(-self.pos()) 
     782#        painter.drawRect(rect) 
     783         
     784    def setGeometry(self, rect): 
     785        self.prepareGeometryChange() 
     786        return QGraphicsWidget.setGeometry(self, rect) 
     787         
     788    def sizeHint(self, which, *args): 
     789        if which == Qt.PreferredSize: 
     790            minval, maxval = self.axis_scale 
     791            ticks = self.ticks() 
     792            metrics = QFontMetrics(self.font()) 
     793            if self.orientation == Qt.Horizontal: 
     794                h = metrics.height() + 5 
     795                w = 100  
    669796            else: 
    670                 vertList.append(QPointF(rect.right()+3, rect.top()-polyOffset)) 
    671  
    672         tmpList=[] 
    673         ptr=obj 
    674         while ptr:        #construct lower part of the polygon 
    675             rect=ptr.rect() 
    676             ptr=ptr.right 
    677             tmpList.append(QPointF(rect.left()-polyOffset,rect.bottom()+polyOffset)) 
    678             if ptr: 
    679                 tmpList.append(QPointF(ptr.rect().left()-polyOffset, rect.bottom()+polyOffset)) 
    680             else: 
    681                 tmpList.append(QPointF(rect.right()+3, rect.bottom()+polyOffset)) 
    682         tmpList.reverse() 
    683         vertList.extend(tmpList) 
    684         new=SelectionPoly(QPolygonF(vertList)) 
    685         self.addItem(new) 
    686         new.rootCluster=obj.cluster 
    687         new.rootGraphics=obj 
    688         self.selectionList.append(new) 
    689         c=float(self.parent.Brightness)/10; 
    690         colorPalette=OWColorPalette.ColorPaletteHSV(len(self.selectionList)) 
    691         #color=[(a.red()+(255-a.red())*c, a.green()+(255-a.green())*c, 
    692         #                 a.blue()+(255-a.blue())*c) for a in colorPalette] 
    693         #colorPalette=[QColor(a[0],a[1],a[2]) for a in color] 
    694         colorPalette=[colorPalette.getColor(i, 150) for i in range(len(self.selectionList))] 
    695         for el, col in zip(self.selectionList, colorPalette): 
    696             brush=QBrush(col,Qt.SolidPattern) 
    697             el.setBrush(brush) 
    698         new.setZValue(self.gZPos-2) 
    699         #new.setZValue(2) 
    700         ## 
    701         new.show() 
    702         #self.parent.updateSelection(self.selectionList) 
    703         self.update() 
    704  
    705     def removeSelectionItem(self, obj): 
    706         i=self.selectionList.index(obj) 
    707         self.selectionList[i].clearGraphics() 
    708         self.selectionList.pop(i) 
    709  
    710     def mousePressEvent(self, e): 
    711         if not self.rootCluster or e.button()!=Qt.LeftButton: 
    712             return 
    713         pos=e.scenePos() 
    714         if self.parent.SelectionMode: 
    715             self.cutOffLineDragged=True 
    716             self.setCutOffLine(pos.x()) 
    717             return 
    718         objList=self.items(pos.x(), pos.y(), 1, 1) 
    719         if len(objList)==0 and not self.parent.ctrlPressed: 
    720             self.clearSelection() 
    721             self.update() 
    722             return 
    723         for e in objList: 
    724             if e.__class__==SelectionPoly: 
    725                 self.removeSelectionItem(e) 
    726                 self.parent.updateSelection(self.selectionList) 
    727                 self.update() 
    728                 return 
    729         if objList[0].__class__==MyCanvasRect: 
    730             inValid=[] 
    731             for el in self.selectionList: 
    732                 if el.rootCluster.first>=objList[0].cluster.first and \ 
    733                         el.rootCluster.last<=objList[0].cluster.last: 
    734                     inValid.append(el) 
    735             for el in inValid: 
    736                 self.removeSelectionItem(el) 
    737             if not self.parent.ctrlPressed: 
    738                 self.clearSelection() 
    739             self.addSelection(objList[0]) 
    740             self.parent.updateSelection(self.selectionList) 
    741             self.update() 
    742  
    743     def mouseReleaseEvent(self, e): 
    744         self.holdoff=False 
    745         if not self.rootCluster: 
    746             return 
    747         if self.parent.SelectionMode and self.cutOffLineDragged: 
    748             self.cutOffLineDragged=False 
    749             self.setCutOffLine(e.scenePos().x()) 
    750  
    751     def mouseMoveEvent(self, e): 
    752         if not self.rootCluster: 
    753             return 
    754         toolTipRect = QRect(-1, -1, 2, 2) 
    755         if self.parent.SelectionMode==1 and self.cutOffLineDragged: 
    756             self.setCutOffLine(e.scenePos().x()) 
    757             if not self.parent.DisableBubble: 
    758                 QToolTip.showText(e.screenPos(), "Cut off height: \n %f" % self.cutOffHeight, e.widget(), toolTipRect) 
    759             return 
    760         objList=self.items(e.scenePos()) 
    761         self.highlight(objList) 
    762         if not self.parent.DisableBubble and self.highlighted: 
    763             cluster=self.highlighted.cluster 
    764             text= "Items: %i \nCluster height: %f" % (len(cluster), cluster.height) 
    765             QToolTip.showText(e.screenPos(), text, e.widget(), toolTipRect) 
    766  
    767         if objList and objList[0].__class__==MyCanvasText and not self.parent.DisableBubble: 
    768             head="Items: %i" %len(objList[0].cluster) 
    769             body="" 
    770             if self.parent.Annotation!=0: 
    771                 bodyItems=[str(a) for a in objList[0].cluster] 
    772                 if len(bodyItems)>20: 
    773                     bodyItems=bodyItems[:20]+["..."] 
    774                 body="\n"+"\n".join(bodyItems) 
    775             QToolTip.showText(e.screenPos(), head+body, e.widget(), toolTipRect) 
    776  
    777     def cutOffSelection(self, node, height): 
    778         if not node: 
    779             return 
    780         if node.cluster.height<height: 
    781             self.addSelection(node) 
     797                h = 100 
     798                w = max([metrics.width(t) for t in ticks]) + 5 
     799            return QSizeF(w, h) 
    782800        else: 
    783             self.cutOffSelection(node.left, height) 
    784             self.cutOffSelection(node.right, height) 
    785  
    786     def setCutOffLine(self, x): 
    787         if self.parent.SelectionMode==1: 
    788             self.cutOffLinePos=x 
    789             self.cutOffHeight=self.treeHeight- \ 
    790                 self.treeHeight/self.treeAreaWidth*(x-leftMargin) 
    791             self.cutOffLine.setPos(x,0) 
    792             self.footer.moveMarker(x) 
    793             self.cutOffLine.show() 
    794             self.update() 
    795             self.clearSelection() 
    796             self.cutOffSelection(self.rootGraphics,self.cutOffHeight) 
    797             self.parent.updateSelection(self.selectionList) 
    798  
     801            return QSizeF() 
     802 
     803    def boundingRect(self): 
     804        metrics = QFontMetrics(self.font()) 
     805        geometry = self.geometry() 
     806        ticks = self.ticks() 
     807        if self.orientation == Qt.Horizontal: 
     808            h = 5 + metrics.height() 
     809            left = - metrics.boundingRect(ticks[0]).width()/2.0 #+ geometry.left()  
     810            right = geometry.width() + metrics.boundingRect(ticks[-1]).width()/2.0 
     811            rect = QRectF(left, 0.0, right - left, h) 
    799812        else: 
    800             self.cutOffLine.hide() 
    801             self.cutOffLine.setPos(x,0) 
    802             self.update() 
    803          
     813            h = geometry.height() 
     814            w = max([metrics.width(t) for t in ticks]) + 5 
     815            rect = QRectF(0.0, 0.0, w, h)  
     816        return rect 
     817     
     818    def set_axis_scale(self, min, max): 
     819        self.axis_scale = (min, max) 
     820        self.updateGeometry() 
     821         
     822    def set_axis_ticks(self, ticks): 
     823        if isinstance(ticks, dict): 
     824            self.ticks = ticks 
     825        self.updateGeometry() 
     826             
     827    def tick_layout(self): 
     828        """ Return the tick layout 
     829        """ 
     830        minval, maxval = self.axis_scale 
     831        ticks = numpy.linspace(minval, maxval, self.tick_count) 
     832        return zip(ticks, self.ticks()) 
     833     
     834#        min, max = self.axis_scale 
     835#        ticks = self.ticks() 
     836#        span = max - min 
     837#        span_log = math.log10(span) 
     838#        log_sign = -1 if log_sign < 0.0 else 1 
     839#        span_log = math.floor(span_log) 
     840#        major_ticks = [(x, 5.0, tick(i, span_log)) for i in range(self.tick_count)] 
     841#        minor_ticks = [(x, 3.0, tick(i, span_log + log_sign))  for i in range(self.tick_count * 2)] 
     842#        return [(i, major, label) for i, tick, label in major_ticks] 
     843     
     844     
     845class ScaleScene(QGraphicsScene): 
     846    def __init__(self, widget, parent=None): 
     847        QGraphicsScene.__init__(self, parent) 
     848        self.widget = widget 
     849        self.scale_widget = AxisScale(orientation=Qt.Horizontal) 
     850        font = self.font() 
     851        font.setPointSize(10) 
     852        self.scale_widget.setFont(font) 
     853        self.marker = QGraphicsLineItem() 
     854        pen = QPen(Qt.black, 2) 
     855        pen.setCosmetic(True) 
     856        self.marker.setPen(pen) 
     857        self.marker.setLine(0.0, 0.0, 0.0, 25.0) 
     858        self.marker.setCursor(Qt.SizeHorCursor) 
     859        self.addItem(self.scale_widget) 
     860        self.addItem(self.marker) 
     861        self.setSceneRect(QRectF(0, 0, self.scale_widget.size().height(), 25.0)) 
     862         
     863    def set_scale(self, min, max): 
     864        self.scale_widget.set_axis_scale(min, max) 
     865         
     866    def set_scale_bounds(self, start, end): 
     867        self.scale_widget.setPos(start, 0) 
     868        size_hint = self.scale_widget.sizeHint(Qt.PreferredSize) 
     869        self.scale_widget.resize(end - start, self.scale_widget.size().height()) 
     870         
     871    def scene_rect_update(self, rect): 
     872        scale_rect = self.scale_widget.sceneBoundingRect() 
     873        rect = QRectF(rect.x(), scale_rect.y(), rect.width(), scale_rect.height()) 
     874        rect = QRectF(rect.x(), 0.0, rect.width(), scale_rect.height()) 
     875        self.marker.setLine(0, 0, 0, scale_rect.height()) 
     876        self.setSceneRect(rect) 
     877         
     878    def set_marker(self, pos): 
     879        pos = self.scale_widget.mapToScene(pos) 
     880        self.marker.setPos(pos.x(), 0.0) 
     881         
     882    def mousePressEvent(self, event): 
     883        if event.button() == Qt.LeftButton: 
     884            pos = self.scale_widget.mapFromScene(event.scenePos()) 
     885            self.widget.set_cuttof_position_from_scale(pos) 
     886             
     887    def mouseMoveEvent(self, event): 
     888        if event.buttons() & Qt.LeftButton: 
     889            pos = self.scale_widget.mapFromScene(event.scenePos()) 
     890            self.widget.set_cuttof_position_from_scale(pos) 
     891         
     892    def mouseReleaseEvent(self, event): 
     893        if event.button() == Qt.LeftButton: 
     894            pos = self.scale_widget.mapFromScene(event.scenePos()) 
     895            self.widget.set_cuttof_position_from_scale(pos) 
     896             
     897                 
    804898class ScaleView(QGraphicsView): 
    805     def __init__(self, parent, *args): 
    806         apply(QGraphicsView.__init__, (self,)+args) 
    807         self.parent=parent 
    808         self.setFixedHeight(20) 
     899    def __init__(self, scene=None, parent=None): 
     900        QGraphicsView.__init__(self, scene, parent) 
    809901        self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 
    810         self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 
    811         self.setAlignment(Qt.AlignLeft | Qt.AlignTop) 
    812         self.scene().obj=[] 
    813         self.scene().marker=QGraphicsRectItem(None, self.scene()) 
    814         self.scene().treeAreaW = 0 
    815         self.markerDragged=False 
    816         self.markerPos=0 
    817  
    818     def clear(self): 
    819         for a in self.scene().obj: 
    820             self.scene().removeItem(a) 
    821         if self.scene().marker.scene() is self.scene(): 
    822             self.scene().removeItem(self.scene().marker) 
    823         self.scene().obj = [] 
    824  
    825     def drawScale(self, treeAreaW, height): 
    826         self.clear() 
    827         self.scene().treeAreaW=treeAreaW 
    828         self.scene().treeHeight=height 
    829         xPos=leftMargin+treeAreaW 
    830         dist=0 
    831         distInc=math.floor(height/5)/2 
    832         if distInc==0: 
    833             distInc=0.25 
    834         while xPos>=leftMargin: 
    835             text=OWCanvasText(self.scene(), str(dist), xPos, 9, Qt.AlignCenter) 
    836             text.setZValue(0) 
    837             line1=OWCanvasLine(self.scene(), xPos, 0, xPos, 2) 
    838             line2=OWCanvasLine(self.scene(), xPos, 16, xPos, 20) 
    839             line1.setZValue(0) 
    840             line2.setZValue(0) 
    841             self.scene().obj.append(text) 
    842             self.scene().obj.append(line1) 
    843             self.scene().obj.append(line2) 
    844             xPos-=(distInc/(height or 1))*treeAreaW 
    845             dist+=distInc 
    846  
    847 #        self.marker=OWCanvasRectangle(self.scene(),self.markerPos - 1, 0, 1, 20, brushColor=QColor("blue")) 
    848         self.marker = QGraphicsRectItem(QRectF(-1.0, 0.0, 1.0, 20.0), None, self.scene()) 
    849         self.marker.setPos(self.markerPos, 0) 
    850         self.marker.setZValue(1) 
    851 #        self.scene().obj.append(self.marker) 
    852         self.scene().marker=self.marker 
    853 #        self.scene().setSceneRect(QRectF(0.0, 0.0, leftMargin + treeAreaW, 20)) 
    854  
    855     def mousePressEvent(self, e): 
    856         pos = self.mapToScene(e.pos()) 
    857         if pos.x()<0 and pos.x()>leftMargin+self.scene().treeAreaW: 
    858             pos = QPointF(max(min(pos.x(), leftMargin+self.scene().treeAreaW), 0), pos.y()) 
    859         self.commitStatus=self.parent.CommitOnChange 
    860         self.parent.CommitOnChange=False 
    861         self.marker=self.scene().marker 
    862         self.markerPos=pos.x() 
    863         self.marker.setPos(self.markerPos, 0) 
    864         self.markerDragged=True 
    865         self.parent.dendrogram.setCutOffLine(pos.x()) 
    866  
    867     def mouseReleaseEvent(self, e): 
    868         pos = self.mapToScene(e.pos()) 
    869         self.markerDragged=False 
    870         self.parent.CommitOnChange=self.commitStatus 
    871         self.parent.dendrogram.setCutOffLine(pos.x()) 
    872  
    873     def mouseMoveEvent(self, e): 
    874         pos = self.mapToScene(e.pos()) 
    875         if pos.x()<0 or pos.x()>leftMargin+self.scene().treeAreaW: 
    876             pos = QPointF(max(min(pos.x(), leftMargin+self.scene().treeAreaW), 0), pos.y()) 
    877         if self.markerDragged and pos: 
    878            self.markerPos=pos.x() 
    879            self.marker.setPos(self.markerPos,0) 
    880            self.parent.dendrogram.setCutOffLine(pos.x()) 
    881  
    882     def moveMarker(self, x): 
    883         self.scene().marker.setPos(x,0) 
    884         self.markerPos = x 
    885          
    886     def sceneRectUpdated(self, rect): 
    887         rect = QRectF(rect.x(), 0, rect.width(), self.scene().sceneRect().height()) 
    888         self.scene().setSceneRect(rect) 
    889  
    890 class MyCanvasRect(QGraphicsRectItem): 
    891     def __init__(self, *args): 
    892         QGraphicsRectItem.__init__(self, *args) 
    893         self.left = None 
    894         self.right = None 
    895         self.cluster = None 
    896          
    897     def highlight(self, pen): 
    898         if self.pen()==pen: 
    899             return 
    900         if self.left: 
    901             self.left.highlight(pen) 
    902         if self.right: 
    903             self.right.highlight(pen) 
    904         self.setPen(pen) 
    905  
    906  
    907     def paint(self, painter, option, widget=None): 
    908         rect=self.rect() 
    909         painter.setPen(self.pen()) 
    910         painter.drawLine(rect.x(),rect.y(),rect.x(),rect.y()+rect.height())    
    911         if self.left: 
    912             rectL=self.left.rect() 
    913             painter.drawLine(rect.x(),rect.y(),rectL.x(),rect.y()) 
    914         else: 
    915             painter.drawLine(rect.x(),rect.y(),rect.x()+rect.width(),rect.y()) 
    916         if self.right: 
    917             rectR=self.right.rect() 
    918             painter.drawLine(rect.x(),rect.y()+rect.height(),rectR.x(), 
    919                 rect.y()+rect.height()) 
    920         else: 
    921             painter.drawLine(rect.x(),rect.y()+rect.height(),rect.x()+rect.width(), 
    922                 rect.y()+rect.height()) 
    923  
    924 class MyCanvasText(OWCanvasText): 
    925     def __init__(self, *args, **kw): 
    926         OWCanvasText.__init__(self, *args, **kw) 
    927         self.cluster = None 
    928  
    929 class SelectionPoly(QGraphicsPolygonItem): 
    930     def __init__(self, *args): 
    931         QGraphicsPolygonItem.__init__(self, *args) 
    932         self.rootCluster=None 
    933         self.rootGraphics=None 
    934         self.setZValue(20) 
    935  
    936     def clearGraphics(self): 
    937         self.scene().removeItem(self) 
    938  
    939  
    940 if __name__=="__main__": 
     902        self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOn) 
     903        self.setAlignment(Qt.AlignLeft | Qt.AlignCenter) 
     904        self.setFixedHeight(25.0) 
     905 
     906 
     907def test(): 
    941908    app=QApplication(sys.argv) 
    942909    w=OWHierarchicalClustering() 
     
    954921            matrix[i, j] = dist(data[i], data[j]) 
    955922 
    956     w.dataset(matrix) 
     923    w.set_matrix(matrix) 
    957924    app.exec_() 
     925     
     926if __name__=="__main__": 
     927    test() 
Note: See TracChangeset for help on using the changeset viewer.