How to plot multiple subplots over a background image with matplotlib? - python

I want to plot some scatter plots over the map of a country (an image). The idea is to depict the data visualization of the area at which the plot is plotted.
So, this is how I plot the image of the map of USA, where the circles I have drawn towards the top left and the middle are where I would like to display my scatter plots:
import numpy as np
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(10,15))
im = plt.imread("usa-states-map.jpg")
implot = plt.imshow(im, extent=[0, 200, 0, 150])
# A circle in the upper left region
theta=np.linspace(0,2*np.pi,50)
faux_radius = 15
z=np.cos(theta)*faux_radius + 45
t=np.sin(theta)*faux_radius + 130
plt.plot(z,t)
# A circle in the middle region
theta=np.linspace(0,3*np.pi,50)
faux_radius = 15
z=np.cos(theta)*faux_radius + 100
t=np.sin(theta)*faux_radius + 80
plt.plot(z,t)
This plots the image like so:
I proceed to plot the scatter plots like so:
import numpy as np
import matplotlib.pyplot as plt
fig=plt.figure(figsize=(10,15))
im = plt.imread("usa-states-map.jpg")
implot = plt.imshow(im, extent=[0, 200, 0, 150])
# A circle in the upper left region
theta=np.linspace(0,2*np.pi,50)
faux_radius = 15
z=np.cos(theta)*faux_radius + 45
t=np.sin(theta)*faux_radius + 130
plt.plot(z,t)
# A circle in the middle region
theta=np.linspace(0,3*np.pi,50)
faux_radius = 15
z=np.cos(theta)*faux_radius + 100
t=np.sin(theta)*faux_radius + 80
plt.plot(z,t)
# Scatter plot 1
ax1 = plt.subplot(2,2,1)
ax1.scatter(x_1_a, y_1_a, marker="s")
ax1.scatter(x_1_b, y_1_b, marker="o")
# Scatter plot 2
ax1 = plt.subplot(2,2,2)
ax1.scatter(x_2_a, y_2_a, marker="s")
ax1.scatter(x_2_a, y_2_b, marker="o")
But the output it produces does not display the background image, and only plots the scatter plots:
[]
I even tried using zorder which is supposed to tell matplotlib which plot should come on top and which on bottom, but to no avail - it produces the same output as above:
implot = plt.imshow(im, extent=[0, 200, 0, 150], zorder=1)
...
...
...
# Scatter plot 1
ax1 = plt.subplot(2,2,1)
ax1.scatter(x_1_a, y_1_a, marker="s", zorder=2)
ax1.scatter(x_1_b, y_1_b, marker="o", zorder=2)
# Scatter plot 2
ax1 = plt.subplot(2,2,2)
ax1.scatter(x_2_a, y_2_a, marker="s", zorder=3)
ax1.scatter(x_2_a, y_2_b, marker="o", zorder=3)
How do I fix this to get the desired result? I don't even need the 2 circles to be present on the map actually - those were just to illustrate where I would like to plot the 2 scatter plots. Thanks.

I was able to solve the problem using the plt.axes suggestion in the comments:
from mpl_toolkits.axes_grid.inset_locator import inset_axes
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10, 15),facecolor='white')
ax = fig.add_axes([0, 0, 1, 1])
ax.axis('off')
im = plt.imread("usa-states-map.jpg")
implot = plt.imshow(im)
plt.xticks([])
plt.yticks([])
# this is an inset axes over the main axes for the top left region
a = plt.axes([.2, .6, .2, .1], facecolor='w')
plt.scatter(x_1_a, y_1_a, marker="s")
plt.scatter(x_1_b, y_1_b, marker="o")
plt.legend(['%.2f%%' %(100*len(x_1_a)/(len(x_1_a)+len(y_1_a))), '%.2f%%' %(100*len(y_1_a)/(len(x_1_a)+len(y_1_a)))], loc='upper right');
# this is an inset axes over the main axes for the middle region
a = plt.axes([.45, .45, .2, .1], facecolor='w')
plt.scatter(x_2_a, y_2_a, marker="s")
plt.scatter(x_2_b, y_2_b, marker="o")
plt.legend(['%.2f%%' %(100*len(x_2_b)/(len(x_2_b)+len(y_2_b))), '%.2f%%' %(100*len(y_2_b)/(len(x_2_b)+len(y_2_b)))], loc='upper right');
plt.show()

Related

How to increase plottable space above a subplot in matplotlib?

I am currently making a plot on matplotlib, which looks like below.
The code for which is:
fig, ax1 = plt.subplots(figsize=(20,5))
ax2 = ax1.twinx()
# plt.subplots_adjust(top=1.4)
ax2.fill_between(dryhydro_df['Time'],dryhydro_df['Flow [m³/s]'],0,facecolor='lightgrey')
ax2.set_ylim([0,10])
AB = ax2.fill_between(dryhydro_df['Time'],[12]*len(dryhydro_df['Time']),9.25,facecolor=colors[0],alpha=0.5,clip_on=False)
ab = ax2.scatter(presence_df['Datetime'][presence_df['AB']==True],[9.5]*sum(presence_df['AB']==True),marker='X',color='black')
# tidal heights
ax1.plot(tide_df['Time'],tide_df['Tide'],color='dimgrey')
I want the blue shaded region and black scatter to be above the plot. I can move the elements above the plot by using clip_on=False but I think I need to extend the space above the plot to do visualise it. Is there a way to do this? Mock-up of what I need is below:
You can use clip_on=False to draw outside the main plot. To position the elements, an xaxis transform helps. That way, x-values can be used in the x direction, while the y-direction uses "axes coordinates". ax.transAxes() uses "axes coordinates" for both directions.
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
dates = pd.date_range('2018-07-01', '2018-07-31', freq='H')
xs = dates.to_numpy().astype(float)
ys = np.sin(xs * .091) * (np.sin(xs * .023) ** 2 + 1)
fig, ax1 = plt.subplots(figsize=(20, 5))
ax1.plot(dates, ys)
ax1.scatter(np.random.choice(dates, 10), np.repeat(1.05, 10), s=20, marker='*', transform=ax1.get_xaxis_transform(),
clip_on=False)
ax1.plot([0, 1], [1.05, 1.05], color='steelblue', lw=20, alpha=0.2, transform=ax1.transAxes, clip_on=False)
plt.tight_layout() # fit labels etc. nicely
plt.subplots_adjust(top=0.9) # make room for the additional elements
plt.show()

How to rotate a Subplot by 45 degree in Matplotlib?

I am trying to explore a subplot 2 plots with square in shape rotated by 45 degree.
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
data = np.random.rand(10, 10) * 20
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
fig, ax= plt.subplots(1,2)
ax[0].imshow(data, cmap=cmap, norm=norm)
# draw gridlines
ax[0].grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax[0].set_xticks(np.arange(-.5, 10, 1));
ax[0].set_yticks(np.arange(-.5, 10, 1));
ax[1].imshow(data, cmap=cmap, norm=norm)
# draw gridlines
ax[1].grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax[1].set_xticks(np.arange(-.5, 10, 1));
ax[1].set_yticks(np.arange(-.5, 10, 1));
plt.show()
Actual Result is :-
I want to rotate individual plot by 45 degree. Something like:-
I am trying to find in Matplotlib Documentation. Still not getting. Any help?
Please note this is NOT DUPLICATE OF
Is there a way to rotate a matplotlib plot by 45 degrees?
The mentioned URL is for a plot. and the solution is to rotate IMAGE. However this is pertaining to Subplot. I want to rotate PLOT not image as whole.
Based on this link and documentation about floating_axes, you can try something like this:
from mpl_toolkits.axisartist.grid_finder import DictFormatter
import matplotlib.pyplot as plt
from matplotlib.transforms import Affine2D
import mpl_toolkits.axisartist.floating_axes as floating_axes
from matplotlib import colors
import numpy as np
def setup_axes1(fig, rect, angle):
tr = Affine2D().scale(2, 2).rotate_deg(angle)
#We create dictionarys to keep the xticks and yticks after the rotation
dictio={i:str(val) for i,val in enumerate(np.arange(-.5, 10, 1).tolist())}
reversedictio={i:dictio[val] for i,val in enumerate(list(reversed(sorted(dictio.keys()))))}
grid_helper = floating_axes.GridHelperCurveLinear(
tr, extremes=(-0.5, 9.5,-0.5, 9.5), tick_formatter1= DictFormatter(dictio),
tick_formatter2=DictFormatter(reversedictio))
ax1 = floating_axes.FloatingSubplot(fig, rect, grid_helper=grid_helper)
fig.add_subplot(ax1)
aux_ax = ax1.get_aux_axes(tr)
grid_helper.grid_finder.grid_locator1._nbins = 10 #Number of rows
grid_helper.grid_finder.grid_locator2._nbins = 10 #Number of columns
return aux_ax
fig1, axes=plt.subplots(2,figsize=(20,20))
plt.rcParams.update({'font.size': 27})
#We erase the first previous axes
fig1.delaxes(axes[0])
fig1.delaxes(axes[1])
data = np.random.rand(10, 10) * 20
#We create the floating_axes
ax0 = setup_axes1(fig1, 121,-45)
ax1 = setup_axes1(fig1, 122,-45)
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
ax0.imshow(data, cmap=cmap, norm=norm,interpolation="nearest")
# draw gridlines
ax0.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
ax1.imshow(data, cmap=cmap, norm=norm,interpolation="nearest")
# draw gridlines
ax1.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
plt.show()
Output:
Or, as an other alternative, I found a "tricky" way to do it, and it's about catching the figures in the buffer, rotate them -45 degrees, and then merge them into a single image, and since you have the same two images, you can try something like this:
import matplotlib
import io
from PIL import Image
import matplotlib.pyplot as plt
from matplotlib import colors
import numpy as np
##PLOTING THE FIGURE##
data = np.random.rand(10, 10) * 20
# create discrete colormap
cmap = colors.ListedColormap(['red', 'blue','green'])
bounds = [0,5,10,15]
norm = colors.BoundaryNorm(bounds, cmap.N)
#We change style values to get the image with better quality
plt.rcParams.update({'font.size': 46})
plt.figure(figsize=(20,20))
plt.imshow(data, cmap=cmap, norm=norm)
# draw gridlines
plt.grid(which='major', axis='both', linestyle='-', color='k', linewidth=0)
plt.gca().set_xticks(np.arange(-.5, 10, 1));
plt.gca().set_yticks(np.arange(-.5, 10, 1));
##SAVING THE FIGURE INTO AN IMAGE##
#We save the current figure as a Image
buf = io.BytesIO()
plt.savefig(buf, format='png',bbox_inches='tight')
buf.seek(0)
im = Image.open(buf) #We open the current image saved in the buffer
#We rotate the image and fill the background with white
img_01=im.rotate(-45, Image.NEAREST, expand = 1, fillcolor = (255,255,255))
buf.close()
##MERGING THE TWO FIGURES##
new_im = Image.new('RGB', (2*img_01.size[0]+20,img_01.size[1]), 'white')
mouse_mask = img_01.convert('RGBA')
new_im.paste(img_01, (0,0))
new_im.paste(img_01, (img_01.size[0]+8,0))
new_im.save("merged_images.png", 'PNG') #Important(just to clarify): save the image, since the buffer is renewed every time you run the script
new_im.show()
Output:
I helped myself with these links:
How-to-merge-images-with-same-size-using-the-python-3-module-pillow
how-to-save-a-pylab-figure-into-in-memory-file-which-can-be-read-into-pil-image
python-pillow-rotate-image-90-180-270-degrees
specify-image-filling-color-when-rotating-in-python-with-pil-and-setting-expand

matplotlib: ordering of zoomed axes objects

I am building a figure with a primary axis that is a scatter plot and a zoomed axis which focuses on a particular region of the primary axis, both of which have gridlines. When I place the zoomed axis as an inset, it "covers" up some of the primary axis data. I want to be able to show the primary axis data (zorder=100) through the zoomed axis, so I set the zoomed axis to be transparent (alpha=0). Finally, I want the primary axis gridlines to "cut-off" when they meet the zoomed axis (zorder=10) but I want to show the zoomed axis gridlines (zorder=50). Is this possible? Below is my attempt:
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
import numpy as np
fig = plt.figure(figsize=(10,7.5))
gs = matplotlib.gridspec.GridSpec(1, 2, width_ratios=[20,1], height_ratios=[1])
ax = plt.subplot(111)
## data
xx = np.linspace(1,100,num=100) + 20 * np.random.normal(0,1,100)
yy = np.linspace(1,100,num=100) + 10 * np.random.normal(0,1,100)
## scatter
sc = ax.scatter(xx, yy, s=250, alpha=0.35, zorder=100)
ax.plot(np.linspace(-100,200,301), np.linspace(-100,200,301),)
ax.set_xlim((0, 100))
ax.set_ylim((0, 100))
ax.grid(linestyle="--", zorder=10)
## zoom
axins = zoomed_inset_axes(ax, 2, loc="upper left")
scins = axins.scatter(xx, yy, s=100, alpha=0.35, zorder=50, marker=".", c="red")
axins.plot(np.linspace(-100,200,301), np.linspace(-100,200,301), c="red")
axins.set_xlim((70, 90))
axins.set_ylim((70, 90))
mark_inset(ax, axins, loc1=1, loc2=4, fc="none", ec="0.5")
axins.grid(linestyle="--", zorder=50)
plt.show()
In particular, one of the blue data points near x=80 gets cut off. I can set axins.patch.set_alpha(0.0), but then it doesn't remove the primary grid lines.
One option is indeed to put a white patch (white rectangle) in ax at the position where axins lives and set that patches' zorder to higher than the one from the gridlines, but lower than the one from the scatter.
# Set axins' background patch invisible
axins.patch.set_visible(False)
# Create a new patch at the position of the axins axes.
rect = matplotlib.patches.Rectangle((0,0), 1,1,
fill=True, facecolor="white", edgecolor="red",zorder=25,
transform=axins.transAxes)
ax.add_patch(rect)
Thanks to #ImportanceOfBeingErnest for the suggestion. It works to add a rectangle with an intermediate zorder to ax per the following (where I've left the red outline of the rectangle):
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import mark_inset
from mpl_toolkits.axes_grid1.inset_locator import zoomed_inset_axes
import matplotlib.patches as patches
import numpy as np
## data
xx = np.linspace(1,100,num=100) + 20 * np.random.normal(0,1,100)
yy = np.linspace(1,100,num=100) + 10 * np.random.normal(0,1,100)
## fig
fig = plt.figure(figsize=(10,7.5))
gs = matplotlib.gridspec.GridSpec(1, 2, width_ratios=[20,1], height_ratios=[1])
ax = plt.subplot(111)
## scatter
sc = ax.scatter(xx, yy, s=250, alpha=0.35, zorder=100)
ax.plot(np.linspace(-100,200,301), np.linspace(-100,200,301))
ax.set_xlim((0, 100))
ax.set_ylim((0, 100))
ax.grid(linestyle="--", zorder=10)
ax.patches.extend([patches.Rectangle((0.2, 0.6), 0.4, 0.4,
fill=True, facecolor="white", edgecolor="red",zorder=25,
transform=ax.transAxes, figure=ax)])
## zoom
axins = zoomed_inset_axes(ax, 2,
bbox_to_anchor=(0.6, 1.0, 0.0, 0.0),
bbox_transform=ax.transAxes)
scins = axins.scatter(xx, yy, s=100, alpha=0.35, zorder=50, marker=".", c="red")
axins.plot(np.linspace(-100,200,301), np.linspace(-100,200,301), c="red")
axins.set_xlim((70, 90))
axins.set_ylim((70, 90))
axins.patch.set_alpha(0.0)
mark_inset(ax, axins, loc1=1, loc2=4, fc="none", ec="0.5")
axins.grid(linestyle="--", zorder=50)
plt.show()

How do you add a background image on a scatter plot in loglog axes in python?

How do you add a Background Image on a scatter plot with loglog axes? My problem is that the background image is also rescaled in log style which I don't want.
ax3 = fig2.add_subplot(1, 1, 1)
pathimage=directory+'\Robertson.PNG'
img = mpimg.imread(directory+'\Robertson.PNG')
ax3.scatter(rf_layer,qc_layer)
ax3.set_title(filename, y=1.1,fontsize=12)
ax3.set_yscale('log')
ax3.set_xscale('log')
ax3.legend(Legend,loc=9, bbox_to_anchor=(0.5, -0.2),ncol=len(layerdepth))
ax3.set_ylim([1, 100])
ax3.set_xlim([0.1, 10])
ax3.set_xlabel('Rf in %')
ax3.set_ylabel('qc in MPa')
ax3.imshow(img,extent=[0.1,10,1,100])
You can do this with a twin Axes, which is not set to loglog scale. In this case, we want to make both twin x and y axes, so we can stack the commands, as shown here:
ax4 = ax3.twinx().twiny()
An alternative is to just create a new Axes instance in the same position as the first (from #ImportanceOfBeingErnest in the comments). For example:
ax4 = fig.add_subplot(111, label="ax4")
We also need to make ax3 transparent so we can see through it to the image below (facecolor='None').
We also need to set the zorder to ax3 is on top of ax4.
Here is a working example:
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
import numpy as np
# Dummy data
rf_layer = 0.1 + np.random.rand(20) * 9.9
qc_layer = 1. + np.random.rand(20) * 99.
fig2 = plt.figure()
# Make ax3 transparent so we can see image behind
ax3 = fig2.add_subplot(1, 1, 1, facecolor='None')
pathimage='./stinkbug.png'
img = mpimg.imread(pathimage)
ax3.scatter(rf_layer, qc_layer)
ax3.set_title('my title', y=1.1, fontsize=12)
ax3.set_yscale('log')
ax3.set_xscale('log')
ax3.set_ylim([1, 100])
ax3.set_xlim([0.1, 10])
ax3.set_xlabel('Rf in %')
ax3.set_ylabel('qc in MPa')
# Create second axes
ax4 = ax3.twinx().twiny()
# Or alternatively
# ax4 = fig.add_subplot(111, label="ax4")
# Add image to twin axes
ax4.imshow(img)
# Fix zorder so ax3 on top of ax4
ax3.set_zorder(10)
ax4.set_zorder(1)
# Turn off ticks from twin axes
ax4.set_yticks([])
ax4.set_xticks([])
plt.show()

Color axis spine with multiple colors using matplotlib

Is it possible to color axis spine with multiple colors using matplotlib in python?
Desired output style:
You can use a LineCollection to create a multicolored line. You can then use the xaxis-transform to keep it fixed to the xaxis, independent of the y-limits. Setting the actual spine invisible and turning clip_on off makes the LineCollection look like the axis spine.
import matplotlib.pyplot as plt
from matplotlib.collections import LineCollection
import numpy as np
fig, ax = plt.subplots()
colors=["b","r","lightgreen","gold"]
x=[0,.25,.5,.75,1]
y=[0,0,0,0,0]
points = np.array([x, y]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
lc = LineCollection(segments,colors=colors, linewidth=2,
transform=ax.get_xaxis_transform(), clip_on=False )
ax.add_collection(lc)
ax.spines["bottom"].set_visible(False)
ax.set_xticks(x)
plt.show()
Here is a slightly different solution. If you don't want to recolor the complete axis, you can use zorder to make sure the colored line segments are visible on top of the original axis.
After drawing the main plot:
save the x and y limits
draw a horizontal line at ylims[0] between the chosen x-values with the desired color
clipping should be switched off to allow the line to be visible outside the strict plot area
zorder should be high enough to put the new line in front of the axes
the saved x and y limits need to be put back, because drawing extra lines moved them (alternatively, you might have turned off autoscaling the axes limits by calling plt.autoscale(False) before drawing the colored axes)
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0, 20, 100)
for i in range(10):
plt.plot(x, np.sin(x*(1-i/50)), c=plt.cm.plasma(i/12))
xlims = plt.xlim()
ylims = plt.ylim()
plt.hlines(ylims[0], 0, 10, color='limegreen', lw=1, zorder=4, clip_on=False)
plt.hlines(ylims[0], 10, 20, color='crimson', lw=1, zorder=4, clip_on=False)
plt.vlines(xlims[0], -1, 0, color='limegreen', lw=1, zorder=4, clip_on=False)
plt.vlines(xlims[0], 0, 1, color='crimson', lw=1, zorder=4, clip_on=False)
plt.xlim(xlims)
plt.ylim(ylims)
plt.show()
To highlight an area on the x-axis, also axvline or axvspan can be interesting. An example:
from matplotlib import pyplot as plt
import numpy as np
x = np.linspace(0, 25, 100)
for i in range(10):
plt.plot(x, np.sin(x)*(1-i/20), c=plt.cm.plasma(i/12))
plt.axvspan(10, 20, color='paleturquoise', alpha=0.5)
plt.show()

Categories