How to update barchart in matplotlib? - 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

plt.gcf() doesn't show previous instance of a plot

I want to save an instance of a plot into an object so that I can display it later by just calling that object.
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
fig1 = plt.gcf()
plt.plot(x, y2, linewidth=1, color = 'red')
fig2 = plt.gcf()
In this example, I first draw a blue line (y1=x) and use plt.gcf() to save an instance of this plot in fig1. Then I add a red curve (y2=sin(x)) to the plot and use plt.gcf() again to save this plot in fig2. Now, I expect that when I call fig1 I only get the blue line, and when I call fig2 I get both lines. Like this (I'm in Jupyter):
fig1 # or fig1.show() if not in Jupyter
Only blue curve
fig2
Both curves
But, in reality, when I call fig1 and fig2, both of them show both curves (like the second picture). Can someone please help how I can correctly get an instance of each plot so that I can display each of them later whenever I want?
You need to force matplotlib to actually draw the figure by setting a plot.show() in your code:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
plt.show()
fig1 = plt.gcf()
plt.plot(x, y2, linewidth=1, color = 'red')
plt.show()
fig2 = plt.gcf()
Using the function plt.plot() always plots to the current axis (if no axis is present, a new one is created).
You can also tell matplotlib explicitly to open a new figure:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
# open first figure
fig1 = plt.figure()
# plot
plt.plot(x, y1, linewidth=1, color = 'deepskyblue')
# open second figure
fig2 = plt.figure()
#plot
plt.plot(x, y2, linewidth=1, color = 'red')
Although this already fixes your problem, it is considered good practice to use an object-oriented version of this like this:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 10, 0.1)
y1 = x
y2 = np.sin(x)
# open first figure, axis
fig1, ax1 = plt.subplots()
# plot
ax1.plot(x, y1, linewidth=1, color = 'deepskyblue')
# open second figure, axis
fig2, ax2 = plt.subplots()
#plot
ax2.plot(x, y2, linewidth=1, color = 'red')
In all cases, you will get:
Now, why don't you need plt.show() in the other approaches? Well, matplotlib by explicitly opening a new figure/axis, it is obvious that the previous axis is finished and can be drawn.
The last approach is the clearest as you tell exactly which figure and which axis you are considering.

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

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.

Categories

Resources