|
17 | 17 | import numpy as np
|
18 | 18 |
|
19 | 19 | import matplotlib.dates as mdates
|
20 |
| -from matplotlib.ticker import AutoMinorLocator |
21 | 20 |
|
22 | 21 | fig, ax = plt.subplots(layout='constrained')
|
23 | 22 | x = np.arange(0, 360, 1)
|
@@ -96,48 +95,47 @@ def one_over(x):
|
96 | 95 | plt.show()
|
97 | 96 |
|
98 | 97 | # %%
|
99 |
| -# Sometime we want to relate the axes in a transform that is ad-hoc from |
100 |
| -# the data, and is derived empirically. In that case we can set the |
101 |
| -# forward and inverse transforms functions to be linear interpolations from the |
102 |
| -# one data set to the other. |
| 98 | +# Sometime we want to relate the axes in a transform that is ad-hoc from the data, and |
| 99 | +# is derived empirically. Or, one axis could be a complicated nonlinear function of the |
| 100 | +# other. In these cases we can set the forward and inverse transform functions to be |
| 101 | +# linear interpolations from the one set of independent variables to the other. |
103 | 102 | #
|
104 | 103 | # .. note::
|
105 | 104 | #
|
106 | 105 | # In order to properly handle the data margins, the mapping functions
|
107 | 106 | # (``forward`` and ``inverse`` in this example) need to be defined beyond the
|
108 |
| -# nominal plot limits. |
109 |
| -# |
110 |
| -# In the specific case of the numpy linear interpolation, `numpy.interp`, |
111 |
| -# this condition can be arbitrarily enforced by providing optional keyword |
112 |
| -# arguments *left*, *right* such that values outside the data range are |
113 |
| -# mapped well outside the plot limits. |
| 107 | +# nominal plot limits. This condition can be enforced by extending the |
| 108 | +# interpolation beyond the plotted values, both to the left and the right, |
| 109 | +# see ``x1n`` and ``x2n`` below. |
114 | 110 |
|
115 | 111 | fig, ax = plt.subplots(layout='constrained')
|
116 |
| -xdata = np.arange(1, 11, 0.4) |
117 |
| -ydata = np.random.randn(len(xdata)) |
118 |
| -ax.plot(xdata, ydata, label='Plotted data') |
119 |
| - |
120 |
| -xold = np.arange(0, 11, 0.2) |
121 |
| -# fake data set relating x coordinate to another data-derived coordinate. |
122 |
| -# xnew must be monotonic, so we sort... |
123 |
| -xnew = np.sort(10 * np.exp(-xold / 4) + np.random.randn(len(xold)) / 3) |
124 |
| - |
125 |
| -ax.plot(xold[3:], xnew[3:], label='Transform data') |
126 |
| -ax.set_xlabel('X [m]') |
| 112 | +x1_vals = np.arange(2, 11, 0.4) |
| 113 | +# second independent variable is a nonlinear function of the other. |
| 114 | +x2_vals = x1_vals ** 2 |
| 115 | +ydata = 50.0 + 20 * np.random.randn(len(x1_vals)) |
| 116 | +ax.plot(x1_vals, ydata, label='Plotted data') |
| 117 | +ax.plot(x1_vals, x2_vals, label=r'$x_2 = x_1^2$') |
| 118 | +ax.set_xlabel(r'$x_1$') |
127 | 119 | ax.legend()
|
128 | 120 |
|
| 121 | +# the forward and inverse functions must be defined on the complete visible axis range |
| 122 | +x1n = np.linspace(0, 20, 201) |
| 123 | +x2n = x1n**2 |
| 124 | + |
129 | 125 |
|
130 | 126 | def forward(x):
|
131 |
| - return np.interp(x, xold, xnew) |
| 127 | + return np.interp(x, x1n, x2n) |
132 | 128 |
|
133 | 129 |
|
134 | 130 | def inverse(x):
|
135 |
| - return np.interp(x, xnew, xold) |
136 |
| - |
| 131 | + return np.interp(x, x2n, x1n) |
137 | 132 |
|
| 133 | +# use axvline to prove that the derived secondary axis is correctly plotted |
| 134 | +ax.axvline(np.sqrt(40), color="grey", ls="--") |
| 135 | +ax.axvline(10, color="grey", ls="--") |
138 | 136 | secax = ax.secondary_xaxis('top', functions=(forward, inverse))
|
139 |
| -secax.xaxis.set_minor_locator(AutoMinorLocator()) |
140 |
| -secax.set_xlabel('$X_{other}$') |
| 137 | +secax.set_xticks([10, 20, 40, 60, 80, 100]) |
| 138 | +secax.set_xlabel(r'$x_2$') |
141 | 139 |
|
142 | 140 | plt.show()
|
143 | 141 |
|
|
0 commit comments