Changing marker colour on selection in matplotlib - python

I'm using matplotlib and I am trying to change the colour of a marker when it is selected. So far I am plotting the markers and adding a pick_event listener that calls an on_pick function, which then modifies the plot's marker properties. This isn't working because I can't figure out how to access the marker's properties. How do I do this?
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
#-----------------------------------------------
# Plots several points with cubic interpolation
#-----------------------------------------------
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(0, 10, num=6, endpoint=True)
y = abs(x**2)
xnew = np.linspace(0, 10, num=40, endpoint=True)
cubicInterp = interp1d(x, y, kind='cubic')
line, = ax.plot(x,y, 'o', picker=5) # 5 points tolerance
lineInterp = ax.plot(xnew,cubicInterp(xnew), '-')
#---------------
# Events
#---------------
def on_pick(event):
line.color='red'
thisline.color='red'
#-----------------------------
fig.canvas.mpl_connect('pick_event', on_pick)
plt.show()

You can use setp method to manipulate the plot elements and update the cavas. This works:
Code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
#-----------------------------------------------
# Plots several points with cubic interpolation
#-----------------------------------------------
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(0, 10, num=6, endpoint=True)
y = abs(x**2)
xnew = np.linspace(0, 10, num=40, endpoint=True)
cubicInterp = interp1d(x, y, kind='cubic')
line = ax.plot(x,y, 'o', picker=5) # 5 points tolerance
lineInterp = ax.plot(xnew,cubicInterp(xnew), '-')
#---------------
# Events
#---------------
def on_pick(event):
print "clicked"
plt.setp(line,'color','red')
fig.canvas.draw()
#-----------------------------
fig.canvas.mpl_connect('pick_event', on_pick)
plt.show()
Ouput:
Before:
After:

This wasn't working because I didn't update the plot using plt.show() and used the incorrect getter method. Here's the correct code:
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import interp1d
#-----------------------------------------------
# Plots several points with cubic interpolation
#-----------------------------------------------
fig = plt.figure()
ax = fig.add_subplot(111)
x = np.linspace(0, 10, num=6, endpoint=True)
y = abs(x**2)
xnew = np.linspace(0, 10, num=40, endpoint=True)
cubicInterp = interp1d(x, y, kind='cubic')
line, = ax.plot(x,y, 'o', picker=5) # 5 points tolerance
lineInterp = ax.plot(xnew,cubicInterp(xnew), '-')
#---------------
# Events
#---------------
def on_pick(event):
thisline = event.artist
thisline.set_markerfacecolor("red")
plt.show()
#-----------------------------
fig.canvas.mpl_connect('pick_event', on_pick)
plt.show()

Related

Plot 3d points (x,y,z) in 2d plot with colorbar

I have computed a lot (~5000) of 3d points (x,y,z) in a quite complicated way so I have no function such that z = f(x,y). I can plot the 3d surface using
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
X = surface_points[:,0]
Y = surface_points[:,1]
Z = surface_points[:,2]
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
surf = ax.plot_trisurf(X, Y, Z, cmap=cm.coolwarm, vmin=np.nanmin(Z), vmax=np.nanmax(Z))
I would like to plot this also in 2d, with a colorbar indicating the z-value. I know there is a simple solution using ax.contour if my z is a matrix, but here I only have a vector.
Attaching the plot_trisurf result when rotated to xy-plane. This is what I what like to achieve without having to rotate a 3d plot. In this, my variable surface_points is an np.array with size 5024 x 3.
I had the same problems in one of my codes, I solved it this way:
import numpy as np
from scipy.interpolate import griddata
import matplotlib.pylab as plt
from matplotlib import cm
N = 10000
surface_points = np.random.rand(N,3)
X = surface_points[:,0]
Y = surface_points[:,1]
Z = surface_points[:,2]
nx = 10*int(np.sqrt(N))
xg = np.linspace(X.min(), X.max(), nx)
yg = np.linspace(Y.min(), Y.max(), nx)
xgrid, ygrid = np.meshgrid(xg, yg)
ctr_f = griddata((X, Y), Z, (xgrid, ygrid), method='linear')
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1)
ax.contourf(xgrid, ygrid, ctr_f, cmap=cm.coolwarm)
plt.show()
You could use a scatter plot to display a projection of your z color onto the x-y axis.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
N = 10000
surface_points = np.random.rand(N,3)
X = surface_points[:,0]
Y = surface_points[:,1]
Z = surface_points[:,2]
# fig = plt.figure()
# ax = fig.add_subplot(projection='3d')
# surf = ax.plot_trisurf(X, Y, Z, cmap=cm.coolwarm, vmin=np.nanmin(Z), vmax=np.nanmax(Z))
fig = plt.figure()
cmap = cm.get_cmap('coolwarm')
color = cmap(Z)[..., :3]
plt.scatter(X,Y,c=color)
plt.show()
Since you seem to have a 3D shape that is hollow, you could split the projection into two like if you cur the shape in two pieces.
fig = plt.figure()
plt.subplot(121)
plt.scatter(X[Z<0.5],Y[Z<0.5],c=color[Z<0.5])
plt.title('down part')
plt.subplot(122)
plt.scatter(X[Z>=0.5],Y[Z>=0.5],c=color[Z>+0.5])
plt.title('top part')
plt.show()

How to apply a Colormap to a line interpolated using CubicHermitSpline, based on value in the y-direction

I want to add a colormap to this interpolated line, such that the colour of the line-segment changes with the value in the y-axis.
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import CubicHermiteSpline
fig, ax = plt.subplots()
x = [1,2,3,4,5,6,7,8,9,10]
y = [8,2,1,7,5,5,8,1,9,5]
cs = CubicHermiteSpline(x, y, np.zeros(len(x)))
xs = np.linspace(min(x), max(x), num=100)
ax.plot(xs, cs(xs))
Further updates:
I took the suggestion of playing around with this tutorial. I've been able to create a LineCollection and apply the colormap. However, the multicoloured line will only show up behind the plotted line. Removing the plot command removes all lines from plot.
import matplotlib.pyplot as plt
import numpy as np
from scipy.interpolate import CubicHermiteSpline
from matplotlib.collections import LineCollection
from matplotlib.colors import ListedColormap, BoundaryNorm
fig, ax = plt.subplots()
x = [1,2,3,4,5,6,7,8,9,10]
y = [8,2,1,7,5,5,8,1,9,5]
cs = CubicHermiteSpline(x, y, np.zeros(len(x)))
xs = np.linspace(min(x), max(x), num=400)
points = np.array([xs, cs(xs)]).T.reshape(-1, 1, 2)
segments = np.concatenate([points[:-1], points[1:]], axis=1)
norm = plt.Normalize(cs(xs).min(), cs(xs).max())
lc = LineCollection(segments, cmap='rainbow', norm=norm)
lc.set_array(cs(xs))
lc.set_linewidth(2)
line = ax.add_collection(lc)
fig.colorbar(line, ax=ax)
ax.plot(xs, cs(xs))

Is there a way to smooth a line between points such that the gradient at each point is 0?

Is there a way to smooth this line between the points such that the line gradient at each point is 0 (as if there were a cubic function between the points, with each data-point as a turning point).
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
x = [1,2,3,4,5,6,7,8,9,10]
y = [8,2,1,7,5,5,8,1,9,5]
ax.plot(x,y)
'Unsmoothed' plot:
I am not sure if the use case is desirable, but you can use a spline interpolation with scipy.interpolate.CubicSpline:
import numpy as np
from scipy.interpolate import CubicSpline
cs = CubicSpline(x, y)
xs = np.linspace(min(x), max(x), num=100)
fig, ax = plt.subplots()
ax.plot(x, y, label='data', marker='o')
ax.plot(xs, cs(xs), label='spline')
ax.legend()
output:
alternative: CubicHermiteSpline
import numpy as np
from scipy.interpolate import CubicHermiteSpline
cs = CubicHermiteSpline(x, y, np.zeros(len(x))) # force gradient to zero
xs = np.linspace(min(x), max(x), num=100)
fig, ax = plt.subplots()
ax.plot(x, y, label='data', marker='o')
ax.plot(xs, cs(xs), label='spline')
ax.legend()
with threshold (only for "nice" display purposes)
import numpy as np
from scipy.interpolate import CubicHermiteSpline
g = np.gradient(y)/np.gradient(x)
g = np.where(abs(g)>2, g, 0)
cs = CubicHermiteSpline(x, y, g)
xs = np.linspace(min(x), max(x), num=100)
fig, ax = plt.subplots()
ax.plot(x, y, label='data', marker='o')
ax.plot(xs, cs(xs), label='spline')
ax.legend()
output:

How to add dynamic contour plot

I wrote some code in juypter to visualize the Bivariate normal distribution. I want to modify the code so that I can visualize the contour plot(isodensity, say the x-y surface) at the same time. What should I add?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import matplotlib as mpl
%matplotlib
if __name__ == '__main__':
mpl.rcParams['font.sans-serif'] = ['SimHei']
mpl.rcParams['axes.unicode_minus'] = False
d = np.random.randn(10000000, 2)
N = 30
density, edges = np.histogramdd(d, bins=[30, 30])
print("样本总数: ", np.sum(density))
density = density/density.max()
x = y = np.arange(N)
t = np.meshgrid(x,y)
fig = plt.figure()
ax = Axes3D(fig)
ax.scatter(t[0], t[1], density, c='r', s=15*density, marker='o', depthshade=True)
ax.plot_surface(t[0], t[1], density, cmap='rainbow', rstride=1, cstride=1, alpha=0.9, lw=1)
cset = ax.contourf(x, y, density,
zdir ='z',
offset = np.min(density),
)
ax.set_xlabel("x轴")
ax.set_ylabel("y轴")
ax.set_zlabel("z轴")
plt.title("二元高斯分布")
# plt.tight_layout(0.1)
plt.show()

How to set zdir in Axes3D of matplotlib to get better waterfall

I am planning to plot waterfall like figures from multi-text data files, x is the wavelength, y are the responses, z is the frame number. x and y have the same dimensions(e.x. 5000 data). Basically, simplified my code is something like the following, I find I can not get the right view. What I want is the z-axis and y-axis be exchanged,
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from numpy import arange, sin, pi
x = arange(0.0, 10, 0.04)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for z in range(0,8):
y = sin(2*x*pi*z)
ax.plot(x, y, z )
plt.xlabel(' x', fontsize = 12, color = 'black')
plt.ylabel(' y', fontsize = 12, color = 'black')
plt.show()
with the "zdir='y'"
Without zdir='y'
What I want is Z-axis and y-axis be exchanged in Fig2. Thanks a lot!
In principle using zdir="y" seems the correct approach. That would however mean to supply the y argument as last one in the call,
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from numpy import arange, sin, pi
x = arange(0.0, 10, 0.04)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for y in list(range(0,8))[::-1]:
z = sin(2*x*pi*y)
ax.plot(x, z ,y, zdir="y")
plt.xlabel(' x', fontsize = 12, color = 'black')
plt.ylabel(' y', fontsize = 12, color = 'black')
ax.set_ylim(0,9)
ax.set_zlim(-1,1)
plt.show()
Not sure, you can achieve this with np.arange, but you can define x and y with np.linspace
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from numpy import sin, pi, linspace
x = linspace(0.0, 10, 100)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
for k in range(1, 4):
#sine wave with amplitude modulation by k
z = sin(2 * x * pi) * k + k
#keeping y constant for each k
y = linspace(k, k, 100)
ax.plot(x, y, z )
plt.xlabel(' x', fontsize = 12, color = 'black')
plt.ylabel(' y', fontsize = 12, color = 'black')
plt.show()
Difference here is that np.linspace has as parameter number of steps, while np.arange uses the step size.

Categories