Related
Literately figured out the last problem as soon as I posted. Was able to fix many of the issues I was having. Now the problem revolves around hgtransform and linkprop. How does one copy the object location and transformation to additional figures. The code below will copy objects from the first axes to the next and animate making all of them move. However it doesn't copy the transformation.
fig = figure();
% create subplots for stim system 3 plate setup
for aa = 1:3
Stimsubfigures{aa} = axes(...
'Position',[((aa*.21)-.2),.2,.2,.2],'color','none');
set(Stimsubfigures{aa},'xLim',[-320,320])
set(Stimsubfigures{aa},'YLim',[-240,240])
set(Stimsubfigures{aa},'Visible','off')
end
axes(Stimsubfigures{1});
for aa = 1:10
Xdata = [1+aa*50,10+aa*50,10+aa*50,1+aa*50];
ObjectTransformation{aa,1} = hgtransform; % Add object to end of transformation list
ObjectList{aa,1} = patch(... % Add object to end of Object list, bind to transformation list
'Parent', ObjectTransformation{aa}, ...
'XData',Xdata, 'YData',[1,1,20,20],...
'Facecolor', [1,0,0], 'EdgeColor', [1,0,0], ...
'visible','on');
ObjectTransformation{aa,1}.Matrix = makehgtform('zrotate',50);
NextStepX{aa,1} = Xdata;
end
tmp = transpose([ObjectList{:}]);
tmptrans = transpose([ObjectTransformation{:}]);
TrialLength = 10;
% copy objects to other figures
copyobj(tmp,Stimsubfigures{2})
copyobj(tmp,Stimsubfigures{3})
property_names = {'XData', 'YData', 'ZData'};
for aa = 1:10
linked_objects = [tmp(aa),...
Stimsubfigures{2}.Children(aa),...
Stimsubfigures{3}.Children(aa)];
hlink{aa} = linkprop(linked_objects,property_names);
end
timer = tic();
while true
t1 = toc(timer);
if t1 >= TrialLength, break;end % break loop once time trial ends
NextStepX = cellfun(#(x) x+1,NextStepX,'UniformOutput',false);
[tmp.XData] = NextStepX{:};
drawnow;
pause(0.1);
step = NextStepX;
end
for aa = 1:3
delete(Stimsubfigures{aa}.Children)
end
When I change this section to copy the transformation, the objects transform correctly but lose the animation.
% copy objects to other figures
copyobj(tmptrans,Stimsubfigures{2})
copyobj(tmptrans,Stimsubfigures{3})
property_names = {'XData', 'YData', 'ZData'};
trans_names = {'zrotate'};
for aa = 1:10
linked_objects = [tmp(aa),...
Stimsubfigures{2}.Children(aa),...
Stimsubfigures{3}.Children(aa)];
Trlink_objects = [tmptrans(aa),...
Stimsubfigures{2}.Children(aa),...
Stimsubfigures{3}.Children(aa)];
hlink{aa} = linkprop(linked_objects,trans_names);
Trhlink{aa} = linkprop(Trlink_objects,trans_names);
end
I tried to perform an copyobj on both handles but it just results in two sets of objects. How can one link all three together so I can perform rotation change Xdata?
Figured it out. Will post answer for those who had similar questions.
The hgtransform is a parent of the objects which are being rotated. because of this when I copy the hgtransform, the children are also copied, hence why the objects appear in the proper orientation in the other windows. From here, I need to link the children from the copied parents to generate the animation.
% copy objects to other figures
copyobj(tmptrans,Stimsubfigures{2})
copyobj(tmptrans,Stimsubfigures{3})
property_names = {'XData', 'YData', 'ZData'};
for aa = 1:10
linked_objects = [tmptrans(aa).Children(1),...
Stimsubfigures{2}.Children(aa).Children(1),...
Stimsubfigures{3}.Children(aa).Children(1)];
hlink{aa} = linkprop(linked_objects,trans_names);
end
By replacing the part of code with the section above. One can transform the objects and animate.
Sorry for a somewhat simple question. I am trying to generate a figure which displays the same animation but in different subplots. I'm starting simple and primarily focusing on duplicating the plot first.
I was originally thinking of attaching the handle for the subplot to the other plots
afig = figure;
a1{1} = axes('Position',[.01,.2,.2,.2], 'color','none','Xlim',[-10,10],'Ylim',[-10,10]);
a1{2} = a1{1};
a1{2}.Position = [.3,.2,.2,.2];
a1{3} = a1{1};
a1{3}.Position = [.6,.2,.2,.2];
obj = patch('Parent',a1{1},'XData',[1,3,1],'YData',[1,1,3]);
But this just moves the existing plot around as opposed to duplicating it. (on account that I'm still referring to the same object even though it has different names)
I next thought of just recreating the same setup 3 times and then update the animation, looping through the three, but this feels inefficient and computationally intensive.
afig = figure;
a1{1} = axes('Position',[.01,.2,.2,.2], 'color','none','Xlim',[-10,10],'Ylim',[-10,10]);
a1{2} = axes('Position',[.3,.2,.2,.2], 'color','none','Xlim',[-10,10],'Ylim',[-10,10]);
a1{3} = axes('Position',[.6,.2,.2,.2], 'color','none','Xlim',[-10,10],'Ylim',[-10,10]);
obj{1} = patch('Parent',a1{1},'XData',[1,3,1],'YData',[1,1,3]);
obj{2} = patch('Parent',a1{2},'XData',[1,3,1],'YData',[1,1,3]);
obj{3} = patch('Parent',a1{3},'XData',[1,3,1],'YData',[1,1,3]);
Is there a way to call 1 subplot, update that 1 subplot but have it propagate to other subplots?
It really depends on what you want to do in the end, how complex the animations are, and if you can prepare your plots in advance.
First, if there are only a few objects, you could use the linkprop function to link graphics objects' properties:
afig = figure;
a1{1} = axes('Position',[.01,.2,.2,.2], 'color','none','Xlim',[-10,10],'Ylim',[-10,10]);
obj = patch('Parent',a1{1},'XData',[1,3,1],'YData',[1,1,3]);
a1{2} = copyobj(a1{1}, afig);
a1{2}.Position = [.3,.2,.2,.2];
a1{3} = copyobj(a1{1}, afig);
a1{3}.Position = [.6,.2,.2,.2];
linked_objects = [ a1{1}.Children, a1{2}.Children, a1{3}.Children];
property_names = {'XData', 'YData', 'ZData'};
hlink = linkprop(linked_objects, property_names);
for ii = 1:10
obj.XData(1) = ii;
drawnow
pause(0.01)
end
Here, we first create the base plot, then we copy the axes (note that children objects get copied, too, but not callbacks and other properties, see copyboy). We then link properties we may want to change during the animation (note that you could also link the axes' view properties), and then change them in a loop.
Another approach would be to change the object's properties in a main axes in every loop iteration, and copy the main axes' children to the other axes afterwards. This approach may be more costly – as a lot of objects get copied and rendered – but on the other hand, individual properties do not have to be tracked. Here is an example:
afig = figure;
a1{1} = axes('Position',[.01,.2,.2,.2], 'color','none','Xlim',[-10,10],'Ylim',[-10,10]);
obj = patch('Parent',a1{1},'XData',[1,3,1],'YData',[1,1,3]);
a1{2} = copyobj(a1{1}, afig);
a1{2}.Position = [.3,.2,.2,.2];
a1{3} = copyobj(a1{1}, afig);
a1{3}.Position = [.6,.2,.2,.2];
for ii = 1:10
obj.XData(1) = ii;
delete(a1{2}.Children);
delete(a1{3}.Children);
copyobj(a1{1}.Children, a1{2});
copyobj(a1{1}.Children, a1{3});
drawnow
pause(0.01)
end
Finally, it could be an option to use getframe to just capture the rendered image and display it in the copy axes.
This post is a continuation of the thread: "Matlab imline snapping" which was resolved. The below is the working code that snaps the imline object to a curve.
function calc_slope(handle,event)
axis_h = findobj(gcf,'Type','axes');
obj_h = get(axis_h,'Children');
obj_type = get(obj_h,'Type');
if ~iscell(obj_type),obj_type = cellstr(obj_type);end
for i=1:length(obj_type)
if strcmpi(obj_type{i},'line'),data = obj_h(i);end
end
xdata = get(data,'XData');
ydata = get(data,'YData');
on = get(handle,'State');
if strcmpi(on,'on') || strcmpi(on,'off'),
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)]);
xy = imline(axis_h, 'PositionConstraintFcn', fcn_constr);
addNewPositionCallback(xy,#(pos) disp_slope(pos));
end
function constr_pos = imline_snap(new_pos, positions)
[~, ind1] = min(sum(bsxfun(#minus, new_pos(1,:), positions).^2, 2));
[~, ind2] = min(sum(bsxfun(#minus, new_pos(2,:), positions).^2, 2));
constr_pos = [positions(ind1,:); positions(ind2,:)];
function disp_slope(pos)
delete(findobj(gca,'Type','text'));
text((pos(1)+pos(2))/2,(pos(3)+pos(4))/2,['\DeltaY/\DeltaX = ',num2str((pos(4)-pos(3))/(pos(2)-pos(1))),...
' [\DeltaX = ',num2str(pos(2)-pos(1)),', \DeltaY = ',num2str((pos(4)-pos(3))),']']);
Every time the toggle button on the toolbar of a figure is toggled (on and off), a new imline object is thrown in. There are many figures with different parameters so that the data has to be extracted from the figure. In a given figure, there can be multiple objects: imline objects, text, and/or line; thus, the first seven lines in the calc_slope function.
The imline objects snaps to the nearest data point of a curve and it's beautifully done by imline_snap function which is an answer by "Luis Mendo". Thank you so much. This has been a biggest headache.
The final problem is now to show the slope of an imline object in a text box (instead of the title or the floating box). It's attempted in the disp_slope function (and it's miserable).
I'm doing "delete(findobj(gca,'Type','text'));" only because without something like that, as the imline object is moved around, it will leave millions of text boxes. I only want to show one most current slope calculation.
There are multiple problems with "delete(findobj(gca,'Type','text'));". If I stop moving the line around, it will nicely show the last slope calculation. However, as soon as I throw in another imline object and move the new one around, the text box in the first imline object will get deleted and of course.
Another problem is that even if I delete the imline object, the associated text box will remain.
In summary,
I want to show the current calculated slope in a text box
I want the text box for each imline object to remain even if there are multiple imline objects.
Finally, I want the corresponding text box to disappear as well when a particular imline object is deleted.
Can this be done? Help please.
Thanks,
Eric
Don't create a new text object every time. Create one initially
ht = text(.45, .85, ''); %// modify coordinates to place it where you want
and then update its content ('String' property) when the imline changes. To do the updating, modify the imline_snap function to accept ht as a third input and add the following line at the end:
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))));
So the function becomes
function constr_pos = imline_snap(new_pos, positions, ht)
[~, ind1] = min(sum(bsxfun(#minus, new_pos(1,:), positions).^2, 2));
[~, ind2] = min(sum(bsxfun(#minus, new_pos(2,:), positions).^2, 2));
constr_pos = [positions(ind1,:); positions(ind2,:)];
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))));
Then, when defining fcn_contr, pass the reference ht to the text object:
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht);
Here's an example, borrowing the curve from my previous answer:
h = plot(0:.01:1, (0:.01:1).^2); %// example curve. Get a handle to it
a = gca; %// handle to current axes
ht = text(.45, .85, ''); %// create text
xdata = get(h,'XData'); %// x values of points from the curve
ydata = get(h,'YData'); %// y values of points from the curve
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht); %// particularize function
imline(a, 'PositionConstraintFcn', fcn_constr); %// create imline
You can also update the text position ('Position' property). Just change the last statement of imline_snap to include that. For example:
set(ht, 'String', ...
num2str((constr_pos(2,2)-constr_pos(1,2))/(constr_pos(2,1)-constr_pos(1,1))), ...
'Position', ...
mean(constr_pos) + [.03 -.03]); %// manually adjust offset if needed
The offset [.03 -.03] is intended to avoid the text overlapping with the line. You may need to change it. Also, it may help to create the text object with boldface. The line becomes
ht = text(.45, .85, '', 'Fontweight', 'bold'); %// create text, in boldface
Here's an example with text position updating:
To delete the associated text object when you delete the imline object you need and event listener. This is an object with three main properties: a cell array of source objects, an event, and a callback function. When the indicated event happens to one of the source objects the callback function of the event listener is executed.
To create an event listener for the imline object's deletion, use that object's addEventListener method and specify the event name and callback function. The callback function is specified by means of a function handle, and it should expect two inputs, which correspond to the source object and the event (this is how the callback function will know "why" it's being called). Even if those inputs won't actually be used, the function needs to be defined that way.
In this case, the event we want to listen to is ObjectBeingDestroyed, the source object is the imline object, and the callback function is delete(ht) (to delete the text object). So, the code in the above example becomes
h = plot(0:.01:1, (0:.01:1).^2); %// example curve. Get a handle to it
a = gca; %// handle to current axes
ht = text(.45, .85, '', 'Fontweight', 'bold'); %// create text, in boldface
xdata = get(h,'XData'); %// x values of points from the curve
ydata = get(h,'YData'); %// y values of points from the curve
fcn_constr = #(pos) imline_snap(pos, [xdata(:) ydata(:)], ht); %// particularize function
hi = imline(a, 'PositionConstraintFcn', fcn_constr); %// create imline and get a handle
addlistener(hi, 'ObjectBeingDestroyed', #(obj,event) delete(ht))
where only the last two lines are new.
Now whenever the imline object is deleted the action delete(ht) will be performed, thus deleting the text object.
I'm working on this function which gets axis handler and data, and is supposed to plot it correctly in the axis. The function is called in for loop. It's supposed to draw the multiple data in one figure. My resulted figure is shown below.
There are only two correctly plotted graphs (those with four colors). Others miss areas plotted before the final area (red area is the last plotted area in each graph). But the script is same for every axis. So where can be the mistake? The whole function is written below.
function [] = powerSpectrumSmooth(axis,signal,fs)
N= length(signal);
samplesPer1Hz = N/fs;
delta = int16(3.5*samplesPer1Hz); %last sample of delta frequncies
theta = int16(7.5*samplesPer1Hz); %last sample of theta frequncies
alpha = int16(13*samplesPer1Hz); %last sample of alpha frequncies
beta = int16(30*samplesPer1Hz); %last sample of beta frequncies
x=fft(double(signal));
powerSpectrum = 20*log10(abs(real(x)));
smoothPS=smooth(powerSpectrum,51);
PSmin=min(powerSpectrum(1:beta));
y1=[(smoothPS(1:delta)); zeros(beta-delta,1)+PSmin];
y2=[zeros(delta-1,1)+PSmin; (smoothPS(delta:theta)); zeros(beta-theta,1)+PSmin];
y3=[zeros(theta-1,1)+PSmin; (smoothPS(theta:alpha)); zeros(beta-alpha,1)+PSmin];
y4=[zeros(alpha-1,1)+PSmin; (smoothPS(alpha:beta))];
a1=area(axis,1:beta,y1);
set(a1,'FaceColor','yellow')
hold on
a2=area(axis,1:beta,y2);
set(a2,'FaceColor','blue')
a3=area(axis,1:beta,y3);
set(a3,'FaceColor','green')
a4=area(axis,1:beta,y4);
set(a4,'FaceColor','red')
ADDED
And here is the function which calls the function above.
function [] = drawPowerSpectrum(axesContainer,dataContainer,fs)
size = length(axesContainer);
for l=1:size
powerSpectrumSmooth(axesContainer{l},dataContainer{l},fs)
set(axesContainer{l},'XTickLabel','')
set(axesContainer{l},'YTickLabel','')
uistack(axesContainer{l}, 'top');
end
ADDED 29th July
Here is a script which reproduces the error, so you can run it in your computer. Before running it again you might need to clear variables.
len = 9;
axesContainer = cell(len,1);
x = [0.1,0.4,0.7,0.1,0.4,0.7,0.1,0.4,0.7];
y = [0.1,0.1,0.1,0.4,0.4,0.4,0.7,0.7,0.7];
figure(1)
for i=1:len
axesContainer{i} = axes('Position',[x(i),y(i),0.2,0.2]);
end
dataContainer = cell(len,1);
N = 1500;
for i=1:len
dataContainer{i} = rand(1,N)*100;
end
for l=1:len
y1=[(dataContainer{l}(1:N/4)) zeros(1,3*N/4)];
y2=[zeros(1,N/4) (dataContainer{l}(N/4+1:(2*N/4))) zeros(1,2*N/4)];
y3=[zeros(1,2*N/4) (dataContainer{l}(2*N/4+1:3*N/4)) zeros(1,N/4)];
y4=[zeros(1,3*N/4) (dataContainer{l}(3*N/4+1:N))];
axes=axesContainer{l};
a1=area(axes,1:N,y1);
set(a1,'FaceColor','yellow')
hold on
a2=area(axes,1:N,y2);
set(a2,'FaceColor','blue')
hold on
a3=area(axes,1:N,y3);
set(a3,'FaceColor','green')
hold on
a4=area(axes,1:N,y4);
set(a4,'FaceColor','red')
set(axes,'XTickLabel','')
set(axes,'YTickLabel','')
end
My result of this script is plotted below:
Again only one picture contains all areas.
It looks like that every call to plot(axes,data) deletes whatever was written in axes.
Important note: Do not use a variable name the same as a function. Do not call something sin ,plot or axes!! I changed it to axs.
To solve the problem I just used the classic subplot instead of creating the axes as you did:
len = 9;
axesContainer = cell(len,1);
x = [0.1,0.4,0.7,0.1,0.4,0.7,0.1,0.4,0.7];
y = [0.1,0.1,0.1,0.4,0.4,0.4,0.7,0.7,0.7];
figure(1)
dataContainer = cell(len,1);
N = 1500;
for i=1:len
dataContainer{i} = rand(1,N)*100;
end
for l=1:len
y1=[(dataContainer{l}(1:N/4)) zeros(1,3*N/4)];
y2=[zeros(1,N/4) (dataContainer{l}(N/4+1:(2*N/4))) zeros(1,2*N/4)];
y3=[zeros(1,2*N/4) (dataContainer{l}(2*N/4+1:3*N/4)) zeros(1,N/4)];
y4=[zeros(1,3*N/4) (dataContainer{l}(3*N/4+1:N))];
axs=subplot(3,3,l);
a1=area(axs,1:N,y1);
set(a1,'FaceColor','yellow')
hold on
a2=area(axs,1:N,y2);
set(a2,'FaceColor','blue')
hold on
a3=area(axs,1:N,y3);
set(a3,'FaceColor','green')
hold on
a4=area(axs,1:N,y4);
set(a4,'FaceColor','red')
set(axs,'XTickLabel','')
set(axs,'YTickLabel','')
axis tight % this is to beautify it.
end
As far as I know, you can still save the axs variable in an axescontainer and then modify the properties you want (like location).
I found out how to do what I needed.
len = 8;
axesContainer = cell(len,1);
x = [0.1,0.4,0.7,0.1,0.4,0.7,0.1,0.4];
y = [0.1,0.1,0.1,0.4,0.4,0.4,0.7,0.7];
figure(1)
for i=1:len
axesContainer{i} = axes('Position',[x(i),y(i),0.2,0.2]);
end
dataContainer = cell(len,1);
N = 1500;
for i=1:len
dataContainer{i} = rand(1,N)*100;
end
for l=1:len
y1=[(dataContainer{l}(1:N/4)) zeros(1,3*N/4)];
y2=[zeros(1,N/4) (dataContainer{l}(N/4+1:(2*N/4))) zeros(1,2*N/4)];
y3=[zeros(1,2*N/4) (dataContainer{l}(2*N/4+1:3*N/4)) zeros(1,N/4)];
y4=[zeros(1,3*N/4) (dataContainer{l}(3*N/4+1:N))];
axes=axesContainer{l};
Y=[y1',y2',y3',y4'];
a=area(axes,Y);
set(axes,'XTickLabel','')
set(axes,'YTickLabel','')
end
The area is supposed to work with matrices like this. The tricky part is, that the signal in every next column is not plotted absolutely, but relatively to the data in previous column. That means, if at time 1 the data in first column has value 1 and data in second column has value 4, the second column data is ploted at value 5. Source: http://www.mathworks.com/help/matlab/ref/area.html
I need to be able to set data tips programmatically from a list of array of x axis values. For example, I create a figure and plot my data.
figure;plot(t1,[filter(b,a,Gyro(:,2)),filter(b,a,Gyro(:,4))])
I have a set of timestamp values from t1 variable (time) (e.g. [0.450, 0.854, 1.2343....]) where I want to place data tips to mark certain events in my data. Without having to place them every time manual by clicking and saving data trip... How can I pass them as array and do this programmatically through matlab script?
You can add matlab datatip programatically and customize them to an extent.
The function below shows how to add a few datatip, position them and customize their display:
The code for this demo (save that in a file demo_datatip.m and run it to obtain the above figure) :
function h = demo_datatip
%// basic sample curve
npts = 600 ;
x = linspace(0,4*pi,npts) ;
y = sin(x) ;
%// plot
h.fig = figure ;
h.ax = axes ;
h.plot = plot(x,y) ;
%// simulate some event times
time_events = x([25 265 442]) ; %// events type 1 at index 25, 265 and 422
%// define the target line for the new datatip
hTarget = handle(h.plot);
%// Add the datatip array
h.dtip = add_datatips( time_events , hTarget ) ;
function hdtip = add_datatips( evt_times , hTarget )
%// retrieve the datacursor manager
cursorMode = datacursormode(gcf);
set(cursorMode, 'UpdateFcn',#customDatatipFunction, 'NewDataCursorOnClick',false);
xdata = get(hTarget,'XData') ;
ydata = get(hTarget,'YData') ;
%// add the datatip for each event
for idt = 1:numel(evt_times)
hdtip(idt) = cursorMode.createDatatip(hTarget) ;
set(hdtip(idt), 'MarkerSize',5, 'MarkerFaceColor','none', ...
'MarkerEdgeColor','r', 'Marker','o', 'HitTest','off');
%// move it into the right place
idx = find( xdata == evt_times(idt) ) ;%// find the index of the corresponding time
pos = [xdata(idx) , ydata(idx) ,1 ];
update(hdtip(idt), pos);
end
function output_txt = customDatatipFunction(~,evt)
pos = get(evt,'Position');
idx = get(evt,'DataIndex');
output_txt = { ...
'*** !! Event !! ***' , ...
['at Time : ' num2str(pos(1),4)] ...
['Value: ' , num2str(pos(2),8)] ...
['Data index: ',num2str(idx)] ...
};
If you need to delete a data tip, you can simply call delete(datatip_handle) on it's handle (or even an array of handles to delete them in group).
Here is a small example that might be what you are looking for:
time = 0:0.5:10;
values = rand(1,length(time));
datatip_index = [3 6]; % an 'event' occurs on the 3rd and 6th datapoint
datatip_text = {'event1', 'event2'};
figure;
plot(time,values)
hold on
text(time(datatip_index), values(datatip_index), datatip_text)