source: orange/Orange/OrangeWidgets/Visualize/OWLinProjGraph.py @ 10913:1b281e538a8f

Revision 10913:1b281e538a8f, 40.0 KB checked in by mstajdohar, 23 months ago (diff)

Fixed Radviz jittering. Before, points in the center were scattered all around the plot.

Line 
1from OWGraph import *
2from copy import copy
3import time
4from operator import add
5from math import *
6from orngScaleLinProjData import *
7import orngVisFuncts
8import OWColorPalette
9from OWGraphTools import UnconnectedLinesCurve
10
11# indices in curveData
12SYMBOL = 0
13PENCOLOR = 1
14BRUSHCOLOR = 2
15
16LINE_TOOLTIPS = 0
17VISIBLE_ATTRIBUTES = 1
18ALL_ATTRIBUTES = 2
19
20TOOLTIPS_SHOW_DATA = 0
21TOOLTIPS_SHOW_SPRINGS = 1
22
23###########################################################################################
24##### CLASS : OWLINPROJGRAPH
25###########################################################################################
26class OWLinProjGraph(OWGraph, orngScaleLinProjData):
27    def __init__(self, widget, parent = None, name = "None"):
28        OWGraph.__init__(self, parent, name)
29        orngScaleLinProjData.__init__(self)
30
31        self.totalPossibilities = 0 # a variable used in optimization - tells us the total number of different attribute positions
32        self.triedPossibilities = 0 # how many possibilities did we already try
33        self.p = None
34
35        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])
36        self.tooltipCurves = []
37        self.tooltipMarkers   = []
38        self.widget = widget
39
40        # moving anchors manually
41        self.shownAttributes = []
42        self.selectedAnchorIndex = None
43
44        self.hideRadius = 0
45        self.showAnchors = 1
46        self.showValueLines = 0
47        self.valueLineLength = 5
48
49        self.onlyOnePerSubset = 1
50        self.showLegend = 1
51        self.useDifferentSymbols = 0
52        self.useDifferentColors = 1
53        self.tooltipKind = 0        # index in ["Show line tooltips", "Show visible attributes", "Show all attributes"]
54        self.tooltipValue = 0       # index in ["Tooltips show data values", "Tooltips show spring values"]
55        self.scaleFactor = 1.0
56        self.showAttributeNames = 1
57
58        self.showProbabilities = 0
59        self.squareGranularity = 3
60        self.spaceBetweenCells = 1
61
62        self.showKNN = 0   # widget sets this to 1 or 2 if you want to see correct or wrong classifications
63        self.insideColors = None
64        self.valueLineCurves = [{}, {}]    # dicts for x and y set of coordinates for unconnected lines
65
66        self.enableXaxis(0)
67        self.enableYLaxis(0)
68        self.setAxisScale(QwtPlot.xBottom, -1.13, 1.13, 1)
69        self.setAxisScale(QwtPlot.yLeft, -1.13, 1.13, 1)
70
71    def setData(self, data, subsetData = None, **args):
72        OWGraph.setData(self, data)
73        orngScaleLinProjData.setData(self, data, subsetData, **args)
74        #self.anchorData = []
75
76        self.setAxisScale(QwtPlot.yLeft, -1.13, 1.13, 1)
77        self.setAxisScale(QwtPlot.xBottom, -1.13, 1.13, 1)
78
79        if data and data.domain.classVar and data.domain.classVar.varType == orange.VarTypes.Continuous:
80            self.setAxisScale(QwtPlot.xBottom, -1.13, 1.13 + 0.1, 1)   # if we have a continuous class we need a bit more space on the right to show a color legend
81
82    # ####################################################################
83    # update shown data. Set labels, coloring by className ....
84    def updateData(self, labels = None, setAnchors = 0, **args):
85        self.removeDrawingCurves()  # my function, that doesn't delete selection curves
86        #self.removeCurves()
87        self.removeMarkers()
88        self.tooltipMarkers = []
89
90        self.__dict__.update(args)
91        if labels == None: labels = [anchor[2] for anchor in self.anchorData]
92        self.shownAttributes = labels
93        self.dataMap = {}   # dictionary with keys of form "x_i-y_i" with values (x_i, y_i, color, data)
94        self.valueLineCurves = [{}, {}]    # dicts for x and y set of coordinates for unconnected lines
95
96        if not self.haveData or len(labels) < 3:
97            self.anchorData = []
98            self.updateLayout()
99            return
100
101        if setAnchors or (args.has_key("XAnchors") and args.has_key("YAnchors")):
102            self.potentialsBmp = None
103            self.setAnchors(args.get("XAnchors"), args.get("YAnchors"), labels)
104            #self.anchorData = self.createAnchors(len(labels), labels)    # used for showing tooltips
105
106        indices = [self.attributeNameIndex[anchor[2]] for anchor in self.anchorData]  # store indices to shown attributes
107
108        # do we want to show anchors and their labels
109        if self.showAnchors:
110            if self.hideRadius > 0:
111                xdata = self.createXAnchors(100)*(self.hideRadius / 10)
112                ydata = self.createYAnchors(100)*(self.hideRadius / 10)
113                self.addCurve("hidecircle", QColor(200,200,200), QColor(200,200,200), 1, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = xdata.tolist() + [xdata[0]], yData = ydata.tolist() + [ydata[0]])
114
115            # draw dots at anchors
116            shownAnchorData = filter(lambda p, r=self.hideRadius**2/100: p[0]**2+p[1]**2>r, self.anchorData)
117
118            if not self.normalizeExamples:
119                r=self.hideRadius**2/100
120                for i,(x,y,a) in enumerate(shownAnchorData):
121                    self.addCurve("l%i" % i, QColor(160, 160, 160), QColor(160, 160, 160), 10, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = [0, x], yData = [0, y], showFilledSymbols = 1, lineWidth=2)
122                    if self.showAttributeNames:
123                        self.addMarker(a, x*1.07, y*1.04, Qt.AlignCenter, bold=1)
124            else:
125                XAnchors = [a[0] for a in shownAnchorData]
126                YAnchors = [a[1] for a in shownAnchorData]
127                self.addCurve("dots", QColor(160,160,160), QColor(160,160,160), 10, style = QwtPlotCurve.NoCurve, symbol = QwtSymbol.Ellipse, xData = XAnchors, yData = YAnchors, showFilledSymbols = 1)
128
129                # draw text at anchors
130                if self.showAttributeNames:
131                    for x, y, a in shownAnchorData:
132                        self.addMarker(a, x*1.07, y*1.04, Qt.AlignCenter, bold = 1)
133
134        if self.showAnchors and self.normalizeExamples:
135            # draw "circle"
136            xdata = self.createXAnchors(100)
137            ydata = self.createYAnchors(100)
138            self.addCurve("circle", QColor(Qt.black), QColor(Qt.black), 1, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = xdata.tolist() + [xdata[0]], yData = ydata.tolist() + [ydata[0]])
139
140        self.potentialsClassifier = None # remove the classifier so that repaint won't recompute it
141        self.updateLayout()
142
143        if self.dataHasDiscreteClass:
144            self.discPalette.setNumberOfColors(len(self.dataDomain.classVar.values))
145
146        useDifferentSymbols = self.useDifferentSymbols and self.dataHasDiscreteClass and len(self.dataDomain.classVar.values) < len(self.curveSymbols)
147        dataSize = len(self.rawData)
148        validData = self.getValidList(indices)
149        # jitter point if Radviz
150        transProjData = self.createProjectionAsNumericArray(indices, validData = validData, scaleFactor = self.scaleFactor, normalize = self.normalizeExamples, jitterSize = self.jitter_size if self.normalizeExamples else -1, useAnchorData = 1, removeMissingData = 0)
151        if transProjData == None:
152            return
153        projData = transProjData.T
154        x_positions = projData[0]
155        y_positions = projData[1]
156        xPointsToAdd = {}
157        yPointsToAdd = {}
158
159
160        if self.showProbabilities and self.haveData and self.dataHasClass:
161            # construct potentialsClassifier from unscaled positions
162            domain = orange.Domain([self.dataDomain[i].name for i in indices]+[self.dataDomain.classVar.name], self.dataDomain)
163            offsets = [self.attrValues[self.attributeNames[i]][0] for i in indices]
164            normalizers = [self.getMinMaxVal(i) for i in indices]
165            selectedData = numpy.take(self.originalData, indices, axis = 0)
166            averages = numpy.average(numpy.compress(validData, selectedData, axis=1), 1)
167            classData = numpy.compress(validData, self.originalData[self.dataClassIndex])
168            if classData.any():
169                self.potentialsClassifier = orange.P2NN(domain, numpy.transpose(numpy.array([numpy.compress(validData, self.unscaled_x_positions), numpy.compress(validData, self.unscaled_y_positions), classData])), self.anchorData, offsets, normalizers, averages, self.normalizeExamples, law=1)
170            else:
171                self.potentialsClassifier = None
172            self.potentialsImage = None
173
174
175        # ##############################################################
176        # show model quality
177        # ##############################################################
178        if self.insideColors != None or self.showKNN and self.haveData:
179            # if we want to show knn classifications of the examples then turn the projection into example table and run knn
180            if self.insideColors:
181                insideData, stringData = self.insideColors
182            else:
183                shortData = self.createProjectionAsExampleTable([self.attributeNameIndex[attr] for attr in labels], useAnchorData = 1)
184                predictions, probabilities = self.widget.vizrank.kNNClassifyData(shortData)
185                if self.showKNN == 2: insideData, stringData = [1.0 - val for val in predictions], "Probability of wrong classification = %.2f%%"
186                else:                 insideData, stringData = predictions, "Probability of correct classification = %.2f%%"
187
188            if self.dataHasDiscreteClass:        classColors = self.discPalette
189            elif self.dataHasContinuousClass:    classColors = self.contPalette
190
191            if len(insideData) != len(self.rawData):
192                j = 0
193                for i in range(len(self.rawData)):
194                    if not validData[i]: continue
195                    if self.dataHasClass:
196                        fillColor = classColors.getRGB(self.originalData[self.dataClassIndex][i], 255*insideData[j])
197                        edgeColor = classColors.getRGB(self.originalData[self.dataClassIndex][i])
198                    else:
199                        fillColor = edgeColor = (0,0,0)
200                    self.addCurve(str(i), QColor(*fillColor+ (self.alphaValue,)), QColor(*edgeColor+ (self.alphaValue,)), self.pointWidth, xData = [x_positions[i]], yData = [y_positions[i]])
201                    if self.showValueLines:
202                        self.addValueLineCurve(x_positions[i], y_positions[i], edgeColor, i, indices)
203                    self.addTooltipKey(x_positions[i], y_positions[i], QColor(*edgeColor), i, stringData % (100*insideData[j]))
204                    j+= 1
205            else:
206                for i in range(len(self.rawData)):
207                    if not validData[i]: continue
208                    if self.dataHasClass:
209                        fillColor = classColors.getRGB(self.originalData[self.dataClassIndex][i], 255*insideData[i])
210                        edgeColor = classColors.getRGB(self.originalData[self.dataClassIndex][i])
211                    else:
212                        fillColor = edgeColor = (0,0,0)
213                    self.addCurve(str(i), QColor(*fillColor+ (self.alphaValue,)), QColor(*edgeColor+ (self.alphaValue,)), self.pointWidth, xData = [x_positions[i]], yData = [y_positions[i]])
214                    if self.showValueLines:
215                        self.addValueLineCurve(x_positions[i], y_positions[i], edgeColor, i, indices)
216                    self.addTooltipKey(x_positions[i], y_positions[i], QColor(*edgeColor), i, stringData % (100*insideData[i]))
217
218        # ##############################################################
219        # do we have a subset data to show?
220        # ##############################################################
221        elif self.haveSubsetData:
222            shownSubsetCount = 0
223            subsetIdsToDraw = dict([(example.id,1) for example in self.rawSubsetData])
224            subsetIdsAlreadyDrawn = set()
225            # draw the rawData data set. examples that exist also in the subset data draw full, other empty
226            for i in range(dataSize):
227                if not validData[i]:
228                    continue
229                if subsetIdsToDraw.has_key(self.rawData[i].id):
230                    instance_filled = 1
231                    subsetIdsAlreadyDrawn.add(self.rawData[i].id)
232                else:
233                    instance_filled = 0
234
235                if self.dataHasDiscreteClass and self.useDifferentColors:
236                    newColor = self.discPalette.getRGB(self.originalData[self.dataClassIndex][i])
237                elif self.dataHasContinuousClass and self.useDifferentColors:
238                    newColor = self.contPalette.getRGB(self.noJitteringScaledData[self.dataClassIndex][i])
239                else:
240                    newColor = (0,0,0)
241
242                if self.useDifferentSymbols and self.dataHasDiscreteClass:
243                    curveSymbol = self.curveSymbols[int(self.originalData[self.dataClassIndex][i])]
244                else:
245                    curveSymbol = self.curveSymbols[0]
246
247                if not xPointsToAdd.has_key((newColor, curveSymbol, instance_filled)):
248                    xPointsToAdd[(newColor, curveSymbol, instance_filled)] = []
249                    yPointsToAdd[(newColor, curveSymbol, instance_filled)] = []
250                xPointsToAdd[(newColor, curveSymbol, instance_filled)].append(x_positions[i])
251                yPointsToAdd[(newColor, curveSymbol, instance_filled)].append(y_positions[i])
252                if self.showValueLines:
253                    self.addValueLineCurve(x_positions[i], y_positions[i], newColor, i, indices)
254
255                self.addTooltipKey(x_positions[i], y_positions[i], QColor(*newColor), i)
256
257            # if we have a data subset that contains examples that don't exist in the original dataset we show them here
258            XAnchors = numpy.array([val[0] for val in self.anchorData])
259            YAnchors = numpy.array([val[1] for val in self.anchorData])
260            anchorRadius = numpy.sqrt(XAnchors*XAnchors + YAnchors*YAnchors)
261            validSubData = self.getValidSubsetList(indices)
262            # jitter points if Radviz
263            projSubData = self.createProjectionAsNumericArray(indices, validData = validSubData, scaleFactor = self.scaleFactor, normalize = self.normalizeExamples, jitterSize = self.jitter_size if self.normalizeExamples else -1, useAnchorData = 1, removeMissingData = 0, useSubsetData = 1).T
264            sub_x_positions = projSubData[0]
265            sub_y_positions = projSubData[1]
266
267            for i in range(len(self.rawSubsetData)):
268                if not validSubData[i]: # check if has missing values
269                    continue
270                if self.rawSubsetData[i].id in subsetIdsAlreadyDrawn:
271                    continue
272
273                if not self.dataHasClass or self.rawSubsetData[i].getclass().isSpecial():
274                    newColor = (0,0,0)
275                else:
276                    if self.dataHasDiscreteClass:
277                        newColor = self.discPalette.getRGB(self.originalSubsetData[self.dataClassIndex][i])
278                    else:
279                        newColor = self.contPalette.getRGB(self.noJitteringScaledSubsetData[self.dataClassIndex][i])
280
281                if self.useDifferentSymbols and self.dataHasDiscreteClass and self.validSubsetDataArray[self.dataClassIndex][i]:
282                    curveSymbol = self.curveSymbols[int(self.originalSubsetData[self.dataClassIndex][i])]
283                else:
284                    curveSymbol = self.curveSymbols[0]
285
286                if not xPointsToAdd.has_key((newColor, curveSymbol, 1)):
287                    xPointsToAdd[(newColor, curveSymbol, 1)] = []
288                    yPointsToAdd[(newColor, curveSymbol, 1)] = []
289                xPointsToAdd[(newColor, curveSymbol, 1)].append(sub_x_positions[i])
290                yPointsToAdd[(newColor, curveSymbol, 1)].append(sub_y_positions[i])
291
292        elif not self.dataHasClass:
293            xs = []; ys = []
294            for i in range(dataSize):
295                if not validData[i]: continue
296                xs.append(x_positions[i])
297                ys.append(y_positions[i])
298                self.addTooltipKey(x_positions[i], y_positions[i], QColor(Qt.black), i)
299                if self.showValueLines:
300                    self.addValueLineCurve(x_positions[i], y_positions[i], (0,0,0), i, indices)
301            self.addCurve(str(1), QColor(0,0,0,self.alphaValue), QColor(0,0,0,self.alphaValue), self.pointWidth, symbol = self.curveSymbols[0], xData = xs, yData = ys, penAlpha = self.alphaValue, brushAlpha = self.alphaValue)
302
303        # ##############################################################
304        # CONTINUOUS class
305        # ##############################################################
306        elif self.dataHasContinuousClass:
307            for i in range(dataSize):
308                if not validData[i]: continue
309                newColor = self.contPalette.getRGB(self.noJitteringScaledData[self.dataClassIndex][i])
310                self.addCurve(str(i), QColor(*newColor+ (self.alphaValue,)), QColor(*newColor+ (self.alphaValue,)), self.pointWidth, symbol = QwtSymbol.Ellipse, xData = [x_positions[i]], yData = [y_positions[i]])
311                if self.showValueLines:
312                    self.addValueLineCurve(x_positions[i], y_positions[i], newColor, i, indices)
313                self.addTooltipKey(x_positions[i], y_positions[i], QColor(*newColor), i)
314
315        # ##############################################################
316        # DISCRETE class
317        # ##############################################################
318        elif self.dataHasDiscreteClass:
319            for i in range(dataSize):
320                if not validData[i]: continue
321                if self.useDifferentColors: newColor = self.discPalette.getRGB(self.originalData[self.dataClassIndex][i])
322                else:                       newColor = (0,0,0)
323                if self.useDifferentSymbols: curveSymbol = self.curveSymbols[int(self.originalData[self.dataClassIndex][i])]
324                else:                        curveSymbol = self.curveSymbols[0]
325                if not xPointsToAdd.has_key((newColor, curveSymbol, self.showFilledSymbols)):
326                    xPointsToAdd[(newColor, curveSymbol, self.showFilledSymbols)] = []
327                    yPointsToAdd[(newColor, curveSymbol, self.showFilledSymbols)] = []
328                xPointsToAdd[(newColor, curveSymbol, self.showFilledSymbols)].append(x_positions[i])
329                yPointsToAdd[(newColor, curveSymbol, self.showFilledSymbols)].append(y_positions[i])
330                if self.showValueLines:
331                    self.addValueLineCurve(x_positions[i], y_positions[i], newColor, i, indices)
332                self.addTooltipKey(x_positions[i], y_positions[i], QColor(*newColor), i)
333
334        # first draw value lines
335        if self.showValueLines:
336            for i, color in enumerate(self.valueLineCurves[0].keys()):
337                curve = UnconnectedLinesCurve("", QPen(QColor(*color + (self.alphaValue,))), self.valueLineCurves[0][color], self.valueLineCurves[1][color])
338                curve.attach(self)
339
340        # draw all the points with a small number of curves
341        for i, (color, symbol, showFilled) in enumerate(xPointsToAdd.keys()):
342            xData = xPointsToAdd[(color, symbol, showFilled)]
343            yData = yPointsToAdd[(color, symbol, showFilled)]
344            self.addCurve(str(i), QColor(*color + (self.alphaValue,)), QColor(*color + (self.alphaValue,)), self.pointWidth, symbol = symbol, xData = xData, yData = yData, showFilledSymbols = showFilled)
345
346        # ##############################################################
347        # draw the legend
348        # ##############################################################
349        if self.showLegend:
350            # show legend for discrete class
351            if self.dataHasDiscreteClass:
352                self.addMarker(self.dataDomain.classVar.name, 0.87, 1.05, Qt.AlignLeft | Qt.AlignVCenter)
353
354                classVariableValues = getVariableValuesSorted(self.dataDomain.classVar)
355                for index in range(len(classVariableValues)):
356                    if self.useDifferentColors: color = QColor(self.discPalette[index])
357                    else:                       color = QColor(Qt.black)
358                    y = 1.0 - index * 0.05
359
360                    if not self.useDifferentSymbols:  curveSymbol = self.curveSymbols[0]
361                    else:                             curveSymbol = self.curveSymbols[index]
362
363                    self.addCurve(str(index), color, color, self.pointWidth, symbol = curveSymbol, xData = [0.95], yData = [y], penAlpha = self.alphaValue, brushAlpha = self.alphaValue)
364                    self.addMarker(classVariableValues[index], 0.90, y, Qt.AlignLeft | Qt.AlignVCenter)
365            # show legend for continuous class
366            elif self.dataHasContinuousClass:
367                xs = [1.15, 1.20, 1.20, 1.15]
368                count = 200
369                height = 2 / float(count)
370                for i in range(count):
371                    y = -1.0 + i*2.0/float(count)
372                    col = self.contPalette[i/float(count)]
373                    col.setAlpha(self.alphaValue)
374                    PolygonCurve(QPen(col), QBrush(col), xData = xs, yData = [y,y, y+height, y+height]).attach(self)
375
376                # add markers for min and max value of color attribute
377                [minVal, maxVal] = self.attrValues[self.dataDomain.classVar.name]
378                self.addMarker("%s = %%.%df" % (self.dataDomain.classVar.name, self.dataDomain.classVar.numberOfDecimals) % (minVal), xs[0] - 0.02, -1.0 + 0.04, Qt.AlignLeft)
379                self.addMarker("%s = %%.%df" % (self.dataDomain.classVar.name, self.dataDomain.classVar.numberOfDecimals) % (maxVal), xs[0] - 0.02, +1.0 - 0.04, Qt.AlignLeft)
380
381        self.replot()
382
383
384    # ##############################################################
385    # create a dictionary value for the data point
386    # this will enable to show tooltips faster and to make selection of examples available
387    def addTooltipKey(self, x, y, color, index, extraString = None):
388        dictValue = "%.1f-%.1f"%(x, y)
389        if not self.dataMap.has_key(dictValue): self.dataMap[dictValue] = []
390        self.dataMap[dictValue].append((x, y, color, index, extraString))
391
392
393    def addValueLineCurve(self, x, y, color, exampleIndex, attrIndices):
394        XAnchors = numpy.array([val[0] for val in self.anchorData])
395        YAnchors = numpy.array([val[1] for val in self.anchorData])
396        xs = numpy.array([x] * len(self.anchorData))
397        ys = numpy.array([y] * len(self.anchorData))
398        dists = numpy.sqrt((XAnchors-xs)**2 + (YAnchors-ys)**2)
399        xVect = 0.01 * self.valueLineLength * (XAnchors - xs) / dists
400        yVect = 0.01 * self.valueLineLength * (YAnchors - ys) / dists
401        exVals = [self.noJitteringScaledData[attrInd, exampleIndex] for attrInd in attrIndices]
402
403        xs = []; ys = []
404        for i in range(len(exVals)):
405            xs += [x, x + xVect[i]*exVals[i]]
406            ys += [y, y + yVect[i]*exVals[i]]
407        self.valueLineCurves[0][color] = self.valueLineCurves[0].get(color, []) + xs
408        self.valueLineCurves[1][color] = self.valueLineCurves[1].get(color, []) + ys
409
410
411    def mousePressEvent(self, e):
412        if self.manualPositioning:
413            self.mouseCurrentlyPressed = 1
414            self.selectedAnchorIndex = None
415            if not self.normalizeExamples:
416                marker, dist = self.closestMarker(e.x(), e.y())
417                if dist < 15:
418                    self.selectedAnchorIndex = self.shownAttributes.index(str(marker.label().text()))
419            else:
420                (curve, dist, x, y, index) = self.closestCurve(e.x(), e.y())
421                if dist < 5 and str(curve.title().text()) == "dots":
422                    self.selectedAnchorIndex = index
423        else:
424            OWGraph.mousePressEvent(self, e)
425
426
427    def mouseReleaseEvent(self, e):
428        if self.manualPositioning:
429            self.mouseCurrentlyPressed = 0
430            self.selectedAnchorIndex = None
431        else:
432            OWGraph.mouseReleaseEvent(self, e)
433
434    # ##############################################################
435    # draw tooltips
436    def mouseMoveEvent(self, e):
437        redraw = (self.tooltipCurves != [] or self.tooltipMarkers != [])
438
439        for curve in self.tooltipCurves:  curve.detach()
440        for marker in self.tooltipMarkers: marker.detach()
441        self.tooltipCurves = []
442        self.tooltipMarkers = []
443
444        canvasPos = self.canvas().mapFrom(self, e.pos())
445        xFloat = self.invTransform(QwtPlot.xBottom, canvasPos.x())
446        yFloat = self.invTransform(QwtPlot.yLeft, canvasPos.y())
447
448        # in case we are drawing a rectangle, we don't draw enhanced tooltips
449        # because it would then fail to draw the rectangle
450        if self.mouseCurrentlyPressed:
451            if not self.manualPositioning:
452                OWGraph.mouseMoveEvent(self, e)
453                if redraw: self.replot()
454            else:
455                if self.selectedAnchorIndex != None:
456                    if self.widget.freeVizDlg.restrain == 1:
457                        rad = sqrt(xFloat**2 + yFloat**2)
458                        xFloat /= rad
459                        yFloat /= rad
460                    elif self.widget.freeVizDlg.restrain == 2:
461                        rad = sqrt(xFloat**2 + yFloat**2)
462                        phi = 2 * self.selectedAnchorIndex * math.pi / len(self.anchorData)
463                        xFloat = rad * cos(phi)
464                        yFloat = rad * sin(phi)
465                    self.anchorData[self.selectedAnchorIndex] = (xFloat, yFloat, self.anchorData[self.selectedAnchorIndex][2])
466                    self.updateData(self.shownAttributes)
467                    self.replot()
468                    #self.widget.recomputeEnergy()
469            return
470
471        dictValue = "%.1f-%.1f"%(xFloat, yFloat)
472        if self.dataMap.has_key(dictValue):
473            points = self.dataMap[dictValue]
474            bestDist = 100.0
475            for (x_i, y_i, color, index, extraString) in points:
476                currDist = sqrt((xFloat-x_i)*(xFloat-x_i) + (yFloat-y_i)*(yFloat-y_i))
477                if currDist < bestDist:
478                    bestDist = currDist
479                    nearestPoint = (x_i, y_i, color, index, extraString)
480
481            (x_i, y_i, color, index, extraString) = nearestPoint
482            intX = self.transform(QwtPlot.xBottom, x_i)
483            intY = self.transform(QwtPlot.yLeft, y_i)
484
485            if self.tooltipKind == LINE_TOOLTIPS and bestDist < 0.05:
486                shownAnchorData = filter(lambda p, r=self.hideRadius**2/100: p[0]**2+p[1]**2>r, self.anchorData)
487                if not self.normalizeExamples:
488                    for (xAnchor,yAnchor,label) in shownAnchorData:
489                        attrVal = self.scaledData[self.attributeNameIndex[label]][index]
490                        markerX, markerY = xAnchor*(attrVal+0.03), yAnchor*(attrVal+0.03)
491                        curve = self.addCurve("", color, color, 1, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = [0, xAnchor*attrVal], yData = [0, yAnchor*attrVal], lineWidth=3)
492
493                        marker = None
494                        fontsize = 9
495                        markerAlign = Qt.AlignCenter
496                        self.tooltipCurves.append(curve)
497                        labelIndex = self.attributeNameIndex[label]
498                        if self.tooltipValue == TOOLTIPS_SHOW_DATA:
499                            if self.dataDomain[labelIndex].varType == orange.VarTypes.Continuous:
500                                text = "%%.%df" % (self.dataDomain[labelIndex].numberOfDecimals) % (self.rawData[index][labelIndex])
501                            else:
502                                text = str(self.rawData[index][labelIndex].value)
503                            marker = self.addMarker(text, markerX, markerY, markerAlign, size = fontsize)
504                        elif self.tooltipValue == TOOLTIPS_SHOW_SPRINGS:
505                            marker = self.addMarker("%.3f" % (self.scaledData[labelIndex][index]), markerX, markerY, markerAlign, size = fontsize)
506                        self.tooltipMarkers.append(marker)
507
508            elif self.tooltipKind == VISIBLE_ATTRIBUTES or self.tooltipKind == ALL_ATTRIBUTES:
509                if self.tooltipKind == VISIBLE_ATTRIBUTES:
510                    shownAnchorData = filter(lambda p, r=self.hideRadius**2/100: p[0]**2+p[1]**2>r, self.anchorData)
511                    labels = [s for (xA, yA, s) in shownAnchorData]
512                else:
513                    labels = []
514
515                text = self.getExampleTooltipText(self.rawData[index], labels)
516                text += "<hr>Example index = %d" % (index)
517                if extraString:
518                    text += "<hr>" + extraString
519                self.showTip(intX, intY, text)
520
521        OWGraph.mouseMoveEvent(self, e)
522        self.replot()
523
524
525    # send 2 example tables. in first is the data that is inside selected rects (polygons), in the second is unselected data
526    def getSelectionsAsExampleTables(self, attrList, useAnchorData = 1, addProjectedPositions = 0):
527        if not self.haveData: return (None, None)
528        if addProjectedPositions == 0 and not self.selectionCurveList: return (None, self.rawData)       # if no selections exist
529        if (useAnchorData and len(self.anchorData) < 3) or len(attrList) < 3: return (None, None)
530
531        xAttr=orange.FloatVariable("X Positions")
532        yAttr=orange.FloatVariable("Y Positions")
533        if addProjectedPositions == 1:
534            domain=orange.Domain([xAttr,yAttr] + [v for v in self.dataDomain.variables])
535        elif addProjectedPositions == 2:
536            domain=orange.Domain(self.dataDomain)
537            domain.addmeta(orange.newmetaid(), xAttr)
538            domain.addmeta(orange.newmetaid(), yAttr)
539        else:
540            domain = orange.Domain(self.dataDomain)
541
542        domain.addmetas(self.dataDomain.getmetas())
543
544        if useAnchorData: indices = [self.attributeNameIndex[val[2]] for val in self.anchorData]
545        else:             indices = [self.attributeNameIndex[label] for label in attrList]
546        validData = self.getValidList(indices)
547        if len(validData) == 0: return (None, None)
548
549        array = self.createProjectionAsNumericArray(attrList, scaleFactor = self.scaleFactor, useAnchorData = useAnchorData, removeMissingData = 0)
550        if array == None:       # if all examples have missing values
551            return (None, None)
552
553        #selIndices, unselIndices = self.getSelectionsAsIndices(attrList, useAnchorData, validData)
554        selIndices, unselIndices = self.getSelectedPoints(array.T[0], array.T[1], validData)
555
556        if addProjectedPositions:
557            selected = orange.ExampleTable(domain, self.rawData.selectref(selIndices))
558            unselected = orange.ExampleTable(domain, self.rawData.selectref(unselIndices))
559            selIndex = 0; unselIndex = 0
560            for i in range(len(selIndices)):
561                if selIndices[i]:
562                    selected[selIndex][xAttr] = array[i][0]
563                    selected[selIndex][yAttr] = array[i][1]
564                    selIndex += 1
565                else:
566                    unselected[unselIndex][xAttr] = array[i][0]
567                    unselected[unselIndex][yAttr] = array[i][1]
568                    unselIndex += 1
569        else:
570            selected = self.rawData.selectref(selIndices)
571            unselected = self.rawData.selectref(unselIndices)
572
573        if len(selected) == 0: selected = None
574        if len(unselected) == 0: unselected = None
575        return (selected, unselected)
576
577
578    def getSelectionsAsIndices(self, attrList, useAnchorData = 1, validData = None):
579        if not self.haveData: return [], []
580
581        attrIndices = [self.attributeNameIndex[attr] for attr in attrList]
582        if validData == None:
583            validData = self.getValidList(attrIndices)
584
585        array = self.createProjectionAsNumericArray(attrList, scaleFactor = self.scaleFactor, useAnchorData = useAnchorData, removeMissingData = 0)
586        if array == None:
587            return [], []
588        array = numpy.transpose(array)
589        return self.getSelectedPoints(array[0], array[1], validData)
590
591
592    # update shown data. Set labels, coloring by className ....
593    def savePicTeX(self):
594        lastSave = getattr(self, "lastPicTeXSave", "C:\\")
595        qfileName = QFileDialog.getSaveFileName(None, "Save to..", lastSave + "graph.pictex","PicTeX (*.pictex);;All files (*.*)")
596        fileName = unicode(qfileName)
597        if fileName == "":
598            return
599
600        if not os.path.splitext(fileName)[1][1:]:
601            fileName = fileName + ".pictex"
602
603        self.lastSave = os.path.split(fileName)[0]+"/"
604        file = open(fileName, "wt")
605
606        file.write("\\mbox{\n")
607        file.write(\\beginpicture\n")
608        file.write(\\setcoordinatesystem units <0.4\columnwidth, 0.4\columnwidth>\n")
609        file.write(\\setplotarea x from -1.1 to 1.1, y from -1 to 1.1\n")
610
611        if not self.normalizeExamples:
612            file.write("\\circulararc 360 degrees from 1 0 center at 0 0\n")
613
614        if self.showAnchors:
615            if self.hideRadius > 0:
616                file.write("\\setdashes\n")
617                file.write("\\circulararc 360 degrees from %5.3f 0 center at 0 0\n" % (self.hideRadius/10.))
618                file.write("\\setsolid\n")
619
620            if self.showAttributeNames:
621                shownAnchorData = filter(lambda p, r=self.hideRadius**2/100: p[0]**2+p[1]**2>r, self.anchorData)
622                if not self.normalizeExamples:
623                    for x,y,l in shownAnchorData:
624                        file.write("\\plot 0 0 %5.3f %5.3f /\n" % (x, y))
625                        file.write("\\put {{\\footnotesize %s}} [b] at %5.3f %5.3f\n" % (l.replace("_", "-"), x*1.07, y*1.04))
626                else:
627                    file.write("\\multiput {\\small $\\odot$} at %s /\n" % (" ".join(["%5.3f %5.3f" % tuple(i[:2]) for i in shownAnchorData])))
628                    for x,y,l in shownAnchorData:
629                        file.write("\\put {{\\footnotesize %s}} [b] at %5.3f %5.3f\n" % (l.replace("_", "-"), x*1.07, y*1.04))
630
631        symbols = ("{\\small $\\circ$}", "{\\tiny $\\times$}", "{\\tiny $+$}", "{\\small $\\star$}",
632                   "{\\small $\\ast$}", "{\\tiny $\\div$}", "{\\small $\\bullet$}", ) + tuple([chr(x) for x in range(97, 123)])
633        dataSize = len(self.rawData)
634        labels = self.widget.getShownAttributeList()
635        indices = [self.attributeNameIndex[label] for label in labels]
636        selectedData = numpy.take(self.scaledData, indices, axis = 0)
637        XAnchors = numpy.array([a[0] for a in self.anchorData])
638        YAnchors = numpy.array([a[1] for a in self.anchorData])
639
640        r = numpy.sqrt(XAnchors*XAnchors + YAnchors*YAnchors)     # compute the distance of each anchor from the center of the circle
641        XAnchors *= r                                               # we need to normalize the anchors by r, otherwise the anchors won't attract points less if they are placed at the center of the circle
642        YAnchors *= r
643
644        x_positions = numpy.dot(XAnchors, selectedData)
645        y_positions = numpy.dot(YAnchors, selectedData)
646
647        if self.normalizeExamples:
648            sum_i = self._getSum_i(selectedData, useAnchorData = 1, anchorRadius = r)
649            x_positions /= sum_i
650            y_positions /= sum_i
651
652        if self.scaleFactor:
653            self.trueScaleFactor = self.scaleFactor
654        else:
655            abss = x_positions*x_positions + y_positions*y_positions
656            self.trueScaleFactor =  1 / sqrt(abss[numpy.argmax(abss)])
657
658        x_positions *= self.trueScaleFactor
659        y_positions *= self.trueScaleFactor
660
661        validData = self.getValidList(indices)
662
663        pos = [[] for i in range(len(self.dataDomain.classVar.values))]
664        for i in range(dataSize):
665            if validData[i]:
666                pos[int(self.originalData[self.dataClassIndex][i])].append((x_positions[i], y_positions[i]))
667
668        for i in range(len(self.dataDomain.classVar.values)):
669            file.write("\\multiput {%s} at %s /\n" % (symbols[i], " ".join(["%5.3f %5.3f" % p for p in pos[i]])))
670
671        if self.showLegend:
672            classVariableValues = getVariableValuesSorted(self.dataDomain.classVar)
673            file.write("\\put {%s} [lB] at 0.87 1.06\n" % self.dataDomain.classVar.name)
674            for index in range(len(classVariableValues)):
675                file.write("\\put {%s} at 1.0 %5.3f\n" % (symbols[index], 0.93 - 0.115*index))
676                file.write("\\put {%s} [lB] at 1.05 %5.3f\n" % (classVariableValues[index], 0.9 - 0.115*index))
677
678        file.write("\\endpicture\n}\n")
679        file.close()
680
681    def computePotentials(self):
682        import orangeom
683        #rx = self.transform(QwtPlot.xBottom, 1) - self.transform(QwtPlot.xBottom, 0)
684        #ry = self.transform(QwtPlot.yLeft, 0) - self.transform(QwtPlot.yLeft, 1)
685
686        rx = self.transform(QwtPlot.xBottom, 1) - self.transform(QwtPlot.xBottom, -1)
687        ry = self.transform(QwtPlot.yLeft, -1) - self.transform(QwtPlot.yLeft, 1)
688        ox = self.transform(QwtPlot.xBottom, 0) - self.transform(QwtPlot.xBottom, -1)
689        oy = self.transform(QwtPlot.yLeft, -1) - self.transform(QwtPlot.yLeft, 0)
690
691        rx -= rx % self.squareGranularity
692        ry -= ry % self.squareGranularity
693
694        if not getattr(self, "potentialsImage", None) \
695           or getattr(self, "potentialContext", None) != (rx, ry, self.shownAttributes, self.trueScaleFactor, self.squareGranularity, self.jitterSize, self.jitterContinuous, self.spaceBetweenCells):
696            if self.potentialsClassifier.classVar.varType == orange.VarTypes.Continuous:
697                imagebmp = orangeom.potentialsBitmap(self.potentialsClassifier, rx, ry, ox, oy, self.squareGranularity, self.trueScaleFactor/2, self.spaceBetweenCells)
698                palette = [qRgb(255.*i/255., 255.*i/255., 255-(255.*i/255.)) for i in range(255)] + [qRgb(255, 255, 255)]
699            else:
700                imagebmp, nShades = orangeom.potentialsBitmap(self.potentialsClassifier, rx, ry, ox, oy, self.squareGranularity, self.trueScaleFactor/2, self.spaceBetweenCells) # the last argument is self.trueScaleFactor (in LinProjGraph...)
701                palette = []
702                sortedClasses = getVariableValuesSorted(self.potentialsClassifier.domain.classVar)
703                for cls in self.potentialsClassifier.classVar.values:
704                    color = self.discPalette.getRGB(sortedClasses.index(cls))
705                    towhite = [255-c for c in color]
706                    for s in range(nShades):
707                        si = 1-float(s)/nShades
708                        palette.append(qRgb(*tuple([color[i]+towhite[i]*si for i in (0, 1, 2)])))
709                palette.extend([qRgb(255, 255, 255) for i in range(256-len(palette))])
710
711            self.potentialsImage = QImage(imagebmp, rx, ry, QImage.Format_Indexed8)
712            self.potentialsImage.setColorTable(OWColorPalette.signedPalette(palette) if qVersion() < "4.5" else palette)
713            self.potentialsImage.setNumColors(256)
714            self.potentialContext = (rx, ry, self.shownAttributes, self.trueScaleFactor, self.squareGranularity, self.jitterSize, self.jitterContinuous, self.spaceBetweenCells)
715            self.potentialsImageFromClassifier = self.potentialsClassifier
716
717
718
719    def drawCanvas(self, painter):
720        if self.showProbabilities and getattr(self, "potentialsClassifier", None):
721            if not (self.potentialsClassifier is getattr(self, "potentialsImageFromClassifier", None)):
722                self.computePotentials()
723            target = QRectF(self.transform(QwtPlot.xBottom, -1), self.transform(QwtPlot.yLeft, 1),
724                            self.transform(QwtPlot.xBottom, 1) - self.transform(QwtPlot.xBottom, -1),
725                            self.transform(QwtPlot.yLeft, -1) - self.transform(QwtPlot.yLeft, 1))
726            source = QRectF(0, 0, self.potentialsImage.size().width(), self.potentialsImage.size().height())
727            painter.drawImage(target, self.potentialsImage, source)
728#            painter.drawImage(self.transform(QwtPlot.xBottom, -1), self.transform(QwtPlot.yLeft, 1), self.potentialsImage)
729        OWGraph.drawCanvas(self, painter)
730
731OWLinProjGraph = graph_deprecator(OWLinProjGraph)
732
733if __name__== "__main__":
734    #Draw a simple graph
735    import os
736    a = QApplication(sys.argv)
737    graph = OWLinProjGraph(None)
738    data = orange.ExampleTable(r"E:\Development\Orange Datasets\UCI\wine.tab")
739    graph.setData(data)
740    graph.updateData([attr.name for attr in data.domain.attributes])
741    graph.show()
742    a.exec_()
Note: See TracBrowser for help on using the repository browser.