Several figures with subplots using a combination list in Matplotlib - matplotlib

I want to make a streamplot of a vectorial field which contains some free constants which I would like to change. So I've made combinations of these constants and I can sucessfully plot the stream plots one by one with this:
Y, X = np.mgrid[-1:10:200j, 0:10:200j]
tau_x = [0.01, 0.1, 1., 10.]
tau_y = [0.01, 0.1, 1., 10.]
alpha = [0.01, 0.1, 1., 10.]
r = [0.1, 0.01, 0.001]
K = [0.1, 0.5, 1.0, 1.5]
combinations_list = list(itertools.product(tau_x,tau_y,alpha,r,K))
for a in combinations_list:
(tau_x, tau_y, alpha, r, K) = a
Fx = (1/tau_x) * ( (-8/3)*(2*r-alpha)*(X-1) + K*X )
Fy = (2/(tau_y*X**(3/2))) * ( -2*(Y-1) + 3*Y*(X-1)/X + K*X*Y )
fig, ax = plt.subplots()
strm = ax.streamplot(X, Y, Fx, Fy, linewidth=0.5)
plt.show()
Now, because we are talking of a very large number of combinations, I would like to make a figure with subplots (say 9 each figure but it could be more) which would reduce a lot the number of figures.
Note: I am interested in seeing one figure each time and that's why plt.show() is inside the loop to avoid opening all figures at once.
EDIT: Following ImportanceOfBeingErnest sugestion I changed the code to
Y, X = np.mgrid[-1:10:200j, 0:10:200j]
tau_x = [0.01, 0.1, 1., 10.]
tau_y = [0.01, 0.1, 1., 10.]
alpha = [0.01, 0.1, 1., 10.]
r = [0.1, 0.01, 0.001]
K = [0.1, 0.5, 1.0, 1.5]
combinations_list = list(itertools.product(tau_x,tau_y,alpha,r,K))
length = len(combinations_list)
N = 9 #number of subplots per figure
for i in range(0,100):
subset = combinations_list[9*i:9*i+9]
fig = plt.figure()
j = 1
for a in subset:
(tau_x, tau_y, alpha, r, K) = a
Fx = (1/tau_x) * ( (-8/3)*(2*r-alpha)*(X-1) + K*X )
Fy = (2/(tau_y*X**(3/2))) * ( -2*(Y-1) + 3*Y*(X-1)/X + K*X*Y )
ax = fig.add_subplot(3,3,j)
ax.streamplot(X, Y, Fx, Fy, linewidth=0.5)
++j
plt.show()
but it's only plotting the first one of each subset and in a weird way with colors in the vectors.

You are not updating j correctly. ++j doesn't update the value of j. Your code will work fine if you replace ++j by j += 1 or j = j+1. Both are equivalent.
for i in range(0,100):
subset = combinations_list[9*i:9*i+9]
fig = plt.figure()
j = 1
for a in subset:
(tau_x, tau_y, alpha, r, K) = a
Fx = (1/tau_x) * ( (-8/3)*(2*r-alpha)*(X-1) + K*X )
Fy = (2/(tau_y*X**(3/2))) * ( -2*(Y-1) + 3*Y*(X-1)/X + K*X*Y )
ax = fig.add_subplot(3,3,j)
ax.streamplot(X, Y, Fx, Fy, linewidth=0.5)
j += 1 # <--- change here

Related

How to plot same colors for same values in a map?

I'm creating a colorbar with the function make_colormap. Source: Create own colormap using matplotlib and plot color scale.
Also i'm plotting many maps with for month, data in normals.groupby('MONTH'):
I want to create a color bar with the same values for the same colors (to be able to compare values in maps) but in the:
rvb = make_colormap(
[c('brown'), c('orange'), 0.10, c('orange'), c('yellow'), 0.20, c('green'), c('cyan'), 0.66, c('blue'), c('purple') ])
I can only put percentages. Do you know how can i modify this to put exact values instead of percentages?
import matplotlib.colors as mcolors
def make_colormap(seq):
"""Return a LinearSegmentedColormap
seq: a sequence of floats and RGB-tuples. The floats should be increasing
and in the interval (0,1).
"""
seq = [(None,) * 3, 0.0] + list(seq) + [1.0, (None,) * 3]
cdict = {'red': [], 'green': [], 'blue': []}
for i, item in enumerate(seq):
if isinstance(item, float):
r1, g1, b1 = seq[i - 1]
r2, g2, b2 = seq[i + 1]
cdict['red'].append([item, r1, r2])
cdict['green'].append([item, g1, g2])
cdict['blue'].append([item, b1, b2])
return mcolors.LinearSegmentedColormap('CustomMap', cdict)
c = mcolors.ColorConverter().to_rgb
rvb = make_colormap(
[c('brown'), c('orange'), 0.10, c('orange'), c('yellow'), 0.20, c('green'), c('cyan'), 0.66, c('blue'), c('purple') ])
for month, data in normals.groupby('MONTH'):
lons, lats= np.array(data['LONGITUDE']), np.array(data['LATITUDE'])
ppvalues=np.array(data['PP']).astype(int)
month = data['MONTH'].iloc[0]
fig = plt.figure('map', figsize=(7,7), dpi=200)
ax = fig.add_axes([0.1, 0.12, 0.80, 0.75], projection=ccrs.PlateCarree())
plt.xlabel('LONGITUDE')
plt.ylabel('LATITUDE')
ax.outline_patch.set_linewidth(0.3)
l = NaturalEarthFeature(category='cultural', name='admin_0_countries', scale='50m', facecolor='none')
ax.add_feature(l, edgecolor='black', linewidth=0.25)
img = ax.scatter(lons, lats, s=7, c=ppvalores, cmap=rvb,
marker='o', transform=ccrs.PlateCarree())
#ticks=[0,1,2,3,4,5,6,7,8,9,10]
cb = plt.colorbar(img, extend='both',
spacing='proportional', orientation='horizontal',
cax=fig.add_axes([0.12, 0.12, 0.76, 0.02]))
plt.show()
fig.savefig("path/".png")
I'm relatively new in python so would you mind to help me?
Thanks in advance.
You could apply a norm. Using the same norm for all plots would make the colors consistent. It is unclear what the range of your data['PP'] column is. Here is an example of the changes if you would like 100, 200 and 660 for the three values in the list given to make_colormap:
vmin = data['PP'].min() # the overall minimum
vmax = data['PP'].max() # the overall maximum
norm = plt.Normalize(vmin, vmax) # function that maps the range of data['PP'] to the range [0,1]
rvb = make_colormap(
[c('brown'), c('orange'), norm(100), c('orange'), c('yellow'), norm(200), c('green'), c('cyan'), norm(660), c('blue'), c('purple')])
for month, data in normals.groupby('MONTH'):
...
img = ax.scatter(..., cmap=rvb, norm=norm)
...

Matplotlib Logarithmic Radar Charts - Remove all values below 0.5 and show last ytick

If you look in the logarithmic radar chart below, there are two changes I would like, if anyone knows the correct way to code:
1)Display a ytick label for the max value (51.81), as it currently gives the top value as 31.62
2)A way to set all values below 0.1 to 0, without causing divide by zero errors.
fig = plt.figure(figsize=(8, 8))
ax = fig.add_subplot(111, polar=True)
np.seterr(divide = 'warn')
sample = samplelistmalshare
get_mag = lambda x: 10**min(np.floor(np.log10(x)))
init_mag = get_mag(sample)
print("init_mag")
print(init_mag)
print("gm")
print(get_mag)
sample = np.array(sample) / get_mag(sample)
N = len(sample)
theta = np.arange(0, 2 * np.pi, 2 * np.pi / N)
bars = ax.bar(theta, np.log10(sample), width=0.4, color = '#003F5C')
ax.set_xticks(theta)
ax.set_xticklabels([' Delayed\n Execution', ' File\n Opening', 'Firewall\nModification', 'Permission \nModification ', 'Persistence ', 'Proxied \nExecution ', 'Reconnaissance ', ' Registry\n Modification', ' Task\n Stopping'], visible=False)
dat = np.log10(sample)
print(max(dat))
#exit()
ax.set_ylim(0,max(dat))
ax.xaxis.grid(False)
ax.yaxis.grid(True)
precision = 2 # Change to your desired decimal precision
ax.set_yticklabels([str(round((10 ** x) * init_mag, precision)) for x in ax.get_yticks()])
for test in ax.get_yticks():
print(test)
for test in ax.get_ymajorticklabels():
print(test)
ax.set_rlabel_position(50)
plt.savefig('radarchartingmalshare.pdf',bbox_inches='tight')
fig.clf()
plt.clf()
One solution is to set yticks and yticklabels manually
right_end = 51.81
ax.set_ylim(0,np.log10(right_end / init_mag))
y_ticks = np.linspace(0,np.log10(right_end/init_mag),10)
ax.set_yticks(y_ticks)
y_ticklabels = ['%.2f' % (init_mag*10**x) if x !=0 else '0.00' for x in ax.get_yticks()]
ax.set_yticklabels(y_ticklabels)
With this manually set ticks and the labels
import numpy as np
from matplotlib import pyplot as plt
fig = plt.figure(figsize=(8, 8));
ax = fig.add_subplot(111, polar=True)
np.seterr(divide = 'warn')
sample = [35.417256011315416,0.028288543140028287,1.3578500707213579,3.3663366336633667,
0.8203677510608205,35.445544554455445,3.3946251768033946,19.46251768033946,0.7072135785007072,]
get_mag = lambda x: 10**min(np.floor(np.log10(x)))
init_mag = get_mag(sample)
sample = np.array(sample) / get_mag(sample)
dat = np.log10(sample)
N = len(sample)
theta = np.arange(0, 2 * np.pi, 2 * np.pi / N)
bars = ax.bar(theta, dat, width=0.4, color = 'deepskyblue')
ax.set_xticks(theta)
ax.xaxis.grid(False)
right_end = 51.81
ax.set_ylim(0,np.log10(right_end / init_mag))
ax.yaxis.grid(True)
y_ticks = np.linspace(0,np.log10(right_end/init_mag),10)
ax.set_yticks(y_ticks)
y_ticklabels = ['%.2f' % (init_mag*10**x) if x !=0 else '0.00' for x in ax.get_yticks()]
ax.set_yticklabels(y_ticklabels)
ax.tick_params(axis='y',colors='darkviolet')
plt.show()

I can't get same point with homography matrix reverse transform

I get invert of homography matrix
self.inv_homography = np.linalg.inv(self.homography)
and my trasnform function
def doTransform(x, y, homography):
p = np.ndarray(shape=(3, 1), dtype=float, order='F')
p[0, 0] = x
p[1, 0] = y
p[2, 0] = 1
res = np.dot(homography, p)
return res
but third row is not same with first row, there is some pixel slip
ref coords :(768, 512, 1024, 768)
ref to wa coords: 569.5178327464915 185.9395922739289 790.8947327112375 448.7356913249636
wa to ref coords: 767.149391928569 510.19931575332294 1022.283053230326 764.3653307505839
how do I fix this slip ?
I think that you have hardcoded the z coordinate might be the problem. If the z coordinate does not transform to exactly 1, you will introduce an error. This code returns the expected output:
import numpy as np
def transform(x, y, z, homography):
p = np.array([x,y,z]).reshape(3,1)
return np.dot(homography, p)
hom = np.array([1.2,3.1, 4.0, 2.4, 5.4, 3.2, 1.1, 3.0, 1.2]).reshape(3,3)
x, y, z = 2.3, 1.7, 1
inv_hom = np.linalg.inv(hom)
x_wa = transform(x, y, z, hom)[0, 0]
y_wa = transform(x, y, z, hom)[1, 0]
z_wa = transform(x, y, z, hom)[2, 0]
print(transform(x_wa, y_wa, z_wa, inv_hom))
>>[[2.3]
[1.7]
[1. ]]

Matplotlib bar charts: Aligning two different y axes to zero

I have two sets of data in a barchart which have very different axes: one is very negative (-7500) and one is slightly positive (+5).
How can I have the two y axes aligned at 0, yet still be a good size? Using set_ylim means you can't see the second data set.
Current code I'm using:
A165H = [-4915, -7037]
B167H = [-6927, -4105]
B186H = [-5597, 0]
CH =[0, 0]
ConH = [0, 0]
# Lists of dS values
A165S = [6.28,-4.91]
B167S = [-3.25, 6.7]
B186S = [3.93, 0]
CS = [0, 0]
ConS = [0, 0]
N1H = [A165H[0], B167H[0], B186H[0], CH[0], ConH[0]]
N1S = [A165S[0], B167S[0], B186S[0], CS[0], ConS[0]]
print(N1H)
print(N1S)
N2H = [A165H[1], B167H[1], B186H[1], CH[1], ConH[1]]
N2S = [A165S[1], B167S[1], B186S[1], CS[1], ConS[1]]
width = 0.35
fig, ax1 = plt.subplots()
ind = np.arange(len(N1H))
rects1 = ax1.bar(ind, N1H, width, color = 'b')
ax1.set_xticks(ind+width)
ax1.set_xticklabels(('A165', 'B167', 'B186', 'C', 'Con'))
ax1.set_ylabel('dH', color='b')
for tl in ax1.get_yticklabels():
tl.set_color('b')
ax2 = ax1.twinx()
rects2 = ax2.bar(ind + width, N1S, width, color = 'r')
ax2.set_ylabel('dS', color='r')
for tl in ax2.get_yticklabels():
tl.set_color('r')
plt.show()
Here is my standard image
EDIT:
using the align_yaxis() from this question only shows me the negative values of the second data set:
If I had carried on reading the the other post I would have found the adjust_yaxis which solved my problem
The code given on that answer:
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
adjust_yaxis(ax2,(y1-y2)/2,v2)
adjust_yaxis(ax1,(y2-y1)/2,v1)
def adjust_yaxis(ax,ydif,v):
"""shift axis ax by ydiff, maintaining point v at the same location"""
inv = ax.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
miny, maxy = ax.get_ylim()
miny, maxy = miny - v, maxy - v
if -miny>maxy or (-miny==maxy and dy > 0):
nminy = miny
nmaxy = miny*(maxy+dy)/(miny+dy)
else:
nmaxy = maxy
nminy = maxy*(miny+dy)/(maxy+dy)
ax.set_ylim(nminy+v, nmaxy+v)

Matplotlib axis with two scales shared origin

I need two overlay two datasets with different Y-axis scales in Matplotlib. The data contains both positive and negative values. I want the two axes to share one origin, but Matplotlib does not align the two scales by default.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax1.bar(range(6), (2, -2, 1, 0, 0, 0))
ax2.plot(range(6), (0, 2, 8, -2, 0, 0))
plt.show()
I suppose it is possible to perform some computation with .get_ylim() and .set_ylim() two align the two scales. Is there an easier solution?
use the align_yaxis() function:
import numpy as np
import matplotlib.pyplot as plt
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
fig = plt.figure()
ax1 = fig.add_subplot(111)
ax2 = ax1.twinx()
ax1.bar(range(6), (2, -2, 1, 0, 0, 0))
ax2.plot(range(6), (0, 2, 8, -2, 0, 0))
align_yaxis(ax1, 0, ax2, 0)
plt.show()
In order to ensure that the y-bounds are maintained (so no data points are shifted off the plot), and to balance adjustment of both y-axes, I made some additions to #HYRY's answer:
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
adjust_yaxis(ax2,(y1-y2)/2,v2)
adjust_yaxis(ax1,(y2-y1)/2,v1)
def adjust_yaxis(ax,ydif,v):
"""shift axis ax by ydiff, maintaining point v at the same location"""
inv = ax.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
miny, maxy = ax.get_ylim()
miny, maxy = miny - v, maxy - v
if -miny>maxy or (-miny==maxy and dy > 0):
nminy = miny
nmaxy = miny*(maxy+dy)/(miny+dy)
else:
nmaxy = maxy
nminy = maxy*(miny+dy)/(maxy+dy)
ax.set_ylim(nminy+v, nmaxy+v)
#drevicko's answer fails for me when plotting the following two sequences of points:
l1 = [0.03, -0.6, 1, 0.05]
l2 = [0.8, 0.9, 1, 1.1]
fig, ax1 = plt.subplots()
ax1.plot(l1)
ax2 = ax1.twinx()
ax2.plot(l2, color='r')
align_yaxis(ax1, 0, ax2, 0)
... so here's my version:
def align_yaxis(ax1, ax2):
"""Align zeros of the two axes, zooming them out by same ratio"""
axes = (ax1, ax2)
extrema = [ax.get_ylim() for ax in axes]
tops = [extr[1] / (extr[1] - extr[0]) for extr in extrema]
# Ensure that plots (intervals) are ordered bottom to top:
if tops[0] > tops[1]:
axes, extrema, tops = [list(reversed(l)) for l in (axes, extrema, tops)]
# How much would the plot overflow if we kept current zoom levels?
tot_span = tops[1] + 1 - tops[0]
b_new_t = extrema[0][0] + tot_span * (extrema[0][1] - extrema[0][0])
t_new_b = extrema[1][1] - tot_span * (extrema[1][1] - extrema[1][0])
axes[0].set_ylim(extrema[0][0], b_new_t)
axes[1].set_ylim(t_new_b, extrema[1][1])
There are in principle infinite different possibilities to align the zeros (or other values, which the other provided solutions accept): wherever you place zero on the y axis, you can zoom each of the two series so that it fits. We just pick the position such that, after the transformation, the two cover a vertical interval of same height.
Or in other terms, we minimize them of a same factor compared to the non-aligned plot.
(This does not mean that 0 is at half of the plot: this will happen e.g. if one plot is all negative and the other all positive.)
Numpy version:
def align_yaxis_np(ax1, ax2):
"""Align zeros of the two axes, zooming them out by same ratio"""
axes = np.array([ax1, ax2])
extrema = np.array([ax.get_ylim() for ax in axes])
tops = extrema[:,1] / (extrema[:,1] - extrema[:,0])
# Ensure that plots (intervals) are ordered bottom to top:
if tops[0] > tops[1]:
axes, extrema, tops = [a[::-1] for a in (axes, extrema, tops)]
# How much would the plot overflow if we kept current zoom levels?
tot_span = tops[1] + 1 - tops[0]
extrema[0,1] = extrema[0,0] + tot_span * (extrema[0,1] - extrema[0,0])
extrema[1,0] = extrema[1,1] + tot_span * (extrema[1,0] - extrema[1,1])
[axes[i].set_ylim(*extrema[i]) for i in range(2)]
The other answers here seem overly complicated and don't necessarily work for all the scenarios (e.g. ax1 is all negative and ax2 is all positive). There are 2 easy methods that always work:
Always put 0 in the middle of the graph for both y axes
A bit fancy and somewhat preserves the positive-to-negative ratio, see below
def align_yaxis(ax1, ax2):
y_lims = numpy.array([ax.get_ylim() for ax in [ax1, ax2]])
# force 0 to appear on both axes, comment if don't need
y_lims[:, 0] = y_lims[:, 0].clip(None, 0)
y_lims[:, 1] = y_lims[:, 1].clip(0, None)
# normalize both axes
y_mags = (y_lims[:,1] - y_lims[:,0]).reshape(len(y_lims),1)
y_lims_normalized = y_lims / y_mags
# find combined range
y_new_lims_normalized = numpy.array([numpy.min(y_lims_normalized), numpy.max(y_lims_normalized)])
# denormalize combined range to get new axes
new_lim1, new_lim2 = y_new_lims_normalized * y_mags
ax1.set_ylim(new_lim1)
ax2.set_ylim(new_lim2)
I've cooked up a solution starting from the above that will align any number of axes:
def align_yaxis_np(axes):
"""Align zeros of the two axes, zooming them out by same ratio"""
axes = np.array(axes)
extrema = np.array([ax.get_ylim() for ax in axes])
# reset for divide by zero issues
for i in range(len(extrema)):
if np.isclose(extrema[i, 0], 0.0):
extrema[i, 0] = -1
if np.isclose(extrema[i, 1], 0.0):
extrema[i, 1] = 1
# upper and lower limits
lowers = extrema[:, 0]
uppers = extrema[:, 1]
# if all pos or all neg, don't scale
all_positive = False
all_negative = False
if lowers.min() > 0.0:
all_positive = True
if uppers.max() < 0.0:
all_negative = True
if all_negative or all_positive:
# don't scale
return
# pick "most centered" axis
res = abs(uppers+lowers)
min_index = np.argmin(res)
# scale positive or negative part
multiplier1 = abs(uppers[min_index]/lowers[min_index])
multiplier2 = abs(lowers[min_index]/uppers[min_index])
for i in range(len(extrema)):
# scale positive or negative part based on which induces valid
if i != min_index:
lower_change = extrema[i, 1] * -1*multiplier2
upper_change = extrema[i, 0] * -1*multiplier1
if upper_change < extrema[i, 1]:
extrema[i, 0] = lower_change
else:
extrema[i, 1] = upper_change
# bump by 10% for a margin
extrema[i, 0] *= 1.1
extrema[i, 1] *= 1.1
# set axes limits
[axes[i].set_ylim(*extrema[i]) for i in range(len(extrema))]
example on 4 random series (you can see the discrete ranges on the 4 separate sets of y axis labels):
#Tim's solution adapted to work for more than two axes:
import numpy as np
def align_yaxis(axes):
y_lims = np.array([ax.get_ylim() for ax in axes])
# force 0 to appear on all axes, comment if don't need
y_lims[:, 0] = y_lims[:, 0].clip(None, 0)
y_lims[:, 1] = y_lims[:, 1].clip(0, None)
# normalize all axes
y_mags = (y_lims[:,1] - y_lims[:,0]).reshape(len(y_lims),1)
y_lims_normalized = y_lims / y_mags
# find combined range
y_new_lims_normalized = np.array([np.min(y_lims_normalized), np.max(y_lims_normalized)])
# denormalize combined range to get new axes
new_lims = y_new_lims_normalized * y_mags
for i, ax in enumerate(axes):
ax.set_ylim(new_lims[i])
I needed to align two subplots but not at their zeros. And other solutions didn't quite work for me.
The main code of my program looks like this. The subplots are not aligned. Further I only change align_yaxis function and keep all other code the same.
import matplotlib.pyplot as plt
def align_yaxis(ax1, v1, ax2, v2):
return 0
x = range(10)
y1 = [3.2, 1.3, -0.3, 0.4, 2.3, -0.9, 0.2, 0.1, 1.3, -3.4]
y2, s = [], 100
for i in y1:
s *= 1 + i/100
y2.append(s)
fig = plt.figure()
ax1 = fig.add_subplot()
ax2 = ax1.twinx()
ax1.axhline(y=0, color='k', linestyle='-', linewidth=0.5)
ax1.bar(x, y1, color='tab:blue')
ax2.plot(x, y2, color='tab:red')
fig.tight_layout()
align_yaxis(ax1, 0, ax2, 100)
plt.show()
Picture of not aligned subplots
Using #HYRY's solution I get aligned subplots, but the second subplot is out of the figure. You can't see it.
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
inv = ax2.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, y1-y2))
miny, maxy = ax2.get_ylim()
ax2.set_ylim(miny+dy, maxy+dy)
Picture without second subplot
Using #drevicko's solution I also get aligned plot. But now the first subplot is out of the picture and first Y axis is quite weird.
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
adjust_yaxis(ax2,(y1-y2)/2,v2)
adjust_yaxis(ax1,(y2-y1)/2,v1)
def adjust_yaxis(ax,ydif,v):
"""shift axis ax by ydiff, maintaining point v at the same location"""
inv = ax.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
miny, maxy = ax.get_ylim()
miny, maxy = miny - v, maxy - v
if -miny>maxy or (-miny==maxy and dy > 0):
nminy = miny
nmaxy = miny*(maxy+dy)/(miny+dy)
else:
nmaxy = maxy
nminy = maxy*(miny+dy)/(maxy+dy)
ax.set_ylim(nminy+v, nmaxy+v)
Picture without firstsubplot
So I've tuned #drevicko's solution a little and got what I wanted.
def align_yaxis(ax1, v1, ax2, v2):
"""adjust ax2 ylimit so that v2 in ax2 is aligned to v1 in ax1"""
_, y1 = ax1.transData.transform((0, v1))
_, y2 = ax2.transData.transform((0, v2))
adjust_yaxis(ax1,(y2 - y1)/2,v1)
adjust_yaxis(ax2,(y1 - y2)/2,v2)
def adjust_yaxis(ax,ydif,v):
"""shift axis ax by ydiff, maintaining point v at the same location"""
inv = ax.transData.inverted()
_, dy = inv.transform((0, 0)) - inv.transform((0, ydif))
miny, maxy = ax.get_ylim()
nminy = miny - v + dy - abs(dy)
nmaxy = maxy - v + dy + abs(dy)
ax.set_ylim(nminy+v, nmaxy+v)
Subplots as I've expected them to look
This might not be what you are looking for but this helped me get whole numbers to line up on two different vertical axis:
ax1.set_ylim(0,4000)
ax2.set_ylim(0,120)
ax2.set_yticks(np.linspace(ax2.get_yticks()[0], ax2.get_yticks()[-1], len(ax1.get_yticks())))