source: orange-bioinformatics/_bioinformatics/widgets/OWHeatMap.py @ 1871:aa8b068bd7a1

Revision 1871:aa8b068bd7a1, 78.2 KB checked in by markotoplak, 7 months ago (diff)

Fixed HeatMap selections with "Sort genes" option. Selection also works correctly with Merge parameter and multiple classes.

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