Py-ser
Py-ser

Reputation: 2098

Plot with upper log-axis as a function of the lower linear-axis

I want to make a plot with linear x- and y-axis, plus a log top x-axis showing ticks as a function of the bottom x-axis. I am unsure on what to pass to the ticks though, or if it is more convenient to separately define the function to build the upper log-axis ticks (something like it is done here). I would like the ticks on the upper log-axis in steps of 0.1. This is a MWE:

from matplotlib.ticker import ScalarFormatter, FormatStrFormatter

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np

fig, ax1 = plt.subplots(1, figsize=(10,6))

ax1.set_ylabel(r'y axis')
ax1.set_xlabel(r'Linear axis')
ax1.set_ylim(0.1,1.)
ax1.set_xlim(0.1,1.5)

#Upper lox-axis
new_tick_locations = 
[np.log(i*1.e37/(2.*(3.809e8))) for i in np.arange(0.1, 10., 0.1)] #I should pass something else instead of arange
                                                  #I'd like the upper axis ticks in steps of 0.1 anyway

axup=ax1.twiny()
axup.set_xticks(new_tick_locations)

axup.set_xlabel(r'Log axis')
plt.show()

Upvotes: 2

Views: 519

Answers (2)

tdy
tdy

Reputation: 41327

Secondary axis

Update: It turns out this is much simpler with secondary_xaxis() instead of twiny(). You can use the functions param to specify the transform and inverse functions between the bottom and top axes:

import matplotlib.pyplot as plt
import numpy as np

fig, ax1 = plt.subplots(1, figsize=(10,6))

ax1.set_ylabel('y axis')
ax1.set_xlabel('Linear axis')
ax1.set_ylim(0.1, 1.)
ax1.set_xlim(0.1e-9, 1.5e-9)

# secondary x-axis transformed with x*(a*b) and inverted with x/(a*b)
a, b = 4.*np.pi, np.float64((2.*3.086e22)**2.) 
axup = ax1.secondary_xaxis('top', functions=(lambda x: x*(a*b), lambda x: x/(a*b)))
axup.set_xscale('log')
axup.set_xlabel('Log axis')

plt.show()

secondary log axis with new params

Original example:

# secondary x-axis transformed with x*a/b and inverted with x*b/a
ax1.set_xlim(0.1, 10.)
a, b = 1.e37, 2.*(3.809e8)
axup = ax1.secondary_xaxis('top', functions=(lambda x: x*a/b, lambda x: x*b/a))

secondary log axis


Callback

You can use Axes callbacks to connect ax1 with axup:

[The Axes callback] events you can connect to are xlim_changed and ylim_changed and the callback will be called with func(ax) where ax is the Axes instance.

Here the ax1.xlim_changed event triggers scale_axup() to scale axup.xlim as scale(ax1.xlim). Note that I increased the xlim up to 10 to demonstrate more major ticks:

from matplotlib.ticker import LogFormatterMathtext
import matplotlib.pyplot as plt
import numpy as np

fig, ax1 = plt.subplots(1, figsize=(15,9))

# axup scaler
scale = lambda x: x*1.e37/(2.*(3.809e8))

# set axup.xlim to scale(ax1.xlim)
def scale_axup(ax1):
    # mirror xlim on both axes
    left, right = scale(np.array(ax1.get_xlim()))
    axup.set_xlim(left, right)
    
    # set xticks to 0.1e28 intervals
    xticks = np.arange(float(f'{left:.1e}'), float(f'{right:.1e}'), 0.1e28)
    axup.set_xticks([float(f'{tick:.0e}') for tick in xticks])
    axup.xaxis.set_major_formatter(LogFormatterMathtext())
    
    # redraw to update xticks
    axup.figure.canvas.draw()

# connect ax1 with axup (before ax1.set_xlim())
axup = ax1.twiny()
axup.set_xscale('log')
axup.set_xlabel(r'Log axis')
ax1.callbacks.connect(r'xlim_changed', scale_axup)

ax1.set_ylabel(r'y axis')
ax1.set_xlabel(r'Linear axis')
ax1.set_ylim(0.1, 1.)
ax1.set_xlim(0.1, 10.)

plt.show()

Upvotes: 1

Prefect
Prefect

Reputation: 1777

By following the answer you shared, I modified the code according to your needs.

import matplotlib.pyplot as plt
import matplotlib.ticker as ticker
import numpy as np

from matplotlib.ticker import StrMethodFormatter

fig, ax1 = plt.subplots(1, figsize=(10,6))

ax1.set_ylabel(r'y axis')
ax1.set_xlabel(r'Linear axis')

ax1.set_xlim(0.1,1.5)

#Upper lox-axis
def tick_function(x):
    v = np.log(x*1.e37/(2.*(3.809e8)))
    return ["%.1f" % z for z in v]


axup_locations = np.arange(0.1, 10., 0.1)


axup=ax1.twiny()

axup.set_xscale('log')
axup.set_xlim(0.1,100)
axup.set_yscale('linear')
axup.xaxis.set_major_formatter(StrMethodFormatter('{x:.0f}'))


axup.set_xlabel(r'Log axis')
plt.show()

log scale

Upvotes: 0

Related Questions