I am trying to use an existing graph as a background for new data that I want to plot on top of the graph.
I have been able to do so when using a graph with all information contained within the axes and using the extent
parameter of plt.imshow
because then I just have to scale the image.
I would like to scale and shift the background graph. Replotting the background is not an option in the real use case.
Here is what I tried so far :
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
ax.plot([0, 5, 10], [8, 5, 12])
ax.set_xlim(0, 20)
ax.set_ylim(0, 15)
ax.set_title('Background graph')
fig.show()
fig.savefig('bg_graph.png')
plt.imshow()
to add the background graph and then superimpose my data.bg_img = plt.imread('bg_graph.png')
fig, ax = plt.subplots()
ax.imshow(bg_img, extent=[0,50,0,50])
ax.scatter([4.9, 5.2], [7, 4.9])
fig.show()
fig.savefig('result.png')
I have made a mockup of the expected result using Excel :
Is there a method to stretch a new graph onto existing axis (from an image) in order to plot new pieces of data ? I assume that the coordinates of the axis in the image are known or can be guessed through trial an error. One way to rephrase this is to say that I would like to stretch the new plot to the image and not the other way around.
We can follow this answer to a related question and adapt it to your needs (see code comments for explanations):
import matplotlib.pyplot as plt
bg_img = plt.imread('stackoverflow/bg.png') # TODO: Adjust as necessary
bg_width, bg_xlim, bg_ylim = 6, (0, 20), (0, 15)
# Create a figure with the same aspect ratio and scale as the image.
# This provides the axes in which we will plot our new data
figsize = (bg_width, bg_width * bg_img.shape[0] / bg_img.shape[1])
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=figsize)
axes.patch.set_alpha(0.0) # Make new figure's area transparent
axes.set_xlim(*bg_xlim) # Adjust limits to background's limits
axes.set_ylim(*bg_ylim)
axes.scatter([4.9, 5.2], [7, 4.9], color='red') # Plot our new data
# Optionally, turn off axes, as we already have them from
# the background and they will not match perfectly:
plt.axis('off')
background_ax = plt.axes([0, 0, 1, 1]) # Create dummy subplot for background
background_ax.set_zorder(-1) # Set background subplot behind the other
background_ax.imshow(bg_img, aspect='auto') # Show background image
plt.axis('off') # Turn off axes that surround the background
For me, using the background image that you shared and loading it as bg.png
results in the following plot:
Luckily, the layout of the whitespace in your background image seems to match Matplotlib's defaults. If that was not the case, however, we could use subplots_adjust()
on the foreground plot, together with a bit of trial and error, to make the axes of the foreground plot and background image align as perfectly as possible. In this case, I would initially leave the axes of the foreground plot turned on (and thus comment out the first plt.axis('off')
in the code above) to make adjustments easier.
To demonstrate this, I created a version of your background image with additional green padding (called bg_padded.png
in the code below), which looks as follows:
I then adjusted the code from above as follows:
import matplotlib.pyplot as plt
bg_img = plt.imread('stackoverflow/bg_padded.png') # TODO: Adjust as necessary
bg_width, bg_xlim, bg_ylim = 7.5, (0, 20), (0, 15)
# Create a figure with the same aspect ratio and scale as the image.
# This provides the axes in which we will plot our new data
figsize = (bg_width, bg_width * bg_img.shape[0] / bg_img.shape[1])
fig, axes = plt.subplots(nrows=1, ncols=1, figsize=figsize)
# Adjust padding of foreground plot to padding of background image
plt.subplots_adjust(left=.2, right=.82, top=.805, bottom=0.19)
axes.patch.set_alpha(0.0) # Make new figure's area transparent
axes.set_xlim(*bg_xlim) # Adjust limits to background's limits
axes.set_ylim(*bg_ylim)
axes.scatter([4.9, 5.2], [7, 4.9], color='red') # Plot our new data
# Optionally, turn off axes, as we already have them from
# the background and they will not match perfectly:
# plt.axis('off')
background_ax = plt.axes([0, 0, 1, 1]) # Create dummy subplot for background
background_ax.set_zorder(-1) # Set background subplot behind the other
background_ax.imshow(bg_img, aspect='auto') # Show background image
plt.axis('off') # Turn off axes that surround the background
Changes are:
bg_padded.png
rather than bg.png
(obviously);bg_width
to 7.5 to account for the increased size of the background image and, with it, for the relative decrease in size (e.g. of the fonts) in the foreground plot;plt.subplots_adjust(left=.2, right=.82, top=.805, bottom=0.19)
to adjust for the padding.This time, I also left the first plt.axis('off')
commented out, as mentioned above, to see and to show how well the axes of the background image and the foreground plot match. The result looks as follows: