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.
- And finally, his function is another example where the new Function Argument Validation feature would be handy.
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.
- 범주:
- Picks
댓글
댓글을 남기려면 링크 를 클릭하여 MathWorks 계정에 로그인하거나 계정을 새로 만드십시오.