source: orange/orange/OrangeWidgets/OWGraphTools.py @ 6538:a5f65d7f0b2c

Revision 6538:a5f65d7f0b2c, 17.8 KB checked in by Mitar <Mitar@…>, 4 years ago (diff)

Made XPM version of the icon 32x32.

Line 
1from PyQt4.QtCore import *
2from PyQt4.QtGui import *
3from PyQt4.Qwt5 import *
4import numpy
5
6SelectionCurveRtti = QwtPlotCurve.Rtti_PlotUserItem + 123
7LegendCurveRtti = QwtPlotCurve.Rtti_PlotUserItem + 124
8
9
10# ####################################################################
11# add val to sorted list list. if len > maxLen delete last element
12def addToList(list, val, ind, maxLen):
13    i = 0
14    for i in range(len(list)):
15        (val2, ind2) = list[i]
16        if val < val2:
17            list.insert(i, (val, ind))
18            if len(list) > maxLen:
19                list.remove(list[maxLen])
20            return
21    if len(list) < maxLen:
22        list.insert(len(list), (val, ind))
23
24
25#A dynamic tool tip class
26class TooltipManager:
27    # Creates a new dynamic tool tip.
28    def __init__(self, qwtplot):
29        self.qwtplot = qwtplot
30        self.positions=[]
31        self.texts=[]
32
33    # Adds a tool tip. If a tooltip with the same name already exists, it updates it instead of adding a new one.
34    def addToolTip(self, x, y, text, customX = 0, customY = 0):
35        self.positions.append((x,y, customX, customY))
36        self.texts.append(text)
37
38    #Decides whether to pop up a tool tip and which text to pop up
39    def maybeTip(self, x, y):
40        if len(self.positions) == 0: return ("", -1, -1)
41        dists = [max(abs(x-position[0])- position[2],0) + max(abs(y-position[1])-position[3], 0) for position in self.positions]
42        nearestIndex = dists.index(min(dists))
43
44        intX = abs(self.qwtplot.transform(self.qwtplot.xBottom, x) - self.qwtplot.transform(self.qwtplot.xBottom, self.positions[nearestIndex][0]))
45        intY = abs(self.qwtplot.transform(self.qwtplot.yLeft, y) - self.qwtplot.transform(self.qwtplot.yLeft, self.positions[nearestIndex][1]))
46        if self.positions[nearestIndex][2] == 0 and self.positions[nearestIndex][3] == 0:   # if we specified no custom range then assume 6 pixels
47            if intX + intY < 6:  return (self.texts[nearestIndex], self.positions[nearestIndex][0], self.positions[nearestIndex][1])
48            else:                return ("", None, None)
49        else:
50            if abs(self.positions[nearestIndex][0] - x) <= self.positions[nearestIndex][2] and abs(self.positions[nearestIndex][1] - y) <= self.positions[nearestIndex][3]:
51                return (self.texts[nearestIndex], x, y)
52            else:
53                return ("", None, None)
54
55    def removeAll(self):
56        self.positions = []
57        self.texts = []
58
59
60# ####################################################################
61# used in widgets that enable to draw a rectangle or a polygon to select a subset of data points
62class SelectionCurve(QwtPlotCurve):
63    def __init__(self, name = "", pen = Qt.SolidLine ):
64        QwtPlotCurve.__init__(self, name)
65        self.setStyle(QwtPlotCurve.Lines)
66        self.setPen(QPen(QColor(128,128,128), 1, pen))
67        self.setItemAttribute(QwtPlotItem.Legend, 0)
68
69    def rtti(self):
70        return SelectionCurveRtti
71
72    def addPoint(self, xPoint, yPoint):
73        self.setData([self.x(i) for i in range(self.dataSize())] + [xPoint], [self.y(i) for i in range(self.dataSize())] + [yPoint])
74
75    def removeLastPoint(self):
76        self.setData([self.x(i) for i in range(self.dataSize()-1)], [self.y(i) for i in range(self.dataSize()-1)])
77
78    def replaceLastPoint(self, xPoint, yPoint):
79        self.setData([self.x(i) for i in range(self.dataSize()-1)] + [xPoint], [self.y(i) for i in range(self.dataSize()-1)] + [yPoint])
80
81    def getPointArray(self):
82        return QPolygonF([QPointF(self.x(i), self.y(i)) for i in range(self.dataSize())] + [QPointF(self.x(0), self.y(0))])
83
84    def getSelectedPoints(self, xData, yData, validData):
85        pointArray = self.getPointArray()
86        selected = numpy.zeros(len(xData))
87
88        for i in range(len(xData)):
89            if validData[i]:
90                selected[i] = pointArray.containsPoint(QPointF(xData[i], yData[i]), Qt.OddEvenFill)
91        return selected
92
93    # is point defined at x,y inside a rectangle defined with this curve
94    def isInside(self, x, y):
95        return self.getPointArray().containsPoint(QPointF(x, y), Qt.OddEvenFill)
96
97    def moveBy(self, dx, dy):
98        xData = [self.x(i) + dx for i in range(self.dataSize())]
99        yData = [self.y(i) + dy for i in range(self.dataSize())]
100        self.setData(xData, yData)
101
102
103    # test if the line going from before last and last point intersect any lines before
104    # if yes, then add the intersection point and remove the outer points
105    def closed(self):
106        if self.dataSize() < 5: return 0
107        x1 = self.x(self.dataSize()-3)
108        x2 = self.x(self.dataSize()-2)
109        y1 = self.y(self.dataSize()-3)
110        y2 = self.y(self.dataSize()-2)
111        for i in range(self.dataSize()-5, -1, -1):
112            X1 = self.x(i)
113            X2 = self.x(i+1)
114            Y1 = self.y(i)
115            Y2 = self.y(i+1)
116            (intersect, xi, yi) = self.lineIntersection(x1, y1, x2, y2, X1, Y1, X2, Y2)
117            if intersect:
118                xData = [xi]; yData = [yi]
119                for j in range(i+1, self.dataSize()-2): xData.append(self.x(j)); yData.append(self.y(j))
120                xData.append(xi); yData.append(yi)
121                self.setData(xData, yData)
122                return 1
123        return 0
124
125    def lineIntersection(self, x1, y1, x2, y2, X1, Y1, X2, Y2):
126        if min(x1,x2) > max(X1, X2) or max(x1,x2) < min(X1,X2): return (0, 0, 0)
127        if min(y1,y2) > max(Y1, Y2) or max(y1,y2) < min(Y1,Y2): return (0, 0, 0)
128
129        if x2-x1 != 0: k1 = (y2-y1)/(x2-x1)
130        else:          k1 = 1e+12
131
132        if X2-X1 != 0: k2 = (Y2-Y1)/(X2-X1)
133        else:          k2 = 1e+12
134
135        c1 = (y1-k1*x1)
136        c2 = (Y1-k2*X1)
137
138        if k1 == 1e+12:
139            yTest = k2*x1 + c2
140            if yTest > min(y1,y2) and yTest  < max(y1,y2): return (1, x1, yTest)
141            else: return (0,0,0)
142
143        if k2 == 1e+12:
144            yTest = k1*X1 + c1
145            if yTest > min(Y1,Y2) and yTest < max(Y1,Y2): return (1, X1, yTest)
146            else: return (0,0,0)
147
148        det_inv = 1/(k2 - k1)
149
150        xi=((c1 - c2)*det_inv)
151        yi=((k2*c1 - k1*c2)*det_inv)
152
153        if xi >= min(x1, x2) and xi <= max(x1,x2) and xi >= min(X1, X2) and xi <= max(X1, X2) and yi >= min(y1,y2) and yi <= max(y1, y2) and yi >= min(Y1, Y2) and yi <= max(Y1, Y2):
154            return (1, xi, yi)
155        else:
156            return (0, xi, yi)
157       
158    def isOnEdge(self, x, y):
159        return 0
160
161class RectangleSelectionCurve(SelectionCurve):
162    def __init__(self, name = "", pen = Qt.SolidLine):
163        SelectionCurve.__init__(self, name, pen)
164        self.point1 = (0,0)
165        self.point2 = (0,0)
166        self.appropriateCursor = Qt.ArrowCursor
167   
168    def setPoints(self, x1, y1, x2, y2):
169        self.point1, self.point2 = (x1,y1), (x2,y2)
170        self.setData([x1, x1, x2, x2, x1], [y1, y2, y2, y1, y1])
171       
172    def approxEqual(self, p1, p2, axis = None):
173        if type(p1) == tuple and type(p2) == tuple:
174            x1, y1 = self.plot().transform(self.plot().xBottom, p1[0]), self.plot().transform(self.plot().yLeft, p1[1])
175            x2, y2 = self.plot().transform(self.plot().xBottom, p2[0]), self.plot().transform(self.plot().yLeft, p2[1])
176            return abs(x1-x2) + abs(y1-y2) <= 4
177        else:
178            v1, v2 = self.plot().transform(axis, p1), self.plot().transform(axis, p2)
179            return abs(v1-v2) <= 2
180   
181    def between(self, val, v1, v2):
182        return val >= min(v1, v2) and val <= max(v1, v2)
183       
184    # check if based on the mouse position (x,y) we should show a different cursor and enable resizing
185    def isOnEdge(self, x, y):
186        xData = [self.x(i) for i in range(self.dataSize())]
187        yData = [self.y(i) for i in range(self.dataSize())]
188        if len(xData) == 0: return 0
189        x1, y1 = min(xData), min(yData)
190        x2, y2 = max(xData), max(yData)
191       
192        if self.approxEqual((min(x1,x2), min(y1,y2)), (x,y)):
193            self.point1, self.point2 = (max(x1,x2), max(y1,y2)), (min(x1,x2), min(y1,y2))
194            self.appropriateCursor = Qt.SizeBDiagCursor
195        elif self.approxEqual((max(x1,x2), max(y1,y2)), (x,y)):
196            self.point1, self.point2 = (min(x1,x2), min(y1,y2)), (max(x1,x2), max(y1,y2))
197            self.appropriateCursor = Qt.SizeBDiagCursor
198        elif self.approxEqual((min(x1,x2), max(y1,y2)), (x,y)):
199            self.point1, self.point2 = (max(x1,x2), min(y1,y2)), (min(x1,x2), max(y1,y2))
200            self.appropriateCursor = Qt.SizeFDiagCursor
201        elif self.approxEqual((max(x1,x2), min(y1,y2)), (x,y)):
202            self.point1, self.point2 = (min(x1,x2), max(y1,y2)), (max(x1,x2), min(y1,y2))
203            self.appropriateCursor = Qt.SizeFDiagCursor
204        elif self.approxEqual(x1, x, self.plot().xBottom) and self.between(y, y1, y2):
205            self.point1, self.point2 = (x2, y2), (x1,y1)
206            self.appropriateCursor = Qt.SizeHorCursor
207        elif self.approxEqual(x2, x, self.plot().xBottom) and self.between(y, y1, y2) :
208            self.point1, self.point2 = (x1, y1), (x2,y2)
209            self.appropriateCursor = Qt.SizeHorCursor
210        elif self.approxEqual(y1, y, self.plot().yLeft) and self.between(x, x1, x2):
211            self.point1, self.point2 = (x2, y2), (x1,y1)
212            self.appropriateCursor = Qt.SizeVerCursor
213        elif self.approxEqual(y2, y, self.plot().yLeft) and self.between(x, x1, x2):
214            self.point1, self.point2 = (x1, y1), (x2,y2)
215            self.appropriateCursor = Qt.SizeVerCursor
216        else:
217            self.appropriateCursor = Qt.ArrowCursor
218        return self.appropriateCursor != Qt.ArrowCursor
219   
220    # update the curve with new x and y coordinates of one edge
221    # here we assume that the isOnEdge was called first since it takes care of preparing the values in point1 and point2
222    def updateCurve(self, x, y):
223        if self.appropriateCursor in [Qt.SizeBDiagCursor, Qt.SizeFDiagCursor]:
224            self.setPoints(self.point1[0], self.point1[1], x, y)
225        elif self.appropriateCursor == Qt.SizeHorCursor:
226            self.setPoints(self.point1[0], self.point1[1], x, self.point2[1])
227        elif self.appropriateCursor == Qt.SizeVerCursor:
228            self.setPoints(self.point1[0], self.point1[1], self.point2[0], y)
229
230# a class that draws unconnected lines. first two points in the xData and yData are considered as the first line,
231# the second two points as the second line, etc.
232class UnconnectedLinesCurve(QwtPlotCurve):
233    def __init__(self, name, pen = QPen(Qt.black), xData = None, yData = None):
234        QwtPlotCurve.__init__(self, name)
235        if pen.width() == 0:
236            pen.setWidth(1)
237        self.setPen(pen)
238        self.Pen = pen
239        self.setStyle(QwtPlotCurve.Lines)
240        self.setItemAttribute(QwtPlotItem.Legend, 0)
241        if xData != None and yData != None:
242            self.setData(xData, yData)
243
244    def drawCurve(self, painter, style, xMap, yMap, start, stop):
245        start = max(start + start%2, 0)
246        if stop == -1:
247            stop = self.dataSize()
248        for i in range(start, stop, 2):
249            QwtPlotCurve.drawLines(self, painter, xMap, yMap, i, i+1)
250
251
252class RectangleCurve(QwtPlotCurve):
253    def __init__(self, pen = QPen(Qt.black), brush = QBrush(Qt.white), xData = None, yData = None):
254        QwtPlotCurve.__init__(self)
255        if pen:
256            self.setPen(pen)
257        if brush:
258            self.setBrush(brush)
259        self.Pen = pen
260        self.Brush = brush
261        self.setStyle(QwtPlotCurve.Lines)
262        self.setItemAttribute(QwtPlotItem.Legend, 0)
263        if xData != None and yData != None:
264            self.setData(xData, yData)
265
266
267    # To show a rectangle, we have to create a closed polygon.
268    # Therefore we add to each rectangle the first point (each rect therefore contains 5 points in the xData and yData)
269    def setData(self, xData, yData):
270        startsX = xData[::4]
271        startsY = yData[::4]
272        for i in range(len(startsX))[::-1]:
273            xData.insert(4+i*4, startsX[i])
274            yData.insert(4+i*4, startsY[i])
275        QwtPlotCurve.setData(self, xData, yData)
276
277    def drawCurve(self, painter, style, xMap, yMap, start, stop):
278        for i in range(start, stop, 5):
279            QwtPlotCurve.drawLines(self, painter, xMap, yMap, i, i+4)
280
281
282# ###########################################################
283# a class that is able to draw arbitrary polygon curves.
284# data points are specified by a standard call to graph.setCurveData(key, xArray, yArray)
285# brush and pen can also be set by calls to setPen and setBrush functions
286class PolygonCurve(QwtPlotCurve):
287    def __init__(self, pen = QPen(Qt.black), brush = QBrush(Qt.white), xData = None, yData = None, tooltip = None):
288        QwtPlotCurve.__init__(self)
289        if pen:
290            self.setPen(pen)
291        if brush:
292            self.setBrush(brush)
293        self.Pen = pen
294        self.Brush = brush
295        self.setStyle(QwtPlotCurve.Lines)
296        self.setItemAttribute(QwtPlotItem.Legend, 0)
297        self.tooltip = tooltip
298        if xData != None and yData != None:
299            self.setData(xData, yData)
300
301
302class errorBarQwtPlotCurve(QwtPlotCurve):
303    def __init__(self, text = "", connectPoints = 0, tickXw = 0.1, tickYw = 0.1, showVerticalErrorBar = 1, showHorizontalErrorBar = 0):
304        QwtPlotCurve.__init__(self, text)
305        self.connectPoints = connectPoints
306        self.tickXw = tickXw
307        self.tickYw = tickYw
308        self.showVerticalErrorBar = showVerticalErrorBar
309        self.showHorizontalErrorBar = showHorizontalErrorBar
310        self.setItemAttribute(QwtPlotItem.Legend, 0)
311
312    def draw(self, p, xMap, yMap, f, t=-1):
313        # save ex settings
314        pen = p.pen()
315
316        if type(f)==QRect:
317            f = 0
318
319        self.setPen( self.symbol().pen() )
320        p.setPen( self.symbol().pen() )
321        if self.style() == QwtPlotCurve.UserCurve:
322            back = p.backgroundMode()
323
324            p.setBackgroundMode(Qt.OpaqueMode)
325            if t < 0: t = self.dataSize() - 1
326
327            if divmod(f, 3)[1] != 0: f -= f % 3
328            if divmod(t, 3)[1] == 0:  t += 1
329            first = 1
330            for i in range(f, t+1, 3):
331                px = xMap.transform(self.x(i))
332                py = yMap.transform(self.y(i))
333
334                if self.showVerticalErrorBar:
335                    vbxl = xMap.transform(self.x(i) - self.tickXw/2.0)
336                    vbxr = xMap.transform(self.x(i) + self.tickXw/2.0)
337
338                    vbyt = yMap.transform(self.y(i + 1))
339                    vbyb = yMap.transform(self.y(i + 2))
340
341                if self.showHorizontalErrorBar:
342                    hbxl = xMap.transform(self.x(i + 1))
343                    hbxr = xMap.transform(self.x(i + 2))
344
345                    hbyt = yMap.transform(self.y(i) + self.tickYw/2.0)
346                    hbyb = yMap.transform(self.y(i) - self.tickYw/2.0)
347
348                if self.connectPoints:
349                    if first:
350                        first = 0
351                    else:
352                        p.drawLine(ppx, ppy, px, py)
353                    ppx = px
354                    ppy = py
355
356                if self.showVerticalErrorBar:
357                    p.drawLine(px,   vbyt, px,   vbyb)   ## |
358                    p.drawLine(vbxl, vbyt, vbxr, vbyt) ## T
359                    p.drawLine(vbxl, vbyb, vbxr, vbyb) ## _
360
361                if self.showHorizontalErrorBar:
362                    p.drawLine(hbxl, py,   hbxr, py)   ## -
363                    p.drawLine(hbxl, hbyt, hbxl, hbyb) ## |-
364                    p.drawLine(hbxr, hbyt, hbxr, hbyb) ## -|
365
366                self.symbol().draw(p, px, py)
367
368            p.setBackgroundMode(back)
369        else:
370            QwtPlotCurve.draw(self, p, xMap, yMap, f, t)
371
372        # restore ex settings
373        p.setPen(pen)
374
375
376# ####################################################################
377# create a marker in QwtPlot, that doesn't have a transparent background. Currently used in parallel coordinates widget.
378class nonTransparentMarker(QwtPlotMarker):
379    def __init__(self, backColor, *args):
380        QwtPlotMarker.__init__(self, *args)
381        self.backColor = backColor
382
383    def draw(self, p, x, y, rect):
384        p.setPen(self.labelPen())
385        p.setFont(self.font())
386
387        th = p.fontMetrics().height();
388        tw = p.fontMetrics().width(self.label());
389        r = QRect(x + 4, y - th/2 - 2, tw + 4, th + 4)
390        p.fillRect(r, QBrush(self.backColor))
391        p.drawText(r, Qt.AlignHCenter + Qt.AlignVCenter, self.label());
392
393
394
395class RotatedMarker(QwtPlotMarker):
396    def __init__(self, parent, label = "", x = 0.0, y = 0.0, rotation = 0):
397        QwtPlotMarker.__init__(self, parent)
398        self.rotation = rotation
399        self.parent = parent
400        self.x = x
401        self.y = y
402        self.setXValue(x)
403        self.setYValue(y)
404        self.parent = parent
405
406        if rotation != 0: self.setLabel(label + "  ")
407        else:             self.setLabel(label)
408
409    def setRotation(self, rotation):
410        self.rotation = rotation
411
412    def draw(self, painter, x, y, rect):
413        rot = math.radians(self.rotation)
414
415        x2 = x * math.cos(rot) - y * math.sin(rot)
416        y2 = x * math.sin(rot) + y * math.cos(rot)
417
418        painter.rotate(-self.rotation)
419        QwtPlotMarker.draw(self, painter, x2, y2, rect)
420        painter.rotate(self.rotation)
421
422
423# ####################################################################
424# draw labels for discrete attributes
425class DiscreteAxisScaleDraw(QwtScaleDraw):
426    def __init__(self, labels):
427        apply(QwtScaleDraw.__init__, (self,))
428        self.labels = labels
429
430    def label(self, value):
431        index = int(round(value))
432        if index != value: return QwtText("")    # if value not an integer value return ""
433        if index >= len(self.labels) or index < 0: return QwtText("")
434        return QwtText(str(self.labels[index]))
435
436# ####################################################################
437# use this class if you want to hide labels on the axis
438class HiddenScaleDraw(QwtScaleDraw):
439    def __init__(self, *args):
440        QwtScaleDraw.__init__(self, *args)
441
442    def label(self, value):
443        return QwtText()
444
Note: See TracBrowser for help on using the repository browser.