source: orange/Orange/OrangeWidgets/Unsupervised/OWDistanceMap.py @ 9671:a7b056375472

Revision 9671:a7b056375472, 37.6 KB checked in by anze <anze.staric@…>, 2 years ago (diff)

Moved orange to Orange (part 2)

Line 
1"""
2<name>Distance Map</name>
3<description>Displays distance matrix as a heat map.</description>
4<icon>icons/DistanceMap.png</icon>
5<contact>Blaz Zupan (blaz.zupan(@at@)fri.uni-lj.si)</contact>
6<priority>1200</priority>
7"""
8from __future__ import with_statement
9import orange, math, sys
10import OWGUI, OWToolbars
11from OWWidget import *
12
13from ColorPalette import *
14from OWDlgs import OWChooseImageSizeDlg
15import OWColorPalette
16import OWToolbars
17#from OWHierarchicalClustering import recursion_limit
18
19#####################################################################
20# parameters that determine the canvas layout
21
22c_offsetX = 10; c_offsetY = 10  # top and left border
23c_spaceX = 10; c_spaceY = 10    # space btw graphical elements
24c_legendHeight = 15             # height of the legend
25c_averageStripeWidth = 12       # width of the stripe with averages
26c_smallcell = 8                 # below this threshold cells are
27                                # considered small and grid dissapears
28
29#####################################################################
30# canvas with events
31
32class EventfulGraphicsView(QGraphicsView):
33    def __init__(self, scene, parent, master):
34        QGraphicsView.__init__(self, scene, parent)
35        self.master = master
36#        self.setRenderHints(QPainter.Antialiasing)
37        self.viewport().setMouseTracking(True)
38        self.setFocusPolicy(Qt.WheelFocus)
39
40class EventfulGraphicsScene(QGraphicsScene):
41    def __init__(self, master):
42        QGraphicsScene.__init__(self)
43        self.master = master
44
45    def mousePressEvent (self, event):
46        if self.master.matrix:
47            self.master.mousePress(event.scenePos().x(), event.scenePos().y())
48
49    def mouseReleaseEvent (self, event):
50        if self.master.matrix:
51            self.master.mouseRelease(event.scenePos().x(), event.scenePos().y())
52
53    def mouseMoveEvent (self, event):
54        if self.master.matrix:
55            self.master.mouseMove(event)
56
57#####################################################################
58# main class
59v_sel_width = 2
60v_legend_width = 104
61v_legend_height = 18
62v_legend_offsetX = 5
63v_legend_offsetY = 15
64
65class OWDistanceMap(OWWidget):
66    settingsList = ["CellWidth", "CellHeight", "Merge", "Gamma", "CutLow",
67                    "CutHigh", "CutEnabled", "Sort", "SquareCells",
68                    "ShowLegend", "ShowLabels", "ShowBalloon",
69                    "Grid", "savedGrid",
70                    "ShowItemsInBalloon", "SendOnRelease", "colorSettings", "selectedSchemaIndex", "palette"]
71
72    def __init__(self, parent=None, signalManager = None):
73        OWWidget.__init__(self, parent, signalManager, 'Distance Map', wantGraph=True)
74
75        self.inputs = [("Distances", orange.SymMatrix, self.setMatrix)]
76        self.outputs = [("Data", ExampleTable), ("Features", orange.VarList)]
77
78        self.clicked = False
79        self.offsetX = 5
80        self.offsetY = 5
81        self.imageWidth = 0
82        self.imageHeight = 0
83        self.distanceImage = None
84        self.legendImage = None
85        self.colorSettings = None
86        self.selectedSchemaIndex = 0
87
88        self.palette = None       
89
90        self.shiftPressed = False
91
92        #set default settings
93        self.CellWidth = 15
94        self.CellHeight = 15
95        self.Merge = 1
96        self.savedMerge = self.Merge
97        self.Gamma = 1
98        self.Grid = 1
99        self.savedGrid = 1
100        self.CutLow = 0
101        self.CutHigh = 0
102        self.CutEnabled = 0
103        self.Sort = 0
104        self.SquareCells = 0
105        self.ShowLegend = 1
106        self.ShowLabels = 1
107        self.ShowBalloon = 1
108        self.ShowItemsInBalloon = 1
109        self.SendOnRelease = 1
110
111        self.loadSettings()
112
113        self.maxHSize = 30; self.maxVSize = 30
114        self.sorting = [("No sorting", self.sortNone),
115                        ("Adjacent distance", self.sortAdjDist),
116                        ("Random order", self.sortRandom),
117                        ("Clustering", self.sortClustering),
118                        ("Clustering with ordered leafs", self.sortClusteringOrdered)]
119
120        self.matrix = self.order = None
121        self.rootCluster = None
122
123        # GUI definition
124        self.tabs = OWGUI.tabWidget(self.controlArea)
125
126        # SETTINGS TAB
127        tab = OWGUI.createTabPage(self.tabs, "Settings")
128        box = OWGUI.widgetBox(tab, "Cell Size (Pixels)")
129        OWGUI.qwtHSlider(box, self, "CellWidth", label='Width: ',
130                         labelWidth=38, minValue=1, maxValue=self.maxHSize,
131                         step=1, precision=0,
132                         callback=[lambda f="CellWidth", t="CellHeight": self.adjustCellSize(f,t),
133                                   self.drawDistanceMap,
134                                   self.manageGrid])
135        OWGUI.qwtHSlider(box, self, "CellHeight", label='Height: ',
136                         labelWidth=38, minValue=1, maxValue=self.maxVSize,
137                         step=1, precision=0,
138                         callback=[lambda f="CellHeight", t="CellWidth": self.adjustCellSize(f,t),
139                                   self.drawDistanceMap,
140                                   self.manageGrid])
141        OWGUI.checkBox(box, self, "SquareCells", "Cells as squares",
142                         callback = [self.setSquares, self.drawDistanceMap])
143        self.gridChkBox = OWGUI.checkBox(box, self, "Grid", "Show grid",
144                                         callback = self.createDistanceMap,
145                                         disabled=lambda: min(self.CellWidth, self.CellHeight) <= c_smallcell)
146
147        OWGUI.separator(tab)
148        OWGUI.qwtHSlider(tab, self, "Merge", box="Merge" ,label='Elements:', labelWidth=50,
149                         minValue=1, maxValue=100, step=1,
150                         callback=self.createDistanceMap, ticks=0)
151       
152        OWGUI.separator(tab)
153        self.labelCombo = OWGUI.comboBox(tab, self, "Sort", box="Sort",
154                         items=[x[0] for x in self.sorting],
155                         tooltip="Sorting method for items in distance matrix.",
156                         callback=self.sortItems)
157        OWGUI.rubber(tab)
158
159        # FILTER TAB
160        tab = OWGUI.createTabPage(self.tabs, "Colors")
161        box = OWGUI.widgetBox(tab, "Color settings", addSpace=True)
162        OWGUI.widgetLabel(box, "Gamma")
163        OWGUI.qwtHSlider(box, self, "Gamma", minValue=0.1, maxValue=1,
164                         step=0.1, maxWidth=100, callback=self.drawDistanceMap)
165
166        OWGUI.separator(box)
167
168        OWGUI.checkBox(box, self, 'CutEnabled', "Enable thresholds", callback=self.setCutEnabled)
169        self.sliderCutLow = OWGUI.qwtHSlider(box, self, 'CutLow', label='Low:',
170                              labelWidth=33, minValue=-100, maxValue=0, step=0.1,
171                              precision=1, ticks=0, maxWidth=80,
172                              callback=self.drawDistanceMap)
173        self.sliderCutHigh = OWGUI.qwtHSlider(box, self, 'CutHigh', label='High:',
174                              labelWidth=33, minValue=0, maxValue=100, step=0.1,
175                              precision=1, ticks=0, maxWidth=80,
176                              callback=self.drawDistanceMap)
177        if not self.CutEnabled:
178            self.sliderCutLow.box.setDisabled(1)
179            self.sliderCutHigh.box.setDisabled(1)
180           
181        box = OWGUI.widgetBox(box, "Colors", orientation="horizontal")
182        self.colorCombo = OWColorPalette.PaletteSelectorComboBox(self)
183        try:
184            self.colorCombo.setPalettes("palette", self.createColorDialog())
185        except Exception, ex:
186            print >> sys.stderr, ex, "Error loading saved color palettes!\nCreating new default palette!"
187            self.colorSettings = None
188            self.colorCombo.setPalettes("palette", self.createColorDialog())
189        self.colorCombo.setCurrentIndex(self.selectedSchemaIndex)
190        self.connect(self.colorCombo, SIGNAL("activated(int)"), self.setColor)
191        box.layout().addWidget(self.colorCombo, 2)
192        OWGUI.button(box, self, "Edit colors", callback=self.openColorDialog, debuggingEnabled = 0)
193        OWGUI.rubber(tab)
194
195        self.setColor(self.selectedSchemaIndex)
196
197        # INFO TAB
198        tab = OWGUI.createTabPage(self.tabs, "Info")
199        box = OWGUI.widgetBox(tab, "Annotation && Legends")
200        OWGUI.checkBox(box, self, 'ShowLegend', 'Show legend',
201                       callback=self.drawDistanceMap)
202        OWGUI.checkBox(box, self, 'ShowLabels', 'Show labels',
203                       callback=self.drawDistanceMap)
204
205        box = OWGUI.widgetBox(tab, "Tool Tips")
206        OWGUI.checkBox(box, self, 'ShowBalloon', "Show tool tips")
207        OWGUI.checkBox(box, self, 'ShowItemsInBalloon', "Display item names")
208
209        box = OWGUI.widgetBox(tab, "Select")
210        box2 = OWGUI.widgetBox(box, orientation = "horizontal")
211        self.box2 = box2
212        self.buttonUndo = OWToolbars.createButton(box2, 'Undo', self.actionUndo,
213                              QIcon(OWToolbars.dlg_undo), toggle = 0)
214        self.buttonRemoveAllSelections = OWToolbars.createButton(box2,
215                              'Remove all selections', self.actionRemoveAllSelections,
216                              QIcon(OWToolbars.dlg_clear), toggle = 0)
217
218        self.buttonSendSelections = OWToolbars.createButton(box2, 'Send selections',
219                              self.sendOutput, QIcon(OWToolbars.dlg_send), toggle = 0)
220        OWGUI.checkBox(box, self, 'SendOnRelease', "Send after mouse release")
221        OWGUI.rubber(tab)
222
223        self.resize(700,400)
224
225        self.scene = EventfulGraphicsScene(self)
226        self.sceneView = EventfulGraphicsView(self.scene, self.mainArea, self)
227        self.mainArea.layout().addWidget(self.sceneView)
228
229        #construct selector
230        self.selector = QGraphicsRectItem(0, 0, self.CellWidth, self.CellHeight, None, self.scene)
231        color = self.cellOutlineColor
232        self.selector.setPen(QPen(self.qrgbToQColor(color),v_sel_width))
233        self.selector.setZValue(20)
234
235        self.selection = SelectionManager()
236
237        self.selectionLines = []
238        self.annotationText = []
239        self.clusterItems = []
240        self.selectionRects = []
241
242        self.legendText1 = QGraphicsSimpleTextItem(None, self.scene)
243        self.legendText2 = QGraphicsSimpleTextItem(None, self.scene)
244
245        self.errorText = QGraphicsSimpleTextItem("Bitmap is too large.", None, self.scene)
246        self.errorText.setPos(10,10)
247        self.errorText.hide()
248       
249        self.connect(self.graphButton, SIGNAL("clicked()"), lambda:OWChooseImageSizeDlg(self.scene, parent=self).exec_())
250
251        self._clustering_cache = {}
252
253    def sendReport(self):
254        if self.matrix:
255            self.reportSettings("Data",
256                                [("Matrix dimension", self.matrix.dim)])
257        self.reportSettings("Settings",
258                            [("Merge", "%i elements in a cell" % self.Merge if self.Merge > 1 else "none"),
259                             ("Sorting", self.sorting[self.Sort][0].lower()),
260                             ("Thresholds", "low %.1f, high %.1f" % (self.CutLow, self.CutHigh) if self.CutEnabled else "none"),
261                             ("Gamma", "%.2f" % self.Gamma)])
262        if self.matrix:
263            self.reportRaw("<br/>")
264            buffer = QPixmap(self.scene.width(), self.scene.height())
265            painter = QPainter(buffer)
266            painter.fillRect(buffer.rect(), QBrush(QColor(255, 255, 255)))
267            self.scene.render(painter)
268            painter.end()
269            self.reportImage(lambda filename: buffer.save(filename, os.path.splitext(filename)[1][1:]))
270       
271    def createColorStripe(self, palette, offsetX):
272        dx = v_legend_width
273        dy = v_legend_height
274        bmp = chr(252)*dx*2 + reduce(lambda x,y:x+y, [chr(i*250/dx) for i in range(dx)] * (dy-4)) + chr(252)*dx*2
275
276        image = ImageItem(bmp, self.scene, dx, dy, palette, x=offsetX, y=v_legend_offsetY, z=0)
277        return image
278
279    def colFromMousePos(self, x, y):
280        if (x <= self.offsetX or x >= self.offsetX + self.imageWidth):
281            return -1
282        else:
283            return int((x - self.offsetX)/self.CellWidth)
284
285    def rowFromMousePos(self, x,y):
286        if (y <= self.offsetY or y >= self.offsetY + self.imageHeight):
287            return -1
288        else:
289            return int((y - self.offsetY)/self.CellHeight)
290
291
292    def qrgbToQColor(self, color):
293        # we could also use QColor(positiveColor(rgb), 0xFFFFFFFF)
294        return QColor(qRed(positiveColor(color)), qGreen(positiveColor(color)), qBlue(positiveColor(color))) # if color cannot be negative number we convert it manually
295
296    def getItemFromPos(self, i):
297        if (len(self.distanceMap.elementIndices)==0):
298            j = i
299        else:
300            j = self.distanceMap.elementIndices[i]
301
302        if self.distanceMapConstructor.order:
303           j = self.distanceMapConstructor.order[j]
304
305        return j
306
307    def sendOutput(self):
308        if len(self.matrix.items)<1:
309            return
310
311        selectedIndices = []
312        tmp = []
313
314        if len(self.selection.getSelection())==0:
315            self.send("Features", None)
316            self.send("Data", None)
317        else:
318            selection = self.selection.getSelection()
319           
320            for sel in selection:
321                x1, y1 = sel.x(), sel.y()
322                x2, y2 = x1 + sel.width(), y1 + sel.height()
323                 
324                if (len(self.distanceMap.elementIndices)==0):
325                    tmp += range(x1, x2)
326                    tmp += range(y1, y2)
327                else:   
328                    tmp += range(self.distanceMap.elementIndices[x1], self.distanceMap.elementIndices[x2])
329                    tmp += range(self.distanceMap.elementIndices[y1], self.distanceMap.elementIndices[y2])
330
331            for i in tmp:
332                if self.distanceMapConstructor.order:
333                    i = self.distanceMapConstructor.order[i]
334
335                if not (i in selectedIndices):
336                    selectedIndices += [i]
337
338            items = self.matrix.items
339            if issubclass(orange.EnumVariable, type(items[0])):
340                selected = orange.VarList()
341                for i in selectedIndices:
342                    selected.append(items[i])
343                self.send("Features", selected)
344
345
346            if isinstance(items[0], orange.Example):
347                ex = [items[x] for x in selectedIndices]
348                selected = orange.ExampleTable(items[0].domain, ex)
349                self.send("Data", selected)
350
351    def getGammaCorrectedPalette(self):
352        return [QColor(*self.contPalette.getRGB(float(i)/250, gamma=self.Gamma)).rgb() for i in range(250)] + self.palette[-6:]
353
354    def setColor(self, index=0):
355        self.selectedSchemaIndex = index
356        dialog = self.createColorDialog()
357        self.colorCombo.setPalettes("palette", dialog)
358        self.colorCombo.setCurrentIndex(self.selectedSchemaIndex)
359       
360        self.contPalette = palette = dialog.getExtendedContinuousPalette("palette")
361        unknown = dialog.getColor("unknown").rgb()
362        underflow = dialog.getColor("underflow").rgb()
363        overflow = dialog.getColor("overflow").rgb()
364
365        background = dialog.getColor("background").rgb()
366        self.cellOutlineColor = dialog.getColor("cellOutline").rgb()
367        self.selectionColor = dialog.getColor("selection").rgb()
368##        color = self.colorPalette.getCurrentColorSchema().getAdditionalColors()["Cell outline"]
369##        self.selector.setPen(QPen(self.qrgbToQColor(color),v_sel_width))
370
371##        self.ColorSchemas = self.colorPalette.getColorSchemas()
372        self.palette = [palette[float(i)/252].rgb() for i in range(250)] + [background]*3 + [underflow, overflow, unknown]
373        self.drawDistanceMap()
374
375    def openColorDialog(self):
376        dialog = self.createColorDialog()
377        if dialog.exec_():
378            self.colorSettings = dialog.getColorSchemas()
379            self.selectedSchemaIndex = dialog.selectedSchemaIndex
380            self.colorCombo.setCurrentIndex(self.selectedSchemaIndex)
381            self.setColor(self.selectedSchemaIndex)
382
383    def createColorDialog(self):
384        c = OWColorPalette.ColorPaletteDlg(self, "Color Palette")
385        c.createExtendedContinuousPalette("palette", "Continuous Palette", initialColor1=QColor(Qt.blue), initialColor2=QColor(255, 255, 0).rgb())
386        box = c.createBox("otherColors", "Other Colors")
387       
388        c.createColorButton(box, "unknown", "Unknown", Qt.gray)
389        box.layout().addSpacing(5)
390        c.createColorButton(box, "overflow", "Overflow", Qt.black)
391        box.layout().addSpacing(5)
392        c.createColorButton(box, "underflow", "Underflow", Qt.white)
393
394        box = c.createBox("cellColors", "Cell colors")
395        c.createColorButton(box, "background", "Background", Qt.white)
396        box.layout().addSpacing(5)
397        c.createColorButton(box, "cellOutline", "Cell outline", Qt.gray)
398        box.layout().addSpacing(5)
399        c.createColorButton(box, "selection", "Selection", Qt.black)
400       
401        c.setColorSchemas(self.colorSettings, self.selectedSchemaIndex)
402        return c
403       
404    def setCutEnabled(self):
405        self.sliderCutLow.box.setDisabled(not self.CutEnabled)
406        self.sliderCutHigh.box.setDisabled(not self.CutEnabled)
407        self.drawDistanceMap()
408
409    def constructDistanceMap(self):
410        if self.matrix:
411            self.distanceMapConstructor = orange.DistanceMapConstructor(distanceMatrix = self.matrix)
412            self.sortItems()
413            self.createDistanceMap()
414
415    def createDistanceMap(self):
416        """creates distance map objects"""
417        if not self.matrix:
418            return
419        merge = min(self.Merge, float(self.matrix.dim))
420        squeeze = 1. / merge
421
422        self.distanceMapConstructor.order = self.order
423        self.distanceMap, self.lowerBound, self.upperBound = self.distanceMapConstructor(squeeze)
424
425        self.sliderCutLow.setRange(self.lowerBound, self.upperBound, 0.1)
426        self.sliderCutHigh.setRange(self.lowerBound, self.upperBound, 0.1)
427        self.CutLow = max(self.CutLow, self.lowerBound)
428        self.CutHigh = min(self.CutHigh, self.upperBound)
429        self.sliderCutLow.setValue(self.CutLow)
430        self.sliderCutHigh.setValue(self.CutHigh)
431
432        self.selection.clear()
433        self.drawDistanceMap()
434
435    def drawDistanceMap(self):
436        """renders distance map object on canvas"""
437       
438        if not self.matrix:
439            return
440       
441        self.clearScene()
442
443        if self.matrix.dim * max(int(self.CellWidth), int(self.CellHeight)) > 32767:
444            self.errorText.show()
445            return
446
447        self.errorText.hide()
448
449        lo = self.CutEnabled and self.CutLow or round(self.lowerBound, 1) + \
450            (-0.1 if round(self.lowerBound, 1) > self.lowerBound else 0)
451        hi = self.CutEnabled and self.CutHigh or round(self.upperBound, 1) + \
452            (0.1 if round(self.upperBound, 1) < self.upperBound else 0)
453
454        self.offsetX = 5
455
456        if self.distanceImage:
457            self.scene.removeItem(self.distanceImage)
458
459        if self.legendImage:
460            self.scene.removeItem(self.legendImage)
461
462        if self.ShowLegend==1:
463            self.offsetY = v_legend_height + 30
464        else:
465            self.offsetY = 5
466
467        palette = self.getGammaCorrectedPalette() #self.palette
468        bitmap, width, height = self.distanceMap.getBitmap(int(self.CellWidth),
469                            int(self.CellHeight), lo, hi, 1.0, self.Grid)
470##                            int(self.CellHeight), lo, hi, self.Gamma, self.Grid)
471
472        for tmpText in self.annotationText:
473            self.scene.removeItem(tmpText)
474
475        for cluster in self.clusterItems:
476            self.scene.removeItem(cluster)
477
478        if self.rootCluster and self.order:
479#            from OWClustering import HierarchicalClusterItem
480#            with recursion_limit(len(self.rootCluster) * 3 + 20 + sys.getrecursionlimit()): #extend the recursion limit
481#                clusterTop = HierarchicalClusterItem(self.rootCluster, None, None)
482#                clusterLeft = HierarchicalClusterItem(self.rootCluster, None, None)
483#                self.scene.addItem(clusterTop)
484#                self.scene.addItem(clusterLeft)
485            from OWClustering import DendrogramWidget
486            clusterTop = DendrogramWidget(self.rootCluster, orientation=Qt.Horizontal, scene=self.scene)
487            clusterLeft = DendrogramWidget(self.rootCluster, orientation=Qt.Horizontal, scene=self.scene)
488           
489            margin = self.CellWidth / 2.0 / self.Merge
490           
491            clusterLeft.layout().setContentsMargins(margin, 0, margin, 10)
492            clusterTop.layout().setContentsMargins(margin, 0, margin, 10)
493           
494            clusterHeight = 100.0
495#            clusterTop.resize(width, clusterHeight)
496            clusterTop.setMinimumSize(QSizeF(width, clusterHeight))
497            clusterTop.setMaximumSize(QSizeF(width, clusterHeight))
498            clusterTop.scale(1.0, -1.0)
499#            clusterTop.setSize(width, -clusterHeight)
500#            clusterLeft.setSize(height, clusterHeight)
501#            clusterLeft.resize(height, clusterHeight)
502            clusterLeft.setMinimumSize(QSizeF(height, clusterHeight))
503            clusterLeft.setMaximumSize(QSizeF(height, clusterHeight))
504           
505            clusterTop.setPos(0 + self.CellWidth / 2.0, self.offsetY + clusterHeight)
506
507
508            clusterLeft.rotate(90)
509            clusterLeft.setPos(self.offsetX + clusterHeight, 0 + self.CellHeight / 2.0)
510            self.offsetX += clusterHeight + 10
511            self.offsetY += clusterHeight + 10
512
513            self.clusterItems += [clusterTop, clusterLeft]
514
515            clusterTop.show()
516            clusterLeft.show()
517           
518#            anchors = list(clusterLeft.leaf_anchors())
519#            minx = min([a.x() for a in anchors])
520#            maxx = max([a.x() for a in anchors])
521#            print maxx - minx, width
522           
523
524        # determine the font size to fit the cell width
525        fontrows = self.getfont(self.CellHeight)
526        fontcols = self.getfont(self.CellWidth)
527       
528        fontmetrics_row = QFontMetrics(fontrows)
529        fontmetrics_col = QFontMetrics(fontcols)
530   
531        # labels rendering
532        self.annotationText = []
533        if self.ShowLabels==1 and self.Merge<=1:
534            # show labels, no merging (one item per line)
535            items = self.matrix.items
536            if len(self.distanceMap.elementIndices)==0:
537                tmp = [i for i in range(0, len(items))]
538            else:
539                tmp = [self.distanceMap.elementIndices[i] for i in range(0, len(items))]
540
541            if self.distanceMapConstructor.order:
542                indices = [self.distanceMapConstructor.order[i] for i in tmp]
543            else:
544                indices = tmp
545               
546            maxHeight = 0
547            maxWidth = 0
548            exampleTableHasNames = type(items) == orange.ExampleTable and any(ex.name for ex in items)
549            for i in range(0, len(indices)):
550                text = items[indices[i]]
551               
552                if type(text) not in [str, unicode]:
553                    if exampleTableHasNames or isinstance(text, orange.Variable): 
554                        text = text.name
555                    else:
556                        text = repr(text)
557                       
558                if text != "":
559                    tmpText = QCustomGraphicsText(text, self.scene, -90.0, font=fontcols)
560                    tmpText.show()
561                    if tmpText.boundingRect().height() > maxHeight:
562                        maxHeight = tmpText.boundingRect().height()
563                    self.annotationText += [tmpText]
564
565                    tmpText = QGraphicsSimpleTextItem(text, None, self.scene)
566                    tmpText.setFont(fontrows)
567                    tmpText.show()
568                    if tmpText.boundingRect().width() > maxWidth:
569                        maxWidth = tmpText.boundingRect().width()
570                    self.annotationText += [tmpText]
571           
572            fix = 0.0 
573                       
574            for i in range(0, len(self.annotationText)/2):
575##                self.annotationText[i*2].setX(self.offsetX + maxWidth + 3 + (i+0.5)*self.CellWidth)
576##                self.annotationText[i*2].setY(self.offsetY)
577
578                self.annotationText[i*2].setPos(self.offsetX + maxWidth + 3 + (i+0.5)*self.CellWidth + \
579                                                fontmetrics_row.height()/2.0 * fix,
580                                                self.offsetY + maxWidth)
581##                self.annotationText[i*2 + 1].setX(self.offsetX)
582##                self.annotationText[i*2 + 1].setY(self.offsetY + maxHeight + 3 + (i+0.5)*self.CellHeight)
583##                self.annotationText[i*2 + 1].setPos(self.offsetX, self.offsetY + maxHeight + 3 + (i+0.5)*self.CellHeight)
584                self.annotationText[i*2 + 1].setPos(self.offsetX, self.offsetY + maxWidth + 3 + (i+0.5)*self.CellHeight +\
585                                                    fontmetrics_col.height()/2.0 * fix)
586
587            self.offsetX += maxWidth + 10
588##            self.offsetY += maxHeight + 10
589            self.offsetY += maxWidth + 10
590
591        # rendering of legend
592        if self.ShowLegend==1:
593##            self.legendImage = self.createColorStripe(self.colorPalette.getCurrentColorSchema().getPalette(), offsetX=self.offsetX)
594            self.legendImage = self.createColorStripe(palette, offsetX=self.offsetX)
595            self.legendText1.setText("%4.2f" % lo)
596            self.legendText2.setText("%4.2f" % hi)
597            self.legendText1.setPos(self.offsetX, 0)
598            self.legendText2.setPos(self.offsetX + v_legend_width - self.legendText2.boundingRect().width(), 0)
599            self.legendText1.show()
600            self.legendText2.show()
601        else:
602            self.legendText1.hide()
603            self.legendText2.hide()
604
605        # paint distance map
606
607        if self.rootCluster and self.order:
608            ## We now know the location of bitmap
609#            clusterTop.setPos(self.offsetX + self.CellWidth/2.0/self.Merge, clusterTop.y())
610#            clusterLeft.setPos(clusterLeft.x(), self.offsetY + self.CellHeight/2.0/self.Merge)
611            clusterTop.setPos(self.offsetX , clusterTop.y())
612            clusterLeft.setPos(clusterLeft.x(), self.offsetY)
613           
614        self.distanceImage = ImageItem(bitmap, self.scene, width, height,
615                                       palette, x=self.offsetX, y=self.offsetY, z=0)
616        self.distanceImage.height = height
617        self.distanceImage.width = width
618
619        self.imageWidth = width
620        self.imageHeight = height
621
622##        color = self.colorPalette.getCurrentColorSchema().getAdditionalColors()["Cell outline"]
623        color = self.cellOutlineColor
624        self.selector.setPen(QPen(self.qrgbToQColor(color),v_sel_width))
625        self.selector.setRect(QRectF(0, 0, self.CellWidth, self.CellHeight))
626
627        self.updateSelectionRect()
628        self.scene.setSceneRect(self.scene.itemsBoundingRect().adjusted(-5, -5, 5, 5))
629        self.scene.update()
630
631    def addSelectionLine(self, x, y, direction):
632        selLine = QGraphicsLineItem(None, self.scene)
633        if direction==0:
634            #horizontal line
635            selLine.setLine(self.offsetX + x*self.CellWidth, self.offsetY + y*self.CellHeight,
636                              self.offsetX + (x+1)*self.CellWidth, self.offsetY + y*self.CellHeight)
637        else:
638            #vertical line
639            selLine.setLine(self.offsetX + x*self.CellWidth, self.offsetY + y*self.CellHeight,
640                              self.offsetX + x*self.CellWidth, self.offsetY + (y+1)*self.CellHeight)
641##        color = self.colorPalette.getCurrentColorSchema().getAdditionalColors()["Selected cells"]
642        color = self.selectionColor
643        selLine.setPen(QPen(self.qrgbToQColor(color),v_sel_width))
644        selLine.setZValue(20)
645        selLine.show();
646        self.selectionLines += [selLine]
647
648    def getfont(self, height):
649        return QFont("", max(min(height, 16), 2))
650       
651    def updateSelectionRect(self):
652        selections = self.selection.getSelection()
653        for rect in self.selectionRects:
654            self.scene.removeItem(rect)
655        self.selectionRects = []
656           
657        color = self.selectionColor
658        pen = QPen(self.qrgbToQColor(color),v_sel_width)
659        pen.setCosmetic(True)
660        for selection in selections:
661            rect = QGraphicsRectItem(QRectF(selection), self.distanceImage, self.scene)
662            rect.setPen(pen)
663            rect.setBrush(QBrush(Qt.NoBrush))
664            rect.setZValue(20)
665            rect.scale(self.CellWidth, self.CellHeight)
666            self.selectionRects.append(rect)
667
668    def clearScene(self):
669        if self.distanceImage:
670            self.scene.removeItem(self.distanceImage)
671            self.distanceImage = None
672            self.selectionRects = [] # selectionRects are children of distanceImage
673           
674        if self.legendImage:
675            self.scene.removeItem(self.legendImage)
676            self.legendImage = None
677           
678        for tmpText in getattr(self, "annotationText", []):
679            self.scene.removeItem(tmpText)
680           
681        self.annotationText = []
682
683        for cluster in getattr(self, "clusterItems", []):
684            self.scene.removeItem(cluster)
685           
686        self.clusterItems = []
687           
688        for line in self.selectionLines:
689            self.scene.removeItem(line)
690           
691        self.selectionLines = []
692       
693        self.selector.hide()
694        self.errorText.hide()
695        self.legendText1.hide()
696        self.legendText2.hide()
697       
698       
699        self.scene.setSceneRect(QRectF(0, 0, 10, 10))
700       
701    def mouseMove(self, event):
702        x, y = event.scenePos().x(), event.scenePos().y()
703        row = self.rowFromMousePos(x,y)
704        col = self.colFromMousePos(x,y)
705        if (self.clicked==True):
706            self.selection.UpdateSel(col, row)
707
708        if (row==-1 or col==-1):
709            self.selector.hide()
710        else:
711            bb = self.selector.sceneBoundingRect()
712            self.selector.setPos(self.offsetX + col * self.CellWidth, self.offsetY + row * self.CellHeight)
713            self.selector.show()
714            self.scene.update(bb) #Update the old boundingRect (left unchanged when scrolling the view. Why?)
715
716            if self.ShowBalloon == 1:
717
718                i = self.getItemFromPos(col)
719                j = self.getItemFromPos(row)
720               
721                head = str(self.matrix[i, j])
722
723                if (self.ShowItemsInBalloon == 1):
724                    namei, namej = self.matrix.items[i], self.matrix.items[j]
725                    if type(namei) not in [str, unicode]:
726                        namei = namei.name
727                    if type(namej) not in [str, unicode]:
728                        namej = namej.name
729                    if namei or namej:
730                        body = namei + "\n" + namej
731                    else:
732                        body = ""
733                else:
734                    body = ""
735
736                QToolTip.showText(event.screenPos(), "")
737                QToolTip.showText(event.screenPos(), "%s\n%s" % (head, body))
738
739            self.updateSelectionRect()
740
741#        self.scene.update()
742
743    def keyPressEvent(self, e):
744        if e.key() == 4128:
745            self.shiftPressed = True
746        else:
747            OWWidget.keyPressEvent(self, e)
748
749    def keyReleaseEvent(self, e):
750        if e.key() == 4128:
751            self.shiftPressed = False
752        else:
753            OWWidget.keyReleaseEvent(self, e)
754
755    def mousePress(self, x,y):
756        self.clicked = True
757        row = self.rowFromMousePos(x,y)
758        col = self.colFromMousePos(x,y)
759        if not (self.shiftPressed == True):
760            self.selection.clear()
761        self.selection.SelStart(col, row)
762
763    def mouseRelease(self, x,y):
764        if self.clicked==True:
765            self.clicked = False
766            row = self.rowFromMousePos(x,y)
767            col = self.colFromMousePos(x,y)
768
769            if (row<>-1 and col<>-1):
770                self.selection.SelEnd()
771            else:
772                self.selection.CancelSel()
773
774            self.updateSelectionRect()
775            if self.SendOnRelease==1:
776                self.sendOutput()
777
778    def actionUndo(self):
779        self.selection.undo()
780        self.updateSelectionRect()
781
782    def actionRemoveAllSelections(self):
783        self.selection.clear()
784        self.updateSelectionRect()
785
786    # input signal management
787
788    def sortNone(self):
789        self.order = None
790
791    def sortAdjDist(self):
792        self.order = None
793
794    def sortRandom(self):
795        import random
796        self.order = range(len(self.matrix.items))
797        random.shuffle(self.order)
798        self.rootCluster = None
799
800    def sortClustering(self):
801        cluster = self._clustering_cache.get("sort clustering", None)
802        if cluster is None:
803            cluster = orange.HierarchicalClustering(self.matrix,
804                linkage=orange.HierarchicalClustering.Average)
805            # Cache the cluster
806            self._clustering_cache["sort clustering"] = cluster
807        self.rootCluster = cluster
808        self.order = list(self.rootCluster.mapping)
809       
810    def sortClusteringOrdered(self):
811        cluster = self._clustering_cache.get("sort ordered clustering", None)
812        if cluster is None:
813            cluster = orange.HierarchicalClustering(self.matrix,
814                linkage=orange.HierarchicalClustering.Average)
815            import orngClustering
816            self.progressBarInit()
817            orngClustering.orderLeaves(cluster, self.matrix, self.progressBarSet)
818            self.progressBarFinished()
819            # Cache the cluster
820            self._clustering_cache["sort ordered clustering"] = cluster
821        self.rootCluster = cluster
822        self.order = list(self.rootCluster.mapping)
823
824    def sortItems(self):
825        if not self.matrix:
826            return
827        self.sorting[self.Sort][1]()
828        self.createDistanceMap()
829
830    def setMatrix(self, matrix):
831        self.send("Data", None)
832        self.send("Features", None)
833        self._clustering_cache.clear()
834
835        if not matrix:
836            self.matrix = None
837            self.clearScene()
838        self.sceneView.viewport().setMouseTracking(bool(matrix))
839
840        # check if the same length
841        self.matrix = matrix
842        self.constructDistanceMap()
843
844    def setSquares(self):
845        if self.SquareCells:
846            if self.CellWidth < self.CellHeight:
847                self.CellHeight = self.CellWidth
848            else:
849                self.CellWidth = self.CellHeight
850
851    def adjustCellSize(self, frm, to):
852        if self.SquareCells:
853            setattr(self, to, getattr(self, frm))
854
855    def manageGrid(self):
856        if min(self.CellWidth, self.CellHeight) <= c_smallcell:
857            if self.gridChkBox.isEnabled():
858                self.savedGrid = self.Grid # remember the state
859                self.Grid = 0
860                self.gridChkBox.setDisabled(True)
861        else:
862            if not self.gridChkBox.isEnabled():
863                self.gridChkBox.setEnabled(True)
864                self.Grid = self.savedGrid
865
866#####################################################################
867# new canvas items
868       
869class ImageItem(QGraphicsPixmapItem):
870    def __init__(self, bitmap, scene, width, height, palette, depth=8, numColors=256, x=0, y=0, z=0):
871        image = QImage(bitmap, width, height, QImage.Format_Indexed8)
872        image.bitmap = bitmap # this is tricky: bitmap should not be freed, else we get mess. hence, we store it in the object
873        if qVersion() <= "4.5":
874            image.setColorTable(signedPalette(palette))
875        else:
876            image.setColorTable(palette)
877        pixmap = QPixmap.fromImage(image)
878        QGraphicsPixmapItem.__init__(self, pixmap, None, scene)
879        self.setPos(x, y)
880        self.setZValue(z)
881#        self.show()
882
883class QCustomGraphicsText(QGraphicsSimpleTextItem):
884    def __init__(self, text, scene = None, rotateAngle = 0.0, font=None):
885        QGraphicsSimpleTextItem.__init__(self, None, scene)
886        self.scene = scene
887        self.font = font
888        self.rotateAngle = rotateAngle
889        if font:
890            self.setFont(font)
891        self.rotate(rotateAngle)
892        self.setText(text)
893
894#####################################################################
895# selection manager class
896
897class SelectionManager:
898    def __init__(self):
899        self.selection = []
900        self.selecting = False
901        self.currSelEnd = None
902        self.currSel = None
903
904    def SelStart(self, x, y):
905        if x < 0: x=0
906        if y < 0: y=0
907        self.currSel = QRect(x, y, 1, 1)
908        self.currSelEnd = QRect(x, y, 1, 1)
909        self.selecting = True
910
911    def UpdateSel(self, x, y):
912        self.currSelEnd = QRect(x, y, 1, 1)
913
914    def CancelSel(self):
915        self.selecting = False
916       
917    def SelEnd(self):
918        self.selection += [self.currSel.united(self.currSelEnd).normalized()]
919        self.selecting = False
920
921    def clear(self):
922        self.selection = []
923
924    def undo(self):
925        if len(self.selection) > 0:
926            del self.selection[len(self.selection)-1]
927   
928    def getSelection(self):
929        res = list(self.selection)
930        if self.selecting == True:
931            res += [self.currSel.united(self.currSelEnd).normalized()]
932        return res
933
934if __name__=="__main__":
935    def distanceMatrix(data):
936        dist = orange.ExamplesDistanceConstructor_Euclidean(data)
937        matrix = orange.SymMatrix(len(data))
938        matrix.setattr('items', data)
939        for i in range(len(data)):
940            for j in range(i+1):
941                matrix[i, j] = dist(data[i], data[j])
942        return matrix
943
944    import orange
945    a = QApplication(sys.argv)
946    ow = OWDistanceMap()
947    ow.show()
948
949    data = orange.ExampleTable(r'../../doc/datasets/iris.tab')
950    data = data.select(orange.MakeRandomIndices2(p0=20)(data), 0)
951    for d in data:
952        d.name = str(d["sepal length"])
953    matrix = distanceMatrix(data)
954    ow.setMatrix(matrix)
955    ow.setMatrix(None)
956    ow.setMatrix(matrix)
957    a.exec_()
958
959    ow.saveSettings()
Note: See TracBrowser for help on using the repository browser.