Plotting a cropped background image on a matplotlib graph - python

I'm trying to plot a simple line plot and insert a background image to a plot.
An example pic (with cat.jpg and dog.jpd):
At the moment I have a code that plots the line (from a pandas dataframe) and places the images into figure. However the images and the line plot do not 'interact' at all.
fig, ax = plt.subplots(figsize=(15,10))
cat = np.array(Image.open('cat.jpg'))
dog = np.array(Image.open('dog.jpg'))
ax.imshow(cat, extent=[0, 10, 0, 18], aspect='auto', cmap='gray',alpha=0.75)
ax.imshow(dog, extent=[10, 20, 0, 18], aspect='auto', cmap='gray',alpha=0.75)
ax.plot(df['Series'],color='#3cb8fb',alpha=0.95,linewidth=3.0)
plt.show()

You can use plt.fill_between to create a polygon that covers the area between the origin and the line, then use the .set_clip_path method of each image object to display only the part of the image that falls within the polygon.
For example:
from matplotlib import pyplot as plt
from scipy.misc import lena
fig, ax = plt.subplots(1, 1)
x = np.linspace(0, 1, 10)
y = np.random.rand(10)
image = ax.imshow(lena(), cmap=plt.cm.gray, extent=[0, 1, 0, 1])
line = ax.plot(x, y, '-r', lw=2)
# invisible clipping polygon
poly = ax.fill_between(x, 0, y, visible=False)
clip = poly.get_paths()[0]
# you will need to do this separately for each of the images in your plot
image.set_clip_path(clip, transform=ax.transData)
plt.show()

Related

Overleaped heatmap doesn't show correctly

I have a heatmap plotted above an image (as shown on image link 1), with gaussian filter and normalize data. The main issue is that there is no value under 92 on the y axis, so the plot doesnt start on (0,0), instead start on (0,92). So, when I put together both pictures (heatmap and background image), there is an abrupt cut on the graph (as shown on 2nd link, where is the heatmap without background).
So, how can I extend the axis on the heatmap so it start on (0,0)?
Below is the code that I'm currently using to plot both images. Thanks!
import matplotlib.pyplot as plt
import matplotlib.image as mpimg
from scipy.ndimage.filters import gaussian_filter
import matplotlib.colors as mcolors
from sklearn.preprocessing import normalize
x = df['x_data']
y = df['y_data']
heatmap, xedges, yedges = np.histogram2d(x, y, bins = [800,600])
extent = [0, xedges[-1], yedges[0], yedges[-1]]
heatmap = normalize(heatmap)
heatmap = gaussian_filter(heatmap, 16)
colors = [(1,1-c,0,c) for c in np.linspace(0,1,100)]
cmapred = mcolors.LinearSegmentedColormap.from_list('mycmap', colors, N=5)
map_img = mpimg.imread('dir/to/background/image.png')
fig, ax = plt.subplots(figsize=(16.1, 9.1))
plt.imshow(map_img, extent=[0, 800, 0, 600], cmap = 'Greys_r')
plt.imshow(heatmap.T, extent = extent, origin = 'lower', cmap = cmapred, alpha = 0.7)
plt.ylim([0,600])
plt.xlim([0,800])
plt.show()
Image of heatmap + background: https://imgur.com/2vX6Bw6
Image of only heatmap: https://imgur.com/axMe7K7
You could add rows to your heat map manually. Maybe easier is to try setting the histogram bins explicitly?
bins=[np.arange(0, 800, 1), np.arange(0, 600, 1)]
heatmap, xedges, yedges = np.histogram2d(x, y, bins=bins)

Save only line without axes, labels and padding in matplotlib

I would like to save matplotlib line chart to transparent png image with aspect ratio 3:1 and without axes or labels. I need the line of the graph to start and end directly at the edge of the image (without any padding).
I found several similar topics, e. g. tight savefig without axes in matplotlib or Removing white space around a saved image in matplotlib, however neither advice helped.
Here is my code:
import matplotlib.pyplot as plt
x = np.arange(1, 10)
y = np.arange(51, 60)
plt.gca().set_axis_off()
plt.subplots_adjust(top=1, bottom=0, right=1, left=0, hspace=0, wspace=0)
plt.margins(0, 0)
plt.gca().xaxis.set_major_locator(plt.NullLocator())
plt.gca().yaxis.set_major_locator(plt.NullLocator())
fig = plt.figure(figsize=(9,3))
ax = fig.add_axes([0, 0, 1, 1], frameon=False)
ax.set_axis_off()
ax.plot(x, y)
# plt.savefig("result.png", format="png", transparent=True, `bbox_inches="tight", pad_inches=0) # Result image is empty.
plt.savefig("result.png", format="png", transparent=True)
plt.show()
Still, there is some padding in result image (there is white background to show padding, but in fact image is transparent):
Is there any way to achieve chart with no padding?
Here is a solution based on one of the question you added:
import matplotlib.pyplot as plt
import numpy as np
import os
x = np.arange(1, 10)
y = np.arange(51, 60)
plt.figure(figsize=(9,3))
plt.plot(x,y)
plt.gca().set_axis_off()
plt.subplots_adjust(top = 1, bottom = 0, right = 1, left = 0,
hspace = 0, wspace = 0)
plt.margins(0,0)
plt.savefig("myfig.png")
#os.system('convert myfig.png -trim myfig.png') #<- a quick workaround if you are on mac or Linux.
plt.show()
Output:

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

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

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()

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