source: orange-bioinformatics/_bioinformatics/widgets/OWHeatMap.py @ 1844:9d532651ea3b

Revision 1844:9d532651ea3b, 77.6 KB checked in by markotoplak, 8 months ago (diff)

OWHeatMap: swapped X,Y labels. Fixed progress bar.

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