matplotlib sequence of figures in the same window - python

I'm testing an algorithm and I'd like to produce a sequence of figures displaying intermediate results using matplotlib.
I'm not needing animations, nor multiple figures all on the screens, nor subplots.
I'd just like to produce a sequence of figures (possibly using pyplot), and when I'm done, a single window is shown. Then I'd like to navigate in the sequence of figures using the arrows.
How can I do something like that?
I tried to search, but I can only find subplot or multiple figures on the screen.
Thanks

The most general approach is to create a sequence of axes in the same figure, and only display one at a time.
Here's an example of that (The left and right arrow keys control which plot is displayed):
import matplotlib.pyplot as plt
import numpy as np
def main():
x = np.linspace(0, 10, 100)
axes = AxesSequence()
for i, ax in zip(range(3), axes):
ax.plot(x, np.sin(i * x))
ax.set_title('Line {}'.format(i))
for i, ax in zip(range(5), axes):
ax.imshow(np.random.random((10,10)))
ax.set_title('Image {}'.format(i))
axes.show()
class AxesSequence(object):
"""Creates a series of axes in a figure where only one is displayed at any
given time. Which plot is displayed is controlled by the arrow keys."""
def __init__(self):
self.fig = plt.figure()
self.axes = []
self._i = 0 # Currently displayed axes index
self._n = 0 # Last created axes index
self.fig.canvas.mpl_connect('key_press_event', self.on_keypress)
def __iter__(self):
while True:
yield self.new()
def new(self):
# The label needs to be specified so that a new axes will be created
# instead of "add_axes" just returning the original one.
ax = self.fig.add_axes([0.15, 0.1, 0.8, 0.8],
visible=False, label=self._n)
self._n += 1
self.axes.append(ax)
return ax
def on_keypress(self, event):
if event.key == 'right':
self.next_plot()
elif event.key == 'left':
self.prev_plot()
else:
return
self.fig.canvas.draw()
def next_plot(self):
if self._i < len(self.axes):
self.axes[self._i].set_visible(False)
self.axes[self._i+1].set_visible(True)
self._i += 1
def prev_plot(self):
if self._i > 0:
self.axes[self._i].set_visible(False)
self.axes[self._i-1].set_visible(True)
self._i -= 1
def show(self):
self.axes[0].set_visible(True)
plt.show()
if __name__ == '__main__':
main()
If they're all the same type of plot, you could just update the data of the artists involved. This is especially easy if you have the same number of items in each plot. I'll leave out an example for the moment, but if the example above is too memory-hungry, just updating the data of the artists will be considerably lighter.

Related

Pycharm SciView truncate history

I am trying to create a program that can visualize the change of a portfolio in real time. To do this, I update my data and create a new plot with it. When I run the code below in PyCharm, SciView stops displaying the plots after 30 iterations. Ideally, I would like to have it only show the most recent plot, but it would also be fine if it just truncated the history so that I at least always see the current plot. Is there any way to do this? I tried different ways to close the figures (e. g. using plt.close()), but did not achieve the desired result.
Code to reproduce:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def create_plot(self):
"""
Takes an x and a y (both 1D arrays and constructs a plot from it)
:return: a pyplot figure object
"""
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
# Draw x and y lists
ax.clear()
ax.plot(self.x, self.y)
# Format plot
plt.xticks(rotation=90)
plt.title('Portfolio')
plt.ylabel('Value')
plt.show()
plt.close('all')
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.create_plot()
Rather than creating a new plot and window every time, you can also update the current Matplotlib figure data in each iteration. You then need to view the plot in an interactive Matplotlib environment.
Live updating Matplotlib plots
You can use code similar to this to update the data inside the plot:
import matplotlib.pyplot as plt
import random
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
# Variables for our updating data
x = []
y = []
for i in range(50):
# Generate random data
x.append(i)
y.append(random.random())
# Update the plot with the new x, y data
ax.plot(x, y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
Allow for interactive Matplotlib mode when using SciView
Deactivate SciView or manually set your backend to another interactive GUI to see the updating plot.
This code snipped automatically chooses the correct backend (same list as in the Matplotlib code):
import matplotlib.pyplot as plt
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
Applied to your code
Your code with suggested modifications would look like this:
import matplotlib.pyplot as plt
import numpy as np
import random
class RealTimeVisualizer:
def __init__(self, x, y):
self.x = x
self.y = y
def update_data(self, x_value, y_value):
"""
Appends values to the data arrays.
"""
self.x.append(x_value)
self.y.append(y_value)
def update_plot(self, fig, ax):
import _tkinter
try:
ax.plot(self.x, self.y, 'ro-')
fig.canvas.draw()
fig.canvas.flush_events()
# Capture an error in case the plotting window is being closed
except _tkinter.TclError:
pass
if __name__ == '__main__':
portfolio_cash = 10000
tick = 0
real_time_visualizer = RealTimeVisualizer([tick], [portfolio_cash])
# Choose the right backend
candidates = ["macosx", "qt5agg", "gtk3agg", "tkagg", "wxagg"]
for candidate in candidates:
try:
plt.switch_backend(candidate)
print('Using backend: ' + candidate)
break
except (ImportError, ModuleNotFoundError):
pass
# Create plot
plt.ion() # Set pyplot to interactive mode
fig = plt.figure() # Create a figure
ax = fig.add_subplot(111) # Add a subplot to the figure
for i in np.arange(50):
tick += 1
portfolio_cash += random.randint(-50, 50)
real_time_visualizer.update_data(tick, portfolio_cash)
real_time_visualizer.update_plot(fig, ax) # Update the plot the new data
Same issue here.
The workaround I found is to change the matplotlib backend to plot outside the PyCharm.
import matplotlib
matplotlib.use('qt5Agg')
matplotlib.pyplot.ioff()
Then you have to explicit open a new figure and show
for i in range(100):
plt.figure()
...
...
plt.show()

Individual colors for animated 3-D scatter plot in Python

I'm trying to make an animated 3-D scatter plot with the ability to plot a dynamic number of classes as different colors. This is one of the attempts. I've included the whole code in case it is helpful, and marked the trouble spot with a row of stars:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.animation as animation
from random import uniform
x_arr,y_arr,depth_arr,time_arr,ml_arr,cluster_arr = np.loadtxt(data, unpack=5, usecols=(0, 1, 2, 5, 6))
class Point:
def __init__(self,x,y,depth,time,cluster):
self.x=x
self.y=y
self.depth=depth
self.time=time
self.cluster=cluster
points = []
for i in range(0,len(x_arr)):
points.append(Point(x_arr[i],y_arr[i],depth_arr[i],time_arr[i],cluster_arr[i]))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlim(min(x_arr), max(x_arr))
ax.set_ylim(min(y_arr), max(y_arr))
ax.set_zlim(min(depth_arr), max(depth_arr))
colors_1 = plt.cm.jet(np.linspace(0,max(cluster_arr),max(cluster_arr)+1))
colors = colors_1.reshape(-1,4)
def plot_points(time):
x = []
y = []
z = []
clust = []
points_cp = list(np.copy(points))
for i in range(0,(int(max(cluster_arr))+1)):
for event in points_cp:
if event.cluster == i:
if event.time < time:
points_cp.remove(event)
elif event.time <= time + 86400:
x.append(event.x)
y.append(event.y)
z.append(event.depth)
clust.append(event.cluster)
points_cp.remove(event)
# **************************************************************
color_ind = 0
first_ind = 0
last_ind = 0
for i in range(0,len(x)):
if clust[i] != color_ind:
last_ind = i
for i in range(0,len(x)):
ax.scatter(x[first_ind:last_ind],y[first_ind:last_ind],z[first_ind:last_ind],c=colors[int(color_ind)])
color_ind = clust[i]
first_ind = i
time = np.linspace(min(time_arr),max(time_arr),100)
ani = animation.FuncAnimation(fig,plot_points,time)
plt.show()
This gives me a plot with the correct colors, but once a point is plotted, it remains throughout the entire animation.
I have also tried set_x, set_color, etc., but this doesn't work with a loop (it is updated with each iteration, so that only the last class is actually plotted), and I need to use a for loop to accommodate a variable number of classes. I've tried using a colormap with a fixed extent, but have been unsuccessful, as colormapping doesn't work with the plot function, and I haven't been able to get the rest of the code to work with a scatter function.
Thanks in advance for your help, and my apologies if the code is a little wonky. I'm pretty new to this.

Python Scatter Plot Multiple Colorbar issue

I am trying to draw scatter plot with dynamic data. I am able to draw the data points through looping; but everytime it creates new colorbar.
Here is my code:
import time
from threading import Thread
import pandas as pd
import matplotlib.pyplot as plt
import random
class RealTime:
def __init__(self):
self.flight_complete = True
self.data = pd.DataFrame(data=None, columns=list('ABC'))
self.fig=None
self.axis = None
def on_launch(self):
plt.ion()
self.fig = plt.figure()
self.axis = self.fig.add_subplot(111)
def create_data(self):
x = round(random.uniform(-1, 1), 2)
y = round(random.uniform(-1.65, 1.65), 2)
z = 0.5
temp_data = pd.DataFrame([[x, y, z]], columns=list('ABC'))
self.data = self.data.append(temp_data, ignore_index=True)
# Plot the data
self.plot()
def start_fly(self):
self.on_launch()
fly = Thread(target=self.fly_running)
fly.start()
def fly_running(self):
for _ in range(10):
print("Flying")
# Create the data
self.create_data()
time.sleep(0.1)
return
def plot(self):
plt.gca().cla()
self.data.plot(kind="scatter", x="A", y="B", alpha=0.4,
s=50, label="Real Time Position",
c="C", cmap=plt.get_cmap("jet"), colorbar=True, ax=self.axis)
plt.colormaps()
plt.title("Flight Path Map")
self.fig.canvas.draw()
self.fig.canvas.flush_events()
if __name__ == '__main__':
obj = RealTime()
obj.on_launch()
obj.fly_running()
I have read this post : How to retrieve colorbar instance from figure in matplotlib. But I couldn't really work with that.
Do you know why it creates a new colorbar? and how to avoid it?
Best Regards
Panda's plot is creating new colobar because you're asking it to create one (colorbar=True), and it looks like there is now way to tell the function that there is already a colorbar and that it should use that instead.
There are many ways to go around this problem.
the first one would be not not use DataFrame.plot() but instead use matplotlib directly to generate the plot. That will give you more control over the axes that are used and will let you recycle the colorbar from frame to frame. Here are some links that might be relevant:
How do you add a colormap to a matplotlib Animation?
Updating the positions and colors of pyplot.scatter
the second option if you want to keep your code close to what it is now it to erase the whole figure at each frame, and let pandas recreate the axes it need every time. i.e.:
def plot(self):
self.fig.clf()
self.axis = self.fig.add_subplot(111)
self.axis = self.data.plot(kind="scatter", x="A", y="B", alpha=0.4,
s=50, label="Real Time Position",
c="C", cmap=plt.get_cmap("jet"), colorbar=True, ax=self.axis)

Consecutive matplotlib animation in same figure

I have an algorithm which consists of two distinct parts which I want to visualize one after another (while possibly keeping the final state of animation 1 on the screen when animation 2 starts).
I can visualize both parts individually by calling animation.FuncAnimation and plt.show(). Since both parts have set number of frames and their very own behaviour, I would like to keep their implementations apart in two different classes and then do a wrapper around them which plays them in sequence.
However, is it possible to have two (or more) animation objects to be displayed one after another in the same figure?
Many thanks,
Matt
Thanks to the hint of ImportanceOfBeingErnest, I came up with a solution which updates only certain elements of my animator state depending on the current time step. Here is a small example illustrating this approach:
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from math import sin, radians
class AnimationHandler:
def __init__(self, ax):
self.ax = ax
self.lines = [self.ax.plot([], []), self.ax.plot([], [])]
self.colors = ['cyan', 'red']
self.n_steps = [360, 360]
self.step = 0
def init_animation(self):
for anim_idx in [0, 1]:
self.lines[anim_idx], = self.ax.plot([0, 10], [0, 0], c=self.colors[anim_idx], linewidth=2)
self.ax.set_ylim([-2, 2])
self.ax.axis('off')
return tuple(self.lines)
def update_slope(self, step, anim_idx):
self.lines[anim_idx].set_data([0, 10], [0, sin(radians(step))])
def animate(self, step):
# animation 1
if 0 < step < self.n_steps[0]:
self.update_slope(step, anim_idx=0)
# animation 2
if self.n_steps[0] < step < sum(self.n_steps):
self.update_slope(step - self.n_steps[0], anim_idx=1)
return tuple(self.lines)
if __name__ == '__main__':
fig, axes = plt.subplots()
animator = AnimationHandler(ax=axes)
my_animation = animation.FuncAnimation(fig,
animator.animate,
frames=sum(animator.n_steps),
interval=10,
blit=True,
init_func=animator.init_animation,
repeat=False)
Writer = animation.writers['ffmpeg']
writer = Writer(fps=24, metadata=dict(artist='Me'), bitrate=1800)
my_animation.save('./anim_test.mp4', writer=writer)
plt.show()
I used this approach to visualize/debug an algorithm which has different elements with varying runtimes. Approach is the same: You know the number of steps of each subsequence and adjust the state accordingly.

Update continuous function in real time

I can't figure how to plot a continuous function using Matplotlib. I got how to plot a scatterplot, but I would like a continuous plot.
Here is my code:
import matplotlib.pyplot as plt
from matplotlib.pyplot import autoscale
import matplotlib.animation as animation
import numpy
class MyPlot():
def __init__(self):
self.index = 0
self.setup()
def setup(self):
plt.ion()
self.fig, self.ax = plt.subplots()
self.line = self.ax.plot([],[])
autoscale()
plt.show()
def anim(self, i):
self.line.set_ydata(i) # update the data
return self.line,
def add(self, val):
print self.index, val
self.ax.plot(self.index, val)
animation.FuncAnimation(self.fig, self.anim, repeat=False)
plt.pause(0.05)
#if(self.index >= ntests):
self.index+=1
if __name__== "__main__":
import time
from random import random
p = MyPlot()
for i in range(100):
p.add(random())
time.sleep(0.5)
This works, but doesn't draw anything. The plot resizes itself, though.
You are only plotting a line with a single point at a time (which doesn't exist), so nothing shows up. If you replace self.ax.plot with self.ax.scatter, it plots correctly.
If you really want lines, you can just keep track of the last index and value and plot a line connecting the last index and value with the current index and value each time.
Add these two lines to add()
self.ax.plot([self.index-1, self.index], [self.lastval, val])
self.lastval = val
as well as a line initializing self.lastval to numpy.nan in setup()
You can actually append values to a line plot in matplotlib:
self.line.set_xdata(numpy.append(self.line.get_xdata(), self.index))
self.line.set_ydata(numpy.append(self.line.get_ydata(), val))
This way, you do not have to do any of the bookkeeping yourself.
More details can be found at https://stackoverflow.com/a/10944967/2988730

Categories

Resources