matplotlib.widgets.Slider with fill_between - python

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()

Related

Plotting part of FancyArrowPatch line in dashed style

I am trying to plot part of an matplotlib.patches.FancyArrowPatch in a dashed style. Using this post pyplot: Dotted line with FancyArrowPatch, I managed to get quite close to it :
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
plt.figure()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k', connectionstyle = "arc3, rad = -0.9" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
plt.gca().add_patch(arrow)
points = np.array([i[0] for i in arrow.get_path().iter_segments(curves = False)])
# arrow.remove()
a, = plt.plot(points[:-3,0], points[:-3,1])
plt.plot(points[-4:,0], points[-4:,1], linestyle = '--', color = a.get_color())
plt.tight_layout()
plt.show()
To my understanding, the blue line does not match the black one because iter_segments() converts curves into straight lines with a density of point too low.
How should I do to get a better result ?
You can evaluate the Bezier curve that is produced by the arrow manually.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.special import binom
fig, ax = plt.subplots()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k',
connectionstyle = "arc3, rad = -0.9" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
ax.add_patch(arrow)
bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)
def bezier(points, t=[0,1], num=200):
N = len(points)
t = np.linspace(*t, num=num)
curve = np.zeros((num, 2))
for i in range(N):
curve += np.outer(bernstein(N - 1, i, t), points[i])
return curve
verts = arrow.get_path().vertices
curve1 = bezier(verts, t=[0.0, 0.5], num=100)
curve2 = bezier(verts, t=[0.5, 1.0], num=100)
ax.plot(curve1[:,0], curve1[:,1], lw=3, color="crimson")
ax.plot(curve2[:,0], curve2[:,1], lw=3, ls="--", color="crimson")
plt.show()
As you notice the two curves, i.e. the original arrow and the manually created bezier curve, are not overlaying each other. This is because matplotlib evaluates the Bezier curve in screen space, while the manual version evaluates it in data space.
To obtain the same curve in both cases, we would need to do the evaluation in screen space, which is shown in the following (where we also plot the three bezier nodes, both in data and in pixel space).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.special import binom
fig, ax = plt.subplots()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k',
connectionstyle = "arc3, rad = -0.4" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
ax.add_patch(arrow)
ax.autoscale()
print(arrow.get_path().vertices)
bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)
def bezier(points, t=[0,1], num=200):
N = len(points)
t = np.linspace(*t, num=num)
curve = np.zeros((num, 2))
for i in range(N):
curve += np.outer(bernstein(N - 1, i, t), points[i])
return curve
trans = ax.transData
trans_inv = trans.inverted()
verts = trans.transform(arrow.get_path().vertices)
curve1 = trans_inv.transform(bezier(verts, t=[0.0, 0.5], num=100))
curve2 = trans_inv.transform(bezier(verts, t=[0.5, 1.0], num=100))
ax.plot(curve1[:,0], curve1[:,1], lw=3, color="crimson", zorder=0)
ax.plot(curve2[:,0], curve2[:,1], lw=3, ls="--", color="crimson", zorder=0)
from matplotlib.transforms import IdentityTransform
ax.plot(*trans.transform(arrow.get_path().vertices).T, ls="", marker="o",
color="C1", ms=7, transform=IdentityTransform())
ax.plot(*arrow.get_path().vertices.T, ls="", marker="o", color="C0", ms=3)
plt.show()

Plotting two cross section intensity at the same time in one figure

I have an array of shape(512,512).
Looks like, (row=x, column=y, density=z=the number of the array)
[[0.012825 0.020408 0.022976 ... 0.015938 0.02165 0.024357]
[0.036332 0.031904 0.025462 ... 0.031095 0.019812 0.024523]
[0.015831 0.027392 0.031939 ... 0.016249 0.01697 0.028686]
...
[0.024545 0.011895 0.022235 ... 0.033226 0.03223 0.030235]]
I had already drawn it into a 2D density plot. My goal is to find the center of the circle and draw a vertical and horizontal cross-section in one figure.
Now, I have the trouble to find the center of the circle and combine two cross-sections in one figure.
Please help.
This is my code:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.ndimage
data = pd.read_csv('D:/BFP.csv', header=None)
# create data
data = np.array(data)
print(data)
#plot data
side = np.linspace(-1.5,1.5,512)
x,y = np.meshgrid(side,side)
z = [[data[i][j] for i in range(len(data[0]))]for j in range(len(data))]
#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 270, 0 # These are in _pixel_ coordinates!!
x1, y1 = 270, 500
num = 512
x_, y_ = np.linspace(x0, x1, num), np.linspace(y0, y1, num)
# Extract the values along the line, using cubic interpolation
zi = scipy.ndimage.map_coordinates(z, np.vstack((x_,y_)))
#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z,origin='lower')
axes[0].plot([x0, x1], [y0, y1], 'ro-')
#axes[0].axis('image')
axes[1].plot(zi)
plt.savefig('D:/vertical.png')
plt.show()
image here:
I cannot help you with finding the center of the circle, but you can create a nice visualization of the cross section by creating 3 axes in a grid. Usually, I would use GridSpec for this, but imhsow has a tendency to mess up the relative size of the axes to maintain square pixels. Thankfully, the AxesGrid toolkit can help.
The base of the code is inspired by this matplotlib example.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
from scipy.stats import multivariate_normal
import scipy
fig, main_ax = plt.subplots(figsize=(5, 5))
divider = make_axes_locatable(main_ax)
top_ax = divider.append_axes("top", 1.05, pad=0.1, sharex=main_ax)
right_ax = divider.append_axes("right", 1.05, pad=0.1, sharey=main_ax)
# make some labels invisible
top_ax.xaxis.set_tick_params(labelbottom=False)
right_ax.yaxis.set_tick_params(labelleft=False)
main_ax.set_xlabel('dim 1')
main_ax.set_ylabel('dim 2')
top_ax.set_ylabel('Z profile')
right_ax.set_xlabel('Z profile')
x, y = np.mgrid[-1:1:.01, -1:1:.01]
pos = np.empty(x.shape + (2,))
pos[:, :, 0] = x; pos[:, :, 1] = y
rv = multivariate_normal([-0.2, 0.2], [[1, 1.5], [0.25, 0.25]])
z = rv.pdf(pos)
z_max = z.max()
cur_x = 110
cur_y = 40
main_ax.imshow(z, origin='lower')
main_ax.autoscale(enable=False)
right_ax.autoscale(enable=False)
top_ax.autoscale(enable=False)
right_ax.set_xlim(right=z_max)
top_ax.set_ylim(top=z_max)
v_line = main_ax.axvline(cur_x, color='r')
h_line = main_ax.axhline(cur_y, color='g')
v_prof, = right_ax.plot(z[:,int(cur_x)],np.arange(x.shape[1]), 'r-')
h_prof, = top_ax.plot(np.arange(x.shape[0]),z[int(cur_y),:], 'g-')
plt.show()
Just for fun, you can even make it interactive
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
from scipy.stats import multivariate_normal
import scipy
fig, main_ax = plt.subplots(figsize=(5, 5))
divider = make_axes_locatable(main_ax)
top_ax = divider.append_axes("top", 1.05, pad=0.1, sharex=main_ax)
right_ax = divider.append_axes("right", 1.05, pad=0.1, sharey=main_ax)
# make some labels invisible
top_ax.xaxis.set_tick_params(labelbottom=False)
right_ax.yaxis.set_tick_params(labelleft=False)
main_ax.set_xlabel('dim 1')
main_ax.set_ylabel('dim 2')
top_ax.set_ylabel('Z profile')
right_ax.set_xlabel('Z profile')
x, y = np.mgrid[-1:1:.01, -1:1:.01]
pos = np.empty(x.shape + (2,))
pos[:, :, 0] = x; pos[:, :, 1] = y
rv = multivariate_normal([-0.2, 0.2], [[1, 1.5], [0.25, 0.25]])
z = rv.pdf(pos)
z_max = z.max()
main_ax.imshow(z, origin='lower')
main_ax.autoscale(enable=False)
right_ax.autoscale(enable=False)
top_ax.autoscale(enable=False)
right_ax.set_xlim(right=z_max)
top_ax.set_ylim(top=z_max)
v_line = main_ax.axvline(np.nan, color='r')
h_line = main_ax.axhline(np.nan, color='g')
v_prof, = right_ax.plot(np.zeros(x.shape[1]),np.arange(x.shape[1]), 'r-')
h_prof, = top_ax.plot(np.arange(x.shape[0]),np.zeros(x.shape[0]), 'g-')
def on_move(event):
if event.inaxes is main_ax:
cur_x = event.xdata
cur_y = event.ydata
v_line.set_xdata([cur_x,cur_x])
h_line.set_ydata([cur_y,cur_y])
v_prof.set_xdata(z[:,int(cur_x)])
h_prof.set_ydata(z[int(cur_y),:])
fig.canvas.draw_idle()
fig.canvas.mpl_connect('motion_notify_event', on_move)
plt.show()
NB: the lag is just due to the convertion in gif, the update is much smoother on my machine

Changing subplot size for displaying ellipse correctly

I'm trying to plot the confidence ellipse for uniformly distributed points. When plotting the ellipse and the scatter plot using Matplotlib, I find that a portion of the ellipse is clipped by the subplot. I tried implementing other solutions suggested here on SO, given here, here, here and here. but am not able to correct the displayed plot.
How do I change the size of this subplot in order to correctly and completely display the ellipse?
Code for generating ellipse: multidimensional confidence intervals
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
def eigsorted(cov):
vals, vecs = np.linalg.eigh(cov)
order = vals.argsort()[::-1]
return vals[order], vecs[:,order]
nstd = 2
fig = plt.figure(1, figsize=(12, 5))
#ax = plt.subplots(111)
ax = fig.add_subplot(111, aspect='equal')
test1 = np.random.uniform(40, 60, 1000)
test2 = np.random.uniform(100, 120, 1000)
cov = np.cov(test1, test2)
vals, vecs = eigsorted(cov)
theta = np.degrees(np.arctan2(*vecs[:,0][::-1]))
w, h = 2 * nstd * np.sqrt(vals)
ell = Ellipse(xy=(np.mean(test1), np.mean(test2)),
width=w, height=h,
angle=theta, color='black')
ell.set_facecolor('none')
ax.add_artist(ell)
plt.scatter(test1, test2)
plt.show()
As #ImportanceOfBeingEarnest rightly commented, a patch needed to be added for displaying the ellipse correctly. Updating the code below for future reference of others.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Ellipse
def eigsorted(cov):
vals, vecs = np.linalg.eigh(cov)
order = vals.argsort()[::-1]
return vals[order], vecs[:,order]
nstd = 2
fig = plt.figure(1, figsize=(12, 5))
#ax = plt.subplots(111)
ax = fig.add_subplot(111, aspect='equal')
test1 = np.random.uniform(40, 60, 1000)
test2 = np.random.uniform(100, 120, 1000)
cov = np.cov(test1, test2)
vals, vecs = eigsorted(cov)
theta = np.degrees(np.arctan2(*vecs[:,0][::-1]))
w, h = 2 * nstd * np.sqrt(vals)
ell = Ellipse(xy=(np.mean(test1), np.mean(test2)),
width=w, height=h,
angle=theta, color='black')
ell.set_facecolor('none')
#Corrected code for displaying ellipse correctly
ax.add_patch(ell)
plt.scatter(test1, test2)
plt.show()

update x value using slider matplotlib

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 ;-)

Updating the colorbar in the contourf plot in matplotlib

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()

Categories

Resources