Code covered by the BSD License  

Highlights from
EZIPLOT

image thumbnail
from EZIPLOT by Jiro Doke
Interactive EZ Function Plotter

eziplot(fun, varargin)
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

Contact us at files@mathworks.com