How to add a subplot to a plot with control widgets - python-3.x

I would like to extend solution of the question "How to keep some text relative to the line into the plot when the plot changes" with two subplots controlled with a pair of widget sliders.
The code I have tried it makes no errors, but it does not show the Figure nor the controls.
from ipywidgets import widgets
from IPython.display import display
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
fig1, ax1 = plt.subplots(211)
line1, = ax1.semilogx([],[], label='Multipath')
hline1 = ax1.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text1 = ax1.text(0, 0, "T Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax1.get_yaxis_transform(),
color='brown', fontsize=10)
ax1.set_xlabel('Separation Distance, r (m)')
ax1.set_ylabel('Received Power, $P_t$ (dBm)')
ax1.grid(True,which="both",ls=":")
ax1.legend()
fig2, ax2 = plt.subplots(212)
line2, = ax2.semilogx([],[], label='Monostatic Link')
hline2 = ax2.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text2 = ax2.text(0, 0, "R Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax2.get_yaxis_transform(),
color='brown', fontsize=10)
ax2.set_xlabel('Separation Distance, r (m)')
ax2.set_ylabel('Received Power, $P_t$ (dBm)')
ax2.grid(True,which="both",ls=":")
ax2.legend()
def update_plot(h1, h2):
D = np.arange(0.5, 12.0, 0.0100)
r = np.sqrt((h1-h2)**2 + D**2)
freq = 865.7 #freq = 915 MHz
lmb = 300/freq
H = D**2/(D**2+2*h1*h2)
theta = 4*np.pi*h1*h2/(lmb*D)
q_e = H**2*(np.sin(theta))**2 + (1 - H*np.cos(theta))**2
q_e_rcn1 = 1
P_x_G = 4 # 4 Watt EIRP
sigma = 1.94
N_1 = np.random.normal(0,sigma,D.shape)
rnd = 10**(-N_1/10)
F = 10
y = 10*np.log10( 1000*(P_x_G*1.622*((lmb)**2) *0.5*1) / (((4*np.pi*r)**2) *1.2*1*F)*q_e*rnd*q_e_rcn1 )
line1.set_data(r,y)
hline1.set_ydata(-18)
text1.set_position((0.02, -18.5))
ax1.relim()
ax1.autoscale_view()
fig1.canvas.draw_idle()
######################################
rd =np.sqrt((h1-h2)**2 + D**2)
rd = np.sort(rd)
P_r=0.8
G_r=5 # 7dBi
q_e_rcn2 = 1
N_2 = np.random.normal(0, sigma*2, D.shape)
rnd_2 = 10**(-N_2/10)
F_2 = 126 # 21 dB for K=3dB and P_outage = 0.05
y = 10*np.log10( 1000*(P_r*(G_r*1.622)**2*(lmb)**4*0.5**2*0.25)/((4*np.pi*rd)**4*1.2**2*1**2*F_2)*
q_e**2*rnd*rnd_2*q_e_rcn1*q_e_rcn2 )
line2.set_data(rd,y)
hline2.set_ydata(-80)
text2.set_position((0.02, -80.5))
ax2.relim()
ax2.autoscale_view()
fig2.canvas.draw_idle()
r_height = widgets.FloatSlider(min=0.5, max=4, value=0.9, description= 'R_Height:')
t_height = widgets.FloatSlider(min=0.15, max=1.5, value=0.5, description= 'T_Height:')
widgets.interactive(update_plot, h1=r_height, h2=t_height)
In the second subplot, there would be a horizontal line at y=-80 with a text that should move in a similar way as the first subplot of the figure.
How could I add the second subplot using the same controls?
Regards.

This code does produce an error. The problem is that you are creating a figure with 211 (i.e. twohundredeleven) subplots. Those are stored in an array called ax1 and this array does not have a .semilogx method. Hence the error AttributeError: 'numpy.ndarray' object has no attribute 'semilogx'.
So, what you need instead is only two subplots, which you may directly unpack.
fig, (ax1, ax2) = plt.subplots(nrows=2)
The rest is basically adjusting the code for the new situation. It should then look like this:
from ipywidgets import widgets
from IPython.display import display
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
fig, (ax1, ax2) = plt.subplots(nrows=2)
line1, = ax1.semilogx([],[], label='Multipath')
hline1 = ax1.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text1 = ax1.text(0, 0, "T Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax1.get_yaxis_transform(),
color='brown', fontsize=10)
ax1.set_xlabel('Separation Distance, r (m)')
ax1.set_ylabel('Received Power, $P_t$ (dBm)')
ax1.grid(True,which="both",ls=":")
ax1.legend()
line2, = ax2.semilogx([],[], label='Monostatic Link')
hline2 = ax2.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text2 = ax2.text(0, 0, "R Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax2.get_yaxis_transform(),
color='brown', fontsize=10)
ax2.set_xlabel('Separation Distance, r (m)')
ax2.set_ylabel('Received Power, $P_t$ (dBm)')
ax2.grid(True,which="both",ls=":")
ax2.legend()
def update_plot(h1, h2):
D = np.arange(0.5, 12.0, 0.0100)
r = np.sqrt((h1-h2)**2 + D**2)
freq = 865.7 #freq = 915 MHz
lmb = 300/freq
H = D**2/(D**2+2*h1*h2)
theta = 4*np.pi*h1*h2/(lmb*D)
q_e = H**2*(np.sin(theta))**2 + (1 - H*np.cos(theta))**2
q_e_rcn1 = 1
P_x_G = 4 # 4 Watt EIRP
sigma = 1.94
N_1 = np.random.normal(0,sigma,D.shape)
rnd = 10**(-N_1/10)
F = 10
y = 10*np.log10( 1000*(P_x_G*1.622*((lmb)**2) *0.5*1) / (((4*np.pi*r)**2) *1.2*1*F)*q_e*rnd*q_e_rcn1 )
line1.set_data(r,y)
hline1.set_ydata(-18)
text1.set_position((0.02, -18.5))
ax1.relim()
ax1.autoscale_view()
######################################
rd =np.sqrt((h1-h2)**2 + D**2)
rd = np.sort(rd)
P_r=0.8
G_r=5 # 7dBi
q_e_rcn2 = 1
N_2 = np.random.normal(0, sigma*2, D.shape)
rnd_2 = 10**(-N_2/10)
F_2 = 126 # 21 dB for K=3dB and P_outage = 0.05
y = 10*np.log10( 1000*(P_r*(G_r*1.622)**2*(lmb)**4*0.5**2*0.25)/((4*np.pi*rd)**4*1.2**2*1**2*F_2)*
q_e**2*rnd*rnd_2*q_e_rcn1*q_e_rcn2 )
line2.set_data(rd,y)
hline2.set_ydata(-80)
text2.set_position((0.02, -80.5))
ax2.relim()
ax2.autoscale_view()
fig.canvas.draw_idle()
r_height = widgets.FloatSlider(min=0.5, max=4, value=0.9, description= 'R_Height:')
t_height = widgets.FloatSlider(min=0.15, max=1.5, value=0.5, description= 'T_Height:')
widgets.interactive(update_plot, h1=r_height, h2=t_height)

Related

How to fill between two lines with different x and y?

How to fill between two lines with different x and y? Now, the filling is for two y functions with the common x-axis, which is not true. When I tried x1, x2, y1, y2 I have got a worse result than displayed below.
import matplotlib.pyplot as plt
import numpy as np
from numpy import exp, sin
def g(y):
amp = 0.6
return amp*exp(-2.5*y)*sin(9.8*y)
def g_e(y):
amp = 0.66
return amp*exp(-2.5*y_e)*sin(8.1*y_e)
y = np.linspace(0, 0.83, 501)
y_e = np.linspace(0, 1.08, 501)
values = g(y)
values_e = g_e(y)
theta = np.radians(-65.9)
c, s = np.cos(theta), np.sin(theta)
rot_matrix = np.array(((c, s), (-s, c)))
xy = np.array([y, values]).T # rot_matrix
theta_e = np.radians(-60)
c_e, s_e = np.cos(theta_e), np.sin(theta_e)
rot_matrix_e = np.array(((c_e, s_e), (-s_e, c_e)))
xy_e = np.array([y, values_e]).T # rot_matrix_e
fig, ax = plt.subplots(figsize=(5,5))
ax.axis('equal')
x_shift = 0.59
y_shift = 0.813
x_shift_e = 0.54
y_shift_e = 0.83
ax.plot(xy[:, 0]+x_shift, xy[:, 1]+y_shift, c='red')
ax.plot(xy_e[:, 0]+x_shift_e, xy_e[:, 1]+y_shift_e, c='black')
ax.fill_between(xy[:, 0]+x_shift, xy[:, 1]+y_shift, xy_e[:, 1]+y_shift_e)
plt.show()
Script for additional question:
for i in range(len(x)-1):
for j in range(i-1):
xs_ys = intersection(x[i],x[i+1],x[j],x[j+1],y[i],y[i+1],y[j],y[j+1])
if xs_ys in not None:
xs.append(xs_ys[0])
ys.append(xs_ys[1])
I got an error:
if xs_ys in not None:
^
SyntaxError: invalid syntax
Here is an approach creating a "polygon" by concatenating the reverse of one curve to the other curve. ax.fill() can be used to fill the polygon. Note that fill_between() can look strange when the x-values aren't nicely ordered (as is the case here after the rotation). Also, the mirror function fill_betweenx() wouldn't be adequate in this case.
import matplotlib.pyplot as plt
import numpy as np
def g(y):
amp = 0.6
return amp * np.exp(-2.5 * y) * np.sin(9.8 * y)
def g_e(y):
amp = 0.66
return amp * np.exp(-2.5 * y_e) * np.sin(8.1 * y_e)
y = np.linspace(0, 0.83, 501)
y_e = np.linspace(0, 1.08, 501)
values = g(y)
values_e = g_e(y)
theta = np.radians(-65.9)
c, s = np.cos(theta), np.sin(theta)
rot_matrix = np.array(((c, s), (-s, c)))
xy = np.array([y, values]).T # rot_matrix
theta_e = np.radians(-60)
c_e, s_e = np.cos(theta_e), np.sin(theta_e)
rot_matrix_e = np.array(((c_e, s_e), (-s_e, c_e)))
xy_e = np.array([y, values_e]).T # rot_matrix_e
fig, ax = plt.subplots(figsize=(5, 5))
ax.axis('equal')
x_shift = 0.59
y_shift = 0.813
x_shift_e = 0.54
y_shift_e = 0.83
xf = np.concatenate([xy[:, 0] + x_shift, xy_e[::-1, 0] + x_shift_e])
yf = np.concatenate([xy[:, 1] + y_shift, xy_e[::-1, 1] + y_shift_e])
ax.plot(xy[:, 0] + x_shift, xy[:, 1] + y_shift, c='red')
ax.plot(xy_e[:, 0] + x_shift_e, xy_e[:, 1] + y_shift_e, c='black')
ax.fill(xf, yf, color='dodgerblue', alpha=0.3)
plt.show()

Using colormap in cycle (python)

How to edit the for cycles under #ax5 and #ax6 to plot graphs in the same fashion? Now, the lower figure has no colour transit, as opposed to the upper one. The colour transit appears in the lower figure after increasing of dpi, however, some unwanted stuff also appears. Is there a scalling problem? Thank you
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec
import math
fig, ax = plt.subplots()
plt.rcParams["figure.figsize"] = [8, 8]
# Function for plotting parallels to curves
def get_parallels(length=.1):
px, py = [], []
for idx in range(len(x)-1):
x0, y0, xa, ya = x[idx], y[idx], x[idx+1], y[idx+1]
dx, dy = xa-x0, ya-y0
norm = math.hypot(dx, dy) * 1/length
dx /= norm
dy /= norm
px.append(x0-dy)
py.append(y0+dx)
return px, py
def offset(x,y, o):
""" Offset coordinates given by array x,y by o """
X = np.c_[x,y].T
m = np.array([[0,-1],[1,0]])
R = np.zeros_like(X)
S = X[:,2:]-X[:,:-2]
R[:,1:-1] = np.dot(m, S)
R[:,0] = np.dot(m, X[:,1]-X[:,0])
R[:,-1] = np.dot(m, X[:,-1]-X[:,-2])
On = R/np.sqrt(R[0,:]**2+R[1,:]**2)*o
Out = On+X
return Out[0,:], Out[1,:]
dpi = 20
def offset_curve(ax, x,y, o):
""" Offset array x,y in data coordinates
by o in points """
trans = ax.transData.transform
inv = ax.transData.inverted().transform
X = np.c_[x,y]
Xt = trans(X)
xto, yto = offset(Xt[:,0],Xt[:,1],o*dpi/72. )
Xto = np.c_[xto, yto]
Xo = inv(Xto)
return Xo[:,0], Xo[:,1]
fig = plt.figure(constrained_layout=True)
gs = GridSpec(3, 6, figure=fig)
ax5 = fig.add_subplot(gs[1, 3:6])
ax6 = fig.add_subplot(gs[2, :3])
ax7 = fig.add_subplot(gs[2, 3:6])
cmap = plt.get_cmap('Greys_r')
# ax5
x = np.linspace(-1, 1, 100)
y = -x**2
ax5.set_ylim(-1.02, 0.3)
width_l = ax5.get_ylim()[1] - ax5.get_ylim()[0]
for t in np.linspace(0, 1, 40):
length = -0.1*width_l*t
ax5.plot(*get_parallels(length=length), color=cmap(t/2 + 0.25))
# ax6
x = np.linspace(-3, 3, 100)
y = -(1/4*x**4 - 1.6*x**2)
ax6.plot(x, y)
ax6.set_xlim(ax6.get_xlim()[0]-0.5, ax6.get_xlim()[1]+0.5)
ax6.scatter(1/2*(ax6.get_xlim()[0] + ax6.get_xlim()[1]), 1.2, marker = 'o', s=900, facecolors='none')
lines = []
width_l = ax6.get_ylim()[1] - ax6.get_ylim()[0]
for t in np.linspace(0, 1, 40):
l, = ax6.plot(x, y - t * 0.1 * width_l, color=cmap(t/2 + 0.25))
lines.append(l)
def plot_rainbow(event=None):
x0 = x
y0 = y
for i in range(len(lines)):
xx, yy = offset_curve(ax, x0, y0, -width_l)
lines[i].set_data(xx, yy)
lines[i].set_linewidth(1.1*width_l)
x0 = xx
y0 = yy
plot_rainbow()
fig.canvas.mpl_connect("resize_event", plot_rainbow)
fig.canvas.mpl_connect("button_release_event", plot_rainbow)
plt.savefig('fig.pdf')

How to plot the figure in the desired fashion?

How to edit this code to have the same width and colour map as in the following figure? The script is based on this question.
import numpy as np
import matplotlib.pyplot as plt
dpi = 100
def offset(x,y, o):
""" Offset coordinates given by array x,y by o """
X = np.c_[x,y].T
m = np.array([[0,-1],[1,0]])
R = np.zeros_like(X)
S = X[:,2:]-X[:,:-2]
R[:,1:-1] = np.dot(m, S)
R[:,0] = np.dot(m, X[:,1]-X[:,0])
R[:,-1] = np.dot(m, X[:,-1]-X[:,-2])
On = R/np.sqrt(R[0,:]**2+R[1,:]**2)*o
Out = On+X
return Out[0,:], Out[1,:]
def offset_curve(ax, x,y, o):
""" Offset array x,y in data coordinates
by o in points """
trans = ax.transData.transform
inv = ax.transData.inverted().transform
X = np.c_[x,y]
Xt = trans(X)
xto, yto = offset(Xt[:,0],Xt[:,1],o*dpi/72. )
Xto = np.c_[xto, yto]
Xo = inv(Xto)
return Xo[:,0], Xo[:,1]
x = np.linspace(-3, 3, 100)
y = -(1/4*x**4 - 1.6*x**2)
fig, ax=plt.subplots(figsize=(4,2.5), dpi=dpi)
cmap = plt.get_cmap('Greys_r')
lw = 2.
lines = []
width_l = ax.get_ylim()[1] - ax.get_ylim()[0]
for t in np.linspace(0, 1, 40):
l, = ax.plot(x, y - t * 0.1 * width_l, color=cmap(t/2 + 0.25))
lines.append(l)
def plot_rainbow(event=None):
# initialization of lists
xr, yr = 6*[None], 6*[None]
xr[0],yr[0] = offset_curve(ax, x,y, lw/2.)
xr[1],yr[1] = offset_curve(ax, x,y, -lw/2.)
xr[2],yr[2] = offset_curve(ax, xr[0],yr[0], lw)
xr[3],yr[3] = offset_curve(ax, xr[1],yr[1], -lw)
xr[4],yr[4] = offset_curve(ax, xr[2],yr[2], lw)
xr[5],yr[5] = offset_curve(ax, xr[3],yr[3], -lw)
for i in range(6):
lines[i].set_data(xr[i], yr[i])
plot_rainbow()
fig.canvas.mpl_connect("resize_event", plot_rainbow)
fig.canvas.mpl_connect("button_release_event", plot_rainbow)
plt.show()
The figure above was created by the following script:
import numpy as np
import matplotlib.pyplot as plt
import math
dpi = 100
# Function for plotting parallels to curves
def get_parallels(length=.1):
px, py = [], []
for idx in range(len(x)-1):
x0, y0, xa, ya = x[idx], y[idx], x[idx+1], y[idx+1]
dx, dy = xa-x0, ya-y0
norm = math.hypot(dx, dy) * 1/length
dx /= norm
dy /= norm
px.append(x0-dy)
py.append(y0+dx)
return px, py
fig, ax=plt.subplots(figsize=(4,2.5), dpi=dpi)
cmap = plt.get_cmap('Greys_r')
x = np.linspace(-1, 1, 100)
y = -x**2
ax.set_ylim(-1.02, 0.3)
ax.scatter(1/2*(ax.get_xlim()[0] + ax.get_xlim()[1]), 0.145, marker = 'o', s=900, facecolors='none')
width_l = ax.get_ylim()[1] - ax.get_ylim()[0]
for t in np.linspace(0, 1, 40):
length = -0.1*width_l*t
ax.plot(*get_parallels(length=length), color=cmap(t/2 + 0.25))
plt.tight_layout()
plt.show()
Several curves are plotted in camp and the length is set.
I would like to have the same "shadow" for the curve in the first scrip. How to do that, please?

How to keep some text relative to the line into the plot when the plot changes

I would like to put some text below a horizontal line in a plot that changes with changing two parameters. I use a function to plot and python widgets to set two controls that change the plot and at the same time change the distances of the x and y-axis.
My iPhyton code is:
from ipywidgets import widgets
from IPython.display import display
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
def update_plot(h1, h2):
plt.figure( figsize = (9,4) )
ax=plt.subplot(111)
D = np.arange(0.5, 12.0, 0.0100)
r = np.sqrt((h1-h2)**2 + D**2)
freq = 865.7 #freq = 915 MHz
lmb = 300/freq
H = D**2/(D**2+2*h1*h2)
theta = 4*np.pi*h1*h2/(lmb*D)
q_e = H**2*(np.sin(theta))**2 + (1 - H*np.cos(theta))**2
q_e_rcn1 = 1
P_x_G = 4 # 4 Watt EIRP
sigma = 1.94
N_1 = np.random.normal(0,sigma,D.shape)
rnd = 10**(-N_1/10)
F = 10 #
plt.semilogx(r,10*np.log10( 1000*(P_x_G*1.622*((lmb)**2) *0.5*1) / (((4*np.pi*r)**2) *1.2*1*F)*q_e*rnd*q_e_rcn1 ),
label='Multipath')
plt.axhline(y = -18, linewidth=1.2, color='black',ls='--')
#plt.text(0.60, -22, r"T Threshold")
ax.text(0.02, 0.37, "T Threshold",
verticalalignment='bottom', horizontalalignment='left',
transform=ax.transAxes,
color='brown', fontsize=10)
plt.xlabel('Separation Distance, r (m)')
plt.ylabel('Received Power, $P_t$ (dBm)')
plt.grid(True,which="both",ls=":")
plt.legend()
#####################
r_height = widgets.FloatSlider(min=0.5, max=4, value=0.9, description= 'R_Height:')
t_height = widgets.FloatSlider(min=0.15, max=1.5, value=0.5, description= 'T_Height:')
widgets.interactive(update_plot, h1=r_height, h2=t_height)
Matplotlib documentation in "Text in Matplotlib Plots" indicates different ways to place text into the plot. I have tried two forms: the set of the exact position into the plot (commented) and the relative position. The first way place the text close to the horizontal line but goes outside the frame when it changes the plot with the controls. The second way to keep the text at a distance from the y-axis but does not change as the horizontal line goes up and down when the parameters change.
I would like to find a way to put the text close to the horizontal line but at a certain distance of the y=0 axis.
Regards
I think you would want to position the text in a blended coordinate system where the x coordinate is the axes coordinate system and the y coordinate is the data coordinate system. Such blended coordinate systems is conveniently already available through the ax.get_yaxis_transform(). E.g. to place the text 2% away from the y axis, at a y coordinate of -18,
ax.text(0.02, -18, "T Threshold", transform=ax.get_yaxis_transform() )
In general you may want to create the plot only once, and then using the sliders, only update the relevant parts of it. In total this would look like:
from ipywidgets import widgets
from IPython.display import display
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
import ipywidgets
fig, ax = plt.subplots()
line, = ax.semilogx([],[], label='Multipath')
hline = ax.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text = ax.text(0, 0, "T Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax.get_yaxis_transform(),
color='brown', fontsize=10)
ax.set_xlabel('Separation Distance, r (m)')
ax.set_ylabel('Received Power, $P_t$ (dBm)')
ax.grid(True,which="both",ls=":")
ax.legend()
def update_plot(h1, h2):
D = np.arange(0.5, 12.0, 0.0100)
r = np.sqrt((h1-h2)**2 + D**2)
freq = 865.7 #freq = 915 MHz
lmb = 300/freq
H = D**2/(D**2+2*h1*h2)
theta = 4*np.pi*h1*h2/(lmb*D)
q_e = H**2*(np.sin(theta))**2 + (1 - H*np.cos(theta))**2
q_e_rcn1 = 1
P_x_G = 4 # 4 Watt EIRP
sigma = 1.94
N_1 = np.random.normal(0,sigma,D.shape)
rnd = 10**(-N_1/10)
F = 10
y = 10*np.log10( 1000*(P_x_G*1.622*((lmb)**2) *0.5*1) / (((4*np.pi*r)**2) *1.2*1*F)*q_e*rnd*q_e_rcn1 )
line.set_data(r,y)
hline.set_ydata(-18)
text.set_position((0.02, -18.5))
ax.relim()
ax.autoscale_view()
fig.canvas.draw_idle()
r_height = widgets.FloatSlider(min=0.5, max=4, value=0.9, description= 'R_Height:')
t_height = widgets.FloatSlider(min=0.15, max=1.5, value=0.5, description= 'T_Height:')
widgets.interactive(update_plot, h1=r_height, h2=t_height)
Note that the sliders will now appear below the plot, as they are created afterwards - this might a bearable downside given the advantages of a much better responsivity of the slider changes due to not recreating the complete plot at each slider movement.

x-Axis in scatter subplot plot does not follow changes of widgets controls [duplicate]

The next code plots three subplots.
from ipywidgets import widgets
from IPython.display import display
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
fig, (ax1, ax2,ax3) = plt.subplots(nrows=3, figsize=(10,9))
line1, = ax1.semilogx([],[], label='Multipath')
hline1 = ax1.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text1 = ax1.text(0, 0, "T Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax1.get_yaxis_transform(),
color='brown', fontsize=10)
#ax1.set_xlabel('Separation Distance, r (m)')
ax1.set_ylabel('Received Power, $P_t$ (dBm)')
ax1.grid(True,which="both",ls=":")
ax1.legend()
line2, = ax2.semilogx([],[], label='Monostatic Link')
hline2 = ax2.axhline(y = 0, linewidth=1.2, color='black',ls='--')
text2 = ax2.text(0, 0, "R Threshold",
verticalalignment='top', horizontalalignment='left',
transform=ax2.get_yaxis_transform(),
color='brown', fontsize=10)
#ax2.set_xlabel('Separation Distance, r (m)')
ax2.set_ylabel('Received Power, $P_t$ (dBm)')
ax2.grid(True,which="both",ls=":")
ax2.legend()
#line3, = ax3.semilogx([],[])
line3 = ax3.scatter([],[], c='blue', alpha=0.75, edgecolors='none', s=6)
ax3.set_xlabel('Separation Distance, r (m)')
ax3.set_ylabel('Probability of error')
ax3.grid(True,which="both",ls=":")
ax3.set_xscale('log')
#ax3.set_xlim((0.55,13.5))
ax3.set_ylim((0,1))
def update_plot(h1, h2):
D = np.arange(0.5, 12.0, 0.0100)
r = np.sqrt((h1-h2)**2 + D**2)
freq = 865.7 #freq = 915 MHz
lmb = 300/freq
H = D**2/(D**2+2*h1*h2)
theta = 4*np.pi*h1*h2/(lmb*D)
q_e = H**2*(np.sin(theta))**2 + (1 - H*np.cos(theta))**2
q_e_rcn1 = 1
P_x_G = 4 # 4 Watt EIRP
sigma = 1.94
N_1 = np.random.normal(0,sigma,D.shape)
rnd = 10**(-N_1/10)
F = 10
y = 10*np.log10( 1000*(P_x_G*1.622*((lmb)**2) *0.5*1) / (((4*np.pi*r)**2) *1.2*1*F)*q_e*rnd*q_e_rcn1 )
line1.set_data(r,y)
hline1.set_ydata(-18)
text1.set_position((0.02, -18.8))
ax1.relim()
ax1.autoscale_view()
######################################
rd =np.sqrt((h1-h2)**2 + D**2)
rd = np.sort(rd)
P_r=0.8
G_r=5 # 7dBi
q_e_rcn2 = 1
N_2 = np.random.normal(0, sigma*2, D.shape)
rnd_2 = 10**(-N_2/10)
F_2 = 126
y = 10*np.log10( 1000*(P_r*(G_r*1.622)**2*(lmb)**4*0.5**2*0.25)/((4*np.pi*rd)**4*1.2**2*1**2*F_2)*
q_e**2*rnd*rnd_2*q_e_rcn1*q_e_rcn2 )
line2.set_data(rd,y)
hline2.set_ydata(-80)
text2.set_position((0.02, -80.8))
ax2.relim()
ax2.autoscale_view()
#######################################
P_r = y
SNR = P_r - ( 20 + 10*np.log10(1.6*10**6)-174 )
CIR = P_r -( -100)
SNR_linear = 10**(SNR/10)
CIR_linear = (10**(CIR/10))/1000
SNIR = 1/( 1/SNR_linear + 1/CIR_linear )
K_dB = 3
K = 10**(K_dB/10)
BER = (1+K)/(2+2*K + SNIR)*np.exp(-3*SNIR/(2+K+SNIR))
prob_error = 1-((1-BER )**6)
#line3.set_data(rd,prob_error)
line3.set_offsets(np.c_[rd,prob_error])
ax3.relim()
ax3.autoscale_view()
fig.canvas.draw_idle()
r_height = widgets.FloatSlider(min=0.5, max=4, value=0.9, description= 'R_Height:')
t_height = widgets.FloatSlider(min=0.15, max=1.5, value=0.5, description= 'T_Height:')
widgets.interactive(update_plot, h1=r_height, h2=t_height)
Subplots 1st and 2nd change their axis limits with variations of the input parameters R_Height and T_Height. However, subplot 3rd does not make the relim() and autoscale() of the plot.
Is there any way to change the limits of the x-axis in a similar way of subplots 1st and 2nd?.
Regards
Both .relim() and .autoscale_view() do not take effect when the axes bounds have previously been set via .set_ylim(). So .set_ylim() needs to be removed from the code.
In addition updating the limits of a scatter plot (which is a matplotlib.collections.PathCollection) is a bit more complicated than for other plots.
You would first need to update the datalimits of the axes before calling autoscale_view(), because .relim() does not work with collections.
ax.ignore_existing_data_limits = True
ax.update_datalim(scatter.get_datalim(ax.transData))
ax.autoscale_view()
Here is a minimal reproducible example:
from ipywidgets import widgets
from IPython.display import display
import matplotlib.pyplot as plt
import numpy as np
%matplotlib notebook
x = np.arange(10)
fig, ax = plt.subplots()
scatter = ax.scatter(x,x, label="y = a*x+b")
ax.legend()
def update_plot(a, b):
y = a*x+b
scatter.set_offsets(np.c_[x,y])
ax.ignore_existing_data_limits = True
ax.update_datalim(scatter.get_datalim(ax.transData))
ax.autoscale_view()
fig.canvas.draw_idle()
a = widgets.FloatSlider(min=0.5, max=4, value=1, description= 'a:')
b = widgets.FloatSlider(min=0, max=40, value=10, description= 'b:')
widgets.interactive(update_plot, a=a, b=b)
As written in the documentation for Axes.relim(), Collections (which is the type returned by scatter()) are not supported at the moment.
Therefore you have to ajust the limits manually, something like
(...)
line3.set_offsets(np.c_[rd,prob_error])
ax3.set_xlim((min(rd),max(rd)))
ax3.set_ylim((min(prob_error),max(prob_error)))
It seems to me that all your plot share the same x values, though? If that's the case, you might want to use fig, (ax1, ax2,ax3) = plt.subplots((...), sharex=True). You will still have to set the ylim for ax3 by hand, but at least your x-axes will be the same across all subplots.
EDIT: I realize now that it looks like your data in ax3are bound between [0-1], and that you probably don't need to change the ylim() and that sharing the x-axis with the other subplots should be enough.

Resources