Want to change the bar chart in matplotlib using slider [duplicate] - python

I have bar chart, with a lot of custom properties ( label, linewidth, edgecolor)
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
ax.cla()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()
With 'normal' plots I'd use set_data(), but with barchart I got an error: AttributeError: 'BarContainer' object has no attribute 'set_data'
I don't want to simply update the heights of the rectangles, I want to plot totally new rectangles. If I use ax.cla(), all my settings (linewidth, edgecolor, title..) are lost too not only my data(rectangles), and to clear many times, and reset everything makes my program laggy. If I don't use ax.cla(), the settings remain, the program is faster (I don't have to set my properties all the time), but the rectangles are drawn of each other, which is not good.
Can you help me with that?

In your case, bars is only a BarContainer, which is basically a list of Rectangle patches. To just remove those while keeping all other properties of ax, you can loop over the bars container and call remove on all its entries or as ImportanceOfBeingErnest pointed out simply remove the full container:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
bars.remove()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()

Related

How to update barchart in matplotlib?

I have bar chart, with a lot of custom properties ( label, linewidth, edgecolor)
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
ax.cla()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()
With 'normal' plots I'd use set_data(), but with barchart I got an error: AttributeError: 'BarContainer' object has no attribute 'set_data'
I don't want to simply update the heights of the rectangles, I want to plot totally new rectangles. If I use ax.cla(), all my settings (linewidth, edgecolor, title..) are lost too not only my data(rectangles), and to clear many times, and reset everything makes my program laggy. If I don't use ax.cla(), the settings remain, the program is faster (I don't have to set my properties all the time), but the rectangles are drawn of each other, which is not good.
Can you help me with that?
In your case, bars is only a BarContainer, which is basically a list of Rectangle patches. To just remove those while keeping all other properties of ax, you can loop over the bars container and call remove on all its entries or as ImportanceOfBeingErnest pointed out simply remove the full container:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
x = np.arange(5)
y = np.random.rand(5)
bars = ax.bar(x, y, color='grey', linewidth=4.0)
bars.remove()
x2 = np.arange(10)
y2 = np.random.rand(10)
ax.bar(x2,y2)
plt.show()

Making multiple figures in matplotlib with legend on each one

I am trying to make multiple figures in parallel, each with its own legend. My code produces multiple figures but I can only ever get the legend to appear on the last figure instance - is there a way of getting it to appear on all figures? I have a large number of datasets so I would like to be able to use a for loop (or similar) - making each figure separately is not really an option.
I have included a minimum working example below that reproduces the problem.
import numpy as np
import matplotlib.pyplot as plt
X1 = np.linspace(0,5,5)
X2 = np.linspace(1,6,5)
Y1 = np.power(X1,2)
Y2 = np.power(X2,2)
Z1 = np.power(X1,3)
Z2 = np.power(X2,3)
Xs = [X1,X2]
Ys = [Y1,Y2]
Zs = [Z1,Z2]
# Marker size
size = 100
for x,y,z, in zip(Xs,Ys,Zs):
plt.figure()
ax = plt.subplot(111)
ax.scatter(x,y,linewidth=1.5,s=size,facecolors='#0571b0',marker='o',alpha=0.5,label='A label')
ax.scatter(x,z,linewidth=1.5,s=size,facecolors='#92c5de',marker='o',alpha=0.5,label='Another label')
plt.legend(bbox_to_anchor=(1.45,1.), loc='top left',scatterpoints=1,fontsize=8)
plt.show()
It seems the legend is simply out of the figure. You place it at (1.45, 1) (in axes coordinates. Putting it at (1,1) and setting the location e.g. to loc="upper right" (note that "top left" does not exist), will produce the legend in the plot.
Here is the complete example:
import numpy as np
import matplotlib.pyplot as plt
X1 = np.linspace(0,5,5)
X2 = np.linspace(1,6,5)
Xs = [X1,X2]
Ys = [X1**2,X2**2]
Zs = [X1**3,X2**3]
# Marker size
size = 100
for x,y,z, in zip(Xs,Ys,Zs):
plt.figure()
ax = plt.subplot(111)
ax.scatter(x,y,linewidth=1.5,s=size,facecolors='#0571b0',marker='o',alpha=0.5,label='A label')
ax.scatter(x,z,linewidth=1.5,s=size,facecolors='#92c5de',marker='o',alpha=0.5,label='Another label')
plt.legend(bbox_to_anchor=(1,1), loc='upper right',scatterpoints=1,fontsize=8)
plt.show()

How can I make legend error bars horizontal in matplotlib

I want to make a graph with matplotlib where the error bars in the graph are vertical,
but the error bar in legend is horizontal. The example code (below) produces a
graph where the error bar in the legend is vertical.
How can I make the legend error bar horizontal?
code:
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 2*np.pi, 6)
y = np.sin(x)
dy = 0.1*np.abs(y)
plt.errorbar(x, y, yerr = dy, label="data", fmt='o')
plt.legend(loc="upperright", numpoints=1, frameon=False)
plt.show()
In the produced graph, I want the error bar inside the legend to be horizontal, while the error bars in the rest of the graph remain vertical. I want this so that the error bar in the legend is not confused for a data point. How can I accomplish this?
You can retrieve the error bar line object from the default legend and then create a custom legend with it and it will automatically be drawn horizontally, like this:
import numpy as np # v 1.19.2
import matplotlib.pyplot as plt # v 3.3.2
x = np.linspace(0, 2*np.pi, 6)
y = np.sin(x)
dy = 0.1*np.abs(y)
fig, ax = plt.subplots()
plt.errorbar(x, y, yerr=dy, label='data', fmt='o', ecolor='red')
# Retrieve handles and labels: note the tuple within the tuple to
# unpack the handles
(errorbar_container,), labels = ax.get_legend_handles_labels()
point, line = errorbar_container.get_children()
# Create the custom legend: note that the handles are drawn on top of
# one another in the order that they are listed in the tuple
plt.legend([(line, point)], labels, frameon=False)
plt.show()

matplotlib: adding second axes() with transparent background?

Define data
x = np.linspace(0,2*np.pi,100)
y = 2*np.sin(x)
Plot
fig = plt.figure()
ax = plt.axes()
fig.add_subplot(ax)
ax.plot(x,y)
Add second axis
newax = plt.axes(axisbg='none')
Gives me ValueError: Unknown element o, even though it does the same thing as what I am about to describe. I can also see that this works (no error) to do the same thing:
newax = plt.axes()
fig.add_subplot(newax)
newax.set_axis_bgcolor('none')
However, it turns the background color of the original figure "gray" (or whatever the figure background is)? I don't understand, as I thought this would make newax transparent except for the axes and box around the figure. Even if I switch the order, same thing:
plt.close('all')
fig = plt.figure()
newax = plt.axes()
fig.add_subplot(newax)
newax.set_axis_bgcolor('none')
ax = plt.axes()
fig.add_subplot(ax)
ax.plot(x,y)
This is surprising because I thought the background of one would be overlaid on the other, but in either case it is the newax background that appears to be visible (or at least this is the color I see).
What is going on here?
You're not actually adding a new axes.
Matplotlib is detecting that there's already a plot in that position and returning it instead of a new axes object.
(Check it for yourself. ax and newax will be the same object.)
There's probably not a reason why you'd want to, but here's how you'd do it.
(Also, don't call newax = plt.axes() and then call fig.add_subplot(newax) You're doing the same thing twice.)
Edit: With newer (>=1.2, I think?) versions of matplotlib, you can accomplish the same thing as the example below by using the label kwarg to fig.add_subplot. E.g. newax = fig.add_subplot(111, label='some unique string')
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
# If you just call `plt.axes()` or equivalently `fig.add_subplot()` matplotlib
# will just return `ax` again. It _won't_ create a new axis unless we
# call fig.add_axes() or reset fig._seen
newax = fig.add_axes(ax.get_position(), frameon=False)
ax.plot(range(10), 'r-')
newax.plot(range(50), 'g-')
newax.axis('equal')
plt.show()
Of course, this looks awful, but it's what you're asking for...
I'm guessing from your earlier questions that you just want to add a second x-axis? If so, this is a completely different thing.
If you want the y-axes linked, then do something like this (somewhat verbose...):
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
newax = ax.twiny()
# Make some room at the bottom
fig.subplots_adjust(bottom=0.20)
# I'm guessing you want them both on the bottom...
newax.set_frame_on(True)
newax.patch.set_visible(False)
newax.xaxis.set_ticks_position('bottom')
newax.xaxis.set_label_position('bottom')
newax.spines['bottom'].set_position(('outward', 40))
ax.plot(range(10), 'r-')
newax.plot(range(21), 'g-')
ax.set_xlabel('Red Thing')
newax.set_xlabel('Green Thing')
plt.show()
If you want to have a hidden, unlinked y-axis, and an entirely new x-axis, then you'd do something like this:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2)
newax = fig.add_axes(ax.get_position())
newax.patch.set_visible(False)
newax.yaxis.set_visible(False)
for spinename, spine in newax.spines.iteritems():
if spinename != 'bottom':
spine.set_visible(False)
newax.spines['bottom'].set_position(('outward', 25))
ax.plot(range(10), 'r-')
x = np.linspace(0, 6*np.pi)
newax.plot(x, 0.001 * np.cos(x), 'g-')
plt.show()
Note that the y-axis values for anything plotted on newax are never shown.
If you wanted, you could even take this one step further, and have independent x and y axes (I'm not quite sure what the point of it would be, but it looks neat...):
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
fig.subplots_adjust(bottom=0.2, right=0.85)
newax = fig.add_axes(ax.get_position())
newax.patch.set_visible(False)
newax.yaxis.set_label_position('right')
newax.yaxis.set_ticks_position('right')
newax.spines['bottom'].set_position(('outward', 35))
ax.plot(range(10), 'r-')
ax.set_xlabel('Red X-axis', color='red')
ax.set_ylabel('Red Y-axis', color='red')
x = np.linspace(0, 6*np.pi)
newax.plot(x, 0.001 * np.cos(x), 'g-')
newax.set_xlabel('Green X-axis', color='green')
newax.set_ylabel('Green Y-axis', color='green')
plt.show()
You can also just add an extra spine at the bottom of the plot. Sometimes this is easier, especially if you don't want ticks or numerical things along it. Not to plug one of my own answers too much, but there's an example of that here: How do I plot multiple X or Y axes in matplotlib?
As one last thing, be sure to look at the parasite axes examples if you want to have the different x and y axes linked through a specific transformation.

Remove colorbar from figure

This should be easy but I'm having a hard time with it. Basically, I have a subplot in matplotlib that I'm drawing a hexbin plot in every time a function is called, but every time I call the function I get a new colorbar, so what I'd really like to do is update the colorbar. Unfortunately, this doesn't seem to work since the object the colorbar is attached to is being recreated by subplot.hexbin.
def foo(self):
self.subplot.clear()
hb = self.subplot.hexbin(...)
if self.cb:
self.cb.update_bruteforce() # Doesn't work (hb is new)
else:
self.cb = self.figure.colorbar(hb)
I'm now in this annoying place where I'm trying to delete the colorbar axes altogether and simply recreate it. Unfortunately, when I delete the colorbar axes, the subplot axes don't reclaim the space, and calling self.subplot.reset_position() isn't doing what I thought it would.
def foo(self):
self.subplot.clear()
hb = self.subplot.hexbin(...)
if self.cb:
self.figure.delaxes(self.figure.axes[1])
del self.cb
# TODO: resize self.subplot so it fills the
# whole figure before adding the new colorbar
self.cb = self.figure.colorbar(hb)
I think the problem is that with del you cancel the variable, but not the referenced object colorbar.
If you want the colorbar to be removed from plot and disappear, you have to use the method remove of the colorbar instance and to do this you need to have the colorbar in a variable, for which you have two options:
holding the colorbar in a value at the moment of creation, as shown in other answers e.g. cb=plt.colorbar()
retrieve an existing colorbar, that you can do following (and upvoting :)) what I wrote here: How to retrieve colorbar instance from figure in matplotlib
then:
cb.remove() plt.draw() #update plot
Full code and result:
from matplotlib import pyplot as plt
import numpy as np
plt.ion()
plt.imshow(np.random.random(15).reshape((5,3)))
cb = plt.colorbar()
plt.savefig('test01.png')
cb.remove()
plt.savefig('test02.png')
Alright, here's my solution. Not terribly elegant, but not a terrible hack either.
def foo(self):
self.subplot.clear()
hb = self.subplot.hexbin(...)
if self.cb:
self.figure.delaxes(self.figure.axes[1])
self.figure.subplots_adjust(right=0.90) #default right padding
self.cb = self.figure.colorbar(hb)
This works for my needs since I only ever have a single subplot. People who run into the same problem when using multiple subplots or when drawing the colorbar in a different position will need to tweak.
I managed to solve the same issue using fig.clear() and display.clear_output()
import matplotlib.pyplot as plt
import IPython.display as display
import matplotlib.tri as tri
from pylab import *
%matplotlib inline
def plot_res(fig):
ax=fig.add_axes([0,0,1,1])
ax.set_xlabel("x")
ax.set_ylabel('y')
plotted=ax.imshow(rand(250, 250))
ax.set_title("title")
cbar=fig.colorbar(mappable=plotted)
display.clear_output(wait=True)
display.display(plt.gcf())
fig.clear()
fig=plt.figure()
N=20
for j in range(N):
plot_res(fig)
If you have a matplotlib figure object you just need to do fig.delaxes(fig.axes[1])
For example:
Plot with colorbar
import matplotlib.pyplot as plt
# setup some generic data
N = 37
x, y = np.mgrid[:N, :N]
Z = (np.cos(x*0.2) + np.sin(y*0.3))
# mask out the negative and positive values, respectively
Zpos = np.ma.masked_less(Z, 0)
Zneg = np.ma.masked_greater(Z, 0)
fig, ax1 = plt.subplots(figsize=(13, 3), ncols=1)
# plot just the positive data and save the
# color "mappable" object returned by ax1.imshow
pos = ax1.imshow(Zpos, cmap='Blues', interpolation='none')
# add the colorbar using the figure's method,
# telling which mappable we're talking about and
# which axes object it should be near
fig.colorbar(pos, ax=ax1)
Remove colorbar
import matplotlib.pyplot as plt
# setup some generic data
N = 37
x, y = np.mgrid[:N, :N]
Z = (np.cos(x*0.2) + np.sin(y*0.3))
# mask out the negative and positive values, respectively
Zpos = np.ma.masked_less(Z, 0)
Zneg = np.ma.masked_greater(Z, 0)
fig, ax1 = plt.subplots(figsize=(13, 3), ncols=1)
# plot just the positive data and save the
# color "mappable" object returned by ax1.imshow
pos = ax1.imshow(Zpos, cmap='Blues', interpolation='none')
# add the colorbar using the figure's method,
# telling which mappable we're talking about and
# which axes object it should be near
fig.colorbar(pos, ax=ax1)
fig.delaxes(fig.axes[1])
I had a similar problem and played around a little bit. I came up with two solutions which might be slightly more elegant:
Clear the whole figure and add the subplot (+colorbar if wanted) again.
If there's always a colorbar, you can simply update the axes with autoscale which also updates the colorbar.
I've tried this with imshow, but I guess it works similar for other plotting methods.
from pylab import *
close('all') #close all figures in memory
#1. Figures for fig.clf method
fig1 = figure()
fig2 = figure()
cbar1=None
cbar2=None
data = rand(250, 250)
def makefig(fig,cbar):
fig.clf()
ax = fig.add_subplot(111)
im = ax.imshow(data)
if cbar:
cbar=None
else:
cbar = fig.colorbar(im)
return cbar
#2. Update method
fig_update = figure()
cbar3=None
data_update = rand(250, 250)
img=None
def makefig_update(fig,im,cbar,data):
if im:
data*=2 #change data, so there is change in output (look at colorbar)
#im.set_data(data) #use this if you use new array
im.autoscale()
#cbar.update_normal(im) #cbar is updated automatically
else:
ax = fig.add_subplot(111)
im = ax.imshow(data)
cbar=fig.colorbar(im)
return im,cbar,data
#Execute functions a few times
for i in range(3):
print i
cbar1=makefig(fig1,cbar1)
cbar2=makefig(fig2,cbar2)
img,cbar3,data_update=makefig_update(fig_update,img,cbar3,data_update)
cbar2=makefig(fig2,cbar2)
fig1.show()
fig2.show()
fig_update.show()
I needed to remove colorbars because I was plotting a pcolormesh and adding colorbar to a figure in a loop. Each loop would create a new colorbar and after ten loops I would have ten colorbars. That was bad.
To remove colorbars, I name the pcolormesh and colorbar a variable, then at the end of my loop I remove each. It is important to remove the colorbar before removing the pcolormesh.
Psudo Code:
for i in range(0,10):
p = plt.pcolormesh(datastuff[i])
cb = plt.colorbar(p)
plt.savefig('name_'+i)
cb.remove()
p.remove()
Again, it was necessary to remove the colorbar before the pcolormesh
I am using matplotlib 1.4.0. This is how I solve this problem:
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt
# A contour plot example:
delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
Z = 10.0 * (Z2 - Z1)
#
# first drawing
fig = plt.figure()
ax = fig.add_subplot(111) # drawing axes
c = ax.contourf(Z) # contour fill c
cb = fig.colorbar(c) # colorbar for contour c
# clear first drawimg
ax.clear() # clear drawing axes
cb.ax.clear() # clear colorbar axes
# replace with new drawing
# 1. drawing new contour at drawing axes
c_new = ax.contour(Z)
# 2. create new colorbar for new contour at colorbar axes
cb_new = ax.get_figure().colorbar(c_new, cax=cb.ax)
plt.show()
Above code draws a contour fill plot with colorbar, clear it and draw a new contour plot with new colorbar at the same figure.
By using
cb.ax
i am able to identify the colorbar axes and clear the old colorbar.
And specifying cax=cb.ax simply draws the new colorbar in the old colorbar axes.
Don't want to take anything away from the author of this blog post (Joseph Long) but this is clearly the best solution I've found so far. It includes pieces of code, great explanations and many examples.
To summarize, from any output of an axis ax of the command: plot, image, scatter, collection, etc. such as:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(5,5), dpi=300)
ax = fig.add_subplot(1, 1, 1)
data = ax.plot(x,y)
# or
data = ax.scatter(x, y, z)
# or
data = ax.imshow(z)
# or
data = matplotlib.collection(patches)
ax.add_collection(data)
You create a color bar axis using the make_axes_locatable and the original axis of the plot.
from mpl_toolkits.axes_grid1 import make_axes_locatable
# the magical part
divider = make_axes_locatable(ax)
caxis = divider.append_axes("right", size="5%", pad=0.05)
fig.colorbar(data, cax=caxis)
plt.show()
The created colorbar will have the same size as the figure or subplot and you can modify it's width, location, padding when using the divider.append_axes command.
My solution consists in having an Axes whose only purpose is to hold the colorbar, and clear it entirely when needed.
For example, define those once:
figure, ax = plt.subplots() # All the plotting is done on `ax`.
cax = ax.inset_axes([1.03, 0, 0.1, 1], transform=ax.transAxes) # Colorbar is held by `cax`.
Then do this as many times as needed:
cax.clear()
colorbar = figure.colorbar(mpl.cm.ScalarMappable(norm=norm, cmap=cmap),
ax=ax,
cax=cax,
**kwargs)
"on_mappable_changed" worked in my case. However, according to docs, the method "Typically ... should not be called manually."
if self.cb:
self.cb.on_mappable_changed(hb)
else:
self.cb = self.fig.colorbar(hb)

Categories

Resources