How to draw a second line on this specific way to plot? - python

I normally plot graphs in a completely different way, which doesn't work here. Here, the y data is defined by set_ydata. So, I don't see a way to have a second graph in the same plot. Does someone have an idea how to achieve it here?
I tried
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
import numpy as np
from matplotlib import pyplot as plt
import serial
stroke=30
conrod=75
bore=40
def update_plot():
y = 59 * np.random.rand(50)
lines[0].set_ydata(array_pressure_1)
canvas.draw()
window.after(25, update_plot) # run again after 25ms (1000ms/25ms = 40 FPS (frames per second))
def main():
global window
global canvas
global lines
#below has to be fiddled with for axis scale
x = np.linspace(0, stroke, 50)
y = 100 * np.random.rand(50)
window = tk.Tk()
window.title('Plotting in Tkinter')
window.geometry("500x500")
#The canvas the figure is drawn on:
fig = Figure(figsize = (4, 4), dpi = 100)
canvas = FigureCanvasTkAgg(fig, master = window)
canvas.draw()
canvas.get_tk_widget().pack()
plot1 = fig.add_subplot(111)
lines = plot1.plot(x, y)
window.after(25, update_plot) # run first time after 25ms
update_plot() # run first time at once
window.mainloop()
if __name__ == '__main__':
main()

I've slightly adapted your code to include new lines... and I've introduced the concept of blitting which will greatly speed up your animations!
(I also re-named plot1 to ax which is the commonly used name for matplotlib axes)
The general concept is the following:
a call to ax.plot() will return a set with 1 new artist
(to directly unpack it use something like l, = ax.plot())
now simply create as much lines as you need,
put them in some container (list, dict etc.)
and then use l.set_xdata(), l.set_color() etc. to update the artist
to avoid re-drawing the whole image all the time, use blitting (e.g. cache the background and only re-draw animated artists on top)
here's a working example with multiple lines:
import tkinter as tk
from matplotlib.figure import Figure
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
import numpy as np
from matplotlib import pyplot as plt
# import serial
stroke=30
conrod=75
bore=40
lines = []
def update_plot():
global lines
global ax
global bg
y = 59 * np.random.rand(50)
# update the lines
lines[0].set_ydata(y)
lines[1].set_ydata(y + np.random.normal(5,10))
lines[2].set_ydata(y - np.random.normal(5,10))
# restore the background
canvas.restore_region(bg)
# re-draw only the artists that have changed
for l in lines:
ax.draw_artist(l)
# efficiently re-draw the image using "blitting"
canvas.blit(ax.bbox)
window.after(25, update_plot) # run again after 25ms (1000ms/25ms = 40 FPS (frames per second))
def main():
global window
global canvas
global lines
global ax
global bg
#below has to be fiddled with for axis scale
x = np.linspace(0, stroke, 50)
y = 100 * np.random.rand(50)
window = tk.Tk()
window.title('Plotting in Tkinter')
window.geometry("500x500")
#The canvas the figure is drawn on:
fig = Figure(figsize = (4, 4), dpi = 100)
canvas = FigureCanvasTkAgg(fig, master = window)
canvas.get_tk_widget().pack()
# add a new axes
ax = fig.add_subplot(111)
ax.set_ylim(-20,100)
# plot a static line on the background
static_l1 = ax.plot(x, y/2, c="r", lw=4)
# draw everything that goes on the background
canvas.draw()
# cache the background
bg = fig.canvas.copy_from_bbox(ax.bbox)
# add some lines (will be updated later)
l1, = ax.plot(x, y)
lines.append(l1)
l2, = ax.plot(x, y)
lines.append(l2)
l3, = ax.plot(x, y)
lines.append(l3)
# set dynamically updated lines to "animated"
# (e.g. exclude them from ordinary draw-events)
for l in lines:
l.set_animated(True)
window.after(25, update_plot) # run first time after 25ms
update_plot() # run first time at once
window.mainloop()
if __name__ == '__main__':
main()

Related

When switching between two matplotlib graphs embedded in tkinter an extra unexpected set of x an y axis labels and ticks appears on the first graph

I have two different matplotlib graphs embedded in tkinter that I am using a button to switch between. The first graph switch cycle plots the graphs as expected however every time thereafter switching between the two graphs causes the first graph to have an unexpected x and y axis set of labels and ticks ranging from 0 to 1. The desired graph one x and y axis labels and ticks are present as expected but in addition an extra 0 to 1 range labels/ticks are overlaid on the expected labels/ticks. The graph two x and y axis labels/ticks behave correctly and do not have the unexpected overlay. The following is a simplified version of my code followed by some pictures of the graphs showing the problem.
import matplotlib
matplotlib.use("TkAgg")
import matplotlib.pyplot as plt
from tkinter import *
from matplotlib.backends.backend_tkagg import (FigureCanvasTkAgg,
NavigationToolbar2Tk)
from matplotlib.backend_bases import key_press_handler
import numpy as np
# Separated out config of plot to just do it once
def config_plot():
fig = plt.figure(figsize=(18, 5))
return (fig)
class graphSwitcher:
def __init__(self, master):
self.master = master
self.frame = Frame(self.master)
self.fig = config_plot()
self.graphIndex = 0
self.canvas = FigureCanvasTkAgg(self.fig, self.master)
self.config_window()
self.graph_one(self.fig)
self.frame.pack(expand=YES, fill=BOTH)
def config_window(self):
self.canvas.mpl_connect("key_press_event", self.on_key_press)
toolbar = NavigationToolbar2Tk(self.canvas, self.master)
toolbar.update()
self.canvas.get_tk_widget().pack(side=TOP, fill=BOTH,
expand=1)
self.button = Button(self.master, text="Quit",
command=self._quit)
self.button.pack(side=BOTTOM)
self.button_switch = Button(self.master, text="Switch Graphs",
command=self.switch_graphs)
self.button_switch.pack(side=BOTTOM)
plt.subplots_adjust(bottom=0.2)
# the def creates the first matplotlib graph
def graph_one(self, fig):
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
self.ax = fig.subplots()
try:
self.ax1.clear() # clear current axes
self.ax2.clear()
except AttributeError:
pass
self.ax.plot(t, s)
self.ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='Graph One')
self.canvas.draw()
# This def creates the second matplotlib graph that uses subplot
# to place two graphs one on top of the other
def graph_two(self, fig):
x1 = np.linspace(0.0, 5.0)
y1 = np.cos(2 * np.pi * x1) * np.exp(-x1)
self.ax1 = plt.subplot2grid((5, 4), (0, 0), rowspan=4,
colspan=4)
self.ax1.plot(x1, y1, 'o-')
self.ax1.set(title='Graph Two')
means_men = (20, 35, 30, 35, 27)
std_men = (2, 3, 4, 1, 2)
self.ax2 = plt.subplot2grid((5, 4), (4, 0), sharex=self.ax1,
rowspan=1, colspan=4)
self.ax2.bar(std_men, means_men, color='green', width=0.5,
align='center')
self.canvas.draw()
def on_key_press(event):
key_press_handler(event, canvas, toolbar)
def _quit(self):
self.master.quit() # stops mainloop
def switch_graphs(self):
self.graphIndex = (self.graphIndex + 1 ) % 2
if self.graphIndex == 0:
self.graph_one(self.fig)
else:
self.graph_two(self.fig)
def main():
root = Tk()
graphSwitcher(root)
root.mainloop()
if __name__ == '__main__':
main()
The following are pictures of the graphs seen in sequence showing that the first two times the individual graphs are seen (cycle 1) the graphs have the correct axis labels and ticks. But that the second time the first graph is shown there is an extra unexpected set of x and y axis labels and ticks. The las two pictured graphs repeat every cycle (two button pushes)
If anybody has any ideas on how to get rid of the unexpected x and y axis labels/ticks seen on graph one I would appreciate the help.
You're clearing the axes created on the call to graph_two, but you're not removing them. So when you come to plot the first graph for a second time, the graph_two axes are still there underneath the axes for graph_one. You can't see the 2 subplots themselves as they are underneath, but the axes tick labels do show at the edges, which is what you are seeing.
This is more easily seen if you remove the plotting and figure creation in graph_one you will be able to see that the 2 subplots are cleared but not removed when you press the button.
The solution is therefore simple, use axes.remove instead. So graph_one looks like:
def graph_one(self, fig):
t = np.arange(0.0, 2.0, 0.01)
s = 1 + np.sin(2 * np.pi * t)
self.ax = fig.subplots()
try:
self.ax1.remove() # remove current axes
self.ax2.remove()
except AttributeError:
pass
self.ax.plot(t, s)
self.ax.set(xlabel='time (s)', ylabel='voltage (mV)',
title='Graph One')
self.canvas.draw()

Saving Animation in matplotlib of Progress Line

Objective: To save a plotted graph(x,y) with a moving a vertical line over the graph w.r.t to timer.
I've built a simple graph with a moving vertical progress line basically identical to this example, but I now need to save it. I do not need the functionality to relocate the progress bar on-click, only to have the progress bar move along the data. I'm not sure where to call the save function because this code does not rely on FuncAnimation.
import sys
import matplotlib.pyplot as plt
import time
import numpy
fig = plt.figure()
ax = fig.add_subplot(111)
max_height = 100
n_pts = 100
y1 = [0, max_height]
x1 = [0, 0]
y = numpy.random.randn(n_pts) * max_height
x = numpy.arange(0, n_pts)
# draw the data
line1, = ax.plot(x, y, color='black')
# fix the limits of the plot
ax.set_ylim(0, max_height)
ax.set_xlim(0, n_pts)
# draw the plot so that we can capture the background and then use blitting
plt.show(block=False)
# get the canvas object
canvas = ax.figure.canvas
background = canvas.copy_from_bbox(ax.bbox)
# add the progress line.
# XXX consider using axvline
line, = ax.plot(x1, y1, color='r', animated=True)
starttime=time.time()
mytimer=0
mytimer_ref=0
def update(canvas, line, ax):
# revert the canvas to the state before any progress line was drawn
canvas.restore_region(background)
# compute the distance that the progress line has made (based on running time)
t = time.time() - starttime
mytimer = t + mytimer_ref
x1 = [mytimer,mytimer]
# update the progress line with its new position
line.set_xdata(x1)
# draw the line, and blit the axes
ax.draw_artist(line)
canvas.blit(ax.bbox)
timer=fig.canvas.new_timer(interval=100)
args=[canvas,line,ax]
timer.add_callback(update,*args) # every 100ms it calls update function
timer.start()
plt.show()

How to dynamically display time data stream on matplotlib

I'm using matplotlib to display data saved on a csv file periodically,
now the data is plotted well but the time axis is hardly moving, in fact
the script is trying to show all the data stored on that file, I want to see only latest data and be able to scrol horizontaly to see older data
this is a part of the script :
style.use('grayscale')
fig = plt.figure()
ax0= fig.add_subplot(511)
def animate(i):
graph_data = open('filelocation','r').read()
lines = graph_data.split('\n')
xs = []
ys = []
for line in lines :
if len(line)>1:
time0 , quantity0 = line.split (',')
xs.append(dt.datetime.strptime(time0,'%H:%M:%S.%f'))
ys.append(quantity0)
ax0.clear()
ax0.plot(xs,ys)
xs = matplotlib.dates.date2num(xs)
hfmt = matplotlib.dates.DateFormatter('%H:%M:%S')
ax0.ticklabel_format(style='sci', axis='y', scilimits=(0, 0))
ax0.set_ylabel('risk')
ax0.xaxis.set_major_formatter(hfmt)
ani = animation.FuncAnimation(fig, animate, interval=1000)
plt.ticklabel_format(style='sci',axis ='y' , scilimits = (0,0))
plt.show()
plt.clear()
From that video, it looks like you want something like the following - I can still see a scrolling window in the video you posted so I'm still a little confused as to what you want. This uses fig.canvas.draw but there are other options using the matplotlib animation module (you didn't specify that it had to be using that module).
import numpy as np, time
from matplotlib import pyplot as plt
import matplotlib
matplotlib.interactive(True)
rest_time = 0.001
data_stream = np.random.randn(200)
# Define the width of the viewing window (xaxis limits),
# and the number of points to be displayed until a scrolling window starts
window_width = 40
n = 60
fig, ax = plt.subplots()
plotted_data, = ax.plot([], [], 'bo-', lw = 1.5)
x = []
y = []
ax.set_xlim([0, window_width])
ax.set_ylim([-4, 4])
for t, d in enumerate(data_stream):
x.append(t)
y.append(d)
plotted_data.set_xdata(x)
plotted_data.set_ydata(y)
if t > window_width:
ax.set_xlim([0, t])
if len(x) > n:
ax.set_xlim([t-n, t])
time.sleep(rest_time)
fig.canvas.draw()

Updating a matplotlib bar graph?

I have a bar graph which retrieves its y values from a dict. Instead of showing several graphs with all the different values and me having to close every single one, I need it to update values on the same graph. Is there a solution for this?
Here is an example of how you can animate a bar plot.
You call plt.bar only once, save the return value rects, and then call rect.set_height to modify the bar plot.
Calling fig.canvas.draw() updates the figure.
import matplotlib
matplotlib.use('TKAgg')
import matplotlib.pyplot as plt
import numpy as np
def animated_barplot():
# http://www.scipy.org/Cookbook/Matplotlib/Animations
mu, sigma = 100, 15
N = 4
x = mu + sigma*np.random.randn(N)
rects = plt.bar(range(N), x, align = 'center')
for i in range(50):
x = mu + sigma*np.random.randn(N)
for rect, h in zip(rects, x):
rect.set_height(h)
fig.canvas.draw()
fig = plt.figure()
win = fig.canvas.manager.window
win.after(100, animated_barplot)
plt.show()
I've simplified the above excellent solution to its essentials, with more details at my blogpost:
import numpy as np
import matplotlib.pyplot as plt
numBins = 100
numEvents = 100000
file = 'datafile_100bins_100000events.histogram'
histogramSeries = np.loadtext(file)
fig, ax = plt.subplots()
rects = ax.bar(range(numBins), np.ones(numBins)*40) # 40 is upper bound of y-axis
for i in range(numEvents):
for rect,h in zip(rects,histogramSeries[i,:]):
rect.set_height(h)
fig.canvas.draw()
plt.pause(0.001)

Clearing background in matplotlib using wxPython

I want to create an animation with matplotlib to monitor the convergence of a clustering algorithm. It should draw a scatterplot of my data when called the first time and draw error ellipses each time the plot is updated. I am trying to use canvas_copy_from_bbox() and restore_region() to save the scatterplot and then draw a new set of ellipses whenever I'm updating the plot.
However, the code just plots the new ellipses on top of the old ones, without clearing the previous plot first.
I suspect, somehow this approach doesn't work well with the Ellipse() and add_path() commands, but I don't know how to fix this.
Here is the code:
import wx
import math
from math import pi
from matplotlib.patches import Ellipse
from matplotlib.figure import Figure
from matplotlib.backends.backend_wxagg import \
FigureCanvasWxAgg as FigureCanvas
TIMER_ID = wx.NewId()
class _MonitorPlot(wx.Frame):
def __init__(self, data, scale=1):
self.scale = scale
wx.Frame.__init__(self, None, wx.ID_ANY,
title="FlowVB Progress Monitor", size=(800, 600))
self.fig = Figure((8, 6), 100)
self.canvas = FigureCanvas(self, wx.ID_ANY, self.fig)
self.ax = self.fig.add_subplot(111)
x_lims = [data[:, 0].min(), data[:, 0].max()]
y_lims = [data[:, 1].min(), data[:, 1].max()]
self.ax.set_xlim(x_lims)
self.ax.set_ylim(y_lims)
self.ax.set_autoscale_on(False)
self.l_data = self.ax.plot(data[:, 0], data[:, 1], color='blue',
linestyle='', marker='o')
self.canvas.draw()
self.bg = self.canvas.copy_from_bbox(self.ax.bbox)
self.Bind(wx.EVT_IDLE, self._onIdle)
def update_plot(self, pos, cov):
self.canvas.restore_region(self.bg)
for k in range(pos.shape[0]):
l_center, = self.ax.plot(pos[k, 0], pos[k, 1],
color='red', marker='+')
U, s, Vh = np.linalg.svd(cov[k, :, :])
orient = math.atan2(U[1, 0], U[0, 0]) * 180 / pi
ellipsePlot = Ellipse(xy=pos[k, :], width=2.0 * math.sqrt(s[0]),
height=2.0 * math.sqrt(s[1]),
angle=orient, facecolor='none',
edgecolor='red')
self.ax.add_patch(ellipsePlot)
self.canvas.draw()
self.canvas.blit(self.ax.bbox)
What's happening is that you're adding new patches to the plot each time, and then drawing all of them when you call self.canvas.draw().
The quickest fix is to just call self.canvas.draw_artist(ellipsePlot) after adding each patch and remove the call to self.canvas.draw()
As a simple, stand-alone example:
# Animates 3 ellipses overlain on a scatterplot
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import numpy as np
num = 10
x = np.random.random(num)
y = np.random.random(num)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line = ax.plot(x, y, 'bo')
fig.canvas.draw()
bg = fig.canvas.copy_from_bbox(ax.bbox)
# Pseudo-main loop
for i in range(100):
fig.canvas.restore_region(bg)
# Make a new ellipse each time... (inefficient!)
for i in range(3):
width, height, angle = np.random.random(3)
angle *= 180
ellip = Ellipse(xy=(0.5, 0.5), width=width, height=height,
facecolor='red', angle=angle, alpha=0.5)
ax.add_patch(ellip)
ax.draw_artist(ellip)
fig.canvas.blit(ax.bbox)
However, this will probably cause memory consumptions problems over time, as the axes object will keep track of all artists added to it. If your axes doesn't hang around for a long time, this may be negligible, but you should at least be aware that it will cause a memory leak. One way around this is to remove the ellipsis artists from the axes by calling ax.remove(ellipsePlot) for each ellipse after drawing them. However, this is still slightly inefficient, as you're constantly creating and destroying ellipse artists, when you could just update them. (Creating and destroying them doesn't have much overhead at all, though, it's mostly a stylistic issue...)
If the number of ellipses is staying the same over time it's better and easier to just update the properties of each ellipse artist object instead of creating and adding new ones. This will avoid have remove "old" ellipses from the axes, as only the number that you need will ever exist.
As a simple, stand-alone example of this:
# Animates 3 ellipses overlain on a scatterplot
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
import numpy as np
num = 10
x = np.random.random(num)
y = np.random.random(num)
plt.ion()
fig = plt.figure()
ax = fig.add_subplot(111)
line = ax.plot(x, y, 'bo')
fig.canvas.draw()
bg = fig.canvas.copy_from_bbox(ax.bbox)
# Make and add the ellipses the first time (won't ever be drawn)
ellipses = []
for i in range(3):
ellip = Ellipse(xy=(0.5, 0.5), width=1, height=1,
facecolor='red', alpha=0.5)
ax.add_patch(ellip)
ellipses.append(ellip)
# Pseudo-main loop
for i in range(100):
fig.canvas.restore_region(bg)
# Update the ellipse artists...
for ellip in ellipses:
ellip.width, ellip.height, ellip.angle = np.random.random(3)
ellip.angle *= 180
ax.draw_artist(ellip)
fig.canvas.blit(ax.bbox)

Categories

Resources