I wanted to plot data in supplemental plot, which correspond to current X-value of mouse hover in main plot.
I coded
import math
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(dpi=100, figsize=(5, 5))
x = np.arange(0, 6, 0.1)
plt.plot(x, np.sin(x), 'r')
fig2, ax2 = plt.subplots(dpi=100, figsize=(5, 5))
def plot_ray(angle, y):
circle = plt.Circle((0, 0), 1, color='b', fill=False)
length = y / math.sin(angle)
line = plt.Line2D([0, length * math.cos(angle)], [0, length * math.sin(angle)])
ax2.clear()
ax2.set_xlim(-2, 2)
ax2.set_ylim(-2, 2)
ax2.add_artist(circle)
ax2.add_artist(line)
def mouse_move(event):
x = event.xdata
y = event.ydata
if x is not None and y is not None:
angle = x
plot_ray(angle, y)
cid = fig.canvas.mpl_connect('motion_notify_event', mouse_move)
plt.show(block=True)
Unfortunately, ax2 behaves unpredicatble. It is either not updated while I hovering mouse, until I click fig2 window. Or it doesn't update, until I set or release breakpoint in pycharm.
How to code correct behaviour?
You forgot to refresh the second figure after changing it. Add fig2.canvas.draw_idle() at the end.
def mouse_move(event):
x = event.xdata
y = event.ydata
if x is not None and y is not None:
angle = x
plot_ray(angle, y)
fig2.canvas.draw_idle()
Note that this would now create new circle and artist on every mouse_move event, which is rather inefficient. You would rather want to create those artists once and only update their properties.
The following runs much more smoothly.
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots(dpi=100, figsize=(5, 5))
x = np.arange(0, 6, 0.1)
plt.plot(x, np.sin(x), 'r')
fig2, ax2 = plt.subplots(dpi=100, figsize=(5, 5))
circle = plt.Circle((0, 0), 1, color='b', fill=False)
ax2.add_artist(circle)
line, = ax2.plot([],[])
ax2.set_xlim(-2, 2)
ax2.set_ylim(-2, 2)
def plot_ray(angle, y):
length = y / np.sin(angle)
line.set_data([0, length * np.cos(angle)], [0, length * np.sin(angle)])
def mouse_move(event):
x = event.xdata
y = event.ydata
if x is not None and y is not None:
angle = x
plot_ray(angle, y)
fig2.canvas.draw_idle()
cid = fig.canvas.mpl_connect('motion_notify_event', mouse_move)
plt.show(block=True)
Related
I would like to move a point in x and y direction using sliders. The problem is that as soon as the point has been moved in one direction, it returns to the origin (X0 and Y0 are read again). is it possible to remember the previous x or y position or is there an easier way to do this? thanks for the replies (geogebra is not an option just pyhton)
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
from matplotlib.widgets import Slider, Button, RadioButtons
f = lambda y: (1.5*y)
g = lambda x: (1.5*x)
# Start pos
x0 = 0
y0 = 0
# Select length of axes and the space between tick labels
xmin, xmax, ymin, ymax = -5, 5, -5, 5
ticks_frequency = 1
# Plot points
fig, ax = plt.subplots(figsize=(50, 10))
ptplot, = plt.plot(x0, y0, 'ko')
ax_x = plt.axes([0.25, 0.15, 0.65, 0.03])
ax_y = plt.axes([0.25, 0.1, 0.65, 0.03])
xSlider = Slider(ax_x, 'x', -5.0, 5.0, valinit=x0, valstep=0.1)
ySlider = Slider(ax_y, 'y', -5.0, 5.0, valinit=y0, valstep=0.1)
def update_x(x):
y = g(x)
ptplot.set_data(x,y)
xSlider.eventson = False
xSlider.set_val(x)
fig.canvas.draw()
xSlider.eventson = True
print("x1", x)
print("y1", y)
def update_y(y):
x = f(y)
ptplot.set_data(x,y)
ySlider.eventson = False
ySlider.set_val(y)
fig.canvas.draw()
ySlider.eventson = True
print("x2", x)
print("y2", y)
xSlider.on_changed(update_x)
ySlider.on_changed(update_y)
# Set identical scales for both axes
ax.set(xlim=(xmin-1, xmax+1), ylim=(ymin-1, ymax+1), aspect='equal')
# Set bottom and left spines as x and y axes of coordinate system
ax.spines['bottom'].set_position('zero')
ax.spines['left'].set_position('zero')
# Remove top and right spines
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
# Create 'x' and 'y' labels placed at the end of the axes
ax.set_xlabel('x', size=14, labelpad=-24, x=1.03)
ax.set_ylabel('y', size=14, labelpad=-21, y=1.02, rotation=0)
# Create custom major ticks to determine position of tick labels
x_ticks = np.arange(xmin, xmax+1, ticks_frequency)
y_ticks = np.arange(ymin, ymax+1, ticks_frequency)
ax.set_xticks(x_ticks[x_ticks != 0])
ax.set_yticks(y_ticks[y_ticks != 0])
# Create minor ticks placed at each integer to enable drawing of minor grid
# lines: note that this has no effect in this example with ticks_frequency=1
ax.set_xticks(np.arange(xmin, xmax+1), minor=True)
ax.set_yticks(np.arange(ymin, ymax+1), minor=True)
# Draw major and minor grid lines
ax.grid(which='both', color='grey', linewidth=1, linestyle='-', alpha=0.2)
# Draw arrows
arrow_fmt = dict(markersize=4, color='black', clip_on=False)
ax.plot((1), (0), marker='>', transform=ax.get_yaxis_transform(), **arrow_fmt)
ax.plot((0), (1), marker='^', transform=ax.get_xaxis_transform(), **arrow_fmt)
plt.show()
You can get the current position of your point with ptplot.get_data.
So your update functions could look like this:
def update_x(x):
y = g(x)
x0, y0 = ptplot.get_data()
ptplot.set_data(x0 + x, y0 + y)
xSlider.eventson = False
xSlider.set_val(x)
fig.canvas.draw()
xSlider.eventson = True
print("x1", x)
print("y1", y)
def update_y(y):
x = f(y)
x0, y0 = ptplot.get_data()
ptplot.set_data(x0 + x, y0 + y)
ySlider.eventson = False
ySlider.set_val(y)
fig.canvas.draw()
ySlider.eventson = True
print("x2", x)
print("y2", y)
However, although that is what you asked for, I doubt that this is the logic you are after.
I have written the following code with function animation with plot_surface which is not drawing, just giving the first picture
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
x = np.outer(np.linspace(-2, 2, 50), np.ones(50))
#print(x)
y = x.copy().T # transpose
fig = plt.figure()
ax = plt.axes(projection='3d')
def animation_frame(i):
z = np.cos(x ** 2 + y ** 2) + np.cos(x ** (2*i) + y ** (2*i))
# print (z)
ax.plot_surface(x, y, z,cmap='viridis', edgecolor='none')
# return ax,
animation = FuncAnimation(fig, func=animation_frame, frames=np.arange(0, 10, 1), interval=1000, blit=False)
#plt.show()
animation
You should call the plt.show() method at the end. Moreover, you should erase the previous plot with ax.cla() at the beginning of the animation_frame.
Whole code
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation
x = np.outer(np.linspace(-2, 2, 50), np.ones(50))
y = x.copy().T
fig = plt.figure()
ax = plt.axes(projection = '3d')
def animation_frame(i):
ax.cla()
z = np.cos(x**2 + y**2) + np.cos(x**(2*i) + y**(2*i))
ax.plot_surface(x, y, z, cmap = 'viridis', edgecolor = 'none')
animation = FuncAnimation(fig, func = animation_frame, frames = np.arange(0, 10, 1), interval = 250, blit = False)
plt.show()
I'm looking for help to draw a 3D cone using matplotlib.
My goal is to draw a HSL cone, then base on the vertex coordinats i will select the color.
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta1 = np.linspace(0, 2*np.pi, 100)
r1 = np.linspace(-2, 0, 100)
t1, R1 = np.meshgrid(theta1, r1)
X1 = R1*np.cos(t1)
Y1 = R1*np.sin(t1)
Z1 = 5+R1*2.5
theta2 = np.linspace(0, 2*np.pi, 100)
r2 = np.linspace(0, 2, 100)
t2, R2 = np.meshgrid(theta2, r2)
X2 = R2*np.cos(t2)
Y2 = R2*np.sin(t2)
Z2 = -5+R2*2.5
ax.set_xlabel('x axis')
ax.set_ylabel('y axis')
ax.set_zlabel('z axis')
# ax.set_xlim(-2.5, 2.5)
# ax.set_ylim(-2.5, 2.5)
# ax.set_zlim(0, 5)
ax.set_aspect('equal')
ax.plot_surface(X1, Y1, Z1, alpha=0.8, color="blue")
ax.plot_surface(X2, Y2, Z2, alpha=0.8, color="blue")
# ax.plot_surface(X, Y, Z, alpha=0.8)
#fig. savefig ("Cone.png", dpi=100, transparent = False)
plt.show()
HSL CONE
My cone
So my question now is how to define color of each element.
i have found a solution, maybe it will be usefull for others.
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
import colorsys
from matplotlib.tri import Triangulation
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
n_angles = 80
n_radii = 20
# An array of radii
# Does not include radius r=0, this is to eliminate duplicate points
radii = np.linspace(0.0, 0.5, n_radii)
# An array of angles
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords
# (0, 0) is added here. There are no duplicate points in the (x, y) plane
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Pringle surface
z = 1+-np.sqrt(x**2+y**2)*2
print(x.shape, y.shape, angles.shape, radii.shape, z.shape)
# NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!
tri = Triangulation(x, y)
triangle_vertices = np.array([np.array([[x[T[0]], y[T[0]], z[T[0]]],
[x[T[1]], y[T[1]], z[T[1]]],
[x[T[2]], y[T[2]], z[T[2]]]]) for T in tri.triangles])
x2 = np.append(0, (radii*np.cos(angles)).flatten())
y2 = np.append(0, (radii*np.sin(angles)).flatten())
# Pringle surface
z2 = -1+np.sqrt(x**2+y**2)*2
# NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!
tri2 = Triangulation(x2, y2)
triangle_vertices2 = np.array([np.array([[x2[T[0]], y2[T[0]], z2[T[0]]],
[x2[T[1]], y2[T[1]], z2[T[1]]],
[x2[T[2]], y2[T[2]], z2[T[2]]]]) for T in tri2.triangles])
triangle_vertices = np.concatenate([triangle_vertices, triangle_vertices2])
midpoints = np.average(triangle_vertices, axis=1)
def find_color_for_point(pt):
c_x, c_y, c_z = pt
angle = np.arctan2(c_x, c_y)*180/np.pi
if (angle < 0):
angle = angle + 360
if c_z < 0:
l = 0.5 - abs(c_z)/2
#l=0
if c_z == 0:
l = 0.5
if c_z > 0:
l = (1 - (1-c_z)/2)
if c_z > 0.97:
l = (1 - (1-c_z)/2)
col = colorsys.hls_to_rgb(angle/360, l, 1)
return col
facecolors = [find_color_for_point(pt) for pt in midpoints] # smooth gradient
# facecolors = [np.random.random(3) for pt in midpoints] # random colors
coll = Poly3DCollection(
triangle_vertices, facecolors=facecolors, edgecolors=None)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.add_collection(coll)
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
ax.elev = 50
plt.show()
Inspired from Jake Vanderplas with Python Data Science Handbook, when you are drawing some 3-D plot whose base is a circle, it is likely that you would try:
# Actually not sure about the math here though:
u, v = np.mgrid[0:2*np.pi:100j, 0:np.pi:20j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
and then think about the z-axis. Since viewing from the z-axis the cone is just a circle, so the relationships between z and x and y is clear, which is simply: z = np.sqrt(x ** 2 + y ** 2). Then you can draw the cone based on the codes below:
from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
def f(x, y):
return np.sqrt(x ** 2 + y ** 2)
fig = plt.figure()
ax = plt.axes(projection='3d')
# Can manipulate with 100j and 80j values to make your cone looks different
u, v = np.mgrid[0:2*np.pi:100j, 0:np.pi:80j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
z = f(x, y)
ax.plot_surface(x, y, z, cmap=cm.coolwarm)
# Some other effects you may want to try based on your needs:
# ax.plot_surface(x, y, -z, cmap=cm.coolwarm)
# ax.scatter3D(x, y, z, color="b")
# ax.plot_wireframe(x, y, z, color="b")
# ax.plot_wireframe(x, y, -z, color="r")
# Can set your view from different angles.
ax.view_init(azim=15, elev=15)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
And from my side, the cone looks like:
and hope it helps.
I have an animation where the range of the data varies a lot. I would like to have a colorbar which tracks the max and the min of the data (i.e. I would like it not to be fixed). The question is how to do this.
Ideally I would like the colorbar to be on its own axis.
I have tried the following four things
1. Naive approach
The problem: A new colorbar is plottet for each frame
#!/usr/bin/env python
"""
An animated image
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax = fig.add_subplot(111)
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar = fig.colorbar(frame)
frames.append(frame.collections)
ani = animation.ArtistAnimation(fig, frames, blit=False)
plt.show()
2. Adding to the images
Changing the for loop above to
initFrame = ax.contourf(f(x,y))
cbar = fig.colorbar(initFrame)
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar.set_clim(vmin = vmin, vmax = vmax)
cbar.draw_all()
frames.append(frame.collections + [cbar])
The problem: This raises
AttributeError: 'Colorbar' object has no attribute 'set_visible'
3. Plotting on its own axis
The problem: The colorbar is not updated.
#!/usr/bin/env python
"""
An animated image
"""
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
fig = plt.figure()
ax1 = fig.add_subplot(121)
ax2 = fig.add_subplot(122)
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
vmax = np.max(curVals)
vmin = np.min(curVals)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
frame = ax1.contourf(curVals, vmax=vmax, vmin=vmin, levels=levels)
cbar = fig.colorbar(frame, cax=ax2) # Colorbar does not update
frames.append(frame.collections)
ani = animation.ArtistAnimation(fig, frames, blit=False)
plt.show()
A combination of 2. and 4.
The problem: The colorbar is constant.
A similar question is posted here, but it looks like the OP is satisfied with a fixed colorbar.
While I'm not sure how to do this specifically using an ArtistAnimation, using a FuncAnimation is fairly straightforward. If I make the following modifications to your "naive" version 1 it works.
Modified Version 1
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure()
ax = fig.add_subplot(111)
# I like to position my colorbars this way, but you don't have to
div = make_axes_locatable(ax)
cax = div.append_axes('right', '5%', '5%')
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
frames.append(curVals)
cv0 = frames[0]
cf = ax.contourf(cv0, 200)
cb = fig.colorbar(cf, cax=cax)
tx = ax.set_title('Frame 0')
def animate(i):
arr = frames[i]
vmax = np.max(arr)
vmin = np.min(arr)
levels = np.linspace(vmin, vmax, 200, endpoint = True)
cf = ax.contourf(arr, vmax=vmax, vmin=vmin, levels=levels)
cax.cla()
fig.colorbar(cf, cax=cax)
tx.set_text('Frame {0}'.format(i))
ani = animation.FuncAnimation(fig, animate, frames=10)
plt.show()
The main difference is that I do the levels calculations and contouring in a function instead of creating a list of artists. The colorbar works because you can clear the axes from the previous frame and redo it every frame.
Doing this redo is necessary when using contour or contourf, because you can't just dynamically change the data. However, as you have plotted so many contour levels and the result looks smooth, I think you may be better off using imshow instead - it means you can actually just use the same artist and change the data, and the colorbar updates itself automatically. It's also much faster!
Better Version
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from mpl_toolkits.axes_grid1 import make_axes_locatable
fig = plt.figure()
ax = fig.add_subplot(111)
# I like to position my colorbars this way, but you don't have to
div = make_axes_locatable(ax)
cax = div.append_axes('right', '5%', '5%')
def f(x, y):
return np.exp(x) + np.sin(y)
x = np.linspace(0, 1, 120)
y = np.linspace(0, 2 * np.pi, 100).reshape(-1, 1)
# This is now a list of arrays rather than a list of artists
frames = []
for i in range(10):
x += 1
curVals = f(x, y)
frames.append(curVals)
cv0 = frames[0]
im = ax.imshow(cv0, origin='lower') # Here make an AxesImage rather than contour
cb = fig.colorbar(im, cax=cax)
tx = ax.set_title('Frame 0')
def animate(i):
arr = frames[i]
vmax = np.max(arr)
vmin = np.min(arr)
im.set_data(arr)
im.set_clim(vmin, vmax)
tx.set_text('Frame {0}'.format(i))
# In this version you don't have to do anything to the colorbar,
# it updates itself when the mappable it watches (im) changes
ani = animation.FuncAnimation(fig, animate, frames=10)
plt.show()
I have a time series plot and I need to draw a moving vertical line to show the point of interest.
I am using the following toy example to accomplish the same. However, it prints all the lines at the same time while I wanted to show these vertical line plotting one at a time.
import time
ion() # turn interactive mode on
# initial data
x = arange(-8, 8, 0.1);
y1 = sin(x)
y2 = cos(x)
line1, = plt.plot(x, y1, 'r')
xvals = range(-6, 6, 2);
for i in xvals:
time.sleep(1)
# update data
plt.vlines(i, -1, 1, linestyles = 'solid', color= 'red')
plt.draw()
If I understood well, you want to use the animation tools of matplotlib. An example (adapted from the doc):
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
X_MIN = -6
X_MAX = 6
Y_MIN = -1
Y_MAX = 1
X_VALS = range(X_MIN, X_MAX+1) # possible x values for the line
def update_line(num, line):
i = X_VALS[num]
line.set_data( [i, i], [Y_MIN, Y_MAX])
return line,
fig = plt.figure()
x = np.arange(X_MIN, X_MAX, 0.1);
y = np.sin(x)
plt.scatter(x, y)
l , v = plt.plot(-6, -1, 6, 1, linewidth=2, color= 'red')
plt.xlim(X_MIN, X_MAX)
plt.ylim(Y_MIN, Y_MAX)
plt.xlabel('x')
plt.ylabel('y = sin(x)')
plt.title('Line animation')
line_anim = animation.FuncAnimation(fig, update_line, len(X_VALS), fargs=(l, ))
#line_anim.save('line_animation.gif', writer='imagemagick', fps=4);
plt.show()
Resulting gif looks like this:
Could you try calling plt.draw after plt.vlines? plt.draw is used to interactively redraw the figure after its been modified.