source: orange-bioinformatics/_bioinformatics/widgets/OWHeatMap.py @ 1840:66a7f9e0a719

Revision 1840:66a7f9e0a719, 79.8 KB checked in by Flashpoint <vid.flashpoint@…>, 8 months ago (diff)

Separated the row and column clustering in OWHeatMap.py

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