I want to 'simulate' the location of two objects in a plot. I wanted to do this with axvspan. I want to move one axvspan with a slider. I want to do something like I this:
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
t = 2
g0 = 10
filter_loc, = plt.axvspan((50-g0-t), (50-g0), color = "blue")
sensor_loc = plt.axvspan(50,80, color="red")
plt.axis([0, 1, -10, 10])
axgap = plt.axes([0.25, 0.1, 0.65, 0.03])
sgap = Slider(axgap, 'Gap', 0.01, 30.0, valinit=g0)
def update(val):
gap = sgap.val
filter_loc.set_xdata(50-gap-t,50-gap)
fig.canvas.draw_idle()
sgap.on_changed(update)
plt.show()
When try it in several ways I always get the error:
filter_loc, = plt.axvspan((50-g0-t), (50-g0))
TypeError: 'Polygon' object is not iterable
So it seems to me that the axvspan does not like the updating of the values by using the slider. Are there ways I can still achieve this by using axvspan?
You've an extra comma right after filter_loc.
Besides that, filter_loc doesn't have a set_xdata attribute. You may modify it by calling set_xy(), for example something like this will do:
def set_xvalues(polygon, x0, x1):
_ndarray = polygon.get_xy()
_ndarray[:, 0] = [x0, x0, x1, x1, x0]
polygon.set_xy(_ndarray)
def update(val):
gap = sgap.val
set_xvalues(filter_loc, 50-gap-t, 50-gap)
fig.canvas.draw_idle()
BTW the current plt.axis() values are preventing the blue square from being shown.
Related
I am performing a data analysis in Python. My data is huge and when plotted, it is so messy to see. So I am trying to use X axis slider, so that, an user can slide over the x axis and visualize it easily .
I tried the following code :
fig, ax1 = plt.subplots(figsize=(18,7))
plt.subplots_adjust(bottom=0.25)
Trial = ax1.plot(x,y,'.', color='blue',markersize=1,label='B1')
axpos = plt.axes([0.25, 0.05, 0.65, 0.03])
spos = Slider(axpos, 'Time', matplotlib.dates.date2num(2022,1,28), matplotlib.dates.date2num(2022,4,12))
def update(val):
pos = spos.val
ax1.axis([matplotlib.dates.date2num(pos), matplotlib.dates.date2num(pos)+relativedelta(months = 1),80,100])
fig.canvas.draw_idle()
spos.on_changed(update)
plt.show()
And I got this output as in figure
I don't know what mistake I am doing. I want a figure where I want to have X axis for one month and then slide it for next month. For example if my start date is 2022,1,28, I want to view it until 2022,2,28 at once and then use the slider to view it for next month. Can anyone help me please ?
here, but you have to replace x and y with your data :
import matplotlib.pyplot as plt
import numpy as np
import ipywidgets as wg
#wg.interact(this=wg.FloatSlider(min=1, max=10, step=0.5, layout=wg.Layout(width="500px")))
def run(this):
fig, ax1 = plt.subplots(figsize=(8,4))
plt.subplots_adjust(bottom=0.25)
# x and y must be your data
x= np.linspace(-5,5,1000)
y= np.sin(x)
Trial = ax1.plot(x,y,'.', color='blue',markersize=1,label='B1')
def update(val):
pos = this
ax1.axis([matplotlib.dates.date2num(pos), matplotlib.dates.date2num(pos)+relativedelta(months = 1),80,100])
ax1.set_xlim(-this, this)
Current version of matplotlib do not allow box-forced anymore, how should I do the same thing as the answer?
I am using matplotlib 3.1.0. After I ploted another set of data on the same plot with twinx() function, I want to change the aspect ratio of the actual plot area to 1.
Normally I do this and it works for non-twinx axis
ratio = 1
xleft, xright = ax.get_xlim()
ybottom, ytop = ax.get_ylim()
ax.set_aspect(abs((xright - xleft) / (ybottom - ytop)) * ratio)
For twinx axis, the above code do not work, but will not raise any error either.
Then I found an answer here
The code basically used the same method to set aspect ratio to 1, only with box-forced option.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 1.6, 50) + 50.0
fig, ax = plt.subplots()
ax2 = ax.twinx()
XLIM = [50.0, 51.6]
YLIM = [0.0, 1.1, 0.0, 11.0]
ax.plot(x, np.sin(x - 50.0), 'b')
ax2.plot(x, np.cos(x - 50.0) * 10., 'r')
# set aspect to 1
ax.set(adjustable='box-forced',
xlim=XLIM, ylim=YLIM[:2],
xticks=np.arange(XLIM[0], XLIM[1], 0.2),
yticks=np.arange(YLIM[0], YLIM[1] + 0.1, 0.1)[:-1],
aspect=(XLIM[1] - XLIM[0]) / (YLIM[1] - YLIM[0]))
ax2.set(adjustable='box-forced',
ylim=YLIM[2:],
yticks=np.arange(YLIM[2], YLIM[3] + 1.0, 1.0),
aspect=(XLIM[1] - XLIM[0]) / (YLIM[3] - YLIM[2]))
ax.grid(True, which='major', linestyle='solid')
plt.show()
This code in my python don't work, raises
ValueError: 'box-forced' is not a valid value for adjustable; supported values are 'box', 'datalim'
And if I change that to 'box', it gives
RuntimeError: Adjustable 'box' is not allowed in a twinned Axes. Use 'datalim' instead.
I am not sure from when the box-forced was removed.
Now how should we set the aspect ratio in a 'box' manner?
Thanks!
For reference: matplotlib.axes.Axes.set_adjustable
As I just commented on a respective matplotlib issue,
"aspect" in matplotlib always refers to the data, not the axes box. Therefore setting the aspect for twinned or shared axes and letting the box be adjustable actually only makes sense when the scales are the same - or differ by an offset (as opposed to any other linear or nonlinear function). Matplotlib does not perform any check on this, so it disallows for adjustable='box' in such case.
It seems to me that using aspect here is merely a workaround for getting a fixed ratio for the axes box. Matplotlib does not provide any clear codepath for that as of now, but one could e.g. force the axes box into a square space by adjusting the subplot parameters
import numpy as np
import matplotlib.pyplot as plt
def squarify(fig):
w, h = fig.get_size_inches()
if w > h:
t = fig.subplotpars.top
b = fig.subplotpars.bottom
axs = h*(t-b)
l = (1.-axs/w)/2
fig.subplots_adjust(left=l, right=1-l)
else:
t = fig.subplotpars.right
b = fig.subplotpars.left
axs = w*(t-b)
l = (1.-axs/h)/2
fig.subplots_adjust(bottom=l, top=1-l)
x = np.linspace(0,1.6,50) + 50.0
fig, ax = plt.subplots()
ax2 = ax.twinx()
ax.set(xlim = [50.0, 51.6], ylim = [0.0, 1.1])
ax2.set(ylim = [0.0, 11.0])
ax.plot(x,np.sin(x-50.0),'b')
ax2.plot(x,np.cos(x-50.0)*10.,'r')
ax.grid(True, which='major',linestyle='solid')
squarify(fig)
fig.canvas.mpl_connect("resize_event", lambda evt: squarify(fig))
plt.show()
Also see this answer for more than one subplot.
If you want to use mpl_toolkits and make your hands dirty, this answer would be a good read.
Thanks to #ImportanceOfBeingErnest, but to make this work in several subplots, I found another way inspired by your answer:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import Divider, Size
from mpl_toolkits.axes_grid1.axes_divider import AxesDivider
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
def demo_fixed_size_axes():
fig, axs = plt.subplots(1, 2, figsize=(12, 9))
axs[0].plot([1, 2, 3])
axs[1].plot([1, 2, 3.5])
ax3 = axs[1].twinx()
ax3.plot([1, 2, 3], [1, 25, 30])
axs[1].spines['right'].set_visible(False)
make_patch_spines_invisible(ax4Alt)
ax4Alt.spines['right'].set_visible(True)
for ax in fig.get_axes():
figPos = AxesDivider(ax).get_position()
h = [Size.Fixed(4)] # has to be fixed
v = h
divider = Divider(fig, figPos, h, v, aspect=False)
ax.set_axes_locator(divider.new_locator(nx=0, ny=0))
if __name__ == "__main__":
demo_fixed_size_axes()
plt.show()
The disadvantage is that one has to decide which size to use in inches.
I do not fully understand my code though...
I'm trying to create a 3D surface plot whose facecolors values can be interactively updated through sliders. It is worth noting that in my case the facecolors values don't have anything to do with the coordinates position of the surface. The surface is there only to represent a certain geometry, and the facecolors are the values mapped into that geometry.
As a basic example, I tried to create a plane with the facecolors are provided by a function of an euclidean distance to a given center. The center is the parameter that I will be able to adjust through the sliders. Here is my code:
from numpy import pi, sin
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm, colors
from matplotlib.widgets import Slider, Button, RadioButtons
def signal(amp, freq):
return amp * sin(2 * pi * freq * t)
axis_color = 'lightgoldenrodyellow'
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Adjust the subplots region to leave some space for the sliders and buttons
fig.subplots_adjust(bottom=0.25)
x = np.linspace(0.0, 1.0, num=50)
y = np.linspace(0.0, 1.0, num=50)
t = np.linspace(0.0, 1.0, num=50)
x, y = np.meshgrid(x, y)
z = 1.0 * x + 2.0 * y
def colormapping_values(x_center, y_center):
return (x-x_center)**2 + (y-y_center)**2
x0 = 0.5
y0 = 0.5
norm = colors.Normalize()
somtethin = cm.jet(norm(colormapping_values(x0, y0)))
surf = ax.plot_surface(x, y, z, facecolors=somtethin)
# Draw the initial plot
# The 'line' variable is used for modifying the line later
# Add two sliders for tweaking the parameters
# Define an axes area and draw a slider in it
x_center_slider_pos = fig.add_axes([0.15, 0.15, 0.65, 0.03], facecolor=axis_color)
x_center_slider = Slider(x_center_slider_pos, 'X center', 0.0, 1.0, valinit=x0)
# Draw another slider
y_center_slider_pos = fig.add_axes([0.15, 0.1, 0.65, 0.03], facecolor=axis_color)
y_center_slider = Slider(y_center_slider_pos, 'Y center', 0.0, 1.0, valinit=y0)
# Define an action for modifying the line when any slider's value changes
def sliders_on_changed(val):
print(cm.jet(colormapping_values(x_center_slider.val, y_center_slider.val)).shape)
surf.set_facecolors(cm.jet(colormapping_values(x_center_slider.val, y_center_slider.val)))
fig.canvas.draw_idle()
x_center_slider.on_changed(sliders_on_changed)
y_center_slider.on_changed(sliders_on_changed)
# Add a button for resetting the parameters
reset_button_ax = fig.add_axes([0.8, 0.025, 0.1, 0.04])
reset_button = Button(reset_button_ax, 'Reset', color=axis_color, hovercolor='0.975')
def reset_button_on_clicked(mouse_event):
y_center_slider.reset()
x_center_slider.reset()
reset_button.on_clicked(reset_button_on_clicked)
plt.show()
The initial plot is correct, however when I click the slider, I get a "ValueError: Invalid RGBA argument", in the line "surf.set_facecolors(cm.jet(colormapping_values(x_center_slider.val, y_center_slider.val)))". I suppose I am doing something wrong when using that function. I've done my research, but I have not yet come to a conclusion of what I am doing wrong, as the use is almost identical to what I am doing when I pass the parameter facecolors
You need to give a 1D array or list of colours to .set_facecolors(), rather than the 2D array you are currently giving it.
To do this, reshape your array to something with the shape ((len(x)-1 * len(y)-1), 4). (note that the 4 is for the 4-channel colour value).
c_len = (len(x)-1 * len(y)-1)
def sliders_on_changed(val):
print(cm.jet(colormapping_values(x_center_slider.val, y_center_slider.val)).shape)
surf.set_facecolors(
cm.jet(colormapping_values(x_center_slider.val, y_center_slider.val)
)[:-1, :-1].reshape(c_len, 4))
surf.set_edgecolors(
cm.jet(colormapping_values(x_center_slider.val, y_center_slider.val)
)[:-1, :-1].reshape(c_len, 4))
fig.canvas.draw_idle()
Note you also need to set the edgecolors in the same way.
Testing this out with some different slider values:
I have a 4D data set (for those who care, its an astronomical Position-Position-Temperature-Opacity image) in a numpy array, that I need to plot in an interactive way. While there are programs to do this, none of them can handle the unusual form that my data steps in (but I can worry about that, thats not part of the question).
I know how to get it plotting with one Slider, but really I need to plot the image with 2 Sliders, one for each of temperature and opacity.
My MWE of a 3D array code is below:
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
array = np.random.rand(300,300,10)
axis = 2
s = [slice(0, 1) if i == axis else slice(None) for i in xrange(array.ndim)]
im = array[s].squeeze()
fig = plt.figure()
ax = plt.subplot(111)
l = ax.imshow(im, origin = 'lower')
axcolor = 'lightgoldenrodyellow'
ax = fig.add_axes([0.2, 0.95, 0.65, 0.03], axisbg=axcolor)
slider = Slider(ax, 'Temperature', 0, array.shape[axis] - 1,
valinit=0, valfmt='%i')
def update(val):
ind = int(slider.val)
s = [slice(ind, ind + 1) if i == axis else slice(None)
for i in xrange(array.ndim)]
im = array[s].squeeze()
l.set_data(im)
fig.canvas.draw()
slider.on_changed(update)
plt.show()
Any way to do it with 2 sliders?
EDIT: The problem I am having is I dont know how to expand to 2 sliders. Particularly how to adapt the line
s = [slice(0, 1) if i == axis else slice(None) for i in xrange(array.ndim)]
and how to modify the update function when I go from np.random.rand(300,300,10) to np.random.rand(300,300,10,10). I supposed I will need to declare both a T_axis = 2 and B_axis = 3 rather than simply an axis = 2, but beyond that, I am rather stuck as to how to modify it.
As I interprete the data structure, you have an array of shape (300,300,n,m), where n is the number of temperatures and m is the number of opacities. The image to show for the ith temperature and the jth opacity is hence, array[:,:,i,j].
You now need of course two different silders where one determines the value of i and the other of j.
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
array = np.random.rand(300,300,10,9)
# assuming you have for each i=Temperature index and j =Opacity index
# an image array(:,:,i,j)
fig, ax = plt.subplots()
l = ax.imshow(array[:,:,0,0], origin = 'lower')
axT = fig.add_axes([0.2, 0.95, 0.65, 0.03])
axO = fig.add_axes([0.2, 0.90, 0.65, 0.03])
sliderT = Slider(axT, 'Temperature', 0, array.shape[2]-1, valinit=0, valfmt='%i')
sliderO = Slider(axO, 'Opacity', 0, array.shape[3]-1, valinit=0, valfmt='%i')
def update(val):
i = int(sliderT.val)
j = int(sliderO.val)
im = array[:,:,i,j]
l.set_data(im)
fig.canvas.draw_idle()
sliderT.on_changed(update)
sliderO.on_changed(update)
plt.show()
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 ;-)