I would like to plot the movement of the mouse in near real-time using matplotlib and pynput, but I suspect I am getting some issues with the code being blocked. Code is uses a simplified version of this answer.
import matplotlib.pyplot as plt
from pynput import mouse
from time import sleep
fig, ax = plt.subplots()
ax.set_xlim(0, 1920-1)
ax.set_ylim(0, 1080-1)
plt.show(False)
plt.draw()
x,y = [0,0]
points = ax.plot(x, y, 'o')[0]
# cache the background
background = fig.canvas.copy_from_bbox(ax.bbox)
def on_move(x, y):
points.set_data(x,y)
# restore background
fig.canvas.restore_region(background)
# redraw just the points
ax.draw_artist(points)
# fill in the axes rectangle
fig.canvas.blit(ax.bbox)
with mouse.Listener(on_move=on_move) as listener:
sleep(10)
The code seems to halt on ax.draw_artist(points). The pynput mouse listener is a threading.Thread, and all callbacks are invoked from the thread. I am not familiar enough with the inner workings of matplotlib or threading to determine what is the cause.
It might cause problems to run a thread with GUI input in parallel with the matplotlib GUI.
In any case, it could make more sense to use matplotlib tools only. There is an event handling mechanism available, which provides a "motion_notify_event" to be used to obtain the current mouse position. A callback registered for this event would then store the mouse position and blit the updated points.
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.set_xlim(0, 1920-1)
ax.set_ylim(0, 1080-1)
x,y = [0], [0]
# create empty plot
points, = ax.plot([], [], 'o')
# cache the background
background = fig.canvas.copy_from_bbox(ax.bbox)
def on_move(event):
# append event's data to lists
x.append(event.xdata)
y.append(event.ydata)
# update plot's data
points.set_data(x,y)
# restore background
fig.canvas.restore_region(background)
# redraw just the points
ax.draw_artist(points)
# fill in the axes rectangle
fig.canvas.blit(ax.bbox)
fig.canvas.mpl_connect("motion_notify_event", on_move)
plt.show()
Related
When i plot something in PyCharm using matplotlib it plots the figure in a seperate window, which is what i want, but it also opens it on the main monitor. Is there a option to open it on my second monitor?
I could not find any similar question (only questions about plotting on the same monitor without a seperate window).
Thanks in advance!
You can specify a position in the code
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
mngr = plt.get_current_fig_manager()
# to put it into the upper left corner for example:
mngr.window.setGeometry(2000,100,640, 545)
plt.show()
I have found a solution in another post How do you set the absolute position of figure windows with matplotlib?
def move_figure(f, x, y):
"""Move figure's upper left corner to pixel (x, y)"""
backend = matplotlib.get_backend()
if backend == 'TkAgg':
f.canvas.manager.window.wm_geometry("+%d+%d" % (x, y))
elif backend == 'WXAgg':
f.canvas.manager.window.SetPosition((x, y))
else:
# This works for QT and GTK
# You can also use window.setGeometry
f.canvas.manager.window.move(x, y)
f, ax = plt.subplots()
move_figure(f, 2200, 500)
plt.show()
I am using matplotlib FuncAnimation to display data received in real time from sensors.
Before updating matplotlib, I was using matplotlib 3.2.2 and the behavior was the following:
graph opens and autoscales axes limits to the data coming in from the sensors
if I interactively zoom in on the graph, the graph remains on the zone I defined (which is convenient to inspect a small portion of the data).
Now, after updating to matplotlib 3.3.4, the graph still opens and autoscales, but if I zoom in on the data, the graph immediately autoscales back to the full extent of the data, which makes inspecting impossible.
I have tried setting the axes autoscaling to False, but then the graph does not autoscale at all, which is not convenient.
I have put below some example code that reproduces the phenomenon described above:
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
from random import random
def test():
"""Test live matplotlib animation"""
fig, (ax1, ax2) = plt.subplots(2, 1)
# for ax in ax1, ax2: # uncomment to deactivate autoscale
# ax.set_autoscale_on(False)
def animate(i):
x = random() # Fake sensor 1
y = 10 * random() + 3 # Fake sensor 2
ax1.plot(i, x, 'ok')
ax2.plot(i, y, 'ob')
ani = FuncAnimation(fig, animate, interval=100)
plt.show()
return ani
Any idea how to get back to the initial behavior while keeping a more recent version of matplotlib? Thank you very much in advance!
The workaround I have found is to use a callback to mouse events on the matplotlib figure, that deactivates autoscaling when there is a left click, and reactivates them with a right click.
def onclick(event):
ax = event.inaxes
if ax is None:
pass
elif event.button == 1: # left click
ax.set_autoscale_on(False)
elif event.button == 3: # right click
ax.set_autoscale_on(True)
else:
pass
cid = fig.canvas.mpl_connect('button_press_event', onclick)
This means that whenever the user uses interactive zooming in some axes of the figure (by left clicking to define the zoom area), autoscaling is deactivated and inspection, panning etc. are possible. The user then just has to right-click to go back to the autoscaled, real-time data.
In fact this is even better than what I had initially in matplotlib 3.2.2, where once zooming was done, there was no way to go back to autoscaled axes limits.
I am trying to automatically update a scatter plot.
The source of my X and Y values is external, and the data is pushed automatically into my code in a non-predicted time intervals (rounds).
I have only managed to plot all the data when the whole process ended, whereas I am trying to constantly add and plot data into my canvas.
What I DO get (at the end of the whole run) is this:
Whereas, what I am after is this:
A simplified version of my code:
import matplotlib.pyplot as plt
def read_data():
#This function gets the values of xAxis and yAxis
xAxis = [some values] #these valuers change in each run
yAxis = [other values] #these valuers change in each run
plt.scatter(xAxis,yAxis, label = 'myPlot', color = 'k', s=50)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
There are several ways to animate a matplotlib plot. In the following let's look at two minimal examples using a scatter plot.
(a) use interactive mode plt.ion()
For an animation to take place we need an event loop. One way of getting the event loop is to use plt.ion() ("interactive on"). One then needs to first draw the figure and can then update the plot in a loop. Inside the loop, we need to draw the canvas and introduce a little pause for the window to process other events (like the mouse interactions etc.). Without this pause the window would freeze. Finally we call plt.waitforbuttonpress() to let the window stay open even after the animation has finished.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
plt.draw()
for i in range(1000):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
fig.canvas.draw_idle()
plt.pause(0.1)
plt.waitforbuttonpress()
(b) using FuncAnimation
Much of the above can be automated using matplotlib.animation.FuncAnimation. The FuncAnimation will take care of the loop and the redrawing and will constantly call a function (in this case animate()) after a given time interval. The animation will only start once plt.show() is called, thereby automatically running in the plot window's event loop.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
def animate(i):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=2, interval=100, repeat=True)
plt.show()
From what I understand, you want to update interactively your plot. If so, you can use plot instead of scatter plot and update the data of your plot like this.
import numpy
import matplotlib.pyplot as plt
fig = plt.figure()
axe = fig.add_subplot(111)
X,Y = [],[]
sp, = axe.plot([],[],label='toto',ms=10,color='k',marker='o',ls='')
fig.show()
for iter in range(5):
X.append(numpy.random.rand())
Y.append(numpy.random.rand())
sp.set_data(X,Y)
axe.set_xlim(min(X),max(X))
axe.set_ylim(min(Y),max(Y))
raw_input('...')
fig.canvas.draw()
If this is the behaviour your are looking for, you just need to create a function appending the data of sp, and get in that function the new points you want to plot (either with I/O management or whatever the communication process you're using).
I hope it helps.
I am trying to automatically update a scatter plot.
The source of my X and Y values is external, and the data is pushed automatically into my code in a non-predicted time intervals (rounds).
I have only managed to plot all the data when the whole process ended, whereas I am trying to constantly add and plot data into my canvas.
What I DO get (at the end of the whole run) is this:
Whereas, what I am after is this:
A simplified version of my code:
import matplotlib.pyplot as plt
def read_data():
#This function gets the values of xAxis and yAxis
xAxis = [some values] #these valuers change in each run
yAxis = [other values] #these valuers change in each run
plt.scatter(xAxis,yAxis, label = 'myPlot', color = 'k', s=50)
plt.xlabel('x')
plt.ylabel('y')
plt.show()
There are several ways to animate a matplotlib plot. In the following let's look at two minimal examples using a scatter plot.
(a) use interactive mode plt.ion()
For an animation to take place we need an event loop. One way of getting the event loop is to use plt.ion() ("interactive on"). One then needs to first draw the figure and can then update the plot in a loop. Inside the loop, we need to draw the canvas and introduce a little pause for the window to process other events (like the mouse interactions etc.). Without this pause the window would freeze. Finally we call plt.waitforbuttonpress() to let the window stay open even after the animation has finished.
import matplotlib.pyplot as plt
import numpy as np
plt.ion()
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
plt.draw()
for i in range(1000):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
fig.canvas.draw_idle()
plt.pause(0.1)
plt.waitforbuttonpress()
(b) using FuncAnimation
Much of the above can be automated using matplotlib.animation.FuncAnimation. The FuncAnimation will take care of the loop and the redrawing and will constantly call a function (in this case animate()) after a given time interval. The animation will only start once plt.show() is called, thereby automatically running in the plot window's event loop.
import matplotlib.pyplot as plt
import matplotlib.animation
import numpy as np
fig, ax = plt.subplots()
x, y = [],[]
sc = ax.scatter(x,y)
plt.xlim(0,10)
plt.ylim(0,10)
def animate(i):
x.append(np.random.rand(1)*10)
y.append(np.random.rand(1)*10)
sc.set_offsets(np.c_[x,y])
ani = matplotlib.animation.FuncAnimation(fig, animate,
frames=2, interval=100, repeat=True)
plt.show()
From what I understand, you want to update interactively your plot. If so, you can use plot instead of scatter plot and update the data of your plot like this.
import numpy
import matplotlib.pyplot as plt
fig = plt.figure()
axe = fig.add_subplot(111)
X,Y = [],[]
sp, = axe.plot([],[],label='toto',ms=10,color='k',marker='o',ls='')
fig.show()
for iter in range(5):
X.append(numpy.random.rand())
Y.append(numpy.random.rand())
sp.set_data(X,Y)
axe.set_xlim(min(X),max(X))
axe.set_ylim(min(Y),max(Y))
raw_input('...')
fig.canvas.draw()
If this is the behaviour your are looking for, you just need to create a function appending the data of sp, and get in that function the new points you want to plot (either with I/O management or whatever the communication process you're using).
I hope it helps.
I am testing matplotlib animation of a plot with random data, I am experiencing the following issues:
The axes ranges xlim, ylim are updated almost randomly, and
when I switch between the program window and other windows.
The annotations are shown only on the frame when xlim and ylim are
updated they disapear the next frames until the plot is updated
again.
Sometimes the default menu of the plot freezes or disapear.
These issues could appear on both linux and windows (slightly differently).
Should I implement threading or eventually multiprocessing? or is it something else?
# -*- coding: utf-8 -*-
import re
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
from mpl_toolkits.axes_grid.anchored_artists import AnchoredText
import random
def init():
line.set_data([], [])
return line,
def animate(i):
y = random.randint(750, 2000)
xdata.append(i)
ydata.append(y)
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
print xmin, xmax
print ymin, ymax
###changing the xmax dynamically
if i >= xmax:
ax.set_xlim(xmin, xmax+(xmax/2))
ax.figure.canvas.draw()
###changing the ymax dynamically
if y >= ymax:
ax.set_ylim(ymin, y+(y/10))
ax.figure.canvas.draw()
#line.set_data(x, y)
line.set_data(xdata, ydata)
if y < 900:
annotation = ax.annotate('Min', xy=(i, y-5))
return line, annotation
#------------------------------------------
#initial max x axis
init_xlim = 5
init_ylim = 2000
fig = plt.figure()
ax = plt.axes(xlim=(0, init_xlim), ylim=(0, init_ylim))
ax.grid()
line, = ax.plot([], [], lw=2)
xdata, ydata = [], []
annotation = ax.annotate('Min', xy=(-1,-1))
annotation.set_animated(True)
anim = animation.FuncAnimation(fig, animate, init_func=init,frames=2000, interval=1000, blit=True)
plt.show()
TL; DR Turn blitting off and everything will 'work', but it might be slow.
You are running into assumptions made in the underlying code using blitting that the only thing changing will be be the stuff in the axes area (ie, not the ticks) and that you will be re-drawing on a fixed background. The way that blitting works is that a copy of the image on the gui canvas is made, each time you update a frame that copy is blitted back into the gui window (the state that is saved is the state at the end of the init function that FuncAnimation takes). The artists returned by your function are then drawn on top of this saved canvas. The region that gets updated this way is the region 'inside' your axes. The tick label are not re-drawn every time because drawing text is expensive.
Hence, your tick labels only update when the system triggers a full-redraw (triggered by changing windows), same with the annotations, they show up because the re-draw draws all artists. They go away again on the next frame because they are not a) on the saved 'base' canvas and b) not included in the list of things to draw returned by your call back function.
If you really must use blitting and add artists each time through you will have to do a bit more work and understand exactly how the animation infrastructure works.