How to animate a 3d plot using python? - plot

I have this python code:
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
from matplotlib import cm
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Plot solid of revolution along x-axis
def sor_x(ll, ul):
u = np.linspace(ll, ul, 60)
v = np.linspace(0, 2 * np.pi, 60)
U, V = np.meshgrid(u, v)
X = U
Y = (U**2)*np.cos(V)
Z = (U**2)*np.sin(V)
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
ax.plot_surface(X, Y, Z, cmap=plt.cm.YlGnBu_r)
if __name__ == '__main__':
ll, ul = 0, 1
sor_x(ll, ul)
plt.show()
This plots the solid of revolution of function y = x**2 along x-axis. Now I have to change this to a 3D animation like this:
The code for this animation in mathematica is:
f[r_, ϕ_, z_] := {(2 + Tan[z])Cos[ϕ], (2 + Cos[z]) Sin[ϕ], z}
vase[α_] :=
ParametricPlot3D[f[r, ϕ, z], {z, 0, 2 Pi}, {ϕ, 0, α},
AspectRatio -> Automatic, PlotRange -> {{-3, 3}, {-3, 3}, {0, 6}}];
animation = Table[
vase[ϕ],
{ϕ, 0.1, 2π, π/12}];
Export["rotationskoerper_animation.gif", animation,
ConversionOptions -> {"AnimationDisplayTime" -> 0.1, "Loop" -> True},
ImageSize -> {1000, 1000}]

Related

How to move object across axis?

I have an interactive plot and I want to move topoplot position across the x-axis according to the slider (or red vertical bar) position.
How can I do that?
In an ideal situation, the topoplot moves until some border (so it would be partially out of the screen).
Also, is it possible to put a line connecting the topolot with a red vertical line?
This is my script with prerequisite functions:
using Makie
using GLMakie
using PyMNE
using JLD2 # loading data
using TopoPlots
using StatsBase # mean/std
using Pipe
using ColorSchemes
using Colors
using LinearAlgebra
function eegHeadMatrix(positions, center, radius)
oldCenter = mean(positions)
oldRadius, _ = findmax(x-> LinearAlgebra.norm(x .- oldCenter),
positions)
radF = radius/oldRadius
return Makie.Mat4f(radF, 0, 0, 0,
0, radF, 0, 0,
0, 0, 1, 0,
center[1]-oldCenter[1]*radF, center[2]-
oldCenter[2]*radF, 0, 1)
end
struct NullInterpolator <: TopoPlots.Interpolator
end
function (ni::NullInterpolator)(
xrange::LinRange, yrange::LinRange,
positions::AbstractVector{<: Point{2}}, data::AbstractVector{<:Number})
return zeros(length(xrange),length(yrange))
end
function posToColor(pos)
cx = 0.5 - pos[1]
cy = 0.5 - pos[2]
rx = cx * 0.7071068 + cy * 0.7071068
ry = cx * -0.7071068 + cy * 0.7071068
b = 1.0 - (2*sqrt(cx^2+cy^2))^2
return RGB(0.5 - rx*1.414, 0.5 - ry*1.414, b)
end
This is the main function
f = Figure(backgroundcolor = RGBf(0.98, 0.98, 0.98), resolution = (1500, 700))
# interaction
xs = range(-0.3, length=size(dat_e, 2), step=1 ./ 128)
sg = SliderGrid(f[4, 1:2],
(label="time", range=xs, format = "{:.3f} ms", startvalue = 0),
)
time = sg.sliders[1].value
str = lift(t -> "[$(round(t, digits = 3)) ms]", time)
topo_slice = lift((t, data) -> mean(data[1:30, indexin(t, xs), :], dims=2)[:,1], time, dat_e)
# butterfly plot
ax = Axis(f[2:3, 1:2], xlabel = "Time [s]", ylabel = "Voltage amplitude [µV]")
N = 1:length(pos) #1:4
hidespines!(ax, :t, :r)
GLMakie.xlims!(-0.3, 1.2)
hlines!(0, color = :gray, linewidth = 1)
vlines!(0, color = :gray, linewidth = 1)
times = range(-0.3, length=size(dat_e,2), step=1 ./ 128)
specialColors = ColorScheme(vcat(RGB(1,1,1.),[posToColor(pos) for pos in pos[N]]...))
for i in N
mean_trial = mean(dat_e[i,:,:], dims=2)[:,1]
lines!(times, mean_trial, color = specialColors[i])
end
hidedecorations!(ax, label = false, ticks = false, ticklabels = false)
# text
vlines!(time, color = :red, linewidth = 1)
text!(time, 8, text = str, align = (:center, :center))
# topoplot
topo_axis = Axis(f[1, 1:2], width = 178, height = 178, aspect = DataAspect())
Makie.xlims!(low = -0.2, high = 1.2)
Makie.ylims!(low = -0.2, high = 1.2)
topoMatrix = eegHeadMatrix(pos[N], (0.5, 0.5), 0.5)
topo = eeg_topoplot!(topo_axis, topo_slice, # averaging all trial of 30 participants on Xth msec
raw.ch_names[1:30];
positions=pos, # produced automatically from ch_names
#interpolation=DelaunayMesh(),
enlarge=1,
extrapolation=GeomExtrapolation(enlarge=1.0, geometry=Circle),
label_text=false)
hidedecorations!(current_axis())
hidespines!(current_axis())
f

How to interact with plot using keyboard arros?

My interactive plot (topoplot) reacts to mouse signals, but how to make it reacting to keyboard signals?
Here is my code:
f = Figure()
xs = 1:1:193 #range(-30, 120, length = size(dat_e, 2))
sg = SliderGrid(f[2, 1],
(label="time", range=xs, format = "{:d} ms", startvalue = 100),
)
time = sg.sliders[1].value
str = lift(t -> "[$t ms]", time)
topo_slice = lift((t, data) -> mean(data[1:30, t, :], dims=2)[:,1], time, dat_e)
topo_axis = Axis(f[1, 1], aspect = DataAspect())
topo = eeg_topoplot!(topo_axis, topo_slice,
raw.ch_names[1:30];
positions=pos, # produced automatically from ch_names
label_text=true) # aspect ratio, correlation of height and width
text!(topo_axis, 1, 1, text = str, align = (:center, :center))
#topo_slice = lift((t, data) -> data[:, :, t], time, topo)
xlims!(-0.2, 1.1)
ylims!(-0.2, 1.2)
hidedecorations!(topo_axis)
hidespines!(topo_axis)
f
There is an official instruction https://docs.juliahub.com/AbstractPlotting/6fydZ/0.12.11/interaction.html, but as usual with Julia documentations, there is no example and I have no idea how implement it in my code.
How my plot looks like:
Expanding on the answer from before:
T = 10
pts = range(-1, 1, length=100)
ts = reshape(1:T, 1, 1, :)
topo = cos.(pts) .+ cos.(ts .* pts')
fig = Figure()
ax = Axis(fig[1, 1])
sg = SliderGrid(fig[2,1],
(label="time", range=1:T))
time = sg.sliders[1].value
str = lift(t -> "[$t ms]", time)
text!(ax, str)
topo_slice = lift((t, data) -> data[:, :, t], time, topo)
# decrement/increment slider with left/right keys
on(events(fig).keyboardbutton) do btn
if btn.action in (Keyboard.press, Keyboard.repeat)
if btn.key == Keyboard.left
set_close_to!(sg.sliders[1], time[] - 1)
elseif btn.key == Keyboard.right
set_close_to!(sg.sliders[1], time[] + 1)
end
end
end
contour!(ax, topo_slice)
hidedecorations!(ax)
hidespines!(ax)
fig

Plot Gaze Heat Map on PNG

With this code I want to plot a heat map from eye tracking gaze data (x, y coordinates on screen) on top of a png. I got really close with other posts here in the forum. The only problem left are some dots/blurry patches on the output image (see link). Maybe somebody can help?
import numpy as np
import matplotlib.pyplot as plt
from scipy.ndimage import gaussian_filter
import pandas as pd
from PIL import Image
with Image.open("/Users/florianteichmann/Desktop/Gaze_Scratch_Paradigm/stimuli_current/trial_image/32space_drop.png") as img3:
w = img3.width
h = img3.height
def myplot(x, y, s, bins=1000):
heatmap, xedges, yedges = np.histogram2d(x, y, bins=bins)
heatmap = gaussian_filter(heatmap, sigma=s)
extent = [xedges[0], xedges[-1], yedges[0], yedges[-1]]
return heatmap.T, extent
def transparent_cmap(cmap, N=255):
mycmap = cmap
mycmap._init()
mycmap._lut[:,-1] = np.linspace(0, 1, N+4)
return mycmap
# Generate some test data
df = pd.read_csv('file-path')
x = (df['X'])
y = (df['Y'])
mycmap = transparent_cmap(plt.cm.Greens)
img2 = plt.imread('file-path')
#sigmas = [0, 16, 32, 64]
s = 64
fig, ax = plt.subplots()
plt.axis([0, w, 0, h])
img, extent = myplot(x, y, s)
ax.imshow(img2, extent=[0, w, 0, h])
#ax.plot(img) #cmap=cm.jet)
ax.imshow(img, extent=[0, w, 0, h], origin='lower', cmap=mycmap) #cmap=cm.jet) origin='lower'
plt.show()
output image

Pyqtgraph plotting is slow

It seems window redraws everything each time new rect is added, although setUpdatesEnabled is set to False, for win and plt. How to disable updates?
def f(n):
import pyqtgraph as pg
pg.setConfigOption('background', '#a0f0ff')
win = pg.GraphicsWindow()
win_size = 1000
win.setGeometry(500, 30, win_size, win_size)
plt = win.addPlot()
win.setUpdatesEnabled = False
plt.setUpdatesEnabled = False
y = range(n)
x = range(n)
plt.showGrid(x=True, y=True)
empty_pen = pg.mkPen((0, 0, 0, 0))
brush = pg.mkBrush((255, 255, 255))
for i1 in range(n):
for i0 in range(n):
print("i1, i0 =", i1, i0)
rect = pg.QtGui.QGraphicsRectItem(i0, i1, 0.5, 0.5)
rect.setPen(empty_pen)
rect.setBrush(brush)
plt.addItem(rect)
pg.QtGui.QApplication.exec_()
f(40)
Add
plt.disableAutoRange()
before drawing and
plt.autoRange()
after it. See Plotting large arrays in pyqtgraph

Matplotlib: Cursor snap to plotted data with datetime axis

I have a plot of 3 data sets that have datetime objetcs on the x axis.
I want to have a cursor that snaps to the data and shows the precise x and y value.
I already have a "snap to cursor", but that only works for scalar x axes.
Can anyone help me to modify the snap to cursor so that it works for datetime x axes as well?
Here are my data plots:
import numpy as np
import matplotlib.pyplot as plot
import matplotlib.ticker as mticker
import matplotlib.dates as dates
import datetime
import Helpers
fig = plot.figure(1)
DAU = ( 2, 20, 25, 60, 190, 210, 18, 196, 212)
WAU = ( 50, 160, 412, 403, 308, 379, 345, 299, 258)
MAU = (760, 620, 487, 751, 612, 601, 546, 409, 457)
firstDay = datetime.datetime(2012,1,15)
#create an array with len(DAU) entries from given starting day
dayArray = [firstDay + datetime.timedelta(days = i) for i in xrange(len(DAU))]
line1 = plot.plot(dayArray, DAU, 'o-', color = '#336699')
line2 = plot.plot(dayArray, WAU, 'o-', color = '#993333')
line3 = plot.plot(dayArray, MAU, 'o-', color = '#89a54e')
ax = plot.subplot(111)
dateLocator = mticker.MultipleLocator(2)
dateFormatter = dates.DateFormatter('%d.%m.%Y')
ax.xaxis.set_major_locator(dateLocator)
ax.xaxis.set_major_formatter(dateFormatter)
fig.autofmt_xdate(rotation = 90, ha = 'center')
yMax = max(np.max(DAU), np.max(WAU), np.max(MAU))
yLimit = 100 - (yMax % 100) + yMax
plot.yticks(np.arange(0, yLimit + 1, 100))
plot.title('Active users', weight = 'bold')
plot.grid(True, axis = 'both')
plot.subplots_adjust(bottom = 0.2)
plot.subplots_adjust(right = 0.82)
legend = plot.legend((line1[0], line2[0], line3[0]),
('DAU',
'WAU',
'MAU'),
'upper left',
bbox_to_anchor = [1, 1],
shadow = True)
frame = legend.get_frame()
frame.set_facecolor('0.80')
for t in legend.get_texts():
t.set_fontsize('small')
#THIS DOES NOT WORK
cursor = Helpers.SnaptoCursor(ax, dayArray, DAU, 'euro daily')
plot.connect('motion_notify_event', cursor.mouse_move)
plot.show()
And this is my module "Helper" that contains the "SnaptoCursor" class:
(I got the basic SnaptoCursor class from somewhere else and modified it a little bit)
from __future__ import print_function
import numpy as np
import matplotlib.pyplot as plot
def minsec(sec, unused):
"""
Returns a string of the input seconds formatted as mm'ss''.
"""
minutes = sec // 60
sec = sec - minutes * 60
return '{0:02d}\'{1:02d}\'\''.format(int(minutes), int(sec))
class SnaptoCursor():
"""
A cursor with crosshair snaps to the nearest x point.
For simplicity, I'm assuming x is sorted.
"""
def __init__(self, ax, x, y, formatting, z = None):
"""
ax: plot axis
x: plot spacing
y: plot data
formatting: string flag for desired formatting
z: optional second plot data
"""
self.ax = ax
self.lx = ax.axhline(color = 'k') #the horiz line
self.ly = ax.axvline(color = 'k') #the vert line
self.x = x
self.y = y
self.z = z
# text location in axes coords
self.txt = ax.text(0.6, 0.9, '', transform = ax.transAxes)
self.formatting = formatting
def format(self, x, y):
if self.formatting == 'minsec':
return 'x={0:d}, y='.format(x) + minsec(y, 0)
elif self.formatting == 'daily euro':
return u'day {0:d}: {1:.2f}€'.format(x, y)
def mouse_move(self, event):
if not event.inaxes: return
mouseX, mouseY = event.xdata, event.ydata
#searchsorted: returns an index or indices that suggest where x should be inserted
#so that the order of the list self.x would be preserved
indx = np.searchsorted(self.x, [mouseX])[0]
mouseX = self.x[indx]
#if z wasn't defined
if self.z == None:
mouseY = self.y[indx]
#if z was defined: compare the distance between mouse and the two plots y and z
#and use the nearest one
elif abs(mouseY - self.y[indx]) < abs(mouseY - self.z[indx]):
mouseY = self.y[indx]
else:
mouseY = self.z[indx]
#update the line positions
self.lx.set_ydata(mouseY)
self.ly.set_xdata(mouseX)
self.txt.set_text(self.format(mouseX, mouseY))
plot.draw()
Of course this does not work since I am calling the SnaptoCursor with the datetime array "dayArray", which is supposed to be compared to the mouse coordinates later on. And these data types are not comparable.
I got it!!!
The problems where these two lines in the init method of the SnaptoCursor class:
self.lx = ax.axhline(color = 'k') #the horiz line
self.ly = ax.axvline(color = 'k') #the vert line
They were somehow messing up the datetime x axis (that has ordinals up to 730,000 e.g.), so you just have to initialize the lines' coordinates:
self.lx = ax.axhline(y = min(y), color = 'k') #the horiz line
self.ly = ax.axvline(x = min(x), color = 'k') #the vert line
Then it works just fine!
I'll be posting my complete SnaptoCursor class now that I have modified so it accepts individual formatting strings, and it can take up to 3 input data plots - that get snapped to according to your mouse position.
def percent(x, unused):
"""
Returns a string of the float number x formatted as %.
"""
return '{0:1.2f}%'.format(x * 100)
def minsec(sec, unused):
"""
Returns a string of the input seconds formatted as mm'ss''.
"""
minutes = sec // 60
sec = sec - minutes * 60
return '{0:02d}\'{1:02d}\'\''.format(int(minutes), int(sec))
class SnaptoCursor():
"""
A cursor with crosshair snaps to the nearest x point.
For simplicity, I'm assuming x is sorted.
"""
def __init__(self, ax, x, y, formatting, y2 = None, y3 = None):
"""
ax: plot axis
x: plot spacing
y: plot data
formatting: string flag for desired formatting
y2: optional second plot data
y3: optional third plot data
"""
self.ax = ax
self.lx = ax.axhline(y = min(y), color = 'k') #the horiz line
self.ly = ax.axvline(x = min(x), color = 'k') #the vert line
self.x = x
self.y = y
self.y2 = y2
self.y3 = y3
# text location in axes coords
self.txt = ax.text(0.6, 0.9, '', transform = ax.transAxes)
self.formatting = formatting
def format(self, x, y):
if self.formatting == 'minsec':
return 'x={0:d}, y='.format(x) + minsec(y, 0)
if self.formatting == 'decimal':
return 'x={0:d}, y={1:d}'.format(x, int(y))
elif self.formatting == 'date decimal':
return 'x={0:%d.%m.%Y}, y={1:d}'.format(x, int(y))
elif self.formatting == 'decimal percent':
return 'x={0:d}, y={1:d}%'.format(x, int(y * 100))
elif self.formatting == 'float':
return 'x={0:d}, y={1:.2f}'.format(x, y)
elif self.formatting == 'float percent':
return 'x={0:d}, y='.format(x) + percent(y, 0)
elif self.formatting == 'daily euro':
return u'day {0:d}: {1:.2f}€'.format(x, y)
def mouse_move(self, event):
if not event.inaxes:
return
mouseX, mouseY = event.xdata, event.ydata
if type(self.x[0]) == datetime.datetime:
mouseX = dates.num2date(int(mouseX)).replace(tzinfo = None)
#searchsorted: returns an index or indices that suggest where mouseX should be inserted
#so that the order of the list self.x would be preserved
indx = np.searchsorted(self.x, [mouseX])[0]
#if indx is out of bounds
if indx >= len(self.x):
indx = len(self.x) - 1
#if y2 wasn't defined
if self.y2 == None:
mouseY = self.y[indx]
#if y2 was defined AND y3 wasn't defined
elif self.y3 == None:
if abs(mouseY - self.y[indx]) < abs(mouseY - self.y2[indx]):
mouseY = self.y[indx]
else:
mouseY = self.y2[indx]
#if y2 AND y3 were defined
elif abs(mouseY - self.y2[indx]) < abs(mouseY - self.y[indx]):
if abs(mouseY - self.y2[indx]) < abs(mouseY - self.y3[indx]):
mouseY = self.y2[indx]
else:
mouseY = self.y3[indx]
#lastly, compare y with y3
elif abs(mouseY - self.y[indx]) < abs(mouseY - self.y3[indx]):
mouseY = self.y[indx]
else:
mouseY = self.y3[indx]
#update the line positions
self.lx.set_ydata(mouseY)
self.ly.set_xdata(mouseX)
self.txt.set_text(self.format(mouseX, mouseY))
plot.draw()

Resources