Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Added onchange for y and x scale #26103

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Luna-v0 wants to merge 4 commits into matplotlib:main from Luna-v0:onchange_xyscale
Closed

Added onchange for y and x scale #26103

Luna-v0 wants to merge 4 commits into matplotlib:main from Luna-v0:onchange_xyscale

Conversation

Copy link

@Luna-v0 Luna-v0 commented Jun 10, 2023

PR summary

closes #8439

Since requested by the issue, I've made these onchange functions for xscale and yscale, but since I can't override directly the setter for y and x scale I've made onchange_scale_y and onchange_scale_x override them instead. Theses two functions are basically the same, here's an example of one of them.

 def onchange_scale_x(self, func):
 __set_xscale = self.set_xscale
 def wrapper_xscale(*args, **kwargs):
 __set_xscale(*args, **kwargs)
 res = self.get_xscale()
 func(res)
 self.set_xscale = wrapper_xscale

PR checklist

Copy link
Member

ksunden commented Jun 14, 2023

overwriting an instance method is probably not the tact we wish to go with for this functionality...

Probably the way to do it is to follow the path of other similar events

This would put the callback management on the figure, but provide the axes in the event (which would probably need to be a new event class, I think...)

This would allow for things like disconnecting signals, which is not provided in the "overwrite the instance method" implementation.

Luna-v0 reacted with thumbs up emoji

Copy link
Author

Luna-v0 commented Jun 17, 2023

@ksunden Here's an explanation on what I did:

In the issue, they mentioned that the xlim and ylim variables already have this type of event handling, so I just tried to replicate on xscale and yscale, here's what is already in matplotlib:

signals=["xlim_changed", "ylim_changed", "zlim_changed"])
self.axes.callbacks.process(f"{name}lim_changed", self.axes)

And here's what I did:

signals=["xlim_changed", "ylim_changed", "zlim_changed",
 "xscale_changed", "yscale_changed", "zscale_changed"])
self.axes.callbacks.process(f"{name}scale_changed", value)

Removed onchange_xyscale functions and added .connect to scale
Removed onchange_xyscale functions and added .connect to scale
Copy link
Member

@ksunden ksunden left a comment

Choose a reason for hiding this comment

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

Implementation wise, this is relatively good, a few questions/improvements:

  • Documentation (in particular in the Axes docstring, where the [xy]lim_changed gets documented and a release note, could also argue for additions to event handling examples as well)
  • Tests (? We don't actually have explicit tests for the limit changed callbacks, so could argue for not being needed)
  • Does 3D need any particular handling? (I note that zscale_changed is added, but not fully sure if Axes3D goes through this same code path or not.
  • Does there need to be an emit=True kwarg/private functionality that does not emit? (the answer may be no, at which point, don't prematurely add the functionality)?
  • Are there implications for e.g. parasite axes or colorbar axes (to be clear, not sure these are needed, just did a quick search for where the similar event type was used so that we can have an eye towards consistent behavior)

Luna-v0 reacted with thumbs up emoji
@Luna-v0 Luna-v0 marked this pull request as draft June 27, 2023 20:48
@Luna-v0 Luna-v0 marked this pull request as ready for review June 28, 2023 00:46
Copy link
Author

Luna-v0 commented Jun 28, 2023

About parasite, colorbar and 3D, I've copied an example of each from matplotlib's site and I altered them to be able to just see if my code generated any conflict or erros and apperantly it doesn't, I'm putting them below in case you want to test yourself, I also read the code that target those xlim and ylim, and it didn't seam the case to put any extra code for the scales, I did create test for the scale, and I did put in the docs that scales events can be used (I also updated the zlim_changed that was missing from the docs), I only didn't created release notes nor examples, cause I never done it, if you would like them anyway I would like some guidence on that.

Parasite

from mpl_toolkits.axisartist.parasite_axes import HostAxes
import matplotlib.pyplot as plt
def xscale_onchange_host(axes):
 print(f"Host: {axes.get_xscale()}")
def xscale_onchange_par2(axes):
 print(f"Parasite 2: {axes.get_xscale()}")
def xscale_onchange_par1(axes):
 print(f"Parasite 1: {axes.get_xscale()}")
fig = plt.figure()
ax = fig.add_subplot(111)
ax.callbacks.connect('xscale_changed', xscale_onchange_host)
ax.set_xscale('log')
host = fig.add_axes([0.15, 0.1, 0.65, 0.8], axes_class=HostAxes)
par1 = host.get_aux_axes(viewlim_mode=None, sharex=host)
par2 = host.get_aux_axes(viewlim_mode=None, sharex=host)
host.axis["right"].set_visible(False)
par1.axis["right"].set_visible(True)
par1.axis["right"].major_ticklabels.set_visible(True)
par1.axis["right"].label.set_visible(True)
par1.axes.callbacks.connect('xscale_changed', xscale_onchange_par1)
par1.axes.set_xscale("linear")
par2.axis["right2"] = par2.new_fixed_axis(loc="right", offset=(60, 0))
par2.axes.callbacks.connect('xscale_changed', xscale_onchange_par2)
par2.axes.set_xscale("linear")
p1, = host.plot([0, 1, 2], [0, 1, 2], label="Density")
p2, = par1.plot([0, 1, 2], [0, 3, 2], label="Temperature")
p3, = par2.plot([0, 1, 2], [50, 30, 15], label="Velocity")
host.set(xlim=(0, 2), ylim=(0, 2), xlabel="Distance", ylabel="Density")
par1.set(ylim=(0, 4), ylabel="Temperature")
par2.set(ylim=(1, 65), ylabel="Velocity")
host.legend()
host.axis["left"].label.set_color(p1.get_color())
par1.axis["right"].label.set_color(p2.get_color())
par2.axis["right2"].label.set_color(p3.get_color())
plt.show()

Colorbar

import numpy as np
import matplotlib.pyplot as plt
# setup some generic data
N = 37
x, y = np.mgrid[:N, :N]
Z = (np.cos(x*0.2) + np.sin(y*0.3))
# mask out the negative and positive values, respectively
Zpos = np.ma.masked_less(Z, 0)
Zneg = np.ma.masked_greater(Z, 0)
fig, (ax1, ax2, ax3) = plt.subplots(figsize=(13, 3), ncols=3)
def ax3scale_change(axes):
 print(f"Color bar: {axes.get_xscale()}")
ax3.callbacks.connect('xscale_changed', ax3scale_change)
ax3.set_xscale('linear')
# plot just the positive data and save the
# color "mappable" object returned by ax1.imshow
pos = ax1.imshow(Zpos, cmap='Blues', interpolation='none')
# add the colorbar using the figure's method,
# telling which mappable we're talking about and
# which axes object it should be near
fig.colorbar(pos, ax=ax1)
# repeat everything above for the negative data
# you can specify location, anchor and shrink the colorbar
neg = ax2.imshow(Zneg, cmap='Reds_r', interpolation='none')
fig.colorbar(neg, ax=ax2, location='right', anchor=(0, 0.3), shrink=0.7)
# Plot both positive and negative values between +/- 1.2
pos_neg_clipped = ax3.imshow(Z, cmap='RdBu', vmin=-1.2, vmax=1.2,
 interpolation='none')
# Add minorticks on the colorbar to make it easy to read the
# values off the colorbar.
cbar = fig.colorbar(pos_neg_clipped, ax=ax3, extend='both')
cbar.minorticks_on()
plt.show()

3D

import numpy as np
import matplotlib.pyplot as plt
def xscale_onchange(axes):
 print(f"X scale: {axes}")
def yscale_onchange(axes):
 print(f"Y scale: {axes}")
def zscale_onchange(axes):
 print(f"Z scale: {axes}")
ax = plt.figure().add_subplot(projection='3d')
# Plot a sin curve using the x and y axes.
x = np.linspace(0, 1, 100)
y = np.sin(x * 2 * np.pi) / 2 + 0.5
ax.plot(x, y, zs=0, zdir='z', label='curve in (x, y)')
# Plot scatterplot data (20 2D points per colour) on the x and z axes.
colors = ('r', 'g', 'b', 'k')
# Fixing random state for reproducibility
np.random.seed(19680801)
x = np.random.sample(20 * len(colors))
y = np.random.sample(20 * len(colors))
c_list = []
for c in colors:
 c_list.extend([c] * 20)
# By using zdir='y', the y value of these points is fixed to the zs value 0
# and the (x, y) points are plotted on the x and z axes.
ax.scatter(x, y, zs=0, zdir='y', c=c_list, label='points in (x, z)')
ax.callbacks.connect('xscale_changed', xscale_onchange)
ax.callbacks.connect('yscale_changed', yscale_onchange)
ax.callbacks.connect('zscale_changed', zscale_onchange)
# Make legend, set axes limits and labels
ax.legend()
ax.set_xlim(0, 1)
ax.set_ylim(0, 1)
ax.set_zlim(0, 1)
ax.set_xscale('linear')
ax.set_yscale('linear')
ax.set_zscale('linear')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
# Customize the view angle so it's easier to see that the scatter points lie
# on the plane y=0
ax.view_init(elev=20., azim=-35, roll=0)
plt.show()

@Luna-v0 Luna-v0 closed this by deleting the head repository Aug 6, 2023
Copy link
Member

Hi @MichaelTheFear - did you mean to close this PR? If not, let us know and we can try recreating it. Cheers!

Copy link
Member

ksunden commented Aug 7, 2023

Sorry I hadn't gotten around to re-reviewing, It was on my list after I get the 3.8 release candidate out.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@ksunden ksunden ksunden requested changes

Assignees
No one assigned
Projects
Status: Waiting for author
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

Emit event when changing xscale/yscale

AltStyle によって変換されたページ (->オリジナル) /