Code covered by the BSD License  

Highlights from
obliqueview

image thumbnail
from obliqueview by John Barber
Obliqueview - View an axes using an oblique projection

obliqueview(varargin)
function hOut = obliqueview(varargin)
% View an axes using an oblique projection
%
% SYNTAX
%
%   OBLIQUEVIEW
%   OBLIQUEVIEW(VPLANE)
%   OBLIQUEVIEW(VPLANE,THETA)
%   OBLIQUEVIEW(VPLANE,THETA,RATIO)
%   OBLIQUEVIEW(VPLANE,THETA,RATIO,OAXESLOCATION)
%   OBLIQUEVIEW off
%   OBLIQUEVIEW clear
%   OBLIQUEVIEW('off')
%   OBLIQUEVIEW('clear')
%   OBLIQUEVIEW(H,...)
%   HT = OBLIQUEVIEW(...)
%
% DESCRIPTION
%
% OBLIQUEVIEW   Transform the current axes to an oblique projection using
% the xy plane, an angle of 30 degrees and a ratio of 0.5.
%
% OBLIQUEVIEW(VPLANE)  Use the plane specified by VPLANE as the viewing
% plane for the oblique projection.  Valid viewing planes are 'xy', 'xz',
% 'yx', 'yz','zx', and 'zy'.
%
% OBLIQUEVIEW(VPLANE,THETA)  Use the angle THETA as the projection angle.
% THETA should be specified in degrees.  THETA is defined as increasing
% counterclockwise from the negative horizontal axis to the out-of-page
% axis.
%
% OBLIQUEVIEW(VPLANE,THETA,RATIO)  Use the value in RATIO to scale the
% projection in the receding axis.  The default value of 0.5 results in
% the 'cabinet' projection.  A value of 1.0 for RATIO results in the
% 'cavalier' projection.
%
% OBLIQUEVIEW(VPLANE,THETA,RATIO,OAXESLOCATION)  Use the value in
% OAXESLOCATION as the origin for an oaxes central axis.  Displaying an
% oaxes object requires the File Exchange submission 'oaxes', version 2.0
% or later.  If oaxes is not installed, or OAXESLOCATION is set to 'none',
% no oaxes will be displayed.
%
% In addition to 'none', valid values of OAXESLOCATION are 'BottomLeft'
% (the default), 'BottomRight', 'TopLeft', 'TopRight', 'Center', or a 1x3
% vector specifying an origin point.  OBLIQUEVIEW will place the oaxes
% origin at the position determined by the value of OAXESLOCATION, and
% determine the proper axes limits to display the plot data and the oaxes
% object.
%
% OBLIQUEVIEW off
% OBLIQUEVIEW clear
% OBLIQUEVIEW('off')
% OBLIQUEVIEW('clear')   Undo the oblique projection and restore the axes
% and plot objects to their prior state.
%
% OBLIQUEVIEW(H,...)   Use the axes specified by H instead of the current
% axes.
%
% HT = OBLIQUEVIEW(...)   Return the handle of the hgtransform in HT.
%
% Note: Passing the empty matrix, [], as an input will cause OBLIQUEVIEW to
% use the default value for that input parameter.
%
% REMARKS
%
% OBLIQUEVIEW creates an hgtransform object in the specified axes, places
% all plot objects into the hgtransform, and sets the transform's 'Matrix'
% property to a shear transformation matrix, resulting in an oblique 
% projection.
%
% The axes view is set to a 2D view, determined by the selected viewing
% plane.  Viewing the results of OBLIQUEVIEW from another viewing angle
% will give distorted results.
%
% OBLIQUEVIEW is designed to be used with oaxes, a central axis display.
% oaxes is available through the MATLAB File Exchange at
% http://www.mathworks.com/matlabcentral/fileexchange/30018 . If oaxes is
% not installed, OBLIQUEVIEW will still run, but with reduced
% functionality.
%
% The current implementation of OBLIQUEVIEW is static - adding new plot
% objects to the axes will result in a mixture of oblique and orthographic
% projections.  The workaround is to set any new plot objects' 'Parent'
% property to HT, the handle of the OBLIQUEVIEW hgtransform.  You may have
% to adjust axes limits and oaxes settings manually after adding new
% objects to the axes.
%
% REFERENCES
% 
% http://en.wikipedia.org/wiki/Oblique_projection
% http://en.wikipedia.org/wiki/Projection_(linear_algebra)
% http://en.wikipedia.org/wiki/Orthographic_projection
% 
% Download oaxes at:
% http://www.mathworks.com/matlabcentral/fileexchange/30018
%
% EXAMPLE
%
% Draw some boxes:
% figure
% view(20,20)
% grid on
% box on
% set(gca,'DataAspectRatio',[1 1 1])
% line(0,0,0,'marker','o','color','k','markerFaceColor','k')
% x = [0  1  1  0  0  0  1  1  0  0  1  1  1  1  0  0];
% y = [0  0  1  1  0  0  0  1  1  0  0  0  1  1  1  1];
% z = [0  0  0  0  0  1  1  1  1  1  1  0  0  1  1  0];
% line(x,y,z,'color','k','linewidth',1)
% line(x+1,y,z,'color','b')
% line(x,y+1,z,'color','g')
% line(x,y,z+1,'color','r')
% v = [1 0 0; 1 0 1; 1 1 1; 1 1 0];
% f = [1 2 3 4];
% patch('vertices',v,'faces',f,'facecolor',[0 0 .5],'facealpha',.3)
% v(:,1) = 2;
% patch('vertices',v,'faces',f,'facecolor',[0 0 .5],'facealpha',1)
% v(:,1) = 0;
% patch('vertices',v,'faces',f,'facecolor','b','facealpha',1)
% v = [0 1 0; 0 1 1; 1 1 1; 1 1 0];
% f = [1 2 3 4];
% patch('vertices',v,'faces',f,'facecolor',[0 .5 0],'facealpha',.3)
% v(:,2) = 2;
% patch('vertices',v,'faces',f,'facecolor',[0 .5 0],'facealpha',1)
% v(:,2) = 0;
% patch('vertices',v,'faces',f,'facecolor',[0 .5 0],'facealpha',1)
% v = [0 0 1; 0 1 1; 1 1 1; 1 0 1];
% f = [1 2 3 4];
% patch('vertices',v,'faces',f,'facecolor',[.5 0 0],'facealpha',.3)
% v(:,3) = 2;
% patch('vertices',v,'faces',f,'facecolor',[.5 0 0],'facealpha',1)        
% v(:,3) = 0;
% patch('vertices',v,'faces',f,'facecolor','r','facealpha',1)   
% 
% % Transform the axes to an oblique projection in the x-z plane, with an
% % angle of 40 degrees:
% obliqueview('xz',40)
% 
% % View in the x-y plane, with an angle of 150 degrees and a 
% % foreshortening ratio of 1:
% obliqueview('xy',150,1,'bottomright')
%
% See also AXES OAXES
%

% $$FileInfo
% $Filename: obliqueview.m
% $Path: $toolboxroot/
% $Product Name: obliqueview
% $Product Release: 1.0
% $Revision: 1.0.55
% $Toolbox Name: Custom Plots Toolbox
% $$
%
% Copyright (c) 2010-2011 John Barber.
%
% Release History:
% v 1.0 : 2011-Mar-29
%       - Initial release
%

%% Default values
vPlane = 'xy';
theta = 30;
ratio = 0.5;
O = [0 0 0];
oaxesLoc = 'BottomLeft';

vpList = {'xy','xz','yx','yz','zx','zy','off','clear'};
locList = {'none','BottomLeft','BottomRight','center','TopLeft',...
    'TopRight'};
%% Parse inputs

% Check for axes handle
if nargin > 0 && isscalar(varargin{1}) && ishandle(varargin{1}) 
    if strcmp(get(varargin{1},'Type'),'axes')
        hAx = varargin{1};
        varargin(1) = [];
    else
        % Error here
    end
else
    hAx = gca;
end

% Assign and validate inputs
nargs = length(varargin);

% Get view plane
if nargs > 0 && ~isempty(varargin{1})
    vPlane = lower(varargin{1});
    if ~any(strcmp(vPlane,vpList))
        eID = [mfilename ':InvalidVPlane'];
        commas = [repmat({', '},length(vpList)-1,1); '.'];
        list = [vpList' commas]';
        list = list(:)';
        eStr = ['Invalid argument.  VPlane must be one of: ' list{:}];
        error(eID,eStr)
    end
end

% Get theta
if nargs > 1 && ~isempty(varargin{2})
    theta = varargin{2};
    
    if ~isnumeric(theta) || ~isreal(theta) || ~isscalar(theta)
        eID = [mfilename ':InvalidTheta'];
        eStr = 'Theta must be a real scalar.';
        error(eID,eStr)
    end
    
    % Restrict theta to [0 360]
    while theta < 0
        theta = theta + 360;
    end
    while theta > 360
        theta  = theta - 360;
    end
end

% Get ratio
if nargs > 2 && ~isempty(varargin{3})
    ratio = varargin{3};
    if ~isnumeric(ratio) || ~isreal(ratio) || ~isscalar(ratio) || ...
            ratio <= 0
        eID = [mfilename ':InvalidRatio'];
        eStr = 'Ratio must be a real, positive scalar.';
        error(eID,eStr)
    end
end

% Get oaxes location
if nargs > 3
    oaxesLoc = varargin{4};
    if isnumeric(oaxesLoc)
        O = oaxesLoc;
        oaxesLoc = 'origin';
    else
        if ~any(strcmpi(oaxesLoc,locList))
            eID = [mfilename ':InvalidOAxesLocation'];
            commas = [repmat({', '},length(locList)-1,1); '.'];
            list = [locList' commas]';
            list = list(:)';
            eStr = ['OAxesLocation must be one of: ' list{:}];
            error(eID,eStr)
        end
    end
end

% List of axes and oaxes properties to set/restore
axesPropList = {'View';
                'CameraUpVector';
                'DataAspectRatio';
                'DataAspectRatioMode';
                'Projection';
                'XDir'; 'YDir'; 'ZDir';
                'XScale'; 'YScale'; 'ZScale';
                'XLim'; 'YLim'; 'ZLim';
                'XLimMode'; 'YLimMode'; 'ZLimMode'
                }';
oaxesPropList = {'Origin';
                 'OriginMode';
                 'Force3D';
                 'ListenersEnabled';
                 'HideParentAxes';
                 'HideParentAxesMode';
                 'TickOrientation';
                 'XLim'; 'YLim'; 'ZLim';
                 'XLimMode'; 'YLimMode'; 'ZLimMode';
                 'XTick'; 'YTick'; 'ZTick';
                 'XTickLabel'; 'YTickLabel'; 'ZTickLabel';
                 'XTickMode'; 'YTickMode'; 'ZTickMode';
                 'XTickLabelMode'; 'YTickLabelMode'; 'ZTickLabelMode';
                 }';
             
% Handle 'off'/'clear'
if any(strcmp(vPlane,{'off','clear'}))
    hT = findobj(hAx,'Tag','ObliqueView');
    if isempty(hT)
        return
    end
    set(get(hT,'Children'),'Parent',hAx)
    % Resore previous axes state
    if isappdata(hT,'AxesPreviousState')
        set(hAx,axesPropList,getappdata(hT,'AxesPreviousState'))
    end
    if isappdata(hT,'OAxesPreviousState')
        % Restore previous oaxes state
        OA = findobj(hAx,'Tag','oaxes','-and','Type','hggroup');
        if ~isempty(OA)
            % Turn off listeners
            enableState = get(OA,'ListenersEnabled');
            set(OA,'ListenersEnabled','off');
            
            % Restore oaxes state
            set(OA,oaxesPropList,getappdata(hT,'OAxesPreviousState'))
            
            % Restore listener state
            set(OA,'ListenersEnabled',enableState);
        end
    end
    delete(hT)
    return
end

% Check for oaxes existence, and get oaxes handle or create oaxes
if exist('oaxes.m','file') == 2 && ~any(strcmpi(oaxesLoc,{'none','off'}))
    hasOAxes = true;
    % Find existing oaxes or create a new one
    OA = oaxes(hAx);
    
    % Get current oaxes state to restore later
    OAState = get(OA,oaxesPropList);
    OAEnabled = get(OA,'ListenersEnabled');
    
    % Freeze the oaxes for now
    OA.freeze;
else
    hasOAxes = false;
    
    % If user requested oaxes, issue a warning
    if ~strcmpi(oaxesLoc,'none')
        wID = [mfilename ':OAxesNotFound'];
        wStr = 'Could not find oaxes.m - oaxes will not be drawn.';
        warning(wID,wStr)
    end
end

% Get settings based on vPlane
switch lower(vPlane)
    case 'xy'
        I = 1;
        J = 2;
        K = 3;
        IName = 'X';
        JName = 'Y';
        KName = 'Z';
        az = 0;
        el = 90;
        camUp = [0 1 0];
        sgn = -1;
        tickOrientation = {'zxx','yxy'};
    case 'xz'
        I = 1;
        J = 3;
        K = 2;
        IName = 'X';
        JName = 'Z';
        KName = 'Y';
        az = 0;
        el = 0;
        camUp = [0 0 1];
        sgn = 1;
        tickOrientation = {'yxx','zzx'};
    case 'yx'
        I = 2;
        J = 1;
        K = 3;
        IName = 'Y';
        JName = 'X';
        KName = 'Z';
        az = 90;
        el = -90;
        camUp = [1 0 0];
        sgn = 1;
        tickOrientation = {'yzy','yxx'};
    case 'yz'
        I = 2;
        J = 3;
        K = 1;
        IName = 'Y';
        JName = 'Z';
        KName = 'X';
        az = 90;
        el = 0;
        camUp = [0 0 1];
        sgn = -1;
        tickOrientation = {'yxy','zzy'};
    case 'zx'
        I = 3;
        J = 1;
        K = 2;
        IName = 'Z';
        JName = 'X';
        KName = 'Y';
        az = 180;
        el = 0;
        camUp = [1 0 0];
        sgn = -1;
        tickOrientation = {'zzy','zxx'};
    case 'zy'
        I = 3;
        J = 2;
        K = 1;
        IName = 'Z';
        JName = 'Y';
        KName = 'X';
        az = 270;
        el = 0;
        camUp = [0 1 0];
        sgn = 1;
        tickOrientation = {'zzx','yzy'};
end

% Get current axes state to restore later
curState = get(hAx,axesPropList);

% Set axes properties
view(hAx,az,el)
set(hAx,'CameraUpVector',camUp)
set(hAx,'DataAspectRatio',[1 1 1])
set(hAx,'Projection','orthographic')
set(hAx,'XDir','normal','YDir','normal','ZDir','normal')
set(hAx,'XScale','linear','YScale','linear','ZScale','linear')
set(hAx,'XLimMode','auto','YLimMode','auto','ZLimMode','auto')

%% Do oblique transform

% Create transform matrix
T = eye(4);
T(I,K) = sgn*ratio*cosd(theta);
T(J,K) = sgn*ratio*sind(theta);

% Look for an exisiting obliqueview hgtransform.  If not found, create a 
% new one.
hT = findobj(hAx,'Type','hgtransform','-and','Tag','ObliqueView');
if isempty(hT)
    hT = hgtransform('Matrix',T,'Parent',hAx,'Tag','ObliqueView');
else
    set(hT,'Matrix',T)
end

% Set output argument
if nargout == 1
    hOut = hT;
end

% Put axes children into the hgtransform
hC = get(hAx,'Children');
hC(hC == hT) = [];
set(hC,'Parent',hT)
drawnow

% Store axes state
setappdata(hT,'AxesPreviousState',curState)

if ~hasOAxes
    % Expand limits and exit
    axLims = [get(hAx,'XLim')' get(hAx,'YLim')' get(hAx,'ZLim')'];
    set(hAx,'XLim',getNiceLims(axLims(:,1)'))
    set(hAx,'YLim',getNiceLims(axLims(:,2)'))
    set(hAx,'ZLim',getNiceLims(axLims(:,3)'))
    
    return
end

%% Get initial limits for oaxes calculations

% Get axis limits
axLims = [get(hAx,'XLim')' get(hAx,'YLim')' get(hAx,'ZLim')'];

% Get bounding box of the plot objects in hAx
objLims = objbounds(hAx);
if isempty(objLims)
    objLims = axLims;
end

% Split into un-transformed I,J,K limits
objILims = objLims(2*(I-1)+[1 2]);
objJLims = objLims(2*(J-1)+[1 2]);
objKLims = objLims(2*(K-1)+[1 2]);

% Apply the transform to the bounding box to get a transformed bounding box
bBox = [objLims(1) objLims(3) objLims(5) 1;
        objLims(1) objLims(4) objLims(5) 1;
        objLims(1) objLims(3) objLims(6) 1;
        objLims(1) objLims(4) objLims(6) 1;
        objLims(2) objLims(3) objLims(5) 1;
        objLims(2) objLims(4) objLims(5) 1;
        objLims(2) objLims(3) objLims(6) 1;
        objLims(2) objLims(4) objLims(6) 1]';
    
tBox = T*bBox;
tLims = [min(tBox(1,:)) max(tBox(1,:)) ...
         min(tBox(2,:)) max(tBox(2,:)) ...
         min(tBox(3,:)) max(tBox(3,:))];

% Get transformed K bounds
objKLimsT = tLims(2*(K-1)+[1 2]);

%% Calculate limits and origin for oaxes

% Initial limits
ILims = getNiceLims(axLims(:,I)');
JLims = getNiceLims(axLims(:,J)');
KLims = objKLimsT;

% Get origin and modify I,J limits if needed
switch lower(oaxesLoc)
    case 'center'
        % Origin is centered to limits
        O(I) = mean(ILims);
        O(J) = mean(JLims);
        O(K) = mean(KLims);
        
    case 'origin'
        % Already have an origin from user input
        
        % Expand I,J limits to include origin
        if ILims(1) > O(I)
            ILims(1) = O(I);
        end
        
        if ILims(2) < O(I)
            ILims(2) = O(I);
        end
        
        if JLims(1) > O(J)
            JLims(1) = O(J);
        end
        
        if JLims(2) < O(J)
            JLims(2) = O(J);
        end           
               
    otherwise
        
        % Get I origin
        if any(strcmpi(oaxesLoc,{'TopLeft','BottomLeft'}))
            O(I) = objILims(1);
        else
            O(I) = objILims(2); 
        end
        
        % Get J origin
        if any(strcmpi(oaxesLoc,{'Bottomleft','BottomRight'}))
            O(J) = objJLims(1);
        else
            O(J) = objJLims(2);
        end
        
        % Get K origin
        O(K) = objKLims(1 + double(sgn==1));
end

% Determine K limits by extending out from O to intersect I and J limits
KI1 = (ILims(1) - O(I))/T(I,K);
KI2 = (ILims(2) - O(I))/T(I,K);
KJ1 = (JLims(1) - O(J))/T(J,K);
KJ2 = (JLims(2) - O(J))/T(J,K);

% Set up vector of test points
testPoints = zeros(4,4);
testPoints(I,:) = O(I)*[1 1 1 1];
testPoints(J,:) = O(J)*[1 1 1 1];

if (90 < theta && theta < 180) || (270 < theta && theta < 360)
    testPoints(K,:) = [KI1 KJ2 KI2 KJ1];
    a = 2;
    b = 1;
    sgnJK = -1;
else
    testPoints(K,:) = [KI1 KJ1 KI2 KJ2];
    a = 1;
    b = 2;
    sgnJK = 1;
end

% Apply transform to test points
testProj = T*testPoints;

% Find K limit for left side
if sgnJK*testProj(J,1) > sgnJK*(JLims(a) + 100*eps(JLims(a)))
    KLims(1) = testProj(K,1);
else
    KLims(1) = testProj(K,2);
end

% Find K limit for right side
if sgnJK*testProj(J,3) < sgnJK*(JLims(b) - 100*eps(JLims(b)))
    KLims(2) = testProj(K,3);
else
    KLims(2) = testProj(K,4);
end

% Ensure K limits are in correct order
KLims = sort(KLims);
oaxesKLims = KLims;

if strcmpi(oaxesLoc,'origin')
    % Make sure KLims include the origin and object data
    KLims(1) = min(min(O(K),objKLimsT(1)),KLims(1));
    KLims(2) = max(max(O(K),objKLimsT(2)),KLims(2));
end

% Set limits
set(hAx,[IName 'Lim'],ILims)
set(hAx,[JName 'Lim'],JLims)
set(hAx,[KName 'Lim'],KLims)

% Store axis info
ObliqueViewInfo.I = I;
ObliqueViewInfo.J = J;
ObliqueViewInfo.K = K;
ObliqueViewInfo.O = O;

setappdata(hT,'ObliqueViewInfo',ObliqueViewInfo)

%% Set oaxes properties

% Store current oaxes state to restore later
setappdata(hT,'OAxesPreviousState',OAState);

% Set origin if necessary
if ~strcmpi(oaxesLoc,'center')
    OA.Origin = O;
end  

% Hide parent axes since it is not useful with obliqueview
OA.HideParentAxes = 'on';

% Force display of K axis
OA.Force3D = 'on';

% Set tick orientation
if theta < 30 || (150 < theta && theta < 210) || theta > 330
    idx = 2;
else
    idx = 1;
end
OA.TickOrientation = tickOrientation{idx};

% Set X/Y/ZLim
XYZLims = zeros(2,3);
XYZLims(:,I) = ILims';
XYZLims(:,J) = JLims';
XYZLims(:,K) = oaxesKLims';
OA.XLim = XYZLims(:,1)';
OA.YLim = XYZLims(:,2)';
OA.ZLim = XYZLims(:,3)';

% Set X/Y/ZTickMode to 'auto'
OA.XTickMode = 'auto';
OA.YTickMode = 'auto';
OA.ZTickMode = 'auto';

% Force a redraw of the oaxes
OA.ListenersEnabled = 'on';
drawnow

% Reset listener state
OA.ListenersEnabled = OAEnabled;

end % End of obliqueview
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function niceLims = getNiceLims(lims)
% Expand limits to 'nice' values outside of original limits.

%% Constants
% Minimum expansion amount (% of full scale, per side)
minExp = 0.15;

% Expansion goal (% of full scale, per side)
tgtExp = minExp*1.25;

%% Get expanded limits
% If limits are negative, flip them around to do the calculation
if lims(2) == 0 || abs(lims(2)) < abs(lims(1))
    lims = -fliplr(lims);
    allNeg = true;
else
    allNeg = false;
end

range = lims(2) - lims(1);
decRange = log10(abs(range));
decMax = floor(log10(max(abs(lims))));

if lims(1) <= 0
    decRange = 1;
end

expLims = lims + range*[-tgtExp tgtExp];
minLims = lims + range*[-minExp minExp];

% Get upper limit
if decRange > 0.4
    vec = [2 3 4 5 6 7 8 9 10 11 12 13 14 15]*10^decMax;
elseif decRange >= 0
    vec = [.1 .2 .3 .4 .5 .6 .7 .8 .9 1 1.1 1.2] + ...
        floor(lims(2)*10^(-decMax+1))/10^(-decMax+1);
else
    dec = floor(decRange-1);
    vec = [0 1 2 3 4 5 6 7 8 9 10 11 12 15 20 30 40 50]*10^(dec) + ...
        floor(lims(2)*10^(-dec))/10^(-dec);
end

d = abs(expLims(2) - vec);
d(vec < minLims(2)) = 100*d(vec < minLims(2));
[dMin,idx] = min(d); %#ok<ASGLU>
ULim = vec(idx);

% Get lower limit
if decRange > 0.4
    if idx == 1
        vec = [-2 -1 -0.5 -0.2 -0.1 0 0.1 0.2 0.5 1];
    elseif idx == 2
        vec = [-3 -2 -1 0.5 0 0.5 1 2];
    elseif idx == 3
        vec = -4:3;
    elseif idx == 4
        vec = -5:4;
    elseif idx == 5
        vec = -6:5; 
    elseif idx == 6
        vec = -7:6;
    elseif idx == 7
        vec = [-8 -6 -4 -2 -1 0 1 2 3 4 5 6 7];
    else
        vec = -idx-1:(idx);
    end
    vec = vec*10^decMax;
    
elseif decRange >= 0
    vec = -[.1 .2 .3 .4 .5 .6 .7 .8 .9 1 1.1 1.2] + ...
        floor(lims(1)*10^(-decMax+1))/10^(-decMax+1);
else
    vec = -[0 1 2 3 4 5 6 7 8 9 10 11 12 15 20 30 40 50]*10^(dec) + ...
        floor(lims(1)*10^(-dec))/10^(-dec);
end

d = abs(expLims(1) - vec);
d(vec > minLims(1)) = 100*d(vec > minLims(1));
[dMin,idx] = min(d);  %#ok<ASGLU>
LLim = vec(idx);

if ~allNeg
    niceLims = [LLim ULim];
else
    niceLims = -[ULim LLim];
end

end % End of obliqueview/niceLims
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Contact us