source: orange/Orange/OrangeWidgets/VisualizeQt/OWParallelGraphQt.py @ 11767:05de076df152

Revision 11767:05de076df152, 26.1 KB checked in by astaric, 5 months ago (diff)

Fixed a bug in ParallelGraphQt.

When lines were shown without splines, an additional line was drawn,
connecting last value of one example with the first value of the next example.

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