Displaying the slope of a line created by imline in a text box - matlab

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.

Related

Matlab draw line with plot and dynamic values

I am trying to draw line with plot in matlab app designer. But i dont know how to create line. I am just redrawing point on new coordinates.
This is my code (functions runs each second) (format of plot is plot(PlotUI,X,Y))
function function2(app)
app.timeCounter = app.timeCounter + 1;
plot(app.UIAxes,app.timeCounter,app.newValDblPublic);
end
I will be thankful for any help.
At the moment you are just plotting the current set of values, if you want to plot the historic values too, you need to keep them in an array and plot the entire array.
%When the GUI is first created, start with one value in the array
app.time_values = [0];
app.y_values = [0];
%Inside your function
function function2(app)
app.time_values(end+1) = app.time_values(end)+1; % Add a new value to the array, 1 greater than the last value
app.y_values(end+1) = app.newValDblPublic;
plot(app.UIAxes,app.time_values,app.y_values);
end

Use text function inside a gui

I want to use the text function to add some info to a curve in a gui. But the problem is that when I set the coordinates and specify the handles to the figure I don't get anything shown on the figure. The text just doesn't show anything. The code is inside a for loop. But here's a small portion of my code containing the text functions..
I have defined some matrices d, s, c, J, and mi somewhere else..
animh=handles.runAnimationOnAxes;
V=s*Es+c*0.7053*Z^2/A^(1/3)+...
J(it).*(J(it)+1)*(hbar)^2./(2*(mi*2/5*M*r0^2*A^(2/3)+...
4*M*a^2));
p=polyfit(d,V,5);
pol=p(1)*d.^5 + p(2)*d.^4+p(3)*d.^3+p(4)*d.^2+p(5)*d+p(6);
plot(animh,d,pol,'k')
ylabel(animh,'$V(r/R_0)\ (Mev)$','interpreter','latex',...
'FontSize',11);
xlabel(animh,'$r/R_0$','interpreter','latex',...
'FontSize',11);
xlim(animh,[0.3 2.5]);
ylim(animh,[Ymin Ymax]);
title(animh,'MLDM potential energy surface');
indexmin = find(min(pol) == pol);
xmin = d(indexmin);
ymin = pol(indexmin);
indexmax = find(max(pol) == pol);
xmax = d(indexmax);
ymax = pol(indexmax);
strmin = ['Minimum = ',num2str(ymin)];
text(animh,xmin,ymin,strmin);
strmax = ['Maximum = ',num2str(ymax)];
text(animh,xmax,ymax,strmax);
The text function does not have, within its input parameters the handle of the axes on which to add the string.
Actually, the first two input should be the x coord and the y coord of the point in which to add the string.
Moreover, text can be used to add strings also in 3D graphs, so in your instructions
text(animh,xmin,ymin,strmin);
and
text(animh,xmax,ymax,strmax);
the axes handle is interpreted as the x coord, while xmax and ymax, respectively as y coord and z coord.
If in your GUI you have only one axes you just have to remove the first parameter in the calls to text.
If in your GUI you have more than one axes you should make the axes in which you want to add the string the "current axes":
EDIT: removed the first parameter in the call to text
axes(handles.runAnimationOnAxes)
strmin = ['Minimum = ',num2str(ymin)];
text(xmin,ymin,strmin);
strmax = ['Maximum = ',num2str(ymax)];
text(xmax,ymax,strmax);
Hope this helps.
Qapla'

Creating an unknown amount of uicontrols in matlab

So for a self project I'm creating a gui minesweeper like game in matlab and want to create an adjustable pushbutton grid however I'm not sure on how to do so. this is what I have got so far.
function createField()
xAmount = str2double(inputdlg('enter row length'));
yAmount = str2double(inputdlg('enter column length'));
for i = 1:xAmount
for j = 1:yAmount
%create buttons
end
end
end
One solution might be:
function create_field(hparent, nx, ny, width, padding)
% Test arguments
if ~test_parent_handle(hparent)
error('Parent must be a single valid graphic handle.');
elseif ~test_positive_integer(nx)
error('Number of buttons on X direction must be a scalar positive integer.');
elseif ~test_positive_integer(ny)
error('Number of buttons on Y direction must be a scalar positive integer.');
elseif ~test_positive_integer(width) ...
|| (width >= 100)
error('Button width must be a scalar positive integer smaller than 100.');
elseif ~test_positive_integer(padding) ...
|| (padding >= 20)
error('Button padding must be a scalar positive integer smaller than 20.');
end;
% Resize the parent to fit the button grid
set(hparent, 'Units', 'pixels');
ppos = get(hparent, 'Position');
ppos(3) = nx*width + (nx-1)*padding;
ppos(4) = ny*width + (ny-1)*padding;
set(hparent, 'Position', ppos);
% Create button grid
for p = 1:nx
for q = 1:ny
bpos = [ % Button spec:
(p-1)*(width+padding) % - X
(q-1)*(width+padding) % - Y
width % - W
width % - H
];
uicontrol( ...
'Units', 'pixels', ...
'Tag', sprintf('X%dY%d',p,q), ...
'Style', 'pushbutton', ...
'Parent', hparent, ...
'Position', bpos ...
);
end;
end;
% ----- NESTED FUNCTIONS -----
function tf = test_parent_handle(value)
tf = isscalar(value) ...
&& ishandle(value);
end
function tf = test_positive_integer(value)
tf = isscalar(value) ...
&& isreal(value) ...
&& isfinite(value) ...
&& (value > 0) ...
&& (fix(value) == value);
end
end
For a figure with 15 x 10 square buttons, each having the side 25 pixels with a padding of 3 pixels between the buttons, call:
create_field(figure(), 15, 10, 20, 3);
As with most problems there are many different approaches, having written something similar I'll give you the same prompts that I used when writing my helper function.
Your code is going to act based on what button is pressed so each button will need its own unique ID and properties. Depending on the MATLAB version used, each of your graphics elements will have a handle. Starting in R2014b, graphics objects can be addressed directly as objects rather than needing to utilize a numeric ID as a pointer.
Start with the figure window and check out the figure properties:
h.mainfig = figure; % Arbitrary figure window
get(h.mainfig); % Dump properties to command window
Right now we're probably most interested in the Units and Position properties of the main figure window, which you can use in a helper function to figure out how to size and space the buttons as you create them.
If we create a pushbutton graphics object with uicontrol() we're going to get mostly the same properties.
h.randombutton = uicontrol('Parent', h.mainfig, 'Style', 'pushbutton');
get(h.randombutton);
Again, we're interested in the Units and Position properties. We're also going to be interested in the Callback property, which is the function that executes when we interact with the button. Another good one is the Tag property, which can be used to set a unique string tag for each button for use with later logic.
You've probably noticed I'm using a structure array to store my graphics handles. This is similar to how MATLAB generates its object data when creating a GUI with GUIDE and has the huge advantage of a tidy data package to pass around our function. A great thing about structure arrays is that you can nest data structures, allowing us to easily generate and address graphics objects without needing to get clever with dynamic field references or eval() (yuck). Instead of having to do something like button_1, button_2, etc, we can do:
h.button(1) = uicontrol('Parent', h.mainfig, 'Style', 'pushbutton');
h.button(2) = uicontrol('Parent', h.mainfig, 'Style', 'pushbutton');
...
h.button(n) = uicontrol('Parent', h.mainfig, 'Style', 'pushbutton');
Now we know how to generate an arbitrary number of buttons programmatically and easily address them later on.
Other than the button generation we have another key function that I mentioned earlier, the button callback. Callback functions follow a slightly different syntax in that they natively pass along two arguments, the handle of the object whose callback is executing and the event data structure (see the documentation for more info). Because the function knows what UI object invoked it, we can make a pretty generic function.
Hopefully this helps!
Have you considered creating the ui in Java - undocumented matlab has some examples. Java would provide you nice LayoutManagers that will take care about resizing and more.

how to prevent uimenu (MATLAB) from disappearing when checked

I have added uicontextmenu to the line object. uicontextmenu includes 3 check boxes. whenever I check any of them uicontextmenu disappears. I want uicontextmenu visible for sometime so that i can check multiple boxes and see the change (same as a button group but in uicontextmenu). Is there any solution to this or some other approach?
cmenu=uicontextmenu;
set(he,'uicontextmenu',cmenu);
item1=uimenu(cmenu,'label','Data A','checked','off','callback',#func_a);
item2=uimenu(cmenu,'label','Data B','checked','off','callback',#func_b);
item3=uimenu(cmenu,'label','Data C','checked','off','callback',#func_c);
basically, he is the line object created by plot(x,y) and func_a, func_b, func_c are function to convert property 'checked' to on|off.
This example is greatly inspired by Benoit_11 solution, but a bit refined. I was also under the impression that the 3 different functions in your callback were doing different things so I made the 3 different menus change different properties of the line (instead of changing the same property with different values).
I made the uimenu callback in one single nested function. It decides what to do based on the parameter what2do supplied at the uimenu definition (but feel free to keep 3 separate functions). However, note that the function that toggle the check mark is the same for all uimenu (you don't need a separate function for each of them).
function hf = TestUiContext2
%// Extension of Benoit_11 solution
clear ; clc ; close all
hf = figure ; %// return the handle of the figure
hax = axes; %// Create axes and save handle
plot(rand(20,3)); %// Plot three lines
hcmenu = uicontextmenu; %// Define a context menu; it is not attached to anything
%// Define the context menu items and install their callbacks
item1 = uimenu(hcmenu, 'Label','Bold line' , 'Callback' , {#uiCallback,'bold'} );
item2 = uimenu(hcmenu, 'Label','Dotted line' , 'Callback' , {#uiCallback,'dots'} );
item3 = uimenu(hcmenu, 'Label','Markers on' , 'Callback' , {#uiCallback,'mark'} );
hlines = findall(hax,'Type','line'); %// Locate line objects
for line = 1:length(hlines) %// Attach the context menu to each line
set(hlines(line),'uicontextmenu',hcmenu)
end
function uiCallback(obj,~,what2do)
hline = gco ;
switch what2do
case 'bold'
toggle_bold_line(hline)
case 'dots'
toggle_dotted_line(hline)
case 'mark'
toggle_markers(hline)
end
%// reposition the context menu and make it visible
set(hcmenu,'Position',get(gcf,'CurrentPoint'),'Visible','on')
toggle_checkmark(obj) %// toggle the checkmark
end
function toggle_checkmark(obj)
if strcmp(get(obj,'Checked'),'on')
set(obj,'Checked','off')
else
set(obj,'Checked','on')
end
end
function toggle_bold_line(hline)
if get(hline,'LineWidth')==0.5
set(hline,'LineWidth',2)
else
set(hline,'LineWidth',0.5)
end
end
function toggle_dotted_line(hline)
if strcmpi(get(hline,'LineStyle'),':')
set(hline,'LineStyle','-')
else
set(hline,'LineStyle',':')
end
end
function toggle_markers(hline)
if strcmpi(get(hline,'Marker'),'none')
set(hline,'Marker','o')
else
set(hline,'Marker','none')
end
end
end
Now you can enjoy ticking all your menu in one go ;)
Here is a workaround which might do the trick for you. That's not too elegant but it seems to work.
The trick is to set the menu 'Visible' property to 'on' in every callback you have (i.e. #func_a, #funct_b and #funct_c). When I run the following example (based on the demo on the Mathworks website) the menu does not disappear when the selection is changed. Notice that I created separate functions for each callback.
Here is the code:
function TestUiContext( ~)
%// Based on example from The Mathworks
%// http://www.mathworks.com/help/matlab/ref/uicontextmenu.html
clear
clc
close all
%// Create axes and save handle
hax = axes;
%// Plot three lines
plot(rand(20,3));
%// Define a context menu.
hcmenu = uicontextmenu;
%// Define the context menu items and install their callbacks
item1 = uimenu(hcmenu,'Label','dashed','Callback',#(s,e) hcb1);
item2 = uimenu(hcmenu,'Label','dotted','Callback',#(s,e) hcb2);
item3 = uimenu(hcmenu,'Label','solid','Callback',#(s,e) hcb3);
%// Locate line objects
hlines = findall(hax,'Type','line');
%// Attach the context menu to each line
for line = 1:length(hlines)
set(hlines(line),'uicontextmenu',hcmenu)
end
%// In the callback of every item/option, set the menu property 'Visible' to 'on'.
function hcb1
set(gco,'LineStyle','--');
set(hcmenu,'Visible','on')
end
function hcb2
set(gco,'LineStyle',':');
set(hcmenu,'Visible','on')
end
function hcb3
set(gco,'LineStyle','-');
set(hcmenu,'Visible','on')
end
end
And 2 screenshots to show what it looks like:
And moving the cursor down:
So as I said, not perfect but hopefully it will do the job for you!

How in Matlab do changes on figure 1 with slider on figure 2?

I have some sliders on figure 1, and I have some images on figure 2. I want to do the callbacks for the sliders in a way that, when I change the sliders in figure 1 , the threshold changes and images update automatically in figure 2.
I'm using addlistener to send values for callback function. The problem is when you move slider the active figure is figure 1, and you want to do changes on figure 2.
adding some code for clarification:
M.rgbImage = imread('euhedral-mag-on-po-edge-pseudo-sub-ophitic-rl-fov-4-8mm.jpg');
[rows, columns, numberOfColorBands] = size(M.rgbImage);
F.f = figure; % This is the figure which has the axes to be controlled.
% Now create the other GUI
S.fh = figure('units','pixels',...
'position',[400 400 500 100],...
'menubar','none',...
'name','Image control',...
'numbertitle','off',...
'resize','off');
S.sl = uicontrol('style','slide',...
'unit','pix',...
'position',[60 10 270 20],...
'min',0,'max',255,'val',100,...
'callback',{#sl_call2,S},'deletefcn',{#delete,F.f});
....
lis = addlistener(S.sl,'Value','PostSet',#(e,h) sl_call3(S,F,M));
function sl_call3(S,F,M)
v = get(S.sl,'value');
figure(F.f), subplot(4, 4, 13);
M.redMask = (M.redPlane > v);
imshow(M.redObjectsMask, []);
set(S.ed(2),'string',v);
Create reference to both your figures:
f1=figure(1);
f2=figure(2);
And then when doing the callback pass f2 as a parameter.
In the callback, you'll have get the handle to the second figure.
There's various ways to do that.
You can specify the handle to the second figure at the time of callback-definition:
figure2 = ...;
addlistener(hSlider, ..., #(a,b) changeStuffOn(figure2));
Or during the callback:
function callbackFunction(hObject, evt)
% get the handle to the second figure, e.g. by a tag, or its name
fig2 = findobj(0, 'type', 'figure', 'tag', 'figure2'); %
% do whatever you want with fig2
end
The latter might be somewhat worse in performance, but e.g. has the benefit of working reliably even if figure2 was deleted and recreated and some point.
To avoid the change of focus you'll have to get rid of this line your callback:
figure(F.f)
This explicitly moves the focus to the second figure.
You'll have to use e.g. the imshow(axes_handle, ...) syntax, in order to show the image not in the "current axes".