How to reproduce this legend with multiple curves? - python

I've been working hard on a package of functions for my work, and I'm stuck on a layout problem. Sometimes I need to work with a lot of columns subplots (1 row x N columns) and the standard matplotlib legend sometimes is not helpful and makes it hard to visualize all the data.
I've been trying to create something like the picture below. I already tried to create a subplot for the curves and another one for the legends (and display the x-axis scale as a horizontal plot). Also, I tried to spine the x-axis, but when I have a lot of curves plotted inside the same subplots the legend becomes huge.
The following image is from a software. I'd like to create a similar look. Notice that these legends are "static": it remains fixed independent of the zooming. Another observation is, I don't need all the ticks or anything like that.
What I'm already have is the following (the code is a mess, becouse I'm trying many different solutions and it is not organized nor pythonic yet.
import matplotlib.pyplot as plt
fig, ax = plt.subplots(1,2, sharey = True)
ax[0].semilogx(np.zeros_like(dados.Depth)+0.02, dados.Depth)
ax[0].semilogx(dados.AHT90, dados.Depth, label = 'aht90')
ax[0].set_xlim(0.2,2000)
ax[0].grid(True, which = 'both', axis = 'both')
axres1 = ax[0].twiny()
axres1.semilogx(dados.AHT90, dados.Depth, label = 'aht90')
axres1.set_xlim(0.2 , 2000)
axres1.set_xticks(np.logspace(np.log10(0.2),np.log10(2000),2))
axres1.spines["top"].set_position(("axes", 1.02))
axres1.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
axres1.tick_params(axis='both', which='both', labelsize=6)
axres1.set_xlabel('sss')#, labelsize = 5)
axres2 = ax[0].twiny()
axres2.semilogx(dados.AHT10, dados.Depth, label = 'aht90')
axres2.set_xlim(0.2 , 2000)
axres2.set_xticks(np.logspace(np.log10(0.2),np.log10(2000),2))
axres2.spines["top"].set_position(("axes", 1.1))
axres2.get_xaxis().set_major_formatter(matplotlib.ticker.ScalarFormatter())
axres2.tick_params(axis='both', which='both', labelsize=6)
axres2.set_xlabel('aht10')#, labelsize = 5)
fig.show()
and the result is:
But well, I'm facing some issues on make a kind of make it automatic. If I add more curves, the prameter "set position" it is not practical to keep setting the position "by hand"
set_position(("axes", 1.02))
and another problem is, more curves I add, that kind of "legend" keep growing upward, and I have to adjust the subplot size with
fig.subplots_adjust(top=0.75)
And I'm also want to make the adjustment automatic, without keeping updating that parameter whenever I add more curves

Related

Modifying xarray plot legend in python

I am new to using the plotting capabilities of xarray, I usually use matplotlib.
I am trying to move the legend that xarray.plot.scatter creates. It would also be cool to be able to remove the legend title.
data.plot.scatter(x = 'HGT', y = var, hue = 'time', add_guide = True)
If i make a separate legend through ax.legend, when add_guide = False, my legend order gets messed up. My plot is labeling different hours pairs (hours2 - hours1) with different colors and the order is very important for ease of understanding.
ax.legend(labels, bbox_to_anchor=(-1.15, 2.4, -1., .102), loc='lower left', ncol = 6)
So I am trying to understand better how to modify the legend xarray.plot.scatter and I am having trouble finding information about it thus far. Any suggestions? :)

How do I position the axis frame inside a figure without changing the size of the figure? [Python, matplotlib]

I'm trying to create a video of many figures, so I need the axis to remain steady across multiple, independent figures. However, the y-axis changes scale, so the framing of the axis keeps moving as the ticklabels change. I'm trying to manually tell matplotlib exactly what size the whole figure should be and tell it exactly the position of the axis within the figure, but it's not working properly.
Here's what a base figure looks like:
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(8,4),facecolor=(0.5,0.5,0.5))
ax=fig.add_subplot()
ax.plot([5,10],[800,900])
plt.show()
Here is one way for how I'm trying to change it if I want the axis frame to start at left=0.5, bottom=0.5, width=0.2, and height=0.2. I've tried many different ways, and all have failed, so this is illustrative of what I'm trying to do:
fig=plt.figure(figsize=(8,4),facecolor=(0.5,0.5,0.5))
ax=fig.add_axes((0.5,0.5,0.2,0.2))
ax.plot([5,10],[800,900])
plt.show()
Now, I want it to look more like this so that the black box of the axis frame will be in the exact same position for every figure, and each figure will be the exact same size. That way, when I make it an animation, the black frame won't be jerking around. (Obviously, I wouldn't make the buffer that big in the real video.)
You need to use ax.set_position.
If your ax box initially occupies the full figure, you can create a new size relatively to the old one, for example:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(8, 4), facecolor=(0.5, 0.5, 0.5))
ax = fig.add_subplot(111)
bbox = ax.get_position()
new_bbox = (bbox.x0+0.40, bbox.y0+0.40, bbox.width*0.5, bbox.height*0.5)
ax.set_position(new_bbox)
ax.plot([5, 10], [800, 900])
plt.show()

Matplotlib imshow() issues: title on top of top xlabels, and ylabels not aligned in pdf

I'm having some issues in using imshow() from matplotlib, in specific with creating a pdf from it.
I'm dealing with a 500x500 matrix which, for the sake of this question, will be just random values:
np.random.seed(1)
arr = np.array(np.random.random((500, 500)))
The rows and columns are all labelled with different names, but for the sake of this question, let's just make them simple:
labels = ["Big_Label" if x % 2 == 0 else "Bigger_Big_Label" for x in range(500)]
So, I have the following code to plot that matrix:
plt.rc('figure', figsize=(5,5), dpi=500)
fig = plt.figure()
ax = fig.add_subplot(111)
im = ax.imshow(arr)
# defining the same labels for rows and columns
ax.set_xticklabels([''] + labels)
ax.set_yticklabels([''] + labels)
# showing the labels for all the ticks
ax.xaxis.set_major_locator(ticker.MultipleLocator(1))
ax.yaxis.set_major_locator(ticker.MultipleLocator(1))
# personalising the ticks. In particular, labels on top
ax.tick_params(axis='both', which='both', labelsize=0.5, length=0)
ax.tick_params(axis='x',which='both', labelbottom='off', labeltop='on')
ax.tick_params(axis='both', pad=1)
# vertical labels
for label in im.axes.xaxis.get_ticklabels():
label.set_rotation(90)
plt.colorbar(im)
plt.title("Just a Big Title With Words")
# Removing outer lines because they hide part of the lines/columns
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
ax.spines['bottom'].set_visible(False)
ax.spines['left'].set_visible(False)
plt.savefig("fig.pdf")
plt.show()
The first issue is with the title because is on top of the xlabels:
The second issue is, when zooming to see the ylabels and xlabels, they are not aligned. With regards the left labels, they have varying spaces between them and the plot, when I specifically coded ax.tick_params(axis='both', pad=1); if I execute this python code in an IDE or in terminal, this issue doesn't happen (they are all close to the plot). So I guess something is going on when putting this image into a pdf?. With regards with both labels, you can see they are not aligned with the actual rows and columns; for example, the second label on the top is in middle of the blue and orange square, when it should be aligned with the middle of the orange square:
Finally, I tried to call fig.autofmt_xdate() just before plt.savefig(), but the result is even worse, as the top labels are just completely not aligned:
Can you help me solving this issues? I know you have to do a big zoom to see labels, but for the real matrices that I have that is necessary. I also inform that I'm using matplotlib 1.5; I can't use 2.x because of a compatibility issue with another tool
There is a PR for automatically moving the title. Should be included in v2.2 (Feb timeframe) https://github.com/matplotlib/matplotlib/pull/9498, and another for the title padding https://github.com/matplotlib/matplotlib/pull/9816. So we are working on it.
As for your label misalignment in PDF, it looks like there is a bug in label alignment if labelsize is less than 1.0, so don't do that: https://github.com/matplotlib/matplotlib/issues/9963 Its a bug, but I would imagine a low-priority one.

Matplotlib: Hiding specific y-tick labels not working when ticks on the right side of plot

I'm creating a subplot figure with 2 columns and a number of rows. I'm using the following code to move my tick labels and axis label to the right side for the right column (but still keeping the tick marks on both sides):
fig, ax = plt.subplots(4, 2, sharex=False, sharey=False)
fig.subplots_adjust(wspace=0, hspace=0)
for a in ax[:,1]:
a.yaxis.tick_right()
a.yaxis.set_ticks_position('both')
a.yaxis.set_label_position('right')
Then, because the subplots are close together (which is what I want, I don't want any padding in between the plots), the top and bottom y-tick labels overlap between plots. I have attempted to fix this using the method described here (this selects only those ticks that are inside the view interval - check the link for more info):
import matplotlib.transforms as mtransforms
def get_major_ticks_within_view_interval(axis):
interval = axis.get_view_interval()
ticks_in_view_interval = []
for tick, loc in zip(axis.get_major_ticks(), axis.get_major_locator()()):
if mtransforms.interval_contains(interval, loc):
ticks_in_view_interval.append(tick)
return ticks_in_view_interval
for i,a in enumerate(ax.ravel()):
nplots = len(ax.ravel())
yticks = get_major_ticks_within_view_interval(a.yaxis)
if i != 0 and i != 1:
yticks[-1].label.set_visible(False)
if i != nplots-2 and i != nplots-1:
yticks[0].label.set_visible(False)
This seems to work fine for the left column, but in the right column the overlapping ticks are still visible. Does anyone know why this happens, and how to fix it? I just can't seem to figure it out.
I have finally found the solution, so I figured I'd put it here as well in case someone ever has the same problem (or if I forget what I did, haha). I found out when I happened upon the following page: http://matplotlib.org/1.3.1/users/artists.html
What I didn't realize is that the labels on the left and the right of the y-axis can be modified independently of each other. When using yticks[0].label.set_visible(False), the label refers only to the left side labels, so the right side labels stay unchanged. To fix it, I replaced
yticks[0].label.set_visible(False)
by
yticks[0].label1.set_visible(False)
yticks[0].label2.set_visible(False)
(and the same for yticks[-1]). Now it works like a charm!
Generally I've found that problems with overlap in matplotlib can be solved by using
plt.tight_layout()
have you tried that?

matplotlib colorbar in subplots: labels are vanishing

I am developing some code to produce an arbitrary number of 2D plots (maps and simple contour plots) on a figure. The matplotlib subplots routine works great for this. In the simplified example below, everything works as it should. However, in my real application - which uses the exact same commands for subplots, contourf and colorbar, only that these are dispersed across several routines - the labels on the colorbars are not showing up (the color patches seem to be ok though). Even after hours of reading documentation and searching the web, I don't even have a clue where I could start looking for what the problem is. If I have my colorbar instance (cbar), I should be able to find out if the ticklabel position makes sense, if the ticklabels are set to visible, if my font settings make sense, etc.... But how do I actually check these properties? Has anyone encountered similar problems already? (and even better: found a solution?) Oh yes: if I manually create a new figure and axes in the actual plotting routine (where the contourf command is issued), then it will work again. But that means losing all control over the figure layout etc. Could it be that I am not passing my axes instance correctly? Here is what I do:
fig, ax = plt.subplots(nrows, ncols)
row, col = getCurrent(...)
plotMap(x, y, data, ax=ax[row,col], ...)
Then, inside plotMap:
c = ax.contourf(x, y, data, ...)
ax.figure.colorbar(c, ax=ax, orientation="horizontal", shrink=0.8)
As said above, the example below with simplified plots and artificial data works fine:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0.,360.,5.)*np.pi/180.
y = np.arange(0.,360.,5.)*np.pi/180.
data = np.zeros((y.size, x.size))
for i in range(x.size):
data[:,i] = np.sin(x[i]**2*y**2)
fig, ax = plt.subplots(2,1)
contour = ax[0].contourf(x, y, data)
cbar = ax[0].figure.colorbar(contour, ax=ax[0], orientation='horizontal', shrink=0.8)
contour = ax[1].contourf(x, y, data, levels=[0.01,0.05,0.1,0.05])
cbar = ax[1].figure.colorbar(contour, ax=ax[1], orientation='horizontal', shrink=0.8)
plt.show()
Thanks for any help!
Addition after some further poking around:
for t in cbar.ax.get_xticklabels():
print t.get_position(), t.get_text(), t.get_visible()
shows me the correct text and visible=True, but all positions are (0.,0.). Could this be a problem?
BTW: axis labels are also missing sometimes... and I am using matplotlib version 1.1.1 with python 2.7.3 on windows.
OK - I could track it down: matplotlib is working as it should!
The error was embedded in a utility routine that adds some finishing touches to each page (=figure) once the given number of plot panels has been produced. In this routine I wanted to hide empty plot panels (i.e. on the last page) and I did this with
ax = fig.axes
for i in range(axCurrent, len(ax)):
ax[i].set_axis_off()
However, axCurrent was already reset to zero when the program entered this routine for any page but the last, hence the axes were switched off for all axes in figure. Adding
if axCurrent > 0:
before the for i... solves the problem.
Sorry if I stole anyone's time. Thanks anyway to everyone who was considering to help!

Categories