source: orange-bioinformatics/widgets/OWHeatMap.py @ 1409:4a01b57fe4aa

Revision 1409:4a01b57fe4aa, 75.5 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Caching the computed clustering.

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