source: orange/orange/OrangeWidgets/Visualize/OWParallelGraph.py @ 8767:62dad3ebb688

Revision 8767:62dad3ebb688, 30.9 KB checked in by ales_erjavec <ales.erjavec@…>, 3 years ago (diff)

Add space for distribution bars on the right most column if needed.
Fixes #928.

Line 
1#
2# OWParallelGraph.py
3#
4import orngEnviron
5from OWGraph import *
6#from OWDistributions import *
7from orngScaleData import *
8from statc import pearsonr
9
10NO_STATISTICS = 0
11MEANS  = 1
12MEDIAN = 2
13
14class OWParallelGraph(OWGraph, orngScaleData):
15    def __init__(self, parallelDlg, parent = None, name = None):
16        OWGraph.__init__(self, parent, name)
17        orngScaleData.__init__(self)
18
19        self.parallelDlg = parallelDlg
20        self.showDistributions = 0
21        self.toolRects = []
22        self.useSplines = 0
23        self.showStatistics = 0
24        self.lastSelectedCurve = None
25        self.enabledLegend = 0
26        self.enableGridXB(0)
27        self.enableGridYL(0)
28        self.domainContingency = None
29        self.alphaValue2 = 150
30        self.autoUpdateAxes = 1
31        self.oldLegendKeys = []
32        self.selectionConditions = {}
33        self.visualizedAttributes = []
34        self.visualizedMidLabels = []
35        self.selectedExamples = []
36        self.unselectedExamples = []
37        self.bottomPixmap = QPixmap(os.path.join(orngEnviron.directoryNames["widgetDir"], "icons/upgreenarrow.png"))
38        self.topPixmap = QPixmap(os.path.join(orngEnviron.directoryNames["widgetDir"], "icons/downgreenarrow.png"))
39
40        self.axisScaleDraw(QwtPlot.xBottom).enableComponent(QwtScaleDraw.Backbone, 0)
41        self.axisScaleDraw(QwtPlot.xBottom).enableComponent(QwtScaleDraw.Ticks, 0)
42        self.axisScaleDraw(QwtPlot.yLeft).enableComponent(QwtScaleDraw.Backbone, 0)
43        self.axisScaleDraw(QwtPlot.yLeft).enableComponent(QwtScaleDraw.Ticks, 0)
44
45    def setData(self, data, subsetData = None, **args):
46        OWGraph.setData(self, data)
47        orngScaleData.setData(self, data, subsetData, **args)
48        self.domainContingency = None
49       
50
51
52    # update shown data. Set attributes, coloring by className ....
53    def updateData(self, attributes, midLabels = None, updateAxisScale = 1):
54        self.removeDrawingCurves(removeLegendItems = 0, removeMarkers = 1)  # don't delete legend items
55        if attributes != self.visualizedAttributes:
56            self.selectionConditions = {}       # reset selections
57
58        self.visualizedAttributes = []
59        self.visualizedMidLabels = []
60        self.selectedExamples = []
61        self.unselectedExamples = []
62
63        if not (self.haveData or self.haveSubsetData):  return
64        if len(attributes) < 2: return
65
66        self.visualizedAttributes = attributes
67        self.visualizedMidLabels = midLabels
68        for name in self.selectionConditions.keys():        # keep only conditions that are related to the currently visualized attributes
69            if name not in self.visualizedAttributes:
70                self.selectionConditions.pop(name)
71
72        # set the limits for panning
73        self.xPanningInfo = (1, 0, len(attributes)-1)
74        self.yPanningInfo = (0, 0, 0)   # we don't enable panning in y direction so it doesn't matter what values we put in for the limits
75
76        if updateAxisScale:
77            if self.showAttrValues: self.setAxisScale(QwtPlot.yLeft, -0.04, 1.04, 1)
78            else:                   self.setAxisScale(QwtPlot.yLeft, -0.02, 1.02, 1)
79
80            if self.autoUpdateAxes:
81                if attributes and isinstance(self.dataDomain[attributes[-1]], orange.EnumVariable):
82                    self.setAxisScale(QwtPlot.xBottom, -0.1, len(attributes) - 0.4, 1)
83                else:
84                    self.setAxisScale(QwtPlot.xBottom, -0.1, len(attributes) - 0.9, 1)
85            else:
86                m = self.axisScaleDiv(QwtPlot.xBottom).interval().minValue()
87                M = self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue()
88                if m < 0 or M > len(attributes) - 2:
89                    self.setAxisScale(QwtPlot.xBottom, 0, len(attributes)-1, 1)
90
91        self.setAxisScaleDraw(QwtPlot.xBottom, DiscreteAxisScaleDraw([self.getAttributeLabel(attr) for attr in attributes]))
92        #self.setAxisScaleDraw(QwtPlot.yLeft, HiddenScaleDraw())
93        self.setAxisMaxMajor(QwtPlot.xBottom, len(attributes))
94        self.setAxisMaxMinor(QwtPlot.xBottom, 0)
95
96        length = len(attributes)
97        indices = [self.attributeNameIndex[label] for label in attributes]
98
99        xs = range(length)
100        dataSize = len(self.scaledData[0])
101
102        if self.dataHasDiscreteClass:
103            self.discPalette.setNumberOfColors(len(self.dataDomain.classVar.values))
104
105
106        # ############################################
107        # draw the data
108        # ############################################
109        subsetIdsToDraw = self.haveSubsetData and dict([(self.rawSubsetData[i].id, 1) for i in self.getValidSubsetIndices(indices)]) or {}
110        validData = self.getValidList(indices)
111        mainCurves = {}
112        subCurves = {}
113        conditions = dict([(name, attributes.index(name)) for name in self.selectionConditions.keys()])
114
115        for i in range(dataSize):
116            if not validData[i]:
117                continue
118
119            if not self.dataHasClass:
120                newColor = (0,0,0)
121            elif self.dataHasContinuousClass:
122                newColor = self.contPalette.getRGB(self.noJitteringScaledData[self.dataClassIndex][i])
123            else:
124                newColor = self.discPalette.getRGB(self.originalData[self.dataClassIndex][i])
125
126            data = [self.scaledData[index][i] for index in indices]
127
128            # if we have selected some conditions and the example does not match it we show it as a subset data
129            if 0 in [data[index] >= self.selectionConditions[name][0] and data[index] <= self.selectionConditions[name][1] for (name, index) in conditions.items()]:
130                alpha = self.alphaValue2
131                curves = subCurves
132                self.unselectedExamples.append(i)
133            # if we have subset data then use alpha2 for main data and alpha for subset data
134            elif self.haveSubsetData and not subsetIdsToDraw.has_key(self.rawData[i].id):
135                alpha = self.alphaValue2
136                curves = subCurves
137                self.unselectedExamples.append(i)
138            else:
139                alpha = self.alphaValue
140                curves = mainCurves
141                self.selectedExamples.append(i)
142                if subsetIdsToDraw.has_key(self.rawData[i].id):
143                    subsetIdsToDraw.pop(self.rawData[i].id)
144
145            newColor += (alpha,)
146
147            if not curves.has_key(newColor):
148                curves[newColor] = []
149            curves[newColor].extend(data)
150
151        # if we have a data subset that contains examples that don't exist in the original dataset we show them here
152        if subsetIdsToDraw != {}:
153            validSubsetData = self.getValidSubsetList(indices)
154
155            for i in range(len(self.rawSubsetData)):
156                if not validSubsetData[i]: continue
157                if not subsetIdsToDraw.has_key(self.rawSubsetData[i].id): continue
158
159                data = [self.scaledSubsetData[index][i] for index in indices]
160                if not self.dataDomain.classVar or self.rawSubsetData[i].getclass().isSpecial():
161                    newColor = (0,0,0)
162                elif self.dataHasContinuousClass:
163                    newColor = self.contPalette.getRGB(self.noJitteringScaledSubsetData[self.dataClassIndex][i])
164                else:
165                    newColor = self.discPalette.getRGB(self.originalSubsetData[self.dataClassIndex][i])
166
167                if 0 in [data[index] >= self.selectionConditions[name][0] and data[index] <= self.selectionConditions[name][1] for (name, index) in conditions.items()]:
168                    newColor += (self.alphaValue2,)
169                    curves = subCurves
170                else:
171                    newColor += (self.alphaValue,)
172                    curves = mainCurves
173
174                if not curves.has_key(newColor):
175                    curves[newColor] = []
176                curves[newColor].extend(data)
177
178        # add main curves
179        keys = mainCurves.keys()
180        keys.sort()     # otherwise the order of curves change when we slide the alpha slider
181        for key in keys:
182            curve = ParallelCoordinatesCurve(len(attributes), mainCurves[key], key)
183            if self.useAntialiasing: 
184                curve.setRenderHint(QwtPlotItem.RenderAntialiased)
185            if self.useSplines:
186                curve.setCurveAttribute(QwtPlotCurve.Fitted)
187#                curve.setCurveFitter(QwtSplineCurveFitter())
188            curve.attach(self)
189
190        # add sub curves
191        keys = subCurves.keys()
192        keys.sort()     # otherwise the order of curves change when we slide the alpha slider
193        for key in keys:
194            curve = ParallelCoordinatesCurve(len(attributes), subCurves[key], key)
195            if self.useAntialiasing: 
196                curve.setRenderHint(QwtPlotItem.RenderAntialiased)
197            if self.useSplines:     
198                curve.setCurveAttribute(QwtPlotCurve.Fitted)
199            curve.attach(self)
200
201
202
203        # ############################################
204        # do we want to show distributions with discrete attributes
205        if self.showDistributions and self.dataHasDiscreteClass and self.haveData:
206            self.showDistributionValues(validData, indices)
207
208        # ############################################
209        # draw vertical lines that represent attributes
210        for i in range(len(attributes)):
211            self.addCurve("", lineWidth = 2, style = QwtPlotCurve.Lines, symbol = QwtSymbol.NoSymbol, xData = [i,i], yData = [0,1])
212            if self.showAttrValues == 1:
213                attr = self.dataDomain[attributes[i]]
214                if attr.varType == orange.VarTypes.Continuous:
215                    strVal1 = "%%.%df" % (attr.numberOfDecimals) % (self.attrValues[attr.name][0])
216                    strVal2 = "%%.%df" % (attr.numberOfDecimals) % (self.attrValues[attr.name][1])
217                    align1 = i == 0 and Qt.AlignRight | Qt.AlignBottom or i == len(attributes)-1 and Qt.AlignLeft | Qt.AlignBottom or Qt.AlignHCenter | Qt.AlignBottom
218                    align2 = i == 0 and Qt.AlignRight | Qt.AlignTop or i == len(attributes)-1 and Qt.AlignLeft | Qt.AlignTop or Qt.AlignHCenter | Qt.AlignTop
219                    self.addMarker(strVal1, i, 0.0-0.01, alignment = align1)
220                    self.addMarker(strVal2, i, 1.0+0.01, alignment = align2)
221
222                elif attr.varType == orange.VarTypes.Discrete:
223                    attrVals = getVariableValuesSorted(self.dataDomain[attributes[i]])
224                    valsLen = len(attrVals)
225                    for pos in range(len(attrVals)):
226                        # show a rectangle behind the marker
227                        self.addMarker(attrVals[pos], i+0.01, float(1+2*pos)/float(2*valsLen), alignment = Qt.AlignRight | Qt.AlignVCenter, bold = 1, brushColor = Qt.white)
228
229        # ##############################################
230        # show lines that represent standard deviation or quartiles
231        # ##############################################
232        if self.showStatistics and self.haveData:
233            data = []
234            for i in range(length):
235                if self.dataDomain[indices[i]].varType != orange.VarTypes.Continuous:
236                    data.append([()])
237                    continue  # only for continuous attributes
238                array = numpy.compress(numpy.equal(self.validDataArray[indices[i]], 1), self.scaledData[indices[i]])  # remove missing values
239
240                if not self.dataHasClass or self.dataHasContinuousClass:    # no class
241                    if self.showStatistics == MEANS:
242                        m = array.mean()
243                        dev = array.std()
244                        data.append([(m-dev, m, m+dev)])
245                    elif self.showStatistics == MEDIAN:
246                        sorted = numpy.sort(array)
247                        if len(sorted) > 0:
248                            data.append([(sorted[int(len(sorted)/4.0)], sorted[int(len(sorted)/2.0)], sorted[int(len(sorted)*0.75)])])
249                        else:
250                            data.append([(0,0,0)])
251                else:
252                    curr = []
253                    classValues = getVariableValuesSorted(self.dataDomain.classVar)
254                    classValueIndices = getVariableValueIndices(self.dataDomain.classVar)
255                    for c in range(len(classValues)):
256                        scaledVal = ((classValueIndices[classValues[c]] * 2) + 1) / float(2*len(classValueIndices))
257                        nonMissingValues = numpy.compress(numpy.equal(self.validDataArray[indices[i]], 1), self.noJitteringScaledData[self.dataClassIndex])  # remove missing values
258                        arr_c = numpy.compress(numpy.equal(nonMissingValues, scaledVal), array)
259                        if len(arr_c) == 0:
260                            curr.append((0,0,0)); continue
261                        if self.showStatistics == MEANS:
262                            m = arr_c.mean()
263                            dev = arr_c.std()
264                            curr.append((m-dev, m, m+dev))
265                        elif self.showStatistics == MEDIAN:
266                            sorted = numpy.sort(arr_c)
267                            curr.append((sorted[int(len(arr_c)/4.0)], sorted[int(len(arr_c)/2.0)], sorted[int(len(arr_c)*0.75)]))
268                    data.append(curr)
269
270            # draw vertical lines
271            for i in range(len(data)):
272                for c in range(len(data[i])):
273                    if data[i][c] == (): continue
274                    x = i - 0.03*(len(data[i])-1)/2.0 + c*0.03
275                    col = QColor(self.discPalette[c])
276                    col.setAlpha(self.alphaValue2)
277                    self.addCurve("", col, col, 3, QwtPlotCurve.Lines, QwtSymbol.NoSymbol, xData = [x,x,x], yData = [data[i][c][0], data[i][c][1], data[i][c][2]], lineWidth = 4)
278                    self.addCurve("", col, col, 1, QwtPlotCurve.Lines, QwtSymbol.NoSymbol, xData = [x-0.03, x+0.03], yData = [data[i][c][0], data[i][c][0]], lineWidth = 4)
279                    self.addCurve("", col, col, 1, QwtPlotCurve.Lines, QwtSymbol.NoSymbol, xData = [x-0.03, x+0.03], yData = [data[i][c][1], data[i][c][1]], lineWidth = 4)
280                    self.addCurve("", col, col, 1, QwtPlotCurve.Lines, QwtSymbol.NoSymbol, xData = [x-0.03, x+0.03], yData = [data[i][c][2], data[i][c][2]], lineWidth = 4)
281
282            # draw lines with mean/median values
283            classCount = 1
284            if not self.dataHasClass or self.dataHasContinuousClass:
285                classCount = 1 # no class
286            else: classCount = len(self.dataDomain.classVar.values)
287            for c in range(classCount):
288                diff = - 0.03*(classCount-1)/2.0 + c*0.03
289                ys = []
290                xs = []
291                for i in range(len(data)):
292                    if data[i] != [()]: ys.append(data[i][c][1]); xs.append(i+diff)
293                    else:
294                        if len(xs) > 1:
295                            col = QColor(self.discPalette[c])
296                            col.setAlpha(self.alphaValue2)
297                            self.addCurve("", col, col, 1, QwtPlotCurve.Lines, QwtSymbol.NoSymbol, xData = xs, yData = ys, lineWidth = 4)
298                        xs = []; ys = []
299                col = QColor(self.discPalette[c])
300                col.setAlpha(self.alphaValue2)
301                self.addCurve("", col, col, 1, QwtPlotCurve.Lines, QwtSymbol.NoSymbol, xData = xs, yData = ys, lineWidth = 4)
302
303
304        # ##################################################
305        # show labels in the middle of the axis
306        if midLabels:
307            for j in range(len(midLabels)):
308                self.addMarker(midLabels[j], j+0.5, 1.0, alignment = Qt.AlignCenter | Qt.AlignTop)
309
310        # show the legend
311        if self.enabledLegend == 1 and self.dataHasDiscreteClass:
312            if self.dataDomain.classVar.varType == orange.VarTypes.Discrete:
313                legendKeys = []
314                varValues = getVariableValuesSorted(self.dataDomain.classVar)
315                #self.addCurve("<b>" + self.dataDomain.classVar.name + ":</b>", QColor(0,0,0), QColor(0,0,0), 0, symbol = QwtSymbol.NoSymbol, enableLegend = 1)
316                for ind in range(len(varValues)):
317                    #self.addCurve(varValues[ind], self.discPalette[ind], self.discPalette[ind], 15, symbol = QwtSymbol.Rect, enableLegend = 1)
318                    legendKeys.append((varValues[ind], self.discPalette[ind]))
319                if legendKeys != self.oldLegendKeys:
320                    self.oldLegendKeys = legendKeys
321                    self.legend().clear()
322                    self.addCurve("<b>" + self.dataDomain.classVar.name + ":</b>", QColor(0,0,0), QColor(0,0,0), 0, symbol = QwtSymbol.NoSymbol, enableLegend = 1)
323                    for (name, color) in legendKeys:
324                        self.addCurve(name, color, color, 15, symbol = QwtSymbol.Rect, enableLegend = 1)
325            else:
326                l = len(attributes)-1
327                xs = [l*1.15, l*1.20, l*1.20, l*1.15]
328                count = 200; height = 1/200.
329                for i in range(count):
330                    y = i/float(count)
331                    col = self.contPalette[y]
332                    curve = PolygonCurve(QPen(col), QBrush(col), xData = xs, yData = [y,y, y+height, y+height])
333                    curve.attach(self)
334
335                # add markers for min and max value of color attribute
336                [minVal, maxVal] = self.attrValues[self.dataDomain.classVar.name]
337                decimals = self.dataDomain.classVar.numberOfDecimals
338                self.addMarker("%%.%df" % (decimals) % (minVal), xs[0] - l*0.02, 0.04, Qt.AlignLeft)
339                self.addMarker("%%.%df" % (decimals) % (maxVal), xs[0] - l*0.02, 1.0 - 0.04, Qt.AlignLeft)
340        else:
341            self.legend().clear()
342            self.oldLegendKeys = []
343
344        self.replot()
345
346
347    # ##########################################
348    # SHOW DISTRIBUTION BAR GRAPH
349    def showDistributionValues(self, validData, indices):
350        # create color table
351        clsCount = len(self.dataDomain.classVar.values)
352        #if clsCount < 1: clsCount = 1.0
353
354        # we create a hash table of possible class values (happens only if we have a discrete class)
355        classValueSorted  = getVariableValuesSorted(self.dataDomain.classVar)
356        if self.domainContingency == None:
357            self.domainContingency = orange.DomainContingency(self.rawData)
358
359        maxVal = 1
360        for attr in indices:
361            if self.dataDomain[attr].varType != orange.VarTypes.Discrete:
362                continue
363            if self.dataDomain[attr] == self.dataDomain.classVar:
364                maxVal = max(maxVal, max(orange.Distribution(attr, self.rawData) or [1]))
365            else:
366                maxVal = max(maxVal, max([max(val or [1]) for val in self.domainContingency[attr].values()] or [1]))
367               
368
369        for graphAttrIndex, index in enumerate(indices):
370            attr = self.dataDomain[index]
371            if attr.varType != orange.VarTypes.Discrete: continue
372            if self.dataDomain[index] == self.dataDomain.classVar:
373                contingency = orange.Contingency(self.dataDomain[index], self.dataDomain[index])
374                dist = orange.Distribution(self.dataDomain[index], self.rawData)
375                for val in self.dataDomain[index].values:
376                    contingency[val][val] = dist[val]
377            else:
378                contingency = self.domainContingency[index]
379                               
380            attrLen = len(attr.values)
381
382            # we create a hash table of variable values and their indices
383            variableValueIndices = getVariableValueIndices(self.dataDomain[index])
384            variableValueSorted = getVariableValuesSorted(self.dataDomain[index])
385
386            # create bar curve
387            for j in range(attrLen):
388                attrVal = variableValueSorted[j]
389                try:
390                    attrValCont = contingency[attrVal]
391                except IndexError, ex:
392                    print >> sys.stderr, ex, attrVal, contingency
393                    continue
394               
395                for i in range(clsCount):
396                    clsVal = classValueSorted[i]
397
398                    newColor = QColor(self.discPalette[i])
399                    newColor.setAlpha(self.alphaValue)
400
401                    width = float(attrValCont[clsVal]*0.5) / float(maxVal)
402                    interval = 1.0/float(2*attrLen)
403                    yOff = float(1.0 + 2.0*j)/float(2*attrLen)
404                    height = 0.7/float(clsCount*attrLen)
405
406                    yLowBott = yOff + float(clsCount*height)/2.0 - i*height
407                    curve = PolygonCurve(QPen(newColor), QBrush(newColor), xData = [graphAttrIndex, graphAttrIndex + width, graphAttrIndex + width, graphAttrIndex], yData = [yLowBott, yLowBott, yLowBott - height, yLowBott - height], tooltip = (self.dataDomain[index].name, variableValueSorted[j], len(self.rawData), [(clsVal, attrValCont[clsVal]) for clsVal in classValueSorted]))
408                    curve.attach(self)
409
410
411    # handle tooltip events
412    def event(self, ev):
413        if ev.type() == QEvent.ToolTip:
414            x = self.invTransform(QwtPlot.xBottom, ev.pos().x())
415            y = self.invTransform(QwtPlot.yLeft, ev.pos().y())
416
417            canvasPos = self.canvas().mapFrom(self, ev.pos())
418            xFloat = self.invTransform(QwtPlot.xBottom, canvasPos.x())
419            contact, (index, pos) = self.testArrowContact(int(round(xFloat)), canvasPos.x(), canvasPos.y())
420            if contact:
421                attr = self.dataDomain[self.visualizedAttributes[index]]
422                if attr.varType == orange.VarTypes.Continuous:
423                    condition = self.selectionConditions.get(attr.name, [0,1])
424                    val = self.attrValues[attr.name][0] + condition[pos] * (self.attrValues[attr.name][1] - self.attrValues[attr.name][0])
425                    strVal = attr.name + "= %%.%df" % (attr.numberOfDecimals) % (val)
426                    QToolTip.showText(ev.globalPos(), strVal)
427            else:
428                for curve in self.itemList():
429                    if type(curve) == PolygonCurve and curve.boundingRect().contains(x,y) and getattr(curve, "tooltip", None):
430                        (name, value, total, dist) = curve.tooltip
431                        count = sum([v[1] for v in dist])
432                        if count == 0: continue
433                        tooltipText = "Attribute: <b>%s</b><br>Value: <b>%s</b><br>Total instances: <b>%i</b> (%.1f%%)<br>Class distribution:<br>" % (name, value, count, 100.0*count/float(total))
434                        for (val, n) in dist:
435                            tooltipText += "&nbsp; &nbsp; <b>%s</b> : <b>%i</b> (%.1f%%)<br>" % (val, n, 100.0*float(n)/float(count))
436                        QToolTip.showText(ev.globalPos(), tooltipText[:-4])
437
438        elif ev.type() == QEvent.MouseMove:
439            QToolTip.hideText()
440
441        return OWGraph.event(self, ev)
442
443
444    def testArrowContact(self, indices, x, y):
445        if type(indices) != list: indices = [indices]
446        for index in indices:
447            if index >= len(self.visualizedAttributes) or index < 0: continue
448            intX = self.transform(QwtPlot.xBottom, index)
449            bottom = self.transform(QwtPlot.yLeft, self.selectionConditions.get(self.visualizedAttributes[index], [0,1])[0])
450            bottomRect = QRect(intX-self.bottomPixmap.width()/2, bottom, self.bottomPixmap.width(), self.bottomPixmap.height())
451            if bottomRect.contains(QPoint(x,y)): return 1, (index, 0)
452            top = self.transform(QwtPlot.yLeft, self.selectionConditions.get(self.visualizedAttributes[index], [0,1])[1])
453            topRect = QRect(intX-self.topPixmap.width()/2, top-self.topPixmap.height(), self.topPixmap.width(), self.topPixmap.height())
454            if topRect.contains(QPoint(x,y)): return 1, (index, 1)
455        return 0, (0, 0)
456
457    def mousePressEvent(self, e):
458        canvasPos = self.canvas().mapFrom(self, e.pos())
459        xFloat = self.invTransform(QwtPlot.xBottom, canvasPos.x())
460        contact, info = self.testArrowContact(int(round(xFloat)), canvasPos.x(), canvasPos.y())
461
462        if contact:
463            self.pressedArrow = info
464        elif self.state in [ZOOMING, PANNING]:
465            OWGraph.mousePressEvent(self, e)
466
467
468    def mouseMoveEvent(self, e):
469        if hasattr(self, "pressedArrow"):
470            canvasPos = self.canvas().mapFrom(self, e.pos())
471            yFloat = min(1, max(0, self.invTransform(QwtPlot.yLeft, canvasPos.y())))
472            index, pos = self.pressedArrow
473            attr = self.dataDomain[self.visualizedAttributes[index]]
474            oldCondition = self.selectionConditions.get(attr.name, [0,1])
475            oldCondition[pos] = yFloat
476            self.selectionConditions[attr.name] = oldCondition
477            self.updateData(self.visualizedAttributes, self.visualizedMidLabels, updateAxisScale = 0)
478
479            if attr.varType == orange.VarTypes.Continuous:
480                val = self.attrValues[attr.name][0] + oldCondition[pos] * (self.attrValues[attr.name][1] - self.attrValues[attr.name][0])
481                strVal = attr.name + "= %%.%df" % (attr.numberOfDecimals) % (val)
482                QToolTip.showText(e.globalPos(), strVal)
483            if self.sendSelectionOnUpdate and self.autoSendSelectionCallback:
484                self.autoSendSelectionCallback()
485
486        elif self.state in [ZOOMING, PANNING]:
487            OWGraph.mouseMoveEvent(self, e)
488
489    def mouseReleaseEvent(self, e):
490        if hasattr(self, "pressedArrow"):
491            del self.pressedArrow
492            if self.autoSendSelectionCallback and not (self.sendSelectionOnUpdate and self.autoSendSelectionCallback):
493                self.autoSendSelectionCallback() # send the new selection
494        elif self.state in [ZOOMING, PANNING]:
495            OWGraph.mouseReleaseEvent(self, e)
496
497
498    def staticMouseClick(self, e):
499        if e.button() == Qt.LeftButton and self.state == ZOOMING:
500            if self.tempSelectionCurve: self.tempSelectionCurve.detach()
501            self.tempSelectionCurve = None
502            canvasPos = self.canvas().mapFrom(self, e.pos())
503            x = self.invTransform(QwtPlot.xBottom, canvasPos.x())
504            y = self.invTransform(QwtPlot.yLeft, canvasPos.y())
505            diffX = (self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue() -  self.axisScaleDiv(QwtPlot.xBottom).interval().minValue()) / 2.
506
507            xmin = x - (diffX/2.) * (x - self.axisScaleDiv(QwtPlot.xBottom).interval().minValue()) / diffX
508            xmax = x + (diffX/2.) * (self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue() - x) / diffX
509            ymin = self.axisScaleDiv(QwtPlot.yLeft).interval().maxValue()
510            ymax = self.axisScaleDiv(QwtPlot.yLeft).interval().minValue()
511
512            self.zoomStack.append((self.axisScaleDiv(QwtPlot.xBottom).interval().minValue(), self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue(), self.axisScaleDiv(QwtPlot.yLeft).interval().minValue(), self.axisScaleDiv(QwtPlot.yLeft).interval().maxValue()))
513            self.setNewZoom(xmin, xmax, ymax, ymin)
514            return 1
515
516        # if the user clicked between two lines send a list with the names of the two attributes
517        elif self.parallelDlg:
518            x1 = int(self.invTransform(QwtPlot.xBottom, e.x()))
519            axis = self.axisScaleDraw(QwtPlot.xBottom)
520            self.parallelDlg.sendShownAttributes([str(axis.label(x1)), str(axis.label(x1+1))])
521        return 0
522
523    def removeAllSelections(self, send = 1):
524        self.selectionConditions = {}
525        self.updateData(self.visualizedAttributes, self.visualizedMidLabels, updateAxisScale = 0)
526        if send and self.autoSendSelectionCallback:
527            self.autoSendSelectionCallback() # do we want to send new selection
528
529    # draw the curves and the selection conditions
530    def drawCanvas(self, painter):
531        OWGraph.drawCanvas(self, painter)
532        for i in range(int(max(0, math.floor(self.axisScaleDiv(QwtPlot.xBottom).interval().minValue()))), int(min(len(self.visualizedAttributes), math.ceil(self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue())+1))):
533            bottom, top = self.selectionConditions.get(self.visualizedAttributes[i], (0, 1))
534            painter.drawPixmap(self.transform(QwtPlot.xBottom, i)-self.bottomPixmap.width()/2, self.transform(QwtPlot.yLeft, bottom), self.bottomPixmap)
535            painter.drawPixmap(self.transform(QwtPlot.xBottom, i)-self.topPixmap.width()/2, self.transform(QwtPlot.yLeft, top)-self.topPixmap.height(), self.topPixmap)
536
537    # get selected examples
538    # this function must be called after calling self.updateGraph
539    def getSelectionsAsExampleTables(self):
540        if not self.haveData:
541            return (None, None)
542
543        selected = self.rawData.getitemsref(self.selectedExamples)
544        unselected = self.rawData.getitemsref(self.unselectedExamples)
545
546        if len(selected) == 0: selected = None
547        if len(unselected) == 0: unselected = None
548        return (selected, unselected)
549
550
551
552# ####################################################################
553# a curve that is able to draw several series of lines
554class ParallelCoordinatesCurve(QwtPlotCurve):
555    def __init__(self, attrCount, yData, color, name = ""):
556        QwtPlotCurve.__init__(self, name)
557        self.setStyle(QwtPlotCurve.Lines)
558        self.setItemAttribute(QwtPlotItem.Legend, 0)
559
560        lineCount = len(yData) / attrCount
561        self.attrCount = attrCount
562        self.xData = range(attrCount) * lineCount
563        self.yData = yData
564       
565#        self._cubic = self.cubicPath(None, None)
566       
567        self.setData(QPolygonF(map(lambda t:QPointF(*t), zip(self.xData, self.yData))))
568        if type(color) == tuple:
569            self.setPen(QPen(QColor(*color)))
570        else:
571            self.setPen(QPen(QColor(color)))
572
573
574    def drawCurve(self, painter, style, xMap, yMap, iFrom, iTo):
575        low = max(0, int(math.floor(xMap.s1())))
576        high = min(self.attrCount-1, int(math.ceil(xMap.s2())))
577        painter.setPen(self.pen())
578        if not self.testCurveAttribute(QwtPlotCurve.Fitted):
579            for i in range(self.dataSize() / self.attrCount):
580                start = self.attrCount * i + low
581                end = self.attrCount * i + high
582                self.drawLines(painter, xMap, yMap, start, end)
583        else:
584            painter.save()
585#            painter.scale(xMap.transform(1.0), yMap.transform(1.0))
586            painter.strokePath(self.cubicPath(xMap, yMap), self.pen())
587#            painter.strokePath(self._cubic, self.pen())
588            painter.restore()
589
590    def cubicPath(self, xMap, yMap):
591        path = QPainterPath()
592        transform = lambda x, y: QPointF(xMap.transform(x), yMap.transform(y))
593#        transform = lambda x, y: QPointF(x, y)
594#        data = [QPointF(transform(x, y)) for x, y in zip(self.xData, self.yData)]
595        data = [(x, y) for x, y in zip(self.xData, self.yData)]
596        for i in range(self.dataSize() / self.attrCount):
597            segment = data[i*self.attrCount: (i + 1)*self.attrCount]
598            for i, p in enumerate(segment[:-1]):
599                x1, y1 = p
600                x2, y2 = segment[i + 1]
601                path.moveTo(transform(x1, y1))
602                path.cubicTo(transform(x1 + 0.5, y1), transform(x2 - 0.5, y2), transform(x2, y2))
603        return path       
604               
605               
606           
607
Note: See TracBrowser for help on using the repository browser.