%PLOTE Plot error curves
%
%   H = PLOTE(E,LINEWIDTH,S,FONTSIZE,OPTIONS,'Location',LOCATION,)
%   H = E*PLOTE(LINEWIDTH,S,FONTSIZE,OPTIONS,'Location',LOCATION,)
%
% 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
%   LOCATION   Legend location, default "Best";
%
% 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>)
% MAPPINGS, 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)

  % RD, this block has been added to facilitate E*PLOTE
  if  (nargin == 0) || (~isstruct(varargin{1}) && ~iscell(varargin{1}))
    varargin = [{[]} varargin];
  end
  if isempty(varargin{1})
    handle = define_mapping(varargin,'fixed');
    return
  end

	% Set default parameter values.

	e         = [];
	s         = [];
  names = [];
	linewidth = 2;
	nolegend  = 0;
	fontsize  = 16;
	errbar    =  0;
	noapperror = 0;
  location   = 'Best';
	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...

  j = 1;
	while j <= nargin
		p = varargin{j};
		if (isstruct(p)) || iscell(p)
			e = p;
		elseif (ischar(p)) && (strcmpi(p,'errorbar'))
			errbar = 1;
		elseif (ischar(p)) && (strcmpi(p,'nolegend'))
			nolegend = 1;
		elseif (ischar(p)) && (strcmpi(p,'noapperror'))
			noapperror = 1;
    elseif (ischar(p)) && (strcmpi(p,'legend'))
     names = varargin{j+1}; j=j+1;
    elseif (ischar(p)) && (strcmpi(p,'location'))
     location = varargin{j+1}; j=j+1;
		elseif (ischar(p))
			ss = p;
		elseif (length(p) == 1) && (p < 5)
			linewidth = p;
		elseif (length(p) == 1) && (p >= 5)
			fontsize = p;
    end
    j = j+1;
  end
	
  hold_stat = ishold;
	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
    if isempty(names)
      names = cell(1,numel(e));
    end
		%hold_stat = ishold;
		h = [];
    xmax = 0;
    xmin = inf;
    errmax = 0;
    for j = 1:numel(e)
			if errbar && isfield(e,'std')
        if noapperror
					hh = plote(e(j),linewidth,ss(j,:),'nolegend','errorbar','noapperror');
				else
					hh = plote(e(j),linewidth,char(ss(j,:),ss_app(j,:)),'nolegend','errorbar');
        end
      else
        if noapperror
					hh = plote(e(j),linewidth,ss(j,:),fontsize,'nolegend','noapperror');
				else
					hh = plote(e(j),linewidth,char(ss(j,:),ss_app(j,:)),fontsize,'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
      if isempty(names{j}) 
        if ~isfield(e(j),'names') || isempty(e(j).names)
          names{j} = ['curve ' num2str(j)];
        else
          names{j} = e(j).names;
        end
      end
			names{j} = correct_(names{j});
			h = [h(:); hh(:)];
      xmin   = min([xmin,e(j).xvalues(:)']);
      xmax   = max([xmax,e(j).xvalues(:)']);
      errmax = max([errmax,e(j).error(:)']);
    end
		%names(1,:) = [];
%     V = axis;
% 		V(4) = ymax;
%     V(2) = xmax;
% 		axis(V);
		if ~nolegend
			legend(h,correct_(names),'Location',location);
		end
		%if ~hold_stat, hold off; end
		if nargout > 0, handle = h; end
  else

    % 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
    if ~isempty(names)
      e.names = names;
    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)) % s is always empty
      if n == 1 && size(ss,1) == 2
        % special case, ss_app stored in ss(2,:)
        ss_app = ss(2,:);
        ss = ss(1,:);
      elseif 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;
      cla;  % neessary in case of subplots
    end
    h = []; ha = []; % handles for true and apparent error
    for j = 1:n
      %L = find(e.error(j,:) ~= NaN);
      L = find(~isnan(e.error(j,:)));
      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
        if ploterrorbar
          hh = feval('errorbar',e.xvalues(j,L),e.apperror(j,L),e.appstd(j,L),deblank(s_app(j,:)));
        else
          hh = feval(e.plot,e.xvalues(j,L),e.apperror(j,L),deblank(s_app(j,:)));
        end
        set(hh,'linewidth',linewidth); hold on; ha = [ha hh(end)];
      end
    end

    % That was basically it, now we only have to beautify it.
    xmin = min(e.xvalues(:));
    xmax = max(e.xvalues(:));
    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),'Location',location);
  % 		end  
    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
  try
    if (xmax >= 2)
      %DXD
      %axis([e.xvalues(1)-1,e.xvalues(end)+1,0,errmax]);
      %RD
      %axis([min(min(e(1).xvalues)),max(max(e(1).xvalues)),0,errmax]);
      axis([xmin,xmax,0,errmax]);
    elseif (xmin == 0)
      axis([-0.003,xmax,0,errmax]);
    else
      axis([xmin,xmax,0,errmax]);
    end
  end
		
	set(gca,'ytick',yticks);
  if ~hold_stat, hold off; end
	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 iscell(s)
    for j=1:numel(s)
      s{j} = correct_(s{j});
    end
    return
  end
  
  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
