Animate a MATLAB figure by pressing left and right arrow keys - matlab

I am trying to change the radius of a sphere by using left and right arrow keys, plotted in a MATLAB figure. For example by pressing right arrow, the value of radius increases by one and then plot the new sphere with the updated radius. Similarly, pressing left arrow key decreases the radius by one and then plots a smaller sphere. However, I want this change to be limited between 1 and rmax.
I got some ideas of how I can approach after reading this post but still it is not what I am looking for. Therefore, I used two global variables to achieve this task in order to somehow pass the information by reference to KeyPressFcn so when a key is pressed KeyPressFcn know what those limits are. In the example below, the code does increase and decrease the radius by one but it is it does not restrict the change of radius within the specified range after left and right arrows are hit.
Is there a better way of approaching this? How can I pass the value of radius and its limits to KeyPressFcn? I want KeyPressFcn to know how much it can change the radius when left and right arrows are pressed.
function animateme()
fig_h = figure;
set(fig_h,'KeyPressFcn', #key_pressed_fcn);
global r rmax
p0 = [0 0 0];
[x,y,z] = sphere;
rmax = 10;
r = 1;
while 1==1
h = surf(x*r+p0(1), y*r+p0(2), z*r+p0(3));
set(h, 'FaceAlpha', 0.5, 'FaceColor', rand([1 3]))
axis equal;
pause
end
function key_pressed_fcn(fig_obj, eventDat)
global r rmax
if strcmpi(eventDat.Key, 'rightarrow')
r = r + 1;
if r < 1
r = 1;
end
elseif strcmpi(eventDat.Key, 'leftarrow')
r = r - 1;
if r > rmax
r = rmax;
end
end
disp(r)

First of all, don't use global variables as there is (almost) always a better way of accomplishing the same thing.
Here is an example by using nested functions which automatically have access to the variables within the parent function's workspace.
function animateme()
fig = figure();
hax = axes('Parent', fig);
set(fig, 'KeyPressFcn', #keypress)
p0 = [0,0,0];
[x,y,z] = sphere();
% Specify limits here which are accessible to nested functions
rmax = 10;
r = 1;
h = surf(x,y,z, 'Parent', hax);
% Subfunction for re-plotting the data
% This prevents you from needing a while loop
function redraw()
set(h, 'XData', x * r + p0(1), ...
'YData', y * r + p0(2), ...)
'ZData', z * r + p0(3));
set(h, 'FaceAlpha', 0.5, ...
'FaceColor', rand([1 3]))
axis(hax, 'equal')
drawnow
end
% Go ahead and do the first redraw
redraw();
% Callback to process keypress events
function keypress(~, evnt)
switch lower(evnt.Key)
case 'rightarrow'
r = min(r + 1, rmax);
case 'leftarrow'
r = max(1, r - 1);
otherwise
return
end
% Always do a redraw
redraw();
end
end
Another option is to store the current value of r within the graphics objects themselves using the UserData field. So you could put it in the surf plot itself. This is actually my preferred method because then your callback function can live anywhere and still have access to the data it needs.
function animateme()
fig = figure();
% Data to store for plotting
data.p = [0,0,0];
data.r = 1;
data.rmax = 10;
% Create a blank surface for starters
h = surf(nan(2), nan(2), nan(2));
set(h, 'UserData', data);
% Update the display of the surface
redraw(h);
% Set the callback and pass the surf handle
set(fig, 'KeyPressFcn', #(fig, evnt)keypress(h, evnt))
end
function redraw(h)
% Get the stored data from the graphics object
userdata = get(h, 'Userdata');
[x,y,z] = sphere();
set(h, 'XData', x * userdata.r + userdata.p(1), ...
'YData', y * userdata.r + userdata.p(2), ...
'ZData', z * userdata.r + userdata.p(3));
set(h, 'FaceAlpha', 0.5, ...
'FaceColor', rand([1 3]))
axis equal
drawnow;
end
function keypress(h, evnt)
% Get the stored data
userdata = get(h, 'Userdata');
switch lower(evnt.Key)
case 'rightarrow'
userdata.r = min(userdata.r + 1, userdata.rmax);
case 'leftarrow'
userdata.r = max(1, userdata.r - 1);
otherwise
return;
end
% Update the stored value
set(h, 'UserData', userdata);
redraw(h);
end

Related

Draw tangent line on curve with MatLab

Can some one help me to find a tangent for a curve.
I have two data set (time, temp). Now I first fit this data using logarithmic function and now I want to draw a tangent over mouse click. I upload a code which is useful but I am not aware how to modify this. Thanks for help.
function test
hh = figure(1); clf, hold on
grid on
x = 0:0.01:2*pi;
f = #(x) sin(x);
fprime = #(x) cos(x);
plot(x, f(x), 'r')
axis tight
D = [];
L = [];
set(hh, ...
'WindowButtonMotionFcn', #mouseMove,...
'WindowButtonDownFcn', #mouseClick);
function mouseMove(varargin)
coords = get(gca, 'currentpoint');
xC = coords(1);
if ishandle(D)
delete(D); end
D = plot(xC, f(xC), 'ko');
end
function mouseClick(obj, varargin)
switch get(obj, 'selectiontype')
% actions for left mouse button
case 'normal'
coords = get(gca, 'currentpoint');
xC = coords(1);
yC = f(xC);
a = fprime(xC);
b = yC-a*xC;
if ishandle(L)
delete(L); end
L = line([0; 2*pi], [b; a*2*pi+b]);
case 'alt'
% actions for right mouse button
case 'extend'
% actions for middle mouse button
case 'open'
% actions for double click
otherwise
% actions for some other X-mouse-whatever button
end
end
end

Matlab onclick callback to execute function

I want Matlab to execute a function that takes the specific point I clicked on as an input, so for example, if I plot
plot(xy(:,1),xy(:,2))
scatter(xy(:,1),xy(:,2))
and then click on a specific point (see figure), it will execute a callback function whose inputs are not only the x,y coordinate of that point but also its index value (ie its the 4th row of variable xy)
Thanks alot!
This can be done by using the ButtonDownFcn property of Scatter objects.
In the main script:
% --- Define your data
N = 10;
x = rand(N,1);
y = rand(N,1);
% --- Plot and define the callback
h = scatter(x, y, 'bo');
set(h, 'ButtonDownFcn', #myfun);
and in the function myfun:
function myfun(h, e)
% --- Get coordinates
x = get(h, 'XData');
y = get(h, 'YData');
% --- Get index of the clicked point
[~, i] = min((e.IntersectionPoint(1)-x).^2 + (e.IntersectionPoint(2)-y).^2);
% --- Do something
hold on
switch e.Button
case 1, col = 'r';
case 2, col = 'g';
case 3, col = 'b';
end
plot(x(i), y(i), '.', 'color', col);
i is the index of the clicked point, so x(i) and y(i) are the coordinates of the clicked point.
Amazingly enough, the mouse button which performed the action is stored in e.Button:
1: left click
2: middle click
3: right click
so you can play around with that too. Here is the result:
Best,

Draw log graph curve on Matlab by clicking?

I'd like to draw a curve on an empty (semilog-y) graph by clicking the points I want it to run through, on the X-Y plane.
Is there a function for this?
edit: I'm trying to do this by obtaining the position of last pointer click -
axis([0 3000 0 1000]);
co=get(gcf, 'CurrentPoint');
It seems to return the cursor position at the time of execution, but it does not change later.
edit2: Here's what works for me. The actual drawing I can do by using the arrays of points collected.
clear
clc
h=plot(0);
grid on;
xlim([0 3000]);
ylim([0 1000]);
datacursormode on;
% Enlarge figure to full screen.
screenSize = get(0,'ScreenSize');
set(gcf, 'units','pixels','outerposition', screenSize);
hold on;
% Print the x,y coordinates - will be in plot coordinates
x=zeros(1,10); y=zeros(1,10);
for p=1:10;
[x(p),y(p)] = ginput(1) ;
% Mark where they clicked with a cross.
plot(x(p),y(p), 'r+', 'MarkerSize', 20, 'LineWidth', 3);
% Print coordinates on the plot.
label = sprintf('(%.1f, %.1f)', x(p), y(p));
text(x(p)+20, y(p), label);
end
Not really, but now there is:
function topLevel
%// parameters
xrange = [0 100];
yrange = [1e-4 1e4];
%// initialize figure, plot
figure, clf, hold on
plot(NaN, NaN);
axis([xrange yrange]);
set(gca, 'YScale', 'log')
t = text(sum(xrange)/2, sum(yrange)/2, ...
'<< Need at least 3 points >>',...
'HorizontalAlignment', 'center');
%// Main loop
xs = []; p = [];
ys = []; P = [];
while true
%// Get new user-input, and collect all of them in a list
[x,y] = ginput(1);
xs = [xs; x]; %#ok<AGROW>
ys = [ys; y]; %#ok<AGROW>
%// Plot the selected points
if ishandle(p)
delete(p); end
p = plot(xs, ys, 'rx');
axis([xrange yrange]);
%// Fit curve through user-injected points
if numel(xs) >= 3
if ishandle(t)
delete(t); end
%// Get parameters of best-fit in a least-squares sense
[A,B,C] = fitExponential(xs,ys);
%// Plot the new curve
xp = linspace(xrange(1), xrange(end), 100);
yp = A + B*exp(C*xp);
if ishandle(P)
delete(P); end
P = plot(xp,yp, 'b');
end
end
%// Fit a model of the form y = A + B·exp(C·x) to data [x,y]
function [A, B, C] = fitExponential(x,y)
options = optimset(...
'maxfunevals', inf);
A = fminsearch(#lsq, 0, options);
[~,B,C] = lsq(A);
function [val, B,C] = lsq(A)
params = [ones(size(x(:))) x(:)] \ log(abs(y-A));
B = exp(params(1));
C = params(2);
val = sum((y - A - B*exp(C*x)).^2);
end
end
end
Note that as always, fitting an exponential curve can be tricky; the square of the difference between model and data is exponentially much greater for higher data values than for lower data values, so there will be a strong bias to fit the higher values better than the lower ones.
I just assumed a simple model and used a simple solution, but this gives a biased curve which might not be "optimal" in the sense that you need it to be. Any decent solution really depends on what you want specifically, and I'll leave that up to you ^_^

Placing picture on axis of MATLAB figure

I want to make animation of ball (given by the picture here) which starts from origin and goes through a track given by x-vector, y-vector, z-vector (each of nX1).I know I need to use the getframe command but I don't know how to move the picture on the axis. I know that I can put a picture in one of the corner by defining new axis, e.g (exmaple taken from MATLAB offical forum):
numberOfDataPoints = 200;
sampleData = 100*rand(1,numberOfDataPoints);
plot(sampleData);
xlim([1, numberOfDataPoints]);
hold on;
plot(sampleData);
xlim([1, numberOfDataPoints]);
axes1Position = get(gca, 'Position');
logoSizeX = 0.1;
logoSizeY = 0.1;
% Position the logo in the upper right.
x1 = axes1Position(1) + axes1Position(3) - logoSizeX;
y1 = axes1Position(2) + axes1Position(4) - logoSizeY;
hAxis2 = axes('Position', [x1 y1 logoSizeX logoSizeY]);
axis off;
imshow(ball.jpeg);
but since I don't want to create seperate axis, this does not help. How can I define movement of my ball on a given axis?
You can move the object by storing the handle returned by the image drawing function and setting its 'XData', 'YData', and 'ZData' properties. Here is a little example; this example uses warp to draw the image on a spherical surface (generated using sphere), and then moves it around a random path.
close all;
% Load image
[img, imgMap] = imread('peppers.png');
sphereImgSize = min(size(img, 1), size(img, 2));
sphereImg = img(1:sphereImgSize, 1:sphereImgSize, :);
% Generate sphere vertices
[X, Y, Z] = sphere(sphereImgSize);
lims = [-10 10];
figure;
axes;
hImg = warp(X, Y, Z, sphereImg); % NOTE: Store handle returned
xlim(lims);
ylim(lims);
zlim(lims);
axis square;
% Set up movement path
nFrames = 100;
randPathFun = #()rand(nFrames, 1) * diff(lims) + lims(1);
pathX = randPathFun();
pathY = randPathFun();
pathZ = randPathFun();
% Move the image by setting its 'XData' and 'YData' properties
for ii = 1:nFrames
xData = X + pathX(ii);
yData = Y + pathY(ii);
zData = Z + pathZ(ii);
set(hImg, 'XData', xData, 'YData', yData, 'ZData', zData);
pause(0.1);
end

Matlab code to draw a tangent to a curve

I need to draw a tangent to a curve at a particular point (say the point is chosen by the user). I have written a code that allows the user to manually pick up two points and then a line is drawn between them. But I would like to automate the process. Can someone please suggest any algorithms/already implemented matlab codes to do so?
Try the function below. Of course, it needs lots of tweaking to apply to your case, but I think this is roughtly what you want.
function test
hh = figure(1); clf, hold on
grid on
x = 0:0.01:2*pi;
f = #(x) sin(x);
fprime = #(x) cos(x);
plot(x, f(x), 'r')
axis tight
D = [];
L = [];
set(hh, ...
'WindowButtonMotionFcn', #mouseMove,...
'WindowButtonDownFcn', #mouseClick);
function mouseMove(varargin)
coords = get(gca, 'currentpoint');
xC = coords(1);
if ishandle(D)
delete(D); end
D = plot(xC, f(xC), 'ko');
end
function mouseClick(obj, varargin)
switch get(obj, 'selectiontype')
% actions for left mouse button
case 'normal'
coords = get(gca, 'currentpoint');
xC = coords(1);
yC = f(xC);
a = fprime(xC);
b = yC-a*xC;
if ishandle(L)
delete(L); end
L = line([0; 2*pi], [b; a*2*pi+b]);
case 'alt'
% actions for right mouse button
case 'extend'
% actions for middle mouse button
case 'open'
% actions for double click
otherwise
% actions for some other X-mouse-whatever button
end
end
end