function jewelersLoupe(hFig)
%JEWELERSLOUPE modifies a figure to enable a Jeweler's Loupe tool.
%   JEWELERSLOUPE(FIG) will modify the figure with handle FIG to allow a
%   jeweler's loupe tool, which magnifies the axes by a factor of 4, to 
%   appear whenever a user clicks on an axes. Dragging will cause the loupe
%   to follow the cursor. The tool assumes that any axes clicked will be a 
%   linear 2-D axes. Performance will degrade as the amount of data in the
%   axes increates. If no figure is given, the loupe will appear in the 
%   current figure.
%
%   Example:
%   plot(magic(3));
%   jewelersLoupe(gcf);
%
%   See also PAN, ZOOM.

%   Copyright 2008 The MathWorks, Inc.

% If a figure has not been passed in, 
if nargin < 1
    hFig = gcf;
end

% Make sure we have a valid figure:
if ~ishandle(hFig) || ~strcmpi(get(hFig,'Type'),'figure')
    error('JEWELERSLOUPE:INVALIDFIGURE',...
        'The first input argument must be a valid figure.');
end

% Set up the callback for the loupe:
set(hFig,'WindowButtonDownFcn',@localCreateLoupe);

%------------------------------------------------------------------------%
function localCreateLoupe(hFig,evd) %#ok<INUSD>
% The loupe will appear for the current axes.
hAx = get(hFig,'CurrentAxes');

% If the axes is empty, we won't do anything.
if isempty(hAx)
    return;
end

% The loupe is implemented in terms of a separate axes:
hLoupe = axes('Parent',hFig,'Tag','loupe','HandleVisibility','off',...
    'XTickLabel','','YTickLabel','','XTick',[],'YTick',[],'Box','on');
% Set up the initial loupe position:
localUpdateLoupePosition(hFig,hLoupe);

% Copy the contents of the axes into the loupe:
copyobj(get(hAx,'Children'),hLoupe);

% We will zoom by a factor of 4 into the loupe based on the current point
% in the axes.
localDoZoom(hLoupe,hAx);

% Set callbacks for motion and button up:
set(hFig,'WindowButtonMotionFcn',{@localMoveLoupe,hAx,hLoupe});
set(hFig,'WindowButtonUpFcn',{@localCleanUp,hLoupe});

%------------------------------------------------------------------------%
function localMoveLoupe(hFig,evd,hAx,hLoupe) %#ok<INUSL>
% Move the loupe based on the current point:
localUpdateLoupePosition(hFig,hLoupe);

% Perform the zoom operation on the axes:
localDoZoom(hLoupe,hAx);

%------------------------------------------------------------------------%
function localCleanUp(hFig,evd,hLoupe) %#ok<INUSL>
% Clean up after the loupe has finished execution:

% Remove the callbacks:
set(hFig,'WindowButtonMotionFcn','');
set(hFig,'WindowButtonUpFcn','');

% Delete the loupe:
delete(hLoupe);

%------------------------------------------------------------------------%
function localUpdateLoupePosition(hFig,hLoupe)
% Moves the loupe based on the current point in the figure:

% The loupe should be centered on the current location and take up 25% of
% the figure space.
oldUnits = get(hFig,'Units');
set(hFig,'Units','Normalized');
mousePoint = get(hFig,'CurrentPoint');
set(hFig,'Units',oldUnits);
loupePosition = [(mousePoint-.125) .25 .25];
set(hLoupe,'Position',loupePosition);

%------------------------------------------------------------------------%
function localDoZoom(hLoupe,hAx)
% First, calculate the center based on the current point in the axes:
newCenter = get(hAx,'CurrentPoint');
% We are assuming a 2-D axes, so we will take the first two coordinates.
newCenter = newCenter(1,1:2);

% Zoom in by a factor of 4 around the given center point.
center_x = newCenter(1);
center_y = newCenter(2);

% Start by zooming the X-limits:
origXLim = get(hAx,'XLim');
xmin = origXLim(1);
xmax = origXLim(2);
dx = diff(origXLim);
newdx = dx * (1/4);
newdx = min(newdx,xmax-xmin);
newXLim = [center_x-newdx/2 center_x+newdx/2];

% Next, zoom the Y-limits:
origYLim = get(hAx,'YLim');
ymin = origYLim(1);
ymax = origYLim(2);
dy = diff(origYLim);
newdy = dy * (1/4);
newdy = min(newdy,ymax-ymin);
newYLim = [center_y-newdy/2 center_y+newdy/2];

% Update the limits:
set(hLoupe,'XLim',newXLim,'YLim',newYLim);
