Delete MATLAB subclass of graphics primitive - matlab

I'm trying to create a class similar to the Line object's class in MATLAB 2017b but having problem with the destructor. I want to achieve a similar behavior with other graphic objects that when deleted the handle is no longer referencing to that object, like in the following example:
>> l1 = line([0 1], [0 1]);
If the figure containing line l1 is now closed, the variable shows that l1 is referencing to a deleted object:
>> l1
l1 =
handle to deleted Line
I created the following class:
classdef circle < handle ...
& matlab.mixin.CustomDisplay & matlab.mixin.SetGet
properties
Center = [];
Color = [0 0.4470 0.7410];
Radius = [];
end
properties (SetAccess = 'protected')
LineHandle = [];
end
methods
function obj = circle(radius, center, varargin)
if nargin > 0
% assign property values
obj.Radius = radius;
obj.Center = center;
% generate plotting variables
phi = linspace(0, 2*pi, 90);
x = radius*cos(phi) + center(1);
y = radius*sin(phi) + center(2);
% draw circle
obj.LineHandle = line(x, y);
% create listeners in line object
obj.createListener;
% set variable properties
for k = 1:2:length(varargin)
% set superclass properties
if (isprop(obj.LineHandle, varargin{k}))
set(obj.LineHandle, varargin{k},varargin{k+1});
if (isprop(obj, varargin{k}))
set(obj, varargin{k}, varargin{k+1});
end
end
end
end
end
% listener to invoke delete if line is closed
function createListener(obj)
set(obj.LineHandle,'DeleteFcn',...
#obj.delete);
end
function delete(obj,varargin)
disp('deleted')
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% command to delete class ???
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
end
end
end
If the figure containing a circle object is closed, the line is deleted but the circle object still existst. Is there a way to also delete the reference to the circle object?

I found out the answer myself. The function just needs to have a different name than delete:
function delete_obj(obj, varargin)
% delete handle
delete(obj)
end

Related

How to set the errorbars in a different color from the plot in Matlab?

When making a plot in Matlab using errorbar(...) the color of the error bars is the same as the plot. How do I set them to be in a different color?
I tried looking for a way to do it in here:
https://www.mathworks.com/help/matlab/ref/matlab.graphics.chart.primitive.errorbar-properties.html
https://www.mathworks.com/help/matlab/ref/errorbar.html
But I couldn't find it.
Edit: This question:
Color of errobar different from the graph matlab
doesn't have the answer to what I'm asking. It was asked almost a year and half ago and no solution was given. The one comment there doesn't give a proper solution. It says to draw the plot twice - once with the errorbars (when the plot and the errorbars are at the same color) and a second time just the plot without the errorbars (which will be drawn on top of the first one using hold on). There should be a way to draw the figure once with the errorbars at a different color than the color of the plot - that is what I'm looking for.
Here is a quick and dirty implementation of a function that allows to style the errorbars and the data separatly. However, internally it does the same as the answers to the previously posted question: it uses two plots.
function varargout = mc_errorbar(ax, x,y,e, varargin)
% MC_ERRORBAR errorbar which allows to style errorbar and data separately
%
% Usage:
% mc_errorbar(X,Y,E)
% plots errorbars and a separate line in the current axis.
%
% mc_errorbar(X,Y,E, property, value)
% plots errorbars and a separate line in the current axis and styles
% the plots according to the properties given. All properties that are
% accepted by the errorbar function are allowed, but should be prefixed
% with 'EB'. Note that the LineStyle property will be overriden.
% Properties not prefixed with 'EB' will be passed to the plot
% function, hence all properties allowed by plot are allowed here, too.
%
% mc_errorbar(ax, ___)
% plots in the axes ax instead of the current axes.
%
% [lh] = mc_errorbar(___)
% returns the line handle
%
% [lh, ebh] = mc_errorbar(___)
% returns the line handle and the handle to the errorbar
%
% See also errorbar plot
if ~isa(ax, 'matlab.graphics.axis.Axes')
if nargin > 3
varargin = [{e}, varargin(:)'];
end
e = y;
y = x;
x = ax;
ax = gca();
end
oldnextplot = ax.NextPlot;
ebargs = {};
lineargs = {};
for k = 1:2:numel(varargin)
if strcmp(varargin{k}(1:2),'EB') == 1
ebargs{end+1} = varargin{k}(3:end);
ebargs{end+1} = varargin{k+1};
else
lineargs{end+1} = varargin{k};
lineargs{end+1} = varargin{k+1};
end
end
ebargs{end+1} = 'LineStyle';
ebargs{end+1} = 'none';
eb = errorbar(ax, x, y, e, ebargs{:});
ax.NextPlot = 'add';
line = plot(ax, x,y, lineargs{:});
ax.NextPlot = oldnextplot;
if nargout > 0
varargout{1} = line;
end
if nargout > 1
varargout{2} = eb;
end
end
Example:
mc_errorbar(1:10, (1:10)*2, (1:10)*.5, 'Color','g', 'EBColor', 'k', 'EBLineWidth', 3, 'LineStyle','-', 'LineWidth',8)

Visualizing matrix values in real time

Suppose I have a 5x5 matrix.
The elements of the matrix change (are refreshed) every second.
I would like to be able to display the matrix (not as a colormap but with the actual values in a grid) in realtime and watch the values in it change as time progresses.
How would I go about doing so in MATLAB?
A combination of clc and disp is the easiest approach (as answered by Tim), here's a "prettier" approach you might fancy, depending on your needs. This is not going to be as quick, but you might find some benefits, such as not having to clear the command window or being able to colour-code and save the figs.
Using dispMatrixInFig (code at the bottom of this answer) you can view the matrix in a figure window (or unique figure windows) at each stage.
Example test code:
fig = figure;
% Loop 10 times, pausing for 1sec each loop, display matrix
for i=1:10
A = rand(5, 5);
dispMatrixInFig(A,fig)
pause(1)
end
Output for one iteration:
Commented function code:
function dispMatrixInFig(A, fig, strstyle, figname)
%% Given a figure "fig" and a matrix "A", the matrix is displayed in the
% figure. If no figure is supplied then a new one is created.
%
% strstyle is optional to specify the string display of each value, for
% details see SPRINTF. Default is 4d.p. Can set to default by passing '' or
% no argument.
%
% figname will appear in the title bar of the figure.
if nargin < 2
fig = figure;
else
clf(fig);
end
if nargin < 3 || strcmp(strstyle, '')
strstyle = '%3.4f';
end
if nargin < 4
figname = '';
end
% Get size of matrix
[m,n] = size(A);
% Turn axes off, set origin to top left
axis off;
axis ij;
set(fig,'DefaultTextFontName','courier', ...
'DefaultTextHorizontalAlignment','left', ...
'DefaultTextVerticalAlignment','bottom', ...
'DefaultTextClipping','on');
fig.Name = figname;
axis([1, m-1, 1, n]);
drawnow
tmp = text(.5,.5,'t');
% height and width of character
ext = get(tmp, 'Extent');
dy = ext(4);
wch = ext(3);
dwc = 2*wch;
dx = 8*wch + dwc;
% set matrix values to fig positions
x = 1;
for i = 1:n
y = 0.5 + dy/2;
for j = 1:m
y = y + 1;
text(x,y,sprintf(strstyle,A(j,i)));
end
x = x + dx;
end
% Tidy up display
axis([1-dwc/2 1+n*dx-dwc/2 1 m+1]);
set(gca, 'YTick', [], 'XTickLabel',[],'Visible','on');
set(gca,'XTick',(1-dwc/2):dx:x);
set(gca,'XGrid','on','GridLineStyle','-');
end
I would have thought you could achieve this with disp:
for i=1:10
A = rand(5, 5);
disp(A);
end
If you mean that you don't want repeated outputs on top of each other in the console, you could include a clc to clear the console before each disp call:
for i=1:10
A = rand(5, 5);
clc;
disp(A);
end
If you want to display your matrix on a figure it is quite easy. Just make a dump matrix and display it. Then use text function to display your matrix on the figure. For example
randMatrix=rand(5);
figure,imagesc(ones(20));axis image;
hold on;text(2,10,num2str(randMatrix))
If you want to do it in a for loop and see the numbers change, try this:
for i=1:100;
randMatrix=rand(5);
figure(1),clf
imagesc(ones(20));axis image;
hold on;text(2,10,num2str(randMatrix));
drawnow;
end

Animate a MATLAB figure by pressing left and right arrow keys

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

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,

MATLAB: How to store clicked coordinates using ButtonDownFcn

Goal: To perform several clicks in one figure, containing an image displayed with imshow and save the coordinates of the "clicked" point(s), to be used in further operations.
Notes: I know about the functions getpts/ginput but I would like to perform this without using them. Is this possible using ButtonDownFcn? (see the following code)
function testClicks
img = ones(300); % image to display
h = imshow(img,'Parent',gca);
set(h,'ButtonDownFcn',{#ax_bdfcn});
function ax_bdfcn(varargin)
a = get(gca,'CurrentPoint');
x = a(1,1);
y = a(1,2);
At this stage the variables x and y only "live" inside ax_bdfcn.
How can I make them available in the testClicks function? Is this possible using ButtonDownFcn? Is this a good approach?
Thanks a lot.
EDIT1:
Thanks for the answer Shai. But I still cannot accomplish what I intended.
function [xArray, yArray] = testClicks()
img = ones(300); % image to display
h = imshow(img,'Parent',gca);
x = [];
y = [];
xArray = [];
yArray = [];
stop = 0;
while stop == 0;
set(h,'ButtonDownFcn',{#ax_bdfcn});
xArray = [xArray x];
yArray = [yArray y];
if length(xArray)>15
stop = 1;
end
end
function ax_bdfcn(varargin)
a = get(gca, 'CurrentPoint');
assignin('caller', 'x', a(1,1) );
assignin('caller', 'y', a(1,2) );
end
end % must have end for nested functions
This code (buggy!) is the closest I can get to what I want (after all the clicking, having an array with the x and y coordinates of the clicked points). I am clealy not understanding the mechanics for the implementation of this task. Any help?
There are several ways
Using nested functions
function testClicks
img = ones(300); % image to display
h = imshow(img,'Parent',gca);
set(h,'ButtonDownFcn',{#ax_bdfcn});
x = []; % define "scope" of x and y
y = [];
% call back as nested function
function ax_bdfcn(varargin)
a = get(gca,'CurrentPoint');
x = a(1,1); % set x and y at caller scope due to "nested"ness of function
y = a(1,2);
end % close nested function
end % must have end for nested functions
Using assignin
function ax_bdfcn(varargin)
a = get(gca, 'CurrentPoint');
assignin('caller', 'x', a(1) );
assignin('caller', 'y', a(2) );
Using 'UserData' property of figure handle
function ax_bdfcn(varargin)
a = get(gca, 'CurrentPoint');
set( gcf, 'UserData', a(1:2) );
'UserData' can be accessed (as long as the figure is alive) using cp = get( gcf, 'UserData');.
EDIT:
An example of a way to "communicate" the clicked locations to 'base' workspace
function ax_bdfcn(varargin)
a = get(gca,'CurrentPoint');
% the hard part - assign points to base
if evalin('base', 'exist(''xArray'',''var'')')
xArray = evalin('base','xArray');
else
xArray = [];
end
xArray = [xArray a(1)]; % add the point
assignin('base','xArray',xArray); % save to base
% do the same for yArray
After calling testClicks there are NO xArray or yArray variables in the workspace (at least there shouldn't). After the first click these two variables will "miraculously" be created. After every other click these two arrays will increase their size until you close the figure.