7

MATLAB provides the addlistener function.

Listeners allow us to keep track of changes of an object's properties and act upon them. For example, we can create a very simple listener that will display a message in the command window when the 'YLim' property of an axes object is changed:

% Example using axes
ax = axes();
addlistener(ax, 'YLim', 'PostSet', @(src, evnt)disp('YLim changed'));

Try panning the axes or zooming in/out and see what happens. This works fine.

I need to do the same but using an uiaxes instead.

Unfortunately, it looks like we are not allowed to do so. Try running the following example:

% Example using uiaxes
ax = uiaxes();
addlistener(ax, 'YLim', 'PostSet', @(src, evnt)disp('YLim changed'));

It throws this error:

Error using matlab.ui.control.UIAxes/addlistener While adding a PostSet listener, property 'YLim' in class 'matlab.ui.control.UIAxes' is not defined to be SetObservable.

I've read the articles Listen for Changes to Property Values and Observe Changes to Property Values and I learned that a property must be declared as SetObservable to allow being listened:

classdef PropLis < handle
   properties (SetObservable)
      ObservedProp = 1 % <-- Observable Property
   end
end

I've tried taking a look at the UIAxes class definition via edit matlab.ui.control.UIAxes but it's not possible because it's a P-file.

If 'YLim' is not observable then how can I keep track of changes in this property?

I'm using App Designer in MATLAB R2018b.

Dev-iL
  • 23,742
  • 7
  • 57
  • 99
codeaviator
  • 2,545
  • 17
  • 42
  • 1
    Wow. Tough question. The _Observability_ of traditional `axes` is already a bit of touch and go (read [Loren's post](https://blogs.mathworks.com/loren/2015/12/14/axes-limits-scream-louder-i-cant-hear-you/) for more details), although I successfully managed to attach working listeners to those. The technique didn't work for the new `uiaxes` but this post on Matlab Central may offer you a clunky workaround for now: https://uk.mathworks.com/matlabcentral/answers/433573-app-designer-how-to-link-axes-of-uiaxes-components-to-enable-user-to-zoom-all-plots-together-in-ap – Hoki Dec 18 '18 at 11:15

2 Answers2

7

We should attach the listener to the internal Axes object, and not the UIAxes itself. Try this:

hFig = uifigure();
hAx = uiaxes(hFig);
addlistener(struct(hAx).Axes, 'YLim', 'PostSet', @(src, evnt)disp("YLim changed"));
hAx.YLim = [0 2];

In case anybody is wondering, I found this via trial and error.

Tested on R2018a & R2018b.

Dev-iL
  • 23,742
  • 7
  • 57
  • 99
  • 2
    rhooo ... very nice. I tried to dig but was kicked out of all these protected properties. The trick of using `struct` to bypass MATLAB protections ... Genius! – Hoki Dec 19 '18 at 17:34
  • Let's just say that I started with a way more complicated approach (trying to interact with the WebGL renderer that draws the axes on the HTML canvas) before implementing the KISS principle which you see here :) – Dev-iL Dec 19 '18 at 17:35
  • Wow. Nice hack! The warning message says it all: `Warning: Calling STRUCT on an object prevents the object from hiding its implementation details and should thus be avoided.` You just gave me superpowers! :) – codeaviator Dec 19 '18 at 17:35
  • 1
    @codeaviator just the sort of voices it's best to ignore (`warning('off','MATLAB:structOnObject');`) :) – Dev-iL Dec 19 '18 at 17:39
  • Your nickname is Dev-iL but you are a God – Carlos Borau Apr 16 '21 at 09:52
  • Hehe @Carlos thanks for the compliment :) To be considered an above-average hacker would be enough for me ;) – Dev-iL Apr 16 '21 at 14:14
0

Thank you so much for this solution! I was having a real problem with zooming in on 3D data on a UIAxes. The 3D axes contained a .png background raster map at z=0 (plotted as a surface) and the 3D position of a UAV flight in x-y-x. When I would zoom in, the z would zoom as well and the new z limits would exclude the map I wanted always displayed. What was odd, is that setting

app.UIAxes2.Interactions = [zoomInteraction('Dimensions','xy')];

would correct the problem when zooming with the scroll wheel on my mouse, but if I selected the zoom toolbar button (clicking to zoom), it would still zoom in z. Really frustrating.

To get around this, I used your example, but added the listener to the 'ZLim' and made a callback function that would look at all the elements of the plot, and reset ZLim to include all the data whenever the ZLim changed.

warning('off','MATLAB:structOnObject');
addlistener(struct(app.UIAxes2).Axes, 'ZLim', 'PostSet', @(src,evnt)mapholdaltlims(app,app.UIAxes2));


    function [inclusivezlim] = mapholdaltlims(app,ax)
        objwithz = findobj(app.UIAxes2.Children,'-property','ZData');
        currmin_z = 0;
        currmax_z = 0;
        for i = 1:length(objwithz)
            currmin_z = min([min(min(objwithz(i).ZData)), currmin_z]);%Need double mins because data could be 2d surface
            currmax_z = max([max(max(objwithz(i).ZData)), currmax_z]);
        end
        inclusivezlim = [currmin_z currmax_z];
        ax.ZLim = inclusivezlim;
        %disp('Updated the limits')
    end

Man, what a pain this was. I am glad it now works. Thanks again.

Michael
  • 15
  • 4