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)
Related
I am trying to plot a random walk in two ways:
static using plot method from matplotlib.pyplot,
animated using FuncAnimation from matplotlib.animation.
The first one works pretty well, you can see an example of a really simple Brownian motion below.
I tried to implement the code which would be capable of animating the random walk however I struggle with colors and the alpha parameter.
First of all the alpha parameter does not work. After successive iterations, the points overlap losing their transparency. In addition, the colors of the individual points change in each iteration, making the animation very unreadable.
You can see one animation below. The paths are the same as in the figure above.
The code I used to create the animation.
import os
from datetime import datetime
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.animation import PillowWriter
class Plotter:
def __init__(self, paths, link, annotate, grid, save, alpha, cmap):
self._paths = paths
self._link = link
self._annotate = annotate
self._alpha = alpha
self._cmap = cmap
self._fig, self._ax = plt.subplots()
self._animation = None
if grid:
self._ax.grid()
self._plot()
if save:
self._check_path()
self._filename = self._create_filename()
self._save()
def _plot(self):
self._animation = FuncAnimation(
self._fig, self._anim, interval=500, frames=len(self._paths[0][0]), repeat=False,
)
def _anim(self, frame):
self._ax.set_title(f'Frame: {frame}')
for path in self._paths:
x, y = path
if self._link:
self._ax.plot(x[:frame+1], y[:frame+1], linestyle='--', marker='o', alpha=self._alpha)
else:
self._ax.scatter(x[:frame+1], y[:frame+1], alpha=self._alpha, cmap=self._cmap)
def _save(self):
self._animation.save(self._filename, dpi=300, writer=PillowWriter(fps=25))
#staticmethod
def _check_path():
if not os.path.exists('figures/'):
os.mkdir('figures/')
#staticmethod
def _create_filename():
filename = 'figures/{name}.gif'
name = datetime.now().strftime("%d-%m-%Y %H:%M:%S")
return filename.format(name=name)
And that's how I created the .gif.
paths = [
(
[0, -0.6655408436280401, 0.10568259609257569, -0.26149279371869266, -0.5009666521908381, 0.3639625899869371, -0.49976435812228837, -0.48682212081220344, -1.2885433457534117, -0.9850803395821125, -0.018343580936876158],
[0, -0.7463614308516193, -1.3829259649135648, -0.45277423905538894, 0.5181285733354615, 1.0200223928090424, 1.523982469709905, 2.5238987154492216, 3.1215968645836893, 4.074440085625576, 4.330213499967454]),
(
[0, 0.06636040923368702, 0.05255193650063124, 0.597356910675901, -0.2793019906386772, -0.894506810533089, 0.06788127670582911, -0.3820663494179474, -0.3801418070740879, 0.6063698370900061, -0.3466678114238062],
[0, 0.9977957186149565, 1.9977003771103456, 1.1591375946582005, 1.6402500251210983, 2.4286173440649295, 2.700295774457584, 3.5933507188734235, 2.593352570806755, 2.4296614716942626, 2.732513317512087]
)
]
Plotter(paths=paths, link=True, annotate=False, grid=True, save=True, alpha=0.5, cmap='Pastel1')
Is it possible to set fixed colours for individual points and a fixed value for the alpha parameter?
A way to approach this is by creating two empty plots (one for each path) in your init function when and update the plots at each frame. You can fix the values for alpha and color while doing that.
Following that, it's also a good idea to fix the limits of your plot to prevent the plot from jumping from frame to frame.
At each frame, you can then update your plots with the values from your paths variable by using the set_data function (see doc here).
See the code you provided with the edit I suggested:
import os
from datetime import datetime
from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
from matplotlib.animation import PillowWriter
import numpy as np
class Plotter:
def __init__(self, paths, link, annotate, grid, save, alpha, cmap):
self._paths = paths
self._link = link
self._annotate = annotate
self._alpha = alpha
self._cmap = cmap
self._fig, self._ax = plt.subplots()
self._animation = None
#Create a list of two empty placeholder plots and initialize alpha and color
self._lns=[self._ax.plot([],[], linestyle='--', marker='o',alpha=self._alpha,color='tab:blue'),self._ax.plot([],[], linestyle='--', marker='o',alpha=self._alpha,color='tab:orange')]
self._ax.set_xlim([-2,2]) #set xlim
self._ax.set_ylim([-2,5]) #set ylim
if grid:
self._ax.grid()
self._plot()
if save:
self._check_path()
self._filename = self._create_filename()
self._save()
def _plot(self):
self._animation = FuncAnimation(self._fig, self._anim, interval=500, frames=len(self._paths[0][0]), repeat=True)
def _anim(self, frame):
self._ax.set_title(f'Frame: {frame}')
for i,path in enumerate(self._paths):
x, y = path
if self._link:
self._lns[i][0].set_data(x[:frame+1], y[:frame+1]) #update values of the plots
def _save(self):
self._animation.save(self._filename, dpi=300, writer=PillowWriter(fps=2))
#staticmethod
def _check_path():
if not os.path.exists('figures/'):
os.mkdir('figures/')
#staticmethod
def _create_filename():
filename = 'figures/{name}.gif'
name = datetime.now().strftime("%d-%m-%Y %H:%M:%S")
return filename.format(name=name)
And the output gives:
My task is to plot a numpy array in real time using matplotlib. Please note that I don't want to use animation function to do this.
import numpy as np
import time
from matplotlib.lines import Line2D
import matplotlib
class Plot:
def __init__(self,f,axis,data):
self.fig = f
self.axis = axis
self.data = data
def plotting(self,i):
xs = [self.data[i,0],self.data[i+1,0]]
ys = [self.data[i,1],self.data[i+1,1]]
line, = self.axis.plot(xs,ys,'g-')
self.fig.canvas.draw()
data = np.random.rand(10,2) #numpy array
f = plt.figure()
axis = f.add_axes([0,0,0.9,0.9])
plotData = Plot(f,axis,data)
for i in range(len(data)-1):
plotData.plotting(i)
time.sleep(1)
plt.show()
But everytime I run this code it returns me one empty figure. How do I rectify it?
import matplotlib.pyplot as plt
import numpy as np
# use ggplot style for more sophisticated visuals
plt.style.use('ggplot')
def live_plotter(x_vec,y1_data,line1,identifier='',pause_time=0.1):
if line1==[]:
# this is the call to matplotlib that allows dynamic plotting
plt.ion()
fig = plt.figure(figsize=(13,6))
ax = fig.add_subplot(111)
# create a variable for the line so we can later update it
line1, = ax.plot(x_vec,y1_data,'-o',alpha=0.8)
#update plot label/title
plt.ylabel('Y Label')
plt.title('Title: {}'.format(identifier))
plt.show()
# after the figure, axis, and line are created, we only need to update the y-data
line1.set_ydata(y1_data)
# adjust limits if new data goes beyond bounds
if np.min(y1_data)<=line1.axes.get_ylim()[0] or np.max(y1_data)>=line1.axes.get_ylim()[1]:
plt.ylim([np.min(y1_data)-np.std(y1_data),np.max(y1_data)+np.std(y1_data)])
# this pauses the data so the figure/axis can catch up - the amount of pause can be altered above
plt.pause(pause_time)
# return line so we can update it again in the next iteration
return line1
A few notes on the function above:
line1.set_ydata(y1_data) can also be switched to line1.set_data(x_vec,y1_data) to change both x and y data on the plots.
plt.pause() is necessary to allow the plotter to catch up - I've been able to use a pause time of 0.01s without any issues
The user will need to return line1 to control the line as it is updated and sent back to the function
The user can also customize the function to allow dynamic changes of title, x-label, y-label, x-limits, etc.
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()
I am trying to create a plotting object that produces an animated matplotlib pcolor plot with a polar projection. Currently the object can either create a set of polar plots or try to create an animation of those plots.
When creating the set of polar plots (but not the animation) the object works as planned.
The animation portion of the object is based on this example, which works on my system. Unfortunately the animation as implemented in my object is not working. There is a figure and an MP4 file produced for the animation but both the figure and the too-short animation both show just some mis-shaped axes.
Does anyone have a suggestion of how to capture this figure series in an animation when embedded in an object?
I am using python 3.7, matplotlib 3.03 on a windows 10 machine
The code for the object and the code to run its instantiation are given below.
class Polar_smudge(object):
# object for creating polar contour plots
def __init__(self, azimuth_grid, range_grid):
import numpy as np
self.azimuth_grid = np.deg2rad(azimuth_grid)
self.range_grid = range_grid
self.fig = None
self.ax = None
self.images = []
#------------------------------------------------------------------
def add_data(self, value_grid):
import numpy as np
self.value_grid = value_grid
self.value_grid[self.value_grid<=0] = np.nan
#------------------------------------------------------------------
def add_figure(self, value_grid):
import matplotlib.pyplot as plt
# make and set-up figure
fig, ax = plt.subplots(subplot_kw=dict(projection='polar'))
ax.set_theta_zero_location("N")
ax.set_theta_direction(-1)
ax.set_rlim([0,10])
# make plot
cax = ax.pcolor(self.azimuth_grid, self.range_grid, value_grid, cmap=plt.cm.viridis_r)
ax.grid()
plt.show()
#------------------------------------------------------------------
def start_figure(self):
import matplotlib.pyplot as plt
# make and set-up figure
if self.fig is None :
self.fig, self.ax = plt.subplots(111, subplot_kw=dict(projection='polar'))
self.ax[0].set_theta_zero_location("N")
self.ax[0].set_theta_direction(-1)
def update_figure(self, value_grid):
import matplotlib.pyplot as plt
# make figure and add to image list
self.images.append((self.ax[0].pcolor(self.azimuth_grid, self.range_grid, value_grid, cmap=plt.cm.viridis_r),))
def end_figure(self):
import matplotlib.animation as animation
# animate the figure list
im_ani = animation.ArtistAnimation(self.fig, self.images, interval=50, repeat_delay=3000,blit=True)
im_ani.save('smudge.mp4')
#============This runs the object ====================================
import numpy as np
azimuth_bins = np.linspace(0, 360, 360)
range_bins = np.linspace(0, 10, 30)
# make plotting azim range grids
range_grid, azimuth_grid = np.meshgrid(range_bins, azimuth_bins)
# this works but isnt what I want
good_smudge = Polar_smudge(azimuth_grid,range_grid)
for ix in range(3):
val_grid = np.random.randn(360,30)
good_smudge.add_figure(val_grid)
# this doesnt work
bad_smudge = Polar_smudge(azimuth_grid,range_grid)
bad_smudge.start_figure()
for ix in range(3):
val_grid = np.random.randn(360,30)
bad_smudge.update_figure(val_grid)
bad_smudge.end_figure()
In response to the comment from Earnest, I did some further refinement and it appears that the problem is not linked to being embedded in an object, and also that increasing the number of frames (to eg. 30) does not solve the problem. The code snippet below provides a more concise demonstration of the problem (but lacks the correctly produced figure output option).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
azimuth_bins = np.linspace(0, 360, 60)
range_bins = np.linspace(0, 10, 30)
images = []
# make plotting azim range grids
range_grid, azimuth_grid = np.meshgrid(range_bins, azimuth_bins)
fig,ax = plt.subplots(111, subplot_kw=dict(projection='polar'))
ax[0].set_theta_zero_location("N")
ax[0].set_theta_direction(-1)
for ix in range(30):
val_grid = np.random.randn(60,30)
images.append((ax[0].pcolor(azimuth_grid, range_grid, val_grid, cmap=plt.cm.viridis_r),))
# animate the figure list
im_ani = animation.ArtistAnimation(fig, images, interval=50, repeat_delay=3000,blit=False)
im_ani.save('smudge2.mp4')
I want to plot pandas histogram to an axis, but the behavior is really strange. I don't know what's wrong here.
fig1, ax1 = plt.subplots(figsize=(4,3))
fig2, ax2 = plt.subplots(figsize=(4,3))
fig3, ax3 = plt.subplots(figsize=(4,3))
# 1. This works
df['speed'].hist()
# 2. This doens't work
df['speed'].hist(ax=ax2)
# 3. This works
data = [1,2,3,5,6,2,3,4]
temp_df = pd.DataFrame(data)
temp_df.hist(ax=ax2)
The error jupyter notebook returns is:
AssertionError Traceback (most recent call last)
<ipython-input-46-d629de832772> in <module>()
7
8 # This doens't work
----> 9 df['speed'].hist(ax=ax2)
10
11 # # This works
D:\Anaconda2\lib\site-packages\pandas\tools\plotting.pyc in hist_series(self, by, ax, grid, xlabelsize, xrot, ylabelsize, yrot, figsize, bins, **kwds)
2953 ax = fig.gca()
2954 elif ax.get_figure() != fig:
-> 2955 raise AssertionError('passed axis not bound to passed figure')
2956 values = self.dropna().values
2957
AssertionError: passed axis not bound to passed figure
The pandas source code is here:
https://github.com/pydata/pandas/blob/d38ee272f3060cb884f21f9f7d212efc5f7656a8/pandas/tools/plotting.py#L2913
Totally have no idea what's wrong with my code.
The problem is that pandas determines which is the active figure by using gcf() to get the "current figure". When you create several figures in a row, the "current figure" is the last one created. But you are trying to plot to an earlier one, which causes a mismatch.
However, as you can see on line 2954 of the source you linked to, pandas will look for an (undocumented) figure argument. So you can make it work by doing df['speed'].hist(ax=ax2, figure=fig2). A comment in the pandas source notes that this is a "hack until the plotting interface is a bit more unified", so I wouldn't rely on it for anything too critical.
The other solution is to not create a new figure until you're ready to use it. In your example above, you only use figure 2, so there's no need to create the others. Of course, that is a contrived example, but in a real-life situation, if you have code like this:
fig1, ax1 = plt.subplots(figsize=(4,3))
fig2, ax2 = plt.subplots(figsize=(4,3))
fig3, ax3 = plt.subplots(figsize=(4,3))
something.hist(ax=ax1)
something.hist(ax=ax2)
something.hist(ax=ax3)
You can change it to this:
fig1, ax1 = plt.subplots(figsize=(4,3))
something.hist(ax=ax1)
fig2, ax2 = plt.subplots(figsize=(4,3))
something.hist(ax=ax2)
fig3, ax3 = plt.subplots(figsize=(4,3))
something.hist(ax=ax3)
That is, put each section of plotting code right after the code that creates the figure for that plot.
I was having similar issues while trying to plot a Pandas histogram into a PyQt5 Widget (from a UI created using Qt Creator).
The code looked something liked this:
from PyQt5 import QtWidgets
from matplotlib.figure import Figure
from matplotlib.backends.backend_qt5agg import FigureCanvas
import numpy as np
import pandas as pd
class MplWidget(QtWidgets.QWidget):
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self, parent)
self.canvas = FigureCanvas(Figure())
vertical_layout = QtWidgets.QVBoxLayout()
vertical_layout.addWidget(self.canvas)
self.canvas.axes = self.canvas.figure.add_subplot(111)
self.setLayout(vertical_layout)
index1 = np.random.rand(10000)
data1 = np.random.gamma(2, 2, 10000)
s1 = pd.Series(data1, index=index1)
MplWidget_object = MplWidget_FROM_MY_GUI
s1.hist(ax=MplWidget_object.canvas.axes) # This would throw an error.
pd.DataFrame(s1).hist(ax=MplWidget_object.canvas.axes) # This Works!!!
I managed to make it work by converting my pandas.Series into a Dataframe (las two lines of the code)