Code covered by the BSD License  

Highlights from
ScrollPlot - scrollable X/Y axes

image thumbnail
from ScrollPlot - scrollable X/Y axes by Yair Altman
ScrollPlot adds a scroll sub-window to any supplied plot handles.

scrollplot(varargin)
function scrollHandles = scrollplot(varargin)
%SCROLLPLOT add scroll sub-window to the supplied plot handle
%
%   scrollplot adds a scroll sub-window to any supplied plot handle(s).
%   The user may specify initial view window parameters or use defaults.
%   Dragging the side-bars or central patch modifies the respective parent
%   axes limits interactively. Conversely, modifying the parent axes
%   limits (with zoom, pan or programatically) modifies the corresponding
%   scroll patch(es) accordingly. Works ok with log and reverse axes.
%   Both X & Y scrolling are possible.
%
%   Syntax:
%     scrollHandles = scrollplot(plotHandles, propName,propValue,...)
%
%   scrollplot(plotHandles) adds a scroll sub-window to the supplied
%   plotHandles using default property values (see below).
%   plotHandles may be any combination of axes and line/data handles.
%   If plotHandles is not supplied then the current axes (<a href="matlab:help gca">gca</a>) is used.
%
%   scrollplot(..., propName,propValue, ...) sets the property value(s)
%   for the initial scroll view window. Property specification order does
%   not matter. The following properties are supported (case-insensitive):
%     - 'Axis'       : string (default = 'X'; accepted values: 'X','Y','XY')
%     - 'Min'        : number (default = minimal value of actual plot data)
%                      sets the same value for both 'MinX' & 'MinY'
%     - 'Max'        : number (default = maximal value of actual plot data)
%                      sets the same value for both 'MaxX' & 'MaxY'
%     - 'MinX','MinY': number (same as 'Min', but only for X or Y axis)
%     - 'MaxX','MaxY': number (same as 'Max', but only for X or Y axis)
%     - 'WindowSize' : number (default = entire range  of actual plot data)
%                      sets the same value for 'WindowSizeX' & 'WindowSizeY'
%     - 'WindowSizeX': number (same as 'WindowSize' but only for X axis)
%     - 'WindowSizeY': number (same as 'WindowSize' but only for Y axis)
%
%   scrollHandles = scrollplot(...) returns handle(s) to the scroll axes.
%   The returned handles are regular axes with a few additional read-only
%   properties:
%     - 'ScrollSideBarHandles' - array of 2 handles to the scroll side-bars
%     - 'ScrollPatchHandle' - handle to the central scroll patch
%     - 'ScrollAxesHandle'  - handle to the scroll axes =double(scrollHandles)
%     - 'ParentAxesHandle'  - handle to the parent axes
%     - 'ScrollMin'         - number
%     - 'ScrollMax'         - number
%
%   Examples:
%     scrollplot;  % add scroll sub-window to the current axes (gca)
%     scrollplot(plot(xdata,ydata), 'WindowSize',50); % plot with initial zoom
%     scrollplot('Min',20, 'windowsize',70); % add x-scroll to current axes
%     scrollplot([h1,h2], 'axis','xy'); % scroll both X&Y of 2 plot axes
%     scrollplot('axis','xy', 'minx',20, 'miny',10); % separate scroll minima
%
%   Notes:
%     1. Matlab 5: scrollplot might NOT work on Matlab versions earlier than 6 (R12)
%     2. Matlab 6: scrollplot is not interactive in zoom mode (ok in Matlab 7+)
%     3. Matlab 6: warnings are disabled as a side-effect (not in Matlab 7+)
%     4. scrollplot modifies the figure's WindowButtonMotionFcn callback
%     5. scrollplot works on 3D plots, but only X & Y axis are scrollable
%
%   Warning:
%     This code relies in [small] part on undocumented and unsupported
%     Matlab functionality. It works on Matlab 6+, but use at your own risk!
%
%   Bugs and suggestions:
%     Please send to Yair Altman (altmany at gmail dot com)
%
%   Change log:
%     2007-May-13: First version posted on MathWorks file exchange: <a href="http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14984">http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?objectId=14984</a>
%     2007-May-14: Set focus on parent axes after scroll-axes creation; added special scroll props; allowed 'Axis'='xy'
%     2007-May-15: Added 'MinX' etc. params; clarified error msgs; added 'ParentAxesHandle' special prop; fixed 'xy' bugs
%     2007-Jun-14: Enabled image exploration per suggestion by Joe Lotz; improved log axis-scaling behavior per suggestion by Fredric Moisy; added scroll visibility & deletion handlers; fixed minor error handling bug
%     2010-Nov-04: Minor fix for uitab compatibility suggested by Fabian Hof
%     2013-Jun-28: Support for the upcoming HG2
%
%   See also:
%     plot, gca

%   Programming notes:
%     1. Listeners are set on parent axes's properties so that whenever
%        any of them (xlim,ylim,parent,units,position) is modified, then
%        so are the corresponding scroll axes properties.
%     2. To bypass the mode managers' (zoom, pan, ...) "hijack" of the
%        WindowButtonUpFcn callback, we use the non-supported JavaFrame's
%        AxisComponent MouseReleasedCallback: this doesn't work in Matlab 6
%        so scrollplot is non-interactive in Matlab 6 during zoom mode.
%     3. To bypass the mode managers over the scroll axes (to ignore zoom/
%        pan), we use the little-known 'ButtonDownFilter' mode property.
%     4. The special read-only properties in the returned scrollHandles were
%        added using the undocumented schema.prop interface. Since these
%        properties are not viewable (only accessible) in the regular axes
%        handle but only in the handle(scrollHandles), the latter form is
%        returned. If you need the regular (numeric) form, use either
%        double(scrollHandles) or the new 'ScrollAxesHandle' read-only prop.

% License to use and modify this code is granted freely without warranty to all, as long as the original author is
% referenced and attributed as such. The original author maintains the right to be solely associated with this work.

% Programmed and Copyright by Yair M. Altman: altmany(at)gmail.com
% $Revision: 1.5 $  $Date: 2013/06/28 00:31:27 $

    try
        % Note: on some systems with Matlab 6, an OpenGL warning is displayed due to semi-
        % transparent scroll patch. This may be safely ignored. Unfortunately, specific warning
        % disabling was not yet available in Matlab 6 so we must turn off all warnings...
        v = version;
        if v(1)<='6'
            warning off;  %#ok for Matlab 6 compatibility
        else
            % Temporarily turn off log-axis warnings
            oldWarn = warning('off','MATLAB:Axes:NegativeDataInLogAxis');
        end

        % Args check
        [plotHandles, pvPairs] = parseparams(varargin);
        if iscell(plotHandles)
            plotHandles = [plotHandles{:}];  % cell2mat is not supported on old Matlab versions...
        end
        if isempty(plotHandles)
            plotHandles = gca;
        else
            plotHandles = plotHandles(:);  % ensure 1-D array of handles
        end

        % Ensure that all supplied handles are valid HG handles
        if isempty(plotHandles) | ~all(ishandle(plotHandles))  %#ok for Matlab 6 compatibility (note that Matlab 6 did not have ishghandle())
            myError('YMA:scrollplot:invalidHandle','invalid plot handle(s) passed to scrollplot');
        end

        % Get the list of axes handles (supplied handles may be axes or axes children)
        validHandles = [];
        try
            for hIdx = 1 : length(plotHandles)
                thisHandle = plotHandles(hIdx);
                if ~strcmpi(get(thisHandle,'type'),'axes')
                    thisHandle = get(thisHandle,'Parent');  % old Matlab versions don't have ancestor()...
                end
                if ~strcmpi(get(thisHandle,'type'),'axes')
                    myError('YMA:scrollplot:invalidHandle','invalid plot handle passed to scrollplot - must be an axes or line/data handle');
                end
                validHandles = [validHandles, thisHandle];  %#ok mlint - preallocate
            end
            validHandles = unique(validHandles);
        catch
            % Probably not a valid axes/line, without a 'type' property (see isprop)
            myError('YMA:scrollplot:invalidHandle','invalid plot handle(s) passed to scrollplot - must be an axes or line/data handle');
        end

        % Pre-process args necessary for creating the scroll-plots, if supplied
        [pvPairs, axName] = preProcessArgs(pvPairs);

        % For each unique axes, add the relevant scroll sub-plot
        %try
            scrollplotHandles = handle([]);

            % Loop over all specified/inferred parent axes
            for hIdx = 1 : length(validHandles)

                % Loop over all requested scroll axes (x,y, or x&y) for this parent axes
                hAx = validHandles(hIdx);
                for axisIdx = 1 : length(axName)
                    % Add the new scroll plot axes
                    h = addScrollPlot(hAx,axName(axisIdx));  %#ok mlint - preallocate
                    try
                        scrollplotHandles(end+1) = h;
                    catch
                        scrollplotHandles = [scrollplotHandles h];
                    end

                    % Process args, if supplied
                    processArgs(pvPairs,scrollplotHandles(end));
                end

                % Set the focus on the parent axes
                axes(hAx);
            end
        %catch
            % Probably not a valid axes handle
            %myError('YMA:scrollplot:invalidHandle','invalid plot handle(s) passed to scrollplot - must be an axes or line/data handle');
        %end

        % If return scrollHandles was requested
        if nargout
            % Return the list of all scroll handles
            scrollHandles = scrollplotHandles;
        end
    catch
        v = version;
        if v(1)<='6'
            err.message = lasterr;  % no lasterror function...
        else
            err = lasterror;
        end
        try
            err.message = regexprep(err.message,'Error using ==> [^\n]+\n','');
        catch
            try
                % Another approach, used in Matlab 6 (where regexprep is unavailable)
                startIdx = findstr(err.message,'Error using ==> ');
                stopIdx = findstr(err.message,char(10));
                for idx = length(startIdx) : -1 : 1
                    idx2 = min(find(stopIdx > startIdx(idx)));  %#ok ML6
                    err.message(startIdx(idx):stopIdx(idx2)) = [];
                end
            catch
                % never mind...
            end
        end
        if isempty(findstr(mfilename,err.message))
            % Indicate error origin, if not already stated within the error message
            err.message = [mfilename ': ' err.message];
        end
        if v(1)<='6'
            while err.message(end)==char(10)
                err.message(end) = [];  % strip excessive Matlab 6 newlines
            end
            error(err.message);
        else
            rethrow(err);
        end
    end

    % Restore original warnings (if available/possible)
    try
        warning(oldWarn);
    catch
        % never mind...
    end
%end  % scrollplot  %#ok for Matlab 6 compatibility

%% Set-up a new scroll sub-plot window to the supplied axes handle
function hScroll = addScrollPlot(hAx,axName)

    % Before modifying the original axes position, we must fix the labels (esp. xlabel)
    hLabel = get(hAx, [axName 'Label']);
    set(hLabel, 'units','normalized');

    % Set a new scroll sub-plot in the bottom 10% of the original axes height
    axPos = get(hAx,'position');
    axVis = get(hAx,'visible');
    axUnits = get(hAx,'units');
    scaleStr = [axName 'Scale'];
    dirStr   = [axName 'Dir'];
    limStr   = [axName 'Lim'];
    if strcmpi(axName,'x')
        newScrollPos = axPos .* [1, 1, 1, 0.10];
        newPlotPos   = axPos .* [1, 1, 1, 0.80] + [0, 0.20*axPos(4), 0, 0];
        specialStr = {'YTick',[]};
        colStr  = 'YColor';  % =axis line to hide
        rotation = 0;
    else  % Y scroll
        newScrollPos = [axPos(1)+axPos(3)*0.85, axPos(2), axPos(3)*0.1, axPos(4)];
        newPlotPos   = axPos .* [1, 1, 0.80, 1];
        specialStr = {'XTick',[], 'YAxisLocation','right'};
        colStr  = 'XColor';  % =axis line to hide
        rotation = 90;
    end
    hScroll = axes('units',axUnits, 'Parent',get(hAx,'Parent'), 'position',newScrollPos, 'visible',axVis, scaleStr,get(hAx,scaleStr), dirStr,get(hAx,dirStr), 'NextPlot','add', 'Box','off', specialStr{:}, 'FontSize',7, 'Tag','scrollAx', 'UserData',axName, 'DeleteFcn',@deleteScrollAx);
    GRAY = 0.8 * [1,1,1];
    try
        bgColor = get(get(hAx,'parent'),'Color');
        set(hScroll, colStr,bgColor);
    catch
        % Maybe the axes is contained in something without a 'Color' property
        set(hScroll, colStr,GRAY);
    end
    %axis(hScroll, 'off');
    set(hAx, 'position',newPlotPos);

    % Store the parent axes in the scroll axes's appdata
    setappdata(hScroll, 'parent',hAx);

    % Set the scroll limits & data based on the original axes's data
    % Note: use any axes child xdata to set the scroll limits, but
    % ^^^^  only plot line children (not scatter/bar/polar etc)
    axLines = get(hAx,'children');
    %lim = [Inf, -Inf];
    lim = get(hAx,limStr);
    if isinf(lim(1)),  lim(1)=+inf;  end
    if isinf(lim(2)),  lim(2)=-inf;  end
    for lineIdx = 1 : length(axLines)
        try
            hLine = axLines(lineIdx);
            xdata = get(hLine,'XData');
            ydata = get(hLine,'YData');
            try
                name = get(hLine,'DisplayName');
            catch
                % Matlab 6 did not have 'DisplayName' property (used by legend) - never mind...
                name = '';
            end
            if strcmpi(axName,'x'),  data=xdata(1,:);  else  data=ydata(1,:);  end
            lim = [min(lim(1),min(data)), max(lim(2),max(data))];
            linType = get(hLine,'type');
            if strcmpi(linType,'line')
                % Add plot line child if and only if it's a line
                lineColor = GRAY;  %=get(hLine,'color'); %orig color looks bad in scroll axes
                hLine2 = plot(xdata, ydata, 'Parent',hScroll, 'color',lineColor, 'tag','scrollDataLine', 'HitTest','off');
                if ~isempty(name)
                    set(hLine2, 'DisplayName',name);
                end
            % 2007-Jun-14: Enabled image exploration per suggestion by Joe Lotz
            elseif strcmpi(linType,'image')
                % Add miniature version of the main image
                hLine2 = image(get(hLine,'CData'), 'Parent',hScroll);  %#ok hLine2 used for debug
                set(hScroll,'YDir','Reverse','XLim',get(hLine,'XData'),'YLim',get(hLine,'YData'));
            end
        catch
            % Probably some axes child without data - skip it...
        end
    end
    if lim(1) > lim(2)
        curLim = get(hScroll, limStr);
        if isinf(lim(1)),  lim(1) = curLim(1);  end
        if isinf(lim(2)),  lim(2) = curLim(2);  end
    end
    if ~isempty(axLines) & lim(1) < lim(2)  %#ok for Matlab 6 compatibility
        set(hScroll, limStr,lim);
    end

    % Get the figure handle
    hFig = ancestor(hAx,'figure');

    % Prevent flicker on axes update
    set(hFig, 'DoubleBuffer','on');

    % Ensure that the axis component has a handle with callbacks
    % Note: this is determined by the first invocation, so ensure we're the first...
    axisComponent = getAxisComponent(hFig);  %#ok unused

    % Set the scroll handle-bars
    xlim = get(hScroll, 'XLim');
    ylim = get(hScroll, 'YLim');
    hPatch = patch(xlim([1,1,2,2]), ylim([1,2,2,1]), 'b', 'FaceAlpha',.15, 'EdgeColor','w', 'EdgeAlpha',.15, 'ButtonDownFcn',@mouseDownCallback, 'tag','scrollPatch', 'userdata',axName);  %Note: FaceAlpha causes an OpenGL warning in Matlab 6
    commonProps = {'Parent',hScroll, 'LineWidth',3, 'ButtonDownFcn',@mouseDownCallback, 'tag','scrollBar'};
    smallDelta = 0.01 * diff(lim);  % don't use eps
    if strcmpi(axName,'x')
        hBars(1) = plot(xlim([1,1]), ylim, '-b', commonProps{:});
        hBars(2) = plot(xlim([2,2]), ylim, '-b', commonProps{:});
    else  % Y scroll
        hBars(1) = plot(xlim, ylim([1,1])+smallDelta, '-b', commonProps{:});
        hBars(2) = plot(xlim, ylim([2,2])-smallDelta, '-b', commonProps{:});
    end
    try
        set(hBars(1), 'DisplayName','Min');
        set(hBars(2), 'DisplayName','Max');
    catch
        % Matlab 6 did not have 'DisplayName' property (used by legend) - never mind...
    end
    % TODO: maybe add a blue diamond or a visual handle in center of hBars?
    set(hScroll, limStr,lim+smallDelta*[-1.2,1.2]);

    % Help messages
    msg = {'drag blue side-bars to zoom', 'drag central patch to pan'};
    xText = getCenterCoord(hScroll, 'x');
    yText = getCenterCoord(hScroll, 'y');
    hText = text(xText,yText,msg, 'Color','r', 'Rotation',rotation, 'HorizontalAlignment','center', 'FontSize',9, 'FontWeight','bold', 'EraseMode','xor', 'HitTest','off', 'tag','scrollHelp');  %#ok ret val used for debug
    hMenu = uicontextmenu;
    set(hScroll, 'UIContextMenu',hMenu);
    uimenu(hMenu, 'Label',msg{1}, 'Callback',@moveCursor, 'UserData',hBars(2));
    uimenu(hMenu, 'Label',msg{2}, 'Callback',@moveCursor, 'UserData',hPatch);

    % Set the mouse callbacks
    winFcn = get(hFig,'WindowButtonMotionFcn');
    if ~isempty(winFcn) & ~isequal(winFcn,@mouseMoveCallback) & (~iscell(winFcn) | ~isequal(winFcn{1},@mouseMoveCallback))  %#ok for Matlab 6 compatibility
        setappdata(hFig, 'scrollplot_oldButtonMotionFcn',winFcn);
    end
    set(hFig,'WindowButtonMotionFcn',@mouseMoveCallback);

    % Fix label position(s)
    oldPos = get(hLabel, 'position');
    if strcmpi(axName,'x')
        if ~isempty(oldPos) & oldPos(2)<0  %#ok for Matlab 6 compatibility
            % Only fix if the X label is on the bottom (usually yes)
            set(hLabel, 'position',oldPos-[0,.20/.80,0]);
        end
    else  % Y scroll
        if ~isempty(oldPos) & oldPos(1)>0  %#ok for Matlab 6 compatibility
            % Only fix if the Y label is on the right side (usually not)
            set(hLabel, 'position',oldPos+[.20/.80,0,0]);
        end
    end

    % Add property listeners
    listenedPropNames = {'XLim','YLim','XDir','YDir','XScale','YScale','Position','Units','Parent'};
    listeners = addPropListeners(hFig, hAx, hScroll, hPatch, hBars, listenedPropNames);
    setappdata(hScroll, 'scrollplot_listeners',listeners);  % These will be destroyed with hScroll so no need to un-listen upon hScroll deletion

    % Add special properties
    addSpecialProps(hAx, hScroll, hPatch, hBars, axName);

    % Convert to handle object, so that the special properties become visible
    hScroll = handle(hScroll);
    return;  % debug point
%end  % addScrollPlot  %#ok for Matlab 6 compatibility

%% Add parent axes listener
function listeners = addPropListeners(hFig, hAx, hScroll, hPatch, hBars, propNames)
    % Listeners on parent axes properties
    hhAx = handle(hAx);
    for propIdx = 1 : length(propNames)
        propName = propNames{propIdx};
        callback = {@parentAxesChanged, hFig, hAx, hPatch, hBars, propName};
        prop = findprop(hhAx, propName);
        try
            listeners(propIdx) = handle.listener(hhAx, prop, 'PropertyPostSet', callback);  %#ok mlint - preallocate
        catch
            callback = @(h,e) parentAxesChanged(h,e,callback{2:end});
            listeners(propIdx) = event.proplistener(hhAx, prop, 'PostSet', callback);  %#ok mlint - preallocate
        end
    end

    % Listeners on scroll axes properties
    hhScroll = handle(hScroll);
    prop = findprop(hhScroll, 'Visible');
    try
        listeners(end+1) = handle.listener(hhScroll, prop, 'PropertyPostSet', {@updateParentPos,hScroll});
    catch
        listeners(end+1) = event.proplistener(hhScroll, prop, 'PostSet', @(h,e) updateParentPos(h,e,hScroll));
    end
%end  % addPropListeners  %#ok for Matlab 6 compatibility

%% Add special scrollplot properties to the hScroll axes
function addSpecialProps(hAx, hScroll, hPatch, hBars, axName)
    try
        hhScroll = handle(hScroll);

        % Read-only props
        addNewProp(hhScroll,'ParentAxesHandle',    hAx,1);
        addNewProp(hhScroll,'ScrollAxesHandle',    double(hScroll),1);
        addNewProp(hhScroll,'ScrollPatchHandle',   hPatch,1);
        addNewProp(hhScroll,'ScrollSideBarHandles',hBars, 1);

        % Note: setting the property's GetFunction is much cleaner but doesn't work in Matlab 6...
        dataStr = [axName,'Data'];
        addNewProp(hhScroll,'ScrollMin',unique(get(hBars(1),dataStr)),1); %,{@getBarVal,hBars(1),dataStr});
        addNewProp(hhScroll,'ScrollMax',unique(get(hBars(2),dataStr)),1); %,{@getBarVal,hBars(2),dataStr});
    catch
        % Never mind...
    end
%end  % addSpecialProps  %#ok for Matlab 6 compatibility

%% Add new property to supplied handle
function addNewProp(hndl,propName,initialValue,readOnlyFlag,getFunc,setFunc)
    sp = schema.prop(hndl,propName,'mxArray');
    set(hndl,propName,initialValue);
    if nargin>3 & ~isempty(readOnlyFlag) & readOnlyFlag  %#ok for Matlab 6 compatibility
        set(sp,'AccessFlags.PublicSet','off');  % default='on'
    end
    if nargin>4 & ~isempty(getFunc)  %#ok for Matlab 6 compatibility
        set(sp,'GetFunction',getFunc);  % unsupported in Matlab 6
    end
    if nargin>5 & ~isempty(setFunc)  %#ok for Matlab 6 compatibility
        set(sp,'SetFunction',setFunc);  % unsupported in Matlab 6
    end
%end  % addNewProp  %#ok for Matlab 6 compatibility

%% Callback for getting side-bar value
function propValue = getBarVal(object,propValue,varargin)  %#ok object & propValue are unused
    propValue = unique(get(varargin{:}));
%end  % getBarVal  %#ok for Matlab 6 compatibility

%% Pre-process args necessary for creating the scroll-plots, if supplied
function [pvPairs, axName] = preProcessArgs(pvPairs)
    % Default axes is 'X'
    axName = 'x';

    % Special check for invalid format
    if ~isempty(pvPairs) & ischar(pvPairs{end}) & any(strcmpi(pvPairs{end},{'axis','axes'}))  %#ok for Matlab 6 compatibility
        myError('YMA:scrollplot:invalidProperty','No data specified for scrollplot property ''Axis''');
    end

    % Loop over all supplied P-V pairs to pre-process the parameters
    idx = 1;
    while idx < length(pvPairs)
        paramName = pvPairs{idx};
        if ~ischar(paramName),  idx=idx+1; continue;  end
        switch lower(paramName)
            % Get the last axes requested by the user (if any)
            % Check for 'axis' or 'axes' ('axes' is a typical typo of 'axis')
            case {'axis','axes'}
                axName = pvPairs{idx+1};
                % Ensure we got a valid axis name: 'x','y','xy' or 'yx'
                if ~ischar(axName) | ~any(strcmpi(axName,{'x','y','xy','yx'}))  %#ok for Matlab 6 compatibility
                    myError('YMA:scrollplot:invalidProperty','Invalid scrollplot ''Axis'' property value: only ''x'',''y'' & ''xy'' are accepted');
                end
                % Remove from the PV pairs list and move on
                axName = lower(axName);
                pvPairs(idx:idx+1) = [];

            % Placeholder for possible future pre-processed args
            otherwise
                % Skip...
                idx = idx + 1;
        end
    end
%end  % preProcessArgs  %#ok for Matlab 6 compatibility

%% Process P-V argument pairs
function processArgs(pvPairs,hScroll)
    try
        minLim = [];
        maxLim = [];
        hScroll = double(hScroll);  % Matlab 6 could not use findall with handle objects...
        axName = get(hScroll, 'userdata');
        if strcmpi(axName,'x')
            otherAxName = 'y';  %#ok mlint mistaken warning - used below
        else  % Y scroll
            otherAxName = 'x';  %#ok mlint mistaken warning - used below
        end
        dataStr = [axName 'Data'];
        limStr  = [axName 'Lim'];
        while ~isempty(pvPairs)
            % Ensure basic format is valid
            paramName = '';
            if ~ischar(pvPairs{1})
                myError('YMA:scrollplot:invalidProperty','Invalid property passed to scrollplot');
            elseif length(pvPairs) == 1
                myError('YMA:scrollplot:invalidProperty',['No data specified for property ''' pvPairs{1} '''']);
            end

            % Process parameter values
            paramName  = pvPairs{1};
            paramValue = pvPairs{2};
            pvPairs(1:2) = [];
            hScrollBars    = unique(findall(hScroll, 'tag','scrollBar'));
            hScrollPatches = unique(findall(hScroll, 'tag','scrollPatch'));
            switch lower(paramName)
                case {'min',['min' axName]}
                    set(hScrollBars(1:2:end), dataStr,paramValue([1,1]));
                    for patchIdx = 1 : length(hScrollPatches)
                        thisPatch = hScrollPatches(patchIdx);
                        data = get(thisPatch, dataStr);
                        if strcmpi(axName,'x')
                            set(thisPatch, dataStr,[paramValue([1;1]); data([4,4])]);
                        else  % Y scroll
                            set(thisPatch, dataStr,[paramValue(1); data([2,2]); paramValue(1)]);
                        end

                        % Update the parent axes with the new limit
                        hAx = getappdata(get(thisPatch,'Parent'), 'parent');
                        lim = get(hAx, limStr);
                        set(hAx, limStr,[paramValue,lim(2)]);
                    end
                    minLim = paramValue;

                case {'max',['max' axName]}
                    set(hScrollBars(2:2:end), dataStr,paramValue([1,1]));
                    for patchIdx = 1 : length(hScrollPatches)
                        thisPatch = hScrollPatches(patchIdx);
                        data = get(thisPatch, dataStr);
                        if strcmpi(axName,'x')
                            set(thisPatch, dataStr,[data([1,1]); paramValue([1;1])]);
                        else  % Y scroll
                            set(thisPatch, dataStr,[data(1); paramValue([1;1]); data(1)]);
                        end

                        % Update the parent axes with the new limit
                        hAx = getappdata(get(thisPatch,'Parent'), 'parent');
                        lim = get(hAx, limStr);
                        set(hAx, limStr,[lim(1),paramValue]);
                    end
                    maxLim = paramValue;

                case {'windowsize',['windowsize' axName]}
                    if isempty(pvPairs)
                        % No min,max after this param, so act based on data so far
                        if ~isempty(minLim)
                            if ~isempty(maxLim) & abs(maxLim-minLim-paramValue(1))>eps  %#ok for Matlab 6 compatibility
                                myError('YMA:scrollplot:invalidWindowSize','Specified WindowSize value conflicts with earlier values specified for Min,Max');
                            end
                            pvPairs = {'Max', minLim+paramValue(1), pvPairs{:}};  % note: can't do [...,pvPairs] because of a Matlab6 bug when pvPairs={}
                        elseif ~isempty(maxLim)  % Only max was specified...
                            pvPairs = {'Min', maxLim-paramValue(1), pvPairs{:}};
                        else
                            % No min,max: act based on actual min for each axes seperately
                            for scrollIdx = 1 : length(hScroll)
                                % Update the right side bar
                                thisScroll = hScroll(scrollIdx);
                                hScrollBars = unique(findall(thisScroll, 'tag','scrollBar'));
                                maxLim = get(hScrollBars(1), dataStr) + paramValue(1);
                                set(hScrollBars(2), dataStr,maxLim);

                                % Now update the patch
                                thisPatch = unique(findall(thisScroll, 'tag','scrollPatch'));
                                data = get(thisPatch, dataStr);
                                if strcmpi(axName,'x')
                                    set(thisPatch, dataStr,[data([1,1]); maxLim']);
                                else  % Y scroll
                                    set(thisPatch, dataStr,[data(1); maxLim'; data(1)]);
                                end

                                % Finally, update the parent axes with the new limit
                                hAx = getappdata(thisScroll, 'parent');
                                lim = get(hAx, limStr);
                                set(hAx, limStr,[lim(1),maxLim(1)]);
                            end
                        end
                    else
                        % Push this P-V pair to the end of the params list (after min,max)
                        pvPairs = {pvPairs{:}, paramName, paramValue(1)};
                    end

                % Not a good idea to let users play with position so easily...
                %case 'position'
                %    set(hScroll, 'position',paramValue);

                case {'axis','axes'}
                    % Do nothing (should never get here: should have been stripped by preProcessArgs()!)

                case {['min' otherAxName], ['max' otherAxName], ['windowsize' otherAxName]}
                    % Do nothing (pass to other axes for processing)

                otherwise
                    myError('YMA:scrollplot:invalidProperty','Unsupported property');

            end  % switch paramName
        end  % loop pvPairs
    catch
        if ~isempty(paramName),  paramName = [' ''' paramName ''''];  end
        myError('YMA:scrollplot:invalidHandle',['Error setting scrollplot property' paramName ':' char(10) lasterr]);
    end
%end  % processArgs  %#ok for Matlab 6 compatibility

%% Internal error processing
function myError(id,msg)
    v = version;
    if (v(1) >= '7')
        error(id,msg);
    else
        % Old Matlab versions do not have the error(id,msg) syntax...
        error(msg);
    end
%end  % myError  %#ok for Matlab 6 compatibility

%% Get ancestor figure - used for old Matlab versions that don't have a built-in ancestor()
function hObj = ancestor(hObj,type)
    if ~isempty(hObj) & ishandle(hObj)  %#ok for Matlab 6 compatibility
        %if ~isa(handle(hObj),type)  % this is best but always returns 0 in Matlab 6!
        if ~strcmpi(get(hObj,'type'),type)
            hObj = ancestor(get(handle(hObj),'parent'),type);
        end
    end
%end  % ancestor  %#ok for Matlab 6 compatibility

%% Helper function to extract first data value(s) from an array
function data = getFirstVals(vals)
    if isempty(vals)
        data = [];
    elseif iscell(vals)
        for idx = 1 : length(vals)
            thisVal = vals{idx};
            data(idx) = thisVal(1);  %#ok mlint - preallocate
        end
    else
        data = vals(:,1);
    end
%end  % getFirstVal  %#ok for Matlab 6 compatibility

%% Mouse movement outside the scroll patch area
function mouseOutsidePatch(hFig,inDragMode,hAx)  %#ok Hax is unused
    try
        % Restore the original figure pointer (probably 'arrow', but not necessarily)
        % On second thought, it should always be 'arrow' since zoom/pan etc. are disabled within hScroll
        %if ~isempty(hAx)
            % Only modify this within hScroll (outside the patch area) - not in other axes
            set(hFig, 'Pointer','arrow');
        %end
        oldPointer = getappdata(hFig, 'scrollplot_oldPointer');
        if ~isempty(oldPointer)
            %set(hFig, oldPointer{:});  % see comment above
            drawnow;
            rmappdataIfExists(hFig, 'scrollplot_oldPointer');
            if isappdata(hFig, 'scrollplot_mouseUpPointer')
                setappdata(hFig, 'scrollplot_mouseUpPointer',oldPointer);
            end
        end

        % Restore the original ButtonUpFcn callback
        if isappdata(hFig, 'scrollplot_oldButtonUpFcn')
            oldButtonUpFcn = getappdata(hFig, 'scrollplot_oldButtonUpFcn');
            axisComponent  = getappdata(hFig, 'scrollplot_oldButtonUpObj');
            if ~isempty(axisComponent)
                set(axisComponent, 'MouseReleasedCallback',oldButtonUpFcn);
            else
                set(hFig, 'WindowButtonUpFcn',oldButtonUpFcn);
            end
            rmappdataIfExists(hFig, 'scrollplot_oldButtonUpFcn');
        end

        % Additional cleanup
        rmappdataIfExists(hFig, 'scrollplot_mouseDownPointer');
        if ~inDragMode
            rmappdataIfExists(hFig, 'scrollplot_originalX');
            rmappdataIfExists(hFig, 'scrollplot_originalLimits');
        end
    catch
        % never mind...
        disp(lasterr);
    end
%end  % outsideScrollCleanup  %#ok for Matlab 6 compatibility

%% Mouse movement within the scroll patch area
function mouseWithinPatch(hFig,inDragMode,hAx,scrollPatch,cx,isOverBar)
    try
        % Separate actions for X,Y scrolling
        axName = get(hAx, 'userdata');
        if strcmpi(axName,'x')
            shapeStr = 'lrdrag';
        else
            shapeStr = 'uddrag';
        end
        dataStr = [axName 'Data'];
        limStr  = [axName 'Lim'];

        % If we have entered the scroll patch area for the first time
        axisComponent = getAxisComponent(hFig);
        if ~isempty(axisComponent)
            winUpFcn = get(axisComponent,'MouseReleasedCallback');
        else
            winUpFcn = get(hFig,'WindowButtonUpFcn');
        end
        if isempty(winUpFcn) | (~isequal(winUpFcn,@mouseUpCallback) & (~iscell(winUpFcn) | ~isequal(winUpFcn{1},@mouseUpCallback)))  %#ok for Matlab 6 compatibility

            % Set the ButtonUpFcn callbacks
            if ~isempty(winUpFcn)
                setappdata(hFig, 'scrollplot_oldButtonUpFcn',winUpFcn);
                setappdata(hFig, 'scrollplot_oldButtonUpObj',axisComponent);
            end
            if ~isempty(axisComponent)
                set(axisComponent, 'MouseReleasedCallback',{@mouseUpCallback,hFig});
            else
                set(hFig, 'WindowButtonUpFcn',@mouseUpCallback);
            end

            % Clear up potential junk that might confuse us later
            rmappdataIfExists(hFig, 'scrollplot_clickedBarIdx');
        end

        % If this is a drag movement (i.e., mouse button is clicked)
        if inDragMode

            % Act according to the dragged object
            if isempty(scrollPatch)
                scrollPatch = findobj(hAx, 'tag','scrollPatch');
            end
            scrollBarIdx = getappdata(hFig, 'scrollplot_clickedBarIdx');
            scrollBars = sort(findobj(hAx, 'tag','scrollBar'));
            %barsXs = cellfun(@(c)c(1),get(scrollBars,dataStr));  % cellfun is very limited on Matlab 6...
            barsXs = getFirstVals(get(scrollBars,dataStr));
            if barsXs(1)>barsXs(2)  % happens after dragging one bar beyond the other
                scrollBarIdx = 3 - scrollBarIdx;  % []=>[], 1=>2, 2=>1
                scrollBars = scrollBars([2,1]);
            end
            oldPatchXs = get(scrollPatch, dataStr);
            axLimits = get(hAx, limStr);
            cx = min(max(cx,axLimits(1)),axLimits(2));
            if isempty(scrollBarIdx)  % patch drag
                originalX = getappdata(hFig, 'scrollplot_originalX');
                originalLimits = getappdata(hFig, 'scrollplot_originalLimits');
                if ~isempty(originalLimits)
                    allowedDelta = [min(0,axLimits(1)-originalLimits(1)), max(0,axLimits(2)-originalLimits(2))];
                    deltaX = min(max(cx-originalX, allowedDelta(1)), allowedDelta(2));
                    if strcmpi(get(hAx,[axName 'Scale']), 'log')
                        newLimits = 10.^(log10(originalLimits) + deltaX);
                    else  % linear axis scale
                        newLimits = originalLimits + deltaX;
                    end
                    %fprintf('%.3f ',[cx-originalX, deltaX, originalLimits(1), newLimits(1), allowedDelta])
                    %fprintf('\n');
                    if strcmpi(axName,'x')
                        set(scrollPatch,   dataStr,newLimits([1,1,2,2]));
                    else
                        set(scrollPatch,   dataStr,newLimits([1,2,2,1]));
                    end
                    set(scrollBars(1), dataStr,newLimits([1,1]));
                    set(scrollBars(2), dataStr,newLimits([2,2]));
                    setappdata(hFig, 'scrollplot_originalLimits', newLimits);
                    setappdata(hFig, 'scrollplot_originalX', cx);
                    if deltaX ~= 0
                        delete(findall(0,'tag','scrollHelp'));
                    end
                end
            elseif (scrollBarIdx == 1)  % left/bottom bar drag
                set(scrollBars(scrollBarIdx), dataStr,[cx,cx]);
                if strcmpi(axName,'x')
                    set(scrollPatch, dataStr,[cx,cx, max(oldPatchXs)*[1,1]]);
                else
                    set(scrollPatch, dataStr,[cx, max(oldPatchXs)*[1,1], cx]);
                end
                delete(findall(0,'tag','scrollHelp'));
            else  % right/top bar drag
                set(scrollBars(scrollBarIdx), dataStr,[cx,cx]);
                if strcmpi(axName,'x')
                    set(scrollPatch, dataStr,[min(oldPatchXs)*[1,1], cx,cx]);
                else
                    set(scrollPatch, dataStr,[cx, min(oldPatchXs)*[1,1], cx]);
                end
                delete(findall(0,'tag','scrollHelp'));
            end

            % Modify the parent axes accordingly
            parentAx = getappdata(hAx, 'parent');
            newXLim = unique(get(scrollPatch,dataStr));
            if length(newXLim) == 2  % might be otherwise if bars merge!
                if size(newXLim,1)==2,  newXLim = newXLim';  end  % needs to be a column array
                set(parentAx, limStr,newXLim);
            end

            % Mode managers (zoom/pan etc.) modify the cursor shape, so we need to force ours...
            newPtr = getappdata(hFig, 'scrollplot_mouseDownPointer');
            if ~isempty(newPtr)
                setptr(hFig, newPtr);
            end

        else  % Normal mouse movement (no drag)

            % Modify the cursor shape
            oldPointer = getappdata(hFig, 'scrollplot_oldPointer');
            if isempty(oldPointer)
                % Preserve original pointer shape for future use
                setappdata(hFig, 'scrollplot_oldPointer',getptr(hFig));
            end
            if isOverBar
                setptr(hFig, shapeStr);
                setappdata(hFig, 'scrollplot_mouseDownPointer',shapeStr);
            else
                setptr(hFig, 'hand');
                setappdata(hFig, 'scrollplot_mouseDownPointer','closedhand');
            end
        end
        drawnow;
    catch
        % never mind...
        disp(lasterr);
    end
%end  % mouseWithinPatch  %#ok for Matlab 6 compatibility

%% Mouse movement callback function
function mouseMoveCallback(varargin)
    try
        try
            % Temporarily turn off log-axis warnings
            oldWarn = warning('off','MATLAB:Axes:NegativeDataInLogAxis');
        catch
            % never mind...
        end

        % Get the figure's current axes
        hFig = gcbf;
        if isempty(hFig) | ~ishandle(hFig),  return;  end  %#ok just in case..
        %hAx = get(hFig,'currentAxes');
        hAx = getCurrentScrollAx(hFig);
        inDragMode = isappdata(hFig, 'scrollplot_clickedBarIdx');

        % Exit if already in progress - don't want to mess everything...
        if isappdata(hFig,'scrollBar_inProgress'),  return;  end

        % Fix case of Mode Managers (pan, zoom, ...)
        try
            modeMgr = get(hFig,'ModeManager');
            hMode = modeMgr.CurrentMode;
            set(hMode,'ButtonDownFilter',@shouldModeBeInactiveFcn);
        catch
            % Never mind - either an old Matlab (no mode managers) or no mode currently active
        end

        % If mouse pointer is not currently over any scroll axes
        if isempty(hAx) %& ~inDragMode  %#ok for Matlab 6 compatibility
            % Perform cleanup
            mouseOutsidePatch(hFig,inDragMode,hAx);
        else

            % Check whether the curser is over any side bar
            scrollPatch = findobj(hAx, 'tag','scrollPatch');
            isOverBar = 0;
            cx = [];
            if ~isempty(scrollPatch)
                scrollPatch = scrollPatch(1);
                axName = get(hAx,'userdata');
                cp = get(hAx,'CurrentPoint');
                cx = cp(1,1);
                cy = cp(1,2);
                xlim = get(hAx,'Xlim');
                ylim = get(hAx,'Ylim');
                limits = get(hAx,[axName 'Lim']);
                barXs = unique(get(scrollPatch,[axName 'Data']));
                if strcmpi(get(hAx,[axName 'Scale']), 'log')
                    fuzz = 0.01 * diff(log(abs(limits)));  % tolerances (1%) in axes units
                    barXs = log10(barXs);
                    if strcmpi(axName,'x')
                        cx = log10(cx);
                    else
                        cy = log10(cy);
                    end
                else
                    fuzz = 0.01 * diff(limits);  % tolerances (1%) in axes units
                end
                if isempty(barXs),  return;  end
                %disp(abs(cy-barXs)')
                if strcmpi(axName,'x')
                    inXTest = any(barXs-fuzz < cx) & any(cx < barXs+fuzz);
                    inYTest = (ylim(1) < cy) & (cy < ylim(2));
                    isOverBar = any(abs(cx-barXs)<fuzz); %(barXs-fuzz < cx) & (cx < barXs+fuzz));
                else  % Y scroll
                    inXTest = (xlim(1) < cx) & (cx < xlim(2));
                    inYTest = any(barXs-fuzz < cy) & any(cy < barXs+fuzz);
                    isOverBar = any(abs(cy-barXs)<fuzz); %(barXs-fuzz < cy) & (cy < barXs+fuzz));
                    cx = cy;  % for use in mouseWithinPatch below
                end
                scrollPatch = scrollPatch(inXTest & inYTest);
                if strcmpi(get(hAx,[axName 'Scale']), 'log')
                    cx = 10^cx;  % used below
                end
            end

            % From this moment on, don't allow any interruptions
            setappdata(hFig,'scrollBar_inProgress',1);

            % If we're within the scroll patch area
            if ~isempty(scrollPatch) | inDragMode  %#ok for Matlab 6 compatibility
                mouseWithinPatch(hFig,inDragMode,hAx,scrollPatch,cx,isOverBar);
            else
                % Perform cleanup
                mouseOutsidePatch(hFig,inDragMode,hAx);
            end
        end

        % Try to chain the original WindowButtonMotionFcn (if available)
        try
            hgfeval(getappdata(hFig, 'scrollplot_oldButtonMotionFcn'));
        catch
            % Never mind...
        end
    catch
        % Never mind...
        disp(lasterr);
    end
    rmappdataIfExists(hFig,'scrollBar_inProgress');

    % Restore original warnings (if available/possible)
    try
        warning(oldWarn);
    catch
        % never mind...
    end
%end  % mouseMoveCallback  %#ok for Matlab 6 compatibility

%% Mouse click down callback function
function mouseDownCallback(varargin)
    try
        % Modify the cursor shape (close hand)
        hFig = gcbf;  %varargin{3};
        if isempty(hFig) & ~isempty(varargin)  %#ok for Matlab 6 compatibility
            hFig = ancestor(varargin{1},'figure');
        end
        if isempty(hFig) | ~ishandle(hFig),  return;  end  %#ok just in case..
        setappdata(hFig, 'scrollplot_mouseUpPointer',getptr(hFig));
        newPtr = getappdata(hFig, 'scrollplot_mouseDownPointer');
        if ~isempty(newPtr)
            setptr(hFig, newPtr);
        end

        % Determine the clicked object: patch, left bar or right bar
        hAx = get(hFig,'currentAxes');
        if isempty(hAx),  return;  end
        axName = get(hAx,'userdata');
        limits = get(hAx,[axName 'Lim']);
        cp = get(hAx,'CurrentPoint');

        % Check whether the curser is over any side bar
        barXs = [-inf,inf];
        scrollBarIdx = [];
        scrollPatch = findobj(hAx, 'tag','scrollPatch');
        if ~isempty(scrollPatch)
            scrollPatch = scrollPatch(1);
            dataStr = [axName 'Data'];
            barXs = unique(get(scrollPatch,dataStr));
            if isempty(barXs),  return;  end
            if strcmpi(axName,'x')
                cx = cp(1,1);
            else  % Y scroll
                cx = cp(1,2);  % actually, this gets the y value...
            end
            if strcmpi(get(hAx,[axName 'Scale']), 'log')
                fuzz = 0.01 * diff(log(abs(limits)));  % tolerances (1%) in axes units
                barXs = log10(barXs);
                cx = log10(cx);
            else
                fuzz = 0.01 * diff(limits);  % tolerances (1%) in axes units
            end
            inTest = abs(cx-barXs)<fuzz; %(barXs-fuzz < cx) & (cx < barXs+fuzz);
            scrollBarIdx = find(inTest);
            scrollBarIdx = scrollBarIdx(min(1:end));  %#ok - find(x,1) is unsupported on Matlab 6!
            if strcmpi(get(hAx,[axName 'Scale']), 'log')
                cx = 10^cx;       % used below
                barXs = 10.^barXs; % used below
            end

            % Re-sort side bars (might have been dragged one over the other...)
            scrollBars = sort(findobj(hAx, 'tag','scrollBar'));
            %barsXs = cellfun(@(c)c(1),get(scrollBars,'xdata'));  % cellfun is very limited on Matlab 6...
            barsXs = getFirstVals(get(scrollBars,dataStr));
            if barsXs(1)>barsXs(2)  % happens after dragging one bar beyond the other
                set(scrollBars(1), dataStr,barsXs(2)*[1,1]);
                set(scrollBars(2), dataStr,barsXs(1)*[1,1]);
            end
        end
        setappdata(hFig, 'scrollplot_clickedBarIdx',scrollBarIdx);
        setappdata(hFig, 'scrollplot_originalX',cx);
        setappdata(hFig, 'scrollplot_originalLimits',barXs);
    catch
        % Never mind...
        disp(lasterr);
    end
%end  % mouseDownCallback  %#ok for Matlab 6 compatibility

%% Mouse click up callback function
function mouseUpCallback(varargin)
    try
        % Restore the previous (pre-click) cursor shape
        hFig = gcbf;  %varargin{3};
        if isempty(hFig) & ~isempty(varargin)  %#ok for Matlab 6 compatibility
            hFig = varargin{3};
            if isempty(hFig)
                hFig = ancestor(varargin{1},'figure');
            end
        end
        if isempty(hFig) | ~ishandle(hFig),  return;  end  %#ok just in case..
        if isappdata(hFig, 'scrollplot_mouseUpPointer')
            mouseUpPointer = getappdata(hFig, 'scrollplot_mouseUpPointer');
            set(hFig,mouseUpPointer{:});
            rmappdata(hFig, 'scrollplot_mouseUpPointer');
        end

        % Cleanup data no longer needed
        rmappdataIfExists(hFig, 'scrollplot_clickedBarIdx');
        rmappdataIfExists(hFig, 'scrollplot_originalX');
        rmappdataIfExists(hFig, 'scrollplot_originalLimits');

        % Try to chain the original WindowButtonUpFcn (if available)
        oldFcn = getappdata(hFig, 'scrollplot_oldButtonUpFcn');
        if ~isempty(oldFcn) & ~isequal(oldFcn,@mouseUpCallback) & (~iscell(oldFcn) | ~isequal(oldFcn{1},@mouseUpCallback))  %#ok for Matlab 6 compatibility
            hgfeval(oldFcn);
        end
    catch
        % Never mind...
        disp(lasterr);
    end
%end  % mouseUpCallback  %#ok for Matlab 6 compatibility

%% Remove appdata if available
function rmappdataIfExists(handle, name)
    if isappdata(handle, name)
        rmappdata(handle, name)
    end
%end  % rmappdataIfExists  %#ok for Matlab 6 compatibility

%% Get the figure's java axis component
function axisComponent = getAxisComponent(hFig)
    try
        if isappdata(hFig, 'scrollplot_axisComponent')
            axisComponent = getappdata(hFig, 'scrollplot_axisComponent');
        else
            axisComponent = [];
            javaFrame = get(hFig,'JavaFrame');
            axisComponent = get(javaFrame,'AxisComponent');
            axisComponent = handle(axisComponent, 'CallbackProperties');
            if ~isprop(axisComponent,'MouseReleasedCallback')
                axisComponent = [];  % wrong axisComponent...
            else
                setappdata(hFig, 'scrollplot_axisComponent',axisComponent);
            end
        end
    catch
        % never mind...
    end
%end  % getAxisComponent  %#ok for Matlab 6 compatibility

%% Get the scroll axes that the mouse is currently over
function hAx = getCurrentScrollAx(hFig)
    try
        hAx = [];
        scrollAxes = findall(hFig, 'tag','scrollAx');
        if isempty(scrollAxes),  return;  end  % should never happen...
        for axIdx = 1 : length(scrollAxes)
            scrollPos(axIdx,:) = getPixelPos(scrollAxes(axIdx));  %#ok mlint - preallocate
        end
        cp = get(hFig, 'CurrentPoint');  % in Matlab pixels
        inXTest = (scrollPos(:,1) <= cp(1)) & (cp(1) <= scrollPos(:,1)+scrollPos(:,3));
        inYTest = (scrollPos(:,2) <= cp(2)) & (cp(2) <= scrollPos(:,2)+scrollPos(:,4));
        hAx = scrollAxes(inXTest & inYTest);
        hAx = hAx(min(1:end));  % ensure we return no more than asingle hAx!
    catch
        % never mind...
        disp(lasterr);
    end
%end  % getCurrentScrollAx  %#ok for Matlab 6 compatibility

%% Get pixel position of an HG object
function pos = getPixelPos(hObj)
    try
        % getpixelposition is unvectorized unfortunately! 
        pos = getpixelposition(hObj);
    catch
        % Matlab 6 did not have getpixelposition nor hgconvertunits so use the old way...
        pos = getPos(hObj,'pixels');
    end
%end  % getPixelPos  %#ok for Matlab 6 compatibility

%% Get position of an HG object in specified units
function pos = getPos(hObj,units)
    % Matlab 6 did not have hgconvertunits so use the old way...
    oldUnits = get(hObj,'units');
    if strcmpi(oldUnits,units)  % don't modify units unless we must!
        pos = get(hObj,'pos');
    else
        set(hObj,'units',units);
        pos = get(hObj,'pos');
        set(hObj,'units',oldUnits);
    end
%end  % getPos  %#ok for Matlab 6 compatibility

%% Temporary setting property value for a read-only property
function setOnce(hndl,propName,propValue)
    try
        prop = findprop(hndl,propName);
        oldSetState = get(prop,'AccessFlags.PublicSet');
        set(prop,'AccessFlags.PublicSet','on');
        set(hndl,propName,propValue);
        set(prop,'AccessFlags.PublicSet',oldSetState);
    catch
        % Never mind...
    end
%end  % setOnce  %#ok for Matlab 6 compatibility

%% Callback for parent axes property changes
function parentAxesChanged(schemaProp, eventData, hFig, hAx, hScrollPatch, hScrollBars, propName)  %#ok - first 2 are unused
    try
        if isempty(hFig) | ~ishandle(hFig),  return;  end  %#ok just in case..
        newPropVal = get(hAx,propName);
        hScroll = get(hScrollPatch, 'Parent');
        axName  = get(hScroll, 'userdata');
        if isappdata(hFig,'scrollBar_inProgress')
            % Update the special prop values
            if strcmpi(propName,[axName,'Lim'])
                setOnce(handle(hScroll),'ScrollMin',newPropVal(1));
                setOnce(handle(hScroll),'ScrollMax',newPropVal(2));
            end
            return;
        end
        switch propName
            case 'XLim'
                if strcmpi(axName,'x')
                    set(hScrollPatch,   'XData',newPropVal([1,1,2,2]));
                    set(hScrollBars(1), 'Xdata',newPropVal([1,1]));
                    set(hScrollBars(2), 'Xdata',newPropVal([2,2]));
                    setOnce(handle(hScroll),'ScrollMin',newPropVal(1));
                    setOnce(handle(hScroll),'ScrollMax',newPropVal(2));
                end

            case 'YLim'
                if strcmpi(axName,'y')
                    set(hScrollPatch,   'YData',newPropVal([1,2,2,1]));
                    set(hScrollBars(1), 'Ydata',newPropVal([1,1]));
                    set(hScrollBars(2), 'Ydata',newPropVal([2,2]));
                    setOnce(handle(hScroll),'ScrollMin',newPropVal(1));
                    setOnce(handle(hScroll),'ScrollMax',newPropVal(2));
                end

            case 'Position'
                if strcmpi(axName,'x')
                    newScrollPos = newPropVal .* [1, 1, 1, 0.10/0.80];
                    newScrollPos = newScrollPos - [0, 0.20/0.80*newPropVal(4), 0, 0];
                else  % Y scroll
                    newScrollPos = newPropVal .* [1, 1, 0.10/0.80, 1];
                    newScrollPos = newScrollPos + [(1+0.05/0.80)*newPropVal(3), 0, 0, 0];
                end
                axUnits = get(hAx, 'Units');  % units might be modified by Mode Managers bypassing listeners!
                set(hScroll, 'Units',axUnits, 'Position',newScrollPos);

            case {'Units','Parent','XDir','YDir','XScale','YScale'}
                set(hScroll, propName,newPropVal);

            otherwise
                % Do nothing...
        end
    catch
        % never mind...
        disp(lasterr);
    end
%end  % parentAxesChanged  %#ok for Matlab 6 compatibility

%% Determine whether a current mode manager should be active or not (filtered)
function shouldModeBeInactive = shouldModeBeInactiveFcn(hObj, eventData)  %#ok - eventData is unused
    try
        shouldModeBeInactive = 0;
        hFig = ancestor(hObj,'figure');
        hScrollAx = getCurrentScrollAx(hFig);
        shouldModeBeInactive = ~isempty(hScrollAx);
    catch
        % never mind...
        disp(lasterr);
    end
%end  % shouldModeBeActiveFcn  %#ok for Matlab 6 compatibility

%% hgfeval replacement for Matlab 6 compatibility
function hgfeval(fcn,varargin)
    if isempty(fcn),  return;  end
    if iscell(fcn)
        feval(fcn{1},varargin{:},fcn{2:end});
    elseif ischar(fcn)
        evalin('base', fcn);
    else
        feval(fcn,varargin{:});
    end
%end  % hgfeval  %#ok for Matlab 6 compatibility

%% Axis to screen coordinate transformation
function T = axis2Screen(ax)
%   computes a coordinate transformation T = [xo,yo,rx,ry] that
%   relates the normalized axes coordinates [xa,ya] of point [xo,yo]
%   to its screen coordinate [xs,ys] (in the root units) by:
%       xs = xo + rx * xa
%       ys = yo + ry * ya
%
%   See also SISOTOOL
%
%   Note: this is a modified internal function within moveptr()

    % Get axes normalized position in figure
    T = getPos(ax,'normalized');

    % Loop all the way up the hierarchy to the root
    % Note: this fixes a bug in Matlab 7's moveptr implementation
    parent = get(ax,'Parent');
    while ~isempty(parent)
        % Transform norm. axis coord -> parent coord.
        if isequal(parent,0)
            parentPos = get(0,'ScreenSize');  % Preserve screen units
        else
            parentPos = getPos(parent, 'normalized');  % Normalized units
        end
        T(1:2) = parentPos(1:2) + parentPos(3:4) .* T(1:2);
        T(3:4) = parentPos(3:4) .* T(3:4);
        parent = get(parent,'Parent');
    end
%end  % axis2Screen  %#ok for Matlab 6 compatibility

%% Get centran axis location
function axisCoord = getCenterCoord(hAx, axName)
    limits = get(hAx, [axName 'Lim']);
    if strcmpi(get(hAx,[axName 'Scale']), 'log')
        axisCoord = sqrt(abs(prod(limits)));  %=10^mean(log10(abs(limits)));
    else
        axisCoord = mean(limits);
    end
%end  %getCenterCoord  %#ok for Matlab 6 compatibility

%% Get normalized axis coordinates
function normCoord = getNormCoord(hAx, axName, curPos)
    limits = get(hAx, [axName 'Lim']);
    if strcmpi(get(hAx,[axName 'Scale']), 'log')
        normCoord = (log2(curPos) - log2(limits(1))) / diff(log2(limits));
    else
        normCoord = (curPos-limits(1)) / diff(limits);
    end
%end  % getNormCoord %#ok for Matlab 6 compatibility

%% moveptr replacement for Matlab 6 compatibility
function moveptr(hAx, x, y)
    % Compute normalized axis coordinates
    NormX = getNormCoord(hAx, 'x', x);
    NormY = getNormCoord(hAx, 'y', y);

    % Compute the new coordinates in screen units
    Transform = axis2Screen(hAx);
    NewLoc = Transform(1:2) + Transform(3:4) .* [NormX NormY];

    % Move the pointer
    set(0,'PointerLocation',NewLoc);
%end  % moveptr  %#ok for Matlab 6 compatibility

%% UiContextMenu callback - Move cursor to center of requested element
function moveCursor(varargin)
    try
        % Get the x,y location of the center of the requested object
        hScroll = handle(gca);
        hObj = get(gcbo,'UserData');
        x = mean(get(hObj,'XData'));
        y = mean(get(hObj,'YData'));

        % Move the mouse pointer to that location
        % Note: Matlab 6 did not have moveptr() so we use a local version above
        %moveptr(hScroll, 'init');
        %moveptr(hScroll, 'move', x, y);
        moveptr(hScroll, x, y);

        % Call mouseMoveCallback to update the pointer shape
        mouseMoveCallback;
        drawnow;
    catch
        % Never mind...
        disp(lasterr);
    end
%end  % moveCursor  %#ok for Matlab 6 compatibility

%% Callback when scroll axes are deleted
function deleteScrollAx(varargin)
    try
        % Update the parent Axes position
        hScroll = varargin{1};
        updateParentPos([],[],hScroll,'off');
        % Note: no need to remove hAx listeners since these are destroyed along with hScroll
    catch
        % Never mind - continue deletion process...
    end
%end  % deleteScrollAx  %#ok for Matlab 6 compatibility

%% Update parent figure position based on scroll axes visibility
function updateParentPos(schemaProp, eventData, hScroll,scrollVisibility)  %#ok first 2 params are unused
    try
        if nargin<4
            scrollVisibility = get(hScroll, 'visible');
        end

        % Update the parent Axes position
        hAx = get(hScroll, 'ParentAxesHandle');
        axPos = get(hAx,'position');
        axName = get(hScroll, 'userdata');
        hLabel = get(hAx, [axName 'Label']);
        set(hLabel, 'units','normalized');
        oldPos = get(hLabel, 'position');  % Get the label position before the axes change

        if strcmpi(scrollVisibility,'off')
            ax_dy1 =  1/0.80;
            ax_dy2 = -1/0.80;
            label_delta = 0.20/0.80;
        else  %'on'
            ax_dy1 = 0.80;
            ax_dy2 = 1;
            label_delta = -0.20/0.80;
        end

        if strcmpi(axName,'x')
            newPlotPos   = axPos .* [1, 1, 1, ax_dy1] + [0, 0.20*axPos(4)*ax_dy2, 0, 0];
        else  % Y scroll
            newPlotPos   = axPos .* [1, 1, ax_dy1, 1];
        end
        set(hAx, 'Position',newPlotPos);

        % Fix label position(s)
        if strcmpi(axName,'x')
            if ~isempty(oldPos) & oldPos(2)<0  %#ok for Matlab 6 compatibility
                % Only fix if the X label is on the bottom (usually yes)
                set(hLabel, 'position',oldPos+[0,label_delta,0]);
            end
        else  % Y scroll
            if ~isempty(oldPos) & oldPos(1)>0  %#ok for Matlab 6 compatibility
                % Only fix if the Y label is on the right side (usually not)
                set(hLabel, 'position',oldPos-[label_delta,0,0]);
            end
        end

        % Show/hide all the axes children (scroll patch, side-bars, text)
        set(findall(hScroll), 'Visible',scrollVisibility);

        % axisComponent gets re-created, so clear the cache
        hFig = ancestor(hScroll,'figure');
        rmappdata(hFig, 'scrollplot_axisComponent');
    catch
        % Never mind...
    end
%end  % updateParentPos  %#ok for Matlab 6 compatibility

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%  TODO  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% - maybe add a blue diamond or a visual handle in center of side-bars?
% - fix or bypass Matlab 6 OpenGL warning due to patch FaceAlpha property

Contact us