source: orange/orange/OrangeWidgets/OWGraph.py @ 8735:065a34c267f2

Revision 8735:065a34c267f2, 43.5 KB checked in by matejd <matejd@…>, 3 years ago (diff)

Moved over code from qtgraph branch, turned primitives into a module (plot.primitives); added Visualize Qt folder to setup.py packages

Line 
1#
2# owGraph.py
3#
4# the base for all graphs
5
6from PyQt4.Qwt5 import *
7from OWGraphTools import *      # user defined curves, ...
8from OWColorPalette import *      # color palletes, ...
9from OWDlgs import OWChooseImageSizeDlg
10import orange, math, time
11from OWBaseWidget import unisetattr
12
13NOTHING = 0
14ZOOMING = 1
15SELECT_RECTANGLE = 2
16SELECT_POLYGON = 3
17PANNING = 4
18SELECT = 5
19
20class OWGraph(QwtPlot):
21    def __init__(self, parent = None, name = "None", showLegend=1):
22        "Constructs the graph"
23        QwtPlot.__init__(self, parent)
24        self.parentName = name
25        #self.setWindowFlags(Qt.WResizeNoErase) #this works like magic.. no flicker during repaint!
26        self.setAutoReplot(False)
27
28        self.setAxisAutoScale(QwtPlot.xBottom)
29        self.setAxisAutoScale(QwtPlot.xTop)
30        self.setAxisAutoScale(QwtPlot.yLeft)
31        self.setAxisAutoScale(QwtPlot.yRight)
32
33        self.axisTitleFont = QFont('Helvetica', 10, QFont.Bold)
34        text = QwtText("")
35        text.setFont(self.axisTitleFont)
36        self.setAxisTitle(QwtPlot.xBottom, text)
37        self.setAxisTitle(QwtPlot.xTop, text)
38        self.setAxisTitle(QwtPlot.yLeft, text)
39        self.setAxisTitle(QwtPlot.yRight, text)
40
41        ticksFont = QFont('Helvetica', 9)
42        self.setAxisFont(QwtPlot.xBottom, ticksFont)
43        self.setAxisFont(QwtPlot.xTop, ticksFont)
44        self.setAxisFont(QwtPlot.yLeft, ticksFont)
45        self.setAxisFont(QwtPlot.yRight, ticksFont)
46        #self.setLegendFont(ticksFont)
47
48        self.tipLeft = None
49        self.tipRight = None
50        self.tipBottom = None
51        self._cursor = Qt.ArrowCursor
52
53        self.showAxisScale = 1
54        self.showMainTitle = 0
55        self.showXaxisTitle = 0
56        self.showYLaxisTitle = 0
57        self.showYRaxisTitle = 0
58        self.mainTitle = None
59        self.XaxisTitle = None
60        self.YLaxisTitle = None
61        self.YRaxisTitle = None
62        self.useAntialiasing = 1
63
64        self.state = ZOOMING
65        self.tempSelectionCurve = None
66        self.selectionCurveList = []
67        self.autoSendSelectionCallback = None   # callback function to call when we add new selection polygon or rectangle
68        self.sendSelectionOnUpdate = 0
69        self.showLegend = showLegend
70        if self.showLegend:
71            self.insertLegend(QwtLegend(), QwtPlot.BottomLegend)
72
73        self.gridCurve = QwtPlotGrid()
74        #self.gridCurve.attach(self)
75
76        self.mouseCurrentlyPressed = 0
77        self.mouseCurrentButton = 0
78        self.enableWheelZoom = 0
79        self.noneSymbol = QwtSymbol()
80        self.noneSymbol.setStyle(QwtSymbol.NoSymbol)
81        self.tips = TooltipManager(self)
82        self.statusBar = None
83        self.canvas().setMouseTracking(1)
84        self.setMouseTracking(1)
85        self.zoomStack = []
86        self.panPosition = None
87        self.optimizedDrawing = 1
88        self.pointWidth = 5
89        self.showFilledSymbols = 1
90        self.alphaValue = 255
91        self.setCanvasColor(QColor(Qt.white))
92        self.curveSymbols = [QwtSymbol.Ellipse, QwtSymbol.Rect, QwtSymbol.Triangle, QwtSymbol.Diamond, QwtSymbol.DTriangle, QwtSymbol.UTriangle, QwtSymbol.LTriangle, QwtSymbol.RTriangle, QwtSymbol.XCross, QwtSymbol.Cross]
93        #self.curveSymbols = [QwtSymbol.Triangle, QwtSymbol.Ellipse, QwtSymbol.Rect, QwtSymbol.Diamond, QwtSymbol.DTriangle, QwtSymbol.UTriangle, QwtSymbol.LTriangle, QwtSymbol.RTriangle, QwtSymbol.XCross, QwtSymbol.Cross]
94
95        # uncomment this if you want to use printer friendly symbols
96        #self.curveSymbols = [QwtSymbol.Ellipse, QwtSymbol.XCross, QwtSymbol.Triangle, QwtSymbol.Cross, QwtSymbol.Diamond, QwtSymbol.DTriangle, QwtSymbol.Rect, QwtSymbol.UTriangle, QwtSymbol.LTriangle, QwtSymbol.RTriangle]
97        self.contPalette = ColorPaletteGenerator(numberOfColors = -1)
98        self.discPalette = ColorPaletteGenerator()
99
100        # when using OWGraph we can define functions that will receive mouse move, press, release events. these functions
101        # HAVE TO RETURN whether the signal was handled, or you also want to use default OWGraph handler
102        self.mousePressEventHandler = None
103        self.mouseMoveEventHandler = None
104        self.mouseReleaseEventHandler = None
105        self.mouseStaticClickHandler = self.staticMouseClick
106        self.enableGridXB(0)
107        self.enableGridYL(0)
108
109        #self.updateLayout()
110
111    def setCursor(self, cursor):
112        self._cursor = cursor
113        self.canvas().setCursor(cursor)
114
115    def __setattr__(self, name, value):
116        unisetattr(self, name, value, QwtPlot)
117
118    # call to update dictionary with settings
119    def updateSettings(self, **settings):
120        self.__dict__.update(settings)
121
122    def saveToFile(self, extraButtons = []):
123        sizeDlg = OWChooseImageSizeDlg(self, extraButtons, parent=self)
124        sizeDlg.exec_()
125
126    def saveToFileDirect(self, fileName, size = None):
127        sizeDlg = OWChooseImageSizeDlg(self)
128        sizeDlg.saveImage(fileName, size)
129
130
131    def setTickLength(self, axis, minor, medium, major):
132        self.axisScaleDraw(axis).setTickLength(QwtScaleDiv.MinorTick, minor)
133        self.axisScaleDraw(axis).setTickLength(QwtScaleDiv.MediumTick, medium)
134        self.axisScaleDraw(axis).setTickLength(QwtScaleDiv.MajorTick, major)
135
136
137    def setYLlabels(self, labels):
138        "Sets the Y-axis labels on the left."
139        self.axisScaleDraw(QwtPlot.yLeft).enableComponent(QwtScaleDraw.Backbone, self.showAxisScale)
140        self.axisScaleDraw(QwtPlot.yLeft).enableComponent(QwtScaleDraw.Ticks, self.showAxisScale)
141        self.axisScaleDraw(QwtPlot.yLeft).enableComponent(QwtScaleDraw.Labels, self.showAxisScale)
142        if not self.showAxisScale:
143            return
144
145        #self.setTickLength(QwtPlot.yLeft, 1, 1, 3)
146
147        if (labels <> None):
148            self.setAxisScaleDraw(QwtPlot.yLeft, DiscreteAxisScaleDraw(labels))
149            self.setAxisScale(QwtPlot.yLeft, 0, len(labels) - 1, 1)
150            self.setAxisMaxMinor(QwtPlot.yLeft, 0)
151            self.setAxisMaxMajor(QwtPlot.yLeft, len(labels))
152        else:
153            self.setAxisScaleDraw(QwtPlot.yLeft, QwtScaleDraw())
154            self.setAxisAutoScale(QwtPlot.yLeft)
155            self.setAxisMaxMinor(QwtPlot.yLeft, 5)
156            self.setAxisMaxMajor(QwtPlot.yLeft, 8)
157
158    def setYRlabels(self, labels):
159        "Sets the Y-axis labels on the right."
160        self.axisScaleDraw(QwtPlot.yRight).enableComponent(QwtScaleDraw.Backbone, self.showAxisScale)
161        self.axisScaleDraw(QwtPlot.yRight).enableComponent(QwtScaleDraw.Ticks, self.showAxisScale)
162        self.axisScaleDraw(QwtPlot.yRight).enableComponent(QwtScaleDraw.Labels, self.showAxisScale)
163        if not self.showAxisScale:
164            return
165
166        if (labels <> None):
167            self.setAxisScaleDraw(QwtPlot.yRight, DiscreteAxisScaleDraw(labels))
168            self.setAxisScale(QwtPlot.yRight, 0, len(labels) - 1, 1)
169            self.setAxisMaxMinor(QwtPlot.yRight, 0)
170            self.setAxisMaxMajor(QwtPlot.yRight, len(labels))
171        else:
172            self.setAxisScaleDraw(QwtPlot.yRight, QwtScaleDraw())
173            self.setAxisAutoScale(QwtPlot.yRight)
174            self.setAxisMaxMinor(QwtPlot.yRight, 5)
175            self.setAxisMaxMajor(QwtPlot.yRight, 8)
176
177    def setXlabels(self, labels):
178        "Sets the x-axis labels if x-axis discrete."
179        "Or leave up to QwtPlot (MaxMajor, MaxMinor) if x-axis continuous."
180        self.axisScaleDraw(QwtPlot.xBottom).enableComponent(QwtScaleDraw.Backbone, self.showAxisScale)
181        self.axisScaleDraw(QwtPlot.xBottom).enableComponent(QwtScaleDraw.Ticks, self.showAxisScale)
182        self.axisScaleDraw(QwtPlot.xBottom).enableComponent(QwtScaleDraw.Labels, self.showAxisScale)
183        if not self.showAxisScale:
184            return
185
186        if (labels <> None):
187            self.setAxisScaleDraw(QwtPlot.xBottom, DiscreteAxisScaleDraw(labels))
188            self.setAxisScale(QwtPlot.xBottom, 0, len(labels) - 1, 1)
189            self.setAxisMaxMinor(QwtPlot.xBottom, 0)
190            self.setAxisMaxMajor(QwtPlot.xBottom, len(labels))
191        else:
192            self.setAxisScaleDraw(QwtPlot.xBottom, QwtScaleDraw())
193            self.setAxisAutoScale(QwtPlot.xBottom)
194            self.setAxisMaxMinor(QwtPlot.xBottom, 5)
195            self.setAxisMaxMajor(QwtPlot.xBottom, 8)
196
197    def enableXaxis(self, enable):
198        self.enableAxis(QwtPlot.xBottom, enable)
199        self.repaint()
200
201    def enableYLaxis(self, enable):
202        self.enableAxis(QwtPlot.yLeft, enable)
203        self.repaint()
204
205    def enableYRaxis(self, enable):
206        self.enableAxis(QwtPlot.yRight, enable)
207        self.repaint()
208
209    def setRightTip(self,explain):
210        "Sets the tooltip for the right y axis"
211        self.tipRight = explain
212
213    def setLeftTip(self,explain):
214        "Sets the tooltip for the left y axis"
215        self.tipLeft = explain
216
217    def setBottomTip(self,explain):
218        "Sets the tooltip for the left x axis"
219        self.tipBottom = explain
220
221    def setShowMainTitle(self, b):
222        self.showMainTitle = b
223        if self.showMainTitle and self.mainTitle:
224            self.setTitle(self.mainTitle)
225        else:
226            self.setTitle(QwtText())
227        self.repaint()
228
229    def setMainTitle(self, t):
230        self.mainTitle = t
231        if self.showMainTitle and self.mainTitle:
232            self.setTitle(self.mainTitle)
233        else:
234            self.setTitle(QwtText())
235        self.repaint()
236
237    def setShowXaxisTitle(self, b = -1):
238        if b == self.showXaxisTitle: return
239        if b != -1:
240            self.showXaxisTitle = b
241        if self.showXaxisTitle and self.XaxisTitle:
242            self.setAxisTitle(QwtPlot.xBottom, self.XaxisTitle)
243        else:
244            self.setAxisTitle(QwtPlot.xBottom, QwtText())
245        self.repaint()
246
247    def setXaxisTitle(self, title):
248        if title == self.XaxisTitle: return
249        self.XaxisTitle = title
250        if self.showXaxisTitle and self.XaxisTitle:
251            self.setAxisTitle(QwtPlot.xBottom, self.XaxisTitle)
252        else:
253            self.setAxisTitle(QwtPlot.xBottom, QwtText())
254        #self.updateLayout()
255        self.repaint()
256
257    def setShowYLaxisTitle(self, b = -1):
258        if b == self.showYLaxisTitle: return
259        if b != -1:
260            self.showYLaxisTitle = b
261        if self.showYLaxisTitle and self.YLaxisTitle:
262            self.setAxisTitle(QwtPlot.yLeft, self.YLaxisTitle)
263        else:
264            self.setAxisTitle(QwtPlot.yLeft, QwtText())
265        #self.updateLayout()
266        self.repaint()
267
268    def setYLaxisTitle(self, title):
269        if title == self.YLaxisTitle: return
270        self.YLaxisTitle = title
271        if self.showYLaxisTitle and self.YLaxisTitle:
272            self.setAxisTitle(QwtPlot.yLeft, self.YLaxisTitle)
273        else:
274            self.setAxisTitle(QwtPlot.yLeft, QwtText())
275        #self.updateLayout()
276        self.repaint()
277
278    def setShowYRaxisTitle(self, b = -1):
279        if b == self.showYRaxisTitle: return
280        if b != -1:
281            self.showYRaxisTitle = b
282        if self.showYRaxisTitle and self.YRaxisTitle:
283            self.setAxisTitle(QwtPlot.yRight, self.YRaxisTitle)
284        else:
285            self.setAxisTitle(QwtPlot.yRight, QwtText())
286        #self.updateLayout()
287        self.repaint()
288
289    def setYRaxisTitle(self, title):
290        if title == self.YRaxisTitle: return
291        self.YRaxisTitle = title
292        if self.showYRaxisTitle and self.YRaxisTitle:
293            self.setAxisTitle(QwtPlot.yRight, self.YRaxisTitle)
294        else:
295            self.setAxisTitle(QwtPlot.yRight, QwtText())
296        #self.updateLayout()
297        self.repaint()
298
299    def enableGridXB(self, b):
300        self.gridCurve.enableX(b)
301        self.replot()
302
303    def enableGridYL(self, b):
304        self.gridCurve.enableY(b)
305        self.replot()
306
307    def setGridColor(self, c):
308        self.gridCurve.setPen(QPen(c))
309        self.replot()
310
311    def setCanvasColor(self, c):
312        self.setCanvasBackground(c)
313        self.repaint()
314
315    # ############################################################
316    # functions that were previously in OWVisGraph
317    # ############################################################
318    def setData(self, data):
319        # clear all curves, markers, tips
320        self.clear()
321        self.removeAllSelections(0)  # clear all selections
322        self.tips.removeAll()
323        self.zoomStack = []
324
325    # ####################################################################
326    # return string with attribute names and their values for example example
327    def getExampleTooltipText(self, example, indices = None, maxIndices = 20):
328        if indices and type(indices[0]) == str:
329            indices = [self.attributeNameIndex[i] for i in indices]
330        if not indices: 
331            indices = range(len(self.dataDomain.attributes))
332       
333        # don't show the class value twice
334        if example.domain.classVar:
335            classIndex = self.attributeNameIndex[example.domain.classVar.name]
336            while classIndex in indices:
337                indices.remove(classIndex)     
338     
339        text = "<b>Attributes:</b><br>"
340        for index in indices[:maxIndices]:
341            attr = self.attributeNames[index]
342            if attr not in example.domain:  text += "&nbsp;"*4 + "%s = ?<br>" % (attr)
343            elif example[attr].isSpecial(): text += "&nbsp;"*4 + "%s = ?<br>" % (attr)
344            else:                           text += "&nbsp;"*4 + "%s = %s<br>" % (attr, str(example[attr]))
345        if len(indices) > maxIndices:
346            text += "&nbsp;"*4 + " ... <br>"
347
348        if example.domain.classVar:
349            text = text[:-4]
350            text += "<hr><b>Class:</b><br>"
351            if example.getclass().isSpecial(): text += "&nbsp;"*4 + "%s = ?<br>" % (example.domain.classVar.name)
352            else:                              text += "&nbsp;"*4 + "%s = %s<br>" % (example.domain.classVar.name, str(example.getclass()))
353
354        if len(example.domain.getmetas()) != 0:
355            text = text[:-4]
356            text += "<hr><b>Meta attributes:</b><br>"
357            # show values of meta attributes
358            for key in example.domain.getmetas():
359                try: text += "&nbsp;"*4 + "%s = %s<br>" % (example.domain[key].name, str(example[key]))
360                except: pass
361        return text[:-4]        # remove the last <br>
362
363    def addCurve(self, name, brushColor = Qt.black, penColor = Qt.black, size = 5, style = QwtPlotCurve.NoCurve, symbol = QwtSymbol.Ellipse, enableLegend = 0, xData = [], yData = [], showFilledSymbols = None, lineWidth = 1, pen = None, autoScale = 0, antiAlias = None, penAlpha = 255, brushAlpha = 255):
364        curve = QwtPlotCurve(name)
365        curve.setRenderHint(QwtPlotItem.RenderAntialiased, antiAlias or self.useAntialiasing)
366        curve.setItemAttribute(QwtPlotItem.Legend, enableLegend)
367        curve.setItemAttribute(QwtPlotItem.AutoScale, autoScale)
368        if penAlpha != 255:
369            penColor.setAlpha(penAlpha)
370        if brushAlpha != 255:
371            brushColor.setAlpha(brushAlpha)
372
373        if showFilledSymbols or (showFilledSymbols == None and self.showFilledSymbols):
374            newSymbol = QwtSymbol(symbol, QBrush(brushColor), QPen(penColor), QSize(size, size))
375        else:
376            newSymbol = QwtSymbol(symbol, QBrush(), QPen(penColor), QSize(size, size))
377        curve.setSymbol(newSymbol)
378        curve.setStyle(style)
379        curve.setPen(pen != None and pen or QPen(penColor, lineWidth))
380        if xData != [] and yData != []:
381            curve.setData(xData, yData)
382        curve.attach(self)
383        return curve
384
385    def addMarker(self, name, x, y, alignment = -1, bold = 0, color = None, brushColor = None, size=None, antiAlias = None):
386        text = QwtText(name, QwtText.PlainText)
387        if color != None:
388            text.setColor(color)
389            text.setPaintAttribute(QwtText.PaintUsingTextColor, 1)
390        if brushColor != None:
391            text.setBackgroundBrush(QBrush(brushColor))
392        font = text.font()
393        if bold:  font.setBold(1)
394        if size:  font.setPixelSize(size)
395        text.setFont(font)
396        text.setPaintAttribute(QwtText.PaintUsingTextFont, 1)
397        #if alignment != -1:  text.setRenderFlags(alignment)
398
399        marker = QwtPlotMarker()
400        marker.setLabel(text)
401        marker.setValue(x,y)
402        marker.setRenderHint(QwtPlotItem.RenderAntialiased, antiAlias == 1 or self.useAntialiasing)
403        if alignment != -1:
404            marker.setLabelAlignment(alignment)
405        marker.attach(self)
406        return marker
407
408    # show a tooltip at x,y with text. if the mouse will move for more than 2 pixels it will be removed
409    def showTip(self, x, y, text):
410        QToolTip.showText(self.mapToGlobal(QPoint(x, y)), text, self.canvas(), QRect(x-3,y-3,6,6))
411
412    # mouse was only pressed and released on the same spot. visualization methods might want to process this event
413    def staticMouseClick(self, e):
414        return 0
415
416    def activateZooming(self):
417        self.state = ZOOMING
418        if self.tempSelectionCurve: self.removeLastSelection()
419
420    def activateRectangleSelection(self):
421        self.state = SELECT_RECTANGLE
422        if self.tempSelectionCurve: self.removeLastSelection()
423
424    def activatePolygonSelection(self):
425        self.state = SELECT_POLYGON
426        if self.tempSelectionCurve: self.removeLastSelection()
427
428    def activatePanning(self):
429        self.state = PANNING
430        if self.tempSelectionCurve: self.removeLastSelection()
431
432    def activateSelection(self):
433        self.state = SELECT
434
435    def removeDrawingCurves(self, removeLegendItems = 1, removeSelectionCurves = 0, removeMarkers = 0):
436        for curve in self.itemList():
437            if not removeLegendItems and curve.testItemAttribute(QwtPlotItem.Legend):
438                continue
439            if not removeSelectionCurves and isinstance(curve, SelectionCurve):
440                continue
441            if not removeMarkers and isinstance(curve, QwtPlotMarker):
442                continue
443            curve.detach()
444        self.gridCurve.attach(self)        # we also removed the grid curve
445
446    def removeMarkers(self):
447        self.detachItems(QwtPlotItem.Rtti_PlotMarker)
448
449    def removeLastSelection(self):
450        removed = 0
451        if self.selectionCurveList != []:
452            lastCurve = self.selectionCurveList.pop()
453            lastCurve.detach()
454            self.tempSelectionCurve = None
455            removed = 1
456        self.replot()
457        if self.autoSendSelectionCallback:
458            self.autoSendSelectionCallback() # do we want to send new selection
459        return removed
460
461    def removeAllSelections(self, send = 1):
462        selectionsExisted = len(self.selectionCurveList) > 0
463        self.detachItems(SelectionCurveRtti)
464        self.selectionCurveList = []
465        if selectionsExisted:
466            self.replot()
467            if send and self.autoSendSelectionCallback:
468                self.autoSendSelectionCallback() # do we want to send new selection
469
470    def zoomOut(self):
471        if len(self.zoomStack):
472            newXMin, newXMax, newYMin, newYMax = self.zoomStack.pop()
473            self.setNewZoom(newXMin, newXMax, newYMin, newYMax)
474            return 1
475        return 0
476
477    def setNewZoom(self, newXMin, newXMax, newYMin, newYMax):
478        oldXMin = self.axisScaleDiv(QwtPlot.xBottom).interval().minValue()
479        oldXMax = self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue()
480        oldYMin = self.axisScaleDiv(QwtPlot.yLeft).interval().minValue()
481        oldYMax = self.axisScaleDiv(QwtPlot.yLeft).interval().maxValue()
482        stepX, stepY = self.axisStepSize(QwtPlot.xBottom), self.axisStepSize(QwtPlot.yLeft)
483
484        steps = 10
485        for i in range(1, steps+1):
486            midXMin = oldXMin * (steps-i)/float(steps) + newXMin * i/float(steps)
487            midXMax = oldXMax * (steps-i)/float(steps) + newXMax * i/float(steps)
488            midYMin = oldYMin * (steps-i)/float(steps) + newYMin * i/float(steps)
489            midYMax = oldYMax * (steps-i)/float(steps) + newYMax * i/float(steps)
490            self.setAxisScale(QwtPlot.xBottom, midXMin, midXMax, stepX)
491            self.setAxisScale(QwtPlot.yLeft, midYMin, midYMax, stepY)
492            #if i == steps:
493            #    self.removeCurve(zoomOutCurveKey)
494            t = time.time()
495            self.replot()
496            if time.time()-t > 0.1:
497                self.setAxisScale(QwtPlot.xBottom, newXMin, newXMax, stepX)
498                self.setAxisScale(QwtPlot.yLeft, newYMin, newYMax, stepY)
499                self.replot()
500                break
501
502    def closestMarker(self, intX, intY):
503        point = QPoint(intX, intY)
504        marker = None
505        dist = 1e30
506        for curve in self.itemList():
507            if isinstance(curve, QwtPlotMarker):
508                curvePoint = QPoint(self.transform(QwtPlot.xBottom, curve.xValue()), self.transform(QwtPlot.yLeft, curve.yValue()))
509                d = (point - curvePoint).manhattanLength()
510                if d < dist:
511                    dist = d
512                    marker = curve
513        return marker, dist
514
515
516    def closestCurve(self, intX, intY):
517        point = QPoint(intX, intY)
518        nearestCurve = None
519        dist = 10000000000
520        index = -1
521        for curve in self.itemList():
522            if isinstance(curve, QwtPlotCurve) and curve.dataSize() > 0:
523                ind, d = curve.closestPoint(point)
524                if d < dist:
525                    nearestCurve, dist, index = curve, d, ind
526        if nearestCurve == None:
527            return None, 0, 0, 0, 0
528        else:
529            return nearestCurve, dist, nearestCurve.x(index), nearestCurve.y(index), index
530
531
532    # ###############################################
533    # HANDLING MOUSE EVENTS
534    # ###############################################
535    def mousePressEvent(self, e):
536        if self.mousePressEventHandler != None:
537            handled = self.mousePressEventHandler(e)
538            if handled: return
539        QwtPlot.mousePressEvent(self, e)
540        canvasPos = self.canvas().mapFrom(self, e.pos())
541        xFloat = self.invTransform(QwtPlot.xBottom, canvasPos.x())
542        yFloat = self.invTransform(QwtPlot.yLeft, canvasPos.y())
543        self.xpos = canvasPos.x()
544        self.ypos = canvasPos.y()
545
546        self.mouseCurrentlyPressed = 1
547        self.mouseCurrentButton = e.button()
548
549        if self.state not in [ZOOMING, PANNING]:
550            insideRects = [rect.isInside(xFloat, yFloat) for rect in self.selectionCurveList]
551            onEdgeRects = [rect.isOnEdge(xFloat, yFloat) for rect in self.selectionCurveList]
552
553        # ####
554        # ZOOM
555        if e.button() == Qt.LeftButton and self.state == ZOOMING:
556            self.tempSelectionCurve = RectangleSelectionCurve(pen = Qt.DashLine)
557            self.tempSelectionCurve.attach(self)
558
559        # ####
560        # PANNING
561        elif e.button() == Qt.LeftButton and self.state == PANNING:
562            self.panPosition = e.globalX(), e.globalY()
563            self.paniniX = self.axisScaleDiv(QwtPlot.xBottom).interval().minValue(), self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue()
564            self.paniniY = self.axisScaleDiv(QwtPlot.yLeft).interval().minValue(), self.axisScaleDiv(QwtPlot.yLeft).interval().maxValue()
565
566        elif e.button() == Qt.LeftButton and 1 in onEdgeRects and self.tempSelectionCurve == None:
567            self.resizingCurve = self.selectionCurveList[onEdgeRects.index(1)]
568
569        # have we pressed the mouse inside one of the selection curves?
570        elif e.button() == Qt.LeftButton and 1 in insideRects and self.tempSelectionCurve == None:
571            self.movingCurve = self.selectionCurveList[insideRects.index(1)]
572            self.movingCurve.mousePosition = (xFloat, yFloat)
573
574        # ####
575        # SELECT RECTANGLE
576        elif e.button() == Qt.LeftButton and self.state == SELECT_RECTANGLE:
577            self.tempSelectionCurve = RectangleSelectionCurve()
578            self.tempSelectionCurve.attach(self)
579            self.selectionCurveList.append(self.tempSelectionCurve)
580
581        # ####
582        # SELECT POLYGON
583        elif e.button() == Qt.LeftButton and self.state == SELECT_POLYGON:
584            if self.tempSelectionCurve == None:
585                self.tempSelectionCurve = SelectionCurve()
586                self.tempSelectionCurve.attach(self)
587                self.selectionCurveList.append(self.tempSelectionCurve)
588                self.tempSelectionCurve.addPoint(self.invTransform(QwtPlot.xBottom, self.xpos), self.invTransform(QwtPlot.yLeft, self.ypos))
589            self.tempSelectionCurve.addPoint(self.invTransform(QwtPlot.xBottom, self.xpos), self.invTransform(QwtPlot.yLeft, self.ypos))
590
591            if self.tempSelectionCurve.closed():    # did we intersect an existing line. if yes then close the curve and finish appending lines
592                self.tempSelectionCurve = None
593                self.replot()
594                if self.autoSendSelectionCallback: self.autoSendSelectionCallback() # do we want to send new selection
595           
596       
597
598
599    # only needed to show the message in statusbar
600    def mouseMoveEvent(self, e):
601        if self.mouseMoveEventHandler != None:
602            handled = self.mouseMoveEventHandler(e)
603            if handled: return
604        QwtPlot.mouseMoveEvent(self, e)
605        canvasPos = self.canvas().mapFrom(self, e.pos())
606        xFloat = self.invTransform(QwtPlot.xBottom, canvasPos.x())
607        yFloat = self.invTransform(QwtPlot.yLeft, canvasPos.y())
608
609        text = ""
610        if not self.mouseCurrentlyPressed:
611            (text, x, y) = self.tips.maybeTip(xFloat, yFloat)
612            if type(text) == int: text = self.buildTooltip(text)
613
614        if self.statusBar != None:
615            self.statusBar.showMessage(text)
616        if text != "":
617            self.showTip(self.transform(QwtPlot.xBottom, x), self.transform(QwtPlot.yLeft, y), text)
618       
619        if self.tempSelectionCurve != None and (self.state == ZOOMING or self.state == SELECT_RECTANGLE):
620            x1 = self.invTransform(QwtPlot.xBottom, self.xpos)
621            y1 = self.invTransform(QwtPlot.yLeft, self.ypos)
622            self.tempSelectionCurve.setPoints(x1, y1, xFloat, yFloat)
623            self.replot()
624
625        elif self.tempSelectionCurve != None and self.state == SELECT_POLYGON:
626            self.tempSelectionCurve.replaceLastPoint(xFloat,yFloat)
627            self.replot()
628
629        elif hasattr(self, "resizingCurve"):
630            self.resizingCurve.updateCurve(xFloat, yFloat)           
631            self.replot()
632            if self.sendSelectionOnUpdate and self.autoSendSelectionCallback:
633                self.autoSendSelectionCallback()
634           
635        # do we have a selection curve we are currently moving?
636        elif hasattr(self, "movingCurve"):
637            self.movingCurve.moveBy(xFloat-self.movingCurve.mousePosition[0], yFloat-self.movingCurve.mousePosition[1])
638            self.movingCurve.mousePosition = (xFloat, yFloat)
639            self.replot()
640            if self.sendSelectionOnUpdate and self.autoSendSelectionCallback:
641                self.autoSendSelectionCallback() 
642
643        elif self.state == PANNING and self.panPosition:
644            if hasattr(self, "paniniX") and hasattr(self, "paniniY"):
645                dx = self.invTransform(QwtPlot.xBottom, self.panPosition[0]) - self.invTransform(QwtPlot.xBottom, e.globalX())
646                dy = self.invTransform(QwtPlot.yLeft, self.panPosition[1]) - self.invTransform(QwtPlot.yLeft, e.globalY())
647                xEnabled, xMin, xMax = getattr(self, "xPanningInfo", (1, self.paniniX[0] + dx, self.paniniX[1] + dx))
648                yEnabled, yMin, yMax = getattr(self, "yPanningInfo", (1, self.paniniY[0] + dy, self.paniniY[1] + dy))
649
650                if self.paniniX[0] + dx < xMin:  # if we reached the left edge, don't change the right edge
651                    xMax = self.paniniX[1] - (self.paniniX[0] - xMin)
652                elif self.paniniX[1] + dx > xMax:   # if we reached the right edge, don't change the left edge
653                    xMin = self.paniniX[0] + (xMax - self.paniniX[1])
654                else:
655                    xMin, xMax = self.paniniX[0] + dx, self.paniniX[1] + dx
656                if xEnabled: self.setAxisScale(QwtPlot.xBottom, xMin, xMax, self.axisStepSize(QwtPlot.xBottom))
657
658                if self.paniniY[0] + dy < yMin:  # if we reached the left edge, don't change the right edge
659                    yMax = self.paniniY[1] - (self.paniniY[0] - yMin)
660                elif self.paniniY[1] + dy > yMax:   # if we reached the right edge, don't change the left edge
661                    yMin = self.paniniY[0] + (yMax - self.paniniY[1])
662                else:
663                    yMin, yMax = self.paniniY[0] + dy, self.paniniY[1] + dy
664                if yEnabled: self.setAxisScale(QwtPlot.yLeft, yMin, yMax, self.axisStepSize(QwtPlot.yLeft))
665
666                if xEnabled or yEnabled: self.replot()
667
668        # if we are in the selection state then we perhaps show the cursors to move or resize the selection curves
669        if self.state not in [ZOOMING, PANNING] and getattr(self, "resizingCurve", None) == None and self.tempSelectionCurve == None:
670            onEdge = [rect.isOnEdge(xFloat, yFloat) for rect in self.selectionCurveList]
671            if 1 in onEdge:
672                self.canvas().setCursor(self.selectionCurveList[onEdge.index(1)].appropriateCursor)
673            # check if we need to change the cursor if we are at some selection box
674            elif 1 in [rect.isInside(xFloat, yFloat) for rect in self.selectionCurveList]:
675                self.canvas().setCursor(Qt.OpenHandCursor)
676            else:
677                self.canvas().setCursor(self._cursor)
678
679
680
681    def mouseReleaseEvent(self, e):
682        if self.mouseReleaseEventHandler != None:
683            handled = self.mouseReleaseEventHandler(e)
684            if handled: return
685        QwtPlot.mouseReleaseEvent(self, e)
686        if not self.mouseCurrentlyPressed: return   # this might happen if we double clicked the widget titlebar
687        self.mouseCurrentlyPressed = 0
688        self.mouseCurrentButton = 0
689        self.panPosition = None
690        staticClick = 0
691        canvasPos = self.canvas().mapFrom(self, e.pos())
692
693        if hasattr(self, "movingCurve"):
694            del self.movingCurve
695            if self.autoSendSelectionCallback:
696                self.autoSendSelectionCallback() # send the new selection
697               
698        if hasattr(self, "resizingCurve"):
699            del self.resizingCurve
700            if self.autoSendSelectionCallback:
701                self.autoSendSelectionCallback() # send the new selection
702
703
704        if e.button() == Qt.LeftButton:
705            if self.xpos == canvasPos.x() and self.ypos == canvasPos.y():
706                handled = self.mouseStaticClickHandler(e)
707                if handled: return
708                staticClick = 1
709               
710            if self.state == ZOOMING:
711                xmin, xmax = min(self.xpos, canvasPos.x()), max(self.xpos, canvasPos.x())
712                ymin, ymax = min(self.ypos, canvasPos.y()), max(self.ypos, canvasPos.y())
713                if self.tempSelectionCurve:
714                    self.tempSelectionCurve.detach()
715                self.tempSelectionCurve = None
716
717                if staticClick or xmax-xmin < 4 or ymax-ymin < 4:
718                    x = self.invTransform(QwtPlot.xBottom, canvasPos.x())
719                    y = self.invTransform(QwtPlot.yLeft, canvasPos.y())
720                    diffX = (self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue() -  self.axisScaleDiv(QwtPlot.xBottom).interval().minValue()) / 2.
721                    diffY = (self.axisScaleDiv(QwtPlot.yLeft).interval().maxValue() -  self.axisScaleDiv(QwtPlot.yLeft).interval().minValue()) / 2.
722
723                    # use this to zoom to the place where the mouse cursor is
724                    if diffX:
725                        xmin = x - (diffX/2.) * (x - self.axisScaleDiv(QwtPlot.xBottom).interval().minValue()) / diffX
726                        xmax = x + (diffX/2.) * (self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue() - x) / diffX
727                    if diffY:
728                        ymin = y + (diffY/2.) * (self.axisScaleDiv(QwtPlot.yLeft).interval().maxValue() - y) / diffY
729                        ymax = y - (diffY/2.) * (y - self.axisScaleDiv(QwtPlot.yLeft).interval().minValue()) / diffY
730                else:
731                    xmin = self.invTransform(QwtPlot.xBottom, xmin);  xmax = self.invTransform(QwtPlot.xBottom, xmax)
732                    ymin = self.invTransform(QwtPlot.yLeft, ymin);    ymax = self.invTransform(QwtPlot.yLeft, ymax)
733
734                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()))
735                self.setNewZoom(xmin, xmax, ymax, ymin)
736
737            elif self.state == SELECT_RECTANGLE:
738                self.tempSelectionCurve = None
739                if self.autoSendSelectionCallback: self.autoSendSelectionCallback() # do we want to send new selection
740           
741        elif e.button() == Qt.RightButton:
742            if self.state == ZOOMING:
743                ok = self.zoomOut()
744                if not ok:
745                    self.removeLastSelection()
746                    return
747
748            elif self.state == SELECT_RECTANGLE:
749                ok = self.removeLastSelection()      # remove the rectangle
750                if not ok: self.zoomOut()
751
752            elif self.state == SELECT_POLYGON:
753                if self.tempSelectionCurve:
754                    self.tempSelectionCurve.removeLastPoint()
755                    if self.tempSelectionCurve.dataSize() == 0: # remove the temp curve
756                        self.tempSelectionCurve = None
757                        self.removeLastSelection()
758                    else:   # set new last point
759                        self.tempSelectionCurve.replaceLastPoint(self.invTransform(QwtPlot.xBottom, canvasPos.x()), self.invTransform(QwtPlot.yLeft, canvasPos.y()))
760                    self.replot()
761                else:
762                    ok = self.removeLastSelection()
763                    if not ok: self.zoomOut()
764
765
766
767    def wheelEvent(self, e):
768        if not self.enableWheelZoom:
769            return
770
771        d = -e.delta()/120.
772
773        if getattr(self, "controlPressed", False):
774            ys = self.axisScaleDiv(QwtPlot.yLeft)
775            yoff = d * (ys.interval().maxValue() - ys.interval().minValue()) / 100.
776            self.setAxisScale(QwtPlot.yLeft, ys.interval().maxValue() + yoff, ys.interval().maxValue() + yoff, self.axisStepSize(QwtPlot.yLeft))
777
778        elif getattr(self, "altPressed", False):
779            xs = self.axisScaleDiv(QwtPlot.xBottom)
780            xoff = d * (xs.interval().maxValue() - xs.interval().minValue()) / 100.
781            self.setAxisScale(QwtPlot.xBottom, xs.interval().minValue() - xoff, xs.interval().maxValue() - xoff, self.axisStepSize(QwtPlot.xBottom))
782
783        else:
784            ro, rn = .9**d, 1-.9**d
785
786            pos = self.mapFromGlobal(e.pos())
787            ex, ey = pos.x(), pos.y()
788
789            xs = self.axisScaleDiv(QwtPlot.xBottom)
790            x = self.invTransform(QwtPlot.xBottom, ex)
791            self.setAxisScale(QwtPlot.xBottom, ro*xs.interval().minValue() + rn*x, ro*xs.interval().maxValue() + rn*x, self.axisStepSize(QwtPlot.xBottom))
792
793            ys = self.axisScaleDiv(QwtPlot.yLeft)
794            y = self.invTransform(QwtPlot.yLeft, ey)
795            self.setAxisScale(QwtPlot.yLeft, ro*ys.interval().minValue() + rn*y, ro*ys.interval().maxValue() + rn*y, self.axisStepSize(QwtPlot.yLeft))
796
797        self.replot()
798
799
800    # does a point (x,y) lie inside one of the selection rectangles (polygons)
801    def isPointSelected(self, x,y):
802        for curve in self.selectionCurveList:
803            if curve.isInside(x,y): return 1
804        return 0
805
806    # return two lists of 0's and 1's whether each point in (xData, yData) is selected or not
807    def getSelectedPoints(self, xData, yData, validData):
808        import numpy
809        total = numpy.zeros(len(xData))
810        for curve in self.selectionCurveList:
811            total += curve.getSelectedPoints(xData, yData, validData)
812        unselected = numpy.equal(total, 0)
813        selected = 1 - unselected
814        return selected.tolist(), unselected.tolist()
815
816    # save graph in matplotlib python file
817    def saveToMatplotlib(self, fileName, size = QSize(400,400)):
818        f = open(fileName, "wt")
819
820        x1 = self.axisScaleDiv(QwtPlot.xBottom).interval().minValue(); x2 = self.axisScaleDiv(QwtPlot.xBottom).interval().maxValue()
821        y1 = self.axisScaleDiv(QwtPlot.yLeft).interval().minValue();   y2 = self.axisScaleDiv(QwtPlot.yLeft).interval().maxValue()
822
823        if self.showAxisScale == 0: edgeOffset = 0.01
824        else: edgeOffset = 0.08
825
826        f.write("from pylab import *\nfrom matplotlib import font_manager\n\n#possible changes in how the plot looks\n#rcParams['xtick.major.size'] = 0\n#rcParams['ytick.major.size'] = 0\n\n#constants\nx1 = %f; x2 = %f\ny1 = %f; y2 = %f\ndpi = 80\nxsize = %d\nysize = %d\nedgeOffset = %f\n\nfigure(facecolor = 'w', figsize = (xsize/float(dpi), ysize/float(dpi)), dpi = dpi)\nhold(True)\n" % (x1,x2,y1,y2,size.width(), size.height(), edgeOffset))
827
828        linestyles = ["None", "-", "-.", "--", ":", "-", "-"]      # qwt line styles: NoCurve, Lines, Sticks, Steps, Dots, Spline, UserCurve
829        markers = ["None", "o", "s", "^", "d", "v", "^", "<", ">", "x", "+"]    # curveSymbols = [None, Ellipse, Rect, Triangle, Diamond, DTriangle, UTriangle, LTriangle, RTriangle, XCross, Cross]
830
831        f.write("#add curves\n")
832        for c in self.itemList():
833            if not isinstance(c, QwtPlotCurve): continue
834            xData = [c.x(i) for i in range(c.dataSize())]
835            yData = [c.y(i) for i in range(c.dataSize())]
836            marker = markers[c.symbol().style()+1]
837
838            markersize = c.symbol().size().width()
839            markeredgecolor, foo = self._getColorFromObject(c.symbol().pen())
840            markerfacecolor, alphaS = self._getColorFromObject(c.symbol().brush())
841            colorP, alphaP = self._getColorFromObject(c.pen())
842            colorB, alphaB = self._getColorFromObject(c.brush())
843            alpha = min(alphaS, alphaP, alphaB)
844            linewidth = c.pen().width()
845            if c.__class__ == PolygonCurve and len(xData) == 4:
846                x0 = min(xData); x1 = max(xData); diffX = x1-x0
847                y0 = min(yData); y1 = max(yData); diffY = y1-y0
848                f.write("gca().add_patch(Rectangle((%f, %f), %f, %f, edgecolor=%s, facecolor = %s, linewidth = %d, fill = 1, alpha = %.3f))\n" % (x0,y0,diffX, diffY, colorP, colorB, linewidth, alpha))
849            elif c.style() < len(linestyles):
850                linestyle = linestyles[c.style()]
851                f.write("plot(%s, %s, marker = '%s', linestyle = '%s', markersize = %d, markeredgecolor = %s, markerfacecolor = %s, color = %s, linewidth = %d, alpha = %.3f)\n" % (xData, yData, marker, linestyle, markersize, markeredgecolor, markerfacecolor, colorP, linewidth, alpha))
852
853        f.write("\n# add markers\n")
854        for marker in self.itemList():
855            if not isinstance(marker, QwtPlotMarker): continue
856            x = marker.xValue()
857            y = marker.yValue()
858            text = str(marker.label().text())
859            align = marker.labelAlignment()
860            xalign = (align & Qt.AlignLeft and "right") or (align & Qt.AlignHCenter and "center") or (align & Qt.AlignRight and "left")
861            yalign = (align & Qt.AlignBottom and "top") or (align & Qt.AlignTop and "bottom") or (align & Qt.AlignVCenter and "center")
862            vertAlign = (yalign and ", verticalalignment = '%s'" % yalign) or ""
863            horAlign = (xalign and ", horizontalalignment = '%s'" % xalign) or ""
864            labelColor = marker.label().color()
865            color = (labelColor.red()/255., labelColor.green()/255., labelColor.blue()/255.)
866            alpha = labelColor.alpha()/255.
867            name = str(marker.label().font().family())
868            weight = marker.label().font().bold() and "bold" or "normal"
869            if marker.__class__ == RotatedMarker: extra = ", rotation = %f" % (marker.rotation)
870            else: extra = ""
871            f.write("text(%f, %f, '%s'%s%s, color = %s, name = '%s', weight = '%s'%s, alpha = %.3f)\n" % (x, y, text, vertAlign, horAlign, color, name, weight, extra, alpha))
872
873        # grid
874        f.write("# enable grid\ngrid(%s)\n\n" % (self.gridCurve.xEnabled() and self.gridCurve.yEnabled() and "True" or "False"))
875
876        # axis
877        if self.showAxisScale == 0:
878            f.write("#hide axis\naxis('off')\naxis([x1, x2, y1, y2])\ngca().set_position([edgeOffset, edgeOffset, 1 - 2*edgeOffset, 1 - 2*edgeOffset])\n")
879        else:
880            if self.axisScaleDraw(QwtPlot.yLeft).__class__ == DiscreteAxisScaleDraw:
881                labels = self.axisScaleDraw(QwtPlot.yLeft).labels
882                f.write("yticks(%s, %s)\nlabels = gca().get_yticklabels()\nsetp(labels, rotation=-%.3f) #, weight = 'bold', fontsize=10)\n\n" % (range(len(labels)), labels, self.axisScaleDraw(QwtPlot.yLeft).labelRotation()))
883            if self.axisScaleDraw(QwtPlot.xBottom).__class__ == DiscreteAxisScaleDraw:
884                labels = self.axisScaleDraw(QwtPlot.xBottom).labels
885                f.write("xticks(%s, %s)\nlabels = gca().get_xticklabels()\nsetp(labels, rotation=-%.3f) #, weight = 'bold', fontsize=10)\n\n" % (range(len(labels)), labels, self.axisScaleDraw(QwtPlot.xBottom).labelRotation()))
886
887            f.write("#set axis labels\nxlabel('%s', weight = 'bold')\nylabel('%s', weight = 'bold')\n\n" % (str(self.axisTitle(QwtPlot.xBottom).text()), str(self.axisTitle(QwtPlot.yLeft).text())))
888            f.write("\naxis([x1, x2, y1, y2])\ngca().set_position([edgeOffset, edgeOffset, 1 - 2*edgeOffset, 1 - 2*edgeOffset])\n#subplots_adjust(left = 0.08, bottom = 0.11, right = 0.98, top = 0.98)\n")
889
890        f.write("\n# possible settings to change\n#axes().set_frame_on(0) #hide the frame\n#axis('off') #hide the axes and labels on them\n\n")
891
892
893        if self.legend().itemCount() > 0:
894            legendItems = []
895            for widget in self.legend().legendItems():
896                item = self.legend().find(widget)
897                text = str(item.title().text()).replace("<b>", "").replace("</b>", "")
898                if not item.symbol():
899                    legendItems.append((text, None, None, None, None))
900                else:
901                    penC, penA = self._getColorFromObject(item.symbol().pen())
902                    brushC, brushA = self._getColorFromObject(item.symbol().brush())
903                    legendItems.append((text, markers[item.symbol().style()+1], penC, brushC, min(brushA, penA)))
904            f.write("""
905#functions to show legend below the figure
906def drawSomeLegendItems(x, items, itemsPerAxis = 1, yDiff = 0.0):
907    axes([x-0.1, .018*itemsPerAxis - yDiff, .2, .018], frameon = 0); axis('off')
908    lines = [plot([],[], label = text, marker = marker, markeredgecolor = edgeC, markerfacecolor = faceC, alpha = alpha) for (text, marker, edgeC, faceC, alpha) in items]
909    legend(lines, [item[0] for item in items], 'upper center', handlelen = 0.1, numpoints = 1, prop = font_manager.FontProperties(size=11))
910    gca().get_legend().draw_frame(False)
911
912def drawLegend(items):
913    if not items: return
914    maxAttrInLine = 5
915    xs = [i/float(min(maxAttrInLine+1, len(items)+1)) for i in range(1, min(maxAttrInLine+1, len(items)+1))]
916    if items[0][1] == None: extraLabelForClass = [xs.pop(0), [items.pop(0)]]
917    itemsPerAxis = len(items) / len(xs) + (len(items) %% len(xs) != 0)
918    if "extraLabelForClass" in dir(): drawSomeLegendItems(extraLabelForClass[0], extraLabelForClass[1], itemsPerAxis, yDiff = 0.004)
919
920    for i, x in enumerate(xs):
921        drawSomeLegendItems(x, items[i*itemsPerAxis: min(len(items), (i+1)*itemsPerAxis)], itemsPerAxis)
922
923items = %s
924drawLegend(items)\n""" % (str(legendItems)))
925
926        f.write("\nshow()")
927
928
929
930    def _getColorFromObject(self, obj):
931        if isinstance(obj, QBrush) and obj.style() == Qt.NoBrush: return "'none'", 1
932        if isinstance(obj, QPen)   and obj.style() == Qt.NoPen: return "'none'", 1
933        col = [obj.color().red(), obj.color().green(), obj.color().blue()];
934        col = tuple([v/float(255) for v in col])
935        return col, obj.color().alpha()/float(255)
Note: See TracBrowser for help on using the repository browser.