function overviewPlot(varargin)
%OVERVIEWPLOT creates a plot with an interactive overview axes.
%   OVERVIEWPLOT(...) takes the same input arguments as the PLOT command
%   and will create a new figure containing the plot and an inset 
%   displaying an overview of the plot. Pan and zoom operations on the main
%   axes will result in a rectangle being drawn in the inset axes 
%   representing the section of the data currently shown in the main axes.
%   Manipulating the rectangle has the same effect as panning the main
%   axes.
%   OVERVIEWPLOT(FIG,...) will put the main axes and inset axes into the 
%   figure represented by the handle FIG. Any callbacks active in the
%   figure will be overwritten as will any data present in the figure.
%
%   See also PLOT, PAN, ZOOM.

%   Copyright 2007 The MathWorks, Inc.

% Check for a figure handle as first input:
if nargin > 0
    if isscalar(varargin{1}) && ishandle(varargin{1}) ...
            && strcmpi(get(varargin{1},'Type'),'Figure')
        hFig = varargin{1};
        clf(hFig,'reset');
        varargin = varargin(2:end);
    else
        hFig = figure;
    end
end

% Create the data axes:
hMainAx = axes('Parent',hFig,'Position',[0.05 0.05 0.5 0.9]);
plot(hMainAx,varargin{:});
% Create the thumbnail axes:
hOverviewAx = axes('Parent',hFig,'Position',[0.6 0.4 0.3 .54]);
set(hOverviewAx,'XTick',[],'YTick',[],'Box','on');
% Make sure the main axes is set as gca:
set(gcf,'CurrentAxes',hMainAx);
% Replicate the children of the original axes:
copyobj(get(hMainAx,'Children'),hOverviewAx);
% Create a box representing the area of the plot being used:
xl = get(hMainAx,'XLim');
yl = get(hMainAx,'YLim');
% Prevent the axes limits from changing:
set(hOverviewAx,'XLim',xl,'YLim',yl);
% Create an overview box in the overview axes:
hBox = line('Parent',hOverviewAx,'XData',[xl(1) xl(1) xl(2) xl(2) xl(1)],...
    'YData',[yl(1) yl(2) yl(2) yl(1) yl(1)]);

% Customize the zoom for the figure:
hZoom = zoom(hFig);
% Prevent users from zooming on the thumbnail axes:
setAllowAxesZoom(hZoom,hOverviewAx,false);
% Set a callback on the zoom:
set(hZoom,'ActionPostCallback',{@localZoomCallback,hBox});

% Customize the pan for the figure:
hPan = pan(hFig);
% Prevent users from zooming on the thumbnail axes:
setAllowAxesPan(hPan,hOverviewAx,false);
% Set a callback on the zoom:
set(hPan,'ActionPreCallback',{@localStartPanMotion,hBox,hPan});

% Disable 3-D rotation for both axes in the figure:
hRotate3d = rotate3d(hFig);
setAllowAxesRotate(hRotate3d,hOverviewAx,false);
setAllowAxesRotate(hRotate3d,hMainAx,false);

% Set up a motion callback on the figure to detect when we are over the
% rectangle.
hExploreModes = [hPan;hZoom;hRotate3d];
set(hFig,'WindowButtonMotionFcn',{@localHoverCallback,hBox,hOverviewAx,hExploreModes});
% Add a button down callback to facilitate dragging the overview rectangle.
set(hFig,'WindowButtonDownFcn',{@localStartMoveCallback,hBox,hOverviewAx,hMainAx});

%-----------------------------------------------------------------------%
function localStartMoveCallback(obj,evd,hBox,hOverviewAx,hMainAx) %#ok<INUSL>
% If we click on the overview rectangle, start a drag operation.

currPoint = get(hOverviewAx,'CurrentPoint');
% Since the overview axes is 2-D, just take the first (x,y) pair we find:
currPoint = [currPoint(1,1) currPoint(1,2)];
hoverType = localComputeHoverType(currPoint,hBox);

% If we are not hovering over the rectangle, return early:
if strcmpi(hoverType,'none')
    return;
end

% Compute the difference between the current point and bottom-left corner
% of the overview box as a starting point for the drag:
xData = get(hBox,'XData');
yData = get(hBox,'YData');
% We are interested in the bottom-left corner of the box:
bottomLeft = [xData(1) yData(1)];
pointDiff = bottomLeft-currPoint;
% Install the new motion function:
oldMotionCallback = get(obj,'WindowButtonMotionFcn');
set(obj,'WindowButtonMotionFcn',{@localDragBox,hBox,hOverviewAx,pointDiff,hMainAx});

% Install a button up function to for when the drag is over:
set(obj,'WindowButtonUpFcn',{@localDragComplete,oldMotionCallback});

%-----------------------------------------------------------------------%
function localDragComplete(obj,evd,oldMotionCallback) %#ok<INUSL>
% Restore the state of the figure after a drag operation.

set(obj,'WindowButtonUpFcn','');
set(obj,'WindowButtonMotionFcn',oldMotionCallback);

%-----------------------------------------------------------------------%
function localDragBox(obj,evd,hBox,hOverviewAx,pointDiff,hMainAx) %#ok<INUSL>
% Move the box based on the new mouse position:

currPoint = get(hOverviewAx,'CurrentPoint');
% Since the overview axes is 2-D, just take the first (x,y) pair we find:
currPoint = [currPoint(1,1) currPoint(1,2)];

newBottomLeft = pointDiff+currPoint;

% Move the box up based on the new bottom left corner:
xData = get(hBox,'XData');
yData = get(hBox,'YData');

oldBottomLeft = [xData(1) yData(1)];
newDiff = newBottomLeft-oldBottomLeft;

xData = xData+newDiff(1);
yData = yData+newDiff(2);
set(hBox,'XData',xData,'YData',yData);

% Update the axes limits of the main axes:
newXLim = [xData(1) xData(3)];
newYLim = [yData(1) yData(2)];
set(hMainAx,'XLim',newXLim,'YLim',newYLim);

%-----------------------------------------------------------------------%
function localHoverCallback(obj,evd,hBox,hOverviewAx,hExploreModes) %#ok<INUSL>
% Change the pointer depending on whether we are inside the bounds of the
% box in the overview axes:

% If any exploratory mode is active, return early.
if any(strcmpi(get(hExploreModes,'Enable'),'on'))
    return;
end
currPoint = get(hOverviewAx,'CurrentPoint');
% Since the overview axes is 2-D, just take the first (x,y) pair we find:
currPoint = [currPoint(1,1) currPoint(1,2)];
pointerShape = localComputeCursorShape(currPoint,hBox);
set(obj,'Pointer',pointerShape);

%-----------------------------------------------------------------------%
function pointerShape = localComputeCursorShape(currPoint,hBox)
% Determine the cursor shape from its position

hoverType = localComputeHoverType(currPoint,hBox);

if strcmpi(hoverType,'over')
    pointerShape = 'fleur';
else
    pointerShape = 'arrow';
end

%-----------------------------------------------------------------------%
function hoverType = localComputeHoverType(currPoint,hBox)
% Depending on where the mouse is, we may be over the overview box 
% (or not at all).

xData = get(hBox,'XData');
yData = get(hBox,'YData');
% We are interested in the corners of the box:
left = xData(1);
right = xData(3);
bottom = yData(1);
top = yData(2);

% Check if we are inside the box:
if (currPoint(1) >= left) && (currPoint(1) <= right) && ...
        (currPoint(2) <= top) && (currPoint(2) >= bottom)
    hoverType = 'over';
else
    hoverType = 'none';
end

%-----------------------------------------------------------------------%
function localZoomCallback(obj,evd,hBox) %#ok<INUSL>
% Adjust the box based on the new limits of the zoomed axes:
localUpdateBox(evd.Axes,hBox);

%-----------------------------------------------------------------------%
function localStartPanMotion(obj,evd,hBox,hPan)
% Set the "WindowButtonMotionFcn" of the figure to track the axes limits.
% As long as the user is panning, make sure to update the box accordingly.
origFun = get(obj,'WindowButtonMotionFcn');
set(obj,'WindowButtonMotionFcn',{@localPanMotionCallback,evd,hBox});
set(hPan,'ActionPostCallback',{@localEndPanMotion,hBox,origFun});

%-----------------------------------------------------------------------%
function localPanMotionCallback(obj,evd,panEvd,hBox) %#ok<INUSL>
% Adjust the box based on the new limits of the zoomed axes:
localUpdateBox(panEvd.Axes,hBox);

%-----------------------------------------------------------------------%
function localEndPanMotion(obj,evd,hBox,origFun)
% Restore the original "WindowButtonMotionFcn" callback.
set(obj,'WindowButtonMotionFcn',origFun);
% Update the box based on the new limits of the panned axes:
localUpdateBox(evd.Axes,hBox);

%-----------------------------------------------------------------------%
function localUpdateBox(hAx,hBox)
% Adjust the box based on the new limits of the axes:
xl = get(hAx,'XLim');
yl = get(hAx,'YLim');
set(hBox,'XData', [xl(1) xl(1) xl(2) xl(2) xl(1)],...
    'YData',[yl(1) yl(2) yl(2) yl(1) yl(1)]);