is there any way to relate a click on the Canvas but outside of a plot axes to the closest axes of the click? I have a canvas with x number of subplots and I'm trying to find out the closest subplot near the mouse click. Ultimately, this would help me create a zoom-in-rectangle that allows the user to zoom in the area of selected subplots (the navigation toolbar only zoom in one subplot).
#import os
#os.environ['QT_API'] = 'pyside'
from PyQt4 import QtGui, QtCore
import sys
import matplotlib
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
import numpy as np
class Canvas(FigureCanvasQTAgg):
def __init__(self, parent=None):
self.figure = Figure()
super(Canvas, self).__init__(self.figure)
self.ax1 = self.figure.add_subplot(1,1,1)
self.figure.subplots_adjust(left = 0.05, bottom = 0.02, right = 0.98, top = 0.99)
self.setMinimumWidth(1000)
self.ax1.plot([1,2,3])
self.draw()
def add_subplot(self, data=[]):
rows = len(self.figure.axes) + 1
for index, axes in enumerate(self.figure.axes, start=1):
axes.change_geometry(rows, 1, index)
ax = self.figure.add_subplot(rows, 1, index+1)
x = np.arange(1000,1)
y = np.arange(1000,1)
ax.step(x,y)
ax.patch.set_facecolor('None')
self.figure.set_figheight(self.figure.get_figheight()*self.figScalingfactor)
def figScaling(self, numSubplot):
self.figScalingfactor = round(1.1729*pow(numSubplot, -0.028),3)
class Window(QtGui.QMainWindow):
def __init__(self):
super(Window, self).__init__()
self.showMaximized()
self.widget = QtGui.QWidget()
self.setCentralWidget(self.widget)
self.widget.setLayout(QtGui.QVBoxLayout())
self.widget.layout().setContentsMargins(0,0,0,0)
self.widget.layout().setSpacing(5)
self.canvas = Canvas(self)
self.scroll = QtGui.QScrollArea(self.widget)
self.scroll.setWidget(self.canvas)
self.scroll.setWidgetResizable(False)
self.nav = NavigationToolbar(self.canvas, self.widget)
self.numSubplots = 30
self.canvas.figScaling(self.numSubplots)
for x in range(self.numSubplots):
self.canvas.add_subplot()
self.canvas.adjustSize()
self.canvas.draw_idle()
self.widget.layout().addWidget(self.nav)
self.widget.layout().addWidget(self.scroll)
self.showVline = False
self.hoveringLine = None
self.canvas.mpl_connect("scroll_event", self.scrolling)
self.canvas.mpl_connect("button_press_event", self.onClick)
self.canvas.mpl_connect("motion_notify_event", self.onMove)
def scrolling(self, event):
val = self.scroll.verticalScrollBar().value()
if event.button =="down":
self.scroll.verticalScrollBar().setValue(val+100)
else:
self.scroll.verticalScrollBar().setValue(val-100)
def onClick(self, event):
if event.dblclick and self.showVline == False:
self.background = self.canvas.copy_from_bbox(self.canvas.figure.bbox)
self.showVline = True
self.hoveringLine = self.canvas.ax1.axvline(x=event.xdata, ymin=-1.2*self.numSubplots, ymax=1.2,
lw=2, zorder=0, clip_on=False)
elif event.dblclick and self.showVline:
self.showVline = False
self.hoveringLine = None
self.canvas.ax1.axvline(x=event.xdata, ymin=-1.2*self.numSubplots, ymax=1.2,
lw=2, zorder=0, clip_on=False)
self.canvas.draw()
else:
print(event.xdata)
print(event.ydata)
def onMove(self, event):
if (self.showVline):
self.canvas.restore_region(self.background)
self.hoveringLine.set_xdata(event.xdata)
self.canvas.ax1.draw_artist(self.hoveringLine)
self.canvas.blit(self.canvas.figure.bbox)
def main():
app = QtGui.QApplication(sys.argv)
app.aboutToQuit.connect(app.deleteLater)
GUI = Window()
GUI.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
Related
Hello!
So I got this button Im clicking to plot a figure then I want that figure to update with every mouse buttton push to draw out a cross hair.
But I can't get it to update the figure after the pressing the button but it works great if I just plot outside on the class.
import cross_hair
import numpy as np
import matplotlib.pyplot as plt
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton
import sys
class PushButton(QWidget):
def __init__(self):
super(PushButton,self).__init__()
self.initUI()
def initUI(self):
self.setWindowTitle("PushButton")
self.setGeometry(400,400,300,260)
self.closeButton = QPushButton(self)
self.closeButton.setText("Press") #text
self.closeButton.clicked.connect(self.button_pressed)
def button_pressed(self):
#Fuction variables
self.x= np.arange(0, 1, 0.01)
self.y = np.sin(2 * 2 * np.pi * self.x)
#Figure
self.fig, self.ax = plt.subplots()
#title
self.ax.set_title('Snapping cursor')
#Plotting
self.line, = self.ax.plot(self.x, self.y, 'o')
#
snap_cursor = cross_hair.SnappingCursor(self.ax, self.line)
self.fig.canvas.mpl_connect('button_press_event', snap_cursor.on_mouse_click)
plt.show()
if __name__ == '__main__':
app = QApplication(sys.argv)
ex = PushButton()
ex.show()
sys.exit(app.exec_())
cross hair code looks like this (cross_hair.py)
import numpy as np
class SnappingCursor:
def __init__(self, ax, line):
self.ax = ax
self.horizontal_line = ax.axhline(color='k', lw=0.8, ls='--')
self.vertical_line = ax.axvline(color='k', lw=0.8, ls='--')
self.x, self.y = line.get_data()
self._last_index = None
# text location in axes coords
self.text = ax.text(0.72, 0.9, '', transform=ax.transAxes)
def set_cross_hair_visible(self, visible):
need_redraw = self.horizontal_line.get_visible() != visible
self.horizontal_line.set_visible(visible)
self.vertical_line.set_visible(visible)
self.text.set_visible(visible)
return need_redraw
def on_mouse_click(self, event):
if not event.inaxes:
self._last_index = None
need_redraw = self.set_cross_hair_visible(False)
print('you no pressed' )#, event.button, event.xdata, event.ydata)
if need_redraw:
self.ax.figure.canvas.draw()
else:
self.set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
print('you pressed') # , event.button, event.xdata, event.ydata)
index = min(np.searchsorted(self.x, x), len(self.x) - 1)
if index == self._last_index:
return # still on the same data point. Nothing to do.
self._last_index = index
x = self.x[index]
y = self.y[index]
# update the line positions
self.horizontal_line.set_ydata(y)
self.vertical_line.set_xdata(x)
self.text.set_text('x=%1.2f, y=%1.2f' % (x, y))
self.ax.figure.canvas.draw()
Solved
So I tried to solve this for some time and now did it with the easiest solution... I think?
added a function update_plot() in the class PushButton with the following
def update_plot(self):
print('Testing')
self.fig.canvas.mpl_connect('button_press_event', self.snap_cursor.on_mouse_click)
And in def button_pressed added
self.update_plot()
plt.show()
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.update_plot)
self.timer.start(100)
In def button_pressed
and ofc also used
from PyQt5 import QtCore
My ui file contains a widget container with a vertical layout named "VL" and a line edit named "Radiance". I created a single bar graph that I want to change as I input values into the line edit. At the moment, it does just that, except it creates a new plot every time. If I use my "remove" function it doesn't make a whole separate plot, but it ruins the layout of the one. I think the problem lies with my "remove" function and where to put it, please help.
I imported QtWidgets, uic, matplot.figure, and necessary backends:
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
uic.loadUi('PyQt_App1.ui', self)
self.setWindowTitle("Window Title")
self.Radiance.textChanged.connect(self.animate)
def animate(self):
self.remove()
r = self.Radiance.text()
if r:
rad = float(r)
positions = [0.25]
fig1 = Figure()
ax1f1 = fig1.add_subplot(111)
ax1f1.set_ylim([0, 100])
ax1f1.set_xlim([0, 0.5])
ax1f1.bar(positions, rad, width=0.2, color="g")
self.addmpl(fig1)
else:
r = 0
rad = float(r)
positions = [0.25]
fig1 = Figure()
ax1f1 = fig1.add_subplot(111)
ax1f1.set_ylim([0, 100])
ax1f1.set_xlim([0, 0.5])
ax1f1.bar(positions, rad, width=0.2, color="g")
self.addmpl(fig1)
def addmpl(self, fig):
self.canvas = FigureCanvas(fig)
self.VL.addWidget(self.canvas)
# self.canvas.setParent(self.Frame)
self.canvas.draw()
def remove(self):
self.VL.removeWidget(self.canvas)
self.canvas.close()
if __name__ == '__main__':
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
main = MyWindow()
main.show()
sys.exit(app.exec_())
Instead of creating a new figure every time I would just keep references to the current bar plot and the current axes, and use those to update the figure, e.g.
from PyQt5 import QtWidgets
from matplotlib.backends.backend_qt5agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.pyplot import Figure
class MyWindow(QtWidgets.QMainWindow):
def __init__(self):
super(MyWindow, self).__init__()
central = QtWidgets.QWidget(self)
self.VL = QtWidgets.QVBoxLayout(central)
self.Radiance = QtWidgets.QLineEdit(self)
self.VL.addWidget(self.Radiance)
self.canvas = FigureCanvas(Figure())
self.VL.addWidget(self.canvas)
self.ax1f1 = self.canvas.figure.subplots()
self.ax1f1.set_ylim([0, 100])
self.ax1f1.set_xlim([0, 0.5])
self.bar = None
self.setWindowTitle("Window Title")
self.setCentralWidget(central)
self.Radiance.textChanged.connect(self.animate)
def animate(self):
r = self.Radiance.text()
try:
rad = float(r)
except ValueError:
rad = 0
positions = [0.25]
if self.bar:
self.bar.remove()
self.bar = self.ax1f1.bar(positions, rad, width=0.2, color="g")
self.canvas.draw()
if __name__ == '__main__':
import sys
from PyQt5 import QtWidgets
app = QtWidgets.QApplication(sys.argv)
main = MyWindow()
main.show()
sys.exit(app.exec_())
I am trying to write a GUI in Python3 using PyQt4.
For data visualization, I need to isolate a specific point on a curve plotted by the function whole_plot(). To do so, I am currently using a slider that let the GUI user choose a point of interest. When the slider_value is changed, the point is selected and plotted by calling the function point_plot().
Regarding some previous answers, I am now trying to update my graph through matplotlib.animation (cf. post python matplotlib update scatter plot from a function). But for some reasons, I still got the wrong updating, can someone help me figure out what is the problem in my code?
import sys
import numpy as np
from PyQt4 import QtGui
from PyQt4 import QtCore
import matplotlib.pyplot as plt
import matplotlib.animation
#
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
#%%
# some Arbitrary data
nbr_points = 500
my_X_data = np.linspace(-10,10,nbr_points)
my_Y_data = my_X_data**3 + 100*np.cos(my_X_data*5)
class MyWidget(QtGui.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(600,300,1000,600)
grid = QtGui.QGridLayout()
self.setLayout(grid)
self.figure_1 = plt.figure(figsize=(15,5))
self.canvas_1 = FigureCanvas(self.figure_1)
self.toolbar = NavigationToolbar(self.canvas_1, self)
grid.addWidget(self.canvas_1, 2,0,1,2)
grid.addWidget(self.toolbar, 0,0,1,2)
# Slider
self.slider = QtGui.QSlider(QtCore.Qt.Horizontal)
self.slider.setMinimum(0)
self.slider.setMaximum(nbr_points-1)
self.slider.setTickInterval(1)
self.slider.valueChanged.connect(self.point_plot)
# Slider info box
self.label = QtGui.QLabel(self)
grid.addWidget(self.label,4,0)
# +1 / -1 buttons
btn_plus_one = QtGui.QPushButton('+1', self)
btn_plus_one.clicked.connect(self.value_plus_one)
btn_minus_one = QtGui.QPushButton('-1', self)
btn_minus_one.clicked.connect(self.value_minus_one)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(btn_minus_one)
hbox.addWidget(self.slider)
hbox.addWidget(btn_plus_one)
grid.addLayout(hbox, 3,0,1,3)
self.whole_plot()
self.point_plot()
self.show()
def whole_plot(self):
ax1 = self.figure_1.add_subplot(111)
ax1.clear()
ax1.cla()
#
ax1.plot(my_X_data,my_Y_data,'b-')
#
ax1.set_xlim([-10,10])
ax1.set_ylim([-1000,1000])
ax1.set_xlabel('X')
ax1.set_ylabel('Y')
#
self.canvas_1.draw()
def point_plot(self):
ax1 = self.figure_1.add_subplot(111)
#
X_point, Y_point = [],[]
scat = ax1.scatter(X_point,Y_point, s=100,c='k')
def animate(i):
index_slider_value = self.slider.value()
X_point = my_X_data[index_slider_value,]
Y_point = my_Y_data[index_slider_value,]
scat.set_offsets(np.c_[X_point,Y_point])
anim = matplotlib.animation.FuncAnimation(self.figure_1,animate, frames=my_X_data, interval=200, repeat=True)
self.canvas_1.draw()
def value_plus_one(self):
# slider +1
if self.slider.value() < (my_X_data.shape[0]-1):
index_slider_value = self.slider.value() + 1
self.slider.setValue(index_slider_value)
def value_minus_one(self):
# slider -1
if self.slider.value() > 0:
index_slider_value = self.slider.value() - 1
self.slider.setValue(index_slider_value)
def main():
app = QtGui.QApplication(sys.argv)
MyWidget()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
You have to learn what is better to reuse than create, in this case you just need to create a scatter and update the data with set_offsets() when the value of the slider changes, so FuncAnimation is not necessary.
import numpy as np
from PyQt4 import QtCore, QtGui
import matplotlib.pyplot as plt
import matplotlib.animation
#
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.backends.backend_qt4agg import NavigationToolbar2QT as NavigationToolbar
#%%
# some Arbitrary data
nbr_points = 500
my_X_data = np.linspace(-10,10,nbr_points)
my_Y_data = my_X_data**3 + 100*np.cos(my_X_data*5)
class MyWidget(QtGui.QWidget):
def __init__(self):
super(MyWidget, self).__init__()
self.initUI()
def initUI(self):
self.setGeometry(600,300,1000,600)
self.figure_1 = plt.figure(figsize=(15,5))
self.canvas = FigureCanvas(self.figure_1)
self.toolbar = NavigationToolbar(self.canvas, self)
# Slider
self.slider = QtGui.QSlider(minimum=0,
maximum= nbr_points-1,
orientation=QtCore.Qt.Horizontal,
tickInterval=1)
self.slider.valueChanged.connect(self.on_valueChanged)
# Slider info box
self.label = QtGui.QLabel()
# +1 / -1 buttons
btn_plus_one = QtGui.QPushButton('+1')
btn_plus_one.clicked.connect(self.value_plus_one)
btn_minus_one = QtGui.QPushButton('-1')
btn_minus_one.clicked.connect(self.value_minus_one)
grid = QtGui.QGridLayout(self)
grid.addWidget(self.canvas, 2, 0, 1, 2)
grid.addWidget(self.toolbar, 0, 0, 1, 2)
grid.addWidget(self.label, 4, 0)
hbox = QtGui.QHBoxLayout()
hbox.addWidget(btn_minus_one)
hbox.addWidget(self.slider)
hbox.addWidget(btn_plus_one)
grid.addLayout(hbox, 3, 0, 1, 3)
self.whole_plot()
def whole_plot(self):
ax = self.figure_1.add_subplot(111)
ax.clear()
ax.plot(my_X_data,my_Y_data,'b-')
ax.set_xlim([-10,10])
ax.set_ylim([-1000,1000])
ax.set_xlabel('X')
ax.set_ylabel('Y')
self.canvas.draw()
X_point, Y_point = [],[]
self.scat = ax.scatter(X_point, Y_point, s=100,c='k')
# set initial
self.on_valueChanged(self.slider.value())
#QtCore.pyqtSlot(int)
def on_valueChanged(self, value):
X_point = my_X_data[value,]
Y_point = my_Y_data[value,]
self.scat.set_offsets(np.c_[X_point,Y_point])
self.canvas.draw()
#QtCore.pyqtSlot()
def value_plus_one(self):
self.slider.setValue(self.slider.value() + 1)
#QtCore.pyqtSlot()
def value_minus_one(self):
self.slider.setValue(self.slider.value() - 1)
def main():
import sys
app = QtGui.QApplication(sys.argv)
w = MyWidget()
w.show()
sys.exit(app.exec_())
if __name__ == '__main__':
main()
On the other hand QSlider will not update the value if it is less than minimum or greater than maximum
I'm trying to make a program in which I have a main window and a second window. The second window should be opened by checking a Check-Box in the main window and closed by unchecking it.
The following minimal example works already fine (thanks to ImportanceOfBeingErnest !), but I want to spin the arrow (the one, which is already bent when you run the example) by changing the SpinBox in the main window.
Solution: See 5th comment in the first answer!
import sys
from PyQt4 import QtGui, QtCore
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import animation
import numpy as np
class Newsphere(QtGui.QMainWindow):
def __init__(self):
super(Newsphere, self).__init__()
self.mainbox = QtGui.QWidget()
self.mainbox.setLayout(QtGui.QHBoxLayout())
self.setCentralWidget(self.mainbox)
self.spin = QtGui.QSpinBox()
self.spin.setValue(20)
self.spin.setMaximum(100)
self.spin.setMinimum(-100)
self.checkPlot = QtGui.QCheckBox("Check")
self.mainbox.layout().addWidget(self.spin)
self.mainbox.layout().addWidget(self.checkPlot)
self.Plot = None
self.checkPlot.clicked.connect(self.showPlot)
def showPlot(self):
if self.Plot == None:
self.Plot = Plot(self.kinematic())
self.Plot.show()
# register signal for closure
self.Plot.signalClose.connect(self.uncheck)
# register signal for spin value changed
self.spin.valueChanged.connect(self.kinematic)
else:
self.Plot.close()
self.Plot = None
def kinematic(self):
x = self.spin.value() / 100
v = np.matrix([[1.,x,0.],[0.,1.,0.],[0.,0.,1.]])
zero = np.matrix([[0.,0.,0.],[0.,0.,0.],[0.,0.,0.]])
pos = np.hstack([v, zero])
return pos
def uncheck(self):
self.checkPlot.setChecked(False)
self.Plot = None
class Plot(QtGui.QWidget):
signalClose = QtCore.pyqtSignal()
def __init__(self, pos=None):
super(Plot, self).__init__()
self.setLayout(QtGui.QHBoxLayout())
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111,projection = '3d')
self.fig.tight_layout()
self.ax.view_init(40, 225)
''' dashed coordinate system '''
self.ax.plot([0,1], [0,0], [0,0], label='$X_0$', linestyle="dashed", color="red")
self.ax.plot([0,0], [0,-10], [0,0], label='$Y_0$', linestyle="dashed", color="green")
self.ax.plot([0,0], [0,0], [0,1], label='$Z_0$', linestyle="dashed", color="blue")
self.ax.set_xlim3d(-3,3)
self.ax.set_ylim3d(-3,3)
self.ax.set_zlim3d(-3,3)
self.canvas = FigureCanvas(self.fig)
self.layout().addWidget(self.canvas)
self.pos = pos
self.setup_plot()
self.ani = animation.FuncAnimation(self.fig, self.update_plot, init_func=self.setup_plot, blit=True)
def setup_plot(self):
self.ax.legend(loc='best')
self.position = self.ax.quiver(0, 0, 0, 0, 0, 0, pivot="tail", color="black")
return self.position,
def update_plot(self, i):
x_zero = self.pos[:,3]
y_zero = self.pos[:,4]
z_zero = self.pos[:,5]
v_x = self.pos[0,0:3]
v_y = self.pos[1,0:3]
v_z = self.pos[2,0:3]
self.position = self.ax.quiver(-x_zero, -y_zero, z_zero, -v_x[0,:], v_y[0,:], v_z[0,:], pivot="tail", color="black")
self.canvas.draw()
return self.position,
# We need to make sure the animation stops, when the window is closed
def closeEvent(self, event):
self.signalClose.emit()
self.close()
super(Plot, self).closeEvent(event)
def close(self):
self.ani.event_source.stop()
super(Plot, self).close()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Newsphere()
main.show()
sys.exit(app.exec_())
Here is an working example of what I think you are trying to achieve.
The Main Window has a spin box and a check box. Once the checkbox is clicked, a new window with a plot will show up and an animation will start. The current value and some array will be given to the plot window. If you change the spin box value while the animation is running, it will be updated. When the plot window is closed or when the checkbox is unchecked, the animation will stop (and be deleted).
import sys
from PyQt4 import QtGui, QtCore
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.backends.backend_qt4agg import FigureCanvasQTAgg as FigureCanvas
from matplotlib import animation
import numpy as np
class Newsphere(QtGui.QMainWindow):
def __init__(self):
super(Newsphere, self).__init__()
self.mainbox = QtGui.QWidget()
self.mainbox.setLayout(QtGui.QHBoxLayout())
self.setCentralWidget(self.mainbox)
self.spin = QtGui.QSpinBox()
self.spin.setValue(5)
self.spin.setMaximum(10)
self.spin.setMinimum(1)
self.checkPlot = QtGui.QCheckBox("Check")
self.mainbox.layout().addWidget(self.spin)
self.mainbox.layout().addWidget(self.checkPlot)
self.Plot = None
self.checkPlot.clicked.connect(self.showPlot)
def showPlot(self):
if self.Plot == None:
self.Plot = Plot(self.kinematic(), self.spin.value())
self.Plot.show()
# register signal for closure
self.Plot.signalClose.connect(self.uncheck)
# register signal for spin value changed
self.spin.valueChanged.connect(self.Plot.update_factor)
else:
self.Plot.close()
self.Plot = None
def kinematic(self):
v = np.array([[1.,2.,3.],[2.,1.,3.],[3.,2.,1.]])
return v
def uncheck(self):
self.checkPlot.setChecked(False)
self.Plot = None
class Plot(QtGui.QWidget):
signalClose = QtCore.pyqtSignal()
def __init__(self, v=None, factor=1):
super(Plot, self).__init__()
self.setLayout(QtGui.QHBoxLayout())
self.fig = plt.figure()
self.ax = self.fig.add_subplot(111,projection = '3d')
self.ax.set_aspect('equal')
self.fig.tight_layout()
self.ax.view_init(40, 225)
self.ax.set_xlim3d(0,3)
self.ax.set_ylim3d(0,3)
self.ax.set_zlim3d(0,4)
self.canvas = FigureCanvas(self.fig)
self.layout().addWidget(self.canvas)
self.pos = v
self.setup_plot()
self.update_factor(factor)
self.ani = animation.FuncAnimation(self.fig, self.update_plot, blit=False)
def setup_plot(self):
xpos, ypos = np.meshgrid(np.arange(self.pos.shape[0]),np.arange(self.pos.shape[1]) )
self.xpos = xpos.flatten('F')
self.ypos = ypos.flatten('F')
self.zpos = np.zeros_like(self.xpos)
self.bar = None
def update_factor(self, factor):
self.factor = factor
self.dx = np.ones_like(self.xpos)*np.min(np.abs(self.factor/10.), 0.1)
self.dy = self.dx.copy()
def update_plot(self, i):
if self.bar != None:
self.bar.remove()
del self.bar
pos = self.pos+np.sin(i/8.)
dz = pos.flatten()
self.bar = self.ax.bar3d(self.xpos, self.ypos, self.zpos, self.dx, self.dy, dz,
color=(1.-self.factor/10.,0,self.factor/10.), zsort='average', linewidth=0)
self.canvas.draw()
# We need to make sure the animation stops, when the window is closed
def closeEvent(self, event):
self.signalClose.emit()
self.close()
super(Plot, self).closeEvent(event)
def close(self):
self.ani.event_source.stop()
super(Plot, self).close()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
main = Newsphere()
main.show()
sys.exit(app.exec_())
Since I wasn't sure about what you want to animate, I changed the plot to a barplot, but you can change it back to whatever you need. Hope that helps.
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_())
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.
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_())