function eziplot(fun, varargin)
% EZIPLOT Easy to use interactive function plotter
%
% EZIPLOT({ARG1, ...}) creates an interactive figure by calling
% EZPLOT(ARG1, ...). The arguments inside the braces follow the same
% syntax as EZPLOT. The figure will contain slider bars for any
% parameters detected. The function handles must be anonymous functions
% (not a function handle to a function file). This tool will
% automatically determine the parameters used in the anonymous
% functions.
%
% EZIPLOT({ARG1, ...}, 'PropertyName1', value1, ...) accepts additional
% properties from the list below:
%
% 'plotfcn' : String indicating the EZ function to use:
% 'ezcontour', 'ezcontourf', 'ezmesh', 'ezmeshc',
% 'ezplot', 'ezplot3', 'ezpolar', 'ezsurf', 'ezsurfc',
% 'fplot'.
% Default is 'ezplot'.
%
% 'params' : Parameters to manipulate. If this property is not
% provided, then the tool will manipulate all parameters
% detected. It must be an n-by-2 cell array, where n is
% the number of parameters. Each row must contain the
% name of the parameter and a 2-element vector indicating
% the lower and upper limit for the slider. e.g.
% {'a', [1 10]; 'b', [0 5]; 'c', []}
% If the lower and upper limit is an empty matrix, then
% the tool will select [x-10*abs(x), x+10*abs(x)].
%
% 'window' : 'new', 'reuse', or 'one'. Default is 'one'.
% 'new' creates a new figure window.
% 'reuse' reuses the current GUI.
% 'one' deletes all previous GUIs and creates a new one.
%
% 'xlim' : [LB, UB] the lower and upper limit for the axes. If not
% 'ylim' specified, then the EZ-functions will automatically
% 'zlim' rescale the axes.
%
% Note:
% Once the tool has started, you can hold the limit
% using the "Axes" menu.
%
% 'xgrid' : 'on' or 'off'. Turns on or off the grids. The default
% 'ygrid' is 'on'.
% 'zgrid'
% Note:
% Once the tool has started, you can turn on and off
% the grid from the "Axes" menu.
%
% Example 1:
% a = 1;
% eziplot({@(x) sin(a*x)});
%
% Example 2:
% a = 4;
% b = 2;
% f1 = @(x) b*sin(a*x) + cos((10-a)*x);
% eziplot({f1}, 'params', {'a', [1 10];'b', [0 2]}, 'ylim', [-5 5]);
%
% Example 3:
% a = 1;
% b = 2;
% f2 = @(t) a*sin(b*t);
% f3 = @(t) b*cos(a*t);
% eziplot({f2, f3})
%
% Example 4:
% a = 1;
% b = 1;
% f4 = @(x,y) y./(1 + a*x.^2 + b*y.^2);
% eziplot({f4}, 'plotfcn', 'ezmeshc', 'zlim', [-1 1]);
%
% Example 5:
% a = 3;
% b = 10;
% c = 1/3;
% f5 = @(x,y) a*(1-x).^2.*exp(-(x.^2) - (y+1).^2) ...
% - b*(x/5 - x.^3 - y.^5).*exp(-x.^2-y.^2) ...
% - c*exp(-(x+1).^2 - y.^2);
% eziplot({f5,[-3,3]}, 'plotfcn', 'ezsurfc', 'zlim', [-10 10]);
%
% See also ezcontour, ezcontourf, ezmesh, ezmeshc, ezplot,
% ezplot3, ezpolar, ezsurf, ezsurfc, fplot
% Versions:
% v1.0 - original version (May 3, 2008)
% v1.01 - added ability to create new windows (May 5, 2008)
%
% Copyright 2008-2010 The MathWorks, Inc.
if ~iscell(fun)
error('EZIPLOT:invalidFirstArg', ...
'The first argument must be a cell array of inputs accepted by EZ-functions.');
end
% Valid EZ-functions
validPlotTypes = {'ezcontour', 'ezcontourf', 'ezmesh', 'ezmeshc', ...
'ezplot', 'ezplot3', 'ezpolar', 'ezsurf', 'ezsurfc', 'fplot'};
% Width of the parameter slider panel
panelWidth = 150; %pixels
% default values
funType = @ezplot;
XL = 0; % auto
YL = 0; % auto
ZL = 0; % auto
xGrid = 'on';
yGrid = 'on';
zGrid = 'on';
GUIwindow = 'one';
% Extract out the function handles from the EZ-fun arguments
fHandleID = [];
idx = 1;
for iFun = 1:length(fun)
if isa(fun{iFun}, 'function_handle')
% Get info regarding the function handles
fI = functions(fun{iFun});
if strcmp(fI.type, 'anonymous') % only accept anonymous functions
fInfo(idx) = fI; %#ok
fHandleID(idx) = iFun; %#ok
idx = idx + 1;
else
error('EZIPLOT:onlyAnonymousFcn', ...
'Only anonymous functions are allowed');
end
end
end
if isempty(fHandleID)
error('EZIPLOT:noFunctionHandles', ...
'Could not find any function handles.');
end
% Extract out all the parameter names in all of the function handles
% This is based on the function handles "workspace".
% If the parameter was not defined at the time of function creation, then
% the parameter will not show up in the "workspace".
fnamesAll = arrayfun(@(x) fieldnames(x.workspace{:}), fInfo, ...
'UniformOutput', false);fnamesAll = vertcat(fnamesAll{:});
valuesAll = arrayfun(@(x) struct2cell(x.workspace{:}), fInfo, ...
'UniformOutput', false);valuesAll = vertcat(valuesAll{:});
numericParams = cellfun(@isnumeric, valuesAll);
% fnamesAll(~numericParams) = '';
% valuesAll(~numericParams) = '';
% Find the unique parameter names
[uniqueID, uniqueID] = unique(fnamesAll);
% Change it to a structure for easy organization
params = cell2struct(valuesAll(uniqueID), fnamesAll(uniqueID));
% Extract all the parameter names
paramNames = fieldnames(params);
paramNames(~numericParams) = '';
% Default list of parameters to process (all params)
processParams = [paramNames, cell(length(paramNames), 1)];
%--------------------------------------------------------------------------
% Process additional arguments
if nargin > 1
if mod(length(varargin), 2) == 1 % must be P-V pairs
error('EZIPLOT:invalidPVPairs', ...
'Additional arguments must come in P-V pairs.');
end
for iArg = 1:length(varargin)/2
P = varargin{iArg*2-1};
V = varargin{iArg*2};
if ~ischar(P)
error('EZIPLOT:invalidPropertyType', ...
'Property has to be a string.');
end
switch lower(P)
case 'plotfcn'
if ischar(V) && ismember(V, validPlotTypes)
% Convert string to a function handle
funType = str2func(V);
else
errString = ['The value for ''plotfcn'' must be a string and one of the following:', ...
sprintf('\n'), sprintf('%s, ', validPlotTypes{1:end-1}), ...
sprintf('%s', validPlotTypes{end})];
error('EZIPLOT:invalidPlotFcn', '%s', errString);
end
case 'params'
if ~iscell(V) || size(V, 2) ~= 2
error('EZIPLOT:invalidParamSize', ...
'The value for ''params'' must be n-by-2 cell array.');
end
for iN = 1:size(V, 1)
if ~ischar(V{iN, 1}) || ~ismember(V{iN, 1}, paramNames)
error('EZIPLOT:invalidParameter', ...
'Cannot find parameter ''%s'' in the equation.', V{iN, 1});
end
if ~isnumeric(V{iN, 2}) || ~ismember(length(V{iN, 2}), [0 2])
error('EZIPLOT:invalidParamRange', ...
'The second argument for ''params'' property must be an empty or a 2-element vector.');
end
end
processParams = V;
case 'window'
if ~ischar(V) || ~ismember(V, {'new', 'reuse', 'one'})
error('EZIPLOT:invalidWindowType', ...
'The ''window'' property must be one of ''new'', ''reuse'', or ''one''.');
end
GUIwindow = V;
case 'xlim'
if ~isnumeric(V) || numel(V) ~=2 || V(2) <= V(1)
error('EZIPLOT:invalidXLim', ...
'%s must be a 2-element numeric vector', P);
end
XL = V;
case 'ylim'
if ~isnumeric(V) || numel(V) ~=2 || V(2) <= V(1)
error('EZIPLOT:invalidYLim', ...
'%s must be a 2-element numeric vector', P);
end
YL = V;
case 'zlim'
if ~isnumeric(V) || numel(V) ~=2 || V(2) <= V(1)
error('EZIPLOT:invalidZLim', ...
'%s must be a 2-element numeric vector', P);
end
ZL = V;
case 'xgrid'
if ~ischar(V) || ~ismember(V, {'on', 'off'})
error('EZIPLOT:invalidXGrid', ...
'%s must be either ''on'' or ''off''', P);
end
xGrid = V;
case 'ygrid'
if ~ischar(V) || ~ismember(V, {'on', 'off'})
error('EZIPLOT:invalidYGrid', ...
'%s must be either ''on'' or ''off''', P);
end
yGrid = V;
case 'zgrid'
if ~ischar(V) || ~ismember(V, {'on', 'off'})
error('EZIPLOT:invalidZGrid', ...
'%s must be either ''on'' or ''off''', P);
end
zGrid = V;
otherwise
error('EZIPLOT:invalidProperty', ...
'Unknown property: %s', P);
end
end
end
%--------------------------------------------------------------------------
% Create GUI
[hFig, hAx] = initGUI();
% Create initial plot. ("true" means initial visualization)
plotFcn(true);
% Make figure visible
set(hFig, 'Visible', 'on');
%--------------------------------------------------------------------------
%--------------------------------------------------------------------------
% Nested Functions
function [hFig, hAx] = initGUI()
% This function initializes the figure;
bgColor = [.8 .8 .8];
switch GUIwindow
case 'new'
% do nothing
case 'reuse'
allWindows = findall(0, 'Type', 'figure', 'Tag', 'InteractiveEZPlot');
if ~isempty(allWindows)
delete(allWindows(end));
end
case 'one'
% Delete existing GUI
delete(findall(0, 'Type', 'figure', 'Tag', 'InteractiveEZPlot'));
end
hFig = figure(...
'Name' , 'Interactive EZPlot', ...
'NumberTitle' , 'off', ...
'Tag' , 'InteractiveEZPlot', ...
'Color' , bgColor, ...
'Units' , 'pixels', ...
'ResizeFcn' , @figResizeFcn, ...
'MenuBar' , 'none', ...
'ToolBar' , 'figure', ...
'Visible' , 'off', ...
'HandleVisibility', 'off');
% Create menu for axes settings
cmenu = uimenu('Parent', hFig, 'Label', 'Axes');
hMenu(1) = uimenu(cmenu, ...
'Label' , 'Hold X-Limit', ...
'Callback' , @menuCallback);
hMenu(2) = uimenu(cmenu, ...
'Label' , 'Hold Y-Limit', ...
'Callback' , @menuCallback);
hMenu(3) = uimenu(cmenu, ...
'Label' , 'Hold Z-Limit', ...
'Callback' , @menuCallback);
hMenu(4) = uimenu(cmenu, ...
'Separator' , 'on', ...
'Label' , 'X-Grid', ...
'Checked' , xGrid, ...
'Callback' , @menuCallback);
hMenu(5) = uimenu(cmenu, ...
'Label' , 'Y-Grid', ...
'Checked' , yGrid, ...
'Callback' , @menuCallback);
hMenu(6) = uimenu(cmenu, ...
'Label' , 'Z-Grid', ...
'Checked' , zGrid, ...
'Callback' , @menuCallback);
if length(XL) > 1
set(hMenu(1), 'Checked', 'on');
end
if length(YL) > 1
set(hMenu(2), 'Checked', 'on');
end
if length(ZL) > 1
set(hMenu(3), 'Checked', 'on');
end
% Create plotting axes
hAx = axes(...
'Parent' , hFig, ...
'Units' , 'pixels');
% Create panel for the sliders
hPanel = uipanel(...
'Parent' , hFig, ...
'BackgroundColor' , bgColor, ...
'Units' , 'pixels', ...
'FontUnits' , 'points', ...
'FontSize' , 11, ...
'FontWeight' , 'bold', ...
'Title' , 'Parameters', ...
'TitlePosition' , 'centertop');
hh = nan(1, size(processParams, 1));
% Create slider control for each parameters selected
for iParam = 1:size(processParams, 1)
pName = processParams{iParam, 1};
hh(iParam) = uipanel(...
'Parent' , hPanel, ...
'BackgroundColor' , bgColor, ...
'Units' , 'pixels', ...
'FontUnits' , 'points', ...
'FontWeight' , 'bold', ...
'FontSize' , 10, ...
'Title' , pName, ...
'TitlePosition' , 'lefttop');
% Calculate min and max values for the slider (based on user-specified
% values, or default value)
if isempty(processParams{iParam, 2})
minVal = params.(pName) - 10*abs(params.(pName));
maxVal = params.(pName) + 10*abs(params.(pName));
else
minVal = processParams{iParam, 2}(1);
maxVal = processParams{iParam, 2}(2);
end
% Determine current slider value. If the current value in the function
% handle is outside the min/max range, then choose minVal
sliderVal = max([minVal, params.(pName)]);
% Interactive edit box
ht = uicontrol(...
'Parent' , hh(iParam), ...
'BackgroundColor' , 'white', ...
'Style' , 'edit', ...
'FontUnits' , 'points', ...
'FontSize' , 10, ...
'String' , sliderVal, ...
'Units' , 'normalized', ...
'Position' , [0.05, 0.5, 0.9, 0.5]);
% Interactive slider bar
ht2 = uicontrol(...
'Parent' , hh(iParam), ...
'Style' , 'slider', ...
'min' , minVal, ...
'max' , maxVal, ...
'value' , sliderVal, ...
'Units' , 'normalized', ...
'Position' , [0.05, 0.1, 0.9, 0.4], ...
'Callback' , {@sliderCallback, ht});
set(ht, 'Callback', {@editboxCallback, ht2});
end
% Apply smart resizing
figResizeFcn();
%--------------------------------------------------------------------------
function figResizeFcn(varargin)
% Resize function for the figure. This makes sure the slider panel
% has the "correct" size when the figure is resized.
fPos = get(hFig, 'Position');
panelPos = [fPos(3)-panelWidth, 5, panelWidth, fPos(4)-5];
set(hAx , 'OuterPosition', [0, 0, fPos(3)-panelWidth, fPos(4)]);
set(hPanel, 'Position' , panelPos);
for iH = 1:length(hh)
set(hh(iH), 'Position', [2, panelPos(4)-65*iH-15, panelPos(3)-6, 61]);
end
end
end
%--------------------------------------------------------------------------
function menuCallback(varargin)
% This callback gets called when an item from the "Axes" menu is
% selected.
% Check the status of the check mark
if strcmpi(get(varargin{1}, 'Checked'), 'on')
isChecked = false;
set(varargin{1}, 'Checked', 'off');
else
isChecked = true;
set(varargin{1}, 'Checked', 'on');
end
% Perform operation based on the menu item
switch get(varargin{1}, 'Label')
case 'Hold X-Limit'
if isChecked
XL = get(hAx, 'xlim');
else
XL = 0; % auto
end
case 'Hold Y-Limit'
if isChecked
YL = get(hAx, 'ylim');
else
YL = 0; % auto
end
case 'Hold Z-Limit'
if isChecked
ZL = get(hAx, 'zlim');
else
ZL = 0; % auto
end
case 'X-Grid'
if isChecked
xGrid = 'on';
else
xGrid = 'off';
end
set(hAx, 'XGrid', xGrid);
case 'Y-Grid'
if isChecked
yGrid = 'on';
else
yGrid = 'off';
end
set(hAx, 'YGrid', yGrid);
case 'Z-Grid'
if isChecked
zGrid = 'on';
else
zGrid = 'off';
end
set(hAx, 'ZGrid', zGrid);
otherwise
% This should never happen
end
end
%--------------------------------------------------------------------------
function sliderCallback(varargin)
% Slider callback function
% Get slider value
val = get(varargin{1}, 'Value');
% Set the edit box to the value
set(varargin{3}, 'String', val);
% Get the name of the parameter
p = get(get(varargin{1}, 'Parent'), 'Title');
% Change the value of the parameter
params.(p) = val;
% Re-evaluate function handle
[fun{fHandleID}] = evalFcn({fInfo.function}, params);
% Plot
plotFcn(false);
end
%--------------------------------------------------------------------------
function editboxCallback(varargin)
% Edit box callback function
% Get text value
val = str2double(get(varargin{1}, 'String'));
if ~isnan(val) && val >= get(varargin{3}, 'min') && val <= get(varargin{3}, 'max')
% Set the slider value to val
set(varargin{3}, 'Value', val);
else % invalid string
% Re-set text value to the slider value
set(varargin{1}, 'String', get(varargin{3}, 'Value'));
val = get(varargin{3}, 'Value');
end
% Get the name of the parameter
p = get(get(varargin{1}, 'Parent'), 'Title');
% Change the value of the parameter
params.(p) = val;
% Re-evaluate function handle
[fun{fHandleID}] = evalFcn({fInfo.function}, params);
% Plot
plotFcn(false);
end
%--------------------------------------------------------------------------
function plotFcn(fst)
% This function will plot using the EZ function specified.
%
% fst - TRUE or FALSE. TRUE if this is the initial plot.
% Save the current view. This is primarily for the 3D case. If the user
% rotates the view, then attempt to maintain the same view
v = get(hAx, 'View');
% "funType" is the function handle of the EZ function
funType(hAx, fun{:});
% If not the first plot, then restore view
if ~fst
set(hAx, 'View', v);
end
% Set limits
if length(XL) > 1
xlim(hAx, XL);
end
if length(YL) > 1
ylim(hAx, YL);
end
if length(ZL) > 1
zlim(hAx, ZL);
end
% Set grids
set(hAx, 'XGrid', xGrid, 'XColor', [.5 .5 .5]);
set(hAx, 'YGrid', yGrid, 'YColor', [.5 .5 .5]);
set(hAx, 'ZGrid', zGrid, 'ZColor', [.5 .5 .5]);
end
end
%--------------------------------------------------------------------------
%--------------------------------------------------------------------------
% Subfunctions
function varargout = evalFcn(fun, param)
% This function re-evaluates the function handles
%
% fun - cell array of function handles
% param - structure of parameters
% First, re-evaluate parameters
paramAssign(param)
for iFun = 1:length(fun)
varargout{iFun} = eval(fun{iFun});
end
end
%--------------------------------------------------------------------------
function paramAssign(param)
% This function evaluates the parameters in the caller workspace
fn = fieldnames(param);
for id = 1:length(fn)
assignin('caller', fn{id}, param.(fn{id}));
end
end