Search code examples
pythonmatplotlibaxes

python/matplotlib - parasite twin axis scaling


Trying to plot a spectrum, ie, velocity versus intensity, with lower x axis = velocity, on the upper twin axis = frequency

The relationship between them (doppler formula) is

f = (1-v/c)*f_0 

where f is the resulting frequency, v the velocity, c the speed of light, and f_0 the frequency at v=0, ie. the v_lsr.

I have tried to solve it by looking at http://matplotlib.sourceforge.net/examples/axes_grid/parasite_simple2.html , where it is solved by

pm_to_kms = 1./206265.*2300*3.085e18/3.15e7/1.e5
aux_trans = matplotlib.transforms.Affine2D().scale(pm_to_kms, 1.)
ax_pm = ax_kms.twin(aux_trans)
ax_pm.set_viewlim_mode("transform")

my problem is, how do I replace the pm_to_kms with my function for frequency?

Anyone know how to solve this?


Solution

  • The solution I ended up using was:

    ax_hz = ax_kms.twiny()
    x_1, x_2 = ax_kms.get_xlim()
    # i want the frequency in GHz so, divide by 1e9
    ax_hz.set_xlim(calc_frequency(x_1,data.restfreq/1e9),calc_frequency(x_2,data.restfreq/1e9))
    

    This works perfect, and much less complicated solution.

    EDIT : Found a very fancy answer. EDIT2 : Changed the transform call according to the comment by @u55

    This basically involves defining our own conversion/transform. Because of the excellent AstroPy Units equivalencies, it becomes even easier to understand and more illustrative.

    from matplotlib import transforms as mtransforms
    import astropy.constants as co
    import astropy.units as un
    import numpy as np 
    import matplotlib.pyplot as plt 
    plt.style.use('ggplot')
    from mpl_toolkits.axes_grid.parasite_axes import SubplotHost 
    
    
    class Freq2WavelengthTransform(mtransforms.Transform): 
        input_dims = 1 
        output_dims = 1 
        is_separable = False 
        has_inverse = True 
    
        def __init__(self):
            mtransforms.Transform.__init__(self)
    
        def transform_non_affine(self, fr): 
            return (fr*un.GHz).to(un.mm, equivalencies=un.spectral()).value 
    
        def inverted(self): 
            return Wavelength2FreqTransform() 
    
    class Wavelength2FreqTransform(Freq2WavelengthTransform): 
        input_dims = 1 
        output_dims = 1 
        is_separable = False 
        has_inverse = True 
    
        def __init__(self):
            mtransforms.Transform.__init__(self)
    
        def transform_non_affine(self, wl): 
            return (wl*un.mm).to(un.GHz, equivalencies=un.spectral()).value 
    
        def inverted(self): 
            return Freq2WavelengthTransform() 
    
    
    
    aux_trans = mtransforms.BlendedGenericTransform(Wavelength2FreqTransform(), mtransforms.IdentityTransform()) 
    
    fig = plt.figure(2) 
    
    ax_GHz = SubplotHost(fig, 1,1,1) 
    fig.add_subplot(ax_GHz) 
    ax_GHz.set_xlabel("Frequency (GHz)") 
    
    
    xvals = np.arange(199.9, 999.9, 0.1) 
    
    # data, noise + Gaussian (spectral) lines
    data = np.random.randn(len(xvals))*0.01 + np.exp(-(xvals-300.)**2/100.)*0.5 + np.exp(-(xvals-600.)**2/400.)*0.5
    
    ax_mm = ax_GHz.twin(aux_trans) 
    ax_mm.set_xlabel('Wavelength (mm)') 
    ax_mm.set_viewlim_mode("transform") 
    ax_mm.axis["right"].toggle(ticklabels=False) 
    
    ax_GHz.plot(xvals, data) 
    ax_GHz.set_xlim(200, 1000) 
    
    plt.draw() 
    plt.show() 
    

    This now produces the desired results: enter image description here