source: orange/orange/OrangeWidgets/Unsupervised/OWDistanceMap.py @ 5279:29dfc117bf19

Revision 5279:29dfc117bf19, 41.7 KB checked in by gregor <gregor@…>, 5 years ago (diff)
  • removed the reference to orngOrangeFoldersQt4
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"""
8
9import orange, math
10import OWGUI, OWToolbars
11from OWWidget import *
12
13from ColorPalette import *
14import OWToolbars
15
16#####################################################################
17# parameters that determine the canvas layout
18
19c_offsetX = 10; c_offsetY = 10  # top and left border
20c_spaceX = 10; c_spaceY = 10    # space btw graphical elements
21c_legendHeight = 15             # height of the legend
22c_averageStripeWidth = 12       # width of the stripe with averages
23c_smallcell = 8                 # below this threshold cells are
24                                # considered small and grid dissapears
25
26#####################################################################
27# canvas with events
28
29class EventfulGraphicsView(QGraphicsView):
30    def __init__(self, scene, parent, master):
31        QGraphicsView.__init__(self, scene, parent)
32        self.master = master
33        self.viewport().setMouseTracking(True)
34
35##    def mousePressEvent (self, event):
36##        self.master.mousePress(event.pos().x(), event.pos().y())
37##
38##    def mouseReleaseEvent (self, event):
39##        self.master.mouseRelease(event.pos().x(), event.pos().y())
40##
41##    def mouseMoveEvent (self, event):
42##        self.master.mouseMove(event.pos().x(), event.pos().y())
43
44class EventfulGraphicsScene(QGraphicsScene):
45    def __init__(self, master):
46        QGraphicsScene.__init__(self)
47        self.master = master
48##        self.setMouseTracking(True)
49
50    def mousePressEvent (self, event):
51        self.master.mousePress(event.scenePos().x(), event.scenePos().y())
52
53    def mouseReleaseEvent (self, event):
54        self.master.mouseRelease(event.scenePos().x(), event.scenePos().y())
55
56    def mouseMoveEvent (self, event):
57        self.master.mouseMove(event.scenePos().x(), event.scenePos().y())
58
59#####################################################################
60# main class
61v_sel_width = 2
62v_legend_width = 104
63v_legend_height = 18
64v_legend_offsetX = 5
65v_legend_offsetY = 15
66
67class OWDistanceMap(OWWidget):
68    settingsList = ["CellWidth", "CellHeight", "Merge", "Gamma", "CutLow",
69                    "CutHigh", "CutEnabled", "Sort", "SquareCells",
70                    "ShowLegend", "ShowLabels", "ShowBalloon",
71                    "Grid", "savedGrid",
72                    "ShowItemsInBalloon", "SendOnRelease", "ColorSchemas"]
73
74    def __init__(self, parent=None, signalManager = None):
75        self.callbackDeposit = [] # deposit for OWGUI callback function
76        OWWidget.__init__(self, parent, signalManager, 'Distance Map')
77
78        self.inputs = [("Distance Matrix", orange.SymMatrix, self.setMatrix)]
79        self.outputs = [("Examples", ExampleTable), ("Attribute List", orange.VarList)]
80
81        self.clicked = False
82        self.offsetX = 5
83        self.offsetY = 5
84        self.imageWidth = 0
85        self.imageHeight = 0
86        self.distanceImage = None
87        self.legendImage = None
88        self.ColorSchemas = None
89
90        self.shiftPressed = False
91
92        #set default settings
93        self.CellWidth = 15; 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; self.CutHigh = 0; self.CutEnabled = 0
100        self.Sort = 0
101        self.SquareCells = 0
102        self.ShowLegend = 1;
103        self.ShowLabels = 1;
104        self.ShowBalloon = 1;
105        self.ShowItemsInBalloon = 1
106        self.SendOnRelease = 1
107
108        self.loadSettings()
109
110        self.maxHSize = 30; self.maxVSize = 30
111        self.sorting = [("No sorting", self.sortNone),
112                        ("Adjacent distance", self.sortAdjDist),
113                        ("Random order", self.sortRandom)]
114
115        self.matrix = self.order = None
116
117        # GUI definition
118        self.tabs = OWGUI.tabWidget(self.controlArea)
119
120        # SETTINGS TAB
121        tab = OWGUI.createTabPage(self.tabs, "Settings")
122        box = OWGUI.widgetBox(tab, "Cell Size (Pixels)", addSpace=True)
123        OWGUI.qwtHSlider(box, self, "CellWidth", label='Width: ',
124                         labelWidth=38, minValue=1, maxValue=self.maxHSize,
125                         step=1, precision=0,
126                         callback=[lambda f="CellWidth", t="CellHeight": self.adjustCellSize(f,t), self.drawDistanceMap, self.manageGrid])
127        OWGUI.qwtHSlider(box, self, "CellHeight", label='Height: ',
128                         labelWidth=38, minValue=1, maxValue=self.maxVSize,
129                         step=1, precision=0,
130                         callback=[lambda f="CellHeight", t="CellWidth": self.adjustCellSize(f,t), self.drawDistanceMap,self.manageGrid])
131        OWGUI.checkBox(box, self, "SquareCells", "Cells as squares",
132                         callback = [self.setSquares, self.drawDistanceMap])
133        self.gridChkBox = OWGUI.checkBox(box, self, "Grid", "Show grid", callback = self.createDistanceMap, disabled=lambda: min(self.CellWidth, self.CellHeight) <= c_smallcell)
134
135        OWGUI.qwtHSlider(tab, self, "Merge", box="Merge" ,label='Elements:', labelWidth=50,
136                         minValue=1, maxValue=100, step=1,
137                         callback=self.createDistanceMap, ticks=0, addSpace=True)
138       
139        self.labelCombo = OWGUI.comboBox(tab, self, "Sort", box="Sort",
140                         items=[x[0] for x in self.sorting],
141                         tooltip="Sorting method for items in distance matrix.",
142                         callback=self.sortItems)
143        OWGUI.rubber(tab)
144
145##        self.tabs.insertTab(tab, "Settings")
146
147        # FILTER TAB
148        tab = OWGUI.createTabPage(self.tabs, "Colors")
149        box = OWGUI.widgetBox(tab, "Color settings", addSpace=True)
150        OWGUI.widgetLabel(box, "Gamma")
151        OWGUI.qwtHSlider(box, self, "Gamma", minValue=0.1, maxValue=1,
152                         step=0.1, maxWidth=100, callback=self.drawDistanceMap)
153
154        OWGUI.separator(box)
155
156        OWGUI.checkBox(box, self, 'CutEnabled', "Enable thresholds", callback=self.setCutEnabled)
157        self.sliderCutLow = OWGUI.qwtHSlider(box, self, 'CutLow', label='Low:',
158                              labelWidth=33, minValue=-100, maxValue=0, step=0.1,
159                              precision=1, ticks=0, maxWidth=80,
160                              callback=self.drawDistanceMap)
161        self.sliderCutHigh = OWGUI.qwtHSlider(box, self, 'CutHigh', label='High:',
162                              labelWidth=33, minValue=0, maxValue=100, step=0.1,
163                              precision=1, ticks=0, maxWidth=80,
164                              callback=self.drawDistanceMap)
165        if not self.CutEnabled:
166            self.sliderCutLow.box.setDisabled(1)
167            self.sliderCutHigh.box.setDisabled(1)
168
169
170        self.colorPalette = ColorPalette(box, self, "",
171                         additionalColors =["Cell outline", "Selected cells"],
172                         callback = self.setColor)
173        box.layout().addWidget(self.colorPalette)
174        OWGUI.rubber(tab)
175
176##        self.tabs.insertTab(tab, "Colors")
177
178        # INFO TAB
179        tab = OWGUI.createTabPage(self.tabs, "Info")
180        box = OWGUI.widgetBox(tab, "Annotation && Legends")
181        OWGUI.checkBox(box, self, 'ShowLegend', 'Show legend',
182                       callback=self.drawDistanceMap)
183        OWGUI.checkBox(box, self, 'ShowLabels', 'Show labels',
184                       callback=self.drawDistanceMap)
185
186        box = OWGUI.widgetBox(tab, "Balloon")
187        OWGUI.checkBox(box, self, 'ShowBalloon', "Show balloon")
188        OWGUI.checkBox(box, self, 'ShowItemsInBalloon', "Display item names")
189
190        box = OWGUI.widgetBox(tab, "Select")
191        box2 = OWGUI.widgetBox(box, orientation = "horizontal")
192        self.box2 = box2
193        self.buttonUndo = OWToolbars.createButton(box2, 'Undo', self.actionUndo,
194                              QIcon(OWToolbars.dlg_undo), toggle = 0)
195        self.buttonRemoveAllSelections = OWToolbars.createButton(box2,
196                              'Remove all selections', self.actionRemoveAllSelections,
197                              QIcon(OWToolbars.dlg_clear), toggle = 0)
198
199        self.buttonSendSelections = OWToolbars.createButton(box2, 'Send selections',
200                              self.sendOutput, QIcon(OWToolbars.dlg_send), toggle = 0)
201        OWGUI.checkBox(box, self, 'SendOnRelease', "Send after mouse release")
202        OWGUI.rubber(tab)
203
204##        self.tabs.insertTab(tab, "Info")
205
206        self.resize(700,400)
207
208        self.scene = EventfulGraphicsScene(self)
209        self.sceneView = EventfulGraphicsView(self.scene, self.mainArea, self)
210        self.mainArea.layout().addWidget(self.sceneView)
211
212        #construct selector
213        self.selector = QGraphicsRectItem(0, 0, self.CellWidth, self.CellHeight, None, self.scene)
214        color = self.colorPalette.getCurrentColorSchema().getAdditionalColors()["Cell outline"]
215        self.selector.setPen(QPen(self.qrgbToQColor(color),v_sel_width))
216        self.selector.setZValue(20)
217
218##        self.bubble = BubbleInfo(self.scene)
219        self.selection = SelectionManager()
220
221        self.selectionLines = []
222        self.annotationText = []
223
224        self.legendText1 = QGraphicsSimpleTextItem(None, self.scene)
225        self.legendText2 = QGraphicsSimpleTextItem(None, self.scene)
226
227        self.errorText = QGraphicsSimpleTextItem("Bitmap is too large.", None, self.scene)
228        self.errorText.setPos(10,10)
229
230        #restore color schemas from settings
231        if self.ColorSchemas:
232            self.colorPalette.setColorSchemas(self.ColorSchemas)
233
234    def createColorStripe(self, palette, offsetX):
235        dx = v_legend_width
236        dy = v_legend_height
237        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
238
239        image = ImageItem(bmp, self.scene, dx, dy, palette, x=offsetX, y=v_legend_offsetY, z=0)
240        return image
241
242    def colFromMousePos(self, x, y):
243        if (x <= self.offsetX or x >= self.offsetX + self.imageWidth):
244            return -1
245        else:
246            return int((x - self.offsetX)/self.CellWidth)
247
248    def rowFromMousePos(self, x,y):
249        if (y <= self.offsetY or y >= self.offsetY + self.imageHeight):
250            return -1
251        else:
252            return int((y - self.offsetY)/self.CellHeight)
253
254
255    def qrgbToQColor(self, color):
256        # we could also use QColor(positiveColor(rgb), 0xFFFFFFFF)
257        return QColor(qRed(positiveColor(color)), qGreen(positiveColor(color)), qBlue(positiveColor(color))) # if color cannot be negative number we convert it manually
258
259    def getItemFromPos(self, i):
260        if (len(self.distanceMap.elementIndices)==0):
261            j = i
262        else:
263            j = self.distanceMap.elementIndices[i]
264
265        if self.distanceMapConstructor.order:
266           j = self.distanceMapConstructor.order[j]
267
268        return j
269
270    def sendOutput(self):
271        if len(self.matrix.items)<1:
272            return
273
274        selectedIndices = []
275        tmp = []
276
277        if len(self.selection.getSelection())==0:
278            self.send("Attribute List", None)
279            self.send("Examples", None)
280        else:
281            selection = self.selection.getSelection()
282            for sel in selection:
283                if (len(self.distanceMap.elementIndices)==0):
284                    tmp += range(sel[0].x(), sel[1].x()+1)
285                    tmp +=range(sel[0].y(), sel[1].y()+1)
286                else:
287                    tmp += range(self.distanceMap.elementIndices[sel[0].x()], self.distanceMap.elementIndices[sel[1].x()+1])
288                    tmp +=range(self.distanceMap.elementIndices[sel[0].y()], self.distanceMap.elementIndices[sel[1].y()+1])
289
290            for i in tmp:
291                if self.distanceMapConstructor.order:
292                    if not (self.distanceMapConstructor.order[i] in selectedIndices):
293                        selectedIndices += [self.distanceMapConstructor.order[i]]
294
295                if not (i in selectedIndices):
296                    selectedIndices += [i]
297
298            items = self.matrix.items
299            if issubclass(orange.EnumVariable, type(items[0])):
300                selected = orange.VarList()
301                for i in selectedIndices:
302                    selected.append(items[i])
303                self.send("Attribute List", selected)
304
305
306            if isinstance(items[0], orange.Example):
307                ex = [items[x] for x in selectedIndices]
308                selected = orange.ExampleTable(items[0].domain, ex)
309                self.send("Examples", selected)
310
311
312    def setColor(self):
313        color = self.colorPalette.getCurrentColorSchema().getAdditionalColors()["Cell outline"]
314        self.selector.setPen(QPen(self.qrgbToQColor(color),v_sel_width))
315
316        self.ColorSchemas = self.colorPalette.getColorSchemas()
317        self.drawDistanceMap()
318
319    def setCutEnabled(self):
320        self.sliderCutLow.box.setDisabled(not self.CutEnabled)
321        self.sliderCutHigh.box.setDisabled(not self.CutEnabled)
322        self.drawDistanceMap()
323
324    def constructDistanceMap(self):
325        if self.matrix:
326            self.distanceMapConstructor = orange.DistanceMapConstructor(distanceMatrix = self.matrix)
327            self.createDistanceMap()
328
329    def createDistanceMap(self):
330        """creates distance map objects"""
331        merge = min(self.Merge, float(self.matrix.dim))
332        squeeze = 1. / merge
333
334        self.distanceMapConstructor.order = self.order
335        self.distanceMap, self.lowerBound, self.upperBound = self.distanceMapConstructor(squeeze)
336
337        self.sliderCutLow.setRange(self.lowerBound, self.upperBound, 0.1)
338        self.sliderCutHigh.setRange(self.lowerBound, self.upperBound, 0.1)
339        self.CutLow = max(self.CutLow, self.lowerBound)
340        self.CutHigh = min(self.CutHigh, self.upperBound)
341        self.sliderCutLow.setValue(self.CutLow)
342        self.sliderCutHigh.setValue(self.CutHigh)
343
344        self.selection.clear()
345        self.drawDistanceMap()
346
347    def drawDistanceMap(self):
348        """renders distance map object on canvas"""
349        if not self.matrix:
350            return
351
352        if self.matrix.dim * max(int(self.CellWidth), int(self.CellHeight)) > 32767:
353            self.errorText.show()
354            return
355
356        self.errorText.hide()
357
358        lo = self.CutEnabled and self.CutLow   or self.lowerBound
359        hi = round(self.CutEnabled and self.CutHigh  or self.upperBound, 1)
360
361        self.offsetX = 5
362
363        if self.distanceImage:
364            self.scene.removeItem(self.distanceImage)
365
366        if self.legendImage:
367            self.scene.removeItem(self.legendImage)
368
369        if self.ShowLegend==1:
370            self.offsetY = v_legend_height + 30
371        else:
372            self.offsetY = 5
373
374        palette = self.colorPalette.getCurrentColorSchema().getPalette()
375        bitmap, width, height = self.distanceMap.getBitmap(int(self.CellWidth),
376                            int(self.CellHeight), lo, hi, self.Gamma, self.Grid)
377
378        self.scene.setSceneRect(0, 0, 2000, 2000) # this needs adjustment
379
380        for tmpText in self.annotationText:
381##            tmpText.setScene(None)
382            self.scene.removeItem(tmpText)
383
384        # determine the font size to fit the cell width
385        fontrows = self.getfont(self.CellHeight)
386        fontcols = self.getfont(self.CellWidth)
387   
388        # labels rendering
389        self.annotationText = []
390        if self.ShowLabels==1 and self.Merge<=1:
391            # show labels, no merging (one item per line)
392            items = self.matrix.items
393            if len(self.distanceMap.elementIndices)==0:
394                tmp = [i for i in range(0, len(items))]
395            else:
396                tmp = [self.distanceMap.elementIndices[i] for i in range(0, len(items))]
397
398            if self.distanceMapConstructor.order:
399                indices = [self.distanceMapConstructor.order[i] for i in tmp]
400            else:
401                indices = tmp
402
403            maxHeight = 0
404            maxWidth = 0
405            for i in range(0, len(indices)):
406                text = items[indices[i]]
407                if type(text) not in [str, unicode]:
408                    text = text.name
409                if text<>"":
410                    tmpText = QCustomGraphicsText(text, self.scene, -90.0, font=fontcols)
411                    tmpText.show()
412                    if tmpText.boundingRect().height() > maxHeight:
413                        maxHeight = tmpText.boundingRect().height()
414                    self.annotationText += [tmpText]
415
416                    tmpText = QGraphicsSimpleTextItem(text, None, self.scene)
417                    tmpText.setFont(fontrows)
418                    tmpText.show()
419                    if tmpText.boundingRect().width() > maxWidth:
420                        maxWidth = tmpText.boundingRect().width()
421                    self.annotationText += [tmpText]
422
423            for i in range(0, len(self.annotationText)/2):
424##                self.annotationText[i*2].setX(self.offsetX + maxWidth + 3 + (i+0.5)*self.CellWidth)
425##                self.annotationText[i*2].setY(self.offsetY)
426                self.annotationText[i*2].setPos(self.offsetX + maxWidth + 3 + (i+0.5)*self.CellWidth, self.offsetY)
427##                self.annotationText[i*2 + 1].setX(self.offsetX)
428##                self.annotationText[i*2 + 1].setY(self.offsetY + maxHeight + 3 + (i+0.5)*self.CellHeight)
429                self.annotationText[i*2 + 1].setPos(self.offsetX, self.offsetY + maxHeight + 3 + (i+0.5)*self.CellHeight)
430
431            self.offsetX += maxWidth + 10
432            self.offsetY += maxHeight + 10
433
434        # rendering of legend
435        if self.ShowLegend==1:
436            self.legendImage = self.createColorStripe(self.colorPalette.getCurrentColorSchema().getPalette(), offsetX=self.offsetX)
437            self.legendText1.setText("%4.2f" % lo)
438            self.legendText2.setText("%4.2f" % hi)
439            self.legendText1.setPos(self.offsetX, 0)
440            self.legendText2.setPos(self.offsetX + v_legend_width - self.legendText2.boundingRect().width(), 0)
441            self.legendText1.show()
442            self.legendText2.show()
443        else:
444            self.legendText1.hide()
445            self.legendText2.hide()
446
447        # paint distance map
448        self.distanceImage = ImageItem(bitmap, self.scene, width, height,
449                                       palette, x=self.offsetX, y=self.offsetY, z=0)
450        self.distanceImage.height = height
451        self.distanceImage.width = width
452
453        self.imageWidth = width
454        self.imageHeight = height
455
456        color = self.colorPalette.getCurrentColorSchema().getAdditionalColors()["Cell outline"]
457        self.selector.setPen(QPen(self.qrgbToQColor(color),v_sel_width))
458        self.selector.setRect(QRectF(0, 0, self.CellWidth, self.CellHeight))
459
460        self.updateSelectionRect()
461        self.scene.update()
462
463    def addSelectionLine(self, x, y, direction):
464        selLine = QGraphicsLineItem(None, self.scene)
465        if direction==0:
466            #horizontal line
467            selLine.setLine(self.offsetX + x*self.CellWidth, self.offsetY + y*self.CellHeight,
468                              self.offsetX + (x+1)*self.CellWidth, self.offsetY + y*self.CellHeight)
469        else:
470            #vertical line
471            selLine.setLine(self.offsetX + x*self.CellWidth, self.offsetY + y*self.CellHeight,
472                              self.offsetX + x*self.CellWidth, self.offsetY + (y+1)*self.CellHeight)
473        color = self.colorPalette.getCurrentColorSchema().getAdditionalColors()["Selected cells"]
474        selLine.setPen(QPen(self.qrgbToQColor(color),v_sel_width))
475        selLine.setZValue(20)
476        selLine.show();
477        self.selectionLines += [selLine]
478
479    def getfont(self, height):
480        """finds the font that for a given height"""
481        dummy = QGraphicsSimpleTextItem("123", None, self.scene)
482        for fontsize in range(8, 2, -1):
483            font = QFont("", fontsize)
484            dummy.setFont(font)
485            if dummy.boundingRect().height() <= height:
486                break
487        return font
488
489    def updateSelectionRect(self):
490        entireSelection = []
491        newSel = False
492        for selLine in self.selectionLines:
493            self.scene.removeItem(selLine)
494       
495        self.selectionLines = []
496        if len(self.selection.getSelection())>0:
497            for sel in self.selection.getSelection():
498                for i in range(sel[0].x(), sel[1].x()):
499                    for j in range(sel[0].y(), sel[1].y()):
500                        selTuple = (i, j)
501                        if not (selTuple in entireSelection):
502                            entireSelection += [selTuple]
503            for selTuple in entireSelection:
504                #check left
505                if (not (selTuple[0] - 1, selTuple[1]) in entireSelection):
506                    self.addSelectionLine(selTuple[0], selTuple[1], 1)
507
508                #check up
509                if (not (selTuple[0], selTuple[1] - 1) in entireSelection):
510                    self.addSelectionLine(selTuple[0], selTuple[1], 0)
511
512                #check down
513                if (not (selTuple[0], selTuple[1] + 1) in entireSelection):
514                    self.addSelectionLine(selTuple[0], selTuple[1] + 1, 0)
515
516                #check right
517                if (not (selTuple[0] + 1, selTuple[1]) in entireSelection):
518                    self.addSelectionLine(selTuple[0] + 1, selTuple[1], 1)
519        self.scene.update()
520
521    def mouseMove(self, x, y):
522        row = self.rowFromMousePos(x,y)
523        col = self.colFromMousePos(x,y)
524
525        if (self.clicked==True):
526            self.selection.UpdateSel(col, row)
527
528        if (row==-1 or col==-1):
529            self.selector.hide()
530##            self.bubble.hide()
531        else:
532##            self.selector.setX(self.offsetX + col * self.CellWidth)
533##            self.selector.setY(self.offsetY + row * self.CellHeight)
534            self.selector.setPos(self.offsetX + col * self.CellWidth, self.offsetY + row * self.CellHeight)
535            self.selector.show()
536
537            if self.ShowBalloon == 1:
538##                self.bubble.move(x + 20, y + 20)
539
540                i = self.getItemFromPos(col)
541                j = self.getItemFromPos(row)
542##                self.bubble.head.setText(str(self.matrix[i, j]))
543                head = str(self.matrix[i, j])
544
545                if (self.ShowItemsInBalloon == 1):
546                    namei, namej = self.matrix.items[i], self.matrix.items[j]
547                    if type(namei) not in [str, unicode]:
548                        namei = namei.name
549                    if type(namej) not in [str, unicode]:
550                        namej = namej.name
551                    if namei or namej:
552##                        self.bubble.body.setText(namei + "\n" + namej)
553                        body = namei + "\n" + namej
554                    else:
555##                        self.bubble.body.setText("")
556                        body = ""
557                else:
558##                    self.bubble.body.setText("")
559                    body = ""
560
561##                self.bubble.show()
562##                QToolTip.showText(QPoint(x,y), "")
563                QToolTip.showText(self.sceneView.mapToGlobal(QPoint(x,y)), "%s\n%s" % (head, body))
564##            else:
565##                self.bubble.hide()
566
567            self.updateSelectionRect()
568
569        self.scene.update()
570
571    def keyPressEvent(self, e):
572        if e.key() == 4128:
573            self.shiftPressed = True
574        else:
575            OWWidget.keyPressEvent(self, e)
576
577    def keyReleaseEvent(self, e):
578        if e.key() == 4128:
579            self.shiftPressed = False
580        else:
581            OWWidget.keyReleaseEvent(self, e)
582
583    def mousePress(self, x,y):
584        self.clicked = True
585        row = self.rowFromMousePos(x,y)
586        col = self.colFromMousePos(x,y)
587        if not (self.shiftPressed == True):
588            self.selection.clear()
589        self.selection.SelStart(col, row)
590
591    def mouseRelease(self, x,y):
592        if self.clicked==True:
593            self.clicked = False
594            row = self.rowFromMousePos(x,y)
595            col = self.colFromMousePos(x,y)
596
597            if (row<>-1 and col<>-1):
598                self.selection.SelEnd()
599            else:
600                self.selection.CancelSel()
601
602            self.updateSelectionRect()
603            if self.SendOnRelease==1:
604                self.sendOutput()
605
606    def actionUndo(self):
607        self.selection.undo()
608        self.updateSelectionRect()
609
610    def actionRemoveAllSelections(self):
611        self.selection.clear()
612        self.updateSelectionRect()
613
614    # input signal management
615
616    def sortNone(self):
617        self.order = None
618
619    def sortAdjDist(self):
620        self.order = None
621
622    def sortRandom(self):
623        import random
624        self.order = range(len(self.matrix.items))
625        random.shuffle(self.order)
626
627    def sortItems(self):
628        if not self.matrix:
629            return
630        self.sorting[self.Sort][1]()
631        self.createDistanceMap()
632
633    def setMatrix(self, matrix):
634        self.send("Examples", None)
635        self.send("Attribute List", None)
636
637        if not matrix:
638            return
639
640        # check if the same length
641        self.matrix = matrix
642        self.constructDistanceMap()
643
644    def setSquares(self):
645        if self.SquareCells:
646            if self.CellWidth < self.CellHeight:
647                self.CellHeight = self.CellWidth
648            else:
649                self.CellWidth = self.CellHeight
650
651    def adjustCellSize(self, frm, to):
652        if self.SquareCells:
653            setattr(self, to, getattr(self, frm))
654
655    def manageGrid(self):
656        if min(self.CellWidth, self.CellHeight) <= c_smallcell:
657            if self.gridChkBox.isEnabled():
658                self.savedGrid = self.Grid # remember the state
659                self.Grid = 0
660                self.gridChkBox.setDisabled(True)
661        else:
662            if not self.gridChkBox.isEnabled():
663                self.gridChkBox.setEnabled(True)
664                self.Grid = self.savedGrid
665
666#####################################################################
667# new canvas items
668
669class ImageItem(QGraphicsRectItem):
670    def __init__(self, bitmap, scene, width, height, palette, depth=8, numColors=256, x=0, y=0, z=0):
671        QGraphicsRectItem.__init__(self, None, scene)
672##        self.image = QImage(bitmap, width, height, depth, signedPalette(palette), numColors, QImage.LittleEndian) # we take care palette has proper values with proper types
673        self.image = QImage(bitmap, width, height, QImage.Format_Indexed8)
674        self.image.bitmap = bitmap # this is tricky: bitmap should not be freed, else we get mess. hence, we store it in the object
675        self.image.setColorTable(signedPalette(palette))
676        self.scene = scene
677        self.setRect(0, 0 ,width, height)
678##        self.setX(x); self.setY(y); self.setZValue(z)
679        self.setPos(x, y)
680        self.setZValue(z)
681        self.show()
682
683    def paint(self, painter, option, widget=None):
684##        painter.drawImage(self.x(), self.y(), self.image, 0, 0, -1, -1)
685        painter.drawImage(0, 0, self.image, 0, 0, -1, -1)
686
687class QCustomGraphicsText(QGraphicsSimpleTextItem):
688    def __init__(self, text, scene = None, rotateAngle = 0.0, font=None):
689        QGraphicsSimpleTextItem.__init__(self, None, scene)
690        self.scene = scene
691        self.font = font
692        self.rotateAngle = rotateAngle
693        if font:
694            self.setFont(font)
695        self.rotate(rotateAngle)
696        self.setText(text)
697       
698##class QCustomCanvasText(QCanvasRectangle):
699##    def __init__(self, text, canvas = None, rotateAngle = 0.0, font=None):
700##        QCanvasRectangle.__init__(self, canvas)
701##        self.canvas = canvas
702##        self.font = font
703##        self.rotateAngle = rotateAngle
704##        self.setText(text)
705##       
706##    def setText(self, text):
707##        self.text = text
708##        self.hiddenText = QCanvasText(text, self.canvas)
709##        self.hiddenText.setFont(self.font)
710##        if self.font:
711##            self.hiddenText.setFont(self.font)
712##        xsize = self.hiddenText.boundingRect().height()
713##        ysize = self.hiddenText.boundingRect().width()
714##        self.setSize(xsize, ysize)
715##
716##    def draw(self, painter):
717##        pixmap = QPixmap()
718##        xsize = self.hiddenText.boundingRect().height()
719##        ysize = self.hiddenText.boundingRect().width()
720##        pixmap.resize(xsize, ysize)
721##
722##        helpPainter = QPainter()
723##        helpPainter.begin(pixmap)
724##        helpPainter.setFont(self.font)
725##
726##        helpPainter.setPen( Qt.black );
727##        helpPainter.setBrush( Qt.white );
728##        helpPainter.drawRect( -1, -1, xsize + 2, ysize + 2);
729##        helpPainter.rotate(self.rotateAngle)
730##        helpPainter.drawText(-ysize, xsize, self.text)
731##        helpPainter.end()
732##
733##        painter.drawPixmap(self.x(), self.y(), pixmap)
734
735#####################################################################
736# selection manager class
737
738class SelectionManager:
739    def __init__(self):
740        self.selection = []
741        self.selecting = False
742        self.currSelEnd = None
743        self.currSel = None
744
745    def SelStart(self, x, y):
746        if x < 0: x=0
747        if y < 0: y=0
748        self.currSel = QPoint(x,y)
749        self.currSelEnd = QPoint(x,y)
750        self.selecting = True
751
752    def UpdateSel(self, x, y):
753        self.currSelEnd = QPoint(x,y)
754
755    def CancelSel(self):
756        self.selecting = False
757
758    def SelEnd(self):
759        minx = min(self.currSel.x(), self.currSelEnd.x())
760        maxx = max(self.currSel.x(), self.currSelEnd.x())
761
762        miny = min(self.currSel.y(), self.currSelEnd.y())
763        maxy = max(self.currSel.y(), self.currSelEnd.y())
764
765        if (minx==maxx) and (miny==maxy):
766            maxx+=1
767            maxy+=1
768
769        self.selection += [(QPoint(minx, miny),QPoint(maxx,maxy))]
770        self.selecting = False
771
772    def clear(self):
773        self.selection = []
774
775    def undo(self):
776        if len(self.selection)>0:
777            del self.selection[len(self.selection)-1]
778
779    def getSelection(self):
780        res = self.selection + []
781        if self.selecting==True:
782            minx = min(self.currSel.x(), self.currSelEnd.x())
783            maxx = max(self.currSel.x(), self.currSelEnd.x())
784
785            miny = min(self.currSel.y(), self.currSelEnd.y())
786            maxy = max(self.currSel.y(), self.currSelEnd.y())
787
788            res += [(QPoint(minx, miny),QPoint(maxx,maxy))]
789        return res
790
791#####################################################################
792# bubble info class
793
794bubbleBorder = 4
795
796class BubbleInfo(QGraphicsRectItem):
797    def __init__(self, *args):
798        QGraphicsRectItem.__init__(self, *args)
799        self.scene = args[0]
800        self.setBrush(QBrush(Qt.white))
801        #self.setPen(QPen(Qt.black, v_sel_width))
802        self.bubbleShadow = QGraphicsRectItem(self, self.scene)
803        self.bubbleShadow.setBrush(QBrush(Qt.black))
804        self.bubbleShadow.setPen(QPen(Qt.black))
805        self.head = QCanvasText(self.canvas)
806        self.line = QCanvasLine(self.canvas)
807        self.body = QCanvasText(self.canvas)
808        self.items = [self.head, self.line, self.body]
809        self.setZ(110)
810        self.bubbleShadow.setZ(109)
811        for i in self.items:
812            i.setZ(111)
813
814    def move(self, x, y):
815        QCanvasRectangle.move(self, x, y)
816        self.setX(x); self.setY(y)
817        self.bubbleShadow.move(x+5, y+5)
818        for item in self.items:
819            item.setX(x + bubbleBorder)
820        w = max(100, self.head.boundingRect().width() + 2 * bubbleBorder, self.body.boundingRect().width() + 2 * bubbleBorder)
821        y += 2
822        self.head.setY(y)
823        y += self.head.boundingRect().height()
824        self.line.setPoints(0,0,w,0)
825        self.line.setX(x); self.line.setY(y)
826        y += 2
827        self.body.setY(y)
828        h = 2 * (2 + (self.body.text()<>None)) + self.head.boundingRect().height() + (self.body.text()<>None) * self.body.boundingRect().height()
829        self.setSize(w,h)
830        self.bubbleShadow.setSize(w,h)
831
832    def show(self):
833        QCanvasRectangle.show(self)
834        self.bubbleShadow.show()
835        self.head.show()
836        if self.body.text():
837            self.line.show()
838            self.body.show()
839
840    def hide(self):
841        QCanvasRectangle.hide(self)
842        self.bubbleShadow.hide()
843        for item in self.items:
844            item.hide()
845
846
847#############################################################
848# color palette
849
850class ColorPalette(QWidget):
851    def __init__(self, parent, master, value, label = "Colors", additionalColors = None, callback = None):
852        QWidget.__init__(self, parent)
853
854        self.constructing = TRUE
855        self.callback = callback
856        self.schema = ""
857        self.passThroughBlack = 0
858
859        self.colorSchemas = {}
860
861        self.setMinimumHeight(300)
862        self.setMinimumWidth(200)
863
864        self.box = OWGUI.widgetBox(self, label, orientation = "vertical")
865
866        self.schemaCombo = OWGUI.comboBox(self.box, self, "schema", callback = self.onComboBoxChange)
867
868        self.interpolationHBox = OWGUI.widgetBox(self.box, orientation = "horizontal")
869        self.colorButton1 = ColorButton(self, self.interpolationHBox)
870        self.interpolationView = InterpolationView(self.interpolationHBox)
871        self.colorButton2 = ColorButton(self, self.interpolationHBox)
872
873        self.chkPassThroughBlack = OWGUI.checkBox(self.box, self, "passThroughBlack", "Pass through black", callback = self.onCheckBoxChange)
874        #OWGUI.separator(self.box, 10, 10)
875        self.box.layout().addSpacing(10)
876
877        #special colors buttons
878
879        self.NAColorButton = ColorButton(self, self.box, "N/A")
880        self.underflowColorButton = ColorButton(self, self.box, "Underflow")
881        self.overflowColorButton = ColorButton(self, self.box, "Overflow")
882        self.backgroundColorButton = ColorButton(self, self.box, "Background (Grid)")
883
884        #set up additional colors
885        self.additionalColorButtons = {}
886
887        if additionalColors<>None:
888            for colorName in additionalColors:
889                self.additionalColorButtons[colorName] = ColorButton(self, self.box, colorName)
890
891        #set up new and delete buttons
892        self.buttonHBox = OWGUI.widgetBox(self.box, orientation = "horizontal")
893        self.newButton = OWGUI.button(self.buttonHBox, self, "New", self.OnNewButtonClicked)
894        self.deleteButton = OWGUI.button(self.buttonHBox, self, "Delete", self.OnDeleteButtonClicked)
895
896        self.setInitialColorPalettes()
897        self.paletteSelected()
898        self.constructing = FALSE
899
900    def onComboBoxChange(self, string):
901        self.paletteSelected()
902
903    def onCheckBoxChange(self, state):
904        self.colorSchemaChange()
905
906    def OnNewButtonClicked(self):
907        message = "Please enter new color schema name"
908        ok = FALSE
909        while (not ok):
910            s = QInputDialog.getText(self, "New Schema", message)
911            ok = TRUE
912            if (s[1]==TRUE):
913                for i in range(self.schemaCombo.count()):
914                    if s[0].lower().compare(self.schemaCombo.itemText(i).lower())==0:
915                        ok = FALSE
916                        message = "Color schema with that name already exists, please enter another name"
917                if (ok):
918                    self.colorSchemas[str(s[0])] = ColorSchema(self.getCurrentColorSchema().getName(),
919                                                               self.getCurrentColorSchema().getPalette(),
920                                                               self.getCurrentColorSchema().getAdditionalColors(),
921                                                               self.getCurrentColorSchema().getPassThroughBlack())
922                    self.schemaCombo.addItem(s[0])
923                    self.schemaCombo.setCurrentIndex(self.schemaCombo.count()-1)
924            self.deleteButton.setEnabled(self.schemaCombo.count()>1)
925
926
927    def OnDeleteButtonClicked(self):
928        i = self.schemaCombo.currentIndex()
929        self.schemaCombo.removeItem(i)
930        self.schemaCombo.setCurrentIndex(i)
931        self.deleteButton.setEnabled(self.schemaCombo.count()>1)
932        self.paletteSelected()
933
934    def getCurrentColorSchema(self):
935        return self.colorSchemas[str(self.schemaCombo.currentText())]
936
937    def setCurrentColorSchema(self, schema):
938        self.colorSchemas[str(self.schemaCombo.currentText())] = schema
939
940
941    def getColorSchemas(self):
942        return self.colorSchemas
943
944    def setColorSchemas(self, schemas):
945        self.colorSchemas = schemas
946        self.schemaCombo.clear()
947        self.schemaCombo.addItems(schemas)
948        self.paletteSelected()
949
950    def createPalette(self,color1,color2, passThroughBlack):
951        palette = []
952        if passThroughBlack:
953            for i in range(paletteInterpolationColors/2):
954                palette += [qRgb(color1.red() - color1.red()*i*2./paletteInterpolationColors,
955                                 color1.green() - color1.green()*i*2./paletteInterpolationColors,
956                                 color1.blue() - color1.blue()*i*2./paletteInterpolationColors)]
957
958            for i in range(paletteInterpolationColors - (paletteInterpolationColors/2)):
959                palette += [qRgb(color2.red()*i*2./paletteInterpolationColors,
960                                 color2.green()*i*2./paletteInterpolationColors,
961                                 color2.blue()*i*2./paletteInterpolationColors)]
962        else:
963            for i in range(paletteInterpolationColors):
964                palette += [qRgb(color1.red() + (color2.red()-color1.red())*i/paletteInterpolationColors,
965                                 color1.green() + (color2.green()-color1.green())*i/paletteInterpolationColors,
966                                 color1.blue() + (color2.blue()-color1.blue())*i/paletteInterpolationColors)]
967        return palette
968
969    def paletteSelected(self):
970        schema = self.getCurrentColorSchema()
971        self.interpolationView.setPalette1(schema.getPalette())
972        self.colorButton1.setColor(self.rgbToQColor(schema.getPalette()[0]))
973        self.colorButton2.setColor(self.rgbToQColor(schema.getPalette()[249]))
974
975        self.chkPassThroughBlack.setChecked(schema.getPassThroughBlack())
976
977        self.NAColorButton.setColor(self.rgbToQColor(schema.getPalette()[255]))
978        self.overflowColorButton.setColor(self.rgbToQColor(schema.getPalette()[254]))
979        self.underflowColorButton.setColor(self.rgbToQColor(schema.getPalette()[253]))
980        self.backgroundColorButton.setColor(self.rgbToQColor(schema.getPalette()[252]))
981
982        for buttonName in self.additionalColorButtons:
983            self.additionalColorButtons[buttonName].setColor(self.rgbToQColor(schema.getAdditionalColors()[buttonName]))
984
985        if not self.constructing:
986            self.callback()
987
988    def rgbToQColor(self, rgb):
989        # we could also use QColor(positiveColor(rgb), 0xFFFFFFFF) but there is probably a reason
990        # why this was not used before so I am leaving it as it is
991
992        return QColor(qRed(positiveColor(rgb)), qGreen(positiveColor(rgb)), qBlue(positiveColor(rgb))) # on Mac color cannot be negative number in this case so we convert it manually
993
994    def qRgbFromQColor(self, qcolor):
995        return qRgb(qcolor.red(), qcolor.green(), qcolor.blue())
996
997    def colorSchemaChange(self):
998        white = qRgb(255,255,255)
999        gray = qRgb(200,200,200)
1000        name = self.getCurrentColorSchema().getName()
1001        passThroughBlack = self.chkPassThroughBlack.isChecked()
1002        palette = self.createPalette(self.colorButton1.getColor(), self.colorButton2.getColor(), passThroughBlack)
1003        palette += [white]*2 + [self.qRgbFromQColor(self.backgroundColorButton.getColor())] + \
1004                               [self.qRgbFromQColor(self.underflowColorButton.getColor())] + \
1005                               [self.qRgbFromQColor(self.overflowColorButton.getColor())] + \
1006                               [self.qRgbFromQColor(self.NAColorButton.getColor())]
1007
1008        self.interpolationView.setPalette1(palette)
1009
1010        additionalColors = {}
1011        for buttonName in self.additionalColorButtons:
1012            additionalColors[buttonName] = self.qRgbFromQColor(self.additionalColorButtons[buttonName].getColor())
1013
1014        schema = ColorSchema(name, palette, additionalColors, passThroughBlack)
1015        self.setCurrentColorSchema(schema)
1016
1017        if not self.constructing and self.callback:
1018            self.callback()
1019
1020
1021    def setInitialColorPalettes(self):
1022        white = qRgb(255,255,255)
1023        gray = qRgb(200,200,200)
1024
1025        additionalColors = {}
1026        for buttonName in self.additionalColorButtons:
1027            additionalColors[buttonName] = gray
1028
1029
1030        self.schemaCombo.addItem("Blue - Yellow")
1031        palette = self.createPalette(QColor(0,0,255), QColor(255,255,0),FALSE)
1032        palette += [white]*3 + [qRgb(0., 0., 255.), qRgb(255., 255., 0.), gray]
1033        self.colorSchemas["Blue - Yellow"] = ColorSchema("Blue - Yellow", palette, additionalColors, FALSE)
1034
1035        self.schemaCombo.addItem("Black - Red")
1036        palette = self.createPalette(QColor(0,0,0), QColor(255,0,0),FALSE)
1037        palette += [white]*3 + [qRgb(0., 0, 0), qRgb(255., 0, 0), gray]
1038        self.colorSchemas["Black - Red"] = ColorSchema("Black - Red", palette, additionalColors, FALSE)
1039
1040        self.schemaCombo.addItem("Green - Black - Red")
1041        palette = self.createPalette(QColor(0,255,0), QColor(255,0,0),TRUE)
1042        palette += [white]*3 + [qRgb(0, 255., 0), qRgb(255., 0, 0), gray]
1043        self.colorSchemas["Green - Black - Red"] = ColorSchema("Green - Black - Red", palette, additionalColors, TRUE)
1044
1045
1046
1047
1048#############################################################
1049# test script
1050
1051if __name__=="__main__":
1052    def distanceMatrix(data):
1053        dist = orange.ExamplesDistanceConstructor_Euclidean(data)
1054        matrix = orange.SymMatrix(len(data))
1055        matrix.setattr('items', data)
1056        for i in range(len(data)):
1057            for j in range(i+1):
1058                matrix[i, j] = dist(data[i], data[j])
1059        return matrix
1060
1061    import orange
1062    a = QApplication(sys.argv)
1063    ow = OWDistanceMap()
1064    ow.show()
1065
1066    data = orange.ExampleTable(r'../../doc/datasets/iris.tab')
1067    data = data.select(orange.MakeRandomIndices2(p0=20)(data), 0)
1068    for d in data:
1069        d.name = str(d["sepal length"])
1070    matrix = distanceMatrix(data)
1071    ow.setMatrix(matrix)
1072
1073    a.exec_()
1074
1075    ow.saveSettings()
Note: See TracBrowser for help on using the repository browser.