Skip to content

Emit xlim_changed / ylim_changed when limits expand via set_xticks / set_yticks#31251

Merged
timhoffm merged 25 commits intomatplotlib:mainfrom
Chirag3841:set_ticks
Mar 17, 2026
Merged

Emit xlim_changed / ylim_changed when limits expand via set_xticks / set_yticks#31251
timhoffm merged 25 commits intomatplotlib:mainfrom
Chirag3841:set_ticks

Conversation

@Chirag3841
Copy link
Copy Markdown
Contributor

PR summary

AI Disclosure

PR checklist

Description

Fixes an issue where axis limit change callbacks were not emitted when the limits changed as a consequence of calling set_xticks or set_yticks.

Currently, when ticks extend beyond the existing axis limits, the limits are expanded internally via set_view_interval, but the corresponding "xlim_changed" / "ylim_changed" callbacks are not emitted. This leads to inconsistent behavior compared to explicitly calling set_xlim / set_ylim.

@scottshambaugh
Copy link
Copy Markdown
Contributor

scottshambaugh commented Mar 10, 2026

Hi @Chirag3841, I'm very sorry but I misunderstood the original issue as not setting the limits correctly, rather than the limits being set but doing so silently without a callback. I believe your original implementation was close to correct, rather than the feedback I gave you here.

I believe a test like this should verify the fix:

def test_set_ticks_emits_lim_changed():
    fig, ax = plt.subplots()
    ax.set_xlim(0.5, 1)
    called = []
    ax.callbacks.connect('xlim_changed', called.append)
    ax.set_xticks([0, 100])
    assert called

@Chirag3841
Copy link
Copy Markdown
Contributor Author

Chirag3841 commented Mar 11, 2026

Hi @Chirag3841, I'm very sorry but I misunderstood the original issue as not setting the limits correctly, rather than the limits being set but doing so silently without a callback. I believe your original implementation was close to correct, rather than the feedback I gave you here.

I believe a test like this should verify the fix:

def test_set_ticks_emits_lim_changed():
    fig, ax = plt.subplots()
    ax.set_xlim(0.5, 1)
    called = []
    ax.callbacks.connect('xlim_changed', called.append)
    ax.set_xticks([0, 100])
    assert called

@scottshambaugh Thanks for the clarification I pushed that commit again and changed the test file according to your requirement too . Suggest me the changes if required as it is causing one test failure colorbar test one which I don't think is related to this .

Copy link
Copy Markdown
Contributor

@scottshambaugh scottshambaugh left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, this looks good to me now. The CI test failures are unrelated.

This should be a squash merge.

@Chirag3841
Copy link
Copy Markdown
Contributor Author

Chirag3841 commented Mar 12, 2026

Ok, this looks good to me now. The CI test failures are unrelated.

This should be a squash merge.

Thanks for the review and suggestion.
Looking forward to follow further instructions.

@Chirag3841
Copy link
Copy Markdown
Contributor Author

Any update on this PR ?

ticks = self.convert_units(ticks)
locator = mticker.FixedLocator(ticks) # validate ticks early.
if len(ticks):
old_vmin, old_vmax = self.get_view_interval()
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can keep this as a tuple

Suggested change
old_vmin, old_vmax = self.get_view_interval()
old_view_interval = self.get_view_interval()

And also do the != comparison on the tuple below. This keeps the logic on a slightly higher level.

Copy link
Copy Markdown
Contributor Author

@Chirag3841 Chirag3841 Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@timhoffm I tried using tuple comparison as suggested, but it led to multiple failures due to array comparison ambiguity. So, I revert back to initial one .
image

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, right it's an array on Axis. Sorry for the noise.

axis.set_view_interval(min(ticks), max(ticks))
new_vmin, new_vmax = self.get_view_interval()
if old_vmin != new_vmin or old_vmax != new_vmax:
self.axes.callbacks.process(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to be sure: Have you checked that this is the right place to call the callbacks? Could it be on a broader (caller of _set_tick_locations) or narrower (set_view_interval) scope?

Copy link
Copy Markdown
Contributor Author

@Chirag3841 Chirag3841 Mar 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kept the callback in _set_tick_locations since this is where limits are updated due to tick changes.
Moving it to set_view_interval would trigger the callback for all view limit updates, which seems broader than intended here.

@Chirag3841
Copy link
Copy Markdown
Contributor Author

Chirag3841 commented Mar 17, 2026

@timhoffm Thanks for reviewing and giving me valuable suggestions in this PR .

@timhoffm timhoffm added this to the v3.11.0 milestone Mar 17, 2026
@timhoffm timhoffm merged commit 02b42db into matplotlib:main Mar 17, 2026
38 of 40 checks passed
@timhoffm
Copy link
Copy Markdown
Member

Thanks @Chirag3841

@Chirag3841 Chirag3841 deleted the set_ticks branch March 18, 2026 05:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Changing limits by setting ticks does not emit "x/ylim_changed"

3 participants