Search code examples
pythonmatplotlibimread

How to properly combine a log scale plot with a background image?


I produced data for a curve, stored in a list. Then I took 1000 random samples out of that data. The y-axis shows my data and the x-axis I the formula output. Up till here everything is fine. The problem starts when I want to plot my data onto an existing image.

Figure

As you can see, my x-axis and y-axis are log scale and lower than 1. I have looked for answers and found that I could use FuncFormatter. However, it doesn't work for me, as I need to plot my data with log-scale. When I use simply plt.xscale('log') the figure looks like this:

Output figure with log-scale

image

Output figure without log-scale

image

import matplotlib.pyplot as plt
import numpy as np

#Producing some data and put them in a list named listGercek 

xekseni2 = []
data = random.sample(listGercek, 1000)

for teta in data:
    olasılık = listGercek.index(teta)/100000
    xekseni2.append(olasılık)

im = plt.imread('figure.png')
xmin, xmax, ymin, ymax = (0.001, 1, 0.01, 1)
aspect = im.shape[0] / im.shape[1] * (xmax-xmin)/(ymax-ymin)
plt.imshow(im, zorder=0, extent=[1e-3, 1e0, 1e-2, 1e0], aspect=aspect)
plt.yscale('log')
plt.xscale('log')
plt.xlabel('P')
plt.ylabel(r'$\tau_{c}^{*}$')
plt.plot(xekseni2, data, "ro", marker="o", markersize=1, label="Present Work")
plt.axis([xmin, xmax, ymin, ymax])
plt.legend()
plt.show()

Some data points as asked:

y:0.09141346037829952, 0.06969760102294438, 0.0473781028644485, 0.059295628198887916, 0.0571418702849134, 0.04050307759274645, 0.08088991113201109, 0.03746878506083184, 0.13583224333004337, 0.03269066677698429, 0.06918929672995293, 0.06040315211901601, 0.05772815718352134, 0.07361582566248871, 0.06212973486945907, 0.03283216378016191, 0.14407484921136313, 0.02266323793619761, 0.04439409523587426, 0.055067724315696655,

x:0.81136, 0.67958, 0.43465, 0.58106, 0.55695, 0.33327, 0.75665, 0.2849, 0.93146, 0.20716, 0.6752, 0.59276, 0.56391, 0.70997, 0.6097, 0.20941, 0.94315, 0.06609, 0.39222, 0.53361,


Solution

  • The problem is that changing to log scale axes, the image also gets deformed. So, the image needs a linear scale.

    One approach is to draw the image on a secondary x and y axis, with linear scale. And to draw the curve on the original axes with double log scale.

    The primary axes get a zorder to be in front of the secondary axes. So the new curve comes on top of everything. To make the image visible, the facecolor of the primary axes need to be transparent (color string 'none' instead of the default 'white').

    To hide the secondary axis, also the intermediate twinx() needs to be hidden. (See the remarks under this post.)

    To get the aspect ratio correct, only aspect='auto' seems to work as desired, setting the limits the same for both the primary and secondary axes.

    import matplotlib.pyplot as plt
    import numpy as np
    
    xekseni2 = [0.81136, 0.67958, 0.43465, 0.58106, 0.55695, 0.33327, 0.75665, 0.2849, 0.93146, 0.20716, 0.6752, 0.59276,
                0.56391, 0.70997, 0.6097, 0.20941, 0.94315, 0.06609, 0.39222, 0.53361]
    data = [0.09141346037829952, 0.06969760102294438, 0.0473781028644485, 0.059295628198887916, 0.0571418702849134,
            0.04050307759274645, 0.08088991113201109, 0.03746878506083184, 0.13583224333004337, 0.03269066677698429,
            0.06918929672995293, 0.06040315211901601, 0.05772815718352134, 0.07361582566248871, 0.06212973486945907,
            0.03283216378016191, 0.14407484921136313, 0.02266323793619761, 0.04439409523587426, 0.055067724315696655]
    
    xmin, xmax, ymin, ymax = (0.001, 1, 0.01, 1)
    
    ax = plt.gca()
    ax.plot(xekseni2, data, "ro", marker="o", markersize=1, label="Present Work")
    ax.set_yscale('log')
    ax.set_xscale('log')
    ax.set_xlabel('P')
    ax.set_ylabel(r'$\tau_{c}^{*}$')
    ax.set_xlim(xmin, xmax)
    ax.set_ylim(ymin, ymax)
    ax.legend(loc='lower right')
    ax.set_zorder(2)
    ax.set_facecolor('none')
    
    ax_tw_x = ax.twinx()
    ax_tw_x.axis('off')
    ax2 = ax_tw_x.twiny()
    
    im = plt.imread('figure.png')
    ax2.imshow(im, extent=[xmin, xmax, ymin, ymax], aspect='auto')
    ax2.axis('off')
    plt.show()
    

    resulting plot

    PS: Another approach could be to draw everything on the same axes but convert the curve coordinates manually to log space. The axes then are linear for both the image as well as the curve. The visual aspect of the axes could be adapted to mimic log axes, as explored in this post.