# v_xyzticksi

## PURPOSE

V_XYZTIXKSI labels an axis of a plot using SI multipliers S=(AX,AH)

## SYNOPSIS

function s=v_xyzticksi(ax,ah)

## DESCRIPTION

    V_XYZTIXKSI labels an axis of a plot using SI multipliers S=(AX,AH)

     This routine is not intended to be called directly. See XTICKSI and YTICKSI.

## CROSS-REFERENCE INFORMATION

This function calls:

This function is called by:

- v_xticksi V_XTIXKSI labels the x-axis of a plot using SI multipliers S=(AH)
- v_yticksi V_YTIXKSI labels the y-axis of a plot using SI multipliers S=(AH)

## SOURCE CODE

    0001 function s=v_xyzticksi(ax,ah)
    0002 %V_XYZTIXKSI labels an axis of a plot using SI multipliers S=(AX,AH)
    0003 %
    0004 % This routine is not intended to be called directly. See XTICKSI and YTICKSI.
    0005
    0006 % Revision History
    0007 %
    0008 % 2018-09-21       Initial version
    0009 % 2022-02-28       Fixed bug that could result in negative zero values as tick labels
    0010
    0011 %       Copyright (C) Mike Brookes 2009-2022
    0012 %      Version: $Id: v_xyzticksi.m 10865 2018-09-21 17:22:45Z dmb $
    0013 %
    0014 %   VOICEBOX is a MATLAB toolbox for speech processing.
    0015 %   Home page: http://www.ee.ic.ac.uk/hp/staff/dmb/voicebox/voicebox.html
    0016 %
    0017 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    0018 %   This program is free software; you can redistribute it and/or modify
    0019 %   it under the terms of the GNU Lesser General Public License as published by
    0020 %   the Free Software Foundation; either version 3 of the License, or
    0021 %   (at your option) any later version.
    0022 %
    0023 %   This program is distributed in the hope that it will be useful,
    0024 %   but WITHOUT ANY WARRANTY; without even the implied warranty of
    0025 %   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    0026 %   GNU Lesser General Public License for more details.
    0027 %
    0028 %   You can obtain a copy of the GNU Lesser General Public License from
    0029 %   https://www.gnu.org/licenses/ .
    0030 %
    0031 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    0032
    0033 % Bugs:
    0034 %   (1) ipan=3 or 4 is not really debugged yet:
    0035 %   (2) axis lengths incorrect for 3D graphs
    0036 %   (3) should take account of axis shortening due to long labels at the ends
    0037 %   (4) should calculate axis orentation from CameraPosition, CameraTarget and CameraUpVector
    0038 %   (5) should allow a scale factor or, better, a mapping function converting from plot to axis coordinates
    0039 if nargin<2
    0040     ah=gca;
    0041     if nargin<1
    0042         ax=1;
    0043     end
    0044 end
    0045 axfield={'XLim' 'YLim' 'ZLim'; 'XTick' 'YTick' 'ZTick'; 'XMinorTick' 'YMinorTick' 'ZMinorTick'; 'XTickLabel' 'YTickLabel' 'ZTickLabel'; 'XScale' 'YScale' 'ZScale'};
    0046 tryglobal=nargout>0;
    0047 digith=1;    % height of a digit in font units
    0048 digitw=0.5;    % width of a digit in font units
    0049 prefix={'y','z','a','f','p','n','μ','m','','k','M','G','T','P','E','Z','Y'};
    0050 marg=[2 0.5 0.25 0.25];     % gap between labels in font units
    0051 ntreq=[3 2 2 1];        % minimum number of labelled ticks required as a function of IPAN
    0052 % grid template: each pair is [#steps final-value]. Start=10, end=100
    0053 lgridtem={1; [1 20 1 50 1]; [1 20 4]; 9; [2 20 8]; [5 20 2 30 7]; [10 20 5 30 4 50 5]};
    0054 ngrid=length(lgridtem);
    0055 lgrid=cell(ngrid,1);
    0056 agrid=zeros(ngrid,1);       % min and max ratio in decades
    0057 % build the actual grid layouts
    0058 for i=1:ngrid
    0059     igridtem=[lgridtem{i} 100];
    0060     igrid=zeros(1,sum(igridtem(1:2:end)));
    0061     ntem=length(igridtem)/2;
    0062     k=0;
    0063     tick0=10;
    0064     for j=1:ntem
    0065         nstep=igridtem(2*j-1);
    0066         igrid(k+1:k+nstep)=tick0+(0:nstep-1)*(igridtem(2*j)-tick0)/igridtem(2*j-1);
    0067         k=k+nstep;
    0068         tick0=igridtem(2*j);
    0069     end
    0070     agrid(i)=sum(log10([igrid(2:end) 100]./igrid).^2); % average log interval
    0071     lgrid{i}=igrid';                 % grid positions
    0072 end
    0073 minsubsp=1;        % minimum subtick spacing (in font units)
    0074 delcheck=[log10(2) log10(5) 2];     % check linear spacing
    0075 delval=[1 2 5];
    0076 dosubtick=0;         % default is not to allow subticks
    0077
    0078 ngrid=length(lgrid);
    0079 loggrid=cell(ngrid,1);
    0080 for i=1:ngrid
    0081     loggrid{i}=log10(lgrid{i})-1;
    0082 end
    0083
    0084 getgca=get(ah);
    0085 set(ah,'Units','points','FontUnits','points');
    0086 getgcac=get(ah);
    0087 set(ah,'Units',getgca.Units,'FontUnits',getgca.FontUnits); % return to original values
    0088 if ax==1
    0089     widthc=getgcac.Position(3)/getgcac.FontSize;     % x axis length in font units
    0090     axdir=[1 0];        % unit vector in axis direction
    0091 else
    0092     widthc=2*getgcac.Position(4)/getgcac.FontSize;     % y axis length in font units
    0093     axdir=[0 1];        % unit vector in axis direction
    0094 end
    0095 axdir=max(abs(axdir),1e-10);        % avoid infinity problems
    0096 a=getgca.(axfield{1,ax})(1);
    0097 b=getgca.(axfield{1,ax})(2);
    0098
    0099 ntick=0;
    0100 tickint=[];                 % integer label
    0101 tickdp=[];                  % tick decimal point position
    0102 ticksi=[];                  % tick SI multiplier (always a multiple of 3)
    0103 subtick=[];                 % sub tick positions
    0104 if strcmp(getgca.(axfield{5,ax}),'log')   % log axis
    0105     width10=widthc/log10(b/a); % fount units per decade
    0106     ai3=3*ceil(log10(a)/3);             % lowest SI multiplier
    0107     bi3=3*floor(log10(b)/3);            % highest SI multiplier
    0108     if ai3>=-24 && bi3<=24              % do nothing if outside the SI multiplier range
    0109         % first sort out if we can use a global SI multiplier
    0110         if tryglobal && a>=10^(bi3-1)
    0111             gi=bi3;
    0112             s=prefix{9+gi/3};
    0113             globalsi=1;                % global multiplier
    0114         else
    0115             gi=0;
    0116             globalsi=0;                % disable global multiplier
    0117             s='';
    0118         end
    0119         g=10^gi;
    0120         ag=a/g;
    0121         bg=b/g;
    0122         al=log10(ag);
    0123         bl=log10(bg);
    0124         ai=ceil(al);
    0125         bi=floor(bl);
    0126         ai3=3*ceil(ai/3);
    0127         bi3=3*floor(bi/3);
    0128         for ipan=1:4                    % panic level: 1=spacious, 2=cramped, 3=any two labels, 4=any label
    0129             % first try labelling only exact SI multiples
    0130             margin=marg(ipan);
    0131             incsi=3*ceil(min((2*digitw+margin)/axdir(1),(digith+margin)/axdir(2))/(3*width10));   % SI increment
    0132             switch ipan
    0133                 case {1,2}
    0134                     ticksi=incsi*ceil(ai/incsi):incsi:incsi*floor(bi/incsi);
    0135                 case {3,4}
    0136                     ticksi=ai3:incsi:bi3;
    0137             end
    0138             ntick=length(ticksi);
    0139             tickint=ones(1,ntick);
    0140             tickdp=zeros(1,ntick);
    0141             if width10>0.25
    0142                 ticki=ai:bi;
    0143                 subtick=10.^(ticki(ticki~=3*fix(ticki/3)));      % put subticks at all powers of 10;
    0144             end
    0145             if incsi==3         % no point in trying anything else if incsi>3
    0146                 ci=floor(al);   % start of first decade that includes the start of the axis
    0147                 cibi=ci:bi;     % ennumerate the decades that cover the entire axis
    0148                 ndec=bi-ci+1;   % number of decades
    0149                 if globalsi
    0150                     siq0=zeros(1,ndec); % no SI multipliers within the labels if using a global multiplier
    0151                 else
    0152                     siq0=3*floor((cibi)/3); % determine the SI multiplier for each of the decades
    0153                 end
    0154                 siw0=siq0~=0;                % width of SI multiplier
    0155                 dpq0=max(siq0-cibi+1,1);    % number of decimal places
    0156                 for jgrid=1:ngrid
    0157                     igrid=jgrid-(ipan<=2)*(2*jgrid-ngrid-1);
    0158                     lgridi=lgrid{igrid};
    0159                     ngridi=length(lgridi);
    0160                     intq=reshape(repmat(lgridi,1,ndec).*repmat(10.^(cibi+dpq0-siq0-1),ngridi,1),1,[]);
    0161                     dpq=reshape(repmat(dpq0,ngridi,1),1,[]);
    0162                     msk=dpq>0 & rem(intq,10)==0;
    0163                     intq(msk)=intq(msk)/10;
    0164                     dpq(msk)=dpq(msk)-1;
    0165                     widq=1+floor(log10(intq));
    0166                     widq=digitw*(widq+(dpq>0).*max(1,dpq+2-widq)+reshape(repmat(siw0,ngridi,1),1,[]));
    0167                     logvq=reshape(repmat(loggrid{igrid},1,ndec)+repmat(ci:ndec+ci-1,ngridi,1),1,[]);
    0168                     % mask out any ticks outside the axis range
    0169                     msk=logvq>=al & logvq<=bl;
    0170                     widq=widq(msk);
    0171                     logvq=logvq(msk);
    0172                     % debug1=[intq(msk); -1 min((widq(1:end-1)+widq(2:end)+2*margin)/axdir(1),2*(digith+margin)/axdir(2))<=2*width10*(logvq(2:end)-logvq(1:end-1))];
    0173                     if numel(widq)>=ntreq(ipan) && all(min((widq(1:end-1)+widq(2:end)+2*margin)/axdir(1),2*(digith+margin)/axdir(2))<=2*width10*(logvq(2:end)-logvq(1:end-1)))
    0174                         % success: we have an acceptable pattern
    0175                         ntick=numel(widq);       % number of ticks
    0176                         tickint=intq(msk);      % integer label value (i.e. neglecting decimal point)
    0177                         tickdp=dpq(msk);        % number of decimal places
    0178                         siq=reshape(repmat(siq0,ngridi,1),1,[]);    % SI mltiplier power
    0179                         ticksi=siq(msk);        % SI multiplier power masked
    0180                         subtick=[];             % recalculate any subticks
    0181                         dosubtick=igrid>1;
    0182                         break;                  % do not try any more grid patterns
    0183                     end
    0184                 end % alternative grid patterns
    0185                 % finally just try a linear increment
    0186                 if ntick<5
    0187                     ldeltamin=log10(bg- bg*10^(-min((digitw+margin)/axdir(1),(digith+margin)/axdir(2))/width10));  % smallest conceivable increment
    0188                     ildelta=floor(ldeltamin);
    0189                     ix=find(ldeltamin-ildelta<=delcheck,1);
    0190                     jx=ildelta*3+ix;
    0191                     while 1
    0192                         deltax=floor(jx/3);
    0193                         deltav=delval(jx-3*deltax+1);
    0194                         delta=deltav*10^deltax;
    0195                         multq=ceil(ag/delta):floor(bg/delta);   % multiples of delta to display
    0196                         ntickq=numel(multq);
    0197                         if ntickq<=ntick || ntickq<ntreq(ipan)   % give up now
    0198                             break;
    0199                         end
    0200                         intq=deltav*multq;
    0201                         lintq=floor(log10(intq));
    0202                         siq=3*floor((lintq+deltax)/3);      % SI multiplier
    0203                         dpq=siq-deltax;
    0204                         msk=dpq<0;
    0205                         intq(msk)=intq(msk).*10.^(-dpq(msk));
    0206                         dpq(msk)=0;
    0207                         msk=rem(intq,10)==0 & dpq>0;
    0208                         while any(msk)      % remove unwanted trailing zeros
    0209                             dpq(msk)=dpq(msk)-1;
    0210                             intq(msk)=intq(msk)/10;
    0211                             msk=rem(intq,10)==0 & dpq>0;
    0212                         end
    0213                         widq=1+floor(log10(intq));
    0214                         widq=digitw*(widq+(dpq>0).*max(1,dpq+2-widq)+(siq~=0));
    0215                         logvq=log10(multq)+log10(deltav)+deltax;
    0216                         % debug2=[intq; widq; -1 (widq(1:end-1)+widq(2:end)+2*margin)<=2*width10*(logvq(2:end)-logvq(1:end-1))];
    0217                         if all(min((widq(1:end-1)+widq(2:end)+2*margin)/axdir(1),2*(digith+margin)/axdir(2))<=2*width10*(logvq(2:end)-logvq(1:end-1)))
    0218                             ntick=ntickq;
    0219                             tickint=intq;
    0220                             tickdp=dpq;
    0221                             ticksi=siq;
    0222                             dosubtick=1;
    0223                             break
    0224                         end
    0225                         jx=jx+1;                            % try next coarser spacing
    0226                     end
    0227                 end
    0228             end % try grid patterns since at most one exact SI multiple
    0229             if ntick>=ntreq(ipan)
    0230                 break% quit if we have enough labels
    0231             end
    0232         end% try next panic level
    0233     end    % check if within SI range
    0234     if ntick
    0235         tickint(tickint==0)=0; % get rid of negative zeros
    0236         tickexp=gi+ticksi-tickdp;
    0237         tickpos=tickint .* 10.^tickexp;
    0238         ratthresh=10^(minsubsp/width10);   % min subtick ratio
    0239         if dosubtick       % check for subticks
    0240             subtick=[];
    0241             if ntick>1      % at least two labelled ticks
    0242                 stepexp=min(tickexp(1:end-1),tickexp(2:end))-1;
    0243                 stepint=round((tickpos(2:end)-tickpos(1:end-1)).*10.^(-stepexp));  % always a multiple of 10
    0244                 stepleft=tickint(1:end-1).*10.^(tickexp(1:end-1)-stepexp); % leftmost label in units of 10^stepexp
    0245                 subbase=10.^ceil(log10(stepint)-1); % base sub-tick interval in units of 10^stepexp
    0246                 substep=[-1 -3 5]*((1+[1; 2; 5]*(subbase./stepleft))>ratthresh); % actual step is 1,2 or 5 times subbase
    0247                 substep(stepint~=10*substep)=max(2-substep(stepint~=10*substep),0); % but only >1 if stepint==10
    0248                 substep=substep.*subbase; % subtick step in units of 10^stepexp
    0249                 for i=1:ntick-1
    0250                     ss=substep(i);
    0251                     sl=stepleft(i);
    0252                     if ss
    0253                         subtick=[subtick (sl+(ss:ss:stepint(i)-ss))*10^stepexp(i)];
    0254                         if i==1 && sl/(sl-ss)>ratthresh
    0255                             subtick=[subtick (sl-(ss:ss:floor((tickpos(1)-a)/(ss*10^stepexp(i)))*ss))*10^stepexp(i)];
    0256                         elseif i==ntick-1 && (1+ss/(sl+stepint(1)))>ratthresh
    0257                             subtick=[subtick (sl+stepint(i)+(ss:ss:floor((b-tickpos(end))/(ss*10^stepexp(i)))*ss))*10^stepexp(i)];
    0258                         end
    0259                     end
    0260                 end
    0261             end
    0262         end % if subtick
    0263         [tps,ix]=sort([tickpos subtick]);
    0264         nticks=length(tps);
    0265         ticklab=cell(nticks,1);
    0266         for j=1:nticks
    0267             i=ix(j);
    0268             if i>ntick
    0269                 ticklab{j}='';
    0270             else
    0271                 ticklab{j}=sprintf(sprintf('%%.%df%%s',tickdp(i)),tickint(i)*10^(-tickdp(i)),prefix{ticksi(i)/3+9});
    0272             end
    0273         end
    0274         if width10<2.5
    0275             set(ah,axfield{3,ax},'off');
    0276         end
    0277         set(ah,axfield{2,ax},tps);
    0278         set(ah,axfield{4,ax},ticklab);
    0279     end
    0280
    0281 else                    % linear axis
    0282     for ipan=1:4                    % panic level: 1=spacious, 2=cramped, 3=any two labels, 4=any label
    0283         margin=marg(ipan);
    0284         % select a global multiplier
    0285         if tryglobal
    0286             gi=3*floor(log10(max(abs(a),abs(b)))/3);
    0287             s=prefix{9+gi/3};
    0288
    0289         else
    0290             gi=0;
    0291             s='';
    0292         end
    0293         g=10^gi;
    0294         ag=a/g;
    0295         bg=b/g;
    0296         width1=widthc/(bg-ag);                  % width of 1 plot unit in font units
    0297         ldeltamin=log10(min((digitw+margin)/axdir(1),(digith+margin)/axdir(2))/width1);        % log of smallest conceivable increment
    0298         ildelta=floor(ldeltamin);
    0299         ix=find(ldeltamin-ildelta<=delcheck,1);
    0300         jx=ildelta*3+ix;
    0301         while 1 % loop trying increasingly coarse labelling
    0302             deltax=floor(jx/3);         % exponent of label increment
    0303             deltav=delval(jx-3*deltax+1); % mantissa of label increment
    0304             delta=deltav*10^deltax;         % actual label inrement
    0305             multq=ceil(ag/delta):floor(bg/delta);   % multiples of delta to display
    0306             ntickq=numel(multq);
    0307             if ntickq<ntreq(ipan)   % give up now if too few labels
    0308                 break;
    0309             end
    0310             intq=deltav*multq;
    0311             lintq=floor(log10(abs(intq)+(intq==0)));
    0312             siq=3*floor((lintq+deltax)/3)*~tryglobal;      % SI multiplier, but only if no global multiplier
    0313             dpq=siq-deltax;
    0314             msk=dpq<0;
    0315             intq(msk)=intq(msk).*10.^(-dpq(msk));
    0316             dpq(msk)=0;
    0317             msk=rem(intq,10)==0 & dpq>0;
    0318             while any(msk)      % remove unwanted trailing zeros
    0319                 dpq(msk)=dpq(msk)-1;
    0320                 intq(msk)=intq(msk)/10;
    0321                 msk=rem(intq,10)==0 & dpq>0;
    0322             end
    0323             widq=1+floor(log10(abs(intq)+(intq==0)));
    0324             widq=digitw*(widq+(dpq>0).*max(1,dpq+2-widq)+(siq~=0).*(intq~=0)+(intq<0)); % calculate width of each label
    0325             % debug2=[intq; widq; digith+margin<=axdir(2)*width1*delta (widq(1:end-1)+widq(2:end)+2*margin)<=2*width1*delta];
    0326             if all((widq(1:end-1)+widq(2:end)+2*margin)<=2*axdir(1)*width1*delta) || (digith+margin<=axdir(2)*width1*delta);
    0327                 ntick=ntickq;
    0328                 tickint=intq;
    0329                 tickdp=dpq;
    0330                 ticksi=siq;
    0331                 if deltav>1 && width1*delta>0.5*deltav          % put explicit subticks if delta = 2 or 5
    0332                     mults=ceil(ag*deltav/delta):floor(bg*deltav/delta);
    0333                     subtick=(mults(deltav*fix(mults/deltav)~=mults))*delta/deltav;
    0334                 else
    0335                     subtick=[];
    0336                 end
    0337                 break                       % do not try any more coarser spacings
    0338             end
    0339             jx=jx+1;                            % try next coarser spacing
    0340         end
    0341         if ntick>=ntreq(ipan)
    0342             break% quit if we have enough labels
    0343         end
    0344     end
    0345     if ntick
    0346         tickint(tickint==0)=0; % get rid of negative zeros
    0347         tickexp=gi+ticksi-tickdp;
    0348         tickpos=tickint .* 10.^tickexp;
    0349         [tps,ix]=sort([tickpos subtick*10^gi]);
    0350         nticks=length(tps);
    0351         ticklab=cell(nticks,1);
    0352         for j=1:nticks
    0353             i=ix(j);
    0354             if i>ntick
    0355                 ticklab{j}='';
    0356             else
    0357                 if tickint(i)==0
    0358                     ticklab{j}=sprintf(sprintf('%%.%df%%s',tickdp(i)),0,prefix{(ticksi(i)/3)*(tickint(i)~=0)+9});
    0359                 else
    0360                     ticklab{j}=sprintf(sprintf('%%.%df%%s',tickdp(i)),tickint(i)*10^(-tickdp(i)),prefix{(ticksi(i)/3)*(tickint(i)~=0)+9});
    0361                 end
    0362             end
    0363         end
    0364         set(ah,axfield{2,ax},tps);
    0365         set(ah,axfield{4,ax},ticklab);
    0366         set(ah,axfield{3,ax},'on');
    0367     end
    0368 end