I have this python code snippet that sets the ticks of the two y-axes. But I can't figure out how I can synchronize the axe's scales.
if y_data_left:
all_left = pd.concat([y for _, y, _, _ in y_data_left])
ymin_left, ymax_left = all_left.min(), all_left.max()
if ymin_left > 0:
ymin_left = 0
if ymin_left == ymax_left or ymax_left - ymin_left <= 0.01:
ymax_left += 1
margin_left = ymax_left * 1.1
ax.set_ylim(ymin_left, margin_left)
major_y_left = get_nice_tick_interval(ymin_left, ymax_left, 5) / tick_factor
ax.yaxis.set_major_locator(ticker.MultipleLocator(major_y_left))
ax.yaxis.set_minor_locator(ticker.MultipleLocator(major_y_left / 5))
fig.canvas.draw()
# Y-Achse rechts unabhängig skalieren, aber gleiche Positionen übernehmen
if ax2 and y_data_right:
all_right = pd.concat([y for _, y, _, _ in y_data_right])
ymin_right = 0 if ymin_right > 0 else all_right.min()
ymax_right = all_right.max()
ax2.set_ylim(ymin_right, ymax_right)
major_left = ax.get_yticks()
n_ticks = len(major_left)
major_right_interval = get_nice_tick_interval(ymin_right, ymax_right, n_ticks - 1)
yt_right = np.arange(ymin_right, ymax_right + major_right_interval, major_right_interval)
ax2.set_yticks(yt_right)
# Set Minor-Ticks
if n_ticks > 1:
minor_interval = major_right_interval / 5
ax2.yaxis.set_minor_locator(MultipleLocator(minor_interval))
ax2.tick_params(axis="y", labelright=True)
Here is the get_nice_tick_interval function:
def get_nice_tick_interval(vmin, vmax, target_steps=5, factor=1.0):
target_steps *= factor
if vmin == vmax:
vmin -= 1
vmax += 1
raw_interval = (vmax - vmin) / target_steps
exponent = np.floor(np.log10(raw_interval))
fraction = raw_interval / 10**exponent
if fraction <= 1:
nice_fraction = 1
elif fraction <= 2:
nice_fraction = 2
elif fraction <= 5:
nice_fraction = 5
else:
nice_fraction = 10
return nice_fraction * 10**exponent
I have tried to get the pixels of the plot like
right_ticks = ax2.transData.inverted().transform(np.column_stack([np.zeros_like(left_pixels), left_pixels]))[:, 1]
but that didn't work.
I hoped that the ticks would be across from each other but that hasn't been the case.
Don’t mess with pixels. Take the left ticks’ relative positions (0→1 along the axis) and map them into the right axis’ data range. Then set the right ticks to those mapped values so they line up perfectly.
Also fix this bug first:
# was using ymin_right before defining it
ymin_right = 0 if all_right.min() > 0 else all_right.min()
fig.canvas.draw() # left ticks are finalized
# set right limits however you want first
ax2.set_ylim(ymin_right, ymax_right)
# map left major ticks -> right coords
y0l, y1l = ax.get_ylim()
y0r, y1r = ax2.get_ylim()
left_major = ax.get_yticks()
frac = (left_major - y0l) / (y1l - y0l)
right_major = y0r + frac * (y1r - y0r)
ax2.yaxis.set_major_locator(ticker.FixedLocator(right_major))
# this is optional but you can mirror minor ticks too
left_minor = ax.yaxis.get_minorticklocs()
if len(left_minor):
frac_m = (left_minor - y0l) / (y1l - y0l)
right_minor = y0r + frac_m * (y1r - y0r)
ax2.yaxis.set_minor_locator(ticker.FixedLocator(right_minor))
Connect a tiny callback that recomputes the same mapping on ylim_changed
if you want it to stay synced on zoom/limit changes.