Plot to highlight selected data - matlab

I'd like to create something similar to this. The differences are: my table only has one column, which is the variable I changed to get my data. I have the table set up and working, and most of what I have works, but I only seem to be able to highlight one thing at once. This is what I have:
% Set up plots etc
for a = 1:length(data);
xAxis = data{a,1}(:,X);
yAxis = data{a,1}(:,Y);
plot(graph,xAxis,yAxis);
% Visible data
hilite = plot(graph,xAxis,yAxis,'o-','LineWidth',2,'HandleVisibility','off','Visible','off');
% Invisible data, to be highlighted later
hold all
end
hold off
% Data table callback function
function dataTable_Callback(hObject, eventdata)
set(hilite,'Visible','off')
names = get(hObject,'Data');
select = eventdata.Indices(:,1);
for d = 1:length(select)
group = select(d);
xAxis = [];
% makes sure that previously selected data is removed
yAxis = [];
xAxis(:,d) = data{group,1}(:,X);
% if I remove the semicolon and let MATLAB print this data, I always get
the correct data in the correct columns, adding and subtracting them when
I select them
yAxis(:,d) = data{group,1}(:,Y);
set(hilite,'Visible','on','XData',xAxis(:,d),'YData',yAxis(:,d))
legend(hilite, num2str(names{select,1}(1,1)));
end
end
I've been playing with this for a few hours now, and I really can't work out what my issue is. Any help is obviously much appreciated! :-)

Related

duplicate axes in a figure

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.

Scatter persists after setting X & YData to fewer points

EDIT: See end of post for code to automatically reproduce the issue without the mouse/keyboard input and for pictures describing the issue.
Please let me know if any of the following explanation or code requires further clarification.
I'm allowing a user to click on an axis and scatter points appear where they click. Left clicking results in larger scatter markers, and right clicking results in smaller scatter markers. Pressing enter adds a new set (represented by scatter markers switching between yellow and red). Pressing backspace should delete the most recent marker, and consecutively delete markers back through the set and into previous sets if continued to be pressed.
This works fine if I use only one size marker, however, if I allow the size to be determined by the left/right click, then upon clicking backspace to delete previous points, it will correctly delete the points until you arrive at the first point (the earliest point, not the first you try to delete) which had a size change. The still appear on the figure as if they were a separate scatter plot, even though they are one scatter plot, and the data the plot is set from has been deleted (and the scatter data set to that deleted data set).
Here is my example code:
function CodeToReplicateIssue()
a = get(0,'ScreenSize');
set(0,'DefaultFigurePosition',[a(3)*.3,a(4)*.1,a(3)*.4,a(4)*.8]);
mainFig = figure;
set(mainFig, 'WindowButtonDownFcn', #mouseDown);
set(mainFig,'WindowKeyPressFcn', #keyPress);
pfTitle = 'Next Set: Enter Delete Previous Point: Backspace';
colormap('gray');
imagesc(zeros(512,512));
set(gca,'XTick',[]);
set(gca,'YTick',[]);
axis image;
title(pfTitle)
hold on;
cStrs2 = {'r','y'};
pfTracks = {[]};
pfTracksRef = {[]};
pfTrackI = 1;
pfScatters{1} = scatter([],[],5,cStrs2{2});
function mouseDown(source,callbackData)
%display('mouseDown');
pos = get (gca, 'CurrentPoint');
pos = pos(1,1:2);
pfTracksRef{pfTrackI}(end+1) = ~strcmp(get(source,'SelectionType'),'alt');
pfTracks{pfTrackI}(end+1,1:2) = pos;
if(numel(pfScatters)<pfTrackI)
pfScatters{pfTrackI} = scatter(pos(1),pos(2),getSizes(pfTrackI),cStrs2{mod(pfTrackI,2)+1});
else
set(pfScatters{pfTrackI},{'XData','YData','SizeData'},{pfTracks{pfTrackI}(:,1),pfTracks{pfTrackI}(:,2),getSizes(pfTrackI)});
end
end
function keyPress(source,eventdata)
%display(eventdata.Key);
if(strcmp(eventdata.Key,'return'))
if(isempty(pfTracks{pfTrackI}))
%do nothing
else
refreshScatter('pfScatters');
pfTrackI = pfTrackI+1;
pfTracks{pfTrackI} = [];
pfTracksRef{pfTrackI} = [];
end
elseif(strcmp(eventdata.Key,'backspace'))
if(isempty(pfTracks{pfTrackI}) && numel(pfTracks)>1)
pfTracks(pfTrackI) = [];
pfTracksRef(pfTrackI) = [];
if(numel(pfScatters) >= pfTrackI)
pfScatters(pfTrackI:end) = [];
end
pfTrackI = pfTrackI -1;
end
if(numel(pfTracks{pfTrackI}))
pfTracks{pfTrackI}(end,:) = [];
pfTracksRef{pfTrackI}(end) = [];
end
refreshScatter('pfScatters');
end
end
function refreshScatter(strOption)
if(nargin<1)
strOption = '';
end
if(any(strcmp(strOption,{'','pfScatters'})))
for n = 1:numel(pfScatters)
if(numel(pfTracks{n})>0)
set(pfScatters{n},{'XData','YData','SizeData','MarkerEdgeColor'},{pfTracks{n}(:,1),pfTracks{n}(:,2),getSizes(n),cStrs2{mod(n,2)+1}});
else
set(pfScatters{n},{'XData','YData'},{[],[]});
end
end
end
end
function scatterSize = getSizes(indx)
scatterSize = 9*(pfTracksRef{indx}(:)+1)-6;
end
end
Line 32:
pfTracksRef{pfTrackI}(end+1) = ~strcmp(get(source,'SelectionType'),'alt');
This is where the left or right mouse button is recorded, later used in getSizes(), with the output used for calls to scatter (or when setting the scatter series SizeData).
I can't figure out why the points created before a size change persist after deleteing the associated data and setting the scatter values to that data. It seems like the first time a size is changed a new scatter series is created.
Any light that could be shed on this issue would be appreciated, and any fixes would be doubly appreciated.
Thanks.
EDIT: One way to see what I'm talking about is to do the following:
Left click several times (lets say 5), spaced out so you can see them all.
Push Enter
Left click 5 more times, again spaced out, note they are in red (this is a desired functionality for my final product).
Press backspace until all the points are no longer visible. This is the correct funcitonality.
Incorrect:
Left click 1 time, right click 4 times.
Press enter
Left click 1 time, right click 4 times.
Backspace repeatedly (10 times). Note that the first 2 points (1 large and 1 small) from both the red and the yellow persist, even though you have clicked backspace enough times to delete everything, which is what happened when there was no size change.
EDIT: I've added code below that replicates the issue I am seeing using some loops and doing away with requiring mouse and keyboard input.
function ReplicateIssueWithLoops()
mainFig = figure;
imshow(zeros(512,512));
hold on;
axis image;
for hh = 1:2
%two runs, the first works fine, the second fails to remove scatter
%points from the figure.
cStrs = {'r','y'};
tracks = {[]};
tracksSizeRef = {[]};
trackI = 1;
scatters{1} = scatter([],[],5,cStrs{2});
for ii = 1:2
%simulates 2 sets
for jj = 1:5
%simulates 5 left clicks each
tracks{ii}(end+1,:) = [256,(ii*6+jj)*20];
if(hh ==1) %Simulates all left clicks
tracksSizeRef{ii}(end+1) = 12;
else %Simulates 1 left click and 4 right clicks
tracksSizeRef{ii}(end+1) = ((jj==1)+1)*9-6;
end
if(numel(scatters)<ii)
scatters{ii} = scatter(tracks{ii}(:,1),tracks{ii}(:,2),tracksSizeRef{ii},cStrs{mod(ii,2)+1});
else
set(scatters{ii},{'XData','YData','SizeData'},{tracks{ii}(:,1),tracks{ii}(:,2),tracksSizeRef{ii}});
end
pause(0.25);
end
tracks{ii+1}=[];
tracksSizeRef{ii+1} = [];
end
trackI = ii;
for kk = 1:10
%simulates 10 backspaces
if(isempty(tracks{trackI}) && numel(tracks)>1)
tracks(trackI) = [];
tracksSizeRef(trackI) = [];
if(numel(scatters) >= trackI)
scatters(trackI:end) = [];
end
trackI = trackI -1;
end
if(numel(tracks{trackI}))
tracks{trackI}(end,:) = [];
tracksSizeRef{trackI}(end) = [];
end
refreshScatters();
pause(0.25);
end
end
function refreshScatters()
for n = 1:numel(scatters)
if(numel(tracks{n})>0)
set(scatters{n},{'XData','YData','SizeData','MarkerEdgeColor'},{tracks{n}(:,1),tracks{n}(:,2),tracksSizeRef{n},cStrs{mod(n,2)+1}});
else
set(scatters{n},{'XData','YData'},{[],[]});
end
end
end
end
Below are pictures demonstrating how the issue looks on my end. In left to right order the pictures show 1) at the end of the first 10 points, 2) after deleting the first 10 points , 3) after the second ten points (the set with size difference), 4) after deleting the second 10 points (4 remain, the two from each set when the size was first changed).

Save multiple GUIdata sets to listbox and then load them again in MATLAB

I have built a calculation software in MATLAB GUIDE. What I want to do is to fill out all my calculation data in different edit fields and some dropdowns and when I press calculate a "listbox" should be populated with the text "Calculation 1".
If I then change some data in some of the input fields and press calculate again I want to populate the listbox with "Calculation 2" beneath "Calculation 1" etc...
But then I would want to be able to highligt "calculation 1" again in the listbox and press a "load input parameters" button to populate all the edit input fields with the data that was used when "calculation 1" was calculated.
I have looked all over the place for this but can't find anything.
//Robin
Here is some code which is very basic but performs what you are looking for. There are a lot of tweaks possible but I'll let you play around with them. I put explanations as comments. You can copy past into Matlab and change the GUI as you like.
function CalculatorGUI
% Dummy GUI to calculate A*B + C...
clc
clear
close all
global hTestResult hEditA hEditB hEditC CalculationList CalculationStrings
% Set up controls
CalculationList = nan(10,3); % Create array in which we store the parameters. 1st column is A, 2nd is B and 3rd is C.
CalculationStrings = cell(10,1);
ScreenSize = get(0,'ScreenSize');
hFig = figure('Visible','off','Position',[ScreenSize(3)/2,ScreenSize(4)/2,450,285]);
hCalculateButton = uicontrol('Style','Pushbutton','Position',[350,150,80,30],'String','Calculate!','Callback',#CalculateCallback);
hTitle = uicontrol('Style','Text','Position',[100,250,100,25],'String','Calculate (A * B) + C');
hTextA = uicontrol('Style','Text','Position',[125,220,70,25],'String','A');
hEditA = uicontrol('Style','Edit','Position',[125,200,70,25],'String','1');
hTextB = uicontrol('Style','Text','Position',[200,220,70,25],'String','B');
hEditB = uicontrol('Style','Edit','Position',[200,200,70,25],'String','2');
hTextC = uicontrol('Style','Text','Position',[275,220,70,25],'String','C');
hEditC = uicontrol('Style','Edit','Position',[275,200,70,25],'String','3');
hResultHeader = uicontrol('Style','Text','Position',[350,220,70,25],'String','Result');
hTestResult = uicontrol('Style','Text','Position',[350,200,70,25],'String','');
hTextCalcu = uicontrol('Style','Text','Position',[100,140,100,50],'String','Calculations');
hListCalcu = uicontrol('Style','Listbox','String','','Position',[100,120,200,50],'max',10,...
'min',1,'Callback',#ListBox_Callback);
set(hFig,'Visible','on')
%======================================================================
%======================================================================
% Callback of the pushbutton
function CalculateCallback(~,~)
% Get the values in the edit boxes. There is no ckechup to make
% sure the user entered a correct value...
A = str2double(get(hEditA,'String'));
B = str2double(get(hEditB,'String'));
C = str2double(get(hEditC,'String'));
Calculation = A*B+C;
% Display the result.
set(hTestResult,'String',sprintf('The result is %0.2f',Calculation));
% Find how many calculations have been performed and append the
% parameters to the current list
[x,~] = find(~isnan(CalculationList));
CurrentCalc = numel(unique(x)); % Get number of rows which are NOT NaNs.
CurrentValues = [A B C];
CalculationList(CurrentCalc+1,:) = CurrentValues;
CurrentString = sprintf('A = %0.2f B = %0.2f C = %0.2f',A,B,C);
% Assign the parameters to the cell array.
CalculationStrings(CurrentCalc+1) = {CurrentString};
set(hListCalcu,'String',CalculationStrings)
end
% Listbox callback: When the selection changes, the corresponding
% parameters in the edit boxes change.
function ListBox_Callback(~,~)
SelectedCalc = get(hListCalcu,'Value');
CalculationList(SelectedCalc,1)
CalculationList(SelectedCalc,2)
CalculationList(SelectedCalc,3)
set(hEditA,'String',CalculationList(SelectedCalc,1));
set(hEditB,'String',CalculationList(SelectedCalc,2));
set(hEditC,'String',CalculationList(SelectedCalc,3));
end
end
The actual interface looks like this:
Of course you can make it much more complex, but this should help you get started and understand how the different callbacks work together. Have fun!

Canonical Way to Aggregate Structures into a Vector

I feel dumb even having to ask this, it really should be dead simple, but being new to MatLab I'd like to know how a more experienced person would do it.
Simple problem; I need to find some regions in multiple images, correlate them by position, save those regions of interest, and use them later. One way to do that would be to store the regions in a vector.
%% pseudo code
regions = [];
for i = some_vector_of_images
% segment, get mask
% find regions
cc = bwconncomp(mask);
stats = regionprops(cc, 'all');
% correlate against known x/y
% save for later
regions[index++] = stats;
end
% use 'regions'
However, the array declaration is problematic. Its default type is double, so that won't work (can't assign a struct to an element). I've tried struct.empty, but the array is not resizable. I've tried a cell array, but I get a similar error (Conversion to cell from struct is not possible.)
Really, I just need a way to have some collection declared prior to the loop that will hold instances of these structures. Again, pretty noobish question and slightly embarrassed here... please take pity.
See if using struct2cell helps you with this. Give this pseudo-code a try -
regions = cell(num_of_images,1) %// This will be before the loop starts
...
regions[index++] = {struct2cell(stats)} %// Inside the loop
Please not that this a pseudo-code, so square brackets and ++ won't work.
Thus, the complete version of pseudo-code would be -
%%// --- pseudo code
%// Without this pre-allocation you would get the error -
%%// "Conversion to cell from struct is not possible"
regions = cell(numel(some_vector_of_images),1)
for i = some_vector_of_images
% segment, get mask
% find regions
cc = bwconncomp(mask);
stats = regionprops(cc, 'all');
% correlate against known x/y
% save for later
regions(i) = {struct2cell(stats)}
end
You can cast your empty array to a structure array by appending a structure. Replace regions[index++] = stats; with
regions = [regions, stats];
This line will continue to build the array within the loop. This idiom is generally frowned on in MATLAB because a new array needs to be created each loop.
Another method is to preallocate the array with a template structure, using repmat.
stats = some_operations_on(some_vector_of_images(1));
regions = repmat(stats, numel(some_vector_of_images), 1);
and within the loop, assign with
regions(i) = stats;
In this scenario, typically I just don't preallocate at all, or use a cell-cat pattern.
Not initializing
This one doesn't initialize the struct array, but works fine. Make sure i is an index of each element in this case.
for i = 1:numel(some_vector_of_images)
% mask = outcome of some_vector_of_images(i)
cc = bwconncomp(mask);
regions(i) = regionprops(cc, 'all');
end
cell-cat pattern
This one catches results in a cell array, and concatenates all elements at the end.
regions = cell(numel(some_vector_of_images), 1);
index = 1;
for i = some_vector_of_images
% mask = outcome of i
cc = bwconncomp(mask);
regions{index} = regionprops(cc, 'all');
index = index + 1;
end
regions = cat(1, regions{:});

Customizing cursor in Matlab

In Matlab 2012a I have generated a figure from a previous code that is SSI as a function of age.
I want to customize datatip by updating my own function instead of the default one. I know how to change x and y and now I have Age and SSI for them. However, I have another piece of information -subjectID- which I want to add to the display text.
By clicking on each point, I want datatip to show Age, SSI and subject ID of corresponding data point.
This is what I have now:
matlab is a saved work place of my SSI-age.
function output_txt = myupdatefcn(obj,event_obj,...
matlab,labels,SubjectID)
pos = get(event_obj,'Position');
x = pos(1);
y = pos(2);
[~, ~, raw0_0] = xlsread('Data.xlsx','CONTROLS','A2:A106');
raw = [raw0_0];
SubjectID = cell2mat(raw);
output_txt = {['AGE: ',num2str(pos(1),4)],...
['SSI: ',num2str(pos(2),4)],...
['SubjectID: ',SubjectID]};
idx = find(matlab == x,1);
[row,col] = ind2sub(size(matlab),idx);
output_txt{end+1} = cell2mat(labels(row));
Obviously, this is not right. Can somebody please help me out here? Thank you.
If I read your code correctly, I make the following assumptions (which may be incorrect):
* the subjectID is a cell containing a vector of strings
* subjectID is X position of the clicked point
First, a quick digression: getting SubjectID into your plot
I noticed, in your function call, you have SubjectID as one of the input parameters. However, it appears that it would never be used, since the next line that uses it assigns it a value.
As written, this will read from the excel file each and every time that the update function is called. You might wish to move the load-from-excel portion into the same section of code where the data is first loaded. If I assume the SubjectID is text, you can store it in the UserData variable of the timeseries. That would make the following work:
Onward to the answer
So, if you include your SubjectID information in the userdata, when you first plot like so:
% ...not shown: get the ages, SSIs and SubjectIDs ....
plot(ages, SSIs, 'UserData', SubjectIDs); % Store SubjectIDs along with the line...
Then the following should work -- or at least put you on solid ground.
function output_txt = myupdatefcn(obj,event_obj)
pos = get(event_obj,'Position');
x = pos(1);
y = pos(2);
allIDs = get(event_obj.Target,'UserData');
thisSubject = event_obj.UserData{pos(1)};
output_txt = {['AGE: ',num2str(pos(1),4)],...
['SSI: ',num2str(pos(2),4)],...
['SubjectID: ',thisSubject]};
You can probably get rid of the last 3 lines of code, since you know a priori that all 3 values are accessible.
Hope that helps.