# source:orange/Orange/OrangeWidgets/VisualizeQt/OWPolyvizGraphQt.py@11474:df0622184ee6

Revision 11474:df0622184ee6, 23.1 KB checked in by markotoplak, 12 months ago (diff)

Renamed Visualize Qt to VisualizeQt (so it can be loaded in new canvas).

RevLine
[8682]1from plot.owplot import *
2from copy import copy, deepcopy
3import time, math
4from OWkNNOptimization import *
5from orngScalePolyvizData import *
6import orngVisFuncts
7from plot.owtools import UnconnectedLinesCurve
8
9# ####################################################################
10# calculate Euclidean distance between two points
11def euclDist(v1, v2):
12    val = 0
13    for i in range(len(v1)):
14        val += (v1[i]-v2[i])**2
15    return math.sqrt(val)
16
17
18# ####################################################################
19# get a list of all different permutations
20def getPermutationList(elements, tempPerm, currList, checkReverse):
21    for i in range(len(elements)):
22        el =  elements[i]
23        elements.remove(el)
24        tempPerm.append(el)
25        getPermutationList(elements, tempPerm, currList, checkReverse)
26
27        elements.insert(i, el)
28        tempPerm.pop()
29
30    if elements == []:
31        temp = copy(tempPerm)
32        # in tempPerm we have a permutation. Check if it already exists in the currList
33        for i in range(len(temp)):
34            el = temp.pop()
35            temp.insert(0, el)
36            if str(temp) in currList: return
37
38
39        if checkReverse == 1:
40            # also try the reverse permutation
41            temp.reverse()
42            for i in range(len(temp)):
43                el = temp.pop()
44                temp.insert(0, el)
45                if str(temp) in currList: return
46        currList[str(tempPerm)] = copy(tempPerm)
47
48def fact(i):
49        ret = 1
50        while i > 1:
51            ret = ret*i
52            i -= 1
53        return ret
54
55# return number of combinations where we select "select" from "total"
56def combinations(select, total):
57    return fact(total)/ (fact(total-select)*fact(select))
58
59
60LINE_TOOLTIPS = 0
61VISIBLE_ATTRIBUTES = 1
62ALL_ATTRIBUTES = 2
63
64TOOLTIPS_SHOW_DATA = 0
65TOOLTIPS_SHOW_SPRINGS = 1
66
67###########################################################################################
68##### CLASS : OWPolyvizGRAPH
69###########################################################################################
70class OWPolyvizGraphQt(OWPlot, orngScalePolyvizData):
71    def __init__(self, polyvizWidget, parent = None, name = None):
72        "Constructs the graph"
[8691]73        OWPlot.__init__(self, parent, name, axes = [], widget=polyvizWidget)
[8682]74        orngScalePolyvizData.__init__(self)
75        self.enableGridXB(0)
76        self.enableGridYL(0)
77
78        self.lineLength = 2
79        self.totalPossibilities = 0 # a variable used in optimization - tells us the total number of different attribute positions
80        self.triedPossibilities = 0 # how many possibilities did we already try
81        self.startTime = time.time()
82        self.enhancedTooltips = 1
83        self.kNNOptimization = None
84        self.polyvizWidget = polyvizWidget
85        self.useDifferentSymbols = 0
86        self.useDifferentColors = 1
87        self.tooltipKind = 0        # index in ["Show line tooltips", "Show visible attributes", "Show all attributes"]
88        self.tooltipValue = 0       # index in ["Tooltips show data values", "Tooltips show spring values"]
89
90        self.dataMap = {}        # each key is of form: "xVal-yVal", where xVal and yVal are discretized continuous values. Value of each key has form: (x,y, HSVValue, [data vals])
91        self.tooltipCurveKeys = []
92        self.tooltipMarkers   = []
93        self.showLegend = 1
94        self.onlyOnePerSubset = 1
95
96        self.showProbabilities = 0
97        self.squareGranularity = 3
98        self.spaceBetweenCells = 1
99        self.scaleFactor = 1.0
100
101        # init axes
102        self.setAxisScale(xBottom, -1.20, 1.20, 1)
103        self.setAxisScale(yLeft, -1.20, 1.20, 1)
104
105    def createAnchors(self, anchorNum):
106        anchors = [[],[]]
107        for i in range(anchorNum):
108            x = math.cos(2*math.pi * float(i) / float(anchorNum)); strX = "%.5f" % (x)
109            y = math.sin(2*math.pi * float(i) / float(anchorNum)); strY = "%.5f" % (y)
110            anchors[0].append(float(strX))  # this might look stupid, but this way we get rid of rounding errors
111            anchors[1].append(float(strY))
112        return anchors
113
114    def setData(self, data, subsetData = None, **args):
115        OWPlot.setData(self, data)
116        orngScalePolyvizData.setData(self, data, subsetData, **args)
117
118    #
119    # update shown data. Set labels, coloring by className ....
120    #
121    def updateData(self, labels, foo, **args):
122        self.clear()
123
124        # initial var values
125        self.showKNNModel = 0
126        self.showCorrect = 1
127        self.__dict__.update(args)
128
129        length = len(labels)
130        self.dataMap = {}               # dictionary with keys of form "x_i-y_i" with values (x_i, y_i, color, data)
131        self.XAnchor = self.createXAnchors(length)
132        self.YAnchor = self.createYAnchors(length)
133        self.shownAttributes = labels
134        polyvizLineCoordsX = []; polyvizLineCoordsY = []    # if class is discrete we will optimize drawing by storing computed values and adding less data curves to plot
135
136        # we must have at least 3 attributes to be able to show anything
137        if not self.haveData or len(labels) < 3:
138            self.updateLayout()
139            return
140
141        dataSize = len(self.rawData)
142
143        if self.dataHasClass: useDifferentColors = self.useDifferentColors   # don't use colors if we don't have a class
144        else:                 useDifferentColors = 0
145
[8200]146        self.setAxisScale(xBottom, -1.20, 1.20, 1)
[8682]147
148        # store indices to shown attributes
149        indices = [self.attributeNameIndex[label] for label in labels]
150
151        # will we show different symbols?
152        useDifferentSymbols = self.useDifferentSymbols and self.dataHasDiscreteClass and len(self.dataDomain.classVar.values) < len(self.curveSymbols)
153
154        # ##########
155        # draw text at lines
156        for i in range(length):
157            # print attribute name
158            self.addMarker(labels[i], 0.6*(self.XAnchor[i]+ self.XAnchor[(i+1)%length]), 0.6*(self.YAnchor[i]+ self.YAnchor[(i+1)%length]), Qt.AlignHCenter | Qt.AlignVCenter, bold = 1)
159
161                # print all possible attribute values
163                count = len(values)
164                k = 1.08
165                for j in range(count):
166                    pos = (1.0 + 2.0*float(j)) / float(2*count)
167                    self.addMarker(values[j], k*(1-pos)*self.XAnchor[i]+k*pos*self.XAnchor[(i+1)%length], k*(1-pos)*self.YAnchor[i]+k*pos*self.YAnchor[(i+1)%length], Qt.AlignHCenter | Qt.AlignVCenter)
168            else:
169                # min and max value
170                if self.tooltipValue == TOOLTIPS_SHOW_SPRINGS:
171                    names = ["%.1f" % (0.0), "%.1f" % (1.0)]
172                elif self.tooltipValue == TOOLTIPS_SHOW_DATA:
173                    names = ["%%.%df" % (self.dataDomain[labels[i]].numberOfDecimals) % (self.attrValues[labels[i]][0]), "%%.%df" % (self.dataDomain[labels[i]].numberOfDecimals) % (self.attrValues[labels[i]][1])]
174                self.addMarker(names[0],0.95*self.XAnchor[i]+0.15*self.XAnchor[(i+1)%length], 0.95*self.YAnchor[i]+0.15*self.YAnchor[(i+1)%length], Qt.AlignHCenter | Qt.AlignVCenter)
175                self.addMarker(names[1], 0.15*self.XAnchor[i]+0.95*self.XAnchor[(i+1)%length], 0.15*self.YAnchor[i]+0.95*self.YAnchor[(i+1)%length], Qt.AlignHCenter | Qt.AlignVCenter)
176
177        XAnchorPositions = numpy.zeros([length, dataSize], numpy.float)
178        YAnchorPositions = numpy.zeros([length, dataSize], numpy.float)
179        XAnchor = self.createXAnchors(length)
180        YAnchor = self.createYAnchors(length)
181
182        for i in range(length):
183            Xdata = XAnchor[i] * (1-self.noJitteringScaledData[indices[i]]) + XAnchor[(i+1)%length] * self.noJitteringScaledData[indices[i]]
184            Ydata = YAnchor[i] * (1-self.noJitteringScaledData[indices[i]]) + YAnchor[(i+1)%length] * self.noJitteringScaledData[indices[i]]
185            XAnchorPositions[i] = Xdata
186            YAnchorPositions[i] = Ydata
187
188        XAnchorPositions = numpy.swapaxes(XAnchorPositions, 0,1)
189        YAnchorPositions = numpy.swapaxes(YAnchorPositions, 0,1)
190
191        selectedData = numpy.take(self.scaledData, indices, axis = 0)
193
194        # test if there are zeros in sum_i
195        if len(numpy.nonzero(sum_i)) < len(sum_i):
196            add = numpy.where(sum_i == 0, 1.0, 0.0)
198
199        x_positions = numpy.sum(numpy.swapaxes(XAnchorPositions * numpy.swapaxes(selectedData, 0,1), 0,1), axis=0) * self.scaleFactor / sum_i
200        y_positions = numpy.sum(numpy.swapaxes(YAnchorPositions * numpy.swapaxes(selectedData, 0,1), 0,1), axis=0) * self.scaleFactor / sum_i
201        validData = self.getValidList(indices)
202
207
208        if self.showKNNModel == 1 and self.dataHasClass:
209            # variables and domain for the table
210            domain = orange.Domain([orange.FloatVariable("xVar"), orange.FloatVariable("yVar"), self.dataDomain.classVar])
211            table = orange.ExampleTable(domain)
212
213            # build an example table
214            for i in range(dataSize):
215                if validData[i]:
216                    table.append(orange.Example(domain, [x_positions[i], y_positions[i], self.rawData[i].getclass()]))
217
218            kNNValues, probabilities = self.kNNOptimization.kNNClassifyData(table)
219            accuracy = copy(kNNValues)
220            measure = self.kNNOptimization.getQualityMeasure()
222                if ((measure == CLASS_ACCURACY or measure == AVERAGE_CORRECT) and self.showCorrect) or (measure == BRIER_SCORE and not self.showCorrect):
223                    kNNValues = [1.0 - val for val in kNNValues]
224            else:
225                if self.showCorrect:
226                    kNNValues = [1.0 - val for val in kNNValues]
227
228            # fill and edge color palettes
229            bwColors = ColorPaletteBW(-1, 55, 255)
230
231            if self.dataHasContinuousClass:
232                preText = 'Mean square error : '
233                classColors = self.contPalette
234            else:
235                classColors = self.discPalette
236                if measure == CLASS_ACCURACY:    preText = "Classification accuracy : "
237                elif measure == AVERAGE_CORRECT: preText = "Average correct classification : "
238                else:                            preText = "Brier score : "
239
240            for i in range(len(table)):
241                fillColor = bwColors.getRGB(kNNValues[i])
242                edgeColor = classColors.getRGB(self.originalData[self.dataClassIndex][i])
243                if not xPointsToAdd.has_key((fillColor, edgeColor, OWPoint.Ellipse, 1)):
244                    xPointsToAdd[(fillColor, edgeColor, OWPoint.Ellipse, 1)] = []
245                    yPointsToAdd[(fillColor, edgeColor, OWPoint.Ellipse, 1)] = []
248                self.addAnchorLine(x_positions[i], y_positions[i], XAnchorPositions[i], YAnchorPositions[i], fillColor, i, length)
249
250        # CONTINUOUS class
251        elif self.dataHasContinuousClass:
252            for i in range(dataSize):
253                if not validData[i]: continue
[8716]254                if useDifferentColors:
255                    newColor = self.contPalette[self.noJitteringScaledData[self.dataClassIndex][i]]
256                else:
257                    newColor = self.color(OWPalette.Data)
[8682]258                self.addCurve(str(i), newColor, newColor, self.pointWidth, xData = [x_positions[i]], yData = [y_positions[i]])
259                self.addTooltipKey(x_positions[i], y_positions[i], XAnchorPositions[i], YAnchorPositions[i], newColor, i)
260                self.addAnchorLine(x_positions[i], y_positions[i], XAnchorPositions[i], YAnchorPositions[i], (newColor.red(), newColor.green(), newColor.blue()), i, length)
261
262        # DISCRETE class or no class at all
263        else:
[8716]264            color = self.color(OWPalette.Data).getRgb()
[8682]265            symbol = self.curveSymbols[0]
266            for i in range(dataSize):
267                if not validData[i]: continue
268                if self.dataHasClass:
269                    if self.useDifferentSymbols:
270                        symbol = self.curveSymbols[int(self.originalData[self.dataClassIndex][i])]
271                    if useDifferentColors:
272                        color = self.discPalette.getRGB(self.originalData[self.dataClassIndex][i])
273                if not xPointsToAdd.has_key((color, color, symbol, 1)):
274                    xPointsToAdd[(color, color, symbol, 1)] = []
275                    yPointsToAdd[(color, color, symbol, 1)] = []
278
279                self.addAnchorLine(x_positions[i], y_positions[i], XAnchorPositions[i], YAnchorPositions[i], color, i, length)
280                self.addTooltipKey(x_positions[i], y_positions[i], XAnchorPositions[i], YAnchorPositions[i], QColor(*color), i)
281
282        # draw the points
283        for i, (fillColor, edgeColor, symbol, showFilled) in enumerate(xPointsToAdd.keys()):
284            xData = xPointsToAdd[(fillColor, edgeColor, symbol, showFilled)]
285            yData = yPointsToAdd[(fillColor, edgeColor, symbol, showFilled)]
286            self.addCurve(str(i), QColor(*fillColor), QColor(*edgeColor), self.pointWidth, symbol = symbol, xData = xData, yData = yData, showFilledSymbols = showFilled)
287
288        self.showAnchorLines()
291
292        # draw polygon
[8716]293        polygon_color = self.color(OWPalette.Axis)
294        self.addCurve("polygon", polygon_color, polygon_color, 0, OWCurve.Lines, symbol = OWPoint.NoSymbol, xData = list(self.XAnchor) + [self.XAnchor[0]], yData = list(self.YAnchor) + [self.YAnchor[0]], lineWidth = 2)
[8682]295
296        #################
297        # draw the legend
[8716]298        if self.dataHasDiscreteClass:
300            for index, value in enumerate(getVariableValuesSorted(self.dataDomain.classVar)):
301                if useDifferentColors:
302                    color = self.discPalette[index]
303                else:
304                    color = self.color(OWPalette.Data)
305
306                if self.useDifferentSymbols:
307                    curveSymbol = self.curveSymbols[index]
308                else:
309                    curveSymbol = self.curveSymbols[0]
310
311                self.legend().add_item(category, str(value), OWPoint(curveSymbol, color, self.point_width))
[8682]312
[8716]313        # show legend for continuous class
314        elif self.dataHasContinuousClass:
[8682]316
317        self.replot()
318
319
320    def addAnchorLine(self, x, y, xAnchors, yAnchors, color, index, count):
321        for j in range(count):
322            dist = euclDist([x, y], [xAnchors[j] , yAnchors[j]])
323            if dist == 0: continue
324            kvoc = float(self.lineLength * 0.05) / dist
325            lineX1 = x; lineY1 = y
326
327            # we don't make extrapolation
328            if kvoc > 1: lineX2 = lineX1; lineY2 = lineY1
329            else:
330                lineX2 = (1.0 - kvoc)*xAnchors[j] + kvoc * lineX1
331                lineY2 = (1.0 - kvoc)*yAnchors[j] + kvoc * lineY1
332
335
336
337    def showAnchorLines(self):
338        for i, color in enumerate(self.xLinesToAdd.keys()):
340            curve.attach(self)
341
342    # create a dictionary value for the data point
343    # this will enable to show tooltips faster and to make selection of examples available
344    def addTooltipKey(self, x, y, xAnchors, yAnchors, color, index):
345        dictValue = "%.1f-%.1f"%(x, y)
346        if not self.dataMap.has_key(dictValue):
347            self.dataMap[dictValue] = []
348        self.dataMap[dictValue].append((x, y, xAnchors, yAnchors, color, index))
349
350
351    # ##############
352    # draw tooltips
353    def onMouseMoved(self, e):
354        redraw = 0
355        if self.tooltipCurveKeys != [] or self.tooltipMarkers != []: redraw = 1
356
357        for key in self.tooltipCurveKeys:  self.removeCurve(key)
358        for marker in self.tooltipMarkers: self.removeMarker(marker)
359        self.tooltipCurveKeys = []
360        self.tooltipMarkers = []
361
362        # in case we are drawing a rectangle, we don't draw enhanced tooltips
363        # because it would then fail to draw the rectangle
364        if self.mouseCurrentlyPressed:
365            OWPlot.onMouseMoved(self, e)
366            if redraw: self.replot()
367            return
368
369        xFloat = self.invTransform(xBottom, e.x())
370        yFloat = self.invTransform(yLeft, e.y())
371        dictValue = "%.1f-%.1f"%(xFloat, yFloat)
372        if self.dataMap.has_key(dictValue):
373            points = self.dataMap[dictValue]
374            bestDist = 100.0
375            nearestPoint = ()
376            for (x_i, y_i, xAnchors, yAnchors, color, index) in points:
377                currDist = sqrt((xFloat-x_i)*(xFloat-x_i) + (yFloat-y_i)*(yFloat-y_i))
378                if currDist < bestDist:
379                    bestDist = currDist
380                    nearestPoint = (x_i, y_i, xAnchors, yAnchors, color, index)
381
382            (x_i, y_i, xAnchors, yAnchors, color, index) = nearestPoint
383            if self.tooltipKind == LINE_TOOLTIPS and bestDist < 0.05:
384                for i in range(len(self.shownAttributes)):
385
386                    # draw lines
387                    key = self.addCurve("Tooltip curve", color, color, 1, style = OWCurve.Lines, symbol = OWPoint.NoSymbol, xData = [x_i, xAnchors[i]], yData = [y_i, yAnchors[i]])
388                    self.tooltipCurveKeys.append(key)
389
390                    # draw text
391                    marker = None
392                    if self.tooltipValue == TOOLTIPS_SHOW_DATA:
393                        marker = self.addMarker(str(self.rawData[index][self.shownAttributes[i]]), (x_i + xAnchors[i])/2.0, (y_i + yAnchors[i])/2.0, Qt.AlignVCenter | Qt.AlignHCenter, bold = 1)
394                    elif self.tooltipValue == TOOLTIPS_SHOW_SPRINGS:
395                        marker = self.addMarker("%.3f" % (self.scaledData[self.attributeNameIndex[self.shownAttributes[i]]][index]), (x_i + xAnchors[i])/2.0, (y_i + yAnchors[i])/2.0, Qt.AlignVCenter | Qt.AlignHCenter, bold = 1)
396                    font = self.markerFont(marker)
397                    font.setPointSize(12)
398                    self.setMarkerFont(marker, font)
399                    self.tooltipMarkers.append(marker)
400
401            elif self.tooltipKind == VISIBLE_ATTRIBUTES or self.tooltipKind == ALL_ATTRIBUTES:
402                if self.tooltipKind == VISIBLE_ATTRIBUTES: labels = self.shownAttributes
403                else:                                      labels = self.attributeNames
404
405                text = self.getExampleTooltipText(self.rawData[index], labels)
406                self.showTip(self.transform(xBottom, x_i), self.transform(yLeft, y_i), text)
407
408        OWPlot.onMouseMoved(self, e)
409        self.update()
410
411
412    def generateAttrReverseLists(self, attrList, fullAttribList, tempList):
413        if attrList == []: return tempList
414        tempList2 = deepcopy(tempList)
415        index = fullAttribList.index(attrList[0])
416        for list in tempList2: list[index] = 1
417        return self.generateAttrReverseLists(attrList[1:], fullAttribList, tempList + tempList2)
418
419
420    # save projection (xAttr, yAttr, classVal) into a filename fileName
421    def saveProjectionAsTabData(self, fileName, attrList):
422        orange.saveTabDelimited(fileName, self.createProjectionAsExampleTable([self.attributeNameIndex[i] for i in attrList]))
423
424
425    # ####################################
426    # send 2 example tables. in first is the data that is inside selected rects (polygons), in the second is unselected data
427    def getSelectionsAsExampleTables(self, attrList, addProjectedPositions = 0):
428        if not self.haveData: return (None, None)
429        if addProjectedPositions == 0 and not self.selectionCurveList: return (None, self.rawData)       # if no selections exist
430
431        xAttr = orange.FloatVariable("X Positions")
432        yAttr = orange.FloatVariable("Y Positions")
434            domain=orange.Domain([xAttr,yAttr] + [v for v in self.dataDomain.variables])
439        else:
441
443
444        attrIndices = [self.attributeNameIndex[attr] for attr in attrList]
445        validData = self.getValidList(attrIndices)
446
447        array = self.createProjectionAsNumericArray(attrIndices, validData = validData, scaleFactor = self.scaleFactor, removeMissingData = 0)
448        if array == None:       # if all examples have missing values
449            return (None, None)
450
451        #selIndices, unselIndices = self.getSelectionsAsIndices(attrList, validData)
452        selIndices, unselIndices = self.getSelectedPoints(array.T[0], array.T[1], validData)
453
455            selected = orange.ExampleTable(domain, self.rawData.selectref(selIndices))
456            unselected = orange.ExampleTable(domain, self.rawData.selectref(unselIndices))
457            selIndex = 0; unselIndex = 0
458            for i in range(len(selIndices)):
459                if selIndices[i]:
460                    selected[selIndex][xAttr] = array[i][0]
461                    selected[selIndex][yAttr] = array[i][1]
462                    selIndex += 1
463                else:
464                    unselected[unselIndex][xAttr] = array[i][0]
465                    unselected[unselIndex][yAttr] = array[i][1]
466                    unselIndex += 1
467        else:
468            selected = self.rawData.selectref(selIndices)
469            unselected = self.rawData.selectref(unselIndices)
470
471        if len(selected) == 0: selected = None
472        if len(unselected) == 0: unselected = None
473        return (selected, unselected)
474
475
476    def getSelectionsAsIndices(self, attrList, validData = None):
477        if not self.haveData: return [], []
478
479        attrIndices = [self.attributeNameIndex[attr] for attr in attrList]
480        if validData == None:
481            validData = self.getValidList(attrIndices)
482
483        array = self.createProjectionAsNumericArray(attrIndices, validData = validData, scaleFactor = self.scaleFactor, removeMissingData = 0)
484        if array == None:
485            return [], []
486        array = numpy.transpose(array)
487        return self.getSelectedPoints(array[0], array[1], validData)
488
489
490
491if __name__== "__main__":
492    #Draw a simple graph
493    a = QApplication(sys.argv)
494    c = OWPolyvizGraph()
495
496    a.setMainWidget(c)
497    c.show()
498    a.exec_()
Note: See TracBrowser for help on using the repository browser.