I am using the matplotlib pie chart: https://matplotlib.org/api/_as_gen/matplotlib.axes.Axes.pie.html.
I am generating a network diagram that uses these piecharts. I am drawing a line down the middle of the pie chart to delineate two different processes. My problem is that when I draw this line down the middle it will overlay ontop of all piecharts, so if they overlap, the lines will not be layered correctly:
I realize that there is no zorder for the matplotlib pie chart, but is there a way to get it to emulate a zorder? That way I can use the zorder for the line, and then layer a pie chart on top of that line to overlap it.
pie() returns a list of patches. These individual patches have a zorder property, so you could loop over them and adjust their zorder
fig,ax = plt.subplots()
ax.set_aspect('equal')
p1,t1 = plt.pie([50,50], center=(0,0))
p2,t2 = plt.pie([1,1,1,1], center=(1.2,0)) # this pie-chart is over the first one
[p.set_zorder(-1) for p in p2] # change the z-order of the patches so that the
# 2nd pie-chart ends up below the first one
Related
Does anybody whether its possible to get multiple horizontal bar plots in one plot. Say I have two horizontal bars plots (attached) which both use the same y-axis data. But their x-value data differs. Can I get these two plots in one plot?
I have attached my bar plots and code that i use to plot
Plot the first hbar
plt.barh(index,b1_plt,color = 'K')
plt.barh(index,b2_plt,color = 'K')
plt.xlabel('Width')
plt.ylabel('layer nr')
plt.title('Section outline')
Plot the second hbar
plt.barh(index,micro_xmi_all)
plt.xlabel('Micro strain')
plt.ylabel('layer nr')
plt.title('Strain diagram')
The list micro_xmi_all have different range than b1_plt or b2_plt
Using matplotlib in python. The legend overlaps with my pie chart. Tried various options for "loc" such as "best" ,1,2,3... but to no avail. Any Suggestions as to how to either exactly mention the legend position (such as giving padding from the pie chart boundaries) or at least make sure that it does not overlap?
The short answer is: You may use plt.legend's arguments loc, bbox_to_anchor and additionally bbox_transform and mode, to position the legend in an axes or figure.
The long version:
Step 1: Making sure a legend is needed.
In many cases no legend is needed at all and the information can be inferred by the context or the color directly:
If indeed the plot cannot live without a legend, proceed to step 2.
Step 2: Making sure, a pie chart is needed.
In many cases pie charts are not the best way to convey information.
If the need for a pie chart is unambiguously determined, let's proceed to place the legend.
Placing the legend
plt.legend() has two main arguments to determine the position of the legend. The most important and in itself sufficient is the loc argument.
E.g. plt.legend(loc="upper left") placed the legend such that it sits in the upper left corner of its bounding box. If no further argument is specified, this bounding box will be the entire axes.
However, we may specify our own bounding box using the bbox_to_anchor argument. If bbox_to_anchor is given a 2-tuple e.g. bbox_to_anchor=(1,1) it means that the bounding box is located at the upper right corner of the axes and has no extent. It then acts as a point relative to which the legend will be placed according to the loc argument. It will then expand out of the zero-size bounding box. E.g. if loc is "upper left", the upper left corner of the legend is at position (1,1) and the legend will expand to the right and downwards.
This concept is used for the above plot, which tells us the shocking truth about the bias in Miss Universe elections.
import matplotlib.pyplot as plt
import matplotlib.patches
total = [100]
labels = ["Earth", "Mercury", "Venus", "Mars", "Jupiter", "Saturn",
"Uranus", "Neptune", "Pluto *"]
plt.title('Origin of Miss Universe since 1952')
plt.gca().axis("equal")
pie = plt.pie(total, startangle=90, colors=[plt.cm.Set3(0)],
wedgeprops = { 'linewidth': 2, "edgecolor" :"k" })
handles = []
for i, l in enumerate(labels):
handles.append(matplotlib.patches.Patch(color=plt.cm.Set3((i)/8.), label=l))
plt.legend(handles,labels, bbox_to_anchor=(0.85,1.025), loc="upper left")
plt.gcf().text(0.93,0.04,"* out of competition since 2006", ha="right")
plt.subplots_adjust(left=0.1, bottom=0.1, right=0.75)
In order for the legend not to exceed the figure, we use plt.subplots_adjust to obtain more space between the figure edge and the axis, which can then be taken up by the legend.
There is also the option to use a 4-tuple to bbox_to_anchor. How to use or interprete this is detailed in this question: What does a 4-element tuple argument for 'bbox_to_anchor' mean in matplotlib?
and one may then use the mode="expand" argument to make the legend fit into the specified bounding box.
There are some useful alternatives to this approach:
Using figure coordinates
Instead of specifying the legend position in axes coordinates, one may use figure coordinates. The advantage is that this will allow to simply place the legend in one corner of the figure without adjusting much of the rest. To this end, one would use the bbox_transform argument and supply the figure transformation to it. The coordinates given to bbox_to_anchor are then interpreted as figure coordinates.
plt.legend(pie[0],labels, bbox_to_anchor=(1,0), loc="lower right",
bbox_transform=plt.gcf().transFigure)
Here (1,0) is the lower right corner of the figure. Because of the default spacings between axes and figure edge, this suffices to place the legend such that it does not overlap with the pie.
In other cases, one might still need to adapt those spacings such that no overlap is seen, e.g.
title = plt.title('What slows down my computer')
title.set_ha("left")
plt.gca().axis("equal")
pie = plt.pie(total, startangle=0)
labels=["Trojans", "Viruses", "Too many open tabs", "The anti-virus software"]
plt.legend(pie[0],labels, bbox_to_anchor=(1,0.5), loc="center right", fontsize=10,
bbox_transform=plt.gcf().transFigure)
plt.subplots_adjust(left=0.0, bottom=0.1, right=0.45)
Saving the file with bbox_inches="tight"
Now there may be cases where we are more interested in the saved figure than at what is shown on the screen. We may then simply position the legend at the edge of the figure, like so
but then save it using the bbox_inches="tight" to savefig,
plt.savefig("output.png", bbox_inches="tight")
This will create a larger figure, which sits tight around the contents of the canvas:
A sophisticated approach, which allows to place the legend tightly inside the figure, without changing the figure size is presented here:
Creating figure with exact size and no padding (and legend outside the axes)
Using Subplots
An alternative is to use subplots to reserve space for the legend. In this case one subplot could take the pie chart, another subplot would contain the legend. This is shown below.
fig = plt.figure(4, figsize=(3,3))
ax = fig.add_subplot(211)
total = [4,3,2,81]
labels = ["tough working conditions", "high risk of accident",
"harsh weather", "it's not allowed to watch DVDs"]
ax.set_title('What people know about oil rigs')
ax.axis("equal")
pie = ax.pie(total, startangle=0)
ax2 = fig.add_subplot(212)
ax2.axis("off")
ax2.legend(pie[0],labels, loc="center")
One on the nuances of legend stylings is that with the histogram legend call, such as:
axHistogram.legend(loc='upper left', prop={'family':'serif', 'size':'x-small'}, frameon=False)
We have boxes rather than lines.
Also, with the scatter legend styling, we have three dots offset:
axHistogram.legend(loc='upper left', prop={'family':'serif', 'size':'x-small'}, frameon=False)
Here is a image of what I mean:
How do I style these so that I have lines rather than boxes and just one dot rather than three offset?
You can use proxy artists to create the legend entries you want, for example remove the label= keyword from your call to hist, and do something like,
axHistogram.plot(np.NaN, np.NaN, label='AGN', color='b', linewidth=1)
Then when you create your legend the entry will be a line rather than a box.
You can use the numpoints and scatterpoints input option to legend to control the number of points that are in the legend.
I use autofmt_xdate to plot long x-axis labels in a readable way. The problem is, when I want to combine different subplots, the x-axis labeling of the other subplots disappears, which I do not appreciate for the leftmost subplot in the figure below (two rows high). Is there a way to prevent autofmt_xdate from quenching the other x-axis labels? Or is there another way to rotate the labels? As you can see I experimented with xticks and "rotate" as well, but the results were not satisfying because the labels were rotated around their center, which resulted in messy labeling.
Script that produces plot below:
from matplotlib import pyplot as plt
from numpy import arange
import numpy
from matplotlib import rc
rc("figure",figsize=(15,10))
#rc('figure.subplot',bottom=0.1,hspace=0.1)
rc("legend",fontsize=16)
fig = plt.figure()
Test_Data = numpy.random.normal(size=20)
fig = plt.figure()
Dimension = (2,3)
plt.subplot2grid(Dimension, (0,0),rowspan=2)
plt.plot(Test_Data)
plt.subplot2grid(Dimension, (0,1),colspan=2)
for i,j in zip(Test_Data,arange(len(Test_Data))):
plt.bar(i,j)
plt.legend(arange(len(Test_Data)))
plt.subplot2grid(Dimension, (1,1),colspan=2)
xticks = [r"%s (%i)" % (a,b) for a,b in zip(Test_Data,Test_Data)]
plt.xticks(arange(len(Test_Data)),xticks)
fig.autofmt_xdate()
plt.ylabel(r'$Some Latex Formula/Divided by some Latex Formula$',fontsize=14)
plt.plot(Test_Data)
#plt.setp(plt.xticks()[1],rotation=30)
plt.tight_layout()
#plt.show()
This is actually a feature of the autofmt_xdate method. From the documentation of the autofmt_xdate method:
Date ticklabels often overlap, so it is useful to rotate them and right align them. Also, a common use case is a number of subplots with shared xaxes where the x-axis is date data. The ticklabels are often long, and it helps to rotate them on the bottom subplot and turn them off on other subplots, as well as turn off xlabels.
If you want to rotate the xticklabels of the bottom right subplot only, use
plt.setp(plt.xticks()[1], rotation=30, ha='right') # ha is the same as horizontalalignment
This rotates the ticklabels 30 degrees and right aligns them (same result as when using autofmt_xdate) for the bottom right subplot, leaving the two other subplots unchanged.
I am plotting a 2D view of a spacecraft orbit using matplotlib. On this orbit, I identify and mark certain events, and then list these events and the corresponding dates in a legend. Before saving the figure to a file, I autozoom on my orbit plot, which causes the legend to be printed directly on top of my plot. What I would like to do is, after autoscaling, somehow find out the width of my legend, and then expand my xaxis to "make room" for the legend on the right side of the plot. Conceptually, something like this;
# ... code that generates my plot up here, then:
ax.autoscale_view()
leg = ax.get_legend()
leg_width = # Somehow get the width of legend in units that I can use to modify my axes
xlims = ax.get_xlim()
ax.set_xlim( [xlims[0], xlims[1] + leg_width] )
fig.savefig('myplot.ps',format='ps')
The main problem I'm having is that ax.set_xlim() takes "data" specific values, whereas leg.get_window_extent reports in window pixels (I think), and even that only after the canvas has been drawn, so I'm not sure how I can get the legend "width" in a way that I can use similar to above.
You can save the figure once to get the real legend location, and then use transData.inverted() to transform screen coordinate to data coordinate.
import pylab as pl
ax = pl.subplot(111)
pl.plot(pl.randn(1000), pl.randn(1000), label="ok")
leg = pl.legend()
pl.savefig("test.png") # save once to get the legend location
x,y,w,h = leg.get_window_extent().bounds
# transform from screen coordinate to screen coordinate
tmp1, tmp2 = ax.transData.inverted().transform([0, w])
print abs(tmp1-tmp2) # this is the with of legend in data coordinate
pl.savefig("test.png")