source: orange-bioinformatics/_bioinformatics/widgets/OWHeatMap.py @ 1843:beaf2389bf47

Revision 1843:beaf2389bf47, 77.3 KB checked in by Flashpoint <vid.flashpoint@…>, 8 months ago (diff)

Merged separate calls to hierarchical clustering for x and y into a single call with an additional parameter in OWHeatMap.py. Changed the progress callback for the Y axis.

RevLine 
[242]1"""
2<name>Heat Map</name>
[1051]3<description>Heatmap visualization.</description>
[723]4<contact>Ales Erjavec, Blaz Zupan, Janez Demsar</contact>
[1701]5<icon>icons/Heatmap.svg</icon>
[1052]6<priority>1040</priority>
[242]7"""
8
[1632]9from __future__ import absolute_import
[1401]10
11from collections import defaultdict
12import itertools
[1632]13import math
14
15import orange
16import orangene
17from Orange.orng import orngClustering
18from Orange.OrangeWidgets import OWColorPalette
19from Orange.OrangeWidgets import OWGUI
20from Orange.OrangeWidgets.ColorPalette import signedPalette
21from Orange.OrangeWidgets.OWClustering import HierarchicalClusterItem, DendrogramWidget, DendrogramItem
22from Orange.OrangeWidgets.OWDlgs import OWChooseImageSizeDlg
23from Orange.OrangeWidgets.OWWidget import *
[1401]24
25DEBUG = False
[242]26
[1304]27#BUG: OWHeatMap does not support heatmaps which need a image which is larger than maxint X maxint pixels!
28#and no warning appears
29
[242]30import warnings
31warnings.filterwarnings("ignore", "'strain'", orange.AttributeWarning)
32
[1401]33def split_domain(domain, split_label):
34    """ Split the domain based on values of `split_label` value.
35    """
36    groups = defaultdict(list)
37    for attr in domain.attributes:
38        groups[attr.attributes.get(split_label)].append(attr)
39       
40    attr_values = [attr.attributes.get(split_label) for attr in domain.attributes]
41   
42    domains = []
43    for value, attrs in groups.items():
44        group_domain = orange.Domain(attrs, domain.class_var)
45        group_domain.add_metas(domain.get_metas())
46        domains.append((value, group_domain))
47       
48    if domains:
49        assert(all(len(dom) == len(domains[0][1]) for _, dom in domains))
50       
51    return sorted(domains, key=lambda t: attr_values.index(t[0]))
52   
53def vstack_by_subdomain(data, sub_domains):
54    domain = sub_domains[0]
55    newtable = orange.ExampleTable(domain)
56   
57    for sub_dom in sub_domains:
58        for ex in data:
59            vals = [ex[a].native() for a in sub_dom]
60            newtable.append(orange.Example(domain, vals))
61   
62    return newtable
63   
64   
65def select_by_class(data, class_):
66    indices = select_by_class_indices(data, class_)
67    return data.select(indices)
68
69def select_by_class_indices(data, class_):
70    return [1 if class_ == ex.getclass() else 0 for ex in data]
71
72def group_by_unordered(iterable, key):
73    groups = defaultdict(list)
74    for item in iterable:
75        groups[key(item)].append(item)
76    return groups.items()
77
[1843]78def hierarchical_cluster_ordering(data, dimension, group_domains=None, opt_order=False, progress_callback=None):
[1840]79    classVar = data.domain.classVar
80    if classVar and isinstance(classVar, orange.EnumVariable):
81        class_data = [select_by_class_indices(data, val) for val in data.domain.classVar.values]
82    else:
83        class_data = [[1] * len(data)]
84       
85    parts = len(class_data) + 1 
86   
87    def pp_callback(part):
88        def callback(value):
89            return progress_callback(value / parts + 100.0 * part / parts)
90        if progress_callback:
91            callback(100.0 * part / parts)
92            return callback
93        else:
94            return progress_callback
95       
96    if group_domains is not None and len(group_domains) > 1:
97        stacked = vstack_by_subdomain(data, group_domains)
98    else:
99        stacked = data   
100   
[1843]101    if dimension == 'X':
102
103        def indices_map(indices):
104            map = zip(range(len(indices)), indices)
105            map = [i for i, test in map if test]
106            return dict(enumerate(map))
[1840]107       
[1843]108        data_ordering = []
109        data_clusters = []
110        for i, indices in enumerate(class_data):
111            sub_data = data.select(indices)
112            cluster = orngClustering.hierarchicalClustering(sub_data, order=opt_order, progressCallback=pp_callback(i + 1))
113            ind_map = indices_map(indices)
114            data_ordering.append([ind_map[m] for m in cluster.mapping])
115            data_clusters.append(cluster)
116           
117        return data_ordering, data_clusters
[1840]118
[1843]119    if dimension == 'Y':
120        attr_cluster = orngClustering.hierarchicalClustering_attributes(stacked, order=opt_order, progressCallback=pp_callback(3))
121        attr_ordering = list(attr_cluster.mapping)
[1840]122           
[1843]123        return attr_ordering, attr_cluster
[242]124
125##############################################################################
126# parameters that determine the canvas layout
127
128c_offsetX = 10; c_offsetY = 10  # top and left border
129c_spaceY = 10                   # space btw graphical elements
130c_spaceAverageX = 5             # space btw stripe with average and microarray
131c_legendHeight = 15             # height of the legend
132c_averageStripeWidth = 12       # width of the stripe with averages
133
134z_heatmap = 5                   # layer with heatmaps
135
136##############################################################################
137# main class
138
[1290]139class ExampleTableContextHandler(ContextHandler):
140    def match(self, context, imperfect, examples):
141        return context.checksum == examples.checksum() and 2
142   
143    def findOrCreateContext(self, widget, examples):
144        context, isNew = ContextHandler.findOrCreateContext(self, widget, examples)
145        if not context:
146            return None, False
147        context.checksum = examples.checksum()
148        return context, isNew
[1401]149   
[1632]150from .OWGenotypeDistances import SetContextHandler
[1290]151
152class OWHeatMap(OWWidget):
153    contextHandlers = {"": DomainContextHandler("", ["CellWidth", "CellHeight"]),
[1401]154                       "Selection": ExampleTableContextHandler("Selection", contextDataVersion=2)}
[1290]155   
[242]156    settingsList = ["CellWidth", "CellHeight", "SpaceX", "Merge",
157                    "Gamma", "CutLow", "CutHigh", "CutEnabled", 
158                    "ShowAnnotation", "LegendOnTop", "LegendOnBottom",
159                    "ShowAverageStripe", "ShowGroupLabel",
160                    "MaintainArrayHeight",
[1401]161                    "GShowToolTip", "BShowColumnID", "BShowSpotIndex",
[242]162                    "BShowAnnotation", 'BShowGeneExpression',
163                    "BSpotVar", "ShowGeneAnnotations",
164                    "ShowDataFileNames", "BAnnotationVar",
165                    "SelectionType",
[1842]166                    "SortExamples", "SortAttributes",
167                    "CurrentPalette", "colorSettings", "selectedSchemaIndex",
[667]168                    "palette", "ShowColumnLabels", "ColumnLabelPosition"]
[242]169
170    def __init__(self, parent=None, signalManager = None):
[495]171        OWWidget.__init__(self, parent, signalManager, 'HeatMap', TRUE)
[242]172       
[1401]173        self.inputs = [("Examples", ExampleTable, self.set_dataset)]
174        self.outputs = [("Examples", ExampleTable, Default)]
[242]175
176        #set default settings
177        self.CellWidth = 3; self.CellHeight = 3
178        self.SpaceX = 10
179        self.Merge = 1; self.savedMerge = self.Merge
180        self.Gamma = 1
[1401]181        self.CutLow = self.CutHigh = self.CutEnabled = 0
[242]182        self.ShowAnnotation = 0
183        self.LegendOnTop = 0           # legend stripe on top (bottom)?
184        self.LegendOnBottom = 1
[1401]185        self.ShowLegend = 1
[242]186        self.ShowGroupLabel = 1        # show class names in case of classified data?
187        self.ShowAverageStripe = 0     # show the stripe with the evarage
188        self.MaintainArrayHeight = 0   # adjust cell height while changing the merge factor
[1401]189        self.GShowToolTip = 1          # balloon help
[242]190        self.ShowGeneAnnotations = 1   # show annotations for genes
[667]191        self.ShowColumnLabels = 1
192        self.ColumnLabelPosition = 0
[242]193        self.ShowDataFileNames = 1     # show the names of the data sets (only in case of multiple files)
194        self.BShowColumnID = 1; self.BShowSpotIndex = 1; self.BShowAnnotation = 1; self.BShowGeneExpression = 1
195        self.BSpotVar = None; self.BAnnotationVar = None  # these are names of variables
196        self.BSpotIndx = None; self.BAnnotationIndx = None # these are id's of the combo boxes
[652]197        self.ShowClustering = 1
[242]198        self.SelectionType = 0         # selection on a single data set
199        self.setColorPalette()
200        self.refFile = 0               # position index of a reference file
201        self.selectedFile = None       # position of the selected file in the list box
[596]202
203        self.colorSettings =None
204        self.selectedSchemaIndex = 0
[1401]205        self.auto_commit = True
206        self.selection_changed_flag = False
[596]207
208        self.palette = self.ColorPalettes[0]
[242]209       
210        self.loadSettings()
211        self.data = []
[723]212        self.maxHSize = 30; self.maxVSize = 15
[242]213
[1840]214        self.SortExamples = 0
215        self.SortAttributes = 0
216       
[596]217
[242]218        # GUI definition
219        self.connect(self.graphButton, SIGNAL("clicked()"), self.saveFig)
[1401]220        self.tabs = OWGUI.tabWidget(self.controlArea)
[242]221
222        # SETTINGS TAB
[1401]223        settingsTab = OWGUI.createTabPage(self.tabs, "Settings")
224        box = OWGUI.widgetBox(settingsTab, "Cell Size (Pixels)", addSpace=True)
225        OWGUI.qwtHSlider(box, self, "CellWidth", label='Width: ',
226                         labelWidth=38, minValue=1, maxValue=self.maxHSize,
227                         step=1, precision=0, callback=self.update_cell_size)
228       
229#        OWGUI.hSlider(box, self, "CellWidth", label="Width:", minValue=1,
230#                      maxValue=self.maxHSize, step=1, ticks=5,
231#                      callback=self.update_cell_size,
232#                      tooltip="Width of each heatmap cell.")
233       
234        self.sliderVSize = OWGUI.qwtHSlider(box, self, "CellHeight",
235                                label='Height: ', labelWidth=38,
236                                minValue=1, maxValue=self.maxVSize,
237                                step=1, precision=0,
238                                callback=self.update_cell_size)
239#        self.sliderVSize = OWGUI.hSlider(box, self, "CellHeight",
240#                      label='Height:',
241#                      minValue=1, maxValue=50, ticks=5,
242#                      callback = self.update_cell_size)
243       
244        OWGUI.qwtHSlider(box, self, "SpaceX", label='Space: ', 
245                         labelWidth=38, minValue=0, maxValue=50,
246                         step=2, precision=0,
247                         callback=self.update_grid_spacing)
248#                         callback=self.drawHeatMap)
249       
250        OWGUI.qwtHSlider(settingsTab, self, "Gamma", box="Gamma",
251                         minValue=0.1, maxValue=1, step=0.1,
252                         callback=self.update_color_schema)
253#                         callback=self.drawHeatMap)
254       
[495]255        OWGUI.separator(settingsTab)
[242]256
257        # define the color stripe to show the current palette
[614]258        box = OWGUI.widgetBox(settingsTab, "Color", orientation = "horizontal")
259        self.colorCombo = OWColorPalette.PaletteSelectorComboBox(self)
[615]260        try:
261            self.colorCombo.setPalettes("palette", self.createColorDialog())
262        except Exception, ex:
263            print >> sys.stderr, ex, "Error loading saved color palettes!\nCreating new default palette!"
264            self.colorSettings = None
265            self.colorCombo.setPalettes("palette", self.createColorDialog())
[614]266        self.colorCombo.setCurrentIndex(self.selectedSchemaIndex)
[655]267        self.setColor(self.selectedSchemaIndex, update=False)
[614]268        self.connect(self.colorCombo, SIGNAL("activated(int)"), self.setColor)
269        box.layout().addWidget(self.colorCombo, 2)
[948]270        button = OWGUI.button(box, self, "Edit colors", callback=self.openColorDialog, tooltip="Edit the heatmap color palette", debuggingEnabled=0)
[614]271       
[495]272        OWGUI.separator(settingsTab)
[1840]273       
[1843]274       
275        boxy = OWGUI.widgetBox(settingsTab, "Sorting", orientation = "vertical", addSpace=True)
276
[1840]277        # For examples
[1843]278        OWGUI.comboBox(boxy, self, "SortExamples",
[1840]279                        items=["No sorting", "Sort examples", "Clustering",
[1843]280                               "Clustering with leaf ordering"], label='X axis',
[1840]281                               callback=self.update_sorting_examples)
282       
283        # For attributes
[1843]284        OWGUI.comboBox(boxy, self, "SortAttributes", 
[1840]285                        items=["No sorting", "Clustering",
[1843]286                               "Clustering with leaf ordering"], label='Y axis',
[1840]287                               callback=self.update_sorting_attributes)
288       
[495]289        OWGUI.rubber(settingsTab)
[242]290       
291        # FILTER TAB
[1401]292        tab = OWGUI.createTabPage(self.tabs, "Filter")
293        box = OWGUI.widgetBox(tab, "Threshold Values", addSpace=True)
294        OWGUI.checkBox(box, self, 'CutEnabled', "Enabled",
295                       callback=self.update_thresholds)
296       
297        self.sliderCutLow = OWGUI.qwtHSlider(box, self, 'CutLow', label='Low:', 
298                            labelWidth=40, minValue=-100, maxValue=0, step=0.1,
299                            precision=1, ticks=0, maxWidth=80,
300                            callback=self.update_thresholds)
301       
302        self.sliderCutHigh = OWGUI.qwtHSlider(box, self, 'CutHigh', label='High:', 
303                            labelWidth=40, minValue=0, maxValue=100, step=0.1,
304                            precision=1, ticks=0, maxWidth=80,
305                            callback=self.update_thresholds)
306       
[242]307        if not self.CutEnabled:
308            self.sliderCutLow.box.setDisabled(1)
309            self.sliderCutHigh.box.setDisabled(1)
310
[1401]311        box = OWGUI.widgetBox(tab, "Merge", addSpace=True)
[768]312##        OWGUI.qwtHSlider(box, self, "Merge", label='Rows:', labelWidth=33, minValue=1, maxValue=500, step=1, callback=self.mergeChanged, precision=0, ticks=0)
[1401]313        OWGUI.spin(box, self, "Merge", min=1, max=500, step=1, label='Rows:',
314#                   callback=self.mergeChanged,
315                   callback=self.on_merge_changed,
316                   callbackOnReturn=True)
[242]317        OWGUI.checkBox(box, self, 'MaintainArrayHeight', "Maintain array height")
[495]318        OWGUI.rubber(tab)
[242]319
320        # INFO TAB
[1401]321        tab = OWGUI.createTabPage(self.tabs, "Info")
[242]322
[1401]323        box = OWGUI.widgetBox(tab,'Annotation && Legends')
324        OWGUI.checkBox(box, self, 'ShowLegend', 'Show legend', 
325                       callback=self.update_legend)
326        OWGUI.checkBox(box, self, 'ShowAverageStripe', 'Stripes with averages', 
327                       callback=self.update_averages_stripe)
328        self.geneAnnotationsCB = OWGUI.checkBox(box, self, 'ShowGeneAnnotations', 'Gene annotations', 
329                                                callback=self.update_annotations)
[242]330       
[1401]331        self.annotationCombo = OWGUI.comboBox(box, self, "BAnnotationIndx", items=[],
332                                              callback=self.update_annotations) 
333                                              #callback=lambda x='BAnnotationVar', y='BAnnotationIndx': self.setMetaID(x, y))
[242]334
[667]335        box = OWGUI.widgetBox(tab, 'Column Labels')
[1401]336        columnLabelCB = OWGUI.checkBox(box, self, "ShowColumnLabels", "Display column labels",
337                                       callback=self.update_column_annotations)
338        posbox = OWGUI.widgetBox(OWGUI.indentedBox(box), "Position", flat=True)
339        comboBox = OWGUI.comboBox(posbox, self, "ColumnLabelPosition", 
340                                  items=["Top", "Bottom"], 
341                                  callback=self.update_column_annotations)
[667]342        columnLabelCB.disables.append(comboBox.box)
343        columnLabelCB.makeConsistent()
344       
[1401]345        box = OWGUI.widgetBox(tab, "Tool Tips")
346        cb = OWGUI.checkBox(box, self, 'GShowToolTip', "Show tool tips")
347        box = OWGUI.widgetBox(OWGUI.indentedBox(box), "Tool Tip Info")
348        box.setFlat(True)
[242]349        OWGUI.checkBox(box, self, 'BShowColumnID', "Column ID")
350        self.spotIndxCB = OWGUI.checkBox(box, self, 'BShowSpotIndex', "Spot Index", \
351            callback=lambda: self.spotCombo.setDisabled(not self.BShowSpotIndex))
352        self.spotCombo = OWGUI.comboBox(box, self, "BSpotIndx", items=[], \
353            callback=lambda x='BSpotVar', y='BSpotIndx': self.setMetaID(x, y))
354        OWGUI.checkBox(box, self, 'BShowGeneExpression', "Gene expression")
355        OWGUI.checkBox(box, self, 'BShowAnnotation', "Annotation")
[1401]356        self.toolTipInfoBox = box
357        cb.disables.append(box)
358        cb.makeConsistent()
[495]359        OWGUI.rubber(tab)
[242]360
[1401]361        # SPLIT TAB
362        self.splitTab = OWGUI.createTabPage(self.tabs, "Split Data")
363        box = OWGUI.widgetBox(self.splitTab, "Split By")
364        self.splitLB = QListWidget(box)
365        box.layout().addWidget(self.splitLB)
366        self.connect(self.splitLB, SIGNAL("itemSelectionChanged()"), self.split_changed)
[242]367
[1401]368        # Scene with microarray
369        self.heatmap_scene = self.scene = HeatmapScene()
370        self.selection_manager = HeatmapSelectionManager(self)
371        self.connect(self.selection_manager, SIGNAL("selection_changed()"), self.on_selection_changed)
372        self.connect(self.selection_manager, SIGNAL("selection_finished()"), self.on_selection_finished)
373        self.heatmap_scene.set_selection_manager(self.selection_manager)
374        item = QGraphicsRectItem(0, 0, 10, 10, None, self.heatmap_scene)
375        self.heatmap_scene.itemsBoundingRect()
376        self.heatmap_scene.removeItem(item)
377       
378        self.sceneView = QGraphicsView(self.scene)
[660]379        self.currentHighlightedCluster = None
380        self.selectedClusters = []
[450]381        self.mainArea.layout().addWidget(self.sceneView)
[1401]382        self.heatmap_scene.widget = None
383        self.heatmap_widget_grid = [[]]
384        self.attr_annotation_widgets = []
385        self.attr_dendrogram_widgets = []
386        self.gene_annotation_widgets = []
387        self.gene_dendrogram_widgets = []
388       
389        self.heatmaps = []
390       
391        self.selection_rects = []
392       
393        self.attr_cluster = None
394        self.data_clusters = []
395        self.sorted_data = None
396       
[1409]397        self._ordering_cache = {}
398       
[1401]399        self.resize(800,400)
[242]400
401    def createColorStripe(self, palette):
402        dx = 104; dy = 18
403        bmp = chr(252)*dx*2 + reduce(lambda x,y:x+y, \
404           [chr(i*250/dx) for i in range(dx)] * (dy-4)) + chr(252)*dx*2 
[1401]405       
406        image = QImage(bmp, dx, dy, QImage.Format_Indexed8)
[495]407        image.setColorTable(signedPalette(self.ColorPalettes[palette]))
408
409        pm = QPixmap.fromImage(image, Qt.AutoColor);
[242]410        return pm
411
412    # set the default palettes used in the program
413    # palette defines 256 colors, 250 are used for heat map, remaining 6 are extra
414    # color indices for unknown is 255, underflow 253, overflow 254, white 252
415    def setColorPalette(self):
416        white = qRgb(255,255,255)
417        gray = qRgb(200,200,200)
418        self.ColorPalettes = \
419          ([qRgb(255.*i/250., 255.*i/250., 255-(255.*i/250.)) \
420            for i in range(250)] + [white]*3 + [qRgb(0., 0., 255.), qRgb(255., 255., 0.), gray],
421           [qRgb(0, 255.*i*2/250., 0) for i in range(125, 0, -1)] \
422           + [qRgb(255.*i*2/250., 0, 0) for i in range(125)] + [white]*3 \
423           + [qRgb(0, 255., 0), qRgb(255., 0, 0), gray],
424           [qRgb(255.*i/250., 0, 0) for i in range(250)] + [white]*3 \
425           + [qRgb(0., 0, 0), qRgb(255., 0, 0), gray])
426        self.SelectionColors = [QColor(0,0,0), QColor(255,255,128), QColor(0,255,255)]
427        self.CurrentPalette = 0
428       
[1401]429    def getGammaCorrectedPalette(self):
430        return [QColor(*self.contPalette.getRGB(float(i)/250, gamma=self.Gamma)).rgb() for i in range(250)] + self.palette[-6:]
431
432    def setColor(self, index, dialog=None, update=True):
433        self.selectedSchemaIndex = index
434        if not dialog:
435            dialog = self.createColorDialog()
436
437        self.colorCombo.setPalettes("palette", dialog)
438        self.colorCombo.setCurrentIndex(self.selectedSchemaIndex)
439        self.contPalette = palette = dialog.getExtendedContinuousPalette("palette")
440        unknown = dialog.getColor("unknown").rgb()
441        underflow = dialog.getColor("underflow").rgb()
442        overflow = dialog.getColor("overflow").rgb()
443        self.palette = [QColor(*palette.getRGB(float(i)/250, gamma=self.Gamma)).rgb() for i in range(250)] + [qRgb(255, 255, 255)]*3 +[underflow, overflow, unknown]
444
445        if update:
446            self.update_color_schema()
447#            self.drawHeatMap()
448       
449    def openColorDialog(self):
450        dialog = self.createColorDialog()
451        if dialog.exec_():
452            self.colorSettings = dialog.getColorSchemas()
453            self.selectedSchemaIndex = dialog.selectedSchemaIndex
454            self.colorCombo.setCurrentIndex(self.selectedSchemaIndex)
455            self.setColor(self.selectedSchemaIndex, dialog)
456
457    def createColorDialog(self):
458        c = OWColorPalette.ColorPaletteDlg(self, "Color Palette")
459        c.createExtendedContinuousPalette("palette", "Continuous Palette", initialColor1=QColor(Qt.blue), initialColor2=QColor(255, 255, 0).rgb(), extendedPassThroughColors = ((Qt.red, 1), (Qt.darkYellow, 1), (Qt.black, 1), (Qt.magenta, 1), (Qt.green, 1)))
460        box = c.createBox("otherColors", "Other Colors")
461        c.createColorButton(box, "unknown", "Unknown", Qt.gray)
462        box.layout().addSpacing(5)
463        c.createColorButton(box, "overflow", "Overflow", Qt.black)
464        box.layout().addSpacing(5)
465        c.createColorButton(box, "underflow", "Underflow", Qt.white)
466        c.setColorSchemas(self.colorSettings, self.selectedSchemaIndex)
467        return c
468   
[242]469    # any time the data changes, the two combo boxes showing meta attributes
470    # have to be adjusted
471    def setMetaCombo(self, cb, value, enabled=1, default=None):
472        cb.clear()
473        if len(self.meta)==0:
474            cb.setDisabled(True)
475            self.spotIndxCB.setDisabled(1); self.geneAnnotationsCB.setDisabled(1)
476            return (None, None)
477        cb.setDisabled(not enabled)
478        self.spotIndxCB.setEnabled(1); self.geneAnnotationsCB.setEnabled(1)
479        for m in self.meta:
[495]480            cb.addItem(m)
[242]481       
482        if not (value in self.meta):
483            if default in self.meta:
484                value = default
485            else:
486                value = None
487
488        if value in self.meta:
[495]489            cb.setCurrentIndex(self.meta.index(value))
[242]490            indx = self.meta.index(value)
491        else:
[495]492            cb.setCurrentIndex(0)
[242]493            value = self.meta[0]; indx = 0
494        return (value, indx)
495
496    def setMetaID(self, val, valIndx):
497        setattr(self, val, self.meta[getattr(self, valIndx)])
[1401]498#        if val=='BAnnotationVar':
499#            self.drawHeatMap()
[242]500
501    def setMetaCombos(self):
[1401]502        self.meta = [m.name for m in self.data.domain.getmetas().values()]
[242]503        self.BSpotVar, self.BSpotIndx = self.setMetaCombo(self.spotCombo, self.BSpotVar, \
504            enabled=self.BShowSpotIndex, default='RMI')
505        self.BAnnotationVar, self.BAnnotationIndx = self.setMetaCombo(self.annotationCombo, \
506            self.BAnnotationVar, enabled=self.BShowAnnotation, default='xannotation')
[1401]507       
508    def set_meta_combos(self):
509        self.spotCombo.clear()
510        self.annotationCombo.clear()
511       
512        self.meta = self.data.domain.getmetas().values()
513        names = [m.name for m in self.meta]
514       
515        self.spotCombo.addItems(names)
516        self.annotationCombo.addItems(names)
517        enabled = bool(self.meta)
518       
519        self.spotIndxCB.setEnabled(enabled)
520        self.geneAnnotationsCB.setEnabled(enabled)
521       
522        self.spotCombo.setEnabled(enabled and self.BShowSpotIndex)
523        self.annotationCombo.setEnabled(enabled and self.BShowAnnotation)
524       
525        self.BSpotIndx = 0
526        self.BSpotVar = self.meta[0] if self.meta else None
527        self.BAnnotationIndx = 0
528        self.BAnnotationVar = self.meta[0] if self.meta else None
[242]529
[1401]530    def get_candidate_splits(self):
531        """ Return candidate labels on which we can split the data.
532        """
533        if self.data is not None:
534            groups = defaultdict(list)
535            for attr in self.data.domain.attributes:
536                for item in attr.attributes.items():
537                    groups[item].append(attr)
538               
539            by_keys = defaultdict(list)
540            for (key, value), attrs in groups.items():
541                by_keys[key].append(attrs)
[1840]542           
[1401]543            # Find the keys for which all values have the same number of attributes.
544            candidates = []
545            for key, groups in by_keys.items():
546                count = len(groups[0])
547                if all(len(attrs) == count for attrs in groups) and len(groups) > 1 and count > 1:
548                    candidates.append(key)
549                   
550            return candidates
551        else:
552            return []
553           
554    def set_split_labels(self):
555        """ Set the list view in Split tab.
556        """
557        self.splitLB.addItems(self.get_candidate_splits())
558       
559    def selected_split_label(self):
560        item = self.splitLB.currentItem()
561        return str(item.text()) if item else None
562       
563    def clear(self):
564        self.data = None
565        self.spotCombo.clear()
566        self.annotationCombo.clear()
567        self.splitLB.clear()
568        self.meta = []
569       
570        self.clear_scene()
571       
572    def clear_scene(self):
573        self.selection_manager.set_heatmap_widgets([[]])
574        self.heatmap_scene.clear()
575        self.heatmap_scene.widget = None
576        self.heatmap_widget_grid = [[]]
577        self.attr_annotation_widgets = []
578        self.attr_dendrogram_widgets = []
579        self.gene_annotation_widgets = []
580        self.gene_dendrogram_widgets = []
581       
582        self.selection_rects = []
583       
584       
[242]585    def saveFig(self):
[1220]586        sizeDlg = OWChooseImageSizeDlg(self.scene, parent=self)
[495]587        sizeDlg.exec_()
[242]588
589    ##########################################################################
590    # handling of input/output signals
[1401]591       
592    def set_dataset(self, data=None, id=None):
[1290]593        self.closeContext("Selection")
[1840]594       
[1409]595        self._ordering_cache.clear()
[1840]596       
[1401]597        self.clear()
598        self.data = data
599        if data is not None:
600#            self.setMetaCombos()
601            self.set_meta_combos()
602            self.set_split_labels()
603           
[652]604        self.unorderedData = None
605        self.groupClusters = None
[1290]606       
607    def handleNewSignals(self):
608        self.send('Examples', None)
609        if self.data:
[1401]610            self.update_heatmaps()
611        else:
612            self.clear()
[1290]613       
[1401]614        if self.data:
615            self.openContext("Selection", self.data)
616           
617    def construct_heatmaps(self, data, split_label=None):
618        if split_label is not None:
619            groups = split_domain(data.domain, split_label)
620        else:
621            groups = [("", data.domain)]
622           
623        group_domains = [dom for _, dom in groups]
[1840]624
[1842]625        attr_ordering = range(len(group_domains[0][1].attributes))
626        attr_cluster = None
627        data_ordering = []
628        data_clusters = [None]
629        sorted_data = data
630
[1843]631        # Only rows
[1842]632        if self.SortExamples > 1:
[1401]633            self.progressBarInit()
[1840]634
[1842]635            args_key = tuple(tuple(d) for d in group_domains), self.SortExamples == 3, "data"
[1843]636            cluster_ordering = self._ordering_cache.get(args_key, None)
637            if cluster_ordering is None:
[1840]638
639                # Rows separately
640                data_ordering, data_clusters = \
[1843]641                        hierarchical_cluster_ordering(data, "X", group_domains,
[1840]642                                      opt_order=self.SortExamples == 3,
643                                      progress_callback=self.progressBarSet)
644
645                # Cache the clusters
[1842]646                self._ordering_cache[args_key] = (data_ordering, data_clusters)
[1840]647            else:
[1843]648                 data_ordering, data_clusters = cluster_ordering
[652]649           
[1840]650            sorted_data = [data[i] for i in itertools.chain(*data_ordering)]
651            self.progressBarFinished()
652       
653        # Only columns
[1842]654        if self.SortAttributes > 0:
[1840]655            self.progressBarInit()
656
[1842]657            args_key = tuple(tuple(d) for d in group_domains), self.SortAttributes == 2, "attributes"
[1843]658            cluster_ordering = self._ordering_cache.get(args_key, None)
659            if cluster_ordering is None:
[1840]660
661                # Columns separately
662                attr_ordering, attr_cluster = \
[1843]663                        hierarchical_cluster_ordering(data, "Y", group_domains,
[1840]664                                      opt_order=self.SortAttributes == 2,
665                                      progress_callback=self.progressBarSet)
666
667                # Cache the clusters
[1842]668                self._ordering_cache[args_key] = (attr_ordering, attr_cluster)
[1840]669            else:
[1843]670                 attr_ordering, attr_cluster = cluster_ordering
[1840]671           
672            self.progressBarFinished()
673
[1401]674        self.heatmapconstructor = []
675        self._group_data = []
676       
677        for name, group_domain in groups:
678            if attr_ordering != sorted(attr_ordering):
679                domain = orange.Domain([group_domain[i] for i in attr_ordering], group_domain.classVar)
680                domain.addmetas(group_domain.getmetas())
681                group_domain = domain
682               
683            group_data = orange.ExampleTable(group_domain, sorted_data)
684            self._group_data.append((group_data, group_domain)) # Crashes at accessing the heatmap.examples[0] without this
[1840]685            if self.SortExamples == 1:
[1401]686                hc = orangene.HeatmapConstructor(group_data)
687            else:
688                hc = orangene.HeatmapConstructor(group_data, None)
689           
690            self.heatmapconstructor.append(hc)
691           
692        self.attr_cluster = attr_cluster
693        self.data_clusters = data_clusters
694        self.sorted_data = sorted_data
695        self.group_domains = groups
[1840]696             
[1401]697    def create_heatmaps(self, constructors):
698        self.lowerBound = 1000
699        self.upperBound = -1000
700        squeeze = 1.0 / self.Merge
701        self.heatmaps = []
702        for hmc in constructors:
703            hm, lb, ub = hmc(squeeze)
704           
705            self.lowerBound = min(self.lowerBound, lb)
706            self.upperBound = max(self.upperBound, ub)
707               
708            self.heatmaps.append(hm)
709           
710        for cluster, heatmap in zip(self.data_clusters, self.heatmaps[0]):
711            if cluster is not None:
712                cluster._heatmap = heatmap
713           
714        self.sliderCutLow.setRange(self.lowerBound, 0, 0.1)
715        self.sliderCutHigh.setRange(1e-10, self.upperBound, 0.1)
716        self.CutLow = max(self.CutLow, self.lowerBound)
717        self.CutHigh = min(self.CutHigh, self.upperBound)
718        self.sliderCutLow.setValue(self.CutLow)
719        self.sliderCutHigh.setValue(self.CutHigh)
720           
721    def point_size_hint(self, height):
722        font = QFont(self.font())
723        font.setPointSize(height)
724        fix = 0
725        while QFontMetrics(font).lineSpacing() > height and height - fix > 1:
726            fix += 1
727            font.setPointSize(height - fix)
728        return height - fix
729   
730    def construct_heatmaps_scene(self, heatmaps, data, attr_cluster=None, data_clusters=None):
731        self.heatmap_scene.clear()
732        widget = GridWidget()
733        self.heatmap_scene.addItem(widget)
734        layout = QGraphicsGridLayout()
735        layout.setSpacing(self.SpaceX)
736        widget.setLayout(layout)
737       
738        classVar = data.domain.classVar
739        if classVar and isinstance(classVar, orange.EnumVariable):
740            classes = classVar.values
741        else:
742            classes = [None]
743       
744        if self.CutEnabled:
745            cut_low, cut_high = self.CutLow, self.CutHigh
746        else:
747            cut_low, cut_high = self.lowerBound, self.upperBound
748           
749        palette = self.getGammaCorrectedPalette() if self.Gamma !=0 else self.palette
750       
751        class_dendrograms = []
752        attr_dendrograms = []
753        heatmap_widgets = []
754        attr_annotation_widgets = []
755        gene_annotation_widgets = []
756        attr_annotation_widgets_top = []
757        attr_annotation_widgets_bottom = []
758       
759        # Dendrograms on the left side
760        if data_clusters and any(data_clusters):
761            for i, cluster in enumerate(data_clusters):
762                class_dendrogram = DendrogramWidget(cluster, parent=widget, orientation=Qt.Vertical)
763                class_dendrogram.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
764                left, top, right, bottom = class_dendrogram.layout().getContentsMargins()
765                class_dendrogram.layout().setContentsMargins(left, self.CellHeight / 2.0 / self.Merge, 0.0, self.CellHeight / 2.0 / self.Merge)
766                class_dendrogram.setMinimumWidth(100)
767                class_dendrogram.setMaximumWidth(100)
768               
769                layout.addItem(class_dendrogram, i*2 + 5, 0)
770                class_dendrograms.append(class_dendrogram)
771           
772        # Class labels   
773        for i, class_ in enumerate(classes):
774            if class_ is not None:
775                item = GtI(class_, widget)
776                item = GraphicsSimpleTextLayoutItem(item, parent=widget)
777                item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
778                layout.addItem(item, i*2 + 4, 2)
779                layout.setRowSpacing(i*2 + 4, 2)
780                layout.setAlignment(item, Qt.AlignLeft | Qt.AlignVCenter)
781               
782        font = QFont()
783        font.setPointSize(self.point_size_hint(self.CellHeight))
784       
785        class_row_labels = [map(str, hm.exampleIndices) for hm in heatmaps[0]]
786        group_column_labels = [[a.name for a in hm[0].examples.domain.attributes] for hm in heatmaps]
787       
788        # Gene annotations on the right side
789        for i, labels in enumerate(class_row_labels):
790            list = GraphicsSimpleTextList(labels, parent=widget, orientation=Qt.Vertical)
791            list.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
792            list.setFont(font)
793            list.setContentsMargins(0.0, 0.0, 0.0, 0.0)
[1403]794            list.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
[1401]795           
796            layout.addItem(list, i*2 + 5, len(self.heatmaps) + 2)
797            layout.setAlignment(list, Qt.AlignLeft)
798            gene_annotation_widgets.append(list)
799           
800        font = QFont()
801        font.setPointSizeF(self.point_size_hint(self.CellWidth))
802       
803        if self.ShowAverageStripe:
804            stripe_offset = c_averageStripeWidth + 2
805        else:
806            stripe_offset = 0
807           
808        for column, (hm, labels, group) in enumerate(zip(heatmaps, group_column_labels, self.group_domains)):
809            column_heatmap_widgets = []
810           
811            # Top group label
812            if len(heatmaps) > 1:
813                item = GtI(group[0], widget)
814                item = GraphicsSimpleTextLayoutItem(item, parent=widget)
815                layout.addItem(item, 1, column + 2)
816                layout.setRowSpacing(1, 2)
817                layout.setRowMaximumHeight(1, item.geometry().height())
818                layout.setAlignment(item, Qt.AlignLeft | Qt.AlignVCenter)
819            else:
820                layout.setRowMaximumHeight(1, 0)
821                layout.setRowSpacing(1, 0)
822               
823            # Top dendrogram
824            if attr_cluster is not None:
825                attr_dendrogram = DendrogramWidget(attr_cluster, parent=widget, orientation=Qt.Horizontal)
826                attr_dendrogram.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
827                attr_dendrogram.translate(0.0, attr_dendrogram.size().height())
828                attr_dendrogram.scale(1.0, -1.0)
829               
830                left, top, right, bottom = attr_dendrogram.layout().getContentsMargins()
831                attr_dendrogram.layout().setContentsMargins(stripe_offset + self.CellWidth / 2.0, 0.0, self.CellWidth / 2.0, bottom)
832                attr_dendrogram.setMinimumHeight(100)
833                attr_dendrogram.setMaximumHeight(100)
834               
835                layout.addItem(attr_dendrogram, 2, column + 2)
836                layout.setRowMaximumHeight(2, 100)
837                attr_dendrograms.append(attr_dendrogram)
838           
839           
840            # Heatmap widget for each class
841            for i, (class_, chm) in enumerate(zip(classes, hm)): 
842                hm_widget = GraphicsHeatmapWidget(heatmap=chm, parent=widget)
843                hm_widget.set_cell_size(int(self.CellWidth), int(self.CellHeight))
844                hm_widget.set_cuts(cut_low, cut_high)
845                hm_widget.set_color_table(palette)
846                hm_widget.set_show_averages(self.ShowAverageStripe)
847                hm_widget.cell_tool_tip = lambda row, col, hm=hm_widget: self.cell_tool_tip(hm, row, col)
848                layout.addItem(hm_widget, i*2 + 5, column + 2)
849                column_heatmap_widgets.append(hm_widget)
850            heatmap_widgets.append(column_heatmap_widgets)
851           
852            # Top attr annotations
853            list = GraphicsSimpleTextList(labels, parent=widget, orientation=Qt.Horizontal)
854            list.setAlignment(Qt.AlignBottom | Qt.AlignLeft)
855           
856            list.setFont(font)
857            list.layout().setContentsMargins(stripe_offset, 0, 0, 0)
858            list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
859           
860            layout.addItem(list, 3, column + 2, Qt.AlignBottom | Qt.AlignLeft)
861            attr_annotation_widgets.append(list)
862            attr_annotation_widgets_top.append(list)
863           
864            # Bottom attr annotations
865            list = GraphicsSimpleTextList(labels, parent=widget, orientation=Qt.Horizontal)
866            list.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
867           
868            list.setFont(font)
869            list.layout().setContentsMargins(stripe_offset, 0, 0, 0)
870            list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
871           
872            layout.addItem(list, len(hm)*2 + 5, column + 2)
873            attr_annotation_widgets.append(list)
874            attr_annotation_widgets_bottom.append(list)
875           
876            # Legend
877            if column == 0:
878                item = GraphicsLegendWidget(self.heatmapconstructor[0], self.lowerBound, self.upperBound, parent=widget)
879                item.set_color_table(palette)
880                item.setVisible(self.ShowLegend)
881                layout.addItem(item, 0, 2, 1, len(self.heatmaps) + 1)
882                layout.setRowSpacing(0, 2)
883#                layout.setRowMaximumHeight(0, item.geometry().height())
884           
885        self.heatmap_scene.addItem(widget)
886        self.heatmap_scene.widget = widget
887        self.heatmap_widget_grid = heatmap_widgets
888        self.gene_annotation_widgets = gene_annotation_widgets
889        self.attr_annotation_widgets = attr_annotation_widgets
890        self.attr_annotation_widgets_top = attr_annotation_widgets_top
891        self.attr_annotation_widgets_bottom = attr_annotation_widgets_bottom
892        self.attr_dendrogram_widgets = attr_dendrograms
893       
894        self.update_annotations()
895        self.update_column_annotations()
896       
897        self.fix_grid_layout()
898       
899        self.selection_manager.set_heatmap_widgets(heatmap_widgets)
900       
901    def fix_grid_layout(self):
902        """ Fix grid layout when cell size changes or average
903        stripes are shown/hiddens.
904        """
905        if self.heatmap_scene.widget:
906            layout = self.heatmap_scene.widget.layout()
907            layout.invalidate()
908            layout.activate()
909           
910            for i, hw in enumerate(self.heatmap_widget_grid[0]):
911                max_h = hw.size().height()
912                layout.setRowMaximumHeight(i*2 + 5, max_h)
913               
914            for i, hw in enumerate(self.heatmap_widget_grid):
915                max_w = hw[0].size().width()
916                layout.setColumnMaximumWidth(i + 2, max_w)
917                if self.attr_dendrogram_widgets:
918                    dendrogram = self.attr_dendrogram_widgets[i]
919#                    dendrogram.resize(max_w, -1)
920                    dendrogram.setMaximumWidth(max_w)
921                self.attr_annotation_widgets_top[i].setMaximumWidth(max_w)
922                self.attr_annotation_widgets_bottom[i].setMaximumWidth(max_w)
923               
924#            for i, (hw, dend) in enumerate(zip(self.heatmap_widget_grid, self.attr_dendrogram_widgets)):
925#                max_w = hw[0].size().width()
926           
927#            self.update_widget_margins()   
928            self.heatmap_scene.widget.resize(self.heatmap_scene.widget.sizeHint(Qt.PreferredSize))
929           
930            self.on_selection_changed()
931            self.update_scene_rect()
932       
933    def update_scene_rect(self):
934        rect = QRectF()
935        for item in self.heatmap_scene.items():
936            rect |= item.sceneBoundingRect()
937        self.heatmap_scene.setSceneRect(rect)
938           
939    def heatmap_widgets(self):
940        """ Iterate over heatmap widgets.
941        """
942        for item in self.heatmap_scene.items():
943            if isinstance(item, GraphicsHeatmapWidget):
944                yield item
945               
946    def label_widgets(self):
947        """ Iterate over GraphicsSimpleTextList widgets.
948        """
949        for item in self.heatmap_scene.items():
950            if isinstance(item, GraphicsSimpleTextList):
951                yield item
952               
953    def dendrogram_widgets(self):
954        """ Iterate over dendrogram widgets
955        """
956        for item in self.heatmap_scene.items():
957            if isinstance(item, DendrogramWidget):
958                yield item
959               
960    def legend_widgets(self):
961        for item in self.heatmap_scene.items():
962            if isinstance(item, GraphicsLegendWidget):
963                yield item
964               
965    def update_cell_size(self):
966        """ Update cell sizes (by user request - height/width sliders)
967        """ 
968        for heatmap in self.heatmap_widgets():
969            heatmap.set_cell_size(self.CellWidth, self.CellHeight)
970           
971        hor_font = QFont(self.font())
972        hor_font.setPointSize(self.point_size_hint(self.CellWidth))
973        vert_font = QFont(self.font())
974        vert_font.setPointSize(self.point_size_hint(self.CellHeight))
975       
976        # Also update the annotation items font.
977        for labels in self.label_widgets():
978            if labels.orientation == Qt.Vertical:
979                labels.setFont(vert_font)
980            else:
981                labels.setFont(hor_font)
[652]982
[1401]983        self.update_widget_margins()
984        ## To hide the annotations if cell sizes to small.
985        self.update_annotations()
986        self.update_column_annotations()
987           
988        self.fix_grid_layout()
[242]989       
[1401]990    def update_widget_margins(self):
991        """ Update dendrogram and text list widgets margins to incude the
992        space for average stripe.
993        """
994        if self.ShowAverageStripe:
995            stripe_offset = c_averageStripeWidth + 2
996        else:
997            stripe_offset = 0
998        right = self.CellWidth / 2.0
999       
1000        top = self.CellHeight / 2.0 / self.Merge
1001        bottom = self.CellHeight / 2.0 / self.Merge
1002       
1003        for dendrogram in self.dendrogram_widgets():
1004            layout = dendrogram.layout() 
1005            if dendrogram.orientation == Qt.Horizontal:
1006#                index = self.attr_dendrogram_widgets.index(dendrogram)
1007#                heatmap = self.heatmap_widget_grid[index][0]
1008#                h_w = heatmap.size().width()
1009#                d_w = dendrogram.size().width()
1010#                right_ = d_w - stripe_offset - self.CellWidth - h_w
1011                _, top_h, _, bottom_h = layout.getContentsMargins()
1012                layout.setContentsMargins(stripe_offset + self.CellWidth / 2.0, top_h, right, bottom_h)
[242]1013            else:
[1401]1014                left_v, _, right_v, _ = layout.getContentsMargins()
1015                layout.setContentsMargins(left_v, top, right_v, bottom)
1016               
1017        for widget in self.label_widgets():
1018            layout = widget.layout()
1019            if widget.orientation == Qt.Horizontal:
1020                left_h, top, right, bottom = layout.getContentsMargins()
1021                layout.setContentsMargins(stripe_offset, top, right, bottom)
1022       
1023    def update_averages_stripe(self):
1024        """ Update the visibility of the averages stripe.
1025        """
1026        if self.data:
1027            for widget in self.heatmap_widgets():
1028                widget.set_show_averages(self.ShowAverageStripe)
1029               
1030            self.update_widget_margins()
1031            self.fix_grid_layout()
[242]1032           
[1401]1033    def update_grid_spacing(self):
1034        """ Update layout spacing.
1035        """
1036        if self.scene.widget:
1037            layout = self.scene.widget.layout()
1038            layout.setSpacing(self.SpaceX)
1039            self.fix_grid_layout()
1040       
1041    def update_color_schema(self):
1042        palette = self.getGammaCorrectedPalette() if self.Gamma !=0 else self.palette
1043        for heatmap in self.heatmap_widgets():
1044            heatmap.set_color_table(palette)
[242]1045           
[1401]1046        for legend in self.legend_widgets():
1047            legend.set_color_table(palette)
1048           
1049    def update_thresholds(self):
[242]1050        self.sliderCutLow.box.setDisabled(not self.CutEnabled)
1051        self.sliderCutHigh.box.setDisabled(not self.CutEnabled)
[1401]1052           
1053        if self.data:
1054            if self.CutEnabled:
1055                low, high = self.CutLow, self.CutHigh
1056            else:
1057                low, high = self.lowerBound, self.upperBound
1058            for heatmap in self.heatmap_widgets():
1059                heatmap.set_cuts(low, high)
1060   
1061    def update_sorting(self):
1062        if self.data:
1063            self.update_heatmaps()
[1840]1064       
1065    def update_sorting_examples(self):
1066        if self.data:
1067            self.update_heatmaps()
1068
1069    def update_sorting_attributes(self):
1070        if self.data:
1071            self.update_heatmaps()
1072
[1401]1073    def update_legend(self):
1074        for item in self.heatmap_scene.items():
1075            if isinstance(item, GraphicsLegendWidget):
1076                item.setVisible(self.ShowLegend)
1077       
1078    def update_annotations(self):
1079        if self.data:
1080            if self.meta:
1081                attr = self.meta[self.BAnnotationIndx]
1082            else:
1083                attr = None
1084           
1085            show = self.ShowGeneAnnotations and attr and self.Merge == 1
1086            show = show and self.CellHeight > 3
1087            for list_widget, hm in zip(self.gene_annotation_widgets, self.heatmap_widget_grid[0]):
1088                list_widget.setVisible(bool(show))
1089                if show:
1090                    hm = hm.heatmap
1091                    examples = hm.examples
1092                    indices = hm.exampleIndices[:-1]
1093                    labels = [str(examples[i][attr]) for i in indices]
1094                    list_widget.set_labels(labels)
[242]1095
[1401]1096    def update_column_annotations(self):
1097        if self.data:
1098            show = self.CellWidth > 3
1099            show_top = self.ShowColumnLabels and self.ColumnLabelPosition == 0 and show
1100            show_bottom = self.ShowColumnLabels and self.ColumnLabelPosition == 1 and show
1101           
1102            for list_widget in self.attr_annotation_widgets_top:
1103                list_widget.setVisible(show_top)
1104               
1105            layout = self.heatmap_scene.widget.layout()
1106            layout.setRowMaximumHeight(3,  -1 if show_top else 0)
1107            layout.setRowSpacing(3, -1 if show_top else 0)
1108               
1109            for list_widget in self.attr_annotation_widgets_bottom:
1110                list_widget.setVisible(show_bottom)
1111               
1112            layout.setRowMaximumHeight(len(self.heatmap_widget_grid[0]) + 4, -1 if show_top else 0)
1113               
1114            self.fix_grid_layout()
1115           
1116    def update_heatmaps(self):
1117        if self.data:
1118            self.construct_heatmaps(self.data, self.selected_split_label())
1119            self.create_heatmaps(self.heatmapconstructor)
1120            self.clear_scene()
1121            self.construct_heatmaps_scene(self.heatmaps, self.data,
1122                                          attr_cluster=self.attr_cluster,
1123                                          data_clusters=self.data_clusters)
1124        else:
1125            self.clear()
1126       
1127    def update_heatmaps_stage2(self):
1128        if self.data:
1129            self.create_heatmaps(self.heatmapconstructor)
1130            self.clear_scene()
1131            self.construct_heatmaps_scene(self.heatmaps, self.data,
1132                                          attr_cluster=self.attr_cluster,
1133                                          data_clusters=self.data_clusters)
1134       
1135    def cell_tool_tip(self, heatmap_widget, row, column):
1136        if not self.GShowToolTip:
1137            return ""
1138        hm = heatmap_widget.heatmap
1139        examples = hm.examples[hm.exampleIndices[row] : hm.exampleIndices[row+1]]
1140        domain = hm.examples.domain
1141        if hm.getCellIntensity(row, column) != None:
1142            head = "%6.4f" % hm.getCellIntensity(row, column)
1143        else:
1144            head = "Missing Data"
1145        if self.BShowColumnID:
1146            head += "\n" + domain.attributes[column].name
1147        # tool tip, construct body
1148        body = ""
1149        if (self.BShowSpotIndex and self.BSpotVar) or \
1150                (self.BShowAnnotation and self.BAnnotationVar) or \
1151                 self.BShowGeneExpression:
1152            for (i, e) in enumerate(examples):
1153                if i > 5:
1154                    body += "\n... (%d more)" % (len(examples) - 5)
1155                    break
1156                else:
1157                    s = []
1158                    if self.BShowSpotIndex and self.BSpotVar:
1159                        s.append(str(e[self.BSpotVar]))
1160                    if self.BShowGeneExpression:
1161                        s.append(str(e[column]))
1162                    if self.BShowAnnotation and self.BAnnotationVar:
1163                        s.append(str(e[self.BAnnotationVar]))
1164           
1165                body += "\n"
1166                body += " | ".join(s)
1167        return head + body
1168   
1169    def on_merge_changed(self):
[242]1170        self.oldMerge = self.savedMerge
[1401]1171        if self.MaintainArrayHeight and self.oldMerge != self.Merge:
1172            k = float(self.Merge) / self.oldMerge
1173            l = max(1, min(int(self.CellHeight * k), self.maxVSize))
1174            if l != self.CellHeight:
[242]1175                self.CellHeight = l
1176                self.sliderVSize.setValue(self.CellHeight)
1177
[1401]1178        self.update_heatmaps_stage2()
[242]1179        self.savedMerge = self.Merge
[1401]1180           
1181    def on_selection_changed(self):
1182        for item in self.selection_rects:
1183            item.hide()
1184            item.setParentItem(None)
1185            item.update()
1186            self.heatmap_scene.removeItem(item)
1187        self.selection_rects = []
1188        self.selection_manager.update_selection_rects()
1189        rects = self.selection_manager.selection_rects
1190        for rect in rects:
1191            item = QGraphicsRectItem(rect, None, self.heatmap_scene)
1192            item.setPen(QPen(Qt.black, 2))
1193            self.selection_rects.append(item)
1194           
1195    def on_selection_finished(self):
1196        self.selected_rows = self.selection_manager.selections
1197        self.commit_if()
[242]1198       
[1401]1199    def commit_if(self):
1200        if self.auto_commit:
1201            self.commit()
1202        else:
1203            self.selection_changed_flag = True
1204       
1205    def commit(self):
1206        data = None
1207        if self.sorted_data:
1208            if self.selected_rows:
1209                examples = [self.sorted_data[i] for i in self.selected_rows]
1210                data = orange.ExampleTable(examples)
[667]1211            else:
[1401]1212                data = None
1213       
1214        self.send("Examples", data)
1215        self.selection_changed_flag
[1136]1216           
[1401]1217    ## handle saved selections
[1290]1218    def settingsFromWidgetCallbackSelection(self, handler, context):
[1401]1219        context.selection = self.selection_manager.selections
[1290]1220
1221    def settingsToWidgetCallbackSelection(self, handler, context):
1222        selection = getattr(context, "selection", None)
1223        if selection:
1224            try:
[1401]1225                self.selection_manager.select_rows(selection)
[1290]1226            except Exception, ex:
[1401]1227                pass
1228#                self.warning(3, "Could not restore selection")
1229               
1230    def split_changed(self):
1231        if self.data:
1232            self.clear_scene()
1233            self.construct_heatmaps(self.data, self.selected_split_label())
1234            self.create_heatmaps(self.heatmapconstructor)
1235            self.construct_heatmaps_scene(self.heatmaps, self.data,
1236                                          attr_cluster=self.attr_cluster,
1237                                          data_clusters=self.data_clusters)
[1290]1238       
[242]1239
[1401]1240class GraphicsPixmapLayoutItem(QGraphicsLayoutItem):
1241    """ A layout item wraping a QGraphicsPixmapItem
1242    """
1243    def __init__(self, pixmap_item, parent=None):
1244        QGraphicsLayoutItem.__init__(self, parent)
1245        self.pixmap_item = pixmap_item
1246        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1247       
1248    def setGeometry(self, rect):
1249        QGraphicsLayoutItem.setGeometry(self, rect)
1250        self.pixmap_item.setPos(rect.topLeft())
1251       
1252    def sizeHint(self, which, constraint=QSizeF()):
1253        return QSizeF(self.pixmap_item.pixmap().size())
1254   
1255    def setPixmap(self, pixmap):
1256        self.pixmap_item.setPixmap(pixmap)
1257        self.updateGeometry()
1258       
1259       
1260class GraphicsHeatmapWidget(QGraphicsWidget):
1261    def __init__(self, heatmap=None, parent=None, scene=None):
1262        QGraphicsWidget.__init__(self, parent)
1263        self.setAcceptHoverEvents(True)
1264        layout = QGraphicsLinearLayout(Qt.Horizontal)
1265        layout.setContentsMargins(0, 0, 0, 0)
1266        item = QGraphicsPixmapItem(self)
1267        item.setShapeMode(QGraphicsPixmapItem.BoundingRectShape)
1268        self.heatmap_item = GraphicsPixmapLayoutItem(item, self)
1269       
1270        item = QGraphicsPixmapItem(self)
1271        item.setShapeMode(QGraphicsPixmapItem.BoundingRectShape)
1272        self.averages_item = GraphicsPixmapLayoutItem(item, self)
1273       
1274        layout.addItem(self.averages_item)
1275        layout.addItem(self.heatmap_item)
1276        layout.setItemSpacing(0, 2)
1277       
1278        self.setLayout(layout)
1279       
1280        self.heatmap = None
1281        self.show_averages = True
1282        self._pixmap_args = None
1283        self.color_table = None
1284        self.selection_manager = None
1285        self.set_cell_size(4, 4)
1286        self.set_cuts(0, 255)
1287        self.set_heatmap(heatmap)
1288       
1289        if scene is not None:
1290            scene.addItem(self)
1291           
1292    def clear(self):
1293        """ Clear the current heatmap.
1294        """
1295        self.heatmap = None
1296        self._pixmap_args = None
1297        self.heatmap_item.setPixmap(QPixmap())
1298        self.averages_item.setPixmap(QPixmap())
1299        self.show_averages = True
1300        self.layout().invalidate()
1301           
1302    def set_heatmap(self, heatmap):
1303        """ Set the heatmap for display.
1304        """
1305        self.clear()
1306        self.heatmap = heatmap
1307        self.update()
1308       
1309    def set_cell_size(self, width, height):
1310        self.cell_width = width
1311        self.cell_height = height
1312        self.update()
1313       
1314    def set_cuts(self, low, high):
1315        self.cut_low = low
1316        self.cut_high = high
1317        self.update()
1318       
1319    def set_show_averages(self, show):
1320        self.show_averages = show
1321        self._pixmap_args = None
1322        self.update()
[242]1323
[1401]1324    def set_color_table(self, color_table):
[1110]1325        if qVersion() <= "4.5":
[1401]1326            self.color_table = signedPalette(color_table)
[1110]1327        else:
[1401]1328            self.color_table = color_table
[242]1329       
[1401]1330        self._pixmap_args = None
1331        self.update()
1332           
1333    def _update_pixmap(self):
1334        """ Update the pixmap if its construction arguments changed.
1335        """
1336        if self.heatmap:
1337            args = (int(self.cell_width), int(self.cell_height),
1338                    self.cut_low, self.cut_high, 1.0)
1339           
1340            if args != self._pixmap_args:
1341                bitmap, width, height = self.heatmap.getBitmap(*args)
1342                image = QImage(bitmap, width, height, QImage.Format_Indexed8)
1343                color_table = self.color_table
1344                if color_table:
1345                    image.setColorTable(color_table)
1346                self.pixmap = QPixmap.fromImage(image)
1347               
1348                bitmap, width, height = self.heatmap.getAverages(*((c_averageStripeWidth,) + args[1:]))
1349                image = QImage(bitmap, width, height, QImage.Format_Indexed8)
1350                if self.color_table:
1351                    image.setColorTable(color_table)
1352                self.averages_pixmap = QPixmap.fromImage(image)
1353               
1354                self._pixmap_args = args
1355               
1356                self.layout().invalidate()
1357        else:
1358            self.averages_pixmap = None
1359            self.pixmap = None
1360           
1361        self.heatmap_item.setPixmap(self.pixmap or QPixmap())
1362        if self.show_averages and self.averages_pixmap:
1363            self.averages_item.setPixmap(self.averages_pixmap)
1364        else:
1365            self.averages_item.setPixmap(QPixmap())
1366           
1367    def update(self):
1368        self._update_pixmap()
1369        QGraphicsWidget.update(self)
1370       
1371    def set_selection_manager(self, manager):
1372        self.selection_manager = manager
1373       
1374    def cell_at(self, pos):
1375        """ Return the cell row, column from a point `pos` in local
1376        coordinates.
1377       
1378        """
1379        pos = self.mapToItem(self.heatmap_item.pixmap_item, pos)
1380        x, y = pos.x(), pos.y()
1381        def clamp(i, m):
1382            return int(min(max(i, 0), m))
1383        return (clamp(math.floor(y / self.cell_height), self.heatmap.height),
1384                clamp(math.floor(x / self.cell_width), self.heatmap.width))
1385   
1386    def cell_rect(self, row, column):
1387        """ Return a QRectF in local coordinates containing the cell
1388        at `row` and `column`.
1389       
1390        """
1391        top = QPointF(column * self.cell_width, row * self.cell_height)
1392        top = self.mapFromItem(self.heatmap_item.pixmap_item, top)
1393        size = QSizeF(self.cell_width, self.cell_height)
1394        return QRectF(top, size)
[242]1395
[1401]1396    def row_rect(self, row):
1397        """ Return a QRectF in local coordinates containing the entire row.
1398        """
1399        rect = self.cell_rect(row, 0).united(self.cell_rect(row, self.heatmap.width - 1))
1400        rect.setLeft(0) # To include the average stripe if show.
1401        return rect
1402   
1403    def cell_tool_tip(self, row, column):
1404        hm = self.heatmap
1405        start = int(hm.exampleIndices[row])
1406        end = int(hm.exampleIndices[row + 1])
1407        examples = [hm.examples[start]]
1408        attr = hm.examples.domain[column]
1409        val = "%i, %i: %f" % (row, column, float(examples[0][attr]))
1410        return val
1411   
1412    def hoverEnterEvent(self, event):
1413        row, col = self.cell_at(event.pos())
1414   
1415    def hoverMoveEvent(self, event):
1416        pos = event.pos()
1417        row, column = self.cell_at(pos)
1418        tooltip = self.cell_tool_tip(row, column)
1419        QToolTip.showText(event.screenPos(), tooltip)
1420        return QGraphicsWidget.hoverMoveEvent(self, event)
1421   
1422    def hoverLeaveEvent(self, event):
1423        row, col = self.cell_at(event.pos())
1424   
1425    if DEBUG:
1426        def paint(self, painter, option, widget=0):
1427            rect =  self.geometry()
1428            rect.translate(-self.pos())
1429            painter.drawRect(rect.adjusted(-1, -1, 1, 1))
1430   
1431   
1432class GridWidget(QGraphicsWidget):
1433    def __init__(self, parent=None):
1434        QGraphicsWidget.__init__(self, parent)
1435       
1436    if DEBUG:
1437        def paint(self, painter, option, widget=0):
1438            rect =  self.geometry()
1439            rect.translate(-self.pos())
1440            painter.drawRect(rect)
1441           
1442           
1443class HeatmapScene(QGraphicsScene):
1444    """ A Graphics Scene with heatmap widgets.
1445    """
1446    def __init__(self, parent=None):
1447        QGraphicsScene.__init__(self, parent)
1448        self.selection_manager = HeatmapSelectionManager()
1449       
1450    def set_selection_manager(self, manager):
1451        self.selection_manager = manager
1452       
1453    def _items(self, pos=None, cls=object):
1454        if pos is not None:
1455            items = self.items(QRectF(pos, QSizeF(3, 3)).translated(-1.5, -1.5))
1456        else:
1457            items = self.items()
1458           
1459        for item in items:
1460            if isinstance(item, cls):
1461                yield item
1462           
1463    def heatmap_at_pos(self, pos):
1464        items  = list(self._items(pos, GraphicsHeatmapWidget))
1465        if items:
1466            return items[0]
1467        else:
1468            return None
1469       
1470    def dendrogram_at_pos(self, pos):
1471        items  = list(self._items(pos, DendrogramItem))
1472        if items:
1473            return items[0]
1474        else:
1475            return None
1476       
1477    def heatmap_widgets(self):
1478        return self._items(None, GraphicsHeatmapWidget)
1479       
1480    def select_from_dendrogram(self, dendrogram, clear=True):
1481        """ Select all heatmap rows which belong to the dendrogram.
1482        """
1483        dendrogram_widget = dendrogram.parentWidget()
1484        anchors = list(dendrogram_widget.leaf_anchors())
1485        cluster = dendrogram.cluster
1486        start, end = anchors[cluster.first], anchors[cluster.last - 1]
1487        start, end = dendrogram_widget.mapToScene(start), dendrogram_widget.mapToScene(end)
1488        # Find a heatmap widget containing start and end y coordinates.
1489       
1490        heatmap = None
1491        for hm in self.heatmap_widgets():
1492            b_rect = hm.sceneBoundingRect()
1493            if b_rect.contains(QPointF(b_rect.center().x(), start.y())):
1494                heatmap = hm
1495                break
1496           
1497        if dendrogram:
1498            b_rect = hm.boundingRect()
1499            start, end = hm.mapFromScene(start), hm.mapFromScene(end)
1500            start, _ = hm.cell_at(QPointF(b_rect.center().x(), start.y()))
1501            end, _ = hm.cell_at(QPointF(b_rect.center().x(), end.y()))
1502            self.selection_manager.selection_add(start, end, hm, clear=clear)
1503        return
1504       
1505    def mousePressEvent(self, event):
1506        pos = event.scenePos()
1507        heatmap = self.heatmap_at_pos(pos)
1508        if heatmap and event.button() & Qt.LeftButton:
1509            row, _ = heatmap.cell_at(heatmap.mapFromScene(pos))
1510            self.selection_manager.selection_start(heatmap, event)
1511           
1512        dendrogram = self.dendrogram_at_pos(pos)
1513        if dendrogram and event.button() & Qt.LeftButton:
1514            if dendrogram.orientation == Qt.Vertical:
1515                self.select_from_dendrogram(dendrogram, clear=not event.modifiers() & Qt.ControlModifier)
1516            return 
1517       
1518        return QGraphicsScene.mousePressEvent(self, event)
1519   
1520    def mouseMoveEvent(self, event):
1521        pos = event.scenePos()
1522        heatmap = self.heatmap_at_pos(pos)
1523        if heatmap and event.buttons() & Qt.LeftButton:
1524            row, _ = heatmap.cell_at(heatmap.mapFromScene(pos))
1525            self.selection_manager.selection_update(heatmap, event)
1526           
1527        dendrogram = self.dendrogram_at_pos(pos)
1528        if dendrogram and dendrogram.orientation == Qt.Horizontal: # Filter mouse move events
1529            return
1530           
1531        return QGraphicsScene.mouseMoveEvent(self, event)
1532   
1533    def mouseReleaseEvent(self, event):
1534        pos = event.scenePos()
1535        heatmap = self.heatmap_at_pos(pos)
1536        if heatmap:
1537            row, _ = heatmap.cell_at(heatmap.mapFromScene(pos))
1538            self.selection_manager.selection_finish(heatmap, event)
1539       
1540        dendrogram = self.dendrogram_at_pos(pos)
1541        if dendrogram and dendrogram.orientation == Qt.Horizontal: # Filter mouse events
1542            return
1543       
1544        return QGraphicsScene.mouseReleaseEvent(self, event)
1545   
1546    def mouseDoubleClickEvent(self, event):
1547        pos = event.scenePos()
1548        dendrogram = self.dendrogram_at_pos(pos)
1549        if dendrogram: # Filter mouse events
1550            return
1551        return QGraphicsScene.mouseDoubleClickEvent(self, event)
1552       
1553       
1554class GtI(QGraphicsSimpleTextItem):
1555    if DEBUG:
1556        def paint(self, painter, option, widget =0):
1557            QGraphicsSimpleTextItem.paint(self, painter, option, widget)
1558            painter.drawRect(self.boundingRect())
1559           
1560   
1561class GraphicsSimpleTextLayoutItem(QGraphicsLayoutItem):
1562    """ A Graphics layout item wrapping a QGraphicsSimpleTextItem alowing it
1563    to be managed by a layout.
1564    """
1565    def __init__(self, text_item, orientation=Qt.Horizontal, parent=None):
1566        QGraphicsLayoutItem.__init__(self, parent)
1567        self.orientation = orientation
1568        self.text_item = text_item
1569        if orientation == Qt.Vertical:
1570            self.text_item.rotate(-90)
[1403]1571            self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
[1401]1572        else:
[1403]1573            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
[1401]1574       
1575    def setGeometry(self, rect):
1576        QGraphicsLayoutItem.setGeometry(self, rect)
1577        if self.orientation == Qt.Horizontal:
1578            self.text_item.setPos(rect.topLeft())
1579        else:
1580            self.text_item.setPos(rect.bottomLeft())
1581       
1582    def sizeHint(self, which, constraint=QSizeF()):
1583        if which in [Qt.PreferredSize]:
1584            size = self.text_item.boundingRect().size()
1585            if self.orientation == Qt.Horizontal:
1586                return size
1587            else:
1588                return QSizeF(size.height(), size.width())
1589        else:
1590            return QSizeF()
1591   
1592    def setFont(self, font):
1593        self.text_item.setFont(font)
1594        self.updateGeometry()
1595       
1596    def setText(self, text):
1597        self.text_item.setText(text)
1598        self.updateGeometry()
1599       
1600       
1601class GraphicsSimpleTextList(QGraphicsWidget):
1602    """ A simple text list widget.
1603    """
1604    def __init__(self, labels=[], orientation=Qt.Vertical, parent=None, scene=None):
1605        QGraphicsWidget.__init__(self, parent)
1606        layout = QGraphicsLinearLayout(orientation)
1607        layout.setContentsMargins(0, 0, 0, 0)
1608        layout.setSpacing(0)
1609        self.setLayout(layout)
1610        self.orientation = orientation
1611        self.alignment = Qt.AlignCenter
1612        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
1613        self.set_labels(labels)
1614       
1615        if scene is not None:
1616            scene.addItem(self)
1617       
1618    def clear(self):
1619        """ Remove all text items.
1620        """
1621        layout = self.layout()
1622        for i in reversed(range(layout.count())):
1623            item = layout.itemAt(i)
1624            item.text_item.setParentItem(None)
1625            if self.scene():
1626                self.scene().removeItem(item.text_item)
1627            layout.removeAt(i)
1628       
1629        self.label_items = []
1630        self.updateGeometry()
1631       
1632    def set_labels(self, labels):
1633        """ Set the text labels to show in the widget.
1634        """
1635        self.clear()
1636        orientation = Qt.Horizontal if self.orientation == Qt.Vertical else Qt.Vertical
1637        for text in labels:
1638#            item = QGraphicsSimpleTextItem(text, self)
1639            item = GtI(text, self)
1640            item.setFont(self.font())
1641            item.setToolTip(text)
1642            item = GraphicsSimpleTextLayoutItem(item, orientation, parent=self)
1643            self.layout().addItem(item)
1644            self.layout().setAlignment(item, self.alignment)
1645            self.label_items.append(item)
1646           
1647        self.layout().activate()
1648        self.updateGeometry()
1649   
1650    def setAlignment(self, alignment):
1651        """ Set alignment of text items in the widget
1652        """
1653        self.alignment = alignment
1654        layout = self.layout()
1655        for i in range(layout.count()):
1656            layout.setAlignment(layout.itemAt(i), alignment)
1657           
1658    def setVisible(self, bool):
1659        QGraphicsWidget.setVisible(self, bool)
1660        self.updateGeometry()
1661           
1662    def setFont(self, font):
1663        """ Set the font for the text.
1664        """
1665        QGraphicsWidget.setFont(self, font)
1666        for item in self.label_items:
1667            item.setFont(font)
1668        self.layout().invalidate()
1669        self.updateGeometry()
1670       
1671    def sizeHint(self, which, constraint=QRectF()):
1672        if not self.isVisible():
1673            return QSizeF(0, 0)
1674        else:
1675            return QGraphicsWidget.sizeHint(self, which, constraint)
1676           
1677    if DEBUG:
1678        def paint(self, painter, options, widget=0):
1679            rect =  self.geometry()
1680            rect.translate(-self.pos())
1681            painter.drawRect(rect)
1682       
1683class GraphicsLegendWidget(QGraphicsWidget):
1684    def __init__(self, heatmap_constructor, low, high, parent=None, scene=None):
1685        QGraphicsWidget.__init__(self, parent)
1686        layout = QGraphicsLinearLayout(Qt.Vertical)
1687        self.setLayout(layout)
1688        layout.setContentsMargins(0, 0, 0, 0)
1689        layout.setSpacing(1)
1690       
1691        layout_labels = QGraphicsLinearLayout(Qt.Horizontal)
1692        layout.addItem(layout_labels)
1693        layout_labels.setContentsMargins(0, 0, 0, 0)
1694        label_lo = GtI("%.2f" % low, self)
1695        label_hi = GtI("%.2f" % high, self)
1696        self.item_low = GraphicsSimpleTextLayoutItem(label_lo, parent=self)
1697        self.item_high = GraphicsSimpleTextLayoutItem(label_hi, parent=self)
1698       
1699        layout_labels.addItem(self.item_low)
1700        layout.addStretch()
1701        layout_labels.addItem(self.item_high)
1702       
1703        self._pixmap = QPixmap(c_legendHeight, c_legendHeight)
1704        self.pixmap_item = QGraphicsPixmapItem(self._pixmap, self)
1705        self.pixmap_item = GraphicsPixmapLayoutItem(self.pixmap_item, parent=self)
1706        layout.addItem(self.pixmap_item)
1707       
1708        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
1709        self.set_legend(heatmap_constructor, low, high)
1710       
1711        if scene is not None:
1712            scene.addItem(self)
1713       
1714    def set_legend(self, heatmap_constructor, low, high):
1715        self.heatmap_constructor = heatmap_constructor
1716        self.low = low
1717        self.high = high
1718        self.color_table = None
1719        self._pixmap = None
1720        self._pixmap_args = None
1721        self.update()
1722        self.updateGeometry()
1723       
1724    def set_color_table(self, color_table):
1725        if qVersion() <= "4.5":
1726            self.color_table = signedPalette(color_table)
1727        else:
1728            self.color_table = color_table
1729       
1730        self._pixmap_args = None
1731        self.update()
1732       
1733    def update(self):
1734        crect = self.contentsRect()
1735        width = crect.width()
1736        height = c_legendHeight
1737        if not self.pixmap_item or self._pixmap_args != (width, height):
1738            bitmap = self.heatmap_constructor.getLegend(int(width), int(height), 1.0)
1739            image = QImage(bitmap, width, height, QImage.Format_Indexed8)
1740            color_table = self.color_table
1741            if color_table:
1742                image.setColorTable(color_table)
1743            self._pixmap = QPixmap.fromImage(image)
1744           
1745        self.pixmap_item.setPixmap(self._pixmap)
1746        self.item_low.setText("%.2f" % self.low)
1747        self.item_high.setText("%.2f" % self.high)
1748        self.layout().activate()
1749        QGraphicsWidget.update(self)
1750           
1751    if DEBUG:
1752        def paint(self, painter, options, widget=0):
1753            rect =  self.geometry()
1754            rect.translate(-self.pos())
1755            painter.drawRect(rect)
[242]1756
[1401]1757       
1758class HeatmapSelectionManager(QObject):
1759    """ Selection manager for heatmap rows
1760    """
1761    def __init__(self, parent=None):
1762        QObject.__init__(self, parent)
1763        self.selections = []
1764        self.selection_ranges = []
1765        self.heatmap_widgets = []
1766        self.selection_rects = []
1767        self.heatmaps = []
1768        self._heatmap_ranges = {}
1769        self._start_row = 0
1770       
1771    def set_heatmap_widgets(self, widgets):
1772        self.remove_rows(self.selections)
1773        self.heatmaps = widgets
1774       
1775        # Compute row ranges for all heatmaps
1776        self._heatmap_ranges = {}
1777        for group in widgets:
1778            start = end = 0
1779            for heatmap in group:
1780                end += heatmap.heatmap.height
1781                self._heatmap_ranges[heatmap] = (start, end)
1782                start = end
1783       
1784    def select_rows(self, rows, heatmap=None, clear=True):
1785        """ Add `rows` to selection. If `heatmap` is provided the rows
1786        are mapped from the local indices to global heatmap indics. If `clear`
1787        then remove previous rows.
1788        """
1789        if heatmap is not None:
1790            start, end = self._heatmap_ranges[heatmap]
1791            rows = [start + r for r in rows]
1792           
1793        old_selection = list(self.selections)
1794        if clear:
1795            self.selections = rows
1796        else:
1797            self.selections = sorted(set(self.selections + rows))
1798        if self.selections != old_selection:
1799            self.update_selection_rects()
1800            self.emit(SIGNAL("selection_changed()"))
1801            self.emit(SIGNAL("selection_rects_changed"), self.selection_rects)
1802           
1803    def remove_rows(self, rows):
1804        """ Remove `rows` from the selection.
1805        """
1806        old_selection = list(self.selections)
1807        self.selections = sorted(set(self.selections) - set(rows))
1808        if old_selection != self.selections:
1809            self.update_selection_rects()
1810            self.emit(SIGNAL("selection_changed()"))
1811            self.emit(SIGNAL("selection_rects_changed"), self.selection_rects)
1812           
1813    def combined_ranges(self, ranges):
1814        combined_ranges = set()
1815        for start, end in ranges:
1816            if start <= end:
1817                rng = range(start, end + 1)
1818            else:
1819                rng = range(start, end - 1, -1)
1820            combined_ranges.update(rng)
1821        return sorted(combined_ranges)
1822       
1823    def selection_start(self, heatmap_widget, event):
1824        """ Selection  started by `heatmap_widget` due to `event`.
1825        """
1826        pos = heatmap_widget.mapFromScene(event.scenePos())
1827        row, column = heatmap_widget.cell_at(pos)
1828        start, _ = self._heatmap_ranges[heatmap_widget]
1829        row = start + row
1830        self._start_row = row
1831        range = (row, row)
1832        if event.modifiers() & Qt.ControlModifier:
1833            self.selection_ranges.append(range)
1834        else:
1835            self.selection_ranges = [range]
1836        self.select_rows(self.combined_ranges(self.selection_ranges))
1837       
1838    def selection_update(self, heatmap_widget, event):
1839        """ Selection updated by `heatmap_widget due to `event` (mouse drag).
1840        """
1841        pos = heatmap_widget.mapFromScene(event.scenePos())
1842        row, column = heatmap_widget.cell_at(pos)
1843        start, _ = self._heatmap_ranges[heatmap_widget]
1844        row = start + row
1845        if self.selection_ranges:
1846            self.selection_ranges[-1] = (self._start_row, row)
1847        else:
1848            self.selection_ranges = [(row, row)]
1849           
1850        self.select_rows(self.combined_ranges(self.selection_ranges))
1851       
1852    def selection_finish(self, heatmap_widget, event):
1853        """ Selection finished by `heatmap_widget due to `event`.
1854        """
1855        pos = heatmap_widget.mapFromScene(event.scenePos())
1856        row, column = heatmap_widget.cell_at(pos)
1857        start, _ = self._heatmap_ranges[heatmap_widget]
1858        row = start + row
1859        range = (self._start_row, row)
1860        self.selection_ranges[-1] = range
1861        self.select_rows(self.combined_ranges(self.selection_ranges),
1862                         clear=not event.modifiers() & Qt.ControlModifier)
1863        self.emit(SIGNAL("selection_finished()"))
1864       
1865    def selection_add(self, start, end, heatmap=None, clear=True):
1866        """ Add a selection range from `start` to `end`.
1867        """ 
1868        if heatmap is not None:
1869            _start, _ = self._heatmap_ranges[heatmap]
1870            start = _start + start
1871            end = _start + end
1872       
1873        if clear:
1874            self.selection_ranges = []
1875        self.selection_ranges.append((start, end))
1876        self.select_rows(self.combined_ranges(self.selection_ranges))
1877        self.emit(SIGNAL("selection_finished()"))
1878       
1879    def update_selection_rects(self):
1880        """ Update the selection rects.
1881        """
1882        def continuous_ranges(selections):
1883            """ Group continuous ranges
1884            """
1885            selections = iter(selections)
1886            start = end = selections.next()
[853]1887            try:
[1401]1888                while True:
1889                    new_end = selections.next()
1890                    if new_end > end + 1:
1891                        yield start, end
1892                        start = end = new_end
1893                    else:
1894                        end = new_end
1895            except StopIteration:
1896                yield start, end
1897               
1898        def group_selections(selections):
1899            """ Group selections along with heatmaps.
1900            """
1901            rows2hm = self.rows_to_heatmaps()
1902            selections = iter(selections)
1903            start = end = selections.next()
1904            end_heatmaps= rows2hm[end]
[1136]1905            try:
[1401]1906                while True:
1907                    new_end = selections.next()
1908                    new_end_heatmaps = rows2hm[new_end]
1909                    if new_end > end + 1 or new_end_heatmaps != end_heatmaps:
1910                        yield start, end, end_heatmaps
1911                        start = end = new_end
1912                        end_heatmaps = new_end_heatmaps
[242]1913                    else:
[1401]1914                        end = new_end
1915                       
1916            except StopIteration:
1917                yield start, end, end_heatmaps
1918               
1919        def selection_rect(start, end, heatmaps):
1920            rect = QRectF()
1921            for heatmap in heatmaps:
1922                h_start, _ = self._heatmap_ranges[heatmap] 
1923                rect |= heatmap.mapToScene(heatmap.row_rect(start - h_start)).boundingRect()
1924                rect |= heatmap.mapToScene(heatmap.row_rect(end - h_start)).boundingRect()
1925            return rect
1926                 
1927        self.selection_rects = []
1928        for start, end, heatmaps in group_selections(self.selections):
1929            rect = selection_rect(start, end, heatmaps)
1930            self.selection_rects.append(rect)
[654]1931           
[1401]1932    def rows_to_heatmaps(self):
1933        heatmap_groups = zip(*self.heatmaps)
1934        rows2hm = {}
1935        for heatmaps in heatmap_groups:
1936            hm = heatmaps[0]
1937            start, end = self._heatmap_ranges[hm]
1938            rows2hm.update(dict.fromkeys(range(start, end), heatmaps))
1939        return rows2hm
1940   
1941         
[242]1942##################################################################################################
1943# test script
1944
1945if __name__=="__main__":
[1300]1946    a=QApplication(sys.argv)
1947    ow = OWHeatMap()
[1372]1948   
[1401]1949#    fn = "/home/marko/pipa4.tab"
1950#    fn = "../../../doc/datasets/brown-selected.tab"
1951    fn = os.path.expanduser("~/Documents/abc-subset")
1952    ow.set_dataset(orange.ExampleTable(fn), 0)
[1300]1953    ow.handleNewSignals()
1954    ow.show()
[1401]1955   
1956    a.exec_()
1957    ow.saveSettings()
[1300]1958
Note: See TracBrowser for help on using the repository browser.