I am writing a wrapper for plot that automates some tasks that I find myself doing frequently.
An example code snippet might look like
function myplot(x,y,varargin)
plot(x,y,varargin{:})
xlabel('x axis')
ylabel('y axis')
end
I'm using Matlab's varargin to pass additional arguments to plot. However, I find that I might want to pass my own optional arguments in varargin. For example, I might want to write something like
>> myplot(1:10, 1:10, 'r', 'LineWidth', 2, 'legend', {'Series 1'})
to have the function automatically include a legend in the plot - that is, I want to be able to mix my own keyword arguments with the ones that you can supply to plot. Short of writing a full parser for my varargs, is there a way to do this simply and reusably in Matlab?
I've tried to use the inputParser object, but that would require me to manually add every possible additional argument to plot (and a default for it) which doesn't seem ideal.
inputParser may still be the best choice. You can construct the object for your additional arguments, and lump all the parameterName/parameterValue pairs that you want to pass to plot into Unmatched.
function myplot(x,y,varargin)
parserObj = inputParser;
parserObj.KeepUnmatched = true;
parserObj.AddParamValue('legend',false);
%# and some more of your special arguments
parserObj.parse(varargin);
%# your inputs are in Results
myArguments = parserObj.Results;
%# plot's arguments are unmatched
tmp = [fieldnames(parserObj.Unmatched),struct2cell(parserObj.Unmatched)];
plotArgs = reshape(tmp',[],1)';
plot(x,y,plotArgs{:})
xlabel('x axis')
ylabel('y axis')
if myArguments.legend
%# add your legend
end
end
To support the Linespec argument without interfering with input parser parameters that have a name of 4 characters or less (e.g., 'grid' or 'bins'), you should have a slightly more sophisticated way to check the validity of the Linespec argument than '#(x) ischar(x) && numel(x) <=4'. This check would return true also for 'grid' and 'bins', although they are no valid Linespec arguments.
The following function will return true only if a valid Linespec argument is entered:
function isls = islinespec(x)
isls = false;
if ~ischar(x)
return;
end
lineStyleSpecifiers = {'--','-.','-',':'};
markerSpecifiers = {'square','diamond','pentagram','hexagram','+','o','*','.','x','s','d','^','v','>','<','p','h'};
colorSpecifiers = {'r','g','b','c','m','y','k','w'};
for oo=1:length(lineStyleSpecifiers)
k = strfind(x,lineStyleSpecifiers{oo});
if ~isempty(k)
x(k:(k+length(lineStyleSpecifiers{oo})-1)) = [];
break;
end
end
for oo=1:length(markerSpecifiers)
k = strfind(x,markerSpecifiers{oo});
if ~isempty(k)
x(k:(k+length(markerSpecifiers{oo})-1)) = [];
break;
end
end
for oo=1:length(colorSpecifiers)
k = strfind(x,colorSpecifiers{oo});
if ~isempty(k)
x(k:(k+length(colorSpecifiers{oo})-1)) = [];
break;
end
end
if isempty(x)
isls = true;
end
(Admittedly, the function could have been written more elegantly with regular expressions, but it does the trick.)
Example usage:
parserObj = inputParser;
parserObj.KeepUnmatched = true;
parserObj.AddParamValue('legend',false);
parserObj.AddParamValue('grid',true);
parserObj.addOptional('LineSpec','-', #(x) islinespec(x));
To add support for LineSpec using the parser in a way that is unambiguous w/ param/val pairs, do:
function myplot(x,y,varargin)
parserObj = inputParser;
parserObj.KeepUnmatched = true;
parserObj.AddParamValue('legend',false);
parserObj.addOptional('LineSpec','-',#(x) ischar(x) && (numel(x) <= 4));
%# and some more of your special arguments
parserObj.parse(varargin{:});
%# your inputs are in Results
myArguments = parserObj.Results;
%# plot's arguments are unmatched
plotArgs = struct2pv(parserObj.Unmatched);
plot(x,y,myArguments.LineSpec,plotArgs{:})
xlabel('x axis')
ylabel('y axis')
if myArguments.legend
%# add your legend
end
end
function [pv_list, pv_array] = struct2pv (s)
p = fieldnames(s);
v = struct2cell(s);
pv_array = [p, v];
pv_list = reshape(pv_array', [],1)';
end
Related
I Need help regarding MovStr Function Update in MATLAB Version 2015. I was using MATLAB Function 2013 before. The function given below is not working for me. How should I make it workable ? Thanks
function MoveStr(ws)
% ws: with of a blank character
p1 = get(gca,'CurrentPoint');
set(gcf,'Pointer','fleur')
set(gcf,'WindowButtonUpFcn', sprintf('MoveStrUp(%1.20g,%1.8g,%1.8g,%1.8g)',gcbo,p1(1,1),p1(1,2),ws))
set(gcf,'WindowButtonMotionFcn',sprintf('MoveStrMo(%1.20g,%1.8g,%1.8g)',gcbo,p1(1,1),p1(1,2)))
% alle Textobjekte mitbewegen
ch = [gcbo get(gcbo,'UserData')];
for i = 1:length(ch)
if strcmpi(get(ch(i),'Type'),'text')
set(ch(i),'Selected','on')
end
end
function MoveStrMo(obj,x,y)
%set(gcf,'WindowButtonMotionFcn','')
d_pos = get(gca,'CurrentPoint');
set(gcf,'WindowButtonMotionFcn',sprintf('MoveStrMo(%20.15f,%f,%f)',obj,d_pos(1,1),d_pos(1,2)))
%set(gcf, 'WindowButtonMotionFcn', #(s,e)MoveStrMo(obj,d_pos(1,1),d_pos(1,2)));
d_pos = [d_pos(1,1)-x,d_pos(1,2)-y 0];
ch = [obj get(obj,'UserData')];
for i = 1:length(ch)
if isgraphics(ch( i ),'text')
pos = get(ch(i),'Position');
set(ch(i),'Position',[pos(1) pos(2) 2] + d_pos)
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function MovePointer
ans = get(gcbo,'UserData');
if strcmpi(get(ans(1),'Type'),'string')
% if isgraphics (ans1(1), 'string')
ans = ans(2);
end
set(gcf,'Pointer','fleur')
% set(gcf,'WindowButtonUpFcn',sprintf('MovePointerUp(%20.15f,%20.15f)',gcbo,ans(1)))
% set(gcf,'WindowButtonMotionFcn',sprintf('MovePointerMo(%20.15f,%20.15f)',gcbo,ans(1)))
set(gcf,'WindowButtonUpFcn',sprintf('MovePointerUp(%20.15f,%20.15f)',double(gcbo),ans(1)))
set(gcf,'WindowButtonMotionFcn',sprintf('MovePointerMo(%20.15f,%20.15f)',double(gcbo),ans(1)))
% set(gcf, 'WindowButtonUpFcn', #(s,e)MoveStrUp(gcbo, ans(1)));
% set(gcf, 'WindowButtonMotionFcn', #(s,e)MoveStrMo(gcbo, ans(1)));
set(gcbo,'Selected','on')
In older versions of MATLAB, graphics handles were simply floating point numbers or integers which could be used to refer to the graphics object. Beginning with R2014b, all graphics objects are objects by default which breaks your code since sprintf('%g') isn't going to be able to convert the object to a number itself.
You could still get the numeric handle of a graphics object by using the double method of the graphics object:
set(gcf,'WindowButtonMotionFcn',sprintf('MoveStrMo(%1.20g,%1.8g,%1.8g)',double(gcbo),p1(1,1),p1(1,2)))
BUT Please do not to this. You should be using function handles to create your callback instead of constructing an elaborate string that will be evaluated by MATLAB. This way you can pass the graphics object directly and you don't have to worry about any loss of precision when converting floating point numbers to strings. Also anyone trying to read your code later will really appreciate it as it is much cleaner.
set(gcf, 'WindowButtonUpFcn', #(s,e)moveStrUp(gcbo, p1(1,1), p1(1,2), ws));
set(gcf, 'WindowButtonMOtionFcn', #(s,e)MoveStrMo(gcbo, p1(1,1), p1(1,2)));
Update
Since it appears that you assign callbacks from within your callbacks, you will need to modify those as well to use the anonymous function format:
function MoveStrMo(obj,x,y)
d_pos = get(gca,'CurrentPoint');
set(gcf, 'WindowButtonMotionFcn', #(s,e)MoveStrMo(obj, d_pos(1,1), d_pos(1,2));
d_pos = [d_pos(1,1)-x,d_pos(1,2)-y 0];
ch = [obj get(obj,'UserData')];
for i = 1:length(ch)
if isgraphics(ch( i ),'text')
pos = get(ch(i),'Position');
set(ch(i),'Position',[pos(1) pos(2) 2] + d_pos)
end
end
end
And MovePointer
function MovePointer
ans = get(gcbo,'UserData');
if strcmpi(get(ans(1),'Type'), 'text')
ans = ans(2);
end
set(gcf,'Pointer','fleur')
set(gcf, 'WindowButtonUpFcn', #(s,e)MoveStrUp(gcbo, ans(1)));
set(gcf, 'WindowButtonMotionFcn', #(s,e)MoveStrMo(gcbo, ans(1)));
set(gcbo,'Selected','on')
end
I would like to have a class which wraps up a containers.Map. In addition, I want to be able use () indexing to access the sub-map from the parent class, for example:
>> map = containers.Map('Foo', 'Bar');
>> ex = Example(map);
>> ex('Foo')
ans =
Bar
The code for this is below, and it works well. The only problem I am having is with other methods defined on the class. According to the docs, I understand I need to override numArgumentsFromSubscript (somehow?) to help nargout. My crude attempt at simply using numel(obj) as I've seen mentioned online works in most cases, but not when the function you are calling has no output arguments (in which case numel(obj) == 1 ~= 0).
Using the example code below,
>> ex.outGoodbye
ans =
Goodbye
Great! However,
>> ex.sayHello
Error using Example/sayHello
Too many output arguments.
Error in Example/subsref (line 17)
[varargout{1:nargout}] = builtin('subsref', obj, struct);
How can you fix this?
classdef Example
% =====================================================================
properties
map
end
% =====================================================================
methods
% -----------------------------------------------------------------
function obj = Example(map)
obj.map = map;
end
% -----------------------------------------------------------------
function varargout = subsref(obj, struct)
if strcmp(struct(1).type, '()')
[varargout{1:nargout}] = builtin('subsref', obj.map, struct);
else
[varargout{1:nargout}] = builtin('subsref', obj, struct);
end
end
% -----------------------------------------------------------------
function n = numArgumentsFromSubscript(obj, struct, indexingContext)
n = numel(obj); % Necessary to overload subsref - for some reason
end
% -----------------------------------------------------------------
function obj = subsasgn(obj, struct, varargin)
if strcmp(struct(1).type, '()')
obj = builtin('subsasgn', obj.map, struct, varargin{:});
obj = Example(obj);
else
obj = builtin('subsasgn', obj, struct, varargin{:});
end
end
% -----------------------------------------------------------------
function sayHello(obj)
disp('Hello'); % nargout == 0. Does NOT work
end
% -----------------------------------------------------------------
function out = outGoodbye(obj)
out = 'Goodbye'; % nargout > 0. Works
end
% -----------------------------------------------------------------
end
% =====================================================================
end
So digging into this a little further, you have a few options for how to get around this behavior.
method(obj) Calling Convention
You could simply change the way that you're calling the class method. Instead of using the dot notation, you could simply use the standard function notation.
sayHello(ex)
%// vs.
ex.sayHello()
This will avoid calling subsref when calling a method. Also, this is actually the fastest way to call a method of a class in the current iteration of MATLAB's OOP. Additionally, this would require no changes to your current code.
Use nargout to determine number of method outputs
Another option is to add a special case in subsref or numArgumentsFromSubscript that looks for methods called using the dot notation. Then you can explicitly determine the number of output arguments of the method using the following call to nargout.
nArgs = nargout('classname>classname.methodname');
Then you would use that rather than numel(obj). This could be implemented in either numArgumentsFromSubscript or subsref.
numArgumentsFromSubscript
function n = numArgumentsFromSubscript(obj, struct, indexingContext)
%// Check if we are calling obj.method
if strcmp(struct(1).type, '.') && ...
ischar(struct(1).subs) && ismember(struct(1).subs, methods(obj))
%// Determine the package (if any)
cls = class(obj);
parts = regexp(cls, '\.', 'split');
%// Import the package (if any) just into the namespace of this method
if numel(parts) > 1
import(cls);
end
%// Determine number of outputs for this method
n = nargout(sprintf('%s>%s.%s', parts{[end end]}, struct(1).subs));
else
%// Default to numel(obj)
n = numel(obj);
end
end
subsref
function varargout = subsref(obj, struct)
if strcmp(struct(1).type, '()')
[varargout{1:nargout}] = builtin('subsref', obj.map, struct);
%// Check if we are calling obj.method
elseif strcmp(struct(1).type, '.') && ...
ischar(struct(1).subs) && ismember(struct(1).subs, methods(obj))
%// Determine the package (if any)
cls = class(obj);
parts = regexp(cls, '\.', 'split');
%// Import the package (if any) just into the namespace of this method
if numel(parts) > 1
import(cls);
end
%// Determine number of outputs for this method
nout = nargout(sprintf('%s>%s.%s', parts{[end end]}, struct(1).subs));
%// Call builtin subsref with this number of outputs
[varargout{1:nout}] = builtin('subsref', obj, struct);
else
[varargout{1:nargout}] = builtin('subsref', obj, struct);
end
end
Summary
subsref is a pain and a lot of times you end up with a lot of logic to determine what is being called and you'll be right only about 80% of the time. I think that the first approach is the most straightforward, would likely be the most performant (you skip all of the checks in subsref) and deals better with arrays of objects.
If you really wanted to keep the obj.method notation, I would probably recommend changing numArgumentsFromSubscript (rather than subsref) to keep the number of output argument stuff in one place.
Update
Based upon your feedback that the nargout trick doesn't work in cases where the class is contained within a package (foo.bar.Example). I have added a workaround to the above examples. The trick is to call import('foo.bar.Example') prior to calling nargout('Example>Example.sayHello').
Is there a way in matlab to force a function to output a certain number of arguments? For example this is what matlab does:
function [a,b,c] = practice
if nargout >=1
a =1;
end
if nargout >=2
b=2;
end
if nargout ==3
c = 3;
end
end
d(1:3) = practice()
% d = [1 1 1]
I would want:
d(1:3) = practice()
% d = [1 2 3]
Can I get this behavior without needing to say
[d(1),d(2),d(3)] = practice()
There is an option to let your function output everything when only a single output argument is used:
function varargout=nargoutdemo(x)
varargout{1}=1;
varargout{2}=2;
varargout{3}=3;
if nargout==1
varargout={[varargout{:}]};
end
end
For non uniform return data, it might be necessary to switch to a cell
If you wish not to change the function, you could use this a little bit more generic code:
out=cell(1,3)
[out{:}]=practice
Please not, that this returns a cell, not an array. That's because array to comma separated list conversion is not directly possible.
The user gives a function, and I make the graphic in an axis with syms(x) and ezplot(funcion). And in another case, the user gives the function and the interval for x. In the second example, I use plot() instead of ezplot(). Here are my codes=
syms x;
funcion=eval(get(handles.txtFuncion, 'String'));
ezplot(funcion);
The second code is this:
a=eval(get(handles.txtA, 'String'));
b=eval(get(handles.txtB, 'String'));
x=a:b;
funcion=eval(get(handles.txtFuncion, 'String'));
plot(x,funcion);
I don't have the symbolic math toolbox so I can only address the second code.
Given the sample data provided:
a = -10;
b = 10;
x = a:b;
h.areaplot = area(x, eval('x.^2'));
Produces:
Edit: Alternatively you can modify your function input syntax to work without needing eval:
a = str2double(get(handles.txtA, 'String'));
b = str2double(get(handles.txtB, 'String'));
x = a:b;
funcion = str2func(get(handles.txtFuncion, 'String'));
h.areaplot = area(x, funcion(x));
Where your text inputs now take the form '#(x) x.^2'
Is there a way to pass in N datasets and plot them in a line graph using for loop?
I did it by passing fixed number of parameter ( eg M1, M2, M3, M4), and repeat plotting manually like below. But I wonder if there are way to code the function dynamically? Let say I can pass in 4 datasets, or 40 datasets, and plot them in one graph via looping.
function plot_four_cdf(M1,M2,M3,M4)
[ycdf1,xcdf1] = cdfcalc(M1);
ycdf1 = ycdf1(2:length(ycdf1));
plot(xcdf1, ycdf1, '-+k', 'LineWidth', 1);
hold on;
[ycdf2,xcdf2] = cdfcalc(M2);
ycdf2 = ycdf2(2:length(ycdf2));
plot(xcdf2, ycdf2, '-ok', 'LineWidth', 1);
hold off;
hold on;
[ycdf3,xcdf3] = cdfcalc(M3);
ycdf3 = ycdf3(2:length(ycdf3));
plot(xcdf3, ycdf3, '-*k', 'LineWidth', 1);
hold off;
hold on;
[ycdf4,xcdf4] = cdfcalc(M4);
ycdf4 = ycdf4(2:length(ycdf4));
plot(xcdf4, ycdf4, '-sk', 'LineWidth', 1);
legend('M100','M80','M50','M20',...
'Location','SE')
xlabel('Relative Error');
ylabel('CDF');
end
You can group all your data in a cell-array and pass that to the function:
function plot_cdfs(M)
figure, hold on
linestyles = {...
'-+k', '-ok', '-*k', '-sk', ...
'-+r', '-or', '-*r', '-sr');
legendentries = cell(size(M));
for ii = 1:numel(M)
[ycdf, xcdf] = cdfcalc(M{ii});
plot(xcdf, ycdf(2:end), linestyles{ii}, 'LineWidth', 1);
legendentries{ii} = ['M' num2str(ii)];
end
legend(legendentries{:}, 'Location','SE')
xlabel('Relative Error');
ylabel('CDF');
end
Note that M is constructed something like
M = {M1, M2, M3, ...}
possibly also in a loop of its own. Note also that the legendentries are now kind of hard to define. You can pass them as a separate argument to the function (better option), or stuff them in the same cell array M next to the data they describe (not very portable).
Note also that you'd have to do some error checking (now, only 8 different plots can be made. An error will result if you do more).