Question

I'm trying to create a scatterplot with about 1000 data points in a way so that each data point can be selected by clicking on them using a mouse, which will result in bringing up a context menu which will allow the user to remove or change the color of the data point. I've been following the tutorials for matplotlib, and looking at the Draggable Rectangle Exercise but I'm having a difficult time. I'm using the matplotlib.patches.Circle class to represent each data point, but I cannot get the 'contains' method to work correctly with the 'button_press_event'. Mainly, there seems to be no 'canvas' object associate with each Circle object. I get the following error at line 16:

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

Here's the code:

#!/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_())

I'm not sure if I'm approaching this the right way, or if I should be looking at another graphing module, but I've looked at gnuplot and chaco, and I felt that matplotlib was more fit for my problem. Are there any other recommendations? Thank you very much.

** My Solution **

Here's what I have working so far. It simply outputs the label of each data point to standard output when the points in the scatter plot are clicked on.

#!/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_())
Was it helpful?

Solution

Oops I was creating two instances of patches.Circle for each data point. After passing in the first instance to my SelectablePoint class, everything worked fine.

OTHER TIPS

I was interested to your code but I did get the event with this change in the SelectablePoint. Is this better ? Well, it now clicks entire screen area, not only in the circles. So I miss the point perhaps? Why you created Circle twice ? I riped it off from the SelectablePoint though, but it didn't catch any event before the change of the onClick I present you. Any case why ?

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_())
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top