Last active
November 4, 2018 11:20
-
-
Save FilipDominec/9101258e8ba979c715a28bc527a43961 to your computer and use it in GitHub Desktop.
Functional dependence of right y-axis on the left one in Matplotlib: how to get nice numbers on both axes
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/python3 | |
#-*- coding: utf-8 -*- | |
import numpy as np | |
import scipy.constants as sc | |
import matplotlib.pyplot as plt | |
import matplotlib | |
## Ordinary plotting code | |
fig = plt.figure(figsize=(7,4), dpi=96, facecolor='#eeeeee', tight_layout=1) | |
ax = fig.add_subplot(121) | |
#XXX ax2.axis['top'].major_ticklabels().set_visible(False) | |
#XXX ax2.axis['top'].major_ticklabels.set_visible(False) | |
#XXX TypeError: 'method' object is not subscriptable | |
import matplotlib.gridspec as gridspec | |
gs = gridspec.GridSpec(1, 2, width_ratios=[.95, .05] ) | |
ax, axR = plt.subplot(gs[0]), plt.subplot(gs[1]) | |
print(ax) | |
#from mpl_toolkits.axes_grid1 import host_subplot | |
#ax = host_subplot(111) | |
#print(ax) | |
#hax = host_axes([0.1,0.1,0.8,0.8], figure=fig) | |
#hax.plot([0,1,2,3],[1,2,4,8]) | |
#fig.savefig("some_file.png") | |
x = np.arange(1.6, 3.2, .1) | |
p = np.arange(2.0, 11.0, .1) | |
xs, ps = np.meshgrid(x, p) | |
zs = 1/(1 + (xs-3)**4 + (ps-5)**2) | |
contourf = ax.contourf(x,p,zs, cmap=matplotlib.cm.viridis) | |
#x = np.arange(1.6, 3.2, 0.1) | |
#ax.plot(x, 6+4*np.sin(x*np.pi*2)) | |
ax.set_title('Cathodoluminescence spectra\n\n\n') | |
ax.set_xlabel('photon energy (eV)') | |
ax.set_xlim((1.6, 3.2)) | |
ax.set_ylabel('electron acceleration voltage (kV)') | |
## Here we define the desired nonlinear dependence between dual y-axes: | |
def right_tick_function(p): return 10.46*(p**1.68) | |
ax2 = ax.twinx() | |
#ax2.axis['right'].major_ticklabels.set_visible(False) | |
ax2.set_ylim(np.array(ax.get_ylim())) | |
ax2.set_ylabel('electron penetration depth (nm)') | |
## If we wish nice round numbers on the secondary axis ticks... | |
right_ax_limits = sorted([right_tick_function(lim) for lim in ax.get_ylim()]) | |
yticks2 = matplotlib.ticker.MaxNLocator(nbins=8, steps=[1,2,5]).tick_values(*right_ax_limits) | |
## ... we must give them correct positions. To do so, we need to numerically invert the tick values: | |
from scipy.optimize import brentq | |
def right_tick_function_inv(r, target_value=0): | |
return brentq(lambda r,q: right_tick_function(r) - target_value, ax.get_ylim()[0], ax.get_ylim()[1], 1e6) | |
valid_ytick2loc, valid_ytick2val = [], [] | |
for ytick2 in yticks2: | |
try: | |
valid_ytick2loc.append(right_tick_function_inv(0, ytick2)) | |
valid_ytick2val.append(ytick2) | |
except ValueError: ## (skip tick if the ticker.MaxNLocator gave invalid target_value to brentq optimization) | |
pass | |
## Finally, we set the positions and tick values | |
ax2.set_yticks(valid_ytick2loc) | |
from matplotlib.ticker import FixedFormatter | |
ax2.yaxis.set_major_formatter(FixedFormatter(["%g" % righttick for righttick in valid_ytick2val])) | |
## The same approach for the x-axes (relation between photon energy and vavelength) | |
def top_tick_function(p): return sc.h*sc.c/(p*sc.nano)/sc.eV | |
ax3 = ax.twiny() | |
#ax2.axis['top'].major_ticklabels.set_visible(False) | |
ax3.set_xlim(np.array(ax.get_xlim())) | |
ax3.set_xlabel('photon wavelength (nm)') | |
## If we wish nice round numbers on the secondary axis ticks... | |
print(ax.get_xlim()) | |
print(top_tick_function(1.0)) | |
print([top_tick_function(lim) for lim in ax.get_xlim()]) | |
top_ax_limits = sorted([top_tick_function(lim) for lim in ax.get_xlim()]) | |
print(top_ax_limits) | |
xticks2 = matplotlib.ticker.MaxNLocator(nbins=8, steps=[1,2,5]).tick_values(*top_ax_limits) | |
## ... we must give them correct positions. To do so, we need to numerically invert the tick values: | |
from scipy.optimize import brentq | |
def top_tick_function_inv(r, target_value=0): | |
return brentq(lambda r,q: top_tick_function(r) - target_value, ax.get_xlim()[0], ax.get_xlim()[1], 1e6) | |
valid_xtick2loc, valid_xtick2val = [], [] | |
for xtick2 in xticks2: | |
try: | |
valid_xtick2loc.append(top_tick_function_inv(0, xtick2)) | |
valid_xtick2val.append(xtick2) | |
except ValueError: ## (skip tick if the ticker.MaxNLocator gave invalid target_value to brentq optimization) | |
pass | |
## Finally, we set the positions and tick values | |
ax3.set_xticks(valid_xtick2loc) | |
from matplotlib.ticker import FixedFormatter | |
ax3.xaxis.set_major_formatter(FixedFormatter(["%g" % toptick for toptick in valid_xtick2val])) | |
#axR = fig.add_subplot(122) | |
plt.colorbar(contourf, extend='both', cax=axR, ax=[ax,ax2,ax3]) ## todo: overlaps the right axis , fraction=0.07 | |
axR.set_ylabel('relative intensity (a. u.)') | |
#plt.colorbar(contourf, shrink=0.8, extend='both') | |
#from mpl_toolkits.axes_grid1.inset_locator import inset_axes | |
#axins = inset_axes(ax2, | |
#width="5%", # width = 10% of parent_bbox width | |
#height="50%", # height : 50% | |
#loc=3, | |
#bbox_to_anchor=(1, 1.1, 1, .5), | |
#bbox_transform=ax2.transAxes, | |
#borderpad=0, | |
#) | |
#plt.colorbar(contourf, cax=axins, ticks=[0, 1]) | |
## Finish the plot | |
ax.legend(prop={'size':10}, loc='upper right') | |
ax.grid() | |
fig.savefig('with_round_numbers_testcbar.png') |
For logarithmic x-axis
matplotlib.rc('font', size=12, family='serif')
for x, y, n, param, label, xlabel, ylabel, color in \
zip(xs, ys, range(len(xs)), params, labels, xlabels, ylabels, colors):
# x, y = x[~np.isnan(y)], y[~np.isnan(y)] ## filter-out NaN points
# convol = 2**-np.linspace(-2,2,25)**2; y = np.convolve(y,convol/np.sum(convol), mode='same') ## simple smoothing
ax.plot(x, y, label="%s" % (label), color='r' if 'CU' in label else 'b' if 'CB' in label else 'g' if 'CG' in label else 'k', ls='-' if '49' in label else '--')
#ax.plot(x, y, label="%s" % (label.split('.dat')[0]), color=colors[c%10], ls=['-','--'][int(c/10)])
ax.set_xlabel('spatial frequency (rad/m)')
ax.set_ylabel('spectral power (A.U.)')
ax.set_xscale('log')
ax.set_yscale('log')
#plot_title = sharedlabels[-4:] ## last few labels that are shared among all curves make a perfect title
#plot_title = sharedlabels[sharedlabels.index('LastCroppedLabel')+1:] ## optionally, use all labels after the chosen one
#ax.set_title("Power spectral density")
## The same approach for the x-axes (relation between photon energy and vavelength)
def top_tick_function(p): return 1e6*(np.pi*2)/p
ax3 = ax.twiny()
ax3.set_xscale('log') ## LOG!
#ax2.axis['top'].major_ticklabels.set_visible(False)
ax3.set_xlim(np.array(ax.get_xlim()))
ax3.set_xlabel('characteristic feature size (μm)')
## If we wish nice round numbers on the secondary axis ticks...
print(ax.get_xlim())
print(top_tick_function(1.0))
print([top_tick_function(lim) for lim in ax.get_xlim()])
top_ax_limits = ([top_tick_function(lim) for lim in ax.get_xlim()])
xticks2 = 10**(matplotlib.ticker.MaxNLocator(nbins=8, steps=[1]).tick_values(np.log10(top_ax_limits[1]), np.log10(top_ax_limits[0])))
print("top_ax_limits", top_ax_limits, "xticks2", xticks2)
## ... we must give them correct positions. To do so, we need to numerically invert the tick values:
from scipy.optimize import brentq
def top_tick_function_inv(r, target_value=0):
return (brentq(lambda r,q: top_tick_function(r) - target_value, ax.get_xlim()[1], ax.get_xlim()[0], 1e-6))
valid_xtick2loc, valid_xtick2val = [], []
for xtick2 in xticks2:
try:
valid_xtick2loc.append(top_tick_function_inv(0, xtick2))
valid_xtick2val.append(xtick2)
except ValueError: ## (skip tick if the ticker.MaxNLocator gave invalid target_value to brentq optimization)
pass
## Finally, we set the positions and tick values
print("valid_xtick2loc",valid_xtick2loc,"valid_xtick2val", valid_xtick2val)
ax3.set_xticks(valid_xtick2loc)
from matplotlib.ticker import FixedFormatter
ax3.xaxis.set_major_formatter(FixedFormatter(["%g" % toptick for toptick in valid_xtick2val]))
#ax.legend(loc='best', prop={'size':10})
#np.savetxt('output.dat', np.vstack([x,ys[0],ys[1]]).T, fmt="%.8g")
#tosave.append('_'.join(plot_title)+'.png') ## whole graph will be saved as PNG
#tosave.append('_'.join(plot_title)+'.pdf') ## whole graph will be saved as PDF
This has been suggested to extend matplotlib in the issue: matplotlib/matplotlib#12734
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
The result: