function aux_out=rs_disp_coordsets(data_in,aux)
% aux_out=rs_disp_coordsets(data_in,aux) displays one or more views of pairs or triplets of coordinates in a `dataset structure`
%
% Multiple views can be plotted, one eachin a subplot of the same figure. This is particularly helpful if the dimensionality
% of the coordinates is high: each subplot could show a different combination of two or three coordinates.
% Plots of two coordinates will produce a planar view; plots of three coordinates will produce an isometric 3D view. Subplots are left in the 'hold on' state.
%
% Args:
% data_in (struct): `dataset structure` to be processed, with fields
%
% - ds (cell array): `coordinate structure`, ds{k}{idim} is an array of [nstims idim] of coordinates for the kth record
% - sas (cell array): `stimulus metadata structure`, sas{k} is the stimulus metadata for the kth record
% - sets (cell array): `set metadata structure`, sets{k} is the response metadata for the kth record
%
% aux (struct): auxiliary inputs, may be omitted, with fields
%
% - opts_disp (struct): options for display, with fields to contol
%
% - **Data selection**
% - set_select (int 1-D array): list of records to show; defaults is [1:length(data_in.ds)]
% - dim_select (int): dimension to show (>=2); i.e., dim_select=k results in display of the coordinates in data_in.ds{set_select}{k}; default is 3 unless only two dimensions are available; must be at least 2
% - coord_group_size (int): number of coordinates to display together, in range [2 3]; default is min(dim_select,number of dimensions available,3)
% - coord_group_method (char): method of selecting coordinates
%
% - 'all': (default) plot all combinations
% - 'keeplow': keep 1 to dim_select-1 and step the highest; [dim_select,coord_group_size]=[5,3] yields [1 2 3],[1 2 4],[1 2 5]
% - 'keepone': keep one dimension and step the rest; [dim_select,coord_group_size]=[5,3] yields [1 2 3],[1 2 4],[1 2 5],[1 3 4],[1 3 5],[1 4 5]
% - 'rolling': rolling contiguous subsets; [dim_select,coord_group_size]=[5,3] yields [1 2 3],[2 3 4],[3 4 5],[4 5 1],[5 1 2]
% - 'onlylowest': only the lowest dimensions; [dim_select,coord_group_size]=[5,3] yields [1 2 3]
% - 'list': specify a list in opts_disp.coord_groups, as an array with coord_group_size columns, e.g., opts_disp.coord_groups=[1 2 3;1 4 5;1 6 7] creates three subplots, with coordinates {1,2,3} in the first, {1,4,5} in the second, {1,6,7} in the third
%
% - coord_groups (int 2-D array): groups of coordinates to show together if coord_group_method='list', as rows (of length coord_group_size) of an integer array; each row will generate one subplot
% - data_show_method (char): which data points to show, options are 'all', 'none', 'first', 'last', 'list'; default is 'all'
% - data_show_list (int 1-D array): list of data points to show, if data_show_method='list'; points plotted are data_in.ds{set_select}{k}(data_show_list,:); This applies to data in all records. To disable display of individual data points in specific records, set their coordinates to NaN.
%
% - **Figure and subplot control**
% - fig_handle (handle): handle to figure; will be created if empty or not provided
% - fig_position (int 1-D array): position parameters [left bottom width height] for figure to be created; see note below regarding customization
% - fig_name (char): title for figure; default is dimension shown
% - axis_handles (cell array of handles): handle to axes, one for each subplot, will be created empty or not provided
%
% - **Labels**
% - set_labels (char or cell array of char): labels for each record that will appear in legend; defaults is 'set 1', etc.; cannot be empty; see note regarding plot formatting
% - data_label_setsel_method (char): selects which records to label individual points, options are 'all','none', 'first' , 'last', or 'list'; default is 'first'; note that 'all','first', and 'last' apply to the records shown
% - data_label_setsel_list (int 1-D array): list of datasets to label, if data_label_setsel_method='list'
% - data_label_method (char): selects which data points to label, options are 'all', 'none', 'first', 'last', 'list'; default is 'all'; labels are taken from data_in.sas{k}.typenames; note that 'all','first',and 'last' refer to data points shown
% - data_label_list (int 1-D array): list of data points to label, if data_label_method='list'
% - data_label_font_size (int): font size for data labels, default is axis_font_size
% - data_label_interpreter (char): interpreter for labeling data, [] (default) uses system default, alternatively 'none','tex','latex'
% - data_label_typenames_vary (int): 1 to allow typenames to vary across datasets, 0 does not; default is 0
% - callout_amount (float): moves the position of a label away from the data point, specified in units of rms deviation of data from callout_center; default is 0
% - callout_colors (cell array of color specifiers): color for callout lines connecting labels and points; default is {'k'}; can also be 'set_colors' to match set_colors
% - callout_linestyles (cell array of char): line styles for above callout lines; default is {'-.'}
% - callout_linewidths (int 1-D array): line widths for above callout lines; default is 1
% - callout_center (char or int 1-D array): callout lines point from the specified location outward; default is 'centroid' (centroid of data), can also be 'zero' or a coordinate value, of length 'dim_select'
%
% - **Formatting: axis and views**
% - axis_font_size (int): font size for axis; default is 8; see note below re customization
% - axis_label_font_size (char): font size for axis labels; default is axis_font_size
% - axis_label_prefix (char): prefix for axis label, default is 'coord'; see note below regarding customization
% - axis_labels (cell array of char): cell array of strings, cycled through if necessary, with text for axis labels. If empty, then axis labels are genrated from axis_label_prefix
% - axis_view (int or float 1-D array or cell array): 3-D view descriptor, default is 3 (standard 3-d view), 2 is 2-d view; can also be azimuth-elevation pair; standard 3-d view is [-37.5 30]; can be also be cell array of view specifiers, is cycled through for each subplot
% - axis_equal (int): 1 to set axes to have equal scales, 0 autoscales; default is 1
% - axis_range (char): 'tight' to set axis range to limits of data, 'auto' for autoscaling, or 'list' to specify by axis_range_list; default is 'tight'
% - axis_range_list (float 2-D array): axis range specification, as rows of [low, high] values, one for each coordinate plotted; cycled through by rows if necessary
%
% - **Formatting: points and lines for each record**
% - set_colors (color specifier or cell array of color specifiers): color assigned to each record; default is {'k','b','c','m','r',[0.5 0.5 0],'g'}; elements can be any valid color specifier; see note regarding plot formatting
% - set_markers (char or cell array of char): marker for each record; defaults is {'.'}; see note below regarding plot formatting
% - set_markersizes (int 1-D array): marker size for each record; default is 8; see below note regarding plot formatting
% - set_filled (int 1-D array): 1 for records in which markers are filled, 0 for unfilled if possible; default is 0; note that only some marker, e.g., o,s,h,p can be unfilled
% - set_colors_filled (color specifer or cell array of color specifiers): color for inside of filled symbols for each record, ignored if set_filled=0; default is set_colors
% - set_alphas (float 1-D array): alpha blending for each record, default is 1 (opaque); note that alpha-blending (transparency) may not be availble on all systems
% - connect_data_method (char): which pairs of data points to connect within a record; default is 'none'; options refer to the points selected for display by 'data_show_method'; see note below regarding plot formatting
%
% - 'none': no connections between points
% - 'all': connect all pairs of points
% - 'star' or 'star_first': connect all points to first
% - 'star_last': connect all points to last
% - 'chain': connect first point =to second, second point to third, ..., and next-to-last point to last point
% - 'circuit': same as 'chain' but adds a connection from last point to first
% - 'list': specify the pairs of points in connect_data_list
%
% - connect_data_list (int 2-D array): two-column array of data points to connect (if connect_data_method='list')
% - connect_data_linestyles (char or cell array of char): line styles assigned to connections within each record, default is 'none' (disconnected)
% - connect_data_linewidths (int 1-D array): line widths for connections within each record; default is 1
%
% - **Formatting: connections between records**
% - connect_sets_method (char): which records to connect; default is 'none'; options refer to the records selected for display by 'set_select'; see note below regarding plot formatting
%
% - 'none': no connections between data from different records
% - 'all': connect all pairs of records
% - 'star' or 'star_first': connect all records to first
% - 'star_last': connect all records to last
% - 'chain': connect first record to second, second record to third, ..., and next-to-last record to last record
% - 'circuit': same as 'chain' but adds a connection from last record to first
% - 'list': specify the pairs of records in connect_sets_list
%
% - connect_sets_list(int 2-D array): two-column array of records to connect, used if connect_sets_method='list'
% - connect_sets_data_method (char): selects the data points to connect between records: any of the options for data_label_method or 'labeled', which connects all data points designated by 'data_label_method'; default is 'all'
% - connect_sets_data_list (int 1-D array): data points to connect between records, if connect_sets_data_method='list'
% - connect_sets_color_mode (char): specifies how the connecting semgent between records is colored; default is 'split'
%
% - 'first': use color of first record in connected pair
% - 'last': use color of last record in connected pair
% - 'split': first half of segment matches first record in connected pair; second half of segmentmatches second record in connected pair
% - 'list': specify colors in connect_sets_colors
%
% - connect_sets_colors (color specifier or cell array of color specifiers): if connect_sets_color_mode='list';, these are the colors for for connecting segments; cycled through if necessary
% - connect_sets_linestyles (char or cell array of char): line styles assigned to connections between records; default is '-' (solid); cycled through if necessary
% - connect_sets_linewidths (int 1-D array): line widths assigned to connections between records; default is 1; cycled through if necessary
%
% - **Formatting: positioning**
% - set_offsets (float 2-D array or char): this allows the data in each record to be offset to avoid overlap; default is no offset; specified by 0
%
% - if an array, each row, of length dim_select (which wll be truncated or padded as needed) specifies the offset for the corresponding record in `data_in`
% - 'margin_amount' puts a margin of set_offsets_margin_amount between each dataset and the next
% - 'margin_fraction' puts a fractional margin of set_offsets_margin_fraction * average span of adjacent sets
%
% - set_offsets_margin_amount (float 1-D array): absolute margin between datasets if set_offsets='margin_amount; defaults to ones(1,dim_select); can be 0 or negative, truncated or padded to dim_select
% - set_offsets_margin_fraction (float 1-D array): fractional margin between datasets if set_offsets='margin_fraction; defaults to zeros(1,dim_select); can be 0 or negative, truncated or padded to dim_select
% - set_offsets_coordchoices (int or char or cell array of char): if set_offsets='margin_amount' or 'margin_fraction', this specifies which coordinate is offset; can be 'first','last','all', or a subset of [1:dim_select]; can also be a cell array of subsets
%
% - **Formatting: perpendiculars from points to walls**
% - perp_data_dims (int or int 1-D array): 1 to drop perpendiculars along that dimension,-1 for perpendicular to top, 0 to omit; default is 0; cycled through if necessary
% - perp_data_linestyles (char or cell array of char): line styles for perpendiculars for each record, or 'use_data' to use the style specified by 'connect_data_linestyles' for connecting data points; default is '-' (solid); cycled through if necessary
% - perp_data_linewidths (int 1-D array): line widths for perpendiculars for each record; default is 1
% - perp_data_usemarkers (int): 1 to use markers from original data at foot of perpendicular, 0 for no markers; default is 1
%
% - **Formatting: legend, box, grid**
%
% - if_legend (int): 1 to include legend, 0 to omit, -1 to omit from all subplots but to create an extra subplot that only has the legend; default is 1
% - legend_font_size (int): font size for legend; default is axis_font_size
% - legend_location (char): location of legend within plot; any Matlab designator accepted; default is 'Best'
% - legend_interpreter (char): interpreter for record labels in legend, [] (default) uses system default, alternatively 'none','tex','latex'
% - legend_tags (char): single string that must be present in a dataset's tag for inclusion in a legend, default is 'set';
% - set_tags (char or cell array of char): the 'tags' field applied to the plot of each record, for selecting which records appear in legend, default is 'set 1', etc.
% - if_box (int): 1 to include the bounding box in a 3d plot, 0 to omit; default is 1
% - if_grid (int): 1 to include the grid, 0 to omit; default is 1
% - if_finalize (int): 1 to apply view, legend, labels, and perpendiculars, 0 to omit; default is 1
%
% - if_warn (int): 1 to display warnings related to plot configurations, 0 to suppress; default is 1
%
% - opts_check (struct): options for consistency checking, with field
%
% - if_warn (int): 1 to show warnings when datasets are checked for consistency, 0 to suppress; default is 1
%
% Returns:
% aux_out (struct): auxiliary outputs and parameter values used, with fields
%
% - warnings (char): warnings generated during consistency check
% - warn_bad (int): number of warnings that prevent further processing
% - opts_disp (struct): aux.opts_disp, with defaults and overrides filled in, including fig_handle (handle to the figure) and axis_handles (handles to the subplot axes)
%
% Note: Note regarding customization
% - The default figure position can be changed by editing the line containing generic.opts_disp.fig_position in `rs_aux_defaults_define`, running it once, and saving the workspace as rs_aux_defaults.mat.
% - The default font size for axis labels can be changed by editing the line containing generic.opts_disp.axis_font_size in `rs_aux_defaults_define`, running it once, and saving the workspace as rs_aux_defaults.mat.
% - The default prefix for the axis label can be changed by editing the line containing generic.opts_disp.axis_label_prefix in `rs_aux_defaults_define`, running it once, and saving the workspace as rs_aux_defaults.mat.
%
% Note: Note regarding plot formatting
% - By default, each record is plotted with a different color, a solid dot marker, and no connecting lines.
% - These choices can be changed by 'set_colors', 'set_markers', 'set_markersizes', 'set_filled', 'set_colors_filled', 'set_alphas' (but alpha blending may not be availble on all systems).
% - The above specifiers can be singletons or cell arrays, and are indexed by the record position in data_in. If there are more records than specifiers, the specifiers are cycled.
% - The data points of each record are, by default, not interconnected. This can be changed with the 'connect_data*' options.
% - The data points between records are, by default, not interconnected . This can be changed with the 'connect_sets*' options.
%
% See also: RS_DISP_ENH_COORDSETS, RS_CHECK_COORDSETS, RS_GET_COORDSETS, RS_ALIGN_COORDSETS, RS_KNIT_COORDSETS, RS_PLOT_STYLE.
%
if (nargin<=1)
aux=struct;
end
%fields that will be made into cells if singletons
make_cell={'set_colors','set_colors_filled','set_markers','connect_data_linestyles','set_labels','set_tags','legend_tags','axis_view','connect_sets_linestyles','connect_sets_colors',...
'callout_colors','callout_linestyles','axis_labels','perp_data_linestyles'};
trunc_pad={'set_offsets','set_offsets_margin_amount','set_offsets_margin_fraction'}; %fields that are truncated or padded to have dim 2 length = dim_select
coords_together_allowed=[2 3]; %how many coords can be plotted together -eventually could include >=4
coords_together_default=[2 3]; %how many coords are plotted together by default
xyzlim={'XLim','YLim','ZLim'};
datapoint_tag='*datapoint*'; %string to identify data points
%
aux=filldefault(aux,'opts_check',struct);
aux.opts_check=filldefault(aux.opts_check,'if_warn',1);
%
%check consistency and get available stimuli, dimensions, typenames
%
aux_out=struct;
%
%prior to consistency checking, see if we need to allow for typenames to vary
%
data_label_typenames_vary=0;
if isfield(aux,'opts_disp')
if isfield(aux.opts_disp,'data_label_typenames_vary')
data_label_typenames_vary=aux.opts_disp.data_label_typenames_vary;
end
end
data_in_check=data_in;
if data_label_typenames_vary
for iset=1:length(data_in.sas)
for istim=1:length(data_in.sas{iset}.typenames)
data_in_check.sas{iset}.typenames{istim}=sprintf('stim_%1.0f',istim);
end
end
end
%
check=rs_check_coordsets(data_in_check,aux.opts_check);
%
aux_out.warnings=check.warnings;
aux_out.warn_bad=check.warn_bad;
nsets=check.nsets;
nstims_each=check.nstims_each;
dim_list_each=check.dim_list_each;
dim_list_union=check.dim_list_union;
dim_list_inter=check.dim_list_inter;
typenames_each=check.typenames_each;
typenames_union=check.typenames_union;
typenames_inter=check.typenames_inter;
%
nstims=min(nstims_each); %all nstims_each should be identical
%
%set up sub-structure options
aux=filldefault(aux,'opts_disp',struct); %options for this module (psg_template)
aux.opts_disp=filldefault(aux.opts_disp,'if_warn',1);
aux.opts_disp=filldefault(aux.opts_disp,'if_finalize',1);
%
aux.opts_disp=filldefault(aux.opts_disp,'fig_handle',[]);
aux.opts_disp=filldefault(aux.opts_disp,'axis_handles',[]);
aux.opts_disp=filldefault(aux.opts_disp,'axis_view',{3});
aux.opts_disp=filldefault(aux.opts_disp,'axis_equal',1);
aux.opts_disp=filldefault(aux.opts_disp,'axis_range','tight');
aux.opts_disp=filldefault(aux.opts_disp,'axis_range_list',[0 1]);
aux.opts_disp=filldefault(aux.opts_disp,'axis_labels',cell(0));
%
aux.opts_disp=filldefault(aux.opts_disp,'dim_select',max(intersect(coords_together_default,dim_list_inter)));
aux.opts_disp=filldefault(aux.opts_disp,'set_select',[1:nsets]);
aux.opts_disp=filldefault(aux.opts_disp,'set_offsets',zeros(1,aux.opts_disp.dim_select));
aux.opts_disp=filldefault(aux.opts_disp,'set_offsets_margin_amount',ones(1,aux.opts_disp.dim_select));
aux.opts_disp=filldefault(aux.opts_disp,'set_offsets_margin_fraction',zeros(1,aux.opts_disp.dim_select));
aux.opts_disp=filldefault(aux.opts_disp,'set_offsets_coordchoices','all');
%
aux.opts_disp=filldefault(aux.opts_disp,'coord_group_size',min(aux.opts_disp.dim_select,max(coords_together_default)));
aux.opts_disp=filldefault(aux.opts_disp,'coord_group_method','all');
aux.opts_disp=filldefault(aux.opts_disp,'fig_name',sprintf('dimension %2.0f',aux.opts_disp.dim_select));
%
aux.opts_disp=filldefault(aux.opts_disp,'set_colors',{'k','b','c','m','r','y','g'});
aux.opts_disp=filldefault(aux.opts_disp,'set_markers',{'.'});
aux.opts_disp=filldefault(aux.opts_disp,'set_markersizes',8);
aux.opts_disp=filldefault(aux.opts_disp,'set_filled',0);
aux.opts_disp=filldefault(aux.opts_disp,'set_colors_filled',aux.opts_disp.set_colors);
aux.opts_disp=filldefault(aux.opts_disp,'set_alphas',1);
%
aux.opts_disp=filldefault(aux.opts_disp,'data_show_method','all');
aux.opts_disp=filldefault(aux.opts_disp,'data_show_list',[]);
aux.opts_disp=filldefault(aux.opts_disp,'data_label_method','all');
aux.opts_disp=filldefault(aux.opts_disp,'data_label_list',[]);
aux.opts_disp=filldefault(aux.opts_disp,'data_label_setsel_method','first');
aux.opts_disp=filldefault(aux.opts_disp,'data_label_setsel_list',[]);
aux.opts_disp=filldefault(aux.opts_disp,'data_label_interpreter',[]);
aux.opts_disp=filldefault(aux.opts_disp,'data_label_typenames_vary',0);
%
aux.opts_disp=filldefault(aux.opts_disp,'callout_amount',0);
aux.opts_disp=filldefault(aux.opts_disp,'callout_colors',{'k'});
aux.opts_disp=filldefault(aux.opts_disp,'callout_linestyles',{'-.'});
aux.opts_disp=filldefault(aux.opts_disp,'callout_linewidths',1);
aux.opts_disp=filldefault(aux.opts_disp,'callout_center','centroid');
%
aux.opts_disp=filldefault(aux.opts_disp,'connect_data_method','none');
aux.opts_disp=filldefault(aux.opts_disp,'connect_data_list',[]);
aux.opts_disp=filldefault(aux.opts_disp,'connect_data_linestyles',{'none'});
aux.opts_disp=filldefault(aux.opts_disp,'connect_data_linewidths',1);
%
aux.opts_disp=filldefault(aux.opts_disp,'connect_sets_method','none');
aux.opts_disp=filldefault(aux.opts_disp,'connect_sets_list',[]);
aux.opts_disp=filldefault(aux.opts_disp,'connect_sets_data_method','all');
aux.opts_disp=filldefault(aux.opts_disp,'connect_sets_data_list',[]);
aux.opts_disp=filldefault(aux.opts_disp,'connect_sets_linestyles','-');
aux.opts_disp=filldefault(aux.opts_disp,'connect_sets_linewidths',1);
aux.opts_disp=filldefault(aux.opts_disp,'connect_sets_color_mode','split');
aux.opts_disp=filldefault(aux.opts_disp,'connect_sets_colors',[]);
%
aux.opts_disp=filldefault(aux.opts_disp,'perp_data_dims',0);
aux.opts_disp=filldefault(aux.opts_disp,'perp_data_linestyles','-');
aux.opts_disp=filldefault(aux.opts_disp,'perp_data_linewidths',1);
aux.opts_disp=filldefault(aux.opts_disp,'perp_data_usemarkers',1);
%
aux.opts_disp=filldefault(aux.opts_disp,'if_box',1);
aux.opts_disp=filldefault(aux.opts_disp,'if_grid',1);
aux.opts_disp=filldefault(aux.opts_disp,'if_legend',1);
aux.opts_disp=filldefault(aux.opts_disp,'legend_location','Best');
aux.opts_disp=filldefault(aux.opts_disp,'legend_interpreter',[]);
aux.opts_disp=filldefault(aux.opts_disp,'legend_tags',{'set'});
%
aux=rs_aux_customize(aux,'rs_disp_coordsets');
%quantities dependent on overall defaults
aux.opts_disp=filldefault(aux.opts_disp,'axis_label_font_size',aux.opts_disp.axis_font_size);
aux.opts_disp=filldefault(aux.opts_disp,'legend_font_size',aux.opts_disp.axis_font_size);
aux.opts_disp=filldefault(aux.opts_disp,'data_label_font_size',aux.opts_disp.axis_font_size);
%
%set up other defaults and check consistency
%
disp_msgs=[]; %for warnings encountered during plots
x=aux.opts_disp; %for convenience
switch x.coord_group_method %determine coordinate groups
case 'all'
x.coord_groups=nchoosek([1:x.dim_select],x.coord_group_size);
case 'keeplow'
x.coord_groups=[repmat([1:x.coord_group_size-1],x.dim_select-x.coord_group_size+1,1),[x.coord_group_size:x.dim_select]'];
case 'keepone'
coords_group_list_hi=nchoosek([2:x.dim_select],x.coord_group_size-1);
x.coord_groups=[ones(size(coords_group_list_hi,1),1) coords_group_list_hi];
case 'rolling'
x.coord_groups=mod(repmat([0:x.coord_group_size-1],x.dim_select,1)+repmat([0:x.dim_select-1]',1,x.coord_group_size),x.dim_select)+1;
case 'onlylowest'
x.coord_groups=[1:x.coord_group_size];
case 'list'
otherwise
wmsg=sprintf('grouping method (%s) not recognized; only lowest coords shown',x.coord_group_method);
x.coord_groups=[1:x.coord_group_size];
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
if x.coord_group_size~=size(x.coord_groups,2)
wmsg=sprintf('number of columnns in coord_groups (%3.0f) and coord_group_size (%3.0f) is inconsistent',size(x.coord_groups,2),x.coord_group_size);
aux_out=rs_warning(wmsg,1,setfield(aux_out,'if_warn',x.if_warn));
end
if ~ismember(x.coord_group_size,coords_together_allowed)
wmsg=sprintf('cannot plot groups of %3.0f coordinates on same axis',x.coord_group_size);
aux_out=rs_warning(wmsg,1,setfield(aux_out,'if_warn',x.if_warn));
end
if any(x.coord_groups(:)<=0) | any(x.coord_groups(:)>x.dim_select)
wmsg=sprintf('some specified dimensions are out of bounds for the dimension plotted (%2.0f)',x.dim_select);
aux_out=rs_warning(wmsg,1,setfield(aux_out,'if_warn',x.if_warn));
end
if ~isempty(setdiff(x.set_select,[1:nsets]))
wmsg=sprintf('some selections in set_select are out of bounds ([1:%1.0f], and ignored )',nsets);
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
x.set_select=intersect(x.set_select,[1:nsets]);
end
ngroups=size(x.coord_groups,1);
ngroups_aug=ngroups;
if x.if_legend==-1
ngroups_aug=ngroups+1;
end
%parse set_offsets
for ifn=1:length(trunc_pad) %trunc_pad={'set_offsets','set_offsets_margin_amount','set_offsets_margin_fraction'}; %fields that are truncated or padded to have dim 2 length = dim_select
tp=trunc_pad{ifn};
if isnumeric(x.(tp))
needcols=[1 x.dim_select];
needcols_msg=sprintf('1 to %1.0f',x.dim_select);
%
if_okcols=double((size(x.(tp),2)>=min(needcols)) & (size(x.(tp),2)<=max(needcols)));
if if_okcols==0
wmsg=sprintf('number of columns in %s (%1.0f) is inconsistent with number needed (%s); truncated or padded',tp,size(x.(tp),2),needcols_msg);
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
if size(x.(tp),2)>x.dim_select
x.(tp)=x.(tp)(:,[1:x.dim_select]);
end
if size(x.(tp),2)<x.dim_select
x.(tp)=cat(2,x.(tp),zeros(size(x.(tp),1),x.dim_select-size(x.(tp),2)));
end
end
end
if ~isnumeric(x.set_offsets)
%compute min and max of each coord in each set
mins=zeros(nsets,x.dim_select);
maxs=zeros(nsets,x.dim_select);
for iset=1:nsets
mins(iset,:)=min(data_in.ds{iset}{x.dim_select},[],1);
maxs(iset,:)=max(data_in.ds{iset}{x.dim_select},[],1);
end
switch x.set_offsets
case 'margin_amount' %space by an absolute amount
upper=-Inf;
x.set_offsets=zeros(nsets,x.dim_select);
for iset=1:nsets
if ismember(iset,x.set_select)
if (upper>-Inf) %have we encountered any sets
x.set_offsets(iset,:)=x.set_offsets_margin_amount+upper-mins(iset,:);
upper=x.set_offsets(iset,:)+maxs(iset,:);
else
upper=maxs(iset,:);
end
end
end
case 'margin_fraction' %space by a fraction of the average size of the spans
upper=-Inf;
x.set_offsets=zeros(nsets,x.dim_select);
for iset=1:nsets
if ismember(iset,x.set_select)
if (upper>-Inf) %have we encountered any sets
meansize=(maxs(iset_prev,:)-mins(iset_prev,:)+maxs(iset,:)-mins(iset,:))/2; %mean span of current and previous set
x.set_offsets(iset,:)=x.set_offsets_margin_fraction.*meansize+upper-mins(iset,:);
upper=x.set_offsets(iset,:)+maxs(iset,:);
iset_prev=iset;
else
upper=maxs(iset,:);
iset_prev=iset;
end
end
end
otherwise
wmsg=sprintf('specification of offset (%s) not recognized; no offset used',x.set_offsets);
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
x.set_offsets=zeros(1,x.dim_select);
end
x.set_offsets=x.set_offsets-repmat(mean(x.set_offsets(x.set_select,:),1),nsets,1); %center around zero
%now process set_offsets_coordchoices: which coordinates are offset in each coordinate group
if ~ischar(x.set_offsets_coordchoices) %only force numeric arrays into cells
if ~iscell(x.set_offsets_coordchoices)
x.set_offsets_coordchoices={x.set_offsets_coordchoices};
end
off_choices=x.set_offsets_coordchoices;
else %string values cannot be cells
if iscell(x.set_offsets_coordchoices)
x.set_offsets_coordchoices=x.set_offsets_coordchoices{1};
end
switch x.set_offsets_coordchoices
case 'all'
off_choices=num2cell(x.coord_groups,2);
case 'first'
off_choices=(num2cell(x.coord_groups(:,1)))';
case 'last'
off_choices=(num2cell(x.coord_groups(:,end)))';
otherwise
wmsg=sprintf('specification of offset coordinate choices (%s) not recognized; all coordinates offset',x.set_offsets_coordchoices);
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
off_choices{1}=[1:x.dim_select];
end
end
%convert off_choices to [0,1] selection
x.offsets_select=zeros(ngroups,x.dim_select); %will have 1's to select coordinates with offsets
for kptr=1:ngroups
k=1+mod(kptr-1,length(off_choices));
if (any(off_choices{k}<1) | any(off_choices{k}>x.dim_select) | any(off_choices{k}~=round(off_choices{k})))
wmsg=sprintf(' specification of offset coordinate choices non-integer or out of range ([1 %1.0f], all coordinates offset',x.dim_select);
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
off_choices{k}=[1:x.dim_select];
end
x.offsets_select(kptr,intersect(off_choices{k},x.coord_groups(kptr,:)))=1;
end
else
x.offsets_select=ones(ngroups,x.dim_select);
end
%
%set up axis scale
switch x.axis_range %check that it is 'tight','auto', or pairs of values
case {'tight','auto'}
case 'list'
if (~isnumeric(x.axis_range_list) | size(x.axis_range_list,2)~=2)
wmsg=sprintf('axis scale list must have two columns; tight scaling used');
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
x.axis_range='tight';
x.axis_range_list=[NaN NaN];
end
otherwise
wmsg=sprintf('axis scale specifier (%s) not recognized, tight scaling used',x.axis_range);
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
x.axis_range='tight';
x.axis_range_list=[NaN NaN];
end
%set up data show and label params
[x.data_show_list,wmsg]=rs_disp_parse_listmethod(x.data_show_method,[1:nstims],x.data_show_list,'specification of data points to show');
if ~isempty(wmsg)
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
[x.data_label_list,wmsg]=rs_disp_parse_listmethod(x.data_label_method,[x.data_show_list],x.data_label_list,'specification of data points to label');
if ~isempty(wmsg)
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
[x.data_label_setsel_list,wmsg]=rs_disp_parse_listmethod(x.data_label_setsel_method,x.set_select,x.data_label_setsel_list,'specification of sets to label');
if ~isempty(wmsg)
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
%set up params for connecting points within sets
[x.connect_data_list,wmsg]=rs_disp_parse_pairmethod(x.connect_data_method,x.data_show_list,x.connect_data_list,'specification of data points to connect within a set');
if ~isempty(wmsg)
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
if ~isempty(x.connect_data_list) %convert a set of pairs to a set of chains to simplify plotting
x.connect_data_chains=pairs2chains(x.connect_data_list);
else
x.connect_data_chains=[];
end
%set up params for connecting points across sets
[x.connect_sets_list,wmsg]=rs_disp_parse_pairmethod(x.connect_sets_method,x.set_select,x.connect_sets_list,'specification of sets to connect');
if ~isempty(wmsg)
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
if strcmp(x.connect_sets_data_method,'labeled')
x.connect_sets_data_method=x.data_label_method;
x.connect_sets_data_list=x.data_label_list;
end
[x.connect_sets_data_list,wmsg]=rs_disp_parse_listmethod(x.connect_sets_data_method,[x.data_show_list],x.connect_sets_data_list,'specification of data points to connect between sets');
if ~isempty(wmsg)
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
%
%ensure that certain options are cell in a column, or cell array of triplets
for imc=1:length(make_cell)
mc=make_cell{imc};
if ~isempty(strfind(mc,'color'))
x.(mc)=rs_disp_colorfix(x.(mc));
end
end
%set up connection colors
if size(x.connect_sets_list,1)>0
connect_sets_list_mod=mod(x.connect_sets_list-1,length(x.set_colors))+1;
switch x.connect_sets_color_mode
%connect_sets_list_mod=mod(x.connect_sets_list-1,length(x.set_colors))+1;
case 'first'
x.connect_sets_colors=x.set_colors(connect_sets_list_mod(:,1));
case 'last'
x.connect_sets_colors=x.set_colors(connect_sets_list_mod(:,2));
case 'split'
x.connect_sets_colors=[x.set_colors(connect_sets_list_mod(:,1)),x.set_colors(connect_sets_list_mod(:,2))];
case 'list'
otherwise
wmsg=sprintf('connect color mode (%s) not recognized, connections ignored',x.connect_sets_method);
x.connect_sets_list=[];
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
end
else
x.connect_sets_list=[];
end
%set up labels
if ~isfield(x,'set_labels')
x.set_labels=cell(nsets,1);
for k=1:nsets
x.set_labels{k}=sprintf('set %1.0f',k);
end
end
if ~isfield(x,'set_tags')
x.set_tags=cell(nsets,1);
for k=1:nsets
x.set_tags{k}=sprintf('set %1.0f',k);
end
end
%ensure that certain options are cells
for imc=1:length(make_cell)
mc=make_cell{imc};
if ~iscell(x.(mc))
x.(mc)={x.(mc)};
end
end
%set_labels and set_tags cannot be empty
for k=1:length(x.set_labels)
if isempty(x.set_labels{k})
x.set_labels{k}=sprintf('set %1.0f',k);
end
end
for k=1:length(x.set_tags)
if isempty(x.set_tags{k})
x.set_tags{k}=sprintf('set %1.0f',k);
end
end
%use set colors for callout colors if requested
if strmatch(x.callout_colors{1},'set_colors')
x.callout_colors=x.set_colors;
end
%
naxis_handles=length(x.axis_handles);
if naxis_handles>0 & naxis_handles~=ngroups_aug
wmsg=sprintf('number of axes (subplots) supplied (%3.0f) does not match number of axes needed (%3.0f)',naxis_handles,ngroups_aug);
aux_out=rs_warning(wmsg,double(naxis_handles<ngroups_aug),setfield(aux_out,'if_warn',x.if_warn));
end
%
if aux_out.warn_bad==0
%set up styles for each component: dataset, connections between data in a set, callouts, connections between sets, perpendiculars from data
set_styles=struct;
set_styles.colors=x.set_colors;
set_styles.markers=x.set_markers;
set_styles.markersizes=x.set_markersizes;
set_styles.linestyles={'none'};
set_styles.linewidths=1;
set_styles.colors_filled=x.set_colors_filled;
set_styles.filled=x.set_filled;
set_styles.alphas=x.set_alphas;
%
connect_data_styles=struct;
connect_data_styles.colors=x.set_colors;
connect_data_styles.markers={'none'};
connect_data_styles.markersizes=8;
connect_data_styles.linestyles=x.connect_data_linestyles;
connect_data_styles.linewidths=x.connect_data_linewidths;
%
callout_styles=struct;
callout_styles.colors=x.callout_colors;
callout_styles.markers={'none'};
callout_styles.markersizes=8;
callout_styles.linestyles=x.callout_linestyles;
callout_styles.linewidths=x.callout_linewidths;
%
connect_set_styles=struct;
connect_set_styles.colors={'k'};
connect_set_styles.markers={'none'};
connect_set_styles.markersizes=8;
connect_set_styles.linestyles=x.connect_sets_linestyles;
connect_set_styles.linewidths=x.connect_sets_linewidths;
%
if isempty(x.fig_handle)
x.fig_handle=figure;
else
if x.fig_handle~=gcf
figure(x.fig_handle);
end
end
set(gcf,'Position',x.fig_position);
set(gcf,'NumberTitle','off');
set(gcf,'Name',x.fig_name);
if naxis_handles==0
fig_posit=get(gcf,'Position');
[nrows,ncols]=nicesubp(ngroups_aug,fig_posit(4)/fig_posit(3)); %find an arrangement of rows and columns that fits the aspect ratio
for igp=1:ngroups_aug
x.axis_handles{igp}=subplot(nrows,ncols,igp);
end
end
callout_center_ok=0;
if isnumeric(x.callout_center)
if length(x.callout_center)==x.dim_select
callout_center_ok=1;
end
else
switch x.callout_center
case 'centroid'
callout_center_ok=1;
case 'zero'
callout_center_ok=1;
end
end
if callout_center_ok==0
wmsg='callout_center specification not recognized, zero used';
aux_out=rs_warning(wmsg,0,setfield(aux_out,'if_warn',x.if_warn));
x.callout_center='zero';
end
%
for igp_aug=1:ngroups_aug
haxis=x.axis_handles{igp_aug};
igp=mod(igp_aug-1,ngroups)+1; %if igp_aug=ngroup+1 (if_legend=-1) then igp=1 but it is plotted in a new subplot
subplot(haxis);
set(gca,'FontSize',x.axis_font_size);
cg=x.coord_groups(igp,:);
%plot points
for isetptr=1:length(x.set_select)
%
[z,z_all,k]=rs_disp_coordsets_tuples(data_in,isetptr,x,igp); %get the tuples of all the points and the plotted ponts
%
[hline,plotstyle_used,opts_plotstyle_used]=rs_disp_doplot(z,k,set_styles);
disp_msgs=strvcat(disp_msgs,opts_plotstyle_used.msgs);
set(hline,'Tag',cat(2,x.set_tags{1+mod(k-1,length(x.set_tags))},' ',datapoint_tag)); %so that we can use it to drop perpendiculars when finalized
set(hline,'DisplayName',x.set_labels{1+mod(k-1,length(x.set_labels))});
%connect requested points
if ~isempty(x.connect_data_chains)
for ichain=1:length(x.connect_data_chains)
[hline,plotstyle_used,opts_plotstyle_used]=rs_disp_doplot(z_all(x.connect_data_chains{ichain},:),k,connect_data_styles);
disp_msgs=strvcat(disp_msgs,opts_plotstyle_used.msgs);
set(hline,'Tag',sprintf('ds %2.0f chain %2.0f',k,ichain));
set(hline,'DisplayName',x.set_labels{1+mod(k-1,length(x.set_labels))});
end
end
%label?
if ismember(k,x.data_label_setsel_list)
zcallout=z;
if x.callout_amount>0 %compute position of call-out for every data point shown
callout_center=zeros(1,x.coord_group_size);
if isnumeric(x.callout_center)
callout_center=x.callout_center(1,cg);
else
switch x.callout_center
case 'centroid'
callout_center=mean(z_all,1,'omitnan');
end
end
dists=sqrt(sum((z_all-repmat(callout_center,size(z_all,1),1)).^2,2,'omitnan')); %distances from callout_center
rmsdist=sqrt(mean(dists.^2)); %rms distances from callout_center
dists(dists==0)=1;
for lab=1:size(x.data_show_list,1)
zcallout(lab,:)=callout_center+(zcallout(lab,:)-callout_center)*(1+x.callout_amount*rmsdist/dists(x.data_show_list(lab)));
end
end
typenames=data_in.sas{k}.typenames;
for ipt_ptr=1:length(x.data_label_list)
ipt=x.data_label_list(ipt_ptr);
ipt_rel=find(x.data_show_list==ipt); %which of the selected points is being labelled
switch x.coord_group_size
case 2
ht=text(zcallout(ipt_rel,1),zcallout(ipt_rel,2),typenames{ipt});
case 3
ht=text(zcallout(ipt_rel,1),zcallout(ipt_rel,2),zcallout(ipt_rel,3),typenames{ipt});
end %coord group size
set(ht,'FontSize',x.data_label_font_size);
if ~isempty(x.data_label_interpreter)
set(ht,'Interpreter',x.data_label_interpreter);
end
if x.callout_amount>0
[hcallout,plotstyle_used,opts_plotstyle_used]=rs_disp_doplot(cat(1,z(ipt_rel,:),zcallout(ipt_rel,:)),k,callout_styles);
disp_msgs=strvcat(disp_msgs,opts_plotstyle_used.msgs);
end
end %ipt
end %set to label?
end %isetptr
%set up view and axis labels
n_axis_labels=length(x.axis_labels);
ax_labels=cell(1,x.coord_group_size);
for ix=1:x.coord_group_size
if isempty(x.axis_labels) | n_axis_labels==0;
ax_labels{ix}=sprintf('%s %1.0f',x.axis_label_prefix,cg(ix));
else
ax_labels{ix}=x.axis_labels{1+mod(ix-1,n_axis_labels)};
end
end
if x.if_finalize
if x.coord_group_size<=3
hl=xlabel(ax_labels{1});
set(hl,'FontSize',x.axis_label_font_size);
hl=ylabel(ax_labels{2});
set(hl,'FontSize',x.axis_label_font_size);
end
if (x.coord_group_size==3)
hl=zlabel(ax_labels{3});
set(hl,'FontSize',x.axis_label_font_size);
axis vis3d;
index_view=1+mod(igp-1,length(x.axis_view));
view(x.axis_view{index_view});
end
end
%connections between sets
for ic=1:size(x.connect_sets_list,1)
cset=x.connect_sets_list(ic,:);
if all(ismember(cset,x.set_select))
data_connect_list=intersect(x.data_show_list,x.connect_sets_data_list); %find the points that are shown and also those selected for inter-set connection
endpoints=zeros(length(data_connect_list),x.coord_group_size,2);
for iz=1:2
endpoints(:,:,iz)=data_in.ds{cset(iz)}{x.dim_select}(data_connect_list,cg);
ko=mod(cset(iz)-1,size(x.set_offsets,1))+1;
endpoints(:,:,iz)=endpoints(:,:,iz)+repmat(x.set_offsets(ko,cg).*x.offsets_select(igp,cg),size(endpoints,1),1,1); %add the offset
end
midpoints=mean(endpoints,3);
if ~strcmp(x.connect_sets_color_mode,'split')
connect_set_styles.colors=x.connect_sets_colors;
for istim=1:length(data_connect_list)
[hconnect,plotstyle_used,opts_plotstyle_used]=rs_disp_doplot([endpoints(istim,:,1);endpoints(istim,:,2)],ic,connect_set_styles);
disp_msgs=strvcat(disp_msgs,opts_plotstyle_used.msgs);
end
else
for istim=1:length(data_connect_list)
for iseg=1:2
[hconnect,plotstyle_used,opts_plotstyle_used]=rs_disp_doplot([endpoints(istim,:,iseg);midpoints(istim,:)],ic,setfield(connect_set_styles,'colors',x.connect_sets_colors(:,iseg)));
disp_msgs=strvcat(disp_msgs,opts_plotstyle_used.msgs);
end %each segment
end %each stimulus
end %split or not
end %both sets are plotted
end %each connection pair
%box, view, axis scaling, legend
if x.if_finalize
if (x.coord_group_size==3)
if x.if_box
box on;
else
box off;
end
end
if x.if_grid
grid on;
else
grid off;
end
if x.axis_equal
axis equal;
else
axis normal;
end
switch x.axis_range
case 'tight'
axis tight;
case 'auto'
axis auto;
case 'list'
for ic=1:x.coord_group_size
set(gca,xyzlim{ic},x.axis_range_list(1+mod(x.coord_groups(igp,ic)-1,size(x.axis_range_list,1)),:));
end
end
%perpendiculars: need to be plotted after axis range is established
if (x.coord_group_size==3)
for ic=1:x.coord_group_size
if_perp=x.perp_data_dims(1+mod(cg(ic)-1,length(x.perp_data_dims)));
axis_lims=get(gca,xyzlim{ic});
if if_perp~=0
hc=get(gca,'Children'); %look for data points via tags
z_foot=axis_lims((3-if_perp)/2); %foot of perp is min if if_perp=1, max if if_perp=-1
for ichild=1:length(hc)
if contains(hc(ichild).Tag,datapoint_tag)
xyz_data=[hc(ichild).XData;hc(ichild).YData;hc(ichild).ZData]';
%
perp_data_style=struct;
perp_data_style.linewidths=x.perp_data_linewidths;
%color taken from data
perp_data_style.colors={hc(ichild).Color};
%marker optionally used
perp_data_style.markersizes=hc(ichild).MarkerSize;
if x.perp_data_usemarkers
perp_data_style.markers={hc(ichild).Marker};
else
perp_data_style.markers={'none'};
end
if strcmp(x.perp_data_linestyles{1},'use_data')
perp_data_style.linestyles={hc(ichild).LineStyle};
else
perp_data_style.linestyles=x.perp_data_linestyles;
end
for iperp=1:size(xyz_data,1)
z_to_perp=repmat(xyz_data(iperp,:),2,1);
z_to_perp(2,ic)=z_foot;
[hperp,plotstyle_used,opts_plotstyle_used]=rs_disp_doplot(z_to_perp,k,perp_data_style);
end %each point
end %each line with data
end
end %if_perp
end %coordinate
end
%legend
if (x.if_legend==1)
need_legend=1;
elseif (x.if_legend==-1)
need_legend=(igp_aug==ngroups_aug);
else
need_legend=0;
end
if need_legend
hc=get(haxis,'Children');
tags_all=cell(length(hc),1);
for ich=1:length(hc)
tags_all{ich}=get(hc(ich),'Tag');
end
hc_show=[];
for itag=1:length(x.legend_tags)
hc_show=union(hc_show,strmatch(x.legend_tags{itag},tags_all)); %select the objects whose tags start with x.legend_tags
end
if ~isempty(hc_show)
h_legend=legend(hc(flipud(hc_show)),'Location',x.legend_location,'FontSize',x.legend_font_size); %flipud since children appear to be added in reverse order
if ~isempty(x.legend_interpreter)
set(h_legend,'Interpreter',x.legend_interpreter);
end
end
end
end %if_finalize
end %igp_aug
end
%
aux_out=rs_warning(unique(disp_msgs,'rows'),0,setfield(aux_out,'if_warn',x.if_warn));
aux_out.opts_disp=x;
return
end