function displaytable(data, colheadings, wid, fms, rowheadings, fid, colsep, rowending) % Prints formatted matrix of numerical data with headings % % Syntax % % displaytable(data, colheadings, wid, fms, rowheadings, fid, colsep, rowending) % % Input % % data: a matrix or cell array, containing the data to be put in the table. % If a matrix the numbers in the table will be printed using the default % format specifier (f), or the specifier(s) supplied in fms. data can also % be a cell array containing a mixture of strings and numbers. each cell in % this case can contain only a single scalar value. In this case numbers % are printed as in the matrix case, while strings are printed up to the % maximum width of the column. % % colheadings: a cell array of strings for the headings of each column. Can % be an empty cell array if no headings are required. If no column widths % are supplied (see below), the columns will be made wide enough to % accomdate the headings. % % wid: (optional) scalar or vector of column widths to use for the table. % If scalar, every column will have the same width. If a vector it must be % of the same length as the number of columns of data. If not supplied, and % column headers are supplied, the width of the widest column header will % be used. If not supplied and column headers are not supplied, a default % with of 16 characters is used. % % fms: (optional) a string, or cell array of strings containing format % specifiers for formatting the numerical output in each column. If a % single string, the same specifier is used for every column. If not % supplied, the 'g' specifier is used for every column. % % rowheadings: (optional) a cell array of strings for the start of each % row. Can be an empty cell array if no row headings are required. If row % headings are supplied, the first column will be made wide enough to % accomodate all the headings. % % fid: (optional) the file id to print to. Use 1 for stdout (to print to % the command line). % % colsep: (optional) A string or character to insert between every column. % The default separation string is ' | ', i.e. a space followed by a % vertical bar, followed by a space. A table suitible for inclusion in a % LaTeX document can be created using the ' & ' string, for example. % % rowending: (optional) An optional string or character to be appended at % the end of every row. Default is an empty string. % % Example 1 - Basic useage % % colheadings = {'number of projects','sales','profit'}; % rowheadings = {'Jimmy Slick', 'Norman Noob'} % data = [3 rand(1) rand(1); 1 rand(1) rand(1)]; % % To format the first number in each row as a decimal (%d), the second % number %16.4f, and the third as %16.5E do the following: % % wid = 16; % fms = {'d','.4f','.5E'}; % % In this case 16 will be the field width, and '.5E' is what to use for the % fms argument % % fileID = 1; % % >> displaytable(data,colheadings,wid,fms,rowheadings,fileID); % |number of projec | sales | profit % Jimmy Slick | 3 | 0.4502 | 5.22908E-001 % Norman Noob | 1 | 0.9972 | 2.78606E-002 % % Example 2 - Produce a latex table % % colheadings = {'number of projects','sales','profit'}; % rowheadings = {'Jimmy Slick', 'Norman Noob'}; % data = [3 rand(1) rand(1); 1 rand(1) rand(1)]; % wid = 16; % fms = {'d'}; % % colsep = ' & '; % rowending = ' \\'; % % fileID = 1; % % >> displaytable(data,colheadings,wid,fms,rowheadings,fileID,colsep,rowending); % % & number of projec & sales & profit \\ % Jimmy Slick & 3 & 6.948286e-001 & 3.170995e-001 \\ % Norman Noob & 1 & 9.502220e-001 & 3.444608e-002 \\ % % Example 3 - Mixed numeric and strings % % colheadings = {'number of projects','sales','profit'}; % rowheadings = {'Jimmy Slick', 'Norman Noob'}; % % data = {3, rand(1), rand(1); % 1, 'none', 0}; % % wid = 16; % fms = {'d','.4f','.5E'}; % % fileID = 1; % % >> displaytable(data,colheadings,wid,fms,rowheadings,fileID); % | number of projec | sales | profit % Jimmy Slick | 3 | 0.4854 | 8.00280E-001 % Norman Noob | 1 | none | 0.00000E+000 % % See Also: DISP, FPRINTF % Created by Richard Crozier 2012 if nargin < 8 || isempty(rowending) rowending = ''; end if nargin < 7 || isempty(colsep) colsep = ' | '; end % do some basic checking of input if nargin < 6 || isempty(fid) % print to the command line fid = 1; end if nargin < 5 || isempty(rowheadings) % no row headings supplied, use empty cell array rowheadings = {}; end if nargin < 4 || isempty(fms) % no format specifiers supplied, use 'g' for all columns fms = 'g'; end if nargin < 3 || isempty(wid) % default width is 10, this will be modified if column headers are % supplied wid = 10; end if nargin < 2 colheadings = {}; end if nargin >= 6 && ~iscellstr(rowheadings) error ('row headings must be cell array of strings'); end % get the numbers of rows and columns of data [nRowD,nColD] = size(data); % check that sensible format specifiers have been supplied if ~iscellstr(fms) if ischar(fms) fms = repmat({fms},1,nColD); else error('fms must be a string or cell array of strings.'); end elseif isempty(fms) fms = repmat({'f'},1,nColD); elseif numel(fms) ~= nColD if numel(fms) == 1 fms = repmat(fms,1,nColD); else error('fms does not have the same number of format specifiers as there are columns.'); end end % replace empty format specifiers with 'f' for i = 1:numel(fms) if isempty(fms{i}) fms{i} = 'f'; end end [nRowFms,nColFms] = size(fms); if(nRowFms>1) error ('fms can not have more than one row'); end if ~isempty(rowheadings) if ~iscellstr(rowheadings) error('rowheadings must be a cell array of strings'); end if numel(rowheadings) ~= size(data, 1) error('Rowheadings must be a cell array of strings of the same size as the number of rows in data.') end rhwid = 0; for r = 1:numel(rowheadings) % find the maximum width the first column must be to accomodate % the row headings rhwid = max(rhwid, length(rowheadings{r})); end end if isscalar(wid) tempwid = zeros(1, numel(fms)); for i = 1:numel(fms) % get the number of decimal places specified [start_idx, end_idx, extents, matches] = regexp(fms{i}, '(?<=\.)\d+'); if isempty(start_idx) % no numbers were found in the format spec, just use the % supplied width tempwid(i) = wid; else % some numbers were supplied, use the larger of the width % or the number of desired decimal places plus two (to % allow for leading number plus decimal point) tempwid(i) = max(wid, round(str2double(matches{1})) + 2); end end % replace scalar width with array of width values wid = tempwid; end if ~isempty(colheadings) [nRowCH,nColCH] = size(colheadings); if(nRowCH>1) error ('column headings can not have more than one row'); end if ~iscellstr(colheadings) error('If not empty, colheadings must be a cell array of strings'); end if(nColCH ~= nColD) error ('data must have same number of columns as headings'); end % fmt = arrayfun(@(x) ['%',num2str(wid(x)),'s |'], 1:nColD, 'UniformOutput', false); if ~isempty(rowheadings) % TODO allow extra heading for column fprintf(fid, ['%s',colsep], repmat(' ', 1,rhwid)); end if nargin < 3 % only data and column headings have been supplied, so % determine a sensible value for the width of each column tempwid = zeros(size(wid)); for i = 1:numel(colheadings) % get a column width which is the minimum to accept the % column heading length or the default width specification tempwid(i) = max(length(colheadings{i}), wid(i)); if tempwid < 1 error('Column width is less than 1, and the column header is empty.') end end wid = tempwid; end % Now loop through the column headings printing them out with the % desired column separator for i = 1:numel(colheadings) str = sprintf(['%',num2str(wid(i)),'s'],colheadings{i}); if i == numel(colheadings) % If we are at the end of a row, don't print the column % separator fprintf(fid, '%s', str(1:wid(i)) ); else % print the column header and column separator fprintf(fid, ['%s',colsep], str(1:wid(i)) ); end end fprintf(fid, '%s\n', rowending); end fmt = arrayfun(@(x) ['%',num2str(wid(x)),fms{x}],1:nColD,'UniformOutput',false); for i = 1:size(data,1) % first print a row header if necessary if ~isempty(rowheadings) fprintf(fid, ['%',num2str(rhwid),'s',colsep], rowheadings{i}); end % now loop through the data formatting and printing as appropriate for j = 1:size(data,2) if iscell(data) % data is a cell array, possibly containing mixed data % types if ischar(data{i,j}) str = sprintf(['%',num2str(wid(j)),'s'],data{i,j}); elseif isscalar(data{i,j}) % write the number str = sprintf(fmt{j},data{i,j}); if length(str) > wid(j) % convert to scientific notation as it doesn't fit str = sprintf(['%',num2str(wid(j)),'g'],data{i,j}); if length(str) > wid(j) % fill space with #s as the number stil doesn't % fit in str = repmat('#', 1, wid(j)); end end elseif isempty(data{i,j}) % indicate an empty value str = sprintf(['%',num2str(wid(j)),'s'],''); else % we can only tabulate strings and scalars, so throw an % error error('CROZIER:displaytable:badcellcontents', ... 'each cell in cell array data must contain only individual strings or scalar values.') end % print the string fprintf(fid, '%s', str(1:wid(j))); if j < size(data,2) % print column separator fprintf(fid, colsep); end else % data is a numerical matrix str = sprintf(fmt{j},data(i,j)); if length(str) > wid(j) % convert to scientific notation as it doesn't fit str = sprintf(['%',num2str(wid(j)),'g'],data(i,j)); if length(str) > wid(j) % fill space with #s as the number doesn't fit in str = repmat('#', 1, wid(j)); end end if j == size(data,2) % do not print the last column separator at the end of % a row fprintf(fid, '%s', str(1:wid(j))); else fprintf(fid, ['%s',colsep], str(1:wid(j))); end end end % end of line so put in a new row fprintf(fid, '%s\n', rowending); end end