source: orange/Orange/OrangeWidgets/Unsupervised/OWDistanceMap.py @ 11406:fab0b2bbca67

Revision 11406:fab0b2bbca67, 38.1 KB checked in by Ales Erjavec <ales.erjavec@…>, 13 months ago (diff)

Added checks for existance of matrix.items attribute.

The widget should not fail if 'items' is not present (references #1264).

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