PyQt4 Scatterplot с точки с възможност за щракване и избор

Опитвам се да създам точкова диаграма с около 1000 точки от данни по начин, така че всяка точка от данни да може да бъде избрана чрез щракване върху тях с помощта на мишката, което ще доведе до извеждане на контекстно меню, което ще позволи на потребителя да премахне или промени цвета на точката с данни. Следвам уроците за matplotlib и разглеждам упражнението за плъзгащ се правоъгълник, но ми е трудно. Използвам класа matplotlib.patches.Circle, за да представя всяка точка от данни, но не мога да накарам метода „contains“ да работи правилно с „button_press_event“. Основно изглежда, че няма обект „платно“, свързан с всеки обект Circle. Получавам следната грешка на ред 16:

AttributeError: 'NoneType' object has no attribute 'canvas'

Ето кода:

#!/usr/bin/python -tt

import sys
import numpy
#import matplotlib.pyplot
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.patches import Circle

class SelectablePoint:
    def __init__(self, xy, label):
        self.point = Circle( (xy[0], xy[0]), .005 )
        self.label = label
        self.point.figure
        self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', self.onClick)

    def onClick(self, e):

        print e.xdata, e.ydata
        #print(dir(self.point))
        print self.point.center
        print self.point.contains(e)[0]
        #if self.point.contains(e)[0]:
        #    print self.label


class ScatterPlot(FigureCanvas):
    '''
    classdocs
    '''

    def __init__(self, parent=None):
        '''
        Constructor
        '''

        self.fig = Figure()
        FigureCanvas.__init__(self, self.fig)

        self.axes = self.fig.add_subplot(111)
        #x = numpy.arange(0.0, 3.0, 0.1)
        #y = numpy.cos(2*numpy.pi*x)
        x = [.5]
        y = [.5]

        #scatterplot = self.axes.scatter(x,y)

        for i in range(len(x)):
            c = Circle( (x[i], y[i]), .05 )
            self.axes.add_patch(c)
            #SelectablePoint( (x[i],y[i]), 'label for: ' + str(i), self.figure.canvas )
            SelectablePoint( (x[i],y[i]), 'label for: ' + str(i) )
            #self.axes.add_artist(c)

class MainContainer(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.resize(900,600)
        self.setWindowTitle('Scatter Plot')

        sp = ScatterPlot(self)
        self.setCentralWidget(sp)

        self.center()

    def center(self):
        # Get the resolution of the screen
        screen = QtGui.QDesktopWidget().screenGeometry()

        # Get the size of widget
        size = self.geometry()
        self.move( (screen.width() - size.width())/2, (screen.height() - size.height())/2 )

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    b = MainContainer()
    b.show()
    sys.exit(app.exec_())

Не съм сигурен дали подхождам към това по правилния начин или трябва да търся друг графичен модул, но разгледах gnuplot и chaco и почувствах, че matplotlib е по-подходящ за моя проблем. Има ли други препоръки? Благодаря ти много.

** Моето решение **

Ето какво съм работил досега. Той просто извежда етикета на всяка точка от данни към стандартен изход, когато се щракне върху точките в диаграмата на разсейване.

#!/usr/bin/python -tt

import sys
import numpy
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.patches import Circle

class SelectablePoint:
    def __init__(self, xy, label, fig):
        self.point = Circle( (xy[0], xy[1]), .25, figure=fig)
        self.label = label
        self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', self.onClick)

    def onClick(self, e):
        if self.point.contains(e)[0]:
            print self.label


class ScatterPlot(FigureCanvas):
    '''
    classdocs
    '''

    def __init__(self, parent=None):
        '''
        Constructor
        '''

        self.fig = Figure()
        FigureCanvas.__init__(self, self.fig)

        self.axes = self.fig.add_subplot(111)
        xlim = [0,7]
        ylim = [0,7]
        self.axes.set_xlim(xlim)
        self.axes.set_ylim(ylim)
        self.axes.set_aspect( 1 )

        x = [1, 1.2, 3, 4, 5, 6]
        y = [1, 1.2, 3, 4, 5, 6]
        labels = ['1', '2', '3', '4', '5', '6']

        for i in range(len(x)):
            sp = SelectablePoint( (x[i],y[i]), labels[i], self.fig)
            self.axes.add_artist(sp.point)

class MainContainer(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.resize(900,600)
        self.setWindowTitle('Scatter Plot')

        sp = ScatterPlot(self)
        self.setCentralWidget(sp)

        self.center()

    def center(self):
        # Get the resolution of the screen
        screen = QtGui.QDesktopWidget().screenGeometry()

        # Get the size of widget
        size = self.geometry()
        self.move( (screen.width() - size.width())/2, (screen.height() - size.height())/2 )

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    b = MainContainer()
    b.show()
    sys.exit(app.exec_())

person jjinking    schedule 20.02.2011    source източник


Отговори (2)


Ами сега, създавах два екземпляра на patches.Circle за всяка точка от данни. След като преминах в първата инстанция към моя клас SelectablePoint, всичко работи добре.

person jjinking    schedule 20.02.2011
comment
Можете също да публикувате правилния код в този отговор. Знаеш ли, за наука :) - person Jeffrey Jose; 20.02.2011
comment
Ето какво имам досега. Все още не съм внедрил контекстното меню и промяната на цветовете и всички тези неща, но този код извежда етикета на всяка точка от данни към стандартен изход, когато щракнете върху точките на диаграмата на разсейване. - person jjinking; 21.02.2011

Интересувах се от вашия код, но получих събитието с тази промяна в SelectablePoint. Така по-добре ли е ? Е, сега щраква върху цялата площ на екрана, не само в кръговете. Така че може би пропускам смисъла? Защо създадохте Circle два пъти? Все пак го изтръгнах от SelectablePoint, но не улови никакво събитие преди промяната на onClick, която ви представям. Всеки случай защо?

import sys
import numpy
from PyQt4 import QtGui, QtCore
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.patches import Circle

class SelectablePoint:
    def __init__(self, xy, label, fig):
        self.point = Circle( (xy[0], xy[1]), .25, figure=fig)
        self.label = label
        self.cidpress = self.point.figure.canvas.mpl_connect('button_press_event', onClick)

def onClick(e):
    print e.__dict__


class ScatterPlot(FigureCanvas):
    '''
    classdocs
    '''

    def __init__(self, parent=None):
        '''
        Constructor
        '''

        self.fig = Figure()
        FigureCanvas.__init__(self, self.fig)

        self.axes = self.fig.add_subplot(111)
        xlim = [0,7]
        ylim = [0,7]
        self.axes.set_xlim(xlim)
        self.axes.set_ylim(ylim)
        self.axes.set_aspect( 1 )

        x = [1, 1.2, 3, 4, 5, 6]
        y = [1, 1.2, 3, 4, 5, 6]
        labels = ['1', '2', '3', '4', '5', '6']

        for i in range(len(x)):
            sp = SelectablePoint( (x[i],y[i]), labels[i], self.fig)
            self.axes.add_artist(sp.point)

class MainContainer(QtGui.QMainWindow):

    def __init__(self):
        QtGui.QMainWindow.__init__(self)
        self.resize(900,600)
        self.setWindowTitle('Scatter Plot')

        sp = ScatterPlot(self)
        self.setCentralWidget(sp)

        self.center()

    def center(self):
        # Get the resolution of the screen
        screen = QtGui.QDesktopWidget().screenGeometry()

        # Get the size of widget
        size = self.geometry()
        self.move( (screen.width() - size.width())/2, (screen.height() - size.height())/2 )

if __name__ == '__main__':
    app = QtGui.QApplication(sys.argv)
    b = MainContainer()
    b.show()
    sys.exit(app.exec_())
person hephestos    schedule 15.04.2011
comment
Здравейте, кодът, който публикувах под „моето решение“, всъщност работи за мен. Разбрах, че създавам два екземпляра на кръг, но поправих това и след това заработи добре. - person jjinking; 16.04.2011
comment
Направихте ли това като промяна може би?: def __init__(self, xy, label, fig) into def __init__(self, c, label)? За да преминете обекта Circle? Ако сте направили това, не работи за мен... python 2.6 и python 2.7 - person hephestos; 16.04.2011
comment
Опитахте ли кода, който публикувах под ** Моето решение ** по-горе? - person jjinking; 23.05.2011