Home > OS >  Setting the aspect ratio of a Circle Patch in Matplotlib doesn't result in predicted ratio
Setting the aspect ratio of a Circle Patch in Matplotlib doesn't result in predicted ratio

Time:10-24

I want to add a bunch of circles with equal aspect ratio to my plot, but the ratio of the datalimits of my x and y axis aren't equal. Therefore I tried to just create a regular circle of radius 1 first (which will be stretched if the datalimits ratio is, say 2:1).

from matplotlib.patches import Circle

circle = Circle(xy,0.5,transform=ax.transAxes) # if r=0.5, then the ellipsis will touch the borders of the x and y axis.

Then I find out the aspect ratio of my datalimits so I can set the width of circle to the aspect ratio.

def get_data_aspect(ax):

    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    return (xlim[1]-xlim[0])/(ylim[1]-ylim[0])

data_aspect = get_data_aspect(ax)

According to my logic, if I divide the width of my squished circle by this aspect ratio, it should compensate the streched width of my circle and make it an actual circle:

circle.set(width=circle.width/data_aspect)

Then I can scale the circle with any factor I want to make it bigger or smaller

a = 0.1
circle.set(width=circle.width*a, height=circle.height*a)

Yet for some reason this does not work and I can't figure out why. Since matplotlib.patches (as far as I know), never call plt.autoscale() when created or changed, resizing of the datalimits shouldn't occur. I don't know what else to try or why my math is wrong.

Full code:

def get_data_aspect(ax):

    xlim = ax.get_xlim()
    ylim = ax.get_ylim()

    return (xlim[1]-xlim[0])/(ylim[1]-ylim[0])



def add_Circle(ax,xy,d,asp):

    asp_is = get_data_aspect(ax)

    circle = Circle(xy,0.5,transform=ax.transAxes)
    circle.set(width=circle.width/asp_is)
    
    circle = ax.add_patch(circle)

    return circle



def LJ_hamonic_plot():
    
    fig, ax = plt.subplots(figsize=(7,4)) 

    sig,eps = 3,1
    
    r_min = 2**(1/6) * sig
    k = (36 * eps * 2**(2/3))/sig**2

    r = np.linspace(r_min-1,r_min 3,1000)

    U_LJ = 4 * eps *((sig/r)**12 - (sig/r)**6)
    U_harm = k/2 * (r - r_min)**2 -eps

    start, end = -1,3
    r_new = np.linspace(start,end,1000)

    ax.plot(r_new, U_LJ, label=r'$\mathrm{harmonisch}$', color='blue', linewidth=0.7)
    # ax.set_ylim(*ax.get_ylim())
    ax.set_ylim(-1.2,0.5)
    ax.set_xlim(-0.76,end)
    ax.plot(r_new, U_harm, label=r'$\mathrm{anharmonisch}$', color='red', linewidth=0.7)
    
    ax.set_ylabel(r'$V(\Delta r)$', size=12, labelpad=7)
    ax.set_xlabel(r'$\Delta r = r - r_0$', size=12)
    
    ax.xaxis.set_minor_locator(AutoMinorLocator())
    ax.xaxis.set_major_locator(MaxNLocator(6))
    
    ax.yaxis.set_minor_locator(AutoMinorLocator())
    ax.yaxis.set_major_locator(MaxNLocator(6))
    
    ax.xaxis.set_ticks_position('bottom')
    
    ax.tick_params(axis='both',which='both',labelsize=12, direction='in')
    ax.legend(prop={'size': 12})

    # make molecule model
    asp = get_data_aspect(ax)
    print(asp)
    c1 = add_Circle(ax,(0.5,0.5),1/2,1)


    plt.show()
        
    # plt.savefig('harmonic_fit.svg',dpi=500, bbox_inches='tight')

LJ_hamonic_plot()

CodePudding user response:

Try trans = (fig.dpi_scale_trans transform.ScaledTranslation(x, y, ax.transData)) where x and y are x- and y-coordinates of the center of the circle while keeping the argument xy as (0, 0) in enter image description here

Please look at the function get_transform(x, y) in the code below which implements correct positioning of the circle and correct transformation required to plot the circle:

import matplotlib.pyplot as plt 
import numpy as np
from matplotlib.ticker import MaxNLocator, AutoMinorLocator
from matplotlib.patches import Circle
import matplotlib.transforms as transform

def LJ_hamonic_plot():
    
    fig, ax = plt.subplots(figsize=(7,4)) 

    sig,eps = 3,1
    
    r_min = 2**(1/6) * sig
    k = (36 * eps * 2**(2/3))/sig**2

    r = np.linspace(r_min-1,r_min 3,1000)

    U_LJ = 4 * eps *((sig/r)**12 - (sig/r)**6)
    U_harm = k/2 * (r - r_min)**2 -eps

    start, end = -1,3
    r_new = np.linspace(start,end,1000)

    ax.plot(r_new, U_LJ, label=r'$\mathrm{harmonisch}$', color='blue', linewidth=0.7)
    # ax.set_ylim(*ax.get_ylim())
    ax.set_ylim(-1.2,0.5)
    ax.set_xlim(-0.76,end)
    ax.plot(r_new, U_harm, label=r'$\mathrm{anharmonisch}$', color='red', linewidth=0.7)
    
    ax.set_ylabel(r'$V(\Delta r)$', size=12, labelpad=7)
    ax.set_xlabel(r'$\Delta r = r - r_0$', size=12)
    
    ax.xaxis.set_minor_locator(AutoMinorLocator())
    ax.xaxis.set_major_locator(MaxNLocator(6))
    
    ax.yaxis.set_minor_locator(AutoMinorLocator())
    ax.yaxis.set_major_locator(MaxNLocator(6))
    
    ax.xaxis.set_ticks_position('bottom')
    
    ax.tick_params(axis='both',which='both',labelsize=12, direction='in')
    ax.legend(prop={'size': 12})


    def get_transform(x, y):
        # x and y are the x- and y-coordinates of the center of the circle.
        trans = (fig.dpi_scale_trans   transform.ScaledTranslation(x, y, ax.transData))
        return(trans)

    # Plot some circles
    for i, j in zip(np.arange(-3, 10), np.arange(-5, 8)):
        circle = Circle((0.0, 0.0), 0.5/(abs(int(i))   2) , transform=get_transform(i/5, j/15), alpha=0.75)
        ax.add_patch(circle)

    plt.show()

LJ_hamonic_plot()

For more details, please check the documentation for matplotlib's advanced transformations to understand how you can create a circle while maintaining the aspect ratio of the figure.

  • Related