function [cfg] = ft_connectivityplot(cfg, varargin) % FT_CONNECTIVITYPLOT plots channel-level frequency resolved connectivity. The % data are rendered in a square grid of subplots, each subplot containing the % connectivity spectrum between the two respective channels. % % Use as % ft_connectivityplot(cfg, data) % where the first input argument is a configuration structure (see below) % and the input data is a structure obtained from FT_CONNECTIVITYANALYSIS % using a frequency-domain connectivity metric. Consequently the input data % should have a dimord of 'chan_chan_freq', or 'chan_chan_freq_time'. % % The configuration can have the following options % cfg.parameter = string, the functional parameter to be plotted (default = 'cohspctrm') % cfg.xlim = 'maxmin', 'maxabs', 'zeromax', 'minzero', or [xmin xmax] (default = 'maxmin') % cfg.ylim = 'maxmin', 'maxabs', 'zeromax', 'minzero', or [ymin ymax] (default = 'maxmin') % cfg.zlim = plotting limits for color dimension, 'maxmin', 'maxabs', 'zeromax', 'minzero', or [zmin zmax] (default = 'maxmin') % cfg.channel = list of channels to be included for the plotting (default = 'all'), see FT_CHANNELSELECTION for details % % See also FT_CONNECTIVITYANALYSIS, FT_CONNECTIVITYSIMULATION, FT_MULTIPLOTCC, FT_TOPOPLOTCC % Copyright (C) 2011-2017, Jan-Mathijs Schoffelen % % This file is part of FieldTrip, see http://www.fieldtriptoolbox.org % for the documentation and details. % % FieldTrip is free software: you can redistribute it and/or modify % it under the terms of the GNU General Public License as published by % the Free Software Foundation, either version 3 of the License, or % (at your option) any later version. % % FieldTrip is distributed in the hope that it will be useful, % but WITHOUT ANY WARRANTY; without even the implied warranty of % MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the % GNU General Public License for more details. % % You should have received a copy of the GNU General Public License % along with FieldTrip. If not, see . % % $Id$ % these are used by the ft_preamble/ft_postamble function and scripts ft_revision = '$Id$'; ft_nargin = nargin; ft_nargout = nargout; % do the general setup of the function ft_defaults ft_preamble init ft_preamble debug ft_preamble loadvar varargin ft_preamble provenance varargin % the ft_abort variable is set to true or false in ft_preamble_init if ft_abort return end % check if the input cfg is valid for this function cfg = ft_checkconfig(cfg, 'forbidden', {'channels'}); % prevent accidental typos, see issue 1729 cfg = ft_checkconfig(cfg, 'renamed', {'zparam', 'parameter'}); cfg = ft_checkconfig(cfg, 'renamed', {'color', 'linecolor'}); cfg = ft_checkconfig(cfg, 'renamed', {'graphcolor', 'linecolor'}); % set the defaults cfg.channel = ft_getopt(cfg, 'channel', 'all'); cfg.parameter = ft_getopt(cfg, 'parameter', 'cohspctrm'); cfg.zlim = ft_getopt(cfg, 'zlim', 'maxmin'); cfg.ylim = ft_getopt(cfg, 'ylim', 'maxmin'); cfg.xlim = ft_getopt(cfg, 'xlim', 'maxmin'); cfg.linecolor = ft_getopt(cfg, 'linecolor', 'brgkywrgbkywrgbkywrgbkyw'); cfg.linestyle = ft_getopt(cfg, 'linestyle', '-'); cfg.linewidth = ft_getopt(cfg, 'linewidth', 0.5); % check if the input data is valid for this function % ensure that the input is correct Ndata = numel(varargin); dtype = cell(Ndata, 1); iname = cell(Ndata+1, 1); for k = 1:Ndata if ischar(cfg.parameter) cfg.parameter = repmat({cfg.parameter}, [1 Ndata]); end % check whether all requested parameters are the same. If not, rename % this, because otherwise a call to ft_selectdata (below) won't work if ~all(strcmp(cfg.parameter,cfg.parameter{1})) fprintf('different types of connectivity are to be displayed in the same figure\n'); varargin{k}.connectivity = varargin{k}.(cfg.parameter{k}); varargin{k} = rmfield(varargin{k}, cfg.parameter{k}); if isfield(varargin{k}, [cfg.parameter{k} 'dimord']) varargin{k}.connectivitydimord = varargin{k}.([cfg.parameter{k} 'dimord']); varargin{k} = rmfield(varargin{k}, [cfg.parameter{k} 'dimord']); end cfg.parameter{k} = 'connectivity'; else % don't worry end % check if the input data is valid for this function varargin{k} = ft_checkdata(varargin{k}, 'datatype', {'timelock', 'freq'}); dtype{k} = ft_datatype(varargin{k}); % convert into the the supported dimord switch varargin{k}.dimord case {'chan_chan_freq' 'chan_chan_freq_time'} % that's ok case {'chancmb_freq' 'chancmb_freq_time'} % convert into 'chan_chan_freq' varargin{k} = ft_checkdata(varargin{k}, 'cmbstyle', 'full'); otherwise ft_error('the data should have a dimord of %s or %s', 'chan_chan_freq', 'chancmb_freq'); end % this is needed for correct treatment of linecolor later on if nargin>1 if ~isempty(inputname(k+1)) iname{k+1} = inputname(k+1); else iname{k+1} = ['input',num2str(k, '%02d')]; end else % not yet supported iname{k+1} = cfg.inputfile{k}; end end if Ndata >1 if ~all(strcmp(dtype{1}, dtype)) ft_error('input data are of different type; this is not supported'); end end if ischar(cfg.linecolor) % ensure it is a column vector of the right length cfg.linecolor = repmat(cfg.linecolor(:), ceil(Ndata/length(cfg.linecolor)), 1); cfg.linecolor = cfg.linecolor(1:Ndata); elseif isnumeric(cfg.linecolor) % ensure it is a Nx3 matrix of the right length cfg.linecolor = repmat(cfg.linecolor, ceil(Ndata/length(cfg.linecolor)), 1); cfg.linecolor = cfg.linecolor(1:Ndata,:); end % ensure that the data in all inputs has the same channels, time-axis, etc. tmpcfg = keepfields(cfg, {'channel', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'}); [varargin{:}] = ft_selectdata(tmpcfg, varargin{:}); % restore the provenance information [cfg, varargin{:}] = rollback_provenance(cfg, varargin{:}); % check presence of time / freq axes hasfreq = isfield(varargin{1}, 'freq'); hastime = isfield(varargin{1}, 'time'); if hasfreq && hastime if Ndata>1 ft_error('when the input data contains time-frequency representations, only a single data argument is allowed'); end xparam = 'time'; yparam = 'freq'; elseif hasfreq xparam = 'freq'; yparam = ''; elseif hastime xparam = 'time'; yparam = ''; end % Get physical min/max range of x, i.e. time if ~isnumeric(cfg.xlim) % Find maxmin throughout all varargins xmin = +inf; xmax = -inf; for i=1:Ndata xmin = nanmin([xmin varargin{i}.(xparam)]); xmax = nanmax([xmax varargin{i}.(xparam)]); end switch cfg.xlim case 'maxmin' % keep them as they are case 'maxabs' xmax = max(abs(xmax), abs(xmin)); xmin = -xmax; case 'zeromax' xmin = 0; case 'minzero' xmax = 0; otherwise ft_error('invalid specification of cfg.xlim'); end % switch else xmin = cfg.xlim(1); xmax = cfg.xlim(2); end cfg.xlim = [xmin xmax]; % Get physical min/max range of y, i.e. freq if isempty(yparam) ymin = []; ymax = []; elseif ~isnumeric(cfg.ylim) % Find maxmin throughout all varargins ymin = +inf; ymax = -inf; for i=1:Ndata ymin = nanmin([ymin varargin{i}.(yparam)]); ymax = nanmax([ymax varargin{i}.(yparam)]); end switch cfg.ylim case 'maxmin' % keep them as they are case 'maxabs' ymax = max(abs(ymax), abs(ymin)); ymin = -ymax; case 'zeromax' ymin = 0; case 'minzero' ymax = 0; otherwise ft_error('invalid specification of cfg.ylim'); end % switch else ymin = cfg.ylim(1); ymax = cfg.ylim(2); end cfg.ylim = [ymin ymax]; % Get physical min/max range of z, which is the functional data: if ~isnumeric(cfg.zlim) zmin = +inf; zmax = -inf; for k = 1:Ndata zmin = nanmin([zmin min(varargin{k}.(cfg.parameter{k})(:))]); zmax = nanmax([zmax max(varargin{k}.(cfg.parameter{k})(:))]); end switch cfg.zlim case 'maxmin' % keep them as they are case 'maxabs' zmax = max(abs(zmax), abs(zmin)); zmin = -zmax; case 'zeromax' zmin = 0; case 'minzero' zmax = 0; otherwise ft_error('invalid specification of cfg.ylim'); end % switch else zmin = cfg.zlim(1); zmax = cfg.zlim(2); end cfg.zlim = [zmin zmax]; % make the function recursive if Ndata>1 if Ndata>1 data = varargin{1}; tmpcfg = cfg; if ischar(cfg.parameter) % do nothing elseif iscell(cfg.parameter) tmpcfg.parameter = cfg.parameter{1}; end ft_connectivityplot(tmpcfg, data); tmpcfg = cfg; if ischar(cfg.linecolor), colorLabels = [iname{2} '=' tmpcfg.linecolor(1) '\n']; elseif isnumeric(cfg.linecolor), colorLabels = [iname{2} '=' num2str(tmpcfg.linecolor(1, :)) '\n']; end for k = 2:Ndata if ischar(cfg.linecolor), tmpcfg.linecolor = tmpcfg.linecolor(2:end); else isnumeric(cfg.linecolor),tmpcfg.linecolor = tmpcfg.linecolor(2:end,:); end tmpcfg.holdfig = 1; if ischar(cfg.parameter) % do nothing elseif iscell(cfg.parameter) tmpcfg.parameter = cfg.parameter{k}; end ft_connectivityplot(tmpcfg, varargin{k}); if ischar(cfg.linecolor); colorLabels = [colorLabels iname{k+1} '=' tmpcfg.linecolor(1) '\n']; elseif isnumeric(cfg.linecolor); colorLabels = [colorLabels iname{k+1} '=' num2str(tmpcfg.linecolor(1, :)) '\n']; end end ft_plot_text(0.5, (numel(varargin{k}.label)+1).*1.2-0.5, sprintf(colorLabels), 'horizontalalignment', 'right'); return; else data = varargin{1}; end if ~isfield(data, cfg.parameter{1}) ft_error('the data does not contain the requested parameter %s', cfg.parameter{1}); end % get the selection of the data tmpcfg = []; if hasfreq && hastime tmpcfg.latency = cfg.xlim; tmpcfg.frequency = cfg.ylim; elseif hasfreq tmpcfg.frequency = cfg.xlim; elseif hastime tmpcfg.latency = cfg.xlim; end data = ft_selectdata(tmpcfg, data); % restore the provenance information [cfg, data] = rollback_provenance(cfg, data); dat = data.(cfg.parameter{k}); nchan = numel(data.label); if hasfreq, nfreq = numel(data.freq); end if hastime, ntime = numel(data.time); end if (isfield(cfg, 'holdfig') && cfg.holdfig==0) || ~isfield(cfg, 'holdfig') cla; hold on; end for k = 1:nchan for m = 1:nchan if k~=m ix = k; iy = nchan - m + 1; % use the convention of the row-channel causing the column-channel if hastime && hasfreq && ntime>1 tmp = reshape(dat(m,k,:,:), [nfreq ntime]); ft_plot_matrix(tmp, 'width', 1, 'height', 1, 'hpos', ix.*1.2, 'vpos', iy.*1.2, 'clim', cfg.zlim, 'box', 'yes'); elseif hasfreq tmp = reshape(dat(m,k,:), [nfreq 1]); ft_plot_vector(tmp, 'width', 1, 'height', 1, 'hpos', ix.*1.2, 'vpos', iy.*1.2, 'vlim', cfg.zlim, 'box', 'yes', 'color', cfg.linecolor(1,:), 'linewidth', cfg.linewidth, 'style', cfg.linestyle); elseif hastime ft_error('plotting data with only a time axis is not supported yet'); end if k==1 % first column, plot scale on y axis if hastime && hasfreq && ntime>1 val1 = cfg.ylim(1); val2 = cfg.ylim(2); elseif hasfreq val1 = cfg.zlim(1); val2 = cfg.zlim(2); elseif hastime end fontsize = 10; ft_plot_text(ix.*1.2-0.5, iy.*1.2-0.5, num2str(val1,3), 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'Middle', 'FontSize', fontsize); ft_plot_text(ix.*1.2-0.5, iy.*1.2+0.5, num2str(val2,3), 'HorizontalAlignment', 'Right', 'VerticalAlignment', 'Middle', 'FontSize', fontsize); end if m==nchan % bottom row, plot scale on x axis fontsize = 10; ft_plot_text(ix.*1.2-0.5, iy.*1.2-0.5, num2str(cfg.xlim(1),3), 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'top', 'FontSize', fontsize); ft_plot_text(ix.*1.2+0.5, iy.*1.2-0.5, num2str(cfg.xlim(2),3), 'HorizontalAlignment', 'Center', 'VerticalAlignment', 'top', 'FontSize', fontsize); end end end end % add channel labels on grand X and Y axes for k = 1:nchan ft_plot_text(0.5, (nchan + 1 - k).*1.2, data.label{k}, 'horizontalalignment', 'right'); ft_plot_text(k.*1.2, (nchan + 1) .*1.2-0.5, data.label{k}, 'horizontalalignment', 'left', 'rotation', 90); end % add 'from' and 'to' labels ft_plot_text(-0.5, (nchan + 1)/1.7, '\it{from}', 'interpreter', 'tex', 'rotation', 90); ft_plot_text((nchan + 1)/1.7, (nchan + 1)*1.2+0.4, '\it{to}', 'interpreter', 'tex'); axis([-0.2 (nchan+1).*1.2+0.2 0 (nchan+1).*1.2+0.2]); axis off; set(gcf, 'color', [1 1 1]); % this is needed for the figure title if isfield(cfg, 'dataname') && ~isempty(cfg.dataname) dataname = cfg.dataname; elseif isfield(cfg, 'inputfile') && ~isempty(cfg.inputfile) dataname = cfg.inputfile; elseif nargin>1 dataname = arrayfun(@inputname, 2:nargin, 'UniformOutput', false); else dataname = {}; end % set the figure window title if ~isempty(dataname) set(gcf, 'Name', sprintf('%d: %s: %s', double(gcf), mfilename, join_str(', ', dataname))); else set(gcf, 'Name', sprintf('%d: %s', double(gcf), mfilename)); end set(gcf, 'NumberTitle', 'off'); % do the general cleanup and bookkeeping at the end of the function ft_postamble debug ft_postamble previous varargin ft_postamble provenance ft_postamble savefig % add a menu to the figure, but only if the current figure does not have subplots menu_fieldtrip(gcf, cfg, false); if ~ft_nargout % don't return anything clear cfg end