matplotlib long legend name - matplotlib

I have a following code which produces a graph -
# imports specific to the plots in this example
import sys
import numpy as np
from matplotlib import cm
import matplotlib.pyplot as plt
resultsDirectory='results'
outputFile=resultsDirectory+".pdf"
axisLabelFontSize=16
borderWidth=0.0
# Twice as wide as it is tall.
fig = plt.figure(figsize=plt.figaspect(0.5))
ax = fig.add_subplot(111)
# Set up the Grid
[i.set_linewidth(borderWidth) for i in ax.spines.itervalues()]
unsatX=[680,2775,3821,680,4073,941,1202,1463]
unsatY=[1,1,1,4,1,2,2,2]
paretoX=[680, 1203, 1726, 4870]
paretoY=[10,7, 4,1]
satX=[4870,680,1727,1726,1203,680]
satY=[1,13,7,4,7,10]
typeX=[680, 1727]
typeY=[13, 7]
leftX=[680]
leftY=[12]
c = np.rec.fromarrays([paretoX, paretoY], names='x,y')
c.sort()
paretoX=c.x
paretoY=c.y
markrsz=8
l4, = plt.plot(paretoX, paretoY, '#000000', lw=2, label='Pareto Curve(unfolding, period locality)',markersize=markrsz,zorder = 10)
l1, = plt.plot(satX, satY, 'bo', label='Sat Points',markersize=markrsz,zorder = 10)
l2, = plt.plot(unsatX, unsatY, 'ro',marker='s',label='Unsat Points',markersize=markrsz,zorder = 10)
l5, = plt.plot(leftX, leftY, 'gp',label='Proc. count pareto points',markersize=markrsz)
l6, = plt.plot(typeX, typeY, 'w*',label='Modulo pareto points',markersize=markrsz,zorder=10)
leg=plt.legend(bbox_to_anchor=(0.,-0.200, 1., 1.102), loc=3, numpoints=1,
ncol=3, mode="expand", borderaxespad=0., fancybox=True, shadow=True,prop={'size':axisLabelFontSize})
rect = leg.get_frame()
rect.set_facecolor('#cccccc') # a grayscale intensity
#leg.set_frame_on(False)
latency=[680,2775,4870, 680,3821,4868, 680,1727,4341,4864, 680,1203,1726,1203, 680,4073,4334,4595,4856, 941,1202,1463,1724]
processor=[1, 1, 1,13, 1, 1, 7, 7, 1, 1, 4, 4, 4, 7,10,1, 1, 1, 1, 2, 2, 2, 2]
ax.set_xlabel('Period',size=axisLabelFontSize,labelpad=10)
ax.set_ylabel('Processors',size=axisLabelFontSize,labelpad=10)
ax.set_xlim(0, max(latency)+100)
ax.set_ylim(0, max(processor)+1)
# Set Border width zero
[i.set_linewidth(0) for i in ax.spines.itervalues()]
gridLineWidth=0.1
ax.set_axisbelow(False)
gridlines = ax.get_xgridlines()+ax.get_ygridlines()
#ax.set_axisbelow(True)
plt.setp(gridlines, 'zorder', 5)
ax.yaxis.grid(True, linewidth=gridLineWidth, linestyle='-', color='0.6',alpha='0.3')
ax.xaxis.grid(False)
ax.xaxis.set_ticks_position('none')
ax.yaxis.set_ticks_position('none')
fig.savefig(outputFile, format="pdf", bbox_inches='tight')
The legends in the plot get messed up.
Could someone tell me how do i fix a long legend entry which overwrites into area of other entry? What would be ideal if, I could do 3 legend entries in first row and two legend entries in the second row.

Besides the workaround found by the question author, a possibility is to add new lines for long labels:
...
l4, = plt.plot(paretoX, paretoY, '#000000', lw=2,
label='Pareto Curve \n(unfolding, period locality)',markersize=markrsz,zorder = 10)
...

The following modification to the code also solved my problem -
leg=plt.legend(bbox_to_anchor=(0.,-0.350, 1., 1.102), loc=3, numpoints=1, ncol=2 , borderaxespad=0., fancybox=True, shadow=True,prop={'size':axisLabelFontSize})

Related

create legend for markercolor and size

I've created the following figure:
With following code:
matplotlib.rcParams.update({'font.size': 10})
fig = plt.figure(figsize=(16, 9), dpi=300, facecolor='white')
ax = plt.subplot(111, projection=ccrs.PlateCarree())
ax.set_extent(extent)
# cartopy layers
country_10m = cartopy.feature.NaturalEarthFeature('cultural', 'admin_0_countries', '10m')
ax.add_feature(country_10m, edgecolor='w', linewidth=0.75, facecolor='#EEEFEE', label='country border')
ax.coastlines(resolution='10m', color='#EEEFEE', linewidth=0.75)
ax.imshow(np.tile(np.array([[[191, 210, 217]]], dtype=np.uint8), [2, 2, 1]), origin='lower', transform=cartopy.crs.PlateCarree(), extent=extent)
ax.scatter(gdf_ldb.x, gdf_ldb.y, c= gdf_ldb.Color, s= gdf_ldb.Markersize, zorder=30)
# ax.scatter(gdf_ports_filt.longitude, gdf_ports_filt.latitude, s= 10, color= 'k', zorder= 30)
ax.gridlines(crs=ccrs.PlateCarree(), draw_labels=True, linewidth=1, color='gray', alpha=0.5, linestyle='-')
ax.text(-0.08, 0.5, 'latitude [°]', va='bottom', ha='center',rotation='vertical', rotation_mode='anchor',transform=ax.transAxes);
ax.text(0.5, -0.09, 'longitude [°]', va='bottom', ha='center', rotation='horizontal', rotation_mode='anchor', transform=ax.transAxes);
How do I create a legend for the markersize as well for the color, so like this:
With x, x1, and x2 representing the values of the markersizes.
gdf_ldb looks like:
x y Type Color Markersize geometry
prograding_feature_polygon_29 12.857701 56.648035 Updrift grey 3.0 POINT (12.85770 56.64804)
prograding_feature_polygon_57 17.781445 54.808079 Updrift grey 3.0 POINT (17.78144 54.80808)
prograding_feature_polygon_58 17.438390 54.754518 Updrift grey 3.0 POINT (17.43839 54.75452)
prograding_feature_polygon_63 4.708077 52.880322 Updrift grey 3.0 POINT (4.70808 52.88032)
prograding_feature_polygon_72 3.953364 51.842299 Updrift grey 3.0 POINT (3.95336 51.84230)
... ... ... ... ... ... ...
retreating_feature_polygon_2018 -10.148432 53.415224 Double Updrift grey 3.0 POINT (-10.14843 53.41522)
retreating_feature_polygon_2019 -9.954510 54.197329 Double Updrift grey 3.0 POINT (-9.95451 54.19733)
retreating_feature_polygon_2119 15.095564 37.389535 Double Updrift grey 3.0 POINT (15.09556 37.38953)
retreating_feature_polygon_2120 14.317893 37.025026 Double Updrift grey 3.0 POINT (14.31789 37.02503)
retreating_feature_polygon_2121 13.952111 37.101009 Updrift grey 3.0 POINT (13.95211 37.10101)
Thanks in advance,
Dante
The key is to capture the artist (PathCollection in this case) returned by the scatter command. That has a method to retrieve the legend items manually, and it has keywords to distinguish between size and color (default). The num keyword can be used to reduce the amount of items returned, which is useful in the case of a (semi)continuous property as the size can be.
The example below plots two separate legends for both properties. You can also combine the handles and labels of both and plot them in a single legend if needed.
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
import cartopy.crs as ccrs
import cartopy
import numpy as np
lons = np.random.randint(-170, 170, 100)
lats = np.random.randint(-80, 80, 100)
sizes = np.random.rand(100) * 100 + 5
colors = np.random.randint(0, 3, 100)
fig, ax = plt.subplots(
figsize=(8,4), dpi=86, facecolor='w',
subplot_kw=dict(projection=ccrs.PlateCarree()),
)
fig.subplots_adjust(left=0, right=1, bottom=0, top=1)
ax.add_feature(cartopy.feature.LAND, ec='none', fc='#EEEFEE', label='country border')
ax.add_feature(cartopy.feature.BORDERS, ec='w', fc='k', lw=0.75, label='country border')
ax.coastlines(resolution='10m', color='#EEEFEE', lw=0.75)
m = ax.scatter(lons, lats, s=sizes, c=colors, zorder=5, label="points")
l1 = ax.legend(
*m.legend_elements(prop="colors", num="auto"), title="Colors", framealpha=1,
loc="upper right", bbox_to_anchor=(0.88, 0.8, 0.12, 0.2), mode="expand",
)
ax.add_artist(l1) # prevent overwriting with second legend
l2 = ax.legend(
*m.legend_elements(prop="sizes", num=5), title="Sizes", framealpha=1,
loc="upper right", bbox_to_anchor=(0.88, 0.55, 0.12, 0.2), mode="expand",
)
The documentation about this shows some variations on this:
https://matplotlib.org/stable/gallery/lines_bars_and_markers/scatter_with_legend.html#automated-legend-creation
The answer by Rutger Kassies is excellent for many use cases. However, he mentions that One can also combine the handles and labels of both and plot them in a single legend if needed.
Here I offer another answer that shows the steps to create the single legend manually. Inside the single legend, 2 groups of sub legends are created and arranged as needed.
With single legend, you don't need to find the values of bbox_to_anchor for the second (or third and so on) to position them properly.
With manual creation of items into a single legend, you have full control of the items' you need in the legend. However, it need some extra coding to achieve the goal.
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import cartopy.crs as ccrs
import cartopy.feature as cfeature
# For `Categories` symbol
# Each item of legends requires 3 properties: color/text/marker_shape
color_V = ["green", "orange", "purple", "red", "cyan", "magenta"]
text_V = ["cat_4", "cat_9", "cat_13", "cat_15", "cat_19", "cat_33"]
marker_V = ["o", "o", "o", "o", "o", "o"]
len_V = len(color_V)
# For `Size/values` symbol
color_S = ["gray", "gray", "gray", "gray"]
sizes_S = [4, 8, 12, 16] #increasing values ...
text_S = ["4", "8", "12", "16"] #cover `sizes1` below
marker_S = ["o", "o", "o", "o"] #use disk shape
len_S = len(color_S)
# Demo data locations and attributes
xs = [23,12,4,25,24,52,17,33]
ys = [41,12,32,15,35,21,23,43]
colors1 = ["green", "orange", "purple", "red", "cyan", "magenta", "green", "orange"]
#texts1 = ["4", "9", "13", "15", "19", "33", "4", "9"]
markers1 = ["o", "o", "o", "o", "o", "o", "o", "o"]
sizes1 = [10,16,9,12,7,4,2,6]
len1 = len(xs)
all_patches = [] #for items in a single legend
# Create figure and `ax` for map plotting
# This form can create a single axes or an array of axes
fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(8,6), subplot_kw={'projection': ccrs.PlateCarree()})
# All steps of plots will be done on `ax`
# [1] Add an invisible object as a spacer in the legend box
#rect = mpatches.Rectangle([0, 0], 0.01, 0.01, ec="none", color="lightgray")
all_patches.append(mlines.Line2D([0, 0], [1, 0], color="none"))
# Explicitly defining the elements in the legend
# [2] Add proxied text: 'Categories' to the legend
line = mlines.Line2D([0, 0], [1, 0], lw=.5, alpha=0.9, color="none")
line.set_label('Categories') # Title for 1st group of symbols in the legend
all_patches.append(line)
# [3] Plot (on the axes) `none` data point and
# save the output patches for `Categories` group
patches_V = [ ax.plot([],[], marker=marker_V[i], ms=8, ls="", color=color_V[i], \
label="{:s}".format(text_V[i]) )[0] \
for i in range(len_V) ]
all_patches += patches_V
# [4] Add an invisible object as a spacer in the legend box
all_patches.append(mlines.Line2D([0, 0], [1, 0], color="none"))
# [5] Add proxied text: 'Sizes' to the legend
x, y = ([0, 1], [0, 0])
line = mlines.Line2D([0, 0], [1, 0], lw=.5, alpha=0.9, color="none")
line.set_label('Sizes') # Title for 2nd group of symbols in the legend
all_patches.append(line)
# [6] Create patches for `Sizes` group
patches_S = [ ax.plot([],[], marker=marker_S[i], ms=sizes_S[i], ls="", \
color=color_S[i], \
label="{:s}".format(text_S[i]) )[0] for i in range(len_S) ]
all_patches += patches_S
# Plot point data using the demo data
for i in range(len1):
ax.plot(xs[i], ys[i], marker=markers1[i], ms=sizes1[i], color=colors1[i])
ax.set_extent([0, 80, 0, 60])
# Plot the legend in the upper-right corner
combined_legend = ax.legend(handles=all_patches,
bbox_to_anchor=(1, 1),
title="The Legend",
loc='upper right',
ncol=1,
numpoints=1,
facecolor="lightgray",
fontsize = 10,
title_fontsize= 12,
labelspacing = 0.55,
shadow=True)
# Draw some basemap features
ax.coastlines(lw=0.3, color="k")
ax.add_feature(cfeature.LAND)
ax.add_feature(cfeature.OCEAN)
plt.title("Legend for Categories and Sizes")
plt.show()
The output map:

Align bar and line plot on x axis without the use of rank and pointplot

Please note, I've looked at other questions like question and my problem is different and not a duplicate!
I would like to have two plots, with the same x axis in matplotlib. I thought this should be achieved via constrained_layout, but apparently this is not the case. Here is an example code.
import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.gridspec as grd
x = np.arange(0, 30, 0.001)
df_line = pd.DataFrame({"x": x, "y": np.sin(x)})
df_bar = pd.DataFrame({
"x_bar": [1, 7, 10, 20, 30],
"y_bar": [0.0, 0.3, 0.4, 0.1, 0.2]
})
fig = plt.subplots(constrained_layout=True)
gs = grd.GridSpec(2, 1, height_ratios=[3, 2], wspace=0.1)
ax1 = plt.subplot(gs[0])
sns.lineplot(data=df_line, x=df_line["x"], y=df_line["y"], ax=ax1)
ax1.set_xlabel("time", fontsize="22")
ax1.set_ylabel("y values", fontsize="22")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
plt.setp(ax1.get_legend().get_texts(), fontsize="22")
ax2 = plt.subplot(gs[1])
sns.barplot(data=df_bar, x="x_bar", y="y_bar", ax=ax2)
ax2.set_xlabel("time", fontsize="22")
ax2.set_ylabel("y values", fontsize="22")
plt.yticks(fontsize=16)
plt.xticks(fontsize=16)
this leads to the following figure.
However, I would like to see the corresponding x values of both plot aligned. How can I achieve this? Note, I've tried to use the following related question. However, this doesn't fully apply to my situation. First with the high number of x points (which I need in reality) point plots is make the picture to big and slow for loading. On top, I can't use the rank method as my categories for the barplot are not evenly distributed. They are specific points on the x axis which should be aligned with the corresponding point on the lineplot
x = np.arange(0, 30, 0.001)
df_line = pd.DataFrame({"x": x, "y": np.sin(x)})
df_bar = pd.DataFrame({
"x_bar": [1, 7, 10, 20, 30],
"y_bar": [0.0, 0.3, 0.4, 0.1, 0.2]
})
fig, (ax1, ax2) = plt.subplots(2,1)
ax1.plot(df_line['x'], df_line['y'])
for i in range(len(df_bar['x_bar'])):
ax2.axvline(x=df_bar['x_bar'][i], ymin=0, ymax=df_bar['y_bar'][i])
Output:
---edit---
I incorporated #mozway advice for linewidth:
lw = (300/ax1.get_xlim()[1])
ax2.axvline(x=df_bar['x_bar'][i], ymin=0, ymax=df_bar['y_bar'][i], solid_capstyle='butt', lw=lw)
Output:
or:

Colormap is not categorizing the data properly

Here is my script to plot data from a Geogtiff file using basemap. The data is categorical and there are 13 categories within this domain. The problem is that some categories get bunched up into one colour and thus some resolution is lost.
Unfortunately, I do not know how to fix this. I read that plt.cm.get_cmp is better for discrete datasets but I have not gotten it to work unfortunately.
gtif = 'some_dir'
ds = gdal.Open(gtif)
data = ds.ReadAsArray()
gt = ds.GetGeoTransform()
proj = ds.GetProjection()
xres = gt[1]
yres = gt[5]
xmin = gt[0] + xres
xmax = gt[0] + (xres * ds.RasterXSize) - xres
ymin = gt[3] + (yres * ds.RasterYSize) + yres
ymax = gt[3] - yres
xy_source = np.mgrid[xmin:xmax+xres:xres, ymax+yres:ymin:yres]
ds = None
fig2 = plt.figure(figsize=[12, 11])
ax2 = fig2.add_subplot(111)
ax2.set_title("Land use plot")
bm2 = Basemap(ax=ax2,projection='cyl',llcrnrlat=ymin,urcrnrlat=ymax,llcrnrlon=xmin,urcrnrlon=xmax,resolution='l')
bm2.drawcoastlines(linewidth=0.2)
bm2.drawcountries(linewidth=0.2)
data_new=np.copy(data)
data_new[data_new==255] = 0
nbins = np.unique(data_new).size
cb =plt.cm.get_cmap('jet', nbins+1)
img2 =bm2.imshow(np.flipud(data_new), cmap=cb)
ax2.set_xlim(3, 6)
ax2.set_ylim(50,53)
plt.show()
labels = [str(i) for i in np.unique(data_new)]
cb2=bm2.colorbar(img2, "right", size="5%", pad='3%', label='NOAH Land Use Category')
cb2.set_ticklabels(labels)
cb2.set_ticks(np.unique(data_new))
Here are the categories that are found within the domain (numbered classes):
np.unique(data_new)
array([ 0, 1, 4, 5, 7, 10, 11, 12, 13, 14, 15, 16, 17], dtype=uint8)
Thanks so much for any help here. I have also attached the output image that shows the mismatch. (not working)
First, this colormap problem is independent of the use of basemap. The following is therefore applicable to any matplotlib plot.
The problem here is that creating a colormap from n values distributes those values equally over the colormap range. Some values from the image therefore fall into the same colorrange within the colormap.
To prevent this, one can generate a colormap with the initial number of categories as shown below.
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors
# generate some data
data = np.array( [ 0, 1, 4, 5, 7, 10]*8 )
np.random.shuffle(data)
data = data.reshape((8,6))
# generate colormap and norm
unique = np.unique(data)
vals = np.arange(int(unique.max()+1))/float(unique.max())
cols = plt.cm.jet(vals)
cmap = matplotlib.colors.ListedColormap(cols, int(unique.max())+1)
norm=matplotlib.colors.Normalize(vmin=-0.5, vmax=unique.max()+0.5)
fig, ax = plt.subplots(figsize=(5,5))
im = ax.imshow(data, cmap=cmap, norm=norm)
for i in range(data.shape[0]):
for j in range(data.shape[1]):
ax.text(j,i,data[i,j], color="w", ha="center", va="center")
cb = fig.colorbar(im, ax=ax, norm=norm)
cb.set_ticks(unique)
plt.show()
This can be extended to exclude the colors not present in the image as follows:
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors
# generate some data
data = np.array( [ 0, 1, 4, 5, 7, 10]*8 )
np.random.shuffle(data)
data = data.reshape((8,6))
unique, newdata = np.unique(data, return_inverse=1)
newdata = newdata.reshape(data.shape)
# generate colormap and norm
new_unique = np.unique(newdata)
vals = np.arange(int(new_unique.max()+1))/float(new_unique.max())
cols = plt.cm.jet(vals)
cmap = matplotlib.colors.ListedColormap(cols, int(new_unique.max())+1)
norm=matplotlib.colors.Normalize(vmin=-0.5, vmax=new_unique.max()+0.5)
fig, ax = plt.subplots(figsize=(5,5))
im = ax.imshow(newdata, cmap=cmap, norm=norm)
for i in range(newdata.shape[0]):
for j in range(newdata.shape[1]):
ax.text(j,i,data[i,j], color="w", ha="center", va="center")
cb = fig.colorbar(im, ax=ax, norm=norm)
cb.ax.set_yticklabels(unique)
plt.show()

Matplotlib subplots size not equal

I am using subplot to display some figures, however the labels are mixed with the last subplot, so the plots don't have equal size. and the previous 5 are not perfectly round circle.
Here's my code:
for i in range(6):
plt.subplot(231 + i)
plt.title("Department " + depts[i])
labels = ['Male', 'Female']
colors = ['#3498DB', '#E74C3C']
sizes = [male_accept_rates[i] / (male_accept_rates[i] + female_accept_rates[i]),
female_accept_rates[i] / (male_accept_rates[i] + female_accept_rates[i])]
patches, texts = plt.pie(sizes, colors=colors, startangle=90)
plt.axis('equal')
plt.tight_layout()
plt.legend(labels, loc="best")
plt.show()
And here's the output:
can anyone give me some advise? Much appreciated.
It appears plt.axis('equal') only applies to the last subplot. So your fix is to put that line of code in the loop.
So:
import numpy as np
import matplotlib.pyplot as plt
depts = 'abcdefg'
male_accept_rates = np.array([ 2, 3, 2, 3, 4, 5], float)
female_accept_rates= np.array([ 3, 3, 4, 3, 2, 4], float)
for i in range(6):
plt.subplot(231 + i)
plt.title("Department " + depts[i])
labels = ['Male', 'Female']
colors = ['#3498DB', '#E74C3C']
sizes = [male_accept_rates[i] / (male_accept_rates[i] + female_accept_rates[i]),
female_accept_rates[i] / (male_accept_rates[i] + female_accept_rates[i])]
patches, texts = plt.pie(sizes, colors=colors, startangle=90)
plt.axis('equal')
plt.tight_layout()
plt.legend(labels, loc="best")
plt.show()
Produces this now:

how to get ticks on both sides at same tick locations

I want y-ticks on both sides(left & right), but with different labels at the same y points. I tried following, but I'm not able to position ticks at same location.
I'm newbie to matplotlib. I have gone through the matplotlib example, but couldn't figure it out the solution to my problem.
http://matplotlib.org/examples/pylab_examples/barchart_demo2.html
Greatly appreciate any suggestions.
Here is the code:
import numpy as np
import matplotlib.pyplot as plt
groups = [ 1, 2, 3, 4, 5 ]
members = [ 1, 2, 3, 4 ]
colors = [ 'r', 'y', 'b', 'k']
#store score of members for the groups
scores = {member: 100*np.random.rand(len(groups)) for member in members}
group_cnt = group_cnt = sum([scores[member] for member in members])
print scores
fig = plt.figure(figsize=(8,6))
ax = fig.add_subplot(111)
width_bar = 0.5
width_space = 0.2
#position of barh
total_space = len(groups)*(len(members)*width_bar)+(len(groups)-1)*width_space
ind_space = len(members)*width_bar
step = ind_space/2.
#pos for labels
pos = np.arange(step, total_space+width_space, ind_space+width_space)
#pos for grin lines
minor_pos = np.arange(ind_space, total_space+width_space, ind_space+width_space)
for idx in xrange(len(members)):
ax.barh(pos-step+idx*width_bar, scores[members[idx]], width_bar, edgecolor='k', color=colors[idx], linewidth=3)
ax.invert_yaxis()
ax.set_yticks(pos)
ax.set_yticklabels(groups)
ax.set_yticks(minor_pos, minor=True)
ax.grid(True, which='minor')
ax.set_ylabel('Groups')
ax2 = ax.twinx()
ax2.set_ylabel('Group totals')
ax2.set_yticks(pos)
ax2.set_yticklabels(group_cnt)
ax2.invert_yaxis()
plt.show()
I think you got caught by a bit of trickery in that example. There is a plot([100, 100], [0, 5]) in the demo code which is doing a lot of non-obvious work (I am working on submitting a PR to improve the demo) in making sure that the ylimits are the same for both yaxis.
You just need to add a
ax2.set_ylim(ax.get_ylim())
before you call show.
You also have an un-related error ax2.set_yticklabels(group_cnt) -> ax2.set_yticklabels(groups).
[side note, generated PR #2327]