%% New in r2021a: LimitsChangedFcn
% Matlab Central Community Highlight
% Author: Adam Danz
% 15 April 2021;  vs 1.2
%
%% New in r2021a, LimitsChangedFcn
% LimitsChangedFcn is a callback function that responds to changes to axis limits (release notes).  The function responds to axis interaction such as panning and zooming, programmatically setting the axis limits, or when axis limits are automatically adjusted by other processes.
% LimitsChangedFcn is a property of ruler objects which are properties of axes and can be independently set for each axis. For example,
% ax = gca();
% ax.XAxis.LimitsChangedFcn = ... % Responds to changes to XLim
% ax.YAxis.LimitsChangedFcn = ... % Responds to changes to YLim
% ax.ZAxis.LimitsChangedFcn = ... % Responds to changes to ZLim
% Previously, a listener could be assigned to respond to changes to axis limits.  Here are some examples.
% Update refline when axis limits change
% Update axis2 limits when axis1 limits change
% However, LimitsChangedFcn responds more reliably than a listener that responds to setting/getting axis limits. For example, after zooming or panning the axes in the demo below, the listener does not respond to the Restore View button in the axis toolbar but LimitsChangedFcn does! After restoring the view, try zooming out which does not result in changes to axis limits yet the listener will respond but the LimitsChangedFcn will not. Adding objects to axes after an axis-limit listener is set will not trigger the listener even if the added object expands the axis limits ( why not? ) but LimitsChangedFcn will!
% ax = gca();
% ax.UserData.Listener = addlistener(ax,'XLim','PostSet',@(~,~)disp('Listener'));
% ax.XAxis.LimitsChangedFcn = @(~,~)disp('LimitsChangedFcn')
%
%% How to use LimitsChangedFcn
% The LimitsChangedFcn works like any other callback.  For review,
% Callback Definition
% Write Callbacks for Apps
% The first input to the LimitsChangedFcn callback function is the handle to the axis ruler object that was changed.
% The second input is a structure that contains the old and new limits. For example,
%   LimitsChanged with properties:
%
%     OldLimits: [0 1]
%     NewLimits: [0.25 0.75]
%        Source: [1×1 NumericRuler]
%     EventName: 'LimitsChanged'
% Importantly, since LimitsChangedFcn is a property of the axis rulers rather than the axis object, changes to the axes may clear the LimitsChangedFcn property if the axes aren't held using hold on.  For example,
% % Axes not held
% ax = gca();
% ax.XAxis.LimitsChangedFcn = @(ruler,~)title(ancestor(ruler,'axes'),'LimitsChangedFcn fired!');
% plot(ax, 1:5, rand(1,5), 'o')
% ax.XAxis.LimitsChangedFcn
%
% ans =
%     0×0 empty char array
%
% % Axes held
% ax = gca();
% hold(ax,'on')
% ax.XAxis.LimitsChangedFcn = @(ruler,~)title(ancestor(ruler,'axes'),'LimitsChangedFcn fired!');
% plot(ax, 1:5, rand(1,5), 'o')
% ax.XAxis.LimitsChangedFcn
%
% ans =
%   function_handle with value:
%     @(ruler,~)title(ancestor(ruler,'axes'),'LimitsChangedFcn fired!')
%
%
%% Demo
% In this simple app a LimitsChangedFcn callback function is assigned to the x and y axes.  The function does two things:
% Text boxes showing the current axis limits are updated
% The prying eyes that are centered on the axes will move to the new axis center
%
% Create app

h.fig = uifigure(Name="LimitsChangedFcn Demo", ...
    Resize="off");
h.fig.Position(3:4) = [500,260];
movegui(h.fig)
h.ax = uiaxes(h.fig,...
    Units="pixels", ...
    Position=[200 26 250 208], ...
    Box="on");
grid(h.ax,"on")
title(h.ax,"I'm following you!")
h.eyeballs = text(h.ax, .5, .5, ...
    char([55357 56385 55357 56385]), ...
    HorizontalAlignment="center", ...
    FontSize=40);
h.label = uilabel(h.fig, ...
    Text="Axis limits", ...
    Position=[25 212 160 15], ...
    FontWeight="bold",...
    HorizontalAlignment="center");
h.xtxt = uitextarea(h.fig, ...
    position=[25 191 160 20], ...
    HorizontalAlignment="center", ...
    WordWrap="off", ...
    Editable="off",...
    FontName=get(groot, 'FixedWidthFontName'));
h.ytxt = uitextarea(h.fig, ...
    position=[25 165 160 20], ...
    HorizontalAlignment="center", ...
    WordWrap="off", ...
    Editable="off", ...
    FontName=get(groot, 'FixedWidthFontName'));
h.label = uilabel(h.fig, ...
    Text=['X',newline,newline,'Y'], ...
    Position=[10 170 15 38], ...
    FontWeight="bold");

% Set LimitsChangedFcn of x and y axes

h.ax.XAxis.LimitsChangedFcn = @(hObj,data)limitsChangedCallbackFcn(hObj,data,h,'x');
h.ax.YAxis.LimitsChangedFcn = @(hObj,data)limitsChangedCallbackFcn(hObj,data,h,'y');

% Update text fields

xlim(h.ax, [-100,100])
ylim(h.ax, [-100,100])

%
% Define LimitsChangedFcn

function limitsChangedCallbackFcn(rulerHand, limChgData, handles, xy)
% limitsChangedCallbackFcn() responds to changes to x or y axis limits.
% - rulerHand: Ruler handle for x or y axis that was changed (not used in this demo)
% - limChgData: LimitsChanged data structure
% - handles: structure of App handles
% - xy: either 'x' or 'y' identifying rulerHand
switch lower(xy)
    case 'x'
        textHandle = handles.xtxt;
        positionIndex = 1; 
    case 'y'
        textHandle = handles.ytxt;
        positionIndex = 2; 
    otherwise
        error('xy is a character ''x'' or ''y''.')
end
% Update text boxes showing rounded axis limits
textHandle.Value = sprintf('[%.3f, %.3f]',limChgData.NewLimits);
% Move the eyes to the new center position
handles.eyeballs.Position(positionIndex) = limChgData.NewLimits(1)+range(limChgData.NewLimits)/2; % for linear scales only!
drawnow
end

%