source: orange-bioinformatics/orangecontrib/bio/widgets/OWHeatMap.py @ 1874:b3e32cc5cf6f

Revision 1874:b3e32cc5cf6f, 78.3 KB checked in by Ales Erjavec <ales.erjavec@…>, 6 months ago (diff)

Added new style widget meta descriptions.

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