-
-
Notifications
You must be signed in to change notification settings - Fork 8k
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
Conversation
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.
@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:
matplotlib/lib/matplotlib/axes/_base.py
Line 1282 in f329eb8
matplotlib/lib/matplotlib/axis.py
Line 1229 in f329eb8
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
There was a problem hiding this 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)
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()
Hi @MichaelTheFear - did you mean to close this PR? If not, let us know and we can try recreating it. Cheers!
Sorry I hadn't gotten around to re-reviewing, It was on my list after I get the 3.8 release candidate out.
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.
PR checklist