I have a matplotlib.PatchCollection that I want to add to a plot. But I also have Text and other Patches I am adding straight onto the plot. So it looks like this:
node.shape = RegularPolygon((node.posX, node.posY),
6,
radius = node.radius,
edgecolor = 'none',
facecolor = node.fillColor,
zorder = node.zorder)
self.patches.append(node.shape)
self.p = PatchCollection(self.patches, edgecolor = 'none', match_original=True )
self.plotAxes.add_collection(self.p)
#Two previously instantiated patches (they are visible)
self.plotAxes.add_artist(selectionRect)
self.plotAxes.add_artist(brushShape)
self.plotCanvas.draw()
I want my Patches in the collection to be drawn first and then the selctionRect and brushShape to be drawn afterwards because they can overlap the Patches in the collection. If they do, they should be visible. However, my plot always shows the Patches in the collection as if they've been drawn last. How can I get around this? Any help is appreciated.
Edit: One thing that appears to work is to keep 2 PatchCollections. However, when I do this, it seems that I can never set the visibilities to false. Does the PatchCollection set the reset the values or something?
As Adam said in the comments you want to set zorder, which sets what order things layer when they are drawn on top of each other, things with higher zorder will overlap things with lower zorder.
Everything has a default zorder, but you can over-ride that value by adding the zorder kwarg to the function call. It is an Artist kwarg, so basically ever plotting function should respect it (and if you find one that does not submit a bug report to the github site)
ex
plt.figure()
ax = plt.gca()
ax.plot(range(5), zorder=2, lw=10)
ax.plot(range(5)[::-1], zorder=1, lw=10)
vs
plt.figure()
ax = plt.gca()
ax.plot(range(5), zorder=1, lw=10)
ax.plot(range(5)[::-1], zorder=2, lw=10)
zorder demo
Related
In a previous answer it was recommended to me to use add_subplot instead of add_axes to show axes correctly, but searching the documentation I couldn't understand when and why I should use either one of these functions.
Can anyone explain the differences?
Common grounds
Both, add_axes and add_subplot add an axes to a figure. They both return a (subclass of a) matplotlib.axes.Axes object.
However, the mechanism which is used to add the axes differs substantially.
add_axes
The calling signature of add_axes is add_axes(rect), where rect is a list [x0, y0, width, height] denoting the lower left point of the new axes in figure coodinates (x0,y0) and its width and height. So the axes is positionned in absolute coordinates on the canvas. E.g.
fig = plt.figure()
ax = fig.add_axes([0,0,1,1])
places a figure in the canvas that is exactly as large as the canvas itself.
add_subplot
The calling signature of add_subplot does not directly provide the option to place the axes at a predefined position. It rather allows to specify where the axes should be situated according to a subplot grid. The usual and easiest way to specify this position is the 3 integer notation,
fig = plt.figure()
ax = fig.add_subplot(231)
In this example a new axes is created at the first position (1) on a grid of 2 rows and 3 columns. To produce only a single axes, add_subplot(111) would be used (First plot on a 1 by 1 subplot grid). (In newer matplotlib versions, add_subplot() without any arguments is possible as well.)
The advantage of this method is that matplotlib takes care of the exact positioning. By default add_subplot(111) would produce an axes positioned at [0.125,0.11,0.775,0.77] or similar, which already leaves enough space around the axes for the title and the (tick)labels. However, this position may also change depending on other elements in the plot, titles set, etc.
It can also be adjusted using pyplot.subplots_adjust(...) or pyplot.tight_layout().
In most cases, add_subplot would be the prefered method to create axes for plots on a canvas. Only in cases where exact positioning matters, add_axes might be useful.
Example
import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = (5,3)
fig = plt.figure()
fig.add_subplot(241)
fig.add_subplot(242)
ax = fig.add_subplot(223)
ax.set_title("subplots")
fig.add_axes([0.77,.3,.2,.6])
ax2 =fig.add_axes([0.67,.5,.2,.3])
fig.add_axes([0.6,.1,.35,.3])
ax2.set_title("random axes")
plt.tight_layout()
plt.show()
Alternative
The easiest way to obtain one or more subplots together with their handles is plt.subplots(). For one axes, use
fig, ax = plt.subplots()
or, if more subplots are needed,
fig, axes = plt.subplots(nrows=3, ncols=4)
The initial question
In the initial question an axes was placed using fig.add_axes([0,0,1,1]), such that it sits tight to the figure boundaries. The disadvantage of this is of course that ticks, ticklabels, axes labels and titles are cut off. Therefore I suggested in one of the comments to the answer to use fig.add_subplot as this will automatically allow for enough space for those elements, and, if this is not enough, can be adjusted using pyplot.subplots_adjust(...) or pyplot.tight_layout().
The answer by #ImportanceOfBeingErnest is great.
Yet in that context usually one want to generate an axes for a plot and add_axes() has too much overhead.
So one trick is, as in the answer of #ImportanceOfBeingErnest, is to use add_subplot(111).
Yet more elegant alternative and simple would be:
hAx = plt.figure(figsize = (10, 10)).gca()
If you want 3D projection you can pass any axes property. For instance the projection:
hAx = plt.figure(figsize = (16, 10)).gca(projection = '3d')
I was trying to plot some data and found constrained layout very helpful in maintaining margins and spaces between subplots. However, when I add a colorbar it reduces the width of all subplots and creates extra white space in the subplots above. This bcomes a problem when such plots are put up in reports where a lot of space goes waste because of extra space taken by colorbar.
I was wondering how I can avoid this and make only the image plots to resize when the colorbar is added without afecting the subplots above and no extra white space is created. An example code of the problem I am facing is:
fig, ax = plt.subplots(4,2, constrained_layout=True)
ax[0,0].plot(range(10))
ax[0,1].plot(range(10))
ax[1,0].plot(range(10))
ax[1,1].plot(range(10))
ax[2,0].pcolor(np.random.rand(2,2))
ax[2,1].pcolor(np.random.rand(2,2))
ax[3,0].pcolor(np.random.rand(2,2))
im = ax[3,1].pcolor(np.random.rand(2,2))
bar = fig.colorbar(im,ax=[[ax[2,0],ax[2,1]],[ax[3,0],ax[3,1]]])
It'll be better if I can get this done with contrantrained_layout=True.
I don't have much experience with adjusting the color bar, but what about the idea of adding a new axis and placing the color bar in the center? I set the placement values manually. I wanted to put the color bar in the bottom two graphs to make the widths the same, but I couldn't do that.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(4,2, constrained_layout=True)
ax[0,0].plot(range(10))
ax[0,1].plot(range(10))
ax[1,0].plot(range(10))
ax[1,1].plot(range(10))
ax[2,0].pcolor(np.random.rand(2,2))
ax[2,1].pcolor(np.random.rand(2,2))
ax[3,0].pcolor(np.random.rand(2,2))
im = ax[3,1].pcolor(np.random.rand(2,2))
cax = fig.add_axes([0.48, 0.11, 0.05, 0.36])
bar = fig.colorbar(im,cax=cax,ax=[[ax[2,0],ax[2,1]],[ax[3,0],ax[3,1]]])
fig.subplots_adjust(wspace=0.7, hspace=0.5)
plt.show()
This question is similar to this one asked 6 years ago. However, their solution is not quite solving my problem; unlike their question, I am dealing with multiple legends, some of which are getting cut off.
I'm trying to create a figure containing multiple legends that sit outside the axes of my graph. I'm following the matplotlib documentation's instructions for creating multiple legends, using add_artist to add all but the final legend to my axes. I then use the bbox_extra_artists parameter in my savefig call as described in the above question to include all my legend objects. As seen in this example output image, the wider legend still gets cut off on the right side.
The code used to generate this plot:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = plt.gca()
for x in [0, 1, 2]:
ax.bar(x+0.5, x+1, width=1, color=['red', 'blue', 'green'][x])
handle1 = plt.Line2D((0,1), (0,0), color='purple')
lgd1 = ax.legend([handle1], ['label1 is very long'], bbox_to_anchor=(1, 1))
ax.add_artist(lgd1)
handle2 = plt.Line2D((0,1), (0,0), color='orange')
lgd2 = ax.legend([handle2], ['label2'], bbox_to_anchor=(1, 0.9))
plt.savefig('output.png', bbox_extra_artists=(lgd1,lgd2), bbox_inches='tight')
Notably, if I change the order in which the legends are added (adding the wider legend first), the problem goes away and both legends are visible as seen here:
handle2 = plt.Line2D((0,1), (0,0), color='orange')
lgd2 = ax.legend([handle2], ['label2'], bbox_to_anchor=(1, 0.9))
ax.add_artist(lgd2)
handle1 = plt.Line2D((0,1), (0,0), color='purple')
lgd1 = ax.legend([handle1], ['label1 is very long'], bbox_to_anchor=(1, 1))
plt.savefig('output.png', bbox_extra_artists=(lgd1,lgd2), bbox_inches='tight')
For my actual project (which has to handle a dynamic number of legends), I've made it figure out which legend is "longest" and always add that legend last to work around this problem. However, this feels messy and it doesn't allow for adding more legends on other sides of the figure (e.g., I cannot add an x-axis legend below the graph without it getting cut off, since only one legend can be added "last").
Is this an intractable bug in matplotlib, or is there a tidy solution I'm missing?
How can I prevent the labels of xticks from overlapping with the labels of yticks when using hist (or other plotting commands) in matplotlib?
There are several ways.
One is to use the tight_layout method of the figure you are drawing, which will automatically try to optimize the appareance of the labels.
fig, ax = subplots(1)
ax.plot(arange(10),rand(10))
fig.tight_layout()
An other way is to modify the rcParams values for the ticks formatting:
rcParams['xtick.major.pad'] = 6
This will draw the ticks a little farter from the axes. after modifying the rcparams (this of any other, you can find the complete list on your matplotlibrc configuration file), remember to set it back to deafult with the rcdefaults function.
A third way is to tamper with the axes locator_params telling it to not draw the label in the corner:
fig, ax = subplots(1)
ax.plot(arange(10),rand(10))
ax.locator_params(prune='lower',axis='both')
the axis keywords tell the locator on which axis it should work and the prune keyword tell it to remove the lowest value of the tick
Try increasing the padding between the ticks on the labels
import matplotlib
matplotlib.rcParams['xtick.major.pad'] = 8 # defaults are 4
matplotlib.rcParams['ytick.major.pad'] = 8
same goes for [x|y]tick.minor.pad.
Also, try setting: [x|y]tick.direction to 'out'. That gives you a little more room and helps makes the ticks a little more visible -- especially on histograms with dark bars.
I need to know the size of the legend in pixels. I seem to only be able to get height = 1. from any function... I've tried the following
this returns 1.
height = legend.get_frame().get_bbox_to_anchor().height
this returns [0,0],[1.,1.]
box = legend.get_window_extent().get_points()
this also returns [0,0],[1.,1.]
box = legend.get_frame().get_bbox().get_points()
all of these return 1, even if the size of the legend changes! what's going on?
This is because you haven't yet drawn the canvas.
Pixel values simply don't exist in matplotlib (or rather, they exist, have no relation to the screen or other output) until the canvas is drawn.
There are a number of reasons for this, but I'll skip them at the moment. Suffice it to say that matplotlib tries to stay as general as possible, and generally avoids working with pixel values until things are drawn.
As a simple example:
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(range(10), label='Test')
legend = ax.legend(loc='upper left')
print 'Height of legend before canvas is drawn:'
print legend.get_window_extent().height
fig.canvas.draw()
print 'Height of legend after canvas is drawn:'
print legend.get_window_extent().height
However, this is only going to represent the height of the legend in pixels as it is drawn on the screen! If you save the figure, it will be saved with a different dpi (100, by default) than it is drawn on the screen, so the size of things in pixels will be different.
There are two ways around this:
Quick and dirty: draw the figure's canvas before outputting pixel values and be sure to explicitly specify the dpi of the figure when saving (e.g. fig.savefig('temp.png', dpi=fig.dpi).
Recommended, but slightly more complicated: Connect a callback to the draw event and only work with pixel values when the figure is drawn. This allows you to work with pixel values while only drawing the figure once.
As a quick example of the latter method:
import matplotlib.pyplot as plt
def on_draw(event):
fig = event.canvas.figure
ax = fig.axes[0] # I'm assuming only one subplot here!!
legend = ax.legend_
print legend.get_window_extent().height
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(range(10), label='Test')
legend = ax.legend(loc='upper left')
fig.canvas.mpl_connect('draw_event', on_draw)
fig.savefig('temp.png')
Notice the different in what is printed as the height of the legend for the first and second examples. (31.0 for the second vs. 24.8 for the first, on my system, but this will depend on the defaults in your .matplotlibrc file)
The difference is due to the different dpi between the default fig.dpi (80 dpi, by default) and the default resolution when saving a figure (100 dpi, by default).
Hopefully that makes some sense, anyway.