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()
Related
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()
In the following code, the color of bars changes as the threshold is changed. Instead of using the threshold and plotting the horizontal line in the code, I want to use the y parameter in the OnMouseMove function so that the user can change the location of "threshold". Then, I want the colors to be updated as the y is changed.
I think what I need is called "observer pattern" or perhaps a trick using the animation tools but not sure how to implement it. I appreciate any insight on how to do this. Thanks
%matplotlib notebook
import pandas as pd
import numpy as np
from scipy import stats
import matplotlib.colors as mcol
import matplotlib.cm as cm
import matplotlib.pyplot as plt
np.random.seed(12345)
df = pd.DataFrame([np.random.normal(335,1500,300),
np.random.normal(410,900,300),
np.random.normal(410,1200,300),
np.random.normal(480,550,300)],
index=[1,2,3,4])
fig, ax = plt.subplots()
plt.show()
bars = plt.bar(range(df.shape[0]), df.mean(axis = 1), color = 'lightslategrey')
fig = plt.gcf()
threshold=420
plt.axhline(y = threshold, color = 'grey', alpha = 0.5)
cm1 = mcol.LinearSegmentedColormap.from_list("Test",["b", "white", "purple"])
cpick = cm.ScalarMappable(cmap=cm1)
cpick.set_array([])
percentages = []
for bar in bars:
percentage = (bar.get_height()-threshold)/bar.get_height()
if percentage>1: percentage = 1
if percentage<0: percentage=0
percentages.append(percentage)
cpick.to_rgba(percentages)
bars = plt.bar(range(df.shape[0]), df.mean(axis = 1), color = cpick.to_rgba(percentages))
plt.colorbar(cpick, orientation='horizontal')
def onMouseMove(event):
ax.lines = [ax.lines[0]]
plt.axhline(y=event.ydata, color="k")
fig.canvas.mpl_connect('motion_notify_event', onMouseMove)
plt.xticks(range(df.shape[0]), df.index, alpha = 0.8)
First you should use exactly one bar plot and exactly one axhline (using more will make everything chaotic). You can set the colors of the bars via
for bar in bars:
bar.set_color(..)
and you can update the axhline's position via line.set_ydata(position).
Now, for every mouse move event you need to update the axhline's position, calculate the percentages and apply a new colors to the bars. So those things should be done in a function, which is called every time the mouse move event is triggered. After those settings have been applied the canvas needs to be drawn for them to become visible.
Here is a complete code.
import pandas as pd
import numpy as np
import matplotlib.colors as mcol
import matplotlib.cm as cm
import matplotlib.pyplot as plt
np.random.seed(12345)
df = pd.DataFrame([np.random.normal(335,1500,300),
np.random.normal(410,900,300),
np.random.normal(410,1200,300),
np.random.normal(480,550,300)],
index=[1,2,3,4])
fig, ax = plt.subplots()
threshold=420.
bars = plt.bar(range(df.shape[0]), df.mean(axis = 1), color = 'lightslategrey')
axline = plt.axhline(y = threshold, color = 'grey', alpha = 0.5)
cm1 = mcol.LinearSegmentedColormap.from_list("Test",["b", "white", "purple"])
cpick = cm.ScalarMappable(cmap=cm1)
cpick.set_array([])
plt.colorbar(cpick, orientation='horizontal')
def percentages(threshold):
percentages = []
for bar in bars:
percentage = (bar.get_height()-threshold)/bar.get_height()
if percentage>1: percentage = 1
if percentage<0: percentage=0
percentages.append(percentage)
return percentages
def update(threshold):
axline.set_ydata(threshold)
perc = percentages(threshold)
for bar, p in zip(bars, perc):
bar.set_color(cpick.to_rgba(p))
# update once before showing
update(threshold)
def onMouseMove(event):
if event.inaxes == ax:
update(event.ydata)
fig.canvas.draw_idle()
fig.canvas.mpl_connect('motion_notify_event', onMouseMove)
plt.xticks(range(df.shape[0]), df.index, alpha = 0.8)
plt.show()
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,
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,
I would like to reduce the width of the boxes in the boxplot below. Here's my code, but it is not working:
bp = plt.boxplot(boxes, widths = 0.6, patch_artist = True)
From the documentation there is a widths option:
widths : array-like, default = 0.5
Either a scalar or a vector and sets the width of each box. The default is 0.5, or 0.15*(distance between extreme positions) if that is smaller.
Here is an example:
import numpy as np
import matplotlib.pyplot as plt
np.random.seed(937)
data = np.random.lognormal(size=(37, 4), mean=1.5, sigma=1.75)
labels = list('ABCD')
fs = 10 # fontsize
plt.boxplot(data, labels=labels, showfliers=False, widths=(1, 0.5, 1.2, 0.1))
plt.show()
Try working via the axes and see if it works:
fig = plt.figure()
ax = fig.add_subplot(111)
ax.boxplot(boxes, widths = 0.6, patch_artist = True)