source: orange-bioinformatics/_bioinformatics/widgets/OWHeatMap.py @ 1845:efe30810466a

Revision 1845:efe30810466a, 77.7 KB checked in by markotoplak, 8 months ago (diff)

OWHeatMap: fixed progress bars.

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       
579        self.clear_scene()
580       
581    def clear_scene(self):
582        self.selection_manager.set_heatmap_widgets([[]])
583        self.heatmap_scene.clear()
584        self.heatmap_scene.widget = None
585        self.heatmap_widget_grid = [[]]
586        self.attr_annotation_widgets = []
587        self.attr_dendrogram_widgets = []
588        self.gene_annotation_widgets = []
589        self.gene_dendrogram_widgets = []
590       
591        self.selection_rects = []
592       
593       
594    def saveFig(self):
595        sizeDlg = OWChooseImageSizeDlg(self.scene, parent=self)
596        sizeDlg.exec_()
597
598    ##########################################################################
599    # handling of input/output signals
600       
601    def set_dataset(self, data=None, id=None):
602        self.closeContext("Selection")
603       
604        self._ordering_cache.clear()
605       
606        self.clear()
607        self.data = data
608        if data is not None:
609#            self.setMetaCombos()
610            self.set_meta_combos()
611            self.set_split_labels()
612           
613        self.unorderedData = None
614        self.groupClusters = None
615       
616    def handleNewSignals(self):
617        self.send('Examples', None)
618        if self.data:
619            self.update_heatmaps()
620        else:
621            self.clear()
622       
623        if self.data:
624            self.openContext("Selection", self.data)
625           
626    def construct_heatmaps(self, data, split_label=None):
627        if split_label is not None:
628            groups = split_domain(data.domain, split_label)
629        else:
630            groups = [("", data.domain)]
631           
632        group_domains = [dom for _, dom in groups]
633
634        attr_ordering = range(len(group_domains[0][1].attributes))
635        attr_cluster = None
636        data_ordering = []
637        data_clusters = [None]
638        sorted_data = data
639
640        self.progressBarInit()
641
642        progress_parts = 0
643
644        args_key_examples = tuple(tuple(d) for d in group_domains), self.SortExamples == 3, "data"
645        args_key_attributes = tuple(tuple(d) for d in group_domains), self.SortAttributes == 2, "attributes"
646
647        if self.SortExamples > 1 and args_key_examples not in self._ordering_cache:
648            classVar = data.domain.class_var
649            if classVar and isinstance(classVar, orange.EnumVariable):
650                progress_parts += len(classVar.values)
651            else:
652                progress_parts += 1
653
654        if self.SortAttributes > 0 and args_key_attributes not in self._ordering_cache:
655            progress_parts += 1
656
657        progress_bar = PP_callback(progress_callback=self.progressBarSet, parts=progress_parts)
658
659        # rows
660        if self.SortExamples > 1:
661
662            cluster_ordering = self._ordering_cache.get(args_key_examples, None)
663            if cluster_ordering is None:
664
665                # Rows separately
666                data_ordering, data_clusters = \
667                        hierarchical_cluster_ordering_examples(data, group_domains,
668                                      opt_order=self.SortExamples == 3,
669                                      pp_callback=progress_bar)
670
671                # Cache the clusters
672                self._ordering_cache[args_key_examples] = (data_ordering, data_clusters)
673            else:
674                data_ordering, data_clusters = cluster_ordering
675           
676            sorted_data = [data[i] for i in itertools.chain(*data_ordering)]
677       
678        # columns
679        if self.SortAttributes > 0:
680
681            cluster_ordering = self._ordering_cache.get(args_key_attributes, None)
682            if cluster_ordering is None:
683
684                # Columns separately
685                attr_ordering, attr_cluster = \
686                        hierarchical_cluster_ordering_attributes(data, group_domains,
687                                      opt_order=self.SortAttributes == 2,
688                                      pp_callback=progress_bar)
689
690                # Cache the clusters
691                self._ordering_cache[args_key_attributes] = (attr_ordering, attr_cluster)
692            else:
693                attr_ordering, attr_cluster = cluster_ordering
694
695        self.progressBarFinished()
696
697        self.heatmapconstructor = []
698        self._group_data = []
699       
700        for name, group_domain in groups:
701            if attr_ordering != sorted(attr_ordering):
702                domain = orange.Domain([group_domain[i] for i in attr_ordering], group_domain.classVar)
703                domain.addmetas(group_domain.getmetas())
704                group_domain = domain
705               
706            group_data = orange.ExampleTable(group_domain, sorted_data)
707            self._group_data.append((group_data, group_domain)) # Crashes at accessing the heatmap.examples[0] without this
708            if self.SortExamples == 1:
709                hc = orangene.HeatmapConstructor(group_data)
710            else:
711                hc = orangene.HeatmapConstructor(group_data, None)
712           
713            self.heatmapconstructor.append(hc)
714           
715        self.attr_cluster = attr_cluster
716        self.data_clusters = data_clusters
717        self.sorted_data = sorted_data
718        self.group_domains = groups
719             
720    def create_heatmaps(self, constructors):
721        self.lowerBound = 1000
722        self.upperBound = -1000
723        squeeze = 1.0 / self.Merge
724        self.heatmaps = []
725        for hmc in constructors:
726            hm, lb, ub = hmc(squeeze)
727           
728            self.lowerBound = min(self.lowerBound, lb)
729            self.upperBound = max(self.upperBound, ub)
730               
731            self.heatmaps.append(hm)
732           
733        for cluster, heatmap in zip(self.data_clusters, self.heatmaps[0]):
734            if cluster is not None:
735                cluster._heatmap = heatmap
736           
737        self.sliderCutLow.setRange(self.lowerBound, 0, 0.1)
738        self.sliderCutHigh.setRange(1e-10, self.upperBound, 0.1)
739        self.CutLow = max(self.CutLow, self.lowerBound)
740        self.CutHigh = min(self.CutHigh, self.upperBound)
741        self.sliderCutLow.setValue(self.CutLow)
742        self.sliderCutHigh.setValue(self.CutHigh)
743           
744    def point_size_hint(self, height):
745        font = QFont(self.font())
746        font.setPointSize(height)
747        fix = 0
748        while QFontMetrics(font).lineSpacing() > height and height - fix > 1:
749            fix += 1
750            font.setPointSize(height - fix)
751        return height - fix
752   
753    def construct_heatmaps_scene(self, heatmaps, data, attr_cluster=None, data_clusters=None):
754        self.heatmap_scene.clear()
755        widget = GridWidget()
756        self.heatmap_scene.addItem(widget)
757        layout = QGraphicsGridLayout()
758        layout.setSpacing(self.SpaceX)
759        widget.setLayout(layout)
760       
761        classVar = data.domain.classVar
762        if classVar and isinstance(classVar, orange.EnumVariable):
763            classes = classVar.values
764        else:
765            classes = [None]
766       
767        if self.CutEnabled:
768            cut_low, cut_high = self.CutLow, self.CutHigh
769        else:
770            cut_low, cut_high = self.lowerBound, self.upperBound
771           
772        palette = self.getGammaCorrectedPalette() if self.Gamma !=0 else self.palette
773       
774        class_dendrograms = []
775        attr_dendrograms = []
776        heatmap_widgets = []
777        attr_annotation_widgets = []
778        gene_annotation_widgets = []
779        attr_annotation_widgets_top = []
780        attr_annotation_widgets_bottom = []
781       
782        # Dendrograms on the left side
783        if data_clusters and any(data_clusters):
784            for i, cluster in enumerate(data_clusters):
785                class_dendrogram = DendrogramWidget(cluster, parent=widget, orientation=Qt.Vertical)
786                class_dendrogram.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
787                left, top, right, bottom = class_dendrogram.layout().getContentsMargins()
788                class_dendrogram.layout().setContentsMargins(left, self.CellHeight / 2.0 / self.Merge, 0.0, self.CellHeight / 2.0 / self.Merge)
789                class_dendrogram.setMinimumWidth(100)
790                class_dendrogram.setMaximumWidth(100)
791               
792                layout.addItem(class_dendrogram, i*2 + 5, 0)
793                class_dendrograms.append(class_dendrogram)
794           
795        # Class labels   
796        for i, class_ in enumerate(classes):
797            if class_ is not None:
798                item = GtI(class_, widget)
799                item = GraphicsSimpleTextLayoutItem(item, parent=widget)
800                item.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
801                layout.addItem(item, i*2 + 4, 2)
802                layout.setRowSpacing(i*2 + 4, 2)
803                layout.setAlignment(item, Qt.AlignLeft | Qt.AlignVCenter)
804               
805        font = QFont()
806        font.setPointSize(self.point_size_hint(self.CellHeight))
807       
808        class_row_labels = [map(str, hm.exampleIndices) for hm in heatmaps[0]]
809        group_column_labels = [[a.name for a in hm[0].examples.domain.attributes] for hm in heatmaps]
810       
811        # Gene annotations on the right side
812        for i, labels in enumerate(class_row_labels):
813            list = GraphicsSimpleTextList(labels, parent=widget, orientation=Qt.Vertical)
814            list.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
815            list.setFont(font)
816            list.setContentsMargins(0.0, 0.0, 0.0, 0.0)
817            list.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
818           
819            layout.addItem(list, i*2 + 5, len(self.heatmaps) + 2)
820            layout.setAlignment(list, Qt.AlignLeft)
821            gene_annotation_widgets.append(list)
822           
823        font = QFont()
824        font.setPointSizeF(self.point_size_hint(self.CellWidth))
825       
826        if self.ShowAverageStripe:
827            stripe_offset = c_averageStripeWidth + 2
828        else:
829            stripe_offset = 0
830           
831        for column, (hm, labels, group) in enumerate(zip(heatmaps, group_column_labels, self.group_domains)):
832            column_heatmap_widgets = []
833           
834            # Top group label
835            if len(heatmaps) > 1:
836                item = GtI(group[0], widget)
837                item = GraphicsSimpleTextLayoutItem(item, parent=widget)
838                layout.addItem(item, 1, column + 2)
839                layout.setRowSpacing(1, 2)
840                layout.setRowMaximumHeight(1, item.geometry().height())
841                layout.setAlignment(item, Qt.AlignLeft | Qt.AlignVCenter)
842            else:
843                layout.setRowMaximumHeight(1, 0)
844                layout.setRowSpacing(1, 0)
845               
846            # Top dendrogram
847            if attr_cluster is not None:
848                attr_dendrogram = DendrogramWidget(attr_cluster, parent=widget, orientation=Qt.Horizontal)
849                attr_dendrogram.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
850                attr_dendrogram.translate(0.0, attr_dendrogram.size().height())
851                attr_dendrogram.scale(1.0, -1.0)
852               
853                left, top, right, bottom = attr_dendrogram.layout().getContentsMargins()
854                attr_dendrogram.layout().setContentsMargins(stripe_offset + self.CellWidth / 2.0, 0.0, self.CellWidth / 2.0, bottom)
855                attr_dendrogram.setMinimumHeight(100)
856                attr_dendrogram.setMaximumHeight(100)
857               
858                layout.addItem(attr_dendrogram, 2, column + 2)
859                layout.setRowMaximumHeight(2, 100)
860                attr_dendrograms.append(attr_dendrogram)
861           
862           
863            # Heatmap widget for each class
864            for i, (class_, chm) in enumerate(zip(classes, hm)): 
865                hm_widget = GraphicsHeatmapWidget(heatmap=chm, parent=widget)
866                hm_widget.set_cell_size(int(self.CellWidth), int(self.CellHeight))
867                hm_widget.set_cuts(cut_low, cut_high)
868                hm_widget.set_color_table(palette)
869                hm_widget.set_show_averages(self.ShowAverageStripe)
870                hm_widget.cell_tool_tip = lambda row, col, hm=hm_widget: self.cell_tool_tip(hm, row, col)
871                layout.addItem(hm_widget, i*2 + 5, column + 2)
872                column_heatmap_widgets.append(hm_widget)
873            heatmap_widgets.append(column_heatmap_widgets)
874           
875            # Top attr annotations
876            list = GraphicsSimpleTextList(labels, parent=widget, orientation=Qt.Horizontal)
877            list.setAlignment(Qt.AlignBottom | Qt.AlignLeft)
878           
879            list.setFont(font)
880            list.layout().setContentsMargins(stripe_offset, 0, 0, 0)
881            list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
882           
883            layout.addItem(list, 3, column + 2, Qt.AlignBottom | Qt.AlignLeft)
884            attr_annotation_widgets.append(list)
885            attr_annotation_widgets_top.append(list)
886           
887            # Bottom attr annotations
888            list = GraphicsSimpleTextList(labels, parent=widget, orientation=Qt.Horizontal)
889            list.setAlignment(Qt.AlignTop | Qt.AlignHCenter)
890           
891            list.setFont(font)
892            list.layout().setContentsMargins(stripe_offset, 0, 0, 0)
893            list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
894           
895            layout.addItem(list, len(hm)*2 + 5, column + 2)
896            attr_annotation_widgets.append(list)
897            attr_annotation_widgets_bottom.append(list)
898           
899            # Legend
900            if column == 0:
901                item = GraphicsLegendWidget(self.heatmapconstructor[0], self.lowerBound, self.upperBound, parent=widget)
902                item.set_color_table(palette)
903                item.setVisible(self.ShowLegend)
904                layout.addItem(item, 0, 2, 1, len(self.heatmaps) + 1)
905                layout.setRowSpacing(0, 2)
906#                layout.setRowMaximumHeight(0, item.geometry().height())
907           
908        self.heatmap_scene.addItem(widget)
909        self.heatmap_scene.widget = widget
910        self.heatmap_widget_grid = heatmap_widgets
911        self.gene_annotation_widgets = gene_annotation_widgets
912        self.attr_annotation_widgets = attr_annotation_widgets
913        self.attr_annotation_widgets_top = attr_annotation_widgets_top
914        self.attr_annotation_widgets_bottom = attr_annotation_widgets_bottom
915        self.attr_dendrogram_widgets = attr_dendrograms
916       
917        self.update_annotations()
918        self.update_column_annotations()
919       
920        self.fix_grid_layout()
921       
922        self.selection_manager.set_heatmap_widgets(heatmap_widgets)
923       
924    def fix_grid_layout(self):
925        """ Fix grid layout when cell size changes or average
926        stripes are shown/hiddens.
927        """
928        if self.heatmap_scene.widget:
929            layout = self.heatmap_scene.widget.layout()
930            layout.invalidate()
931            layout.activate()
932           
933            for i, hw in enumerate(self.heatmap_widget_grid[0]):
934                max_h = hw.size().height()
935                layout.setRowMaximumHeight(i*2 + 5, max_h)
936               
937            for i, hw in enumerate(self.heatmap_widget_grid):
938                max_w = hw[0].size().width()
939                layout.setColumnMaximumWidth(i + 2, max_w)
940                if self.attr_dendrogram_widgets:
941                    dendrogram = self.attr_dendrogram_widgets[i]
942#                    dendrogram.resize(max_w, -1)
943                    dendrogram.setMaximumWidth(max_w)
944                self.attr_annotation_widgets_top[i].setMaximumWidth(max_w)
945                self.attr_annotation_widgets_bottom[i].setMaximumWidth(max_w)
946               
947#            for i, (hw, dend) in enumerate(zip(self.heatmap_widget_grid, self.attr_dendrogram_widgets)):
948#                max_w = hw[0].size().width()
949           
950#            self.update_widget_margins()   
951            self.heatmap_scene.widget.resize(self.heatmap_scene.widget.sizeHint(Qt.PreferredSize))
952           
953            self.on_selection_changed()
954            self.update_scene_rect()
955       
956    def update_scene_rect(self):
957        rect = QRectF()
958        for item in self.heatmap_scene.items():
959            rect |= item.sceneBoundingRect()
960        self.heatmap_scene.setSceneRect(rect)
961           
962    def heatmap_widgets(self):
963        """ Iterate over heatmap widgets.
964        """
965        for item in self.heatmap_scene.items():
966            if isinstance(item, GraphicsHeatmapWidget):
967                yield item
968               
969    def label_widgets(self):
970        """ Iterate over GraphicsSimpleTextList widgets.
971        """
972        for item in self.heatmap_scene.items():
973            if isinstance(item, GraphicsSimpleTextList):
974                yield item
975               
976    def dendrogram_widgets(self):
977        """ Iterate over dendrogram widgets
978        """
979        for item in self.heatmap_scene.items():
980            if isinstance(item, DendrogramWidget):
981                yield item
982               
983    def legend_widgets(self):
984        for item in self.heatmap_scene.items():
985            if isinstance(item, GraphicsLegendWidget):
986                yield item
987               
988    def update_cell_size(self):
989        """ Update cell sizes (by user request - height/width sliders)
990        """ 
991        for heatmap in self.heatmap_widgets():
992            heatmap.set_cell_size(self.CellWidth, self.CellHeight)
993           
994        hor_font = QFont(self.font())
995        hor_font.setPointSize(self.point_size_hint(self.CellWidth))
996        vert_font = QFont(self.font())
997        vert_font.setPointSize(self.point_size_hint(self.CellHeight))
998       
999        # Also update the annotation items font.
1000        for labels in self.label_widgets():
1001            if labels.orientation == Qt.Vertical:
1002                labels.setFont(vert_font)
1003            else:
1004                labels.setFont(hor_font)
1005
1006        self.update_widget_margins()
1007        ## To hide the annotations if cell sizes to small.
1008        self.update_annotations()
1009        self.update_column_annotations()
1010           
1011        self.fix_grid_layout()
1012       
1013    def update_widget_margins(self):
1014        """ Update dendrogram and text list widgets margins to incude the
1015        space for average stripe.
1016        """
1017        if self.ShowAverageStripe:
1018            stripe_offset = c_averageStripeWidth + 2
1019        else:
1020            stripe_offset = 0
1021        right = self.CellWidth / 2.0
1022       
1023        top = self.CellHeight / 2.0 / self.Merge
1024        bottom = self.CellHeight / 2.0 / self.Merge
1025       
1026        for dendrogram in self.dendrogram_widgets():
1027            layout = dendrogram.layout() 
1028            if dendrogram.orientation == Qt.Horizontal:
1029#                index = self.attr_dendrogram_widgets.index(dendrogram)
1030#                heatmap = self.heatmap_widget_grid[index][0]
1031#                h_w = heatmap.size().width()
1032#                d_w = dendrogram.size().width()
1033#                right_ = d_w - stripe_offset - self.CellWidth - h_w
1034                _, top_h, _, bottom_h = layout.getContentsMargins()
1035                layout.setContentsMargins(stripe_offset + self.CellWidth / 2.0, top_h, right, bottom_h)
1036            else:
1037                left_v, _, right_v, _ = layout.getContentsMargins()
1038                layout.setContentsMargins(left_v, top, right_v, bottom)
1039               
1040        for widget in self.label_widgets():
1041            layout = widget.layout()
1042            if widget.orientation == Qt.Horizontal:
1043                left_h, top, right, bottom = layout.getContentsMargins()
1044                layout.setContentsMargins(stripe_offset, top, right, bottom)
1045       
1046    def update_averages_stripe(self):
1047        """ Update the visibility of the averages stripe.
1048        """
1049        if self.data:
1050            for widget in self.heatmap_widgets():
1051                widget.set_show_averages(self.ShowAverageStripe)
1052               
1053            self.update_widget_margins()
1054            self.fix_grid_layout()
1055           
1056    def update_grid_spacing(self):
1057        """ Update layout spacing.
1058        """
1059        if self.scene.widget:
1060            layout = self.scene.widget.layout()
1061            layout.setSpacing(self.SpaceX)
1062            self.fix_grid_layout()
1063       
1064    def update_color_schema(self):
1065        palette = self.getGammaCorrectedPalette() if self.Gamma !=0 else self.palette
1066        for heatmap in self.heatmap_widgets():
1067            heatmap.set_color_table(palette)
1068           
1069        for legend in self.legend_widgets():
1070            legend.set_color_table(palette)
1071           
1072    def update_thresholds(self):
1073        self.sliderCutLow.box.setDisabled(not self.CutEnabled)
1074        self.sliderCutHigh.box.setDisabled(not self.CutEnabled)
1075           
1076        if self.data:
1077            if self.CutEnabled:
1078                low, high = self.CutLow, self.CutHigh
1079            else:
1080                low, high = self.lowerBound, self.upperBound
1081            for heatmap in self.heatmap_widgets():
1082                heatmap.set_cuts(low, high)
1083   
1084    def update_sorting(self):
1085        if self.data:
1086            self.update_heatmaps()
1087       
1088    def update_sorting_examples(self):
1089        if self.data:
1090            self.update_heatmaps()
1091
1092    def update_sorting_attributes(self):
1093        if self.data:
1094            self.update_heatmaps()
1095
1096    def update_legend(self):
1097        for item in self.heatmap_scene.items():
1098            if isinstance(item, GraphicsLegendWidget):
1099                item.setVisible(self.ShowLegend)
1100       
1101    def update_annotations(self):
1102        if self.data:
1103            if self.meta:
1104                attr = self.meta[self.BAnnotationIndx]
1105            else:
1106                attr = None
1107           
1108            show = self.ShowGeneAnnotations and attr and self.Merge == 1
1109            show = show and self.CellHeight > 3
1110            for list_widget, hm in zip(self.gene_annotation_widgets, self.heatmap_widget_grid[0]):
1111                list_widget.setVisible(bool(show))
1112                if show:
1113                    hm = hm.heatmap
1114                    examples = hm.examples
1115                    indices = hm.exampleIndices[:-1]
1116                    labels = [str(examples[i][attr]) for i in indices]
1117                    list_widget.set_labels(labels)
1118
1119    def update_column_annotations(self):
1120        if self.data:
1121            show = self.CellWidth > 3
1122            show_top = self.ShowColumnLabels and self.ColumnLabelPosition == 0 and show
1123            show_bottom = self.ShowColumnLabels and self.ColumnLabelPosition == 1 and show
1124           
1125            for list_widget in self.attr_annotation_widgets_top:
1126                list_widget.setVisible(show_top)
1127               
1128            layout = self.heatmap_scene.widget.layout()
1129            layout.setRowMaximumHeight(3,  -1 if show_top else 0)
1130            layout.setRowSpacing(3, -1 if show_top else 0)
1131               
1132            for list_widget in self.attr_annotation_widgets_bottom:
1133                list_widget.setVisible(show_bottom)
1134               
1135            layout.setRowMaximumHeight(len(self.heatmap_widget_grid[0]) + 4, -1 if show_top else 0)
1136               
1137            self.fix_grid_layout()
1138           
1139    def update_heatmaps(self):
1140        if self.data:
1141            self.construct_heatmaps(self.data, self.selected_split_label())
1142            self.create_heatmaps(self.heatmapconstructor)
1143            self.clear_scene()
1144            self.construct_heatmaps_scene(self.heatmaps, self.data,
1145                                          attr_cluster=self.attr_cluster,
1146                                          data_clusters=self.data_clusters)
1147        else:
1148            self.clear()
1149       
1150    def update_heatmaps_stage2(self):
1151        if self.data:
1152            self.create_heatmaps(self.heatmapconstructor)
1153            self.clear_scene()
1154            self.construct_heatmaps_scene(self.heatmaps, self.data,
1155                                          attr_cluster=self.attr_cluster,
1156                                          data_clusters=self.data_clusters)
1157       
1158    def cell_tool_tip(self, heatmap_widget, row, column):
1159        if not self.GShowToolTip:
1160            return ""
1161        hm = heatmap_widget.heatmap
1162        examples = hm.examples[hm.exampleIndices[row] : hm.exampleIndices[row+1]]
1163        domain = hm.examples.domain
1164        if hm.getCellIntensity(row, column) != None:
1165            head = "%6.4f" % hm.getCellIntensity(row, column)
1166        else:
1167            head = "Missing Data"
1168        if self.BShowColumnID:
1169            head += "\n" + domain.attributes[column].name
1170        # tool tip, construct body
1171        body = ""
1172        if (self.BShowSpotIndex and self.BSpotVar) or \
1173                (self.BShowAnnotation and self.BAnnotationVar) or \
1174                 self.BShowGeneExpression:
1175            for (i, e) in enumerate(examples):
1176                if i > 5:
1177                    body += "\n... (%d more)" % (len(examples) - 5)
1178                    break
1179                else:
1180                    s = []
1181                    if self.BShowSpotIndex and self.BSpotVar:
1182                        s.append(str(e[self.BSpotVar]))
1183                    if self.BShowGeneExpression:
1184                        s.append(str(e[column]))
1185                    if self.BShowAnnotation and self.BAnnotationVar:
1186                        s.append(str(e[self.BAnnotationVar]))
1187           
1188                body += "\n"
1189                body += " | ".join(s)
1190        return head + body
1191   
1192    def on_merge_changed(self):
1193        self.oldMerge = self.savedMerge
1194        if self.MaintainArrayHeight and self.oldMerge != self.Merge:
1195            k = float(self.Merge) / self.oldMerge
1196            l = max(1, min(int(self.CellHeight * k), self.maxVSize))
1197            if l != self.CellHeight:
1198                self.CellHeight = l
1199                self.sliderVSize.setValue(self.CellHeight)
1200
1201        self.update_heatmaps_stage2()
1202        self.savedMerge = self.Merge
1203           
1204    def on_selection_changed(self):
1205        for item in self.selection_rects:
1206            item.hide()
1207            item.setParentItem(None)
1208            item.update()
1209            self.heatmap_scene.removeItem(item)
1210        self.selection_rects = []
1211        self.selection_manager.update_selection_rects()
1212        rects = self.selection_manager.selection_rects
1213        for rect in rects:
1214            item = QGraphicsRectItem(rect, None, self.heatmap_scene)
1215            item.setPen(QPen(Qt.black, 2))
1216            self.selection_rects.append(item)
1217           
1218    def on_selection_finished(self):
1219        self.selected_rows = self.selection_manager.selections
1220        self.commit_if()
1221       
1222    def commit_if(self):
1223        if self.auto_commit:
1224            self.commit()
1225        else:
1226            self.selection_changed_flag = True
1227       
1228    def commit(self):
1229        data = None
1230        if self.sorted_data:
1231            if self.selected_rows:
1232                examples = [self.sorted_data[i] for i in self.selected_rows]
1233                data = orange.ExampleTable(examples)
1234            else:
1235                data = None
1236       
1237        self.send("Examples", data)
1238        self.selection_changed_flag
1239           
1240    ## handle saved selections
1241    def settingsFromWidgetCallbackSelection(self, handler, context):
1242        context.selection = self.selection_manager.selections
1243
1244    def settingsToWidgetCallbackSelection(self, handler, context):
1245        selection = getattr(context, "selection", None)
1246        if selection:
1247            try:
1248                self.selection_manager.select_rows(selection)
1249            except Exception, ex:
1250                pass
1251#                self.warning(3, "Could not restore selection")
1252               
1253    def split_changed(self):
1254        if self.data:
1255            self.clear_scene()
1256            self.construct_heatmaps(self.data, self.selected_split_label())
1257            self.create_heatmaps(self.heatmapconstructor)
1258            self.construct_heatmaps_scene(self.heatmaps, self.data,
1259                                          attr_cluster=self.attr_cluster,
1260                                          data_clusters=self.data_clusters)
1261       
1262
1263class GraphicsPixmapLayoutItem(QGraphicsLayoutItem):
1264    """ A layout item wraping a QGraphicsPixmapItem
1265    """
1266    def __init__(self, pixmap_item, parent=None):
1267        QGraphicsLayoutItem.__init__(self, parent)
1268        self.pixmap_item = pixmap_item
1269        self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
1270       
1271    def setGeometry(self, rect):
1272        QGraphicsLayoutItem.setGeometry(self, rect)
1273        self.pixmap_item.setPos(rect.topLeft())
1274       
1275    def sizeHint(self, which, constraint=QSizeF()):
1276        return QSizeF(self.pixmap_item.pixmap().size())
1277   
1278    def setPixmap(self, pixmap):
1279        self.pixmap_item.setPixmap(pixmap)
1280        self.updateGeometry()
1281       
1282       
1283class GraphicsHeatmapWidget(QGraphicsWidget):
1284    def __init__(self, heatmap=None, parent=None, scene=None):
1285        QGraphicsWidget.__init__(self, parent)
1286        self.setAcceptHoverEvents(True)
1287        layout = QGraphicsLinearLayout(Qt.Horizontal)
1288        layout.setContentsMargins(0, 0, 0, 0)
1289        item = QGraphicsPixmapItem(self)
1290        item.setShapeMode(QGraphicsPixmapItem.BoundingRectShape)
1291        self.heatmap_item = GraphicsPixmapLayoutItem(item, self)
1292       
1293        item = QGraphicsPixmapItem(self)
1294        item.setShapeMode(QGraphicsPixmapItem.BoundingRectShape)
1295        self.averages_item = GraphicsPixmapLayoutItem(item, self)
1296       
1297        layout.addItem(self.averages_item)
1298        layout.addItem(self.heatmap_item)
1299        layout.setItemSpacing(0, 2)
1300       
1301        self.setLayout(layout)
1302       
1303        self.heatmap = None
1304        self.show_averages = True
1305        self._pixmap_args = None
1306        self.color_table = None
1307        self.selection_manager = None
1308        self.set_cell_size(4, 4)
1309        self.set_cuts(0, 255)
1310        self.set_heatmap(heatmap)
1311       
1312        if scene is not None:
1313            scene.addItem(self)
1314           
1315    def clear(self):
1316        """ Clear the current heatmap.
1317        """
1318        self.heatmap = None
1319        self._pixmap_args = None
1320        self.heatmap_item.setPixmap(QPixmap())
1321        self.averages_item.setPixmap(QPixmap())
1322        self.show_averages = True
1323        self.layout().invalidate()
1324           
1325    def set_heatmap(self, heatmap):
1326        """ Set the heatmap for display.
1327        """
1328        self.clear()
1329        self.heatmap = heatmap
1330        self.update()
1331       
1332    def set_cell_size(self, width, height):
1333        self.cell_width = width
1334        self.cell_height = height
1335        self.update()
1336       
1337    def set_cuts(self, low, high):
1338        self.cut_low = low
1339        self.cut_high = high
1340        self.update()
1341       
1342    def set_show_averages(self, show):
1343        self.show_averages = show
1344        self._pixmap_args = None
1345        self.update()
1346
1347    def set_color_table(self, color_table):
1348        if qVersion() <= "4.5":
1349            self.color_table = signedPalette(color_table)
1350        else:
1351            self.color_table = color_table
1352       
1353        self._pixmap_args = None
1354        self.update()
1355           
1356    def _update_pixmap(self):
1357        """ Update the pixmap if its construction arguments changed.
1358        """
1359        if self.heatmap:
1360            args = (int(self.cell_width), int(self.cell_height),
1361                    self.cut_low, self.cut_high, 1.0)
1362           
1363            if args != self._pixmap_args:
1364                bitmap, width, height = self.heatmap.getBitmap(*args)
1365                image = QImage(bitmap, width, height, QImage.Format_Indexed8)
1366                color_table = self.color_table
1367                if color_table:
1368                    image.setColorTable(color_table)
1369                self.pixmap = QPixmap.fromImage(image)
1370               
1371                bitmap, width, height = self.heatmap.getAverages(*((c_averageStripeWidth,) + args[1:]))
1372                image = QImage(bitmap, width, height, QImage.Format_Indexed8)
1373                if self.color_table:
1374                    image.setColorTable(color_table)
1375                self.averages_pixmap = QPixmap.fromImage(image)
1376               
1377                self._pixmap_args = args
1378               
1379                self.layout().invalidate()
1380        else:
1381            self.averages_pixmap = None
1382            self.pixmap = None
1383           
1384        self.heatmap_item.setPixmap(self.pixmap or QPixmap())
1385        if self.show_averages and self.averages_pixmap:
1386            self.averages_item.setPixmap(self.averages_pixmap)
1387        else:
1388            self.averages_item.setPixmap(QPixmap())
1389           
1390    def update(self):
1391        self._update_pixmap()
1392        QGraphicsWidget.update(self)
1393       
1394    def set_selection_manager(self, manager):
1395        self.selection_manager = manager
1396       
1397    def cell_at(self, pos):
1398        """ Return the cell row, column from a point `pos` in local
1399        coordinates.
1400       
1401        """
1402        pos = self.mapToItem(self.heatmap_item.pixmap_item, pos)
1403        x, y = pos.x(), pos.y()
1404        def clamp(i, m):
1405            return int(min(max(i, 0), m))
1406        return (clamp(math.floor(y / self.cell_height), self.heatmap.height),
1407                clamp(math.floor(x / self.cell_width), self.heatmap.width))
1408   
1409    def cell_rect(self, row, column):
1410        """ Return a QRectF in local coordinates containing the cell
1411        at `row` and `column`.
1412       
1413        """
1414        top = QPointF(column * self.cell_width, row * self.cell_height)
1415        top = self.mapFromItem(self.heatmap_item.pixmap_item, top)
1416        size = QSizeF(self.cell_width, self.cell_height)
1417        return QRectF(top, size)
1418
1419    def row_rect(self, row):
1420        """ Return a QRectF in local coordinates containing the entire row.
1421        """
1422        rect = self.cell_rect(row, 0).united(self.cell_rect(row, self.heatmap.width - 1))
1423        rect.setLeft(0) # To include the average stripe if show.
1424        return rect
1425   
1426    def cell_tool_tip(self, row, column):
1427        hm = self.heatmap
1428        start = int(hm.exampleIndices[row])
1429        end = int(hm.exampleIndices[row + 1])
1430        examples = [hm.examples[start]]
1431        attr = hm.examples.domain[column]
1432        val = "%i, %i: %f" % (row, column, float(examples[0][attr]))
1433        return val
1434   
1435    def hoverEnterEvent(self, event):
1436        row, col = self.cell_at(event.pos())
1437   
1438    def hoverMoveEvent(self, event):
1439        pos = event.pos()
1440        row, column = self.cell_at(pos)
1441        tooltip = self.cell_tool_tip(row, column)
1442        QToolTip.showText(event.screenPos(), tooltip)
1443        return QGraphicsWidget.hoverMoveEvent(self, event)
1444   
1445    def hoverLeaveEvent(self, event):
1446        row, col = self.cell_at(event.pos())
1447   
1448    if DEBUG:
1449        def paint(self, painter, option, widget=0):
1450            rect =  self.geometry()
1451            rect.translate(-self.pos())
1452            painter.drawRect(rect.adjusted(-1, -1, 1, 1))
1453   
1454   
1455class GridWidget(QGraphicsWidget):
1456    def __init__(self, parent=None):
1457        QGraphicsWidget.__init__(self, parent)
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)
1464           
1465           
1466class HeatmapScene(QGraphicsScene):
1467    """ A Graphics Scene with heatmap widgets.
1468    """
1469    def __init__(self, parent=None):
1470        QGraphicsScene.__init__(self, parent)
1471        self.selection_manager = HeatmapSelectionManager()
1472       
1473    def set_selection_manager(self, manager):
1474        self.selection_manager = manager
1475       
1476    def _items(self, pos=None, cls=object):
1477        if pos is not None:
1478            items = self.items(QRectF(pos, QSizeF(3, 3)).translated(-1.5, -1.5))
1479        else:
1480            items = self.items()
1481           
1482        for item in items:
1483            if isinstance(item, cls):
1484                yield item
1485           
1486    def heatmap_at_pos(self, pos):
1487        items  = list(self._items(pos, GraphicsHeatmapWidget))
1488        if items:
1489            return items[0]
1490        else:
1491            return None
1492       
1493    def dendrogram_at_pos(self, pos):
1494        items  = list(self._items(pos, DendrogramItem))
1495        if items:
1496            return items[0]
1497        else:
1498            return None
1499       
1500    def heatmap_widgets(self):
1501        return self._items(None, GraphicsHeatmapWidget)
1502       
1503    def select_from_dendrogram(self, dendrogram, clear=True):
1504        """ Select all heatmap rows which belong to the dendrogram.
1505        """
1506        dendrogram_widget = dendrogram.parentWidget()
1507        anchors = list(dendrogram_widget.leaf_anchors())
1508        cluster = dendrogram.cluster
1509        start, end = anchors[cluster.first], anchors[cluster.last - 1]
1510        start, end = dendrogram_widget.mapToScene(start), dendrogram_widget.mapToScene(end)
1511        # Find a heatmap widget containing start and end y coordinates.
1512       
1513        heatmap = None
1514        for hm in self.heatmap_widgets():
1515            b_rect = hm.sceneBoundingRect()
1516            if b_rect.contains(QPointF(b_rect.center().x(), start.y())):
1517                heatmap = hm
1518                break
1519           
1520        if dendrogram:
1521            b_rect = hm.boundingRect()
1522            start, end = hm.mapFromScene(start), hm.mapFromScene(end)
1523            start, _ = hm.cell_at(QPointF(b_rect.center().x(), start.y()))
1524            end, _ = hm.cell_at(QPointF(b_rect.center().x(), end.y()))
1525            self.selection_manager.selection_add(start, end, hm, clear=clear)
1526        return
1527       
1528    def mousePressEvent(self, event):
1529        pos = event.scenePos()
1530        heatmap = self.heatmap_at_pos(pos)
1531        if heatmap and event.button() & Qt.LeftButton:
1532            row, _ = heatmap.cell_at(heatmap.mapFromScene(pos))
1533            self.selection_manager.selection_start(heatmap, event)
1534           
1535        dendrogram = self.dendrogram_at_pos(pos)
1536        if dendrogram and event.button() & Qt.LeftButton:
1537            if dendrogram.orientation == Qt.Vertical:
1538                self.select_from_dendrogram(dendrogram, clear=not event.modifiers() & Qt.ControlModifier)
1539            return 
1540       
1541        return QGraphicsScene.mousePressEvent(self, event)
1542   
1543    def mouseMoveEvent(self, event):
1544        pos = event.scenePos()
1545        heatmap = self.heatmap_at_pos(pos)
1546        if heatmap and event.buttons() & Qt.LeftButton:
1547            row, _ = heatmap.cell_at(heatmap.mapFromScene(pos))
1548            self.selection_manager.selection_update(heatmap, event)
1549           
1550        dendrogram = self.dendrogram_at_pos(pos)
1551        if dendrogram and dendrogram.orientation == Qt.Horizontal: # Filter mouse move events
1552            return
1553           
1554        return QGraphicsScene.mouseMoveEvent(self, event)
1555   
1556    def mouseReleaseEvent(self, event):
1557        pos = event.scenePos()
1558        heatmap = self.heatmap_at_pos(pos)
1559        if heatmap:
1560            row, _ = heatmap.cell_at(heatmap.mapFromScene(pos))
1561            self.selection_manager.selection_finish(heatmap, event)
1562       
1563        dendrogram = self.dendrogram_at_pos(pos)
1564        if dendrogram and dendrogram.orientation == Qt.Horizontal: # Filter mouse events
1565            return
1566       
1567        return QGraphicsScene.mouseReleaseEvent(self, event)
1568   
1569    def mouseDoubleClickEvent(self, event):
1570        pos = event.scenePos()
1571        dendrogram = self.dendrogram_at_pos(pos)
1572        if dendrogram: # Filter mouse events
1573            return
1574        return QGraphicsScene.mouseDoubleClickEvent(self, event)
1575       
1576       
1577class GtI(QGraphicsSimpleTextItem):
1578    if DEBUG:
1579        def paint(self, painter, option, widget =0):
1580            QGraphicsSimpleTextItem.paint(self, painter, option, widget)
1581            painter.drawRect(self.boundingRect())
1582           
1583   
1584class GraphicsSimpleTextLayoutItem(QGraphicsLayoutItem):
1585    """ A Graphics layout item wrapping a QGraphicsSimpleTextItem alowing it
1586    to be managed by a layout.
1587    """
1588    def __init__(self, text_item, orientation=Qt.Horizontal, parent=None):
1589        QGraphicsLayoutItem.__init__(self, parent)
1590        self.orientation = orientation
1591        self.text_item = text_item
1592        if orientation == Qt.Vertical:
1593            self.text_item.rotate(-90)
1594            self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
1595        else:
1596            self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Expanding)
1597       
1598    def setGeometry(self, rect):
1599        QGraphicsLayoutItem.setGeometry(self, rect)
1600        if self.orientation == Qt.Horizontal:
1601            self.text_item.setPos(rect.topLeft())
1602        else:
1603            self.text_item.setPos(rect.bottomLeft())
1604       
1605    def sizeHint(self, which, constraint=QSizeF()):
1606        if which in [Qt.PreferredSize]:
1607            size = self.text_item.boundingRect().size()
1608            if self.orientation == Qt.Horizontal:
1609                return size
1610            else:
1611                return QSizeF(size.height(), size.width())
1612        else:
1613            return QSizeF()
1614   
1615    def setFont(self, font):
1616        self.text_item.setFont(font)
1617        self.updateGeometry()
1618       
1619    def setText(self, text):
1620        self.text_item.setText(text)
1621        self.updateGeometry()
1622       
1623       
1624class GraphicsSimpleTextList(QGraphicsWidget):
1625    """ A simple text list widget.
1626    """
1627    def __init__(self, labels=[], orientation=Qt.Vertical, parent=None, scene=None):
1628        QGraphicsWidget.__init__(self, parent)
1629        layout = QGraphicsLinearLayout(orientation)
1630        layout.setContentsMargins(0, 0, 0, 0)
1631        layout.setSpacing(0)
1632        self.setLayout(layout)
1633        self.orientation = orientation
1634        self.alignment = Qt.AlignCenter
1635        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
1636        self.set_labels(labels)
1637       
1638        if scene is not None:
1639            scene.addItem(self)
1640       
1641    def clear(self):
1642        """ Remove all text items.
1643        """
1644        layout = self.layout()
1645        for i in reversed(range(layout.count())):
1646            item = layout.itemAt(i)
1647            item.text_item.setParentItem(None)
1648            if self.scene():
1649                self.scene().removeItem(item.text_item)
1650            layout.removeAt(i)
1651       
1652        self.label_items = []
1653        self.updateGeometry()
1654       
1655    def set_labels(self, labels):
1656        """ Set the text labels to show in the widget.
1657        """
1658        self.clear()
1659        orientation = Qt.Horizontal if self.orientation == Qt.Vertical else Qt.Vertical
1660        for text in labels:
1661#            item = QGraphicsSimpleTextItem(text, self)
1662            item = GtI(text, self)
1663            item.setFont(self.font())
1664            item.setToolTip(text)
1665            item = GraphicsSimpleTextLayoutItem(item, orientation, parent=self)
1666            self.layout().addItem(item)
1667            self.layout().setAlignment(item, self.alignment)
1668            self.label_items.append(item)
1669           
1670        self.layout().activate()
1671        self.updateGeometry()
1672   
1673    def setAlignment(self, alignment):
1674        """ Set alignment of text items in the widget
1675        """
1676        self.alignment = alignment
1677        layout = self.layout()
1678        for i in range(layout.count()):
1679            layout.setAlignment(layout.itemAt(i), alignment)
1680           
1681    def setVisible(self, bool):
1682        QGraphicsWidget.setVisible(self, bool)
1683        self.updateGeometry()
1684           
1685    def setFont(self, font):
1686        """ Set the font for the text.
1687        """
1688        QGraphicsWidget.setFont(self, font)
1689        for item in self.label_items:
1690            item.setFont(font)
1691        self.layout().invalidate()
1692        self.updateGeometry()
1693       
1694    def sizeHint(self, which, constraint=QRectF()):
1695        if not self.isVisible():
1696            return QSizeF(0, 0)
1697        else:
1698            return QGraphicsWidget.sizeHint(self, which, constraint)
1699           
1700    if DEBUG:
1701        def paint(self, painter, options, widget=0):
1702            rect =  self.geometry()
1703            rect.translate(-self.pos())
1704            painter.drawRect(rect)
1705       
1706class GraphicsLegendWidget(QGraphicsWidget):
1707    def __init__(self, heatmap_constructor, low, high, parent=None, scene=None):
1708        QGraphicsWidget.__init__(self, parent)
1709        layout = QGraphicsLinearLayout(Qt.Vertical)
1710        self.setLayout(layout)
1711        layout.setContentsMargins(0, 0, 0, 0)
1712        layout.setSpacing(1)
1713       
1714        layout_labels = QGraphicsLinearLayout(Qt.Horizontal)
1715        layout.addItem(layout_labels)
1716        layout_labels.setContentsMargins(0, 0, 0, 0)
1717        label_lo = GtI("%.2f" % low, self)
1718        label_hi = GtI("%.2f" % high, self)
1719        self.item_low = GraphicsSimpleTextLayoutItem(label_lo, parent=self)
1720        self.item_high = GraphicsSimpleTextLayoutItem(label_hi, parent=self)
1721       
1722        layout_labels.addItem(self.item_low)
1723        layout.addStretch()
1724        layout_labels.addItem(self.item_high)
1725       
1726        self._pixmap = QPixmap(c_legendHeight, c_legendHeight)
1727        self.pixmap_item = QGraphicsPixmapItem(self._pixmap, self)
1728        self.pixmap_item = GraphicsPixmapLayoutItem(self.pixmap_item, parent=self)
1729        layout.addItem(self.pixmap_item)
1730       
1731        self.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
1732        self.set_legend(heatmap_constructor, low, high)
1733       
1734        if scene is not None:
1735            scene.addItem(self)
1736       
1737    def set_legend(self, heatmap_constructor, low, high):
1738        self.heatmap_constructor = heatmap_constructor
1739        self.low = low
1740        self.high = high
1741        self.color_table = None
1742        self._pixmap = None
1743        self._pixmap_args = None
1744        self.update()
1745        self.updateGeometry()
1746       
1747    def set_color_table(self, color_table):
1748        if qVersion() <= "4.5":
1749            self.color_table = signedPalette(color_table)
1750        else:
1751            self.color_table = color_table
1752       
1753        self._pixmap_args = None
1754        self.update()
1755       
1756    def update(self):
1757        crect = self.contentsRect()
1758        width = crect.width()
1759        height = c_legendHeight
1760        if not self.pixmap_item or self._pixmap_args != (width, height):
1761            bitmap = self.heatmap_constructor.getLegend(int(width), int(height), 1.0)
1762            image = QImage(bitmap, width, height, QImage.Format_Indexed8)
1763            color_table = self.color_table
1764            if color_table:
1765                image.setColorTable(color_table)
1766            self._pixmap = QPixmap.fromImage(image)
1767           
1768        self.pixmap_item.setPixmap(self._pixmap)
1769        self.item_low.setText("%.2f" % self.low)
1770        self.item_high.setText("%.2f" % self.high)
1771        self.layout().activate()
1772        QGraphicsWidget.update(self)
1773           
1774    if DEBUG:
1775        def paint(self, painter, options, widget=0):
1776            rect =  self.geometry()
1777            rect.translate(-self.pos())
1778            painter.drawRect(rect)
1779
1780       
1781class HeatmapSelectionManager(QObject):
1782    """ Selection manager for heatmap rows
1783    """
1784    def __init__(self, parent=None):
1785        QObject.__init__(self, parent)
1786        self.selections = []
1787        self.selection_ranges = []
1788        self.heatmap_widgets = []
1789        self.selection_rects = []
1790        self.heatmaps = []
1791        self._heatmap_ranges = {}
1792        self._start_row = 0
1793       
1794    def set_heatmap_widgets(self, widgets):
1795        self.remove_rows(self.selections)
1796        self.heatmaps = widgets
1797       
1798        # Compute row ranges for all heatmaps
1799        self._heatmap_ranges = {}
1800        for group in widgets:
1801            start = end = 0
1802            for heatmap in group:
1803                end += heatmap.heatmap.height
1804                self._heatmap_ranges[heatmap] = (start, end)
1805                start = end
1806       
1807    def select_rows(self, rows, heatmap=None, clear=True):
1808        """ Add `rows` to selection. If `heatmap` is provided the rows
1809        are mapped from the local indices to global heatmap indics. If `clear`
1810        then remove previous rows.
1811        """
1812        if heatmap is not None:
1813            start, end = self._heatmap_ranges[heatmap]
1814            rows = [start + r for r in rows]
1815           
1816        old_selection = list(self.selections)
1817        if clear:
1818            self.selections = rows
1819        else:
1820            self.selections = sorted(set(self.selections + rows))
1821        if self.selections != old_selection:
1822            self.update_selection_rects()
1823            self.emit(SIGNAL("selection_changed()"))
1824            self.emit(SIGNAL("selection_rects_changed"), self.selection_rects)
1825           
1826    def remove_rows(self, rows):
1827        """ Remove `rows` from the selection.
1828        """
1829        old_selection = list(self.selections)
1830        self.selections = sorted(set(self.selections) - set(rows))
1831        if old_selection != self.selections:
1832            self.update_selection_rects()
1833            self.emit(SIGNAL("selection_changed()"))
1834            self.emit(SIGNAL("selection_rects_changed"), self.selection_rects)
1835           
1836    def combined_ranges(self, ranges):
1837        combined_ranges = set()
1838        for start, end in ranges:
1839            if start <= end:
1840                rng = range(start, end + 1)
1841            else:
1842                rng = range(start, end - 1, -1)
1843            combined_ranges.update(rng)
1844        return sorted(combined_ranges)
1845       
1846    def selection_start(self, heatmap_widget, event):
1847        """ Selection  started by `heatmap_widget` due to `event`.
1848        """
1849        pos = heatmap_widget.mapFromScene(event.scenePos())
1850        row, column = heatmap_widget.cell_at(pos)
1851        start, _ = self._heatmap_ranges[heatmap_widget]
1852        row = start + row
1853        self._start_row = row
1854        range = (row, row)
1855        if event.modifiers() & Qt.ControlModifier:
1856            self.selection_ranges.append(range)
1857        else:
1858            self.selection_ranges = [range]
1859        self.select_rows(self.combined_ranges(self.selection_ranges))
1860       
1861    def selection_update(self, heatmap_widget, event):
1862        """ Selection updated by `heatmap_widget due to `event` (mouse drag).
1863        """
1864        pos = heatmap_widget.mapFromScene(event.scenePos())
1865        row, column = heatmap_widget.cell_at(pos)
1866        start, _ = self._heatmap_ranges[heatmap_widget]
1867        row = start + row
1868        if self.selection_ranges:
1869            self.selection_ranges[-1] = (self._start_row, row)
1870        else:
1871            self.selection_ranges = [(row, row)]
1872           
1873        self.select_rows(self.combined_ranges(self.selection_ranges))
1874       
1875    def selection_finish(self, heatmap_widget, event):
1876        """ Selection finished by `heatmap_widget due to `event`.
1877        """
1878        pos = heatmap_widget.mapFromScene(event.scenePos())
1879        row, column = heatmap_widget.cell_at(pos)
1880        start, _ = self._heatmap_ranges[heatmap_widget]
1881        row = start + row
1882        range = (self._start_row, row)
1883        self.selection_ranges[-1] = range
1884        self.select_rows(self.combined_ranges(self.selection_ranges),
1885                         clear=not event.modifiers() & Qt.ControlModifier)
1886        self.emit(SIGNAL("selection_finished()"))
1887       
1888    def selection_add(self, start, end, heatmap=None, clear=True):
1889        """ Add a selection range from `start` to `end`.
1890        """ 
1891        if heatmap is not None:
1892            _start, _ = self._heatmap_ranges[heatmap]
1893            start = _start + start
1894            end = _start + end
1895       
1896        if clear:
1897            self.selection_ranges = []
1898        self.selection_ranges.append((start, end))
1899        self.select_rows(self.combined_ranges(self.selection_ranges))
1900        self.emit(SIGNAL("selection_finished()"))
1901       
1902    def update_selection_rects(self):
1903        """ Update the selection rects.
1904        """
1905        def continuous_ranges(selections):
1906            """ Group continuous ranges
1907            """
1908            selections = iter(selections)
1909            start = end = selections.next()
1910            try:
1911                while True:
1912                    new_end = selections.next()
1913                    if new_end > end + 1:
1914                        yield start, end
1915                        start = end = new_end
1916                    else:
1917                        end = new_end
1918            except StopIteration:
1919                yield start, end
1920               
1921        def group_selections(selections):
1922            """ Group selections along with heatmaps.
1923            """
1924            rows2hm = self.rows_to_heatmaps()
1925            selections = iter(selections)
1926            start = end = selections.next()
1927            end_heatmaps= rows2hm[end]
1928            try:
1929                while True:
1930                    new_end = selections.next()
1931                    new_end_heatmaps = rows2hm[new_end]
1932                    if new_end > end + 1 or new_end_heatmaps != end_heatmaps:
1933                        yield start, end, end_heatmaps
1934                        start = end = new_end
1935                        end_heatmaps = new_end_heatmaps
1936                    else:
1937                        end = new_end
1938                       
1939            except StopIteration:
1940                yield start, end, end_heatmaps
1941               
1942        def selection_rect(start, end, heatmaps):
1943            rect = QRectF()
1944            for heatmap in heatmaps:
1945                h_start, _ = self._heatmap_ranges[heatmap] 
1946                rect |= heatmap.mapToScene(heatmap.row_rect(start - h_start)).boundingRect()
1947                rect |= heatmap.mapToScene(heatmap.row_rect(end - h_start)).boundingRect()
1948            return rect
1949                 
1950        self.selection_rects = []
1951        for start, end, heatmaps in group_selections(self.selections):
1952            rect = selection_rect(start, end, heatmaps)
1953            self.selection_rects.append(rect)
1954           
1955    def rows_to_heatmaps(self):
1956        heatmap_groups = zip(*self.heatmaps)
1957        rows2hm = {}
1958        for heatmaps in heatmap_groups:
1959            hm = heatmaps[0]
1960            start, end = self._heatmap_ranges[hm]
1961            rows2hm.update(dict.fromkeys(range(start, end), heatmaps))
1962        return rows2hm
1963   
1964         
1965##################################################################################################
1966# test script
1967
1968if __name__=="__main__":
1969    a=QApplication(sys.argv)
1970    ow = OWHeatMap()
1971   
1972    ow.set_dataset(orange.ExampleTable("brown-selected"), 0)
1973    ow.handleNewSignals()
1974    ow.show()
1975   
1976    a.exec_()
1977    ow.saveSettings()
1978
Note: See TracBrowser for help on using the repository browser.