How to add tooltips or overlay text in a Matlab figure - matlab

I have a figure with two or more lines. These lines have additional, important information associated with them, like how many data points were averaged to create the line etc. I would like to access this information in my figure.
I thought a good solution for this would be to if you could hover over a line with your mouse and get that extended information.
However searching for tooltips/overlays/hover-over on figures seemed to not be fruitful.
Example:
figure; hold on;
plot(1:10,rand(10,1))
plot(1:10,rand(10,1))
% additional info
plot_1_info.name = 'Alice';
plot_2_info.name = 'Bob';
plot_1_info.age = 24;
plot_2_info.age = 12;
Any good solutions or better approaches for this?

You can change the data cursor behaviour, this option has good backwards compatability (I've tested the below in R2017b, used similar before in 15b).
See my comments for details:
% Create some data
x = (1:2:20).';
y = rand(10,1);
name = { 'Alice'; 'Alice'; 'Alice'; 'Alice'; 'Bob'; 'Bob'; 'Bob'; 'Chris'; 'Chris'; 'Chris' };
age = [ 24; 24; 24; 24; 12; 12; 12; 17; 17; 17 ];
% Put it in a table, so we have it all together for indexing as plot data
tbl = table( x, y, name, age );
% Create the plot, assign the UserData property to the plot object
f = figure;
plt = plot( x, y );
plt.UserData = tbl;
% Hijack the Data Cursor update callback so we can inject our own info
dcm = datacursormode( f );
set( dcm, 'UpdateFcn', #onDataCursor );
% Function which returns the text to be displayed on the data cursor
function txt = onDataCursor( ~, evt )
% Get containing figure
f = ancestor( evt.Target, 'figure' );
% Get the index within the original data
idx = getfield( getCursorInfo( datacursormode( f ) ), 'DataIndex' );
% The original data is stored in the UserData property
data = evt.Target.UserData;
% Each element of the cell array is a new line on the cursor
txt = { sprintf( 'X: %g', data.x(idx) ), ...
sprintf( 'Y: %g', data.y(idx) ), ...
sprintf( 'Name: %s', data.name{idx} ), ...
sprintf( 'Age: %g', data.age(idx) ) };
end
Output:
Note: I've not handled anywhere the case that there is more than one data cursor tip. You can easily implement a loop over idx within the callback to handle this, I leave it as an exercise.
This approach is really flexible. For instance if we had 3 lines (one per 'person') then they can each have their own UserData struct, and we don't need to repeat all the info in table rows.
A = struct( 'X', 1:4, 'Y', rand(1,4), 'Name', 'Alice', 'Age', 24 );
B = struct( 'X', 1:3, 'Y', rand(1,3), 'Name', 'Bob', 'Age', 12 );
C = struct( 'X', 1:3, 'Y', rand(1,3), 'Name', 'Chris', 'Age', 17 );
f = figure; hold on;
plt = plot( A.X, A.Y ); plt.UserData = A;
plt = plot( B.X, B.Y ); plt.UserData = B;
plt = plot( C.X, C.Y ); plt.UserData = C;
% ... Now the struct fields can be accessed from the callback

Using the new data tip customization system introduced in R2019a, we can do the following:
figure(); hP = plot(1:10,rand(10,1),1:10,rand(10,1));
nPts = cellfun(#numel, {hP.XData});
hP(1).DataTipTemplate.DataTipRows(end+1) = dataTipTextRow('Name', repmat("Alice",nPts(1),1) );
hP(1).DataTipTemplate.DataTipRows(end+1) = dataTipTextRow('Age', repmat(12, nPts(1),1) );
hP(2).DataTipTemplate.DataTipRows(end+1) = dataTipTextRow('Name', repmat("Bob", nPts(2),1) );
hP(2).DataTipTemplate.DataTipRows(end+1) = dataTipTextRow('Age', repmat(24, nPts(2),1) );
% (Of course the above can be organized in a nicer way using a function)
Which yields:
Notice that the "hover datatip" has black text, while the "click datatip" has blue text - this is default behavior.

Related

Problem with Matlab Greybox Identification Toolbox

Dear stackoverflow community
I have been struggeling with the following problem. I have got this code to estimate Parameters with provided Matlab functions for Greybox Modeling. Matlab doesnt give me a step response for my given idnlgrey element. How can that be? If I code one of the given example. I have tried to change the code of one of the given examples regarding greybox identification and the same problems occurrs. I really dont understand what can be the problem here.
clear all; close all; clc;
ParName = {'a'; ... %3x3
'b'; ... %3x3
'c' ... %1x1
};
ParUnit = {'kg'; 'kg/s'; 'm'};
ParValue = {diag([2652.25; 2825.29; 4201.26]); ...
diag([848.05; 10161.62; 22719.39]); ...
100};
FileName = 'boat_c';
Order = [3 2 3];
Parameters = struct('Name', ParName, 'Unit', ParUnit, 'Value', ParValue, 'Minimum', 0, 'Maximum', Inf, 'Fixed', false);
Ts = 0;
InitialStates = struct('Name', {'r'; 's'; 't'}, 'Unit', {'m/s'; 'm/s'; '1/s'}, ...
'Value', [0; 0; 0], 'Minimum', -Inf, 'Maximum', Inf, 'Fixed', true);
nlgr = idnlgrey(FileName, Order, Parameters, InitialStates, Ts, 'Name', 'Boot', ...
'InputName', {'x'; 'y'}, 'InputUnit', {'N'; 'rad'}, ...
'OutputName', {'r'; 's'; 't'}, 'OutputUnit', {'m/s'; 'm/s'; '1/s'}, 'TimeUnit', 's');
step(nlgr)
function [dx, y] = boat_c(t, x, u, m, d, varargin)
y = [x(1); x(2); x(3)];
dx = [m(2,2)/m(1,1)*x(2)*x(3)-d(1,1)/m(1,1)*x(1)+u(1)*cos(u(2))/m(1,1); ...
-m(1,1)/m(2,2)*x(1)*x(3)-d(2,2)*m(2,2)*x(2)+u(1)*sin(u(2))/m(2,2); ...
(m(1,1)-m(2,2))/m(3,3)*x(1)*x(2)-d(3,3)/m(3,3)*x(3)+L/2*u(1)*sin(u(2))/m(3,3)];
end

Detect faces, crop, and save them in different file

I have image data (for 40 people) and I am trying to detect face in each image, crop it and save it in another file. I am using MATLAB for this but it doesn't work.
ERROR : Unable to open file "C:\Users\mstfy\Desktop\Matlab\alex\newdata\cropped\" for writing. You might not have write permission.
I think there is something wrong in my for loop.
location = 'C:\Users\mstfy\Desktop\Matlab\alex\newdata\*.jpg';
croppedimg = 'C:\Users\mstfy\Desktop\Matlab\alex\newdata\cropped\';
imds = imageDatastore ( 'C:\Users\mstfy\Desktop\Matlab\alex\newdata' , ...
'IncludeSubfolders' , true, ...
'LabelSource' , 'foldernames' );
idx = randperm (numel (imds.Files), 16);
j = 1;
figure
for t = 1: 16
img = readimage (imds, idx (t));
FaceDetect = vision.CascadeObjectDetector;
FaceDetect.MergeThreshold = 7;
BB = step (FaceDetect, img);
for i = 1: size (BB, 1)
rectangle ( 'Position' , BB (i, :), 'LineWidth' , 3, 'LineStyle' , '-' , 'EdgeColor' , 'r' );
end
for i = 1: size (BB, 1)
J = imcrop (img, BB (i, :));
figure (3);
subplot (6, 6, i);
imshow (J);
j = j + 1;
imwrite (J,croppedimg,'jpg' )
end
end

A more efficient way of store the vectors from a for loop in MATLAB?

I am trying to store the column vectors from a for loop.
See the below code:
for i = 1:10
a=rand(10,1)
astore = [a a a a a a a a a a a]
end
I know there must be a more efficient way to do this. Especially if I where to have, say i = 1:5000?
As clarified in the comments, you want to append to astore, rather than overwrite it each loop.
You should pre-allocate your output for memory efficiency
k = 10; % number of iterations
aHeight = 10; % Height of each a matrix in the loop.
astore = NaN( aHeight, k );
for ii = 1:k
a = rand( aHeight, 1 );
astore( :, ii ) = a;
end
I'm assuming aHeight is consistent from your example, but if it's not you can use a cell array
k = 10;
astore = cell( 1, k );
for ii = 1:k
a = rand( 10, 1 ); % could be anything
astore{ ii } = a;
end
Pre-allocation is better than appending within a loop using methods like astore(end+1) = a or astore = [astore, a]. Although these are both valid options.

Using xlswrite to save numerical data

Hi I would like to save numerical data from an operation inside a loop. Lets see my segmentation example:
clear all, clc;
a=1:35;
A = arrayfun( #(x) sprintf( '%04d', x ), a, 'UniformOutput', false );
I = cellfun( #(b) imread( ['C:Teste/' b '/c1/' b '.png'] ), A, 'UniformOutput', false );
for i = a
% Gaussian Filter
W = fspecial('gaussian',[10,10],2);
J = imfilter(I,W);
% Finding Circular objects -- Houng Transform
[centers, radii, metric] = imfindcircles(J,[5 10], 'Sensitivity',0.93,'Edge',0.27);
idx_mask = ones(size(radii));
min_dist = 2; % relative value.
for i = 2:length(radii)
cur_cent = centers(i, :);
for j = 1:i-1
other_cent = centers(j,:);
x_dist = other_cent(1) - cur_cent(1);
y_dist = other_cent(2) - cur_cent(2);
if sqrt(x_dist^2+y_dist^2) < min_dist*(radii(i) + radii(j)) && idx_mask(j) == 1
idx_mask(i) = 0;
break
end
end
end
idx_mask = logical(idx_mask);
centers_use = centers(idx_mask, :);
radii_use = radii(idx_mask, :);
metric_use = metric(idx_mask, :);
viscircles(centers_use, radii_use,'EdgeColor','b');
a=length(centers_use)
end
So the point is to save the 35 results in one column of an xls file.
I was trying to do this but only the last element of the loop is printed in the exel file...
filename = 'testdata.xlsx';
A = vertcat('Test', 'Results', num2cell(a'));
sheet = 1;
xlRange = 'F03';
xlswrite(filename,A,sheet,xlRange)
Can please anyone help me out? I know there many questions related to this one but none of them covers my issue...
I will leave here one image for testing:
Thanks a lot in advance.
John
As #excaza said, you need to expand b
a=1:35;
for i = a
b=10+a;
end
filename = 'testdata.xlsx';
A = vertcat('Example', 'Results', num2cell(b'));
sheet = 1;
xlRange = 'B1';
xlswrite(filename,A,sheet,xlRange)

updating a function in MATLAB

The code below works. You can copy-paste this code in case you don't understand what I ask below.
In my gui, I have a pushbutton, popupmenu and edittext. Pushbutton enables adding new strings to popupmenu with the answers obtained from inputdlg. The strings in popupmenu have additional information which is defined in windowType function. When scrolling between popupmenu list, pmh_call calls windowType function and displays the information on edittext. The information on windowType function is predefined. However, what I want is to add new window types and information to this function. This is the first time I develop a GUI. So, I may have started this saving new info business to a function all wrong. Any help is appreciated.
function [] = mygui(varargin)
S.fh = figure('Visible','on','numbertitle','off','Name','Gunebakan GUI',...
'units','pixels','Position',[500 500 200 100]);
S.pbh_definewindow = uicontrol(S.fh,'Style','pushbutton','String','Define window',...
'HorizontalAlignment','center','BackgroundColor',[0.6602 0.0234 0.2539],...
'Position',[10 80 100 20]);
S.pmh_window = uicontrol(S.fh,'Style','popupmenu','String',{'Type1','Type2'},...
'Value',1,'BackgroundColor','w','Position',[10 50 150 18]);
S.eth_windowlength = uicontrol(S.fh,'Style','edit','String','4','Position',[10 10 50 20]);
set(S.pbh_definewindow,'callback',{#pbh_call,S});
set(S.pmh_window,'callback',{#pmh_call,S});
function [] = pmh_call(varargin)
S = varargin{3};
string = get(S.pmh_window,{'String','Value'});
string = string{1}(string{2});
[ windowtypeinfo ] = windowType( string );
str = num2str(windowtypeinfo.length);
set(S.eth_windowlength,'String',str);
function [] = pbh_call(varargin)
S = varargin{3};
error = 1;
prompt = {'Type name:','Type length:'};
name = 'Yeni Agac Tanimlama';
numlines = 1;
defaultanswer = {'benimagacim','2'};
answer = inputdlg(prompt,name,numlines,defaultanswer);
options.Resize = 'on';
options.WindowStyle = 'normal';
options.Interpreter = 'tex';
q = get(S.pmh_window,'String');
error = 1;
while error == 1
if ~any(strcmpi(answer{1},q))
error = 0;
else
answer(1) = inputdlg('Type name already used, enter another!')
error = 1;
end
end
P = get(S.pmh_window,{'string','value'});
q(length(q)+1) = answer(1);
set(S.pmh_window,'string',q);
function [ windowtypeinfo ] = windowType( string )
if strcmpi(string,'Type1')
windowtypeinfo.typename = 'Type1';
windowtypeinfo.length = 4;
elseif strcmpi(string,'Type2')
windowtypeinfo.typename = 'Type2';
windowtypeinfo.length = 5;
end
The question isn't really well posed but I think I understand what you're asking. To answer your final thought, I would say there is a better way to store your data. Consider the following simplified example:
function testgui
% Initialize GUI
h.myfig = figure;
h.mybutton = uicontrol( ...
'Style', 'pushbutton', ...
'Units', 'normalized', ...
'Position', [0.15 0.70, 0.70 0.15], ...
'String', 'Add Style' ...
);
h.mymenu = uicontrol( ...
'Style', 'popupmenu', ...
'Units', 'normalized', ...
'Position', [0.15 0.50 0.70 0.10], ...
'String', ' ' ...
);
h.myedit = uicontrol( ...
'Style', 'edit', ...
'Units', 'normalized', ...
'Position', [0.15 0.15 0.70 0.10], ...
'String', ' ' ...
);
set(h.mybutton, 'callback',{#addwindow, h});
set(h.mymenu, 'callback',{#changewindow, h});
% Initialize data structure
mydata(1).typename = 'Type1';
mydata(1).windowlength = 4;
mydata(2).typename = 'Type2';
mydata(2).windowlength = 5;
updatepopup(mydata, h);
% Set initial editbox value
set(h.myedit, 'String', mydata(1).windowlength);
% Store data for later
guidata(h.myfig, mydata);
end
function updatepopup(mydata, h) % Update strings in popup menu based on data array
typenames = {mydata.typename}; % Collect all the existing typenames
set(h.mymenu, 'String', typenames); % Update popup menu
end
function addwindow(~, ~, h) % Prompt user to add window
prompt = {'Type name:','Type length:'};
name = 'myprompt';
nlines = 1;
defaultanswer = {'asdf', '1'};
myanswer = inputdlg(prompt, name, nlines, defaultanswer);
% Prepare new information for storage
newwindow.typename = myanswer{1};
newwindow.windowlength = str2double(myanswer{2});
mydata = guidata(h.myfig); % Pull in existing data
mydata = [mydata newwindow]; % Add new data to existing data
updatepopup(mydata, h); % Update popup menu
guidata(h.myfig, mydata); % Store data for later
end
function changewindow(~, ~, h) % Update editbox based on popup menu selection
mydata = guidata(h.myfig); % Pull in stored data
windowselected = get(h.mymenu, 'Value'); % Get index of window selected
set(h.myedit, 'String', mydata(windowselected).windowlength); % Change editbox
end
Rather than manually getting your window type with a separate function (windowType() in your example), I would try storing your data in the GUI as a data structure and pulling it out as necessary. This allows you to use more generalized code and not have to think about how to handle every case with the loop you have now. Hopefully this is easy to follow and adapt to your needs.
You should use guide in order to easily develop GUI's in MATLAB. If you do, you will find that each element in your GUI is called by a handles.something.
For example:
Lets say that you want to add the string in the inputdlg to your popup menu, you should do something like this:
set(handles.popupMenu,'String',[predefStrings; get(handles.inputdlg,'String')]);
The 'String' argument in a popup menu must be an array of strings.