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
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
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
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
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
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()