I try to draw a graph, which show the progress of a chemical reaction. The progress itself (time or reactionsteps) should be changeable using a slider.
The code I have so far:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
fig.canvas.set_window_title('Reaktionsfortschritt')
t0 = 0
t = np.arange(0, t0, .5)
k0 = 0.17
a = np.exp(- k0 * t)
l, = plt.plot(t, a, lw=3, color='crimson')
plt.axis([0, 20, 0, 1])
axrs = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor='lightblue')
srs = Slider(axrs, 'Reaktionsschritte', 0, 20, valinit=0)
def xval(*args):
x = srs.val
t = np.arange(0, x, 0.5)
#l.set_ydata(np.exp(- 0.6 * t))
#plt.plot(t, a)
fig.canvas.draw_idle()
srs.on_changed(xval)
plt.show()
As far as I understand the plot-range (t) is updated using the xval-function. However, there is no plotting of the graph.
I tried both replotting using plt.plot(t, a) as well as l.set_ydata(...).
edited
Ok, so now I added a second function (b) that describes product formation. I added the function also in the same way to the update-function. As a result I get a very strange behaviour: using the slider, I can plot only in positive x-direction, e.g. there I no going back. Once the graph is drawn, it won't 'undraw' when reducing the slider value. Any suggestion why that is?
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
fig.canvas.set_window_title('Reaktionsfortschritt')
t = np.arange(0, 0, .5)
k0 = 0.17
a = np.exp(- k0 * t)
b = 1 - np.exp(- k0 * t)
# plot
l, = plt.plot(t, a, lw=3, color='crimson')
m, = plt.plot(t, b, lw=3, color='dodgerblue')
plt.axis([0, 20, 0, 1])
plt.grid(True)
axrs = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor='lightblue')
srs = Slider(axrs, 'Zeit', 0, 20, valinit=0)
def update(x):
t = np.arange(0, x, 2)
ax.lines.pop(0) # remove previous line plot
ax.plot(t, np.exp(- k0 * t), lw=3, color='crimson')
ax.plot(t, 1 - np.exp(- k0 * t), lw=3, color='dodgerblue')
fig.canvas.draw()
srs.on_changed(update)
plt.show()
Assuming you have time on the x-axis and want to change the maximum time of your plot that is created by the same function every time, I came up with this:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
fig.canvas.set_window_title('Reaktionsfortschritt')
t0 = 0
t = np.arange(0, t0, .5)
k0 = 0.17
a = np.exp(- k0 * t)
l, = ax.plot(t, a, lw=3, color='crimson')
plt.axis([0, 20, 0, 1])
axrs = plt.axes([0.25, 0.1, 0.65, 0.03])
srs = Slider(axrs, 'Reaktionsschritte', 0, 20, valinit=0)
def update(x):
t0 = x
t = np.arange(0, t0, .5)
ax.lines.pop(0) # remove previous line plot
ax.plot(t, np.exp(- k0 * t), lw=3, color='crimson') # plot new one
fig.canvas.draw()
srs.on_changed(update)
plt.show()
See what it does when changing the Slider value and let me know if this is what you wanted it to do.
To your edit:
When you add a second plot, you have two lines objects. Try to print ax.lines directly after you run the code (before touching the Slider) and see that it really is a list of two lines. Then call ax.lines.pop(0) and see that one element is popped from the list. That's what the above code does, it removes lines from the axes object ax every time you touch the Slider (because then update is called), which after calling fig.canvas.draw() leads to vanishing of previous plots. If you now touch the Slider once, then two new lines are added to ax and only one is removed. This is why you think there is no going back.
So if you now added a second plot, you just have to pop twice from the list ax.lines with ax.lines.pop(0) and the code works fine ;-)
Related
sorry but i can't post my real data or plot.. so I made pictoral plot in MS paint.
So I have my plot - orange line, given as set of X and Y values plt.plot(data_x, data_y).
Then I added horizontal line - blue line that way: plt.axvline(x=10).
Now I would like to fill with color space between this line and my plot (ultimately, with one color when values are belowe horizontal line, and second when they are above).
I tried with plt.fill and plt.fill_between and plt.axhspan though, i receive errors either with dimensionality issues or elements vs sequence.
Is there an easy way to do this?
Yes, there is a where parameter of ax.fill_between for doing this:
import matplotlib.pyplot as plt
import numpy as np
# make data
x = np.linspace(0, np.pi * 2, 300)
y = np.sin(x)
# init figure
fig, ax = plt.subplots()
# plot sin and line
ax.plot(x, y, color='orange')
ax.axhline(0)
# fill between hline and y, but use (y > 0) and (y < 0)
# to create boolean masks determining where to fill
ax.fill_between(x, y, where=(y > 0), color='orange', alpha=.3)
ax.fill_between(x, y, where=(y < 0), color='blue', alpha=.3)
you have to use
import matplotlib.pyplot as plt
import numpy as np
data_x = np.arange(0.0, 2, 0.01)
data_y = np.sin(2 * np.pi * x)
data_y2 = 0
fig, ax = plt.subplots()
ax.fill_between(data_x, data_y, data_y2,
where=data_y2 >= data_y,
facecolor='green', interpolate=True)
ax.fill_between(data_x, data_y, data_y2,
where=data_y2 <= data_y,
facecolor='red', interpolate=True)
Note that data_y2 has to be a scalar (e.g. 0) or of the same shape as data_y.
Here you will find the relevant docu:
https://matplotlib.org/3.1.1/gallery/lines_bars_and_markers/fill_between_demo.html
and
https://matplotlib.org/3.1.1/api/_as_gen/matplotlib.pyplot.fill_between.html
I'm trying to Add the slider in the plot similar to the slider demo example.
I'm plotting fill_between which gives PolyCollection object.
Although I tried with plot too which give Line2D object as shown picture below, but plot doesn't update as expected as in demo.
code
import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
def get_pdf(mu, sigma=1, offset=4):
o = sigma * offset
x = np.linspace(mu - o, mu + o, 100)
rv = ss.norm(mu, sigma)
return x, rv.pdf(x)
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
ax.fill_between(*get_pdf(0, 1), alpha=0.7)
# t = plt.fill_between(*get_pdf(2, 1), alpha=0.7) # this gives ployCollection
t = ax.plot(*get_pdf(2, 1), label='treatment', alpha=0.7)
a = plt.axes([0.25, 0.1, 0.5, 0.03])
slider = widgets.Slider(a, "shift", 0, 10, valinit=2, valstep=1)
def update(val):
x, y = get_pdf(val)
t[0].set_ydata(y)
fig.canvas.draw_idle()
slider.on_changed(update)
plt.show()
To update the line plot, t[0].set_xdata(x) needs to be set, as it is different for each call. In this particular case, get_pdf each time returns the same y.
Updating the coordinates of the polyCollection generated by fill_between doesn't seem to be possible. However, you can delete and recreate it at every update. Note that this is slower than just updating the coordinates.
import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
def get_pdf(mu, sigma=1, offset=4):
o = sigma * offset
x = np.linspace(mu - o, mu + o, 100)
rv = ss.norm(mu, sigma)
return x, rv.pdf(x)
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
ax.fill_between(*get_pdf(0, 1), alpha=0.7)
t = ax.fill_between(*get_pdf(2), color='crimson', alpha=0.7)
a = plt.axes([0.25, 0.1, 0.5, 0.03])
slider = widgets.Slider(a, "shift", 0, 10, valinit=2, valstep=1)
def update(val):
global t
t.remove()
t = ax.fill_between(*get_pdf(val), color='crimson', alpha=0.7)
fig.canvas.draw_idle()
slider.on_changed(update)
plt.show()
I'm reading output data from some simulations in fortran to make a movie of orbits, after generating a couple graphs. At first, I didn't use blitting for the animation, so while it worked, it was very, very slow.
I originally thought that the animation I wanted lent itself to scatter, since I'd have five series of data with decreasing alphas to create a trailing effect. Here's my original (non-blit) update function:
def animate(frame):
jptx, jpty = jx[frame-3:frame], jy[frame-3:frame]
cptx, cpty = cx[frame-3:frame], cy[frame-3:frame]
eptx, epty = ex[frame-3:frame], ey[frame-3:frame]
gptx, gpty = gx[frame-3:frame], gy[frame-3:frame]
iptx, ipty = ix[frame-3:frame], iy[frame-3:frame]
ax2.clear()
ax2.scatter(jptx, jpty, s=32, c=ablue, marker="s", label='Jupiter')
ax2.scatter(cptx, cpty, s=8, c=ared, marker="o", label='Callisto')
ax2.scatter(eptx, epty, s=8, c=agreen, marker="o", label='Europa')
ax2.scatter(gptx, gpty, s=8, c=ablack, marker="o", label='Ganymede')
ax2.scatter(iptx, ipty, s=8, c=ayellow, marker="o", label='Io')
ax2.set_xlim(-3, 7)
ax2.set_ylim(-3, 4)
animation = animation.FuncAnimation(fig2, animate, interval=0.5, frames=jt.size)
print('Begin saving animation')
animation.save('Tabbys Star.mp4', writer='ffmpeg', fps=60)
print('Animation saved')
plt.show()
Now, when I run the script, a window appears for a fraction of a second, and there is very clearly a yellow circle on the screen, indicating the background is being drawn. However, the window closes immediately after. This is the relevant code for the second attempt. The yellow circle was added in this attempt.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
# j_file = location + 'JUPITER.aei'
# jt, jx, jy, jz = read_data(j_file)
jt, jx, jy, jz = np.random.random([100,4]), np.random.random([100,4]), np.random.random([100,4]), np.random.random([100,4])
# c_file = location + 'CALLISTO.aei'
# ct, cx, cy, cz = read_data(c_file)
ct, cx, cy, cz = np.random.random([100,4]), np.random.random([100,4]), np.random.random([100,4]), np.random.random([100,4])
alphas = [0.25, 0.5, 0.75, 1]
ablue = np.zeros((4, 4))
ablue[:, 2] = 1.0
ablue[:, 3] = alphas
ared = np.zeros((4, 4))
ared[:, 0] = 1.0
ared[:, 3] = alphas
fig2 = plt.figure()
ax2 = fig2.add_subplot(111, aspect='equal')
xdata, ydata = np.zeros((4,)), np.zeros((4,))
jpt, = plt.plot(xdata, ydata, marker='.', ms=32, c=ablue, label='Jupiter')
cpt, = plt.plot(xdata, ydata, marker='.', ms=8, c=ared, label='Callisto')
def init():
ax2.set_xlim(-3, 7)
ax2.set_ylim(-3, 4)
circle = plt.Circle((0, 0), 0.1, color='y')
ax2.add_patch(circle)
for pt in [jpt, cpt]:
pt.set_data(np.zeros((4,)), np.zeros((4,)))
return jpt, cpt
def animate(frame, j, c):
jptx, jpty = jx[frame-3:frame], jy[frame-3:frame]
cptx, cpty = cx[frame-3:frame], cy[frame-3:frame]
j.set_data(jptx, jpty)
c.set_data(cptx, cpty)
return j, c
animation = animation.FuncAnimation(fig2, animate, fargs=(jpt, cpt), interval=0.5, frames=jt.size, init_func=init, blit=True)
print('Begin saving animation')
# animation.save('Tabbys Star.mp4', writer='ffmpeg', fps=60)
print('Animation saved')
plt.show()
I'd also eventually like to add a legend and some axis labels, but I believe that can be done normally.
So what's the problem with animate in the second code snippet?
Thanks
Edited for clarity (again)
Please make sure, that you render it for more than 1 frame, by setting frames to a high value. In the code you posted, the number of frames is not clearly defined, which may cause this problem.
You are confusing plt.plot and plt.scatter here. The error you get would even be produced without any animation.
While plt.plot has arguments color and ms to set the color and markersize respectively, they do not allow to use different values for different points. This is why there exists a scatter plot.
plt.scatter has arguments c and s to set the color and markersize respectively.
So you need to use scatter to obtain differently colored points.
jpt = plt.scatter(xdata, ydata, marker='.', s=32, c=ablue, label='Jupiter')
Then for the animation you would need to adjust your code for the use with scatter since it does not have a .set_data method, but a .set_offsets method, which expects a 2 column array input.
j.set_offsets(np.c_[jptx, jpty])
In total the script would look like
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
jt, jx, jy, jz = [np.random.random([100,4]) for _ in range(4)]
ct, cx, cy, cz = [np.random.random([100,4]) for _ in range(4)]
alphas = [0.25, 0.5, 0.75, 1]
ablue = np.zeros((4, 4))
ablue[:, 2] = 1.0
ablue[:, 3] = alphas
ared = np.zeros((4, 4))
ared[:, 0] = 1.0
ared[:, 3] = alphas
fig2 = plt.figure()
ax2 = fig2.add_subplot(111, aspect='equal')
xdata, ydata = np.zeros((4,)), np.zeros((4,))
jpt = plt.scatter(xdata, ydata, marker='.', s=32, c=ablue, label='Jupiter')
cpt = plt.scatter(xdata, ydata, marker='.', s=8, c=ared, label='Callisto')
def init():
ax2.axis([0,1,0,1])
circle = plt.Circle((0, 0), 0.1, color='y')
ax2.add_patch(circle)
for pt in [jpt, cpt]:
pt.set_offsets(np.c_[np.zeros((4,)), np.zeros((4,))])
return jpt, cpt
def animate(frame, j, c):
jptx, jpty = jx[frame-3:frame], jy[frame-3:frame]
cptx, cpty = cx[frame-3:frame], cy[frame-3:frame]
j.set_offsets(np.c_[jptx, jpty])
c.set_offsets(np.c_[cptx, cpty])
return j, c
animation = animation.FuncAnimation(fig2, animate, fargs=(jpt, cpt),
interval=50, frames=jt.size, init_func=init, blit=True)
plt.show()
I made a figure with four subplots of a histogram based on random normal, gamma, exponential, and uniform distributions respectively. I made it using matplotlib and Jupyter notebook. It is an interactive figure via ipywidgets lib. In particular, there are four slide bars that control the sample size on each histogram and update them accordingly. However, when updating the histograms, it annoyingly flickers. Is there any way to avoid this? Thx.
Now the code to be run on a jupyter notebook:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib notebook
from ipywidgets import *
n = 1000
x1 = np.random.normal(-2.5, 1, n)
x2 = np.random.gamma(2, 1.5, n)
x3 = np.random.exponential(2, n)+7
x4 = np.random.uniform(14,20, n)
x = [x1, x2, x3, x4]
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(10,7))
axs = [ax1,ax2,ax3,ax4]
titles = ['x1\nNormal', 'x2\nGamma', 'x3\nExponential', 'x4\nUniform']
subplots_axes = [[-7,2,0,250], [0,4.5,0,250], [7,25,0,250], [14,20,0,250]]
bins = [np.arange(-6, 6, 0.5),
np.arange(0, 10, 0.5),
np.arange(7, 17, 0.5),
np.arange(14, 24, 0.5)]
fig.subplots_adjust(hspace=0.5)
def plt_dist(s, sample):
axs[s].hist(x[s][:sample], bins=bins[s], linewidth=0, color='#1F77B4')
axs[s].axis(subplots_axes[s])
axs[s].set_title('{}'.format(titles[s]))
axs[s].set_ylabel('Frequency')
axs[s].set_xlabel('Value')
axs[s].annotate('n = {}'.format(sample), xycoords='axes fraction', xy = [0.8,0.9])
display(fig)
for s in range(0,4):
sld_bar = interact(plt_dist, s = fixed(s), sample = widgets.IntSlider(min=100,max=1000+45,step=1,value=100))
In case anyone else comes across this issue having a print statement in your interact function can also cause flickering.
fig, ax = plt.subplots()
#widgets.interact
def run(
a = 1.2,
):
ax.clear()
print(1) # Comment this line to stop flickering
ax.plot([1,2,3])
display(fig)
It's not really clear what display(fig) would do or what it's needed for.
For me, removing that line and instead clearing the axes (axs[s].clear()) at the beginning of the plt_hist function works just fine and the "flickering" is not there anymore.
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib notebook
from ipywidgets import *
n = 1000
x1 = np.random.normal(-2.5, 1, n)
x2 = np.random.gamma(2, 1.5, n)
x3 = np.random.exponential(2, n)+7
x4 = np.random.uniform(14,20, n)
x = [x1, x2, x3, x4]
fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(10,7))
axs = [ax1,ax2,ax3,ax4]
titles = ['x1\nNormal', 'x2\nGamma', 'x3\nExponential', 'x4\nUniform']
subplots_axes = [[-7,2,0,250], [0,4.5,0,250], [7,25,0,250], [14,20,0,250]]
bins = [np.arange(-6, 6, 0.5),
np.arange(0, 10, 0.5),
np.arange(7, 17, 0.5),
np.arange(14, 24, 0.5)]
fig.subplots_adjust(hspace=0.5)
def plt_dist(s, sample):
axs[s].clear() # <-- clear axes
axs[s].hist(x[s][:sample], bins=bins[s], linewidth=0, color='#1F77B4')
axs[s].axis(subplots_axes[s])
axs[s].set_title('{}'.format(titles[s]))
axs[s].set_ylabel('Frequency')
axs[s].set_xlabel('Value')
axs[s].annotate('n = {}'.format(sample), xycoords='axes fraction', xy = [0.8,0.9])
#display(fig) <--- delete this
for s in range(0,4):
sld_bar = interact(plt_dist, s = fixed(s),
sample = widgets.IntSlider(min=100,max=1000+45,step=1,value=100))
I am trying to update the colorbars in my plot. Unfortunately, only the colors update i.e. the tick values do not change only the colors of the bar change accorgingly with the current values in the contour plot. I would like to make the ticks change as well as the colors in the colorbar.
import matplotlib
import numpy as np
import pylab as py
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
fig, axs = plt.subplots(1, 2)
# I define the variables below but do not give the exact values as they come from computations
x - one dimensional array
v - four dimensional array
v_mag - three dimensional array
T_string - three dimensional array
X, Y = np.meshgrid(x, x)
cs1 = axs[0].quiver(X, Y, v[0][0], v[0][1],v_mag[0], cmap=cm.seismic)
cs2 = axs[1].contourf(X, Y, T[0], 100)
cbar1=fig.colorbar(cs1, ax=axs[0], format=\"%.2f\")
cbar2=fig.colorbar(cs2, ax=axs[1], format=\"%.2f\")
axcolor = 'lightgoldenrodyellow'
time = py.axes([0.1, 0.01, 0.65, 0.03], axisbg = axcolor)
S_time = Slider(time, 'Time', 0, 50, valinit = 0);
def update(val) :
timeval = int(S_time.val)
cs1.set_UVC(v[timeval][0],v[timeval][1], v_mag[timeval])
cbar1.on_mappable_changed(cs1)
cs2 = axs[1].contourf(X, Y, T[timeval], 100)
cbar2.on_mappable_changed(cs2)
plt.show()
#second try
def update(val) :
timeval = int(S_time.val)
cs1.set_UVC(v[timeval][0],v[timeval][1], v_mag[timeval])
cbar1.on_mappable_changed(cs1)
cs2 = axs[1].contourf(X, Y, T[timeval], 100)
cbar2.set_clim( np.amin(np.array(T[timeval])) , np.amax(np.array(T[timeval])) )
cbar2.update_ticks()
cbar2.draw_all()
plt.draw()
S_time.on_changed(update)
plt.show()
The answer is to define the update function as:
def update(val) :
timeval = int(S_time.val)
cs1.set_UVC(v[timeval][0],v[timeval][1], v_mag[timeval])
cbar1.on_mappable_changed(cs1)
cs2 = axs[1].contourf(X, Y, T[timeval], 100, cmap=cm.jet)
cbar2.set_ticklabels(np.linspace(np.amin(np.array(T[timeval])),np.amax(np.array(T[timeval])), num = 12))
cbar2.update_ticks()
plt.show()