2

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()
Py-ser
  • 1,860
  • 9
  • 32
  • 58

2 Answers2

1

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()
tdy
  • 36,675
  • 19
  • 86
  • 83
  • Thanks. However, this way the axup ticks are not separated as in a log scale as, e.g., [here](https://matplotlib.org/3.1.1/gallery/scales/log_test.html) – Py-ser Apr 23 '21 at 17:11
  • @Py-ser Not sure I understand. Using your conversion formula, the `axup` ticks are in the 60's range. Based on your other comments, I thought you wanted `axup` to have correspondence between the linear axis and the conversion formula, e.g. 0.1 -> 62.44, 0.8 -> 64.52, 1.4 -> 65.08, etc. – tdy Apr 23 '21 at 17:37
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/231531/discussion-between-py-ser-and-tdy). – Py-ser Apr 23 '21 at 19:17
0

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

Prefect
  • 1,719
  • 1
  • 7
  • 16
  • 1
    Thank you. However, I'd like the ticks to be separated like in a log axis scale (e.g., [here](https://matplotlib.org/3.1.1/gallery/scales/log_test.html)). – Py-ser Apr 21 '21 at 15:12
  • I see your point. I just updated my answer, is this what you are looking for? – Prefect Apr 21 '21 at 18:33
  • This is closer, but it changes the scale on the yaxis and, in my case, it draws a function that I am not sure where it comes from. Look at your y-axis in your figure, and compare it with the `ax1.set_ylim(0.1,1.)`. Also, if possible, the top ticks should not be scientific notation. – Py-ser Apr 21 '21 at 20:04
  • Updated. I am slightly confused about `tick_function` though. What exactly do you want to draw on `axup` by using that? Scientific notation removed. – Prefect Apr 21 '21 at 21:04
  • Thank you. But this does not take into account the function `tick_function` at all? `axup` shows a function `axup = f(ax1)` of the values on `ax1`. – Py-ser Apr 22 '21 at 16:49
  • `tick_function(np.arange(0.1, 10., 0.1))` returns some scalar values ranging from 63 to 67. How exactly do you want to plot them on `axup`? – Prefect Apr 22 '21 at 16:59
  • What matters is that, as it is now, if I change the definition of the `tick_function` (say, adding a factor of `200` to the `v`), the `axup` ticks position do not change? There is no correspondence between the ax1 ticks and the axup ticks, while there should be one. – Py-ser Apr 22 '21 at 17:32
  • So, all you need is to adjust the ticks according to the range of `tick_function`? But not creating new ticks by using `set_xticks`? – Prefect Apr 25 '21 at 08:40