Slider on matshow for 3rd dimension [duplicate] - python

I am generating 2D heat map plots of a set of 3D data. I would like to be able to have a mechanism to interactively page through each pane. Below is a simple sample code, I would like to be able to interactively view both panes (ie, z = [0,1]) via a slider bar (or some other means). Is this possible with matplotlib or is this something I'll need to do post processing after generating the image files?
import numpy as np
from matplotlib import pyplot as plt
data = np.random.randint(10, size=(5, 5, 2))
data_slice = np.zeros((5,5))
for i in range(0, 5):
for j in range(0, 5):
data_slice[i][j] = data[i][j][0]
plt.imshow(data_slice, cmap='hot', interpolation='nearest')
plt.show()
Edit : I want to be able to do this interactively and it appears that the possible duplicate is trying to do this automatically.

The solution could indeed be to use a Slider as in the excellent answer by #hashmuke. In his answer he mentioned that
"The slider is continuous while the layer index is a discrete integer [...]"
This brought me to think about a solution that wouldn't have this restriction and have
a more page-like look and feel.
The outcome is PageSlider. Subclassing Slider it makes use of the slider functionality, but displays the slider in integer steps starting at 1. It takes the number of pages numpages as init argument, but except of that works as Slider seen from the outside. Additionally it also provides a back- and forward button.
An example, similar to the one from #hashmuke, is given below the class.
import matplotlib.widgets
import matplotlib.patches
import mpl_toolkits.axes_grid1
class PageSlider(matplotlib.widgets.Slider):
def __init__(self, ax, label, numpages = 10, valinit=0, valfmt='%1d',
closedmin=True, closedmax=True,
dragging=True, **kwargs):
self.facecolor=kwargs.get('facecolor',"w")
self.activecolor = kwargs.pop('activecolor',"b")
self.fontsize = kwargs.pop('fontsize', 10)
self.numpages = numpages
super(PageSlider, self).__init__(ax, label, 0, numpages,
valinit=valinit, valfmt=valfmt, **kwargs)
self.poly.set_visible(False)
self.vline.set_visible(False)
self.pageRects = []
for i in range(numpages):
facecolor = self.activecolor if i==valinit else self.facecolor
r = matplotlib.patches.Rectangle((float(i)/numpages, 0), 1./numpages, 1,
transform=ax.transAxes, facecolor=facecolor)
ax.add_artist(r)
self.pageRects.append(r)
ax.text(float(i)/numpages+0.5/numpages, 0.5, str(i+1),
ha="center", va="center", transform=ax.transAxes,
fontsize=self.fontsize)
self.valtext.set_visible(False)
divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
bax = divider.append_axes("right", size="5%", pad=0.05)
fax = divider.append_axes("right", size="5%", pad=0.05)
self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_back.label.set_fontsize(self.fontsize)
self.button_forward.label.set_fontsize(self.fontsize)
self.button_back.on_clicked(self.backward)
self.button_forward.on_clicked(self.forward)
def _update(self, event):
super(PageSlider, self)._update(event)
i = int(self.val)
if i >=self.valmax:
return
self._colorize(i)
def _colorize(self, i):
for j in range(self.numpages):
self.pageRects[j].set_facecolor(self.facecolor)
self.pageRects[i].set_facecolor(self.activecolor)
def forward(self, event):
current_i = int(self.val)
i = current_i+1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
def backward(self, event):
current_i = int(self.val)
i = current_i-1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
if __name__ == "__main__":
import numpy as np
from matplotlib import pyplot as plt
num_pages = 23
data = np.random.rand(9, 9, num_pages)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
im = ax.imshow(data[:, :, 0], cmap='viridis', interpolation='nearest')
ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
slider = PageSlider(ax_slider, 'Page', num_pages, activecolor="orange")
def update(val):
i = int(slider.val)
im.set_data(data[:,:,i])
slider.on_changed(update)
plt.show()

You can either animate the layers as suggested by Andrew's comment or you can manually walk through the the layers using a slider as follow:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
# generate a five layer data
data = np.random.randint(10, size=(5, 5, 5))
# current layer index start with the first layer
idx = 0
# figure axis setup
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)
# display initial image
im_h = ax.imshow(data[:, :, idx], cmap='hot', interpolation='nearest')
# setup a slider axis and the Slider
ax_depth = plt.axes([0.23, 0.02, 0.56, 0.04])
slider_depth = Slider(ax_depth, 'depth', 0, data.shape[2]-1, valinit=idx)
# update the figure with a change on the slider
def update_depth(val):
idx = int(round(slider_depth.val))
im_h.set_data(data[:, :, idx])
slider_depth.on_changed(update_depth)
plt.show()
The slider is continues while the layer index is discrete integer, I hope that is not a problem. Here is the resulting figure,

Related

How can I adjust 2 plot with one button in matplotlib

I'm just studying Python for a month and have no experience.
I'm trying to hide/show two graphs with one Check button in matplotlib. But with my code, when clicking the button, there is only one graph hidden. Please see my code and show me my mistake.
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import math
from matplotlib.widgets import Button, RadioButtons, CheckButtons
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
p = ax.scatter(5,6,7) and ax.scatter(1,2,3, color='red', marker='+', s=1e2)
lines = [p]
labels = ["Hide/Show"]
def func1(label):
index = labels.index(label)
lines[index].set_visible(not lines[index].get_visible())
fig.canvas.draw()
a = [True]
# xposition, yposition, width, height
ax_check = plt.axes([0, 0.01, 0.25, 0.25])
plot_button = CheckButtons(ax_check, labels, a)
plot_button.on_clicked(func1)
plt.show()
# D.L Your suggestion is perfect.
Just add another line to the figure and in the function fun1 add a calling of the line 2:
import pandas as pd
import matplotlib.pyplot as plt
import numpy as np
import math
from matplotlib.widgets import Button, RadioButtons, CheckButtons
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
p1 = ax.scatter(5,6,7)
p2 = ax.scatter(1,2,3, color='red', marker='+', s=1e2)
lines = [p1, p2]
labels = ["Hide/Show"]
def func1(label):
index = labels.index(label)
lines[index].set_visible(not lines[index].get_visible())
lines[index+1].set_visible(not lines[index+1].get_visible())
fig.canvas.draw()
a = [True]
# xposition, yposition, width, height
ax_check = plt.axes([0, 0.01, 0.25, 0.25])
plot_button = CheckButtons(ax_check, labels, a)
plot_button.on_clicked(func1)
plt.show()

Inconsistent label position with gridspec

I'm try to label each of the differently-sized panels in a Gridspec at the upper left corner outside the panel. However, this results in the labels being offset by different distances. I think this is because the x and y offset are a function the axis dimensions, but what's the most elegant way to make the labels all the same absolute horizontal and vertical distance to their respective panels? Here's the code:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.gridspec import GridSpec
def label_panels(fig, panels, xloc = -0.1, yloc = 1.2, size = 20):
for n, ax in enumerate(fig.axes):
if n < len(panels):
ax.text(xloc, yloc, panels[n],
size=size, weight='bold')
fig_7 = plt.figure(constrained_layout=True, figsize = (4,5))
fig7_gs = GridSpec(5, 4, figure=fig_7)
exemplars_subplots = np.array([[fig_7.add_subplot(fig7_gs[i:i+1, j]) for i in range(2)] for j in range(4)])
STDP_subplots = [fig_7.add_subplot(fig7_gs[2+i:3+i,0:2]) for i in range(3)]
ax_imshow = fig_7.add_subplot(fig7_gs[2:5, 2:4])
label_panels(fig_7, range(len(fig_7.axes)), xloc = -0.25, yloc = 1.2)
plt.show()
And this is what it shows. As you can see, panels 0-7 have a different label offset than 8-10 and 11 has a different offset than both of them.:
You are quite correct, the text positions need to be corrected to the actual scale for each axis. For this, you need to obtain the dimension of the gridspec container and the relative dimension of each axis.
A solution (Note: this is quickly written, this should be cleaned up especially with regards to the parameters actually needed for label_panels()) might look like this:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.gridspec import GridSpec
## need to include transforms
import matplotlib.transforms as transforms
def label_panels(fig, panels, xloc = 0, yloc = 0, size = 20):
# first, we get the bounding box for the gridspec "container"
gs_bb=transforms.Bbox(fig7_gs[1].get_position(fig))
x0,y0,x1,y1= gs_bb.extents
_gsu_x=x1-x0
_gsu_y=y1-y0
for n, ax in enumerate(fig.axes):
if n < len(panels):
# now, we get the bounding box for each axis inside the gridspec
ax_bb=transforms.Bbox(ax.get_position(fig))
x0,y0,x1,y1= ax_bb.extents
ax_scaling_x=(x1-x0)/_gsu_x
ax_scaling_y=(y1-y0)/_gsu_y
print(f"ax {n} has scaling: ({ax_scaling_x:.2f},{ax_scaling_y:.2f})",)
ax.text(-0.1, 1+0.2/ax_scaling_y, panels[n],
ha="center",va="center",
transform=ax.transAxes,
size=size, weight='bold')
fig_7 = plt.figure(constrained_layout=True, figsize = (4,5))
fig7_gs = GridSpec(5, 4, figure=fig_7)
exemplars_subplots = np.array([[fig_7.add_subplot(fig7_gs[i:i+1, j]) for i in range(2)] for j in range(4)])
STDP_subplots = [fig_7.add_subplot(fig7_gs[2+i:3+i,0:2]) for i in range(3)]
ax_imshow = fig_7.add_subplot(fig7_gs[2:5, 2:4])
label_panels(fig_7, range(len(fig_7.axes)), xloc = -0.25, yloc = 1.2)
plt.show()
Thanks to Jody's link, this works:
import matplotlib.transforms as transforms
def label_panels(fig, labels, xloc = 0, yloc = 1.0, size = 20):
for n, ax in enumerate(fig.axes):
trans = transforms.ScaledTranslation(-20/72, 7/72, fig.dpi_scale_trans)
ax.text(xloc, yloc, labels[n], transform=ax.transAxes + trans,
fontsize=size, va='bottom')

matplotlib.widgets.Slider with histogram

I'm trying to make the interactive histogram but during
update old histogram is not been cleared, as shown in the image below.
)
above plot is generated using following code
from functools import lru_cache
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
weight = ss.lognorm(0.23, 0, 70.8)
#lru_cache
def sampling(n):
return [ weight.rvs().mean() for i in range(1000) ]
theme = {
'color' : "#1f77b4",
'alpha' : 0.7,
}
t = ax.hist(sampling(100), **theme)
slider = widgets.Slider(
ax = plt.axes([0.25, 0.1, 0.5, 0.03]),
label = "n",
valmin = 10,
valmax = 1000,
valinit = 100,
valstep = 1
def update(val):
global t
del t
t = ax.hist(sampling(int(val)), **theme)
fig.canvas.draw_idle()
slider.on_changed(update)
ax.set_title('Distribution of Sample Size Mean')
plt.show()
ax.hist is returning a tuple containing the number of bins, the edges of the bins and then the patches... you thus need to capture the patches and remove them in update.
You need to use t.remove() in update, as in:
*_, patches = ax.hist()
def update(val):
global patches
for p in patches:
p.remove()
*_, patches = ax.hist(sampling(int(val)), **theme)
fig.canvas.draw_idle()

Paging/scrolling through set of 2D heat maps in matplotlib

I am generating 2D heat map plots of a set of 3D data. I would like to be able to have a mechanism to interactively page through each pane. Below is a simple sample code, I would like to be able to interactively view both panes (ie, z = [0,1]) via a slider bar (or some other means). Is this possible with matplotlib or is this something I'll need to do post processing after generating the image files?
import numpy as np
from matplotlib import pyplot as plt
data = np.random.randint(10, size=(5, 5, 2))
data_slice = np.zeros((5,5))
for i in range(0, 5):
for j in range(0, 5):
data_slice[i][j] = data[i][j][0]
plt.imshow(data_slice, cmap='hot', interpolation='nearest')
plt.show()
Edit : I want to be able to do this interactively and it appears that the possible duplicate is trying to do this automatically.
The solution could indeed be to use a Slider as in the excellent answer by #hashmuke. In his answer he mentioned that
"The slider is continuous while the layer index is a discrete integer [...]"
This brought me to think about a solution that wouldn't have this restriction and have
a more page-like look and feel.
The outcome is PageSlider. Subclassing Slider it makes use of the slider functionality, but displays the slider in integer steps starting at 1. It takes the number of pages numpages as init argument, but except of that works as Slider seen from the outside. Additionally it also provides a back- and forward button.
An example, similar to the one from #hashmuke, is given below the class.
import matplotlib.widgets
import matplotlib.patches
import mpl_toolkits.axes_grid1
class PageSlider(matplotlib.widgets.Slider):
def __init__(self, ax, label, numpages = 10, valinit=0, valfmt='%1d',
closedmin=True, closedmax=True,
dragging=True, **kwargs):
self.facecolor=kwargs.get('facecolor',"w")
self.activecolor = kwargs.pop('activecolor',"b")
self.fontsize = kwargs.pop('fontsize', 10)
self.numpages = numpages
super(PageSlider, self).__init__(ax, label, 0, numpages,
valinit=valinit, valfmt=valfmt, **kwargs)
self.poly.set_visible(False)
self.vline.set_visible(False)
self.pageRects = []
for i in range(numpages):
facecolor = self.activecolor if i==valinit else self.facecolor
r = matplotlib.patches.Rectangle((float(i)/numpages, 0), 1./numpages, 1,
transform=ax.transAxes, facecolor=facecolor)
ax.add_artist(r)
self.pageRects.append(r)
ax.text(float(i)/numpages+0.5/numpages, 0.5, str(i+1),
ha="center", va="center", transform=ax.transAxes,
fontsize=self.fontsize)
self.valtext.set_visible(False)
divider = mpl_toolkits.axes_grid1.make_axes_locatable(ax)
bax = divider.append_axes("right", size="5%", pad=0.05)
fax = divider.append_axes("right", size="5%", pad=0.05)
self.button_back = matplotlib.widgets.Button(bax, label=ur'$\u25C0$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_forward = matplotlib.widgets.Button(fax, label=ur'$\u25B6$',
color=self.facecolor, hovercolor=self.activecolor)
self.button_back.label.set_fontsize(self.fontsize)
self.button_forward.label.set_fontsize(self.fontsize)
self.button_back.on_clicked(self.backward)
self.button_forward.on_clicked(self.forward)
def _update(self, event):
super(PageSlider, self)._update(event)
i = int(self.val)
if i >=self.valmax:
return
self._colorize(i)
def _colorize(self, i):
for j in range(self.numpages):
self.pageRects[j].set_facecolor(self.facecolor)
self.pageRects[i].set_facecolor(self.activecolor)
def forward(self, event):
current_i = int(self.val)
i = current_i+1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
def backward(self, event):
current_i = int(self.val)
i = current_i-1
if (i < self.valmin) or (i >= self.valmax):
return
self.set_val(i)
self._colorize(i)
if __name__ == "__main__":
import numpy as np
from matplotlib import pyplot as plt
num_pages = 23
data = np.random.rand(9, 9, num_pages)
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.18)
im = ax.imshow(data[:, :, 0], cmap='viridis', interpolation='nearest')
ax_slider = fig.add_axes([0.1, 0.05, 0.8, 0.04])
slider = PageSlider(ax_slider, 'Page', num_pages, activecolor="orange")
def update(val):
i = int(slider.val)
im.set_data(data[:,:,i])
slider.on_changed(update)
plt.show()
You can either animate the layers as suggested by Andrew's comment or you can manually walk through the the layers using a slider as follow:
import numpy as np
from matplotlib import pyplot as plt
from matplotlib.widgets import Slider
# generate a five layer data
data = np.random.randint(10, size=(5, 5, 5))
# current layer index start with the first layer
idx = 0
# figure axis setup
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.15)
# display initial image
im_h = ax.imshow(data[:, :, idx], cmap='hot', interpolation='nearest')
# setup a slider axis and the Slider
ax_depth = plt.axes([0.23, 0.02, 0.56, 0.04])
slider_depth = Slider(ax_depth, 'depth', 0, data.shape[2]-1, valinit=idx)
# update the figure with a change on the slider
def update_depth(val):
idx = int(round(slider_depth.val))
im_h.set_data(data[:, :, idx])
slider_depth.on_changed(update_depth)
plt.show()
The slider is continues while the layer index is discrete integer, I hope that is not a problem. Here is the resulting figure,

Changing the MinorLocator when plotting with AxisGrid

I am using this function from the documentation to make a nice plot with matplotlib.
def demo_grid_with_single_cbar(fig):
"""
A grid of 2x2 images with a single colorbar
"""
grid = AxesGrid(fig, 132, # similar to subplot(132)
nrows_ncols = (2, 2),
axes_pad = 0.0,
share_all=True,
label_mode = "L",
cbar_location = "top",
cbar_mode="single",
)
Z, extent = get_demo_image()
for i in range(4):
im = grid[i].imshow(Z, extent=extent, interpolation="nearest")
#plt.colorbar(im, cax = grid.cbar_axes[0])
grid.cbar_axes[0].colorbar(im)
for cax in grid.cbar_axes:
cax.toggle_label(False)
# This affects all axes as share_all = True.
grid.axes_llc.set_xticks([-2, 0, 2])
grid.axes_llc.set_yticks([-2, 0, 2])
I would like to change the minorlocator. but I really have no clue where to change the code.
If it where just a single graphics I would manipulate the axes object. But with AxesGrid I am lost.
majorLocator = MultipleLocator(50)
majorFormatter = FormatStrFormatter('%d')
minorLocator = MultipleLocator(10)
ax.yaxis.set_major_locator(majorLocator)
ax.yaxis.set_major_formatter(majorFormatter)
ax.yaxis.set_minor_locator(minorLocator)
EDIT after the question was solved.
The result should look like the following picture, notice the minor ticks!
The result should look like this image
Maybe someone else can append it. I cannot because my reputation credits do not suffice.
Note the warning on main page. If you have a new enough version of matplotlib this works.
from mpl_toolkits.axes_grid1 import ImageGrid
def get_demo_image():
import numpy as np
from matplotlib.cbook import get_sample_data
f = get_sample_data("axes_grid/bivariate_normal.npy", asfileobj=False)
z = np.load(f)
# z is a numpy array of 15x15
return z, (-3,4,-4,3)
def demo_grid_with_single_cbar(fig):
"""
A grid of 2x2 images with a single colorbar
"""
grid = ImageGrid(fig, 132, # similar to subplot(132)
nrows_ncols = (2, 2),
axes_pad = 0.0,
share_all=True,
label_mode = "L",
cbar_location = "top",
cbar_mode="single",
)
Z, extent = get_demo_image()
for i in range(4):
im = grid[i].imshow(Z, extent=extent, interpolation="nearest")
#plt.colorbar(im, cax = grid.cbar_axes[0])
grid.cbar_axes[0].colorbar(im)
for cax in grid.cbar_axes:
cax.toggle_label(False)
# This affects all axes as share_all = True.
grid.axes_llc.set_xticks([-2, 0, 2])
grid.axes_llc.set_yticks([-2, 0, 2])
for i in range(4):
ax = grid[i]
ax.yaxis.set_minor_locator(MultipleLocator(.5))

Categories