Setting z_order when working with ConnectionPatch - matplotlib

I am trying to produce a figure where red line is not going to be visible inside the green rectangle (i.e. within the middle panel). Setting z order seems to be in effect only for the lower panel (subplot) and it is ignored for the upper and middle one. Can anyone help with this please.
Alternatively, what would also work for me is, if I plot two subplots: top and bottom, and the piece of line that connects points X (from the bottom) and Y (from the top subplot) does not get plotted in the region which is between two places. In other words, line looks as a broken line going from X to the top of bottom panel, then having some skip and then continuing from min_y in top panel and going all the way to Y.
I am planning to achieve this by setting color of rectangle to be white so that it overwrites these lines (but this does not work).
from matplotlib.patches import ConnectionPatch
import matplotlib.patches as patches
from matplotlib.patches import Rectangle
import matplotlib.pyplot as plt
fig, (ax_upper, ax_middle, ax_lower) = plt.subplots(3, 1, sharey = False)
ax_upper.spines['top'].set_visible(False)
ax_upper.spines['bottom'].set_visible(False)
ax_upper.spines['right'].set_visible(False)
ax_upper.get_xaxis().set_ticks([])
ax_middle.spines['left'].set_visible(False)
ax_middle.spines['right'].set_visible(False)
ax_middle.spines['top'].set_visible(False)
ax_middle.spines['bottom'].set_visible(False)
ax_middle.get_xaxis().set_ticks([])
ax_middle.get_yaxis().set_ticks([])
ax_lower.spines['top'].set_visible(False)
ax_lower.spines['right'].set_visible(False)
ax_upper.set_ylim(10, 100)
ax_lower.set_ylim(0, 10)
ax_lower.set_xlim(0, 100)
ax_upper.set_xlim(0, 100)
con = ConnectionPatch(xyA = (2, 2), xyB = (80,90), coordsA = "data", coordsB = "data", axesA = ax_lower, axesB = ax_upper)
ax_lower.add_artist(con)
con.set_zorder(1)
con.set_color("red")
con.set_linewidth(3)
con = ConnectionPatch(xyA = (95,5), xyB = (80, 90), coordsA = "data", coordsB = "data", axesA = ax_lower, axesB = ax_upper, lw=1)
ax_lower.add_artist(con)
con.set_zorder(1)
con.set_color("red")
con.set_linewidth(3)
ax_lower.plot([2], [2], marker="o", color = "red")
ax_upper.plot([80], [90], marker="o", color = "red", zorder = 2)
ax_lower.plot([95], [5], marker="o", color = "red")
ax_upper.plot([0,0],[0,0], label="class A", color = "red", zorder=1, marker = "o", )
ax_upper.legend(loc='upper left')
rect = Rectangle((0,0), 1, 1, linewidth=1, edgecolor='black', facecolor='green', zorder = 3)
ax_middle.add_patch(rect)
plt.show()
example output

Related

Matplotlib - Change the white space between specific subplots in a grid grid

I have a grid of subplots and I would like to adjust the white space between only two of them such that the shared x labels are centred without overlapping either graph.
This question has a solution for when these are the only two subplots. However I'm struggling to adjust this to two specific subplots in a grid of many.
This code can be used to illustrate my problem.
In [1]
fig = plt.figure(figsize = (15, 10))
gs = fig.add_gridspec(2,4)
ax1 = fig.add_subplot(gs[0, 0])
ax2 = fig.add_subplot(gs[0, 1:3])
ax3 = fig.add_subplot(gs[0, 3])
ax4 = fig.add_subplot(gs[1, 0])
ax5 = fig.add_subplot(gs[1, 1])
ax6 = fig.add_subplot(gs[1, 2])
ax7 = fig.add_subplot(gs[1, 3])
np.random.seed(19680801)
# Example data
people = ('Really Really Long Name', 'Another Long Name', 'Short Name', 'Name', 'N')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
ax5.barh(y_pos, performance, align='center')
ax5.set_yticks(y_pos, labels=people)
ax5.invert_xaxis()
ax5.set_xlabel('Label')
ax5.set_title('Bar 1')
ax6.barh(y_pos, performance, align='center')
ax6.set_yticks(y_pos, labels=people)
ax6.set_xlabel('Label')
ax6.set_title('Bar 2')
Out [1]
If I apply the solution to the linked question here then every subplot's white space is effected. I know this is because it calls on fig.dpi_scale_trans which effects the whole figure but I'm new to transforms and can't work out what to use in its place
In [2]
fig.tight_layout()
fig.subplots_adjust(wspace=0.7)
plt.setp(axes[0].yaxis.get_majorticklabels(), ha='center')
# Create offset transform by some points in x direction
dx = 60 / 72.
dy = 0 / 72.
offset = mlb.transforms.ScaledTranslation(dx, dy, fig.dpi_scale_trans)
# apply offset transform to all y ticklabels.
for label in ax6.yaxis.get_majorticklabels():
label.set_transform(label.get_transform() + offset)
Out [2]
I figured out how to solve this so posting my own answer in case anybody has a similar problem in the future.
This question and answer from 7 years ago contained the necessary help to solve the problem.
Essentially you must plot and position different GridSpecs in the figure using GridSpec from matplotlib.gridspec rather than calling one with fig.add_gridspec()
Link to GridSpec documentation
Following on from my example above I wanted to create a 2x4 grid. To do that we can plot the following grids in set positions of the figure:
Left: 1x2
Top Centre: 1x1
Bottom Centre: 2x1
Right: 1x2
In [1]:
from matplotlib.gridspec import GridSpec
fig = plt.figure(figsize = (15, 10))
# Example Data
people = ('Really Really Long Name', 'Another Long Name', 'Short Name', 'Name',
'N')
y_pos = np.arange(len(people))
performance = 3 + 10 * np.random.rand(len(people))
# Left portion of grid (2x1).
# 'left' and 'right' tell the grid where it should start and finish
gs1 = GridSpec(2, 1)
gs1.update(left = 0, right = 0.2)
# Plotting empty subplots for illustration purposes
for i in gs1:
ax = plt.subplot(i)
# Mirroring on the right portion of the grid
gs2 = GridSpec(2, 1)
gs2.update(left = 0.8, right = 1)
for i in gs2:
ax = plt.subplot(i)
# Plotting in top center
# Note here we only need to plot a 1x1
gs3 = GridSpec(1, 1)
gs3.update(left = 0.25, right = 0.75, bottom = 0.53) #0.53 aligns with sides
ax3 = plt.subplot(gs3[0])
# Plotting the barh in the bottom center
# wsapce only adjusts this grid not the entire figure
gs4 = GridSpec(1, 2)
gs4.update(left = 0.2, right = 0.8, top = 0.45, wspace = 0.75)
# Left barh
ax5 = plt.subplot(gs4[0])
ax5.barh(y_pos, performance, align='center')
ax5.set_yticks([])
ax5.invert_xaxis()
ax5.set_xlabel('Label')
ax5.set_title('Bar 1')
# Right barh
ax6 = plt.subplot(gs4[1])
ax6.barh(y_pos, performance, align='center')
ax6.set_yticks(y_pos, labels=people)
ax6.set_xlabel('Label')
ax6.set_title('Bar 2')
plt.show()
Out [1]:

How to align a legend relative to a GridSpec cell?

I am creating a figure like this:
fig = plt.figure(figsize = (7, 8))
outer_grid = gridspec.GridSpec(2, 1, height_ratios = [2, 1])
inner_grid1 = gridspec.GridSpecFromSubplotSpec(4, 3, subplot_spec=outer_grid[0])
inner_grid2 = gridspec.GridSpecFromSubplotSpec(2, 3, subplot_spec=outer_grid[1])
Now I would like to have one legend for all plots in inner_grid1 and a separate legend for all plots in inner_grid2. And I would like those legends to be placed nicely, even though they are higher than a single plot, and cannot have more than one column to not make the figure too wide.
Here is an example where I tried to align the legends with trial and error with method 2 below, however this took ages to make.
So I see three options to achieve this, none of which work:
Place the legend as part of an Axes object, but manually move it outside of the actual plot using axes.legend([...], bbox_to_anchor=(x, y)). This does not work when the legend is higher as a single plot, because it rescales the plots to fit the legend into its grid cell.
Place the legend globally on the Figure object. This works, but makes the correct placement really hard. I cannot use loc = "center right", since it centers it for the full figure instead of just the inner_grid1 or inner_grid2 plots.
Place the legend locally on the GridSpecFromSubplotSpec object. This would be perfect. However there is no method to create a legend on a GridSpecFromSubplotSpec or related classes, and the pyplot.legend method misses parameters to restrict the loc to parts of a grid.
Is there a way to place a legend as described?
As requested, a small code example generating something similar as desired.
This example uses method 2:
#!/usr/bin/env python3
import pandas as pd, seaborn as sns
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
GENOMES = ["spneumoniae", "ecoliK12", "scerevisiae", "celegans", "bmori", "hg38"]
fig = plt.figure(figsize = (7, 8))
outer_grid = gridspec.GridSpec(2, 1, height_ratios = [2, 1])
inner_grid1 = gridspec.GridSpecFromSubplotSpec(4, 3, subplot_spec=outer_grid[0])
inner_grid2 = gridspec.GridSpecFromSubplotSpec(2, 3, subplot_spec=outer_grid[1])
# plots are in sets of six, 2 rows by 3 columns each
for index, genome in enumerate(GENOMES):
data = pd.DataFrame({"x": [0, 1, 2, 3, 0, 1, 2, 3], "y": [1, 0, 3, 2, 1, 0, 3, 2], "hue": ["a", "a", "a", "a", "b", "b", "b", "b"]})
# first set of six
ax1 = plt.Subplot(fig, inner_grid1[index])
ax1 = sns.lineplot(data = data, x = "x", y = "y", hue = "hue", ax = ax1)
ax1.set_xlabel("")
ax1.set_ylabel("")
if index == 2:
ax1.legend()
handles, labels = ax1.get_legend_handles_labels()
fig.legend(handles, labels, loc = "center left", title = "", bbox_to_anchor=(0.9, 2/3 - 0.03))
ax1.legend([], [], loc = "lower center", title = f"{genome}")
fig.add_subplot(ax1)
# second set of six
ax2 = plt.Subplot(fig, inner_grid1[index + 6])
ax2 = sns.lineplot(data = data, x = "x", y = "y", hue = "hue", ax = ax2)
ax2.set_xlabel("")
ax2.set_ylabel("")
ax2.legend([], [], loc = "upper center", title = f"{genome}")
fig.add_subplot(ax2)
#third set of six
ax3 = plt.Subplot(fig, inner_grid2[index])
ax3 = sns.lineplot(data = data, x = "x", y = "y", hue = "hue", ax = ax3)
ax3.set_xlabel("")
ax3.set_ylabel("")
if index == 2:
ax3.legend(["#unitigs", "avg. unitig len."])
handles, labels = ax3.get_legend_handles_labels()
fig.legend(handles, labels, loc = "center left", title = "", bbox_to_anchor=(0.9, 1/6 + 0.05))
ax3.legend([], [], loc = "upper center", title = f"{genome}")
fig.add_subplot(ax3)
plt.savefig("stackoverflow_test.pdf", bbox_inches="tight")

define size of individual subplots side by side

I am using subplots side by side
plt.subplot(1, 2, 1)
# plot 1
plt.xlabel('MEM SET')
plt.ylabel('Memory Used')
plt.bar(inst_memory['MEMORY_SET_TYPE'], inst_memory['USED_MB'], alpha = 0.5, color = 'r')
# pol 2
plt.subplot(1, 2, 2)
plt.xlabel('MEM POOL')
plt.ylabel('Memory Used')
plt.bar(set_memory['POOL_TYPE'], set_memory['MEMORY_POOL_USED'], alpha = 0.5, color = 'g')
they have identical size - but is it possible to define the width for each subplot, so the right one could be wider as it has more entries and text would not squeeze or would it be possible to replace the bottom x-text by a number and have a legend with 1:means xx 2:means yyy
I find GridSpec helpful for subplot arrangements, see this demo at matplotlib.
import matplotlib.pyplot as plt
from matplotlib.gridspec import GridSpec
import pandas as pd
N=24
inst_memory = pd.DataFrame({'MEMORY_SET_TYPE': np.random.randint(0,3,N),
'USED_MB': np.random.randint(0,1000,N)})
set_memory = pd.DataFrame({'MEMORY_POOL_USED': np.random.randint(0,1000,N),
'POOL_TYPE': np.random.randint(0,10,N)})
fig = plt.figure()
gs = GridSpec(1, 2, width_ratios=[1, 2],wspace=0.3)
ax1 = fig.add_subplot(gs[0])
ax2 = fig.add_subplot(gs[1])
ax1.bar(inst_memory['MEMORY_SET_TYPE'], inst_memory['USED_MB'], alpha = 0.5, color = 'r')
ax2.bar(set_memory['POOL_TYPE'], set_memory['MEMORY_POOL_USED'], alpha = 0.5, color = 'g')
You may need to adjust width_ratios and wspace to get the desired layout.
Also, rotating the text in x-axis might help, some info here.

How to get position of the bottom left corner in an matplotlib axes?

I would like to have the bottom left corner of my plotting area, to add an red dot there (after it is saved to a ".png" and loaded somewhere else.)
Here is the plot:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
plt.rcParams['figure.dpi'] = 300
x= np.arange(0,10,1)
fig, ax = plt.subplots(1, figsize=(1,1))
plt.plot(x,x)
Now I save the plot and reload it, further I want to see some properties of positions:
fig.savefig( "bla.png",bbox_inches='tight' )
image1=plt.imread("bla.png")
print("(y, x, color) = " , image1.shape)
print("Position =" , ax.get_position())
Output:
(y, x, color) = (357, 348, 4)
Position = Bbox(x0=0.125, y0=0.125, x1=0.9, y1=0.88)
ok, now I try to add a red dot at the position, where my plotting area is starting:
red = [255,0,0,1]
blue= [0, 0 ,255,1]
image2= image1.copy()
(y,x) = (0,0)
image2[y:y+3,x:x+3] = blue
(y,x) = (357,348)
image2[y-3:y,x-3:x] = blue
yp = int(0.125*357)
xp = int(0.125*348)
(y,x) = (yp,xp)
image2[y-3:y+3,x-3:x+3] = red
plt.axis('off')
plt.imshow(image2)
This is not what was expected.
Do you have any idea how I can get this left bottom corner?

Shapefile zooming to plot with geopandas

I have a shapefile of Italy and I'm plotting the GIS Data on it. Thing is I've got a small trajectory of a bus going within a city(Rome) and when i`m plotting, it appears like 1 dot. I guess because of my map.
How to zoom the map (.shp) ?
street_map = gpd.read_file("roads.shp")
...
...
fig,ax = plt.subplots(figsize = (20,15))
street_map.plot(ax = ax, alpha = 0.4, color = "grey")
geo_df[geo_df['Perc_'] > 25].plot(ax = ax, markersize = 20, color = "blue",
marker = "o", label = "Neg")
geo_df[geo_df['Perc_'] < 25].plot(ax = ax, markersize = 20, color = "red",
marker = "^", label = "Pos")
plt.legend(prop={'size':15})
Based on your 1st image, it is possible to get the zoom-in plot by specifying proper x and y limits.
...
ax.set_ylim([40.4, 47.2])
ax.set_xlim([7.0, 14.4])
(Place this code before plt.legend().
Hope this is useful.