%PLOTE Plot error curves
%
%   H = PLOTE(E,LINEWIDTH,S,FONTSIZE,OPTIONS)
%
% INPUT
%   E          Structure containing error curves (see e.g. CLEVAL)
%   LINEWIDTH  Line width, < 5 (default 2)
%   S          Plot strings
%   FONTSIZE   Font size, >= 5 (default 16)
%   OPTIONS    Character strings:
%              'nolegend' suppresses the legend plot
%              'errorbar' add errorbars to the plot
%              'noapperror' suppresses the apparent error plot
%
% OUTPUT
%   H          Array of graphics handles
%
% DESCRIPTION
% Various evaluation routines like CLEVAL return an error curves packed in a
% structure E. PLOTE uses the information stored in E to plot the curves. The 
% remaining parameters may be given in an arbitrary order.
%
% E may contain the following fields (E.ERROR is obligatory):
%   E.ERROR    C x N matrix of error values for C methods at N points
%              (typically errors estimated on an independent test set)
%   E.XVALUES  C x N matrix of measurement points; if 1 x N, it is used for 
%                all C curves
%   E.TITLE    the title of the plot
%   E.XLABEL   the label for the x-axis
%   E.YLABEL   the label for the y-axis
%   E.NAMES    a string array of C names used for creating a LEGEND
%   E.PLOT     the plot command in a string: 'plot', 'semilogx', 'semilogy' 
%                or 'loglog'
%   E.STD      C x N matrix with standard deviations of the mean error values
%                which are plotted if ERRBAR == 1
%                Note that this is the st. dev. in the estimate of the mean
%                and not the std. dev. of the error itself.
%   E.APPERROR C x N matrix of error values for C methods at N points
%              (typically errors estimated on the training set)
%   E.APPSTD   C x N matrix with standard deviations of the mean
%              APPERROR values which are plotted if ERRBAR == 1
%
% These fields are automatically set by a series of commands like CLEVAL,
% CLEVALF, PRROC and REJECT.
%
% The legend generated by PLOTE can be removed by LEGEND OFF. A new legend
% may be created by the LEGEND command using the handles stored in H.
%
% E may be a cell array of structures. These structures are combined
% vertically, assuming multiple runs of the same method and
% horizontally, assuming different methods.
%
% EXAMPLES
% See PREX_CLEVAL
%
% SEE ALSO (<a href="http://37steps.com/prtools">PRTools Guide</a>)
% CLEVAL, CLEVALF, PRROC, REJECT

% Copyright: R.P.W. Duin, r.p.w.duin@37steps.com
% Faculty EWI, Delft University of Technology
% P.O. Box 5031, 2600 GA Delft, The Netherlands

% $Id: plote.m,v 1.5 2009/11/26 10:45:43 davidt Exp $

function handle = plote(varargin)

		
	% Set default parameter values.

	e         = [];
	s         = [];
	linewidth = 2;
	nolegend  = 0;
	fontsize  = 16;
	errbar    =  1;
	noapperror = 0;
	ss = char('k-','r-','b-','m-','k--','r--','b--','m--');
	ss = char(ss,'k-.','r-.','b-.','m-.','k:','r:','b:','m:');
	ss_app = char('k--','r--','b--','m--','k:','r:','b:','m:');
	ss_app = char(ss_app,'k--','r--','b--','m--','k-','r-','b-','m-');

	% The input is so flexible, that we have to do a lot of work...

	for j = 1:nargin
		p = varargin{j};
		if (isstruct(p)) | iscell(p)
			e = p;
		elseif (isstr(p)) & (strcmp(p,'errorbar') | strcmp(p,'ERRORBAR'))
			errbar = 1;
		elseif (isstr(p)) & (strcmp(p,'nolegend') | strcmp(p,'NOLEGEND'))
			nolegend = 1;
		elseif (isstr(p)) & (strcmp(p,'noapperror') | strcmp(p,'NOAPPERROR'))
			noapperror = 1;
		elseif (isstr(p))
			ss = p;
		elseif (length(p) == 1) & (p < 5)
			linewidth = p;
		elseif (length(p) == 1) & (p >= 5)
			fontsize = p;
		end
	end
	
	if iscell(e) 
		if min(size(e)) > 1
			ee = cell(1,size(e,2));
			for j=1:size(e,2)
				ee{j} = vertcomb(e(:,j));
			end
			e = horzcomb(ee);
		elseif size(e,1) > 1
      for j=1:size(e,1)
        if j>1, figure; end
        plote(e{j});
      end
      showfigs
      return
			%e = vertcomb(e);
		elseif size(e,2) > 1
			e = horzcomb(e);
		else
			e = e{1};
		end
	end
	
	% Handle multiple plots
	
	if length(e) > 1
		names = [];
		hold_stat = ishold;
		h = [];
		ymax = 0;
    xmax = 0;
		for j = 1:length(e)
			if errbar & isfield(e,'std')
				if noapperror
					hh = plote(e(j),linewidth,ss(j,:),'nolegend','errorbar','noapperror');
				else
					hh = plote(e(j),linewidth,ss(j,:),'nolegend','errorbar');
				end
			else
				if noapperror
					hh = plote(e(j),linewidth,ss(j,:),'nolegend','noapperror');
				else
					hh = plote(e(j),linewidth,ss(j,:),'nolegend');
				end
			end
			V = axis; 
      ymax = max(ymax,V(4));
      xmax = max(xmax,V(2));
			hold on
      if ~isfield(e(j),'names')
        e(j).names = ' ';
      end
			names = correct_(char(names,e(j).names));
			h = [h(:); hh(:)];
		end
		names(1,:) = [];
		V(4) = ymax;
    V(2) = xmax;
		axis(V);
		if ~nolegend
			legend(h,correct_(names),0);
		end
		if ~hold_stat, hold off; end
		if nargout > 0, handle = h; end
		return
	end

	% Check if we have the required data and data fields.

	if (isempty(e))
		error('Error structure not specified.')
	end

	if (~isfield(e,'error'))
		error('Input structure should contain the ''error''-field.');
	end

	n = size(e.error,1);

	if (~isfield(e,'xvalues'))
		e.xvalues = [1:length(e.error)];
	end
	if (size(e.xvalues,1) == 1)
		e.xvalues = repmat(e.xvalues,n,1);
	end

	if (isempty(s))
		if n > size(ss,1)
			nn = ceil(n/size(ss,1));
			ss = repmat(ss,nn,1);
			ss_app = repmat(ss_app,nn,1);
		end
		s = ss(1:n,:);
		s_app = ss_app(1:n,:);
	end
	if (size(s,1) == 1) & (n > 1)
		s = repmat(s,n,1);
		s_app = repmat(s_app,n,1);
	end
	if (size(s,1) < n)
		error('Insufficient number of plot strings.')
	end

	if (~isfield(e,'plot'))
		e.plot = 'plot';
	end

	if errbar & isfield(e,'std')
		ploterrorbar = 1;
	else
		ploterrorbar = 0;
	end

	plotapperror = (~noapperror && isfield(e,'apperror'));
	
	% We can now start making the plot.

	if ~ishold
		clf;
	end
	h = []; ha = []; % handles for true and apparent error
	for j = 1:n
		L = find(e.error(j,:) ~= NaN);
		if ploterrorbar
			hh = feval('errorbar',e.xvalues(j,L),e.error(j,L),e.std(j,L),deblank(s(j,:)));
		else
			hh = feval(e.plot,e.xvalues(j,L),e.error(j,L),deblank(s(j,:)));
		end
		set(hh,'linewidth',linewidth); hold on; h = [h hh(end)];

		% and the apparent error
		if plotapperror
			hh = errorbar(e.xvalues(j,L),e.apperror(j,L),e.appstd(j,L),...
			deblank(s_app(j,:)));
			ha = [ha hh(end)];
		end
	end
		
	% That was basically it, now we only have to beautify it.
	errmax = max(e.error(:));
	set(gca,'fontsize',fontsize);

	if (isfield(e,'xlabel')), xlabel(correct_(e.xlabel)); end
	if (isfield(e,'ylabel')), ylabel(correct_(e.ylabel)); end
	if (isfield(e,'title')),  title(correct_(e.title));   end
	if (isfield(e,'names')) & (~isempty(e.names) & (~nolegend))
		if plotapperror % take care for the legend in this case
			nrn = size(e.names,1);
      e.names = correct_(e.names);
			names = {};
			for j=1:nrn
				names{j} = ['\epsilon ' e.names(j,:)];
				%names{j} = ['true error ' e.names(j,:)];
			end
			for j=1:nrn
				names{nrn+j} = ['\epsilon_A ' e.names(j,:)];
				%names{nrn+j} = ['apparent error ' e.names(j,:)];
			end
			legend([h ha],strvcat(names),0);
		else
			legend(h,correct_(e.names),0);
		end  
	end

	% A lot of work to make the scaling of the y-axis nice.

	if (errmax > 0.6)
		errmax = ceil(errmax*5)/5;
		yticks = [0:0.2:errmax];
	elseif (errmax > 0.3)
		errmax = ceil(errmax*10)/10;
		yticks = [0:0.1:errmax];
	elseif (errmax > 0.2)
		errmax = ceil(errmax*20)/20;
		yticks = [0:0.05:errmax];
	elseif (errmax > 0.1)
		errmax = ceil(errmax*30)/30;
		yticks = [0:0.03:errmax];
	elseif (errmax > 0.06)
		errmax = ceil(errmax*50)/50;
		yticks = [0:0.02:errmax];
	elseif (errmax > 0.03)
		errmax = ceil(errmax*100)/100;
		yticks = [0:0.01:errmax];
	else
		yticks = [0:errmax/3:errmax];
	end

		% atttempt to beautify plot
	if (e.xvalues(end) >= 2)
		%DXD
		%axis([e.xvalues(1)-1,e.xvalues(end)+1,0,errmax]);
		axis([min(min(e.xvalues)),max(max(e.xvalues)),0,errmax]);
	elseif (e.xvalues(1) == 0)
		axis([-0.003,e.xvalues(end),0,errmax]);
	end
		
	set(gca,'ytick',yticks);
	
	hold off; if (nargout > 0), handle = h; end;
	
	return
	
function e = vertcomb(e) % combine cell array
	e1 = e{1};
	for j=2:length(e);
		e2 = e{j};
		v = e1.n*(e1.n*(e1.std.^2) + e1.error.^2);
		v = v + e2.n*(e2.n*(e2.std.^2) + e2.error.^2);
		n = e1.n + e2.n;
		e1.error = (e1.n*e1.error + e2.n*e2.error)/n;
		e1.std = sqrt((v/n - e1.error.^2)/n);
		e1.n = n;
	end
	e = e1;
return

function ee = horzcomb(e) % combine cell array
  isx = false;
  isy = false;
  isd = false;
  for j=1:length(e)
    isx = (isx || strcmp(e{j}.plot,'semilogx'));
    isy = (isy || strcmp(e{j}.plot,'semilogy'));
    isd = (isd || strcmp(e{j}.plot,'loglog'));
  end
  if isx && isy, isd = true; end
	for j=1:length(e)
    if isd
      e{j}.plot = 'loglog';
    elseif isx
      e{j}.plot = 'semilogx';
    elseif isy
      e{j}.plot = 'semilogy';
    else
      e{j}.plot = 'plot'; 
    end
		ee(j) = e{j};
  end
  
function s = correct_(s) %correct string for underscores

  if size(s,1) > 1
    ss = cell(1,size(s,1));
    for j=1:size(s,1)
      ss{j} = correct_(s(j,:));
    end
    s = char(ss);
    return
  end

  J = find(s=='_');
  if ~isempty(J) 
    J = fliplr(J(:)');
    for j=1:length(J)
      if s(J(j)-1) ~= '\'
        s = [s(1:J(j)-1) '\' s(J(j):end)];
      end
    end
  end

return
