File Exchange Pick of the Week

Our best user submissions

Spider plots and more argument validation

Jiro‘s Pick this week is spider_plot by Moses.

There are quite a few “spider plots” on the File Exchange, but this one by Moses caught my attention for a few reasons.

  • Moses includes a detailed help inside the function.
help spider_plot
 spider_plot Create a spider or radar plot with individual axes.
 
  Syntax:
    spider_plot(P)
    spider_plot(___, Name, Value)
 
  Input Arguments:
    (Required)
    P                - The data points used to plot the spider chart. The
                       rows are the groups of data and the columns are the
                       data points. The axes labels and axes limits are
                       automatically generated if not specified.
                       [vector | matrix]
 
  Name-Value Pair Arguments:
    (Optional)
    AxesLabels       - Used to specify the label each of the axes.
                       [auto-generated (default) | cell of strings | 'none']
 
    AxesInterval     - Used to change the number of intervals displayed
                       between the webs.
                       [3 (default) | integer]
 
    AxesPrecision    - Used to change the precision level on the value
                       displayed on the axes. Enter in 'none' to remove
                       axes text.
                       [1 (default) | integer | 'none']
 
    AxesLimits       - Used to manually set the axes limits. A matrix of
                       2 x size(P, 2). The top row is the minimum axes
                       limits and the bottow row is the maximum axes limits.
                       [auto-scaled (default) | matrix]
 
    FillOption       - Used to toggle color fill option.
                       ['off' (default) | 'on']
 
    FillTransparency - Used to set color fill transparency.
                       [0.1 (default) | scalar in range (0, 1)]
 
    Color            - Used to specify the line color, specified as an RGB
                       triplet. The intensities must be in the range (0, 1).
                       [MATLAB colors (default) | RGB triplet]
 
    LineStyle        - Used to change the line style of the plots.
                       ['-' (default) | '--' | ':' | '-.' | 'none']
 
    LineWidth        - Used to change the line width, where 1 point is 
                       1/72 of an inch.
                       [0.5 (default) | positive value]
 
    Marker           - Used to change the marker symbol of the plots.
                       ['o' (default) | 'none' | '*' | 's' | 'd' | ...]
 
    MarkerSize       - Used to change the marker size, where 1 point is
                       1/72 of an inch.
                       [8 (default) | positive value]
 
    FontSize         - Used to change the font size of the labels and
                       values displayed on the axes.
                       [10 (default) | scalar value greater than zero]
 
  Examples:
    % Example 1: Minimal number of arguments. All non-specified, optional
                 arguments are set to their default values. Axes labels
                 and limits are automatically generated and set.
 
    D1 = [5 3 9 1 2];   % Initialize data points
    D2 = [5 8 7 2 9];
    D3 = [8 2 1 4 6];
    P = [D1; D2; D3];
    spider_plot(P);
    legend('D1', 'D2', 'D3', 'Location', 'southoutside');
  
    % Example 2: Manually setting the axes limits. All non-specified,
                 optional arguments are set to their default values.
 
    axes_limits = [1, 2, 1, 1, 1; 10, 8, 9, 5, 10]; % Axes limits [min axes limits; max axes limits]
    spider_plot(P,...
        'AxesLimits', axes_limits);
  
    % Example 3: Set fill option on. The fill transparency can be adjusted.
 
    axes_labels = {'S1', 'S2', 'S3', 'S4', 'S5'}; % Axes properties
    axes_interval = 2;
    fill_option = 'on';
    fill_transparency = 0.1;
    spider_plot(P,...
        'AxesLabels', axes_labels,...
        'AxesInterval', axes_interval,...
        'FillOption', fill_option,...
        'FillTransparency', fill_transparency);
  
    % Example 4: Maximum number of arguments.
 
    axes_labels = {'S1', 'S2', 'S3', 'S4', 'S5'}; % Axes properties
    axes_interval = 4;
    axes_precision = 'none';
    axes_limits = [1, 2, 1, 1, 1; 10, 8, 9, 5, 10];
    fill_option = 'on';
    fill_transparency = 0.2;
    colors = [1, 0, 0; 0, 1, 0; 0, 0, 1];
    line_style = '--';
    line_width = 3;
    marker_type = 'd';
    marker_size = 10;
    font_size = 12;
    spider_plot(P,...
        'AxesLabels', axes_labels,...
        'AxesInterval', axes_interval,...
        'AxesPrecision', axes_precision,...
        'AxesLimits', axes_limits,...
        'FillOption', fill_option,...
        'FillTransparency', fill_transparency,...
        'Color', colors,...
        'LineStyle', line_style,...
        'LineWidth', line_width,...
        'Marker', marker_type,...
        'MarkerSize', marker_size,...
        'FontSize', font_size);
  
  Author:
    Moses Yoo, (jyoo at hatci dot com)
    2019-10-16: Minor revision to add name-value pairs for customizing
                color, marker, and line settings.
    2019-10-08: Another major revision to convert to name-value pairs and
                add color fill option.
    2019-09-17: Major revision to improve speed, clarity, and functionality
 
  Special Thanks:
    Special thanks to Gabriela Andrade & Andrテゥs Garcia for their
    feature recommendations and suggested bug fixes.

  • He includes a Live Script example that showcases various use cases

  • He is very active in his communications with his users in the comments section and is also very diligent about updating the entry.

I’m thrilled to see a nice piece of code with robust error-checking. He has roughly 100 lines of code just for error checking the inputs to his function. He also sets default values for the optional arguments.

Here is the input argument processing/error checking piece of the code.

%%% Data Properties %%%
% Point properties
[num_data_groups, num_data_points] = size(P);

% Number of optional arguments
numvarargs = length(varargin);

% Check for even number of name-value pair argments
if mod(numvarargs, 2) == 1
    error('Error: Please check name-value pair arguments');
end

% Create default labels
axes_labels = cell(1, num_data_points);

% Iterate through number of data points
for ii = 1:num_data_points
    % Default axes labels
    axes_labels{ii} = sprintf('Label %i', ii);
end

% Default arguments
axes_interval = 3;
axes_precision = 1;
axes_limits = [];
fill_option = 'off';
fill_transparency = 0.2;
colors = [0, 0.4470, 0.7410;...
    0.8500, 0.3250, 0.0980;...
    0.9290, 0.6940, 0.1250;...
    0.4940, 0.1840, 0.5560;...
    0.4660, 0.6740, 0.1880;...
    0.3010, 0.7450, 0.9330;...
    0.6350, 0.0780, 0.1840];
line_style = '-';
line_width = 2;
marker_type = 'o';
marker_size = 8;
font_size = 10;

% Check if optional arguments were specified
if numvarargs > 1
    % Initialze name-value arguments
    name_arguments = varargin(1:2:end);
    value_arguments = varargin(2:2:end);
    % Iterate through name-value arguments
    for ii = 1:length(name_arguments)
        % Set value arguments depending on name
        switch lower(name_arguments{ii})
            case 'axeslabels'
                axes_labels = value_arguments{ii};
            case 'axesinterval'
                axes_interval = value_arguments{ii};
            case 'axesprecision'
                axes_precision = value_arguments{ii};
            case 'axeslimits'
                axes_limits = value_arguments{ii};
            case 'filloption'
                fill_option = value_arguments{ii};
            case 'filltransparency'
                fill_transparency = value_arguments{ii};
            case 'color'
                colors = value_arguments{ii};
            case 'linestyle'
                line_style = value_arguments{ii};
            case 'linewidth'
                line_width = value_arguments{ii};
            case 'marker'
                marker_type = value_arguments{ii};
            case 'markersize'
                marker_size = value_arguments{ii};
            case 'fontsize'
                font_size = value_arguments{ii};
            otherwise
                error('Error: Please enter in a valid name-value pair.');
        end
    end
end

%%% Error Check %%%
% Check if axes labels is a cell
if iscell(axes_labels)
    % Check if the axes labels are the same number as the number of points
    if length(axes_labels) ~= num_data_points
        error('Error: Please make sure the number of labels is the same as the number of points.');
    end
else
    % Check if valid string entry
    if ~strcmp(axes_labels, 'none')
        error('Error: Please enter in valid labels or "none" to remove labels.');
    end
end

% Check if axes limits is not empty
if ~isempty(axes_limits)
    % Check if the axes limits same length as the number of points
    if size(axes_limits, 1) ~= 2 || size(axes_limits, 2) ~= num_data_points
        error('Error: Please make sure the min and max axes limits match the number of data points.');
    end
end

% Check if axes precision is string
if ~ischar(axes_precision)
    % Check if axes properties are an integer
    if floor(axes_interval) ~= axes_interval || floor(axes_precision) ~= axes_precision
        error('Error: Please enter in an integer for the axes properties.');
    end
    % Check if axes properties are positive
    if axes_interval < 1 || axes_precision < 1
        error('Error: Please enter value greater than one for the axes properties.');
    end
else
    % Check if axes precision is valid string entry
    if ~strcmp(axes_precision, 'none')
        error('Error: Invalid axes precision entry. Please enter in "none" to remove axes text.');
    end
end

% Check if not a valid fill option arguement
if ~ismember(fill_option, {'off', 'on'})
    error('Error: Please enter either "off" or "on" for fill option.');
end

% Check if fill transparency is valid
if fill_transparency < 0 || fill_transparency > 1
    error('Error: Please enter a transparency value between [0, 1].');
end

% Check if font size is greater than zero
if font_size <= 0
    error('Error: Please enter a font size greater than zero.');
end

This can be converted into an arguments block.

arguments
    P (:,:) double
    options.AxesLabels {validateAxesLabels(options.AxesLabels,P)} = cellstr("Label " + (1:size(P,2)))
    options.AxesInterval (1,1) double {mustBeInteger} = 3
    options.AxesPrecision {validateAxesPrecision(options.AxesPrecision)} = 1
    options.AxesLimits double {validateAxesLimits(options.AxesLimits,P)} = []
    options.FillOption char {mustBeMember(options.FillOption,{'off','on'})} = 'off'
    options.FillTransparency (1,1) double {mustBeGreaterThanOrEqual(options.FillTransparency,0),mustBeLessThanOrEqual(options.FillTransparency,1)} = 0.1
    options.Color (:,3) double {mustBeGreaterThanOrEqual(options.Color,0),mustBeLessThanOrEqual(options.Color,1)} = get(groot,'defaultAxesColorOrder')
    options.LineStyle char {mustBeMember(options.LineStyle,{'-','--',':','-.','none'})} = '-'
    options.LineWidth (1,1) double {mustBePositive} = 2
    options.Marker char {mustBeMember(options.Marker,{'+','o','*','.','x','square','s','diamond','d','v','^','>','<','pentagram','p','hexagram','h','none'})} = 'o'
    options.MarkerSize (1,1) double {mustBePositive} = 8
    options.FontSize (1,1) double {mustBePositive} = 10
end

I also created custom validation functions for few of the arguments.

function validateAxesPrecision(x)
if isnumeric(x)
    validateattributes(x,{'double'},{'scalar','integer'},mfilename,'AxesPrecision')
else
    if ~isequal(x,'none')
        error('AxesPrecision must be a scalar integer or ''none''')
    end
end
end

function validateAxesLimits(axLim,P)
if ~isempty(axLim)
    validateattributes(axLim,{'double'},{'size',[2 size(P,2)]},mfilename,'AxesLimits')
end
end

function validateAxesLabels(axLabels,P)
if ~isequal(axLabels,'none')
    validateattributes(axLabels,{'cell'},{'size',[1 size(P,2)]},mfilename,'AxesLabels')
end
end

These changes reduced the number of lines in the code from 500 to 400. Pretty neat!

Comments

Give it a try and let us know what you think here or leave a comment for Moses.




Published with MATLAB® R2019b

|
  • print

Comments

To leave a comment, please click here to sign in to your MathWorks Account or create a new one.