Spider Plot III – Custom Charts (Authoring)
Sean‘s pick this week is spider_plot by Moses.
Last week, we looked at the custom chart I created. This week, we’ll look at authoring it.
Contents
Authoring the Custom Chart
Now let’s look at the steps to author the custom SpiderChart.
First we need a class that inherits from matlab.graphics.chartcontainer.ChartContainer.
classdef SpiderChart < matlab.graphics.chartcontainer.ChartContainer & ... matlab.graphics.chartcontainer.mixin.Legend
It needs a public property for everything we want our end users to be able to set or get. You’ll notice defaults and validation are done on the property level.
properties (SetObservable) P {mustBeNumeric} AxesInterval(1,1) double {mustBeInteger} = 3 % Number of axes grid lines AxesPrecision = 1 % Tick precision AxesLimits = [] % Axes limits FillOption matlab.lang.OnOffSwitchState = 'off' % Whether to shade data FillTransparency(1,1) double {mustBeGreaterThanOrEqual(FillTransparency,0),mustBeLessThanOrEqual(FillTransparency,1)} % Shading alpha Color(:,3) double {mustBeGreaterThanOrEqual(Color,0),mustBeLessThanOrEqual(Color,1)} = get(groot,'defaultAxesColorOrder') % Color order LineStyle {mustBeMember(LineStyle,{'-','--',':','-.','none'})} = '-' % Data line style LineWidth(1,1) double {mustBePositive} = 2 % Data line width Marker {mustBeMember(Marker,{'+','o','*','.','x','square','s','diamond','d','v','^','>','<','pentagram','p','hexagram','h','none'})} = 'o' % Data marker MarkerSize(1,1) double {mustBePositive} = 8 % Data marker size LabelFontSize(1,1) double {mustBePositive} = 10 % Label font size TickFontSize(1,1) double {mustBePositive} = 10 % Tick font size AxesLabels = "Label " + (1:100); % Axes labels DataLabels = "Data " + (1:100); % Data labels end
We also need properties for the underlying graphics object that our chart will create, adjust, or destroy as necessary. These can’t be saved or replicated so they’ll be Transient and NonCopyable.
properties (Access = private, Transient, NonCopyable) ThetaAxesLines = gobjects(0) RhoAxesLines = gobjects(0) DataLines = gobjects(0) LabelObjects = gobjects(0) FillPatches = gobjects(0) AxesTextLabels = gobjects(0) AxesTickLabels = gobjects(0) DoWholeUpdate = true AxesValues end
Then we need a constructor. I want my class to have both normal syntaxes for a chart, i.e.:
SpiderChart(data) SpiderChart(data, 'Name', value, ...) SpiderChart(parent, ____)
so I’ll handle this in the constructor.
% Constructor function obj = SpiderChart(parentOrP, varargin) narginchk(1, inf); if isa(parentOrP, 'matlab.graphics.Graphics') % SpiderPlot(parent, P, 'name', value) in = [{parentOrP, 'P'}, varargin]; else % SpiderPlot(P, 'name', value) in = [{'P', parentOrP} varargin]; end % Construct obj@matlab.graphics.chartcontainer.ChartContainer(in{:}); end
With a custom chart, we need to have a setup and update method. The setup runs once when the chart is constructed and update any time a property is changed and a drawnow called.
There are two properties (P, AxesInterval) that when changed could require adjusting the total number of graphics objects needed (i.e. could require creating or deleting objects in an update). Because of this, the only thing I’ll do in setup is set the axes properties. The update will deal with graphics object creation.
function setup(obj) % Configure axes ax = getAxes(obj); hold(ax, 'on') axis(ax, 'square') axis(ax, 'off') end
The decision of whether to destroy and recreate objects is based on a DoWholeUpdate property that is adjusted in the setters for the aforementioned properties. It also defaults to true so on first run, everything is created.
%Update implementation function update(obj) if obj.DoWholeUpdate % Only reset and reinitialize if P or AxesInterval changed resetStoredGraphicsObjects(obj); initializeEverything(obj) obj.DoWholeUpdate = false; end adjustAppearances(obj); end
These are the setters that toggle updating everything.
function set.P(obj, val) obj.P = val; obj.DoWholeUpdate = true; end
function set.AxesInterval(obj, val) obj.AxesInterval = val; obj.DoWholeUpdate = true; end
From the update method, you can see there are three main algorithmic parts: resetStoredGraphicsObjects, initializeEverything, and adjustAppearances.
The reset step deletes old graphics objects and reinitializes the properties to empty graphics placeholders.
function resetStoredGraphicsObjects(obj) % Delete old objects delete(obj.ThetaAxesLines) delete(obj.RhoAxesLines) delete(obj.DataLines) delete(obj.LabelObjects) delete(obj.FillPatches) delete(obj.AxesTextLabels) delete(obj.AxesTickLabels) % Preallocate new ones as empties obj.ThetaAxesLines = gobjects(0); obj.RhoAxesLines = gobjects(0); obj.DataLines = gobjects(0); obj.LabelObjects = gobjects(0); obj.FillPatches = gobjects(0); obj.AxesTextLabels = gobjects(0); obj.AxesTickLabels = gobjects(0); obj.AxesValues = []; end
Next, we initialize new objects where the number of objects is based on the size of P and the AxesInterval. All of them are initialized invisible with no data associated to them. This is the biggest change I had to make from the original code – I.e. rather than creating objects and not retaining their handles, I need to create them without setting their customizable properties, store the handles, and then adjust those properties later.
function initializeEverything(obj) % Initialize data children ax = getAxes(obj); for ii = obj.NumDataGroups:-1:1 obj.FillPatches(ii) = patch(nan, nan, nan, 'EdgeColor', 'none', 'HandleVisibility', 'off', 'Parent', ax); obj.DataLines(ii) = line(nan, nan, 'Parent', ax); end % Plot colors grey = [0.5, 0.5, 0.5]; % Polar increments theta_increment = 2*pi/obj.NumDataPoints; rho_increment = 1/(obj.AxesInterval+1); %%% Scale Data %%% % Pre-allocation P_scaled = zeros(size(obj.P)); axes_range = zeros(3, obj.NumDataPoints); % Iterate through number of data points for ii = 1:obj.NumDataPoints % Group of points group_points = obj.P(:, ii); % Automatically the range of each group min_value = min(group_points); max_value = max(group_points); rangeii = max_value - min_value; % Check if axes_limits is empty if isempty(obj.AxesLimits) % Scale points to range from [rho_increment, 1] P_scaled(:, ii) = ((group_points - min_value) / rangeii) * (1 - rho_increment) + rho_increment; else % Manually set the range of each group min_value = obj.AxesLimits(1, ii); max_value = obj.AxesLimits(2, ii); rangeii = max_value - min_value; % Check if the axes limits are within range of points if min_value > min(group_points) || max_value < max(group_points) error('Error: Please ensure the manually specified axes limits are within range of the data points.'); end % Scale points to range from [rho_increment, 1] P_scaled(:, ii) = ((group_points - min_value) / rangeii) * (1 - rho_increment) + rho_increment; end % Store to array axes_range(:, ii) = [min_value; max_value; rangeii]; end %%% Polar Axes %%% % Polar coordinates rho = 0:rho_increment:1; theta = 0:theta_increment:2*pi; % Iterate through each theta for ii = (length(theta)-1):-1:1 % Convert polar to cartesian coordinates [x_axes, y_axes] = pol2cart(theta(ii), rho); % Plot obj.ThetaAxesLines(ii) = line(ax, x_axes, y_axes, 'LineWidth', 1.5, ... 'Color', grey, 'HandleVisibility', 'off'); min_value = axes_range(1, ii); rangeii = axes_range(3, ii); % Iterate through points on isocurve for jj = length(rho):-1:2 % Axes increment value axes_value = min_value + (rangeii/obj.AxesInterval) * (jj-2); % Display axes text obj.AxesValues(ii, jj-1) = axes_value; obj.AxesTickLabels(ii, jj-1) = text(ax, x_axes(jj), y_axes(jj), '', ... 'Units', 'Data', ... 'Color', 'k', ... 'HorizontalAlignment', 'center', ... 'VerticalAlignment', 'middle', ... 'Visible', 'off'); end end % Iterate through each rho for ii = length(rho):-1:2 % Convert polar to cartesian coordinates [x_axes, y_axes] = pol2cart(theta, rho(ii)); % Plot obj.RhoAxesLines(ii-1) = line(ax, x_axes, y_axes, 'Color', grey, 'HandleVisibility', 'off'); end %%% Plot %%% % Iterate through number of data groups for ii = obj.NumDataGroups:-1:1 % Convert polar to cartesian coordinates [x_points, y_points] = pol2cart(theta(1:end-1), P_scaled(ii, :)); % Make points circular x_circular = [x_points, x_points(1)]; y_circular = [y_points, y_points(1)]; % Plot data points obj.DataLines(ii).XData = x_circular; obj.DataLines(ii).YData = y_circular; % Check if fill option is toggled on obj.FillPatches(ii).XData = x_circular; obj.FillPatches(ii).YData = y_circular; end %%% Labels %%% % Iterate through number of data points for ii = 1:obj.NumDataPoints % Angle of point in radians [horz_align, vert_align, x_pos, y_pos] = getQuadrantPosition(theta(ii)); % Display text label obj.AxesTextLabels(ii) = text(ax, x_axes(ii)+x_pos, y_axes(ii)+y_pos, '', ... 'Units', 'Data', ... 'HorizontalAlignment', horz_align, ... 'VerticalAlihggnment', vert_align, ... 'EdgeColor', 'k', ... 'BackgroundColor', 'w', ... 'Visible', 'off'); end end
And then finally, adjust the appearances based on the properties. This fires any time any of the properties are changed. It updates the properties of the underlying graphics objects to implement the change.
function adjustAppearances(obj) % Repeat colors as necessary repeat_colors = fix(obj.NumDataPoints/size(obj.Color, 1))+1; colors = repmat(obj.Color, repeat_colors, 1); % Patches for ii = 1:numel(obj.FillPatches) if obj.FillOption obj.FillPatches(ii).FaceColor = colors(ii, :); obj.FillPatches(ii).FaceAlpha = obj.FillTransparency; else obj.FillPatches(ii).FaceColor = 'none'; end end % Data appearances for ii = 1:numel(obj.DataLines) obj.DataLines(ii).LineStyle = obj.LineStyle; obj.DataLines(ii).Marker = obj.Marker; obj.DataLines(ii).Color = colors(ii, :); obj.DataLines(ii).LineWidth = obj.LineWidth; obj.DataLines(ii).MarkerSize = obj.MarkerSize; obj.DataLines(ii).MarkerFaceColor = colors(ii, :); obj.DataLines(ii).DisplayName = obj.DataLabels(ii); end if isequal(obj.AxesLabels, 'none') set(obj.AxesTextLabels, 'Visible', 'off') else set(obj.AxesTextLabels, 'Visible', 'on') % Iterate through number of data points for ii = 1:obj.NumDataPoints % Display text label obj.AxesTextLabels(ii).String = obj.AxesLabels{ii}; obj.AxesTextLabels(ii).FontSize = obj.LabelFontSize; end end if isequal(obj.AxesPrecision, 'none') set(obj.AxesTickLabels, 'Visible', 'off') else set(obj.AxesTickLabels, 'Visible', 'on') % Traverse to update precision for ii = 1:numel(obj.AxesValues) text_str = sprintf(sprintf('%%.%if', obj.AxesPrecision), obj.AxesValues(ii)); obj.AxesTickLabels(ii).String = text_str; obj.AxesTickLabels(ii).FontSize = obj.TickFontSize; end end end
Full Custom Chart
Obviously, it took some effort, design, and a lot of refactoring to turn this into a custom chart and there are still further improvements that could be made (e.g. not updating everything, but one property at a time). If you want to play with it yourself, here is the full SpiderChart.m class definition file. I took the liberty to make a couple enhancements to Moses’ original, e.g. separating label fontsize and tick fontsize and making things work with string arrays.
classdef SpiderChart < matlab.graphics.chartcontainer.ChartContainer & ... matlab.graphics.chartcontainer.mixin.Legend %spider_plot Create a spider or radar plot with individual axes. % % Syntax: % SpiderChart(PData) % SpiderChart(PData, Name, Value, ...) % SpiderChart(parent, ___) % % Input Arguments: % (Required) % PData - 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] % % DataLabels - Labels for data to be used in legend. String vector % with number of elements size(PData, 1). % % 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] % % LabelFontSize - Used to change the font size of the labels and % values displayed on the axes. % [10 (default) | scalar value greater than zero] % % TickFontSize - Used to change the font size of the 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]; % SpiderChart(P, 'DataLabels', "D" + (1:3)); % legend show %% % 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] % SpiderChart(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; % SpiderChart(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; % label_font_size = 12; % tick_font_size = 8; % data_labels = ["Hello" "World" "Happy 2020"]; % SpiderChart(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,... % 'LabelFontSize', label_font_size, ... % 'TickFontSize', tick_font_size, ... % 'DataLabels', data_labels) % legend show properties (SetObservable) P {mustBeNumeric} AxesInterval(1,1) double {mustBeInteger} = 3 % Number of axes grid lines AxesPrecision = 1 % Tick precision AxesLimits = [] % Axes limits FillOption matlab.lang.OnOffSwitchState = 'off' % Whether to shade data FillTransparency(1,1) double {mustBeGreaterThanOrEqual(FillTransparency,0),mustBeLessThanOrEqual(FillTransparency,1)} % Shading alpha Color(:,3) double {mustBeGreaterThanOrEqual(Color,0),mustBeLessThanOrEqual(Color,1)} = get(groot,'defaultAxesColorOrder') % Color order LineStyle {mustBeMember(LineStyle,{'-','--',':','-.','none'})} = '-' % Data line style LineWidth(1,1) double {mustBePositive} = 2 % Data line width Marker {mustBeMember(Marker,{'+','o','*','.','x','square','s','diamond','d','v','^','>','<','pentagram','p','hexagram','h','none'})} = 'o' % Data marker MarkerSize(1,1) double {mustBePositive} = 8 % Data marker size LabelFontSize(1,1) double {mustBePositive} = 10 % Label font size TickFontSize(1,1) double {mustBePositive} = 10 % Tick font size AxesLabels = "Label " + (1:100); % Axes labels DataLabels = "Data " + (1:100); % Data labels end properties (Access = private, Transient, NonCopyable) ThetaAxesLines = gobjects(0) RhoAxesLines = gobjects(0) DataLines = gobjects(0) LabelObjects = gobjects(0) FillPatches = gobjects(0) AxesTextLabels = gobjects(0) AxesTickLabels = gobjects(0) DoWholeUpdate = true AxesValues end properties (Dependent, Access = protected, Hidden) NumDataGroups NumDataPoints end methods % Constructor function obj = SpiderChart(parentOrP, varargin) narginchk(1, inf); if isa(parentOrP, 'matlab.graphics.Graphics') % SpiderPlot(parent, P, 'name', value) in = [{parentOrP, 'P'}, varargin]; else % SpiderPlot(P, 'name', value) in = [{'P', parentOrP} varargin]; end % Construct obj@matlab.graphics.chartcontainer.ChartContainer(in{:}); end %% Getters function numpts = get.NumDataPoints(obj) numpts = size(obj.P, 2); end function numpts = get.NumDataGroups(obj) numpts = size(obj.P, 1); end %% Setters % Okay to set DoWholeUpdate property in setters. %#ok<*MCSUP> function set.P(obj, val) obj.P = val; obj.DoWholeUpdate = true; end function set.AxesInterval(obj, val) obj.AxesInterval = val; obj.DoWholeUpdate = true; end end methods (Access = protected) % Setup implementation function setup(obj) % Configure axes ax = getAxes(obj); hold(ax, 'on') axis(ax, 'square') axis(ax, 'off') end % Update implementation function update(obj) if obj.DoWholeUpdate % Only reset and reinitialize if P or AxesInterval changed resetStoredGraphicsObjects(obj); initializeEverything(obj) obj.DoWholeUpdate = false; end adjustAppearances(obj); end % Adjust existing graphics objects function adjustAppearances(obj) % Repeat colors as necessary repeat_colors = fix(obj.NumDataPoints/size(obj.Color, 1))+1; colors = repmat(obj.Color, repeat_colors, 1); % Patches for ii = 1:numel(obj.FillPatches) if obj.FillOption obj.FillPatches(ii).FaceColor = colors(ii, :); obj.FillPatches(ii).FaceAlpha = obj.FillTransparency; else obj.FillPatches(ii).FaceColor = 'none'; end end % Data appearances for ii = 1:numel(obj.DataLines) obj.DataLines(ii).LineStyle = obj.LineStyle; obj.DataLines(ii).Marker = obj.Marker; obj.DataLines(ii).Color = colors(ii, :); obj.DataLines(ii).LineWidth = obj.LineWidth; obj.DataLines(ii).MarkerSize = obj.MarkerSize; obj.DataLines(ii).MarkerFaceColor = colors(ii, :); obj.DataLines(ii).DisplayName = obj.DataLabels(ii); end if isequal(obj.AxesLabels, 'none') set(obj.AxesTextLabels, 'Visible', 'off') else set(obj.AxesTextLabels, 'Visible', 'on') % Iterate through number of data points for ii = 1:obj.NumDataPoints % Display text label obj.AxesTextLabels(ii).String = obj.AxesLabels{ii}; obj.AxesTextLabels(ii).FontSize = obj.LabelFontSize; end end if isequal(obj.AxesPrecision, 'none') set(obj.AxesTickLabels, 'Visible', 'off') else set(obj.AxesTickLabels, 'Visible', 'on') % Traverse to update precision for ii = 1:numel(obj.AxesValues) text_str = sprintf(sprintf('%%.%if', obj.AxesPrecision), obj.AxesValues(ii)); obj.AxesTickLabels(ii).String = text_str; obj.AxesTickLabels(ii).FontSize = obj.TickFontSize; end end end % Initialize and preconfigure graphics objects function initializeEverything(obj) % Initialize data children ax = getAxes(obj); for ii = obj.NumDataGroups:-1:1 obj.FillPatches(ii) = patch(nan, nan, nan, 'EdgeColor', 'none', 'HandleVisibility', 'off', 'Parent', ax); obj.DataLines(ii) = line(nan, nan, 'Parent', ax); end % Plot colors grey = [0.5, 0.5, 0.5]; % Polar increments theta_increment = 2*pi/obj.NumDataPoints; rho_increment = 1/(obj.AxesInterval+1); %%% Scale Data %%% % Pre-allocation P_scaled = zeros(size(obj.P)); axes_range = zeros(3, obj.NumDataPoints); % Iterate through number of data points for ii = 1:obj.NumDataPoints % Group of points group_points = obj.P(:, ii); % Automatically the range of each group min_value = min(group_points); max_value = max(group_points); rangeii = max_value - min_value; % Check if axes_limits is empty if isempty(obj.AxesLimits) % Scale points to range from [rho_increment, 1] P_scaled(:, ii) = ((group_points - min_value) / rangeii) * (1 - rho_increment) + rho_increment; else % Manually set the range of each group min_value = obj.AxesLimits(1, ii); max_value = obj.AxesLimits(2, ii); rangeii = max_value - min_value; % Check if the axes limits are within range of points if min_value > min(group_points) || max_value < max(group_points) error('Error: Please ensure the manually specified axes limits are within range of the data points.'); end % Scale points to range from [rho_increment, 1] P_scaled(:, ii) = ((group_points - min_value) / rangeii) * (1 - rho_increment) + rho_increment; end % Store to array axes_range(:, ii) = [min_value; max_value; rangeii]; end %%% Polar Axes %%% % Polar coordinates rho = 0:rho_increment:1; theta = 0:theta_increment:2*pi; % Iterate through each theta for ii = (length(theta)-1):-1:1 % Convert polar to cartesian coordinates [x_axes, y_axes] = pol2cart(theta(ii), rho); % Plot obj.ThetaAxesLines(ii) = line(ax, x_axes, y_axes, 'LineWidth', 1.5, ... 'Color', grey, 'HandleVisibility', 'off'); min_value = axes_range(1, ii); rangeii = axes_range(3, ii); % Iterate through points on isocurve for jj = length(rho):-1:2 % Axes increment value axes_value = min_value + (rangeii/obj.AxesInterval) * (jj-2); % Display axes text obj.AxesValues(ii, jj-1) = axes_value; obj.AxesTickLabels(ii, jj-1) = text(ax, x_axes(jj), y_axes(jj), '', ... 'Units', 'Data', ... 'Color', 'k', ... 'HorizontalAlignment', 'center', ... 'VerticalAlignment', 'middle', ... 'Visible', 'off'); end end % Iterate through each rho for ii = length(rho):-1:2 % Convert polar to cartesian coordinates [x_axes, y_axes] = pol2cart(theta, rho(ii)); % Plot obj.RhoAxesLines(ii-1) = line(ax, x_axes, y_axes, 'Color', grey, 'HandleVisibility', 'off'); end %%% Plot %%% % Iterate through number of data groups for ii = obj.NumDataGroups:-1:1 % Convert polar to cartesian coordinates [x_points, y_points] = pol2cart(theta(1:end-1), P_scaled(ii, :)); % Make points circular x_circular = [x_points, x_points(1)]; y_circular = [y_points, y_points(1)]; % Plot data points obj.DataLines(ii).XData = x_circular; obj.DataLines(ii).YData = y_circular; % Check if fill option is toggled on obj.FillPatches(ii).XData = x_circular; obj.FillPatches(ii).YData = y_circular; end %%% Labels %%% % Iterate through number of data points for ii = 1:obj.NumDataPoints % Angle of point in radians [horz_align, vert_align, x_pos, y_pos] = getQuadrantPosition(theta(ii)); % Display text label obj.AxesTextLabels(ii) = text(ax, x_axes(ii)+x_pos, y_axes(ii)+y_pos, '', ... 'Units', 'Data', ... 'HorizontalAlignment', horz_align, ... 'VerticalAlignment', vert_align, ... 'EdgeColor', 'k', ... 'BackgroundColor', 'w', ... 'Visible', 'off'); end end % Delete graphics objects and reset properties before reinitializing function resetStoredGraphicsObjects(obj) % Delete old objects delete(obj.ThetaAxesLines) delete(obj.RhoAxesLines) delete(obj.DataLines) delete(obj.LabelObjects) delete(obj.FillPatches) delete(obj.AxesTextLabels) delete(obj.AxesTickLabels) % Preallocate new ones as empties obj.ThetaAxesLines = gobjects(0); obj.RhoAxesLines = gobjects(0); obj.DataLines = gobjects(0); obj.LabelObjects = gobjects(0); obj.FillPatches = gobjects(0); obj.AxesTextLabels = gobjects(0); obj.AxesTickLabels = gobjects(0); obj.AxesValues = []; end end end function [horz_align, vert_align, x_pos, y_pos] = getQuadrantPosition(theta_point) % Find out which quadrant the point is in if theta_point == 0 quadrant = 0; elseif theta_point == pi/2 quadrant = 1.5; elseif theta_point == pi quadrant = 2.5; elseif theta_point == 3*pi/2 quadrant = 3.5; elseif theta_point == 2*pi quadrant = 0; elseif theta_point > 0 && theta_point < pi/2 quadrant = 1; elseif theta_point > pi/2 && theta_point < pi quadrant = 2; elseif theta_point > pi && theta_point < 3*pi/2 quadrant = 3; elseif theta_point > 3*pi/2 && theta_point < 2*pi quadrant = 4; end % Adjust label alignment depending on quadrant % Shift axis label shift_pos = 0.13; switch quadrant case 0 horz_align = 'left'; vert_align = 'middle'; x_pos = shift_pos; y_pos = 0; case 1 horz_align = 'left'; vert_align = 'bottom'; x_pos = shift_pos; y_pos = shift_pos; case 1.5 horz_align = 'center'; vert_align = 'bottom'; x_pos = 0; y_pos = shift_pos; case 2 horz_align = 'right'; vert_align = 'bottom'; x_pos = -shift_pos; y_pos = shift_pos; case 2.5 horz_align = 'right'; vert_align = 'middle'; x_pos = -shift_pos; y_pos = 0; case 3 horz_align = 'right'; vert_align = 'top'; x_pos = -shift_pos; y_pos = -shift_pos; case 3.5 horz_align = 'center'; vert_align = 'top'; x_pos = 0; y_pos = -shift_pos; case 4 horz_align = 'left'; vert_align = 'top'; x_pos = shift_pos; y_pos = -shift_pos; end end
Comments
Do you have a use for custom charts or a chart you would like MathWorks to make?
Give it a try and let us know what you think here or leave a comment for Moses.
Published with MATLAB® R2020a
评论
要发表评论,请点击 此处 登录到您的 MathWorks 帐户或创建一个新帐户。