6

Dear all I want to recalculate the x y values written in the tick labeling of my figure after i have zoomed in it in such a way that the origin is always at (0,0) and obviously the relative distances of the values on the x and y axis stay the same.

I think I need to track the limits of my figure after having zoomed in to it and than simply subtract the current xmin and ymin from the actual x y tick values. I guess this can be achieved with the event handling API Event handling as i have learned here : Source1

This is also the place where I got the start of my MWE:

import matplotlib.pyplot as plt

#
# Some toy data
x_seq = [x / 100.0 for x in xrange(1, 100)]
y_seq = [x**2 for x in x_seq]

#
# Scatter plot
fig, ax = plt.subplots(1, 1)
ax.scatter(x_seq, y_seq)


#
# Declare and register callbacks
def on_xlims_change(axes):
    a=axes.get_xlim()
    print "updated xlims: ", axes.get_xlim()
    return a
    
def on_ylims_change(axes):
    a=axes.get_ylim()
    print "updated ylims: ", axes.get_ylim()
    return a
    
ax.callbacks.connect('xlim_changed', on_xlims_change)
ax.callbacks.connect('ylim_changed', on_ylims_change)



#
# Show
plt.show()

But I do not really know how I should go from here? Do i have to do the calculation inside the on_xlims_change function and change the x and y tick labels there? Again, I think I really only need to change the value given in the label, right? or would it be easier to change the actual value of the coordinates such that the automatic tick labeling still works?

Nimantha
  • 6,405
  • 6
  • 28
  • 69
NorrinRadd
  • 545
  • 6
  • 21

2 Answers2

1

This may not be as easy as it sounds. When changing the limits, you would change the limits, such that the callback runs infinitly, making your window crash.

I would hence opt for another solution, using a second axes. So let's say you have two axes:

  • ax2 is the axes to plot to. But is has no frame and no ticklabels. This is the axes you can change the limits with.
  • ax is empty. It initially has the same limits as ax2. And it will show the ticklabels.

Once you zoom in on ax2 the callback function can change the limits of ax to your liking. This is then what is shown on the screen.

import matplotlib.pyplot as plt

# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]

# ax is empty
fig, ax = plt.subplots()
ax.set_navigate(False)
# ax2 will hold the plot, but has invisible labels
ax2 = fig.add_subplot(111,zorder=2)
ax2.scatter(x_seq, y_seq)
ax2.axis("off")

ax.set_xlim(ax2.get_xlim())
ax.set_ylim(ax2.get_ylim())

#
# Declare and register callbacks
def on_lims_change(axes):
    # change limits of ax, when ax2 limits are changed.
    a=ax2.get_xlim()
    ax.set_xlim(0, a[1]-a[0])
    a=ax2.get_ylim()
    ax.set_ylim(0, a[1]-a[0])

ax2.callbacks.connect('xlim_changed', on_lims_change)
ax2.callbacks.connect('ylim_changed', on_lims_change)

# Show
plt.show()
G M
  • 20,759
  • 10
  • 81
  • 84
ImportanceOfBeingErnest
  • 321,279
  • 53
  • 665
  • 712
1

What you want to do can be achieved by updating your limits within on_ylims_change. However, we need to make sure not to end up with an infinite callback loop. One way to do this is to disconnect the callback while we are updating xlim and ylim, and then reconnecting it immediately afterward.

import matplotlib.pyplot as plt

#
# Some toy data
x_seq = [x / 100.0 for x in range(1, 100)]
y_seq = [x**2 for x in x_seq]

#
# Scatter plot
fig, ax = plt.subplots(1, 1)
ax.scatter(x_seq, y_seq)

#
# Declare and register callbacks
def on_lims_change(event_ax):
    cid_list = list(event_ax.callbacks.callbacks['ylim_changed'].keys())
    for cid in cid_list:
        event_ax.callbacks.disconnect(cid)
    
    a = ax.get_xlim()
    span = a[1]-a[0]
    event_ax.set_xlim(-span/2, span/2)
    a = ax.get_ylim()
    span = a[1]-a[0]
    event_ax.set_ylim(-span/2, span/2)
    
    event_ax.callbacks.connect('ylim_changed', on_lims_change)


ax.callbacks.connect('ylim_changed', on_lims_change)

#
# Show
plt.show()

Generally we only want to connect to the ylim_changed event since this will be called after xlim_changed for a standard zoom event. This way we have the final zoom results before starting to make changes.

amicitas
  • 13,053
  • 5
  • 38
  • 50
  • This answer was inspired by @ImportanceOfBeingErnest's answer, but I thought that this was a little easier that creating two overlapping axes and correctly updates all of the labels without additional code. – amicitas Jul 29 '21 at 04:54