Undocumented Matlab
  • SERVICES
    • Consulting
    • Development
    • Training
    • Gallery
    • Testimonials
  • PRODUCTS
    • IQML: IQFeed-Matlab connector
    • IB-Matlab: InteractiveBrokers-Matlab connector
    • EODML: EODHistoricalData-Matlab connector
    • Webinars
  • BOOKS
    • Secrets of MATLAB-Java Programming
    • Accelerating MATLAB Performance
    • MATLAB Succinctly
  • ARTICLES
  • ABOUT
    • Policies
  • CONTACT
  • SERVICES
    • Consulting
    • Development
    • Training
    • Gallery
    • Testimonials
  • PRODUCTS
    • IQML: IQFeed-Matlab connector
    • IB-Matlab: InteractiveBrokers-Matlab connector
    • EODML: EODHistoricalData-Matlab connector
    • Webinars
  • BOOKS
    • Secrets of MATLAB-Java Programming
    • Accelerating MATLAB Performance
    • MATLAB Succinctly
  • ARTICLES
  • ABOUT
    • Policies
  • CONTACT

Using linkaxes vs. linkprop

July 22, 2015 8 Comments

One of my clients recently asked me to solve a very peculiar problem: He had several axes and was using Matlab’s builtin linkaxes function to link their axis limits. However, it didn’t behave quite the way that he expected. His axes were laid out as 2×2 subplots, and he wanted the two columns to be independently linked in the X axis, and the two rows to be independently linked in the Y axis:

% Prepare the axes
ax(1,1) = subplot(2,2,1);
ax(1,2) = subplot(2,2,2);
ax(2,1) = subplot(2,2,3);
ax(2,2) = subplot(2,2,4);
% Plot something
x = 0 : 0.01 : 10;
line(x, sin(x),   'Parent',ax(1,1));
line(x, sin(2*x), 'Parent',ax(1,2));
line(x, cos(x),   'Parent',ax(2,1));
line(x, cos(5*x), 'Parent',ax(2,2));
% Link the relevant axes
linkaxes(ax(:,1),'x');  % left column
linkaxes(ax(:,2),'x');  % right column
linkaxes(ax(1,:),'y');  % top row
linkaxes(ax(2,:),'y');  % bottom row

% Prepare the axes ax(1,1) = subplot(2,2,1); ax(1,2) = subplot(2,2,2); ax(2,1) = subplot(2,2,3); ax(2,2) = subplot(2,2,4); % Plot something x = 0 : 0.01 : 10; line(x, sin(x), 'Parent',ax(1,1)); line(x, sin(2*x), 'Parent',ax(1,2)); line(x, cos(x), 'Parent',ax(2,1)); line(x, cos(5*x), 'Parent',ax(2,2)); % Link the relevant axes linkaxes(ax(:,1),'x'); % left column linkaxes(ax(:,2),'x'); % right column linkaxes(ax(1,:),'y'); % top row linkaxes(ax(2,:),'y'); % bottom row

The problem was that the plots didn’t behave as expected: when zooming in on the bottom-left axes, for example, only the bottom-right axes was updated (Y-limits synced), whereas the top-left axes’ X-limits remained unchanged:

Badly-synced axes limits
Badly-synced axes limits


Apparently, the second set of two linkaxes commands (to sync the rows’ Y-limits) overrode the first set of two linkaxes commands (to sync the columns’ X-limits).

Analysis

The reason for this unexpected behavior is that under the hood, linkaxes attaches property-change listeners to the corresponding axes, and stores these listeners in the axes’ hidden ApplicationData property (which is typically accessible via the getappdata / setappdata / isappdata / rmappdata set of functions). Specifically, up to a certain Matlab release (R2013b?), the listeners were placed in a field called ‘listener__’, and since then in a field called ‘graphics_linkaxes’. In either case, the field name was constant.
Therefore, when we placed the first set of linkaxes commands, the axes were correctly synced vertically (ax(1,1) with ax(2,1) in their X-limits, and similarly ax(1,2) with ax(2,2)). But when we placed the second set of linkaxes commands, the internal field in the axes’ ApplicationData property was overriden with the new listeners (that synced the rows’ Y-limits).
It so happens that Matlab listeners have a very nasty feature of being deleted when they are no longer referenced anywhere (within a workspace variable or object property). So when we overrode the first set of listener handles, we effectively deleted them, as if they were never set in the first place.
Some people may possibly complain about both issues at this point:

  • That Matlab listeners get deleted so easily without so much as a console warning, and certainly against naive intuition.
  • That repeated calls to linkaxes should override (rather than complement) each other.

As a side note, the addlistener function creates a listener and then persists it in the object’s hidden AutoListeners__ property. However, unlike the linkaxes behavior, addlistener‘s listener handles are always appended to AutoListeners__‘s contents, rather than replacing it. This ensures that all listeners are accessible and active until their container is deleted or they are specifically modified/removed. I wish that linkaxes used this mechanism, rather than its current ApplicationData one.

Workaround: linkprop

Luckily, there is a very easy and simple workaround, namely to use linkprop rather than linkaxes. The linkprop function is a lower-level function that creates a property-change listener that syncs corresponding properties in any specified array of object handles. In fact, linkaxes uses linkprop in order to create the necessary listeners. In our case, we can use linkprop directly, to independently attach such listeners to the axes’ XLim and YLim properties. We just need to ensure that all these listeners remain accessible to Matlab throughout the corresponding objects’ life-cycle. This is easily done using ApplicationData, as is done by linkaxes.m but in a smarter manner that does not override the previous values. The benefit of this is that when the axes are deleted, then so are the listeners; as long as the axes are accessible then so are the listeners. We just need to ensure that we don’t override these listener values:

setappdata(ax(1,1), 'YLim_listeners', linkprop(ax(1,:),'YLim'));
setappdata(ax(2,1), 'YLim_listeners', linkprop(ax(2,:),'YLim'));
setappdata(ax(1,1), 'XLim_listeners', linkprop(ax(:,1),'XLim'));
setappdata(ax(1,2), 'XLim_listeners', linkprop(ax(:,2),'XLim'));

setappdata(ax(1,1), 'YLim_listeners', linkprop(ax(1,:),'YLim')); setappdata(ax(2,1), 'YLim_listeners', linkprop(ax(2,:),'YLim')); setappdata(ax(1,1), 'XLim_listeners', linkprop(ax(:,1),'XLim')); setappdata(ax(1,2), 'XLim_listeners', linkprop(ax(:,2),'XLim'));

This results in the expected behavior:

properly-linked axes
properly-linked axes

Conclusions

The design decision by MathWorks to automatically delete Matlab listeners as soon as their reference count is zeroed and they get garbage-collected, causes a myriad of unexpected run-time behaviors, one of which is exemplified in today’s post on linkaxes. This would still have not caused any problem had the developers of linkaxes been aware of this listener feature and taken care to store the linked listener handles in an accumulating repository (e.g., adding the listener handle to an array of existing handles, rather than replacing a scalar handle).
Luckily, now that we know how Matlab listeners behave, we can easily identify abnormal behavior that results from listener handles going out of scope, and can easily take steps to persist the handles somewhere, so that they will remain active.
I wish to stress here that the listeners’ limited scope is fully documented in several places in the documentation (e.g., here as well as the linkprop doc page). The non-additive behavior of linkaxes is also documented, albeit in an almost-invisible footnote on its doc-page.
However, I humbly contend that the fact that these behaviors are documented doesn’t meant that they are correct. After all, figure windows or timers aren’t deleted when their handle goes out of scope, are they? At the very least, I hope that MathWorks will improve the relevant doc pages, to highlight these non-intuitive behaviors, and in the case of linkaxes to present a linkprop usage example as a workaround.
If you are interested in the topic of Matlab listeners, note that I’ve written quite a few listener-related posts over the years (about property-change listeners as well as event listeners). I urge you to take a look at the list of related articles presented below, or to use the search box at the top of the page.

Related posts:

  1. Property value change listeners – HG handle property changes can be trapped in a user-defined callback. ...
  2. Determining axes zoom state – The information of whether or not an axes is zoomed or panned can easily be inferred from an internal undocumented object....
  3. Plot legend title – Titles to plot legends are easy to achieve in HG1 (R2014a or earlier), but much more difficult in HG2 (R2014b or newer). ...
  4. Plot performance – Undocumented inner plot mechanisms can significantly improve plotting performance ...
  5. Multi-column (grid) legend – This article explains how to use undocumented axes listeners for implementing multi-column plot legends...
  6. FIG files format – FIG files are actually MAT files in disguise. This article explains how this can be useful in Matlab applications....
Handle graphics Hidden property Listener Pure Matlab Undocumented feature
Print Print
« Previous
Next »
8 Responses
  1. Yaroslav July 24, 2015 at 05:48 Reply

    Hi @Yair,

    The solution you proposed with linkprop has a small issue: it does not restore the linking after saving and reloading. On the other hand, linkaxes recovers its linking correctly.

    A simple workaround is to use Matlab’s own solution: “MCOS graphics cannot rely on custom machinery in hgload to restore linkaxes. Instead, create a matlab.graphics.internal.LinkAxes to wrap the linkprop which will restore the linkaxes when it is de-serialized.” In simple words,

    % Link the relevant axes
    restorable_linkprop = @(varargin)matlab.graphics.internal.LinkAxes(linkprop(varargin{:}));
    %
    setappdata(ax(1,1), 'YLim_listeners', restorable_linkprop(ax(1,:),'YLim'));
    setappdata(ax(2,1), 'YLim_listeners', restorable_linkprop(ax(2,:),'YLim'));
    setappdata(ax(1,1), 'XLim_listeners', restorable_linkprop(ax(:,1),'XLim'));
    setappdata(ax(1,2), 'XLim_listeners', restorable_linkprop(ax(:,2),'XLim'));

    % Link the relevant axes restorable_linkprop = @(varargin)matlab.graphics.internal.LinkAxes(linkprop(varargin{:})); % setappdata(ax(1,1), 'YLim_listeners', restorable_linkprop(ax(1,:),'YLim')); setappdata(ax(2,1), 'YLim_listeners', restorable_linkprop(ax(2,:),'YLim')); setappdata(ax(1,1), 'XLim_listeners', restorable_linkprop(ax(:,1),'XLim')); setappdata(ax(1,2), 'XLim_listeners', restorable_linkprop(ax(:,2),'XLim'));

  2. Amin February 26, 2016 at 18:50 Reply

    Amazing post Yair. Thanks!

    One question though. Is it possible to link two different properties? For example ‘x-axis’ of an ax to ‘x-axis’ and ‘y-axis’ of another ax?

    • Yair Altman February 27, 2016 at 18:42 Reply

      @Amin – I believe that you can link multiple properties/handles for example:

      linkprop([hAxes1,hAxes2], {'XLim','YLim'})

      linkprop([hAxes1,hAxes2], {'XLim','YLim'})

      aught to link hAxes1.XLim to hAxes2.YLim

    • Yaroslav March 1, 2016 at 08:43 Reply

      @Yair — I am afraid that your solution doesn’t answer @Amin’s question. It, actually, behaves almost exactly as

      linkaxes([hAxes1 hAxes2], 'xy');

      linkaxes([hAxes1 hAxes2], 'xy');

      @Amin — If you want to link two different properties, you must specify the listeners explicitly. For example,

      addlistener(hAxes1,'XLim','PostSet',@(~,~)set(hAxes2,'YLim',get(hAxes1,'XLim'))); % link hAxes2.YLim to hAxes1.XLim
      addlistener(hAxes1,'YLim','PostSet',@(~,~)set(hAxes2,'XLim',get(hAxes1,'YLim'))); % link hAxes2.XLim to hAxes1.YLim
      addlistener(hAxes2,'XLim','PostSet',@(~,~)set(hAxes1,'YLim',get(hAxes2,'XLim'))); % link hAxes1.YLim to hAxes2.XLim
      addlistener(hAxes2,'YLim','PostSet',@(~,~)set(hAxes1,'XLim',get(hAxes2,'YLim'))); % link hAxes1.XLim to hAxes2.YLim

      addlistener(hAxes1,'XLim','PostSet',@(~,~)set(hAxes2,'YLim',get(hAxes1,'XLim'))); % link hAxes2.YLim to hAxes1.XLim addlistener(hAxes1,'YLim','PostSet',@(~,~)set(hAxes2,'XLim',get(hAxes1,'YLim'))); % link hAxes2.XLim to hAxes1.YLim addlistener(hAxes2,'XLim','PostSet',@(~,~)set(hAxes1,'YLim',get(hAxes2,'XLim'))); % link hAxes1.YLim to hAxes2.XLim addlistener(hAxes2,'YLim','PostSet',@(~,~)set(hAxes1,'XLim',get(hAxes2,'YLim'))); % link hAxes1.XLim to hAxes2.YLim

      The linkprop function won’t do, since it links the same property between two different handle objects.

      • Yair Altman March 1, 2016 at 09:58

        @Yaroslav – you are correct, I was misled by the wording of linkprop‘s documentation:

        “…maintain the same values for the corresponding properties of different graphics objects.”

        Serves me right to trust the builtin documentation! – I should have known better… 🙂

  3. Mark D. August 8, 2018 at 14:53 Reply

    @Yaroslav your solution works great, but the double mouseclick (of zoom out or zoom in), which puts the plot in the default optimized axes, doesn’t work or behaves the same, instead of just linking by linkaxes. Do you know the solution for that too?

    x = 1:5;
    y = randn(1,5);
     
    figure;
    a1 = subplot(1,2,1);
    p1 = plot(x,y);
     
    a2 = subplot(1,2,2);
    p2 = plot(y,x);
     
    addlistener(a1,'XLim','PostSet',@(~,~)set(a2,'YLim',get(a1,'XLim'))); % link a1.XLim to a2.YLim
    addlistener(a1,'YLim','PostSet',@(~,~)set(a2,'XLim',get(a1,'YLim'))); % link a1.YLim to a2.XLim
    % and vice versa
    addlistener(a2,'XLim','PostSet',@(~,~)set(a1,'YLim',get(a2,'XLim'))); % link a1.XLim to a2.YLim
    addlistener(a2,'YLim','PostSet',@(~,~)set(a1,'XLim',get(a2,'YLim'))); % link a1.YLim to a2.XLim
     
    %Use case, Zoom about 4 Times in and zoom out per doubleclick an you will see, that it then doesn't update the axes

    x = 1:5; y = randn(1,5); figure; a1 = subplot(1,2,1); p1 = plot(x,y); a2 = subplot(1,2,2); p2 = plot(y,x); addlistener(a1,'XLim','PostSet',@(~,~)set(a2,'YLim',get(a1,'XLim'))); % link a1.XLim to a2.YLim addlistener(a1,'YLim','PostSet',@(~,~)set(a2,'XLim',get(a1,'YLim'))); % link a1.YLim to a2.XLim % and vice versa addlistener(a2,'XLim','PostSet',@(~,~)set(a1,'YLim',get(a2,'XLim'))); % link a1.XLim to a2.YLim addlistener(a2,'YLim','PostSet',@(~,~)set(a1,'XLim',get(a2,'YLim'))); % link a1.YLim to a2.XLim %Use case, Zoom about 4 Times in and zoom out per doubleclick an you will see, that it then doesn't update the axes

    • Yaroslav August 9, 2018 at 10:52 Reply

      @Mark, the zoom function calls resetplotview (undocumented). It, in turn, resets all modes (X/YLimMode) to auto. That is why the listeners do not trigger.

      To solve the issue, link these properties too:

      linkprop([a1 a2],{'XLimMode','YLimMode'}); % trigger linking on double click

      linkprop([a1 a2],{'XLimMode','YLimMode'}); % trigger linking on double click

  4. Gabriel December 21, 2021 at 17:58 Reply

    This didn’t work when trying to link y axes across the plots in each column and x axes across the plots in each row on an (m x n) tiledlayout.
    On matlab R2021a found that I had to mix linkaxes and setappdata, like this:

    f = figure;
    m = 2;
    n = 3;
    tiles = tiledlayout(f, m, n);
    for i = 1:m  %rows
        for j = 1:n  %cols
            ax(i,j) = nexttile;
            plot(linspace(0,1,10)*i, linspace(2,1,10)*j);
            linkaxes(ax(:,j), 'y');
            setappdata(ax(i,j), 'XLim_listeners', linkprop(ax(i,:),'XLim')); %using linkaxes(ax(i, :), 'x'); 
        end % for all cols
    end % for all rows

    f = figure; m = 2; n = 3; tiles = tiledlayout(f, m, n); for i = 1:m %rows for j = 1:n %cols ax(i,j) = nexttile; plot(linspace(0,1,10)*i, linspace(2,1,10)*j); linkaxes(ax(:,j), 'y'); setappdata(ax(i,j), 'XLim_listeners', linkprop(ax(i,:),'XLim')); %using linkaxes(ax(i, :), 'x'); end % for all cols end % for all rows

Leave a Reply
HTML tags such as <b> or <i> are accepted.
Wrap code fragments inside <pre lang="matlab"> tags, like this:
<pre lang="matlab">
a = magic(3);
disp(sum(a))
</pre>
I reserve the right to edit/delete comments (read the site policies).
Not all comments will be answered. You can always email me (altmany at gmail) for private consulting.

Click here to cancel reply.

Useful links
  •  Email Yair Altman
  •  Subscribe to new posts (feed)
  •  Subscribe to new posts (reader)
  •  Subscribe to comments (feed)
 
Accelerating MATLAB Performance book
Recent Posts

Speeding-up builtin Matlab functions – part 3

Improving graphics interactivity

Interesting Matlab puzzle – analysis

Interesting Matlab puzzle

Undocumented plot marker types

Matlab toolstrip – part 9 (popup figures)

Matlab toolstrip – part 8 (galleries)

Matlab toolstrip – part 7 (selection controls)

Matlab toolstrip – part 6 (complex controls)

Matlab toolstrip – part 5 (icons)

Matlab toolstrip – part 4 (control customization)

Reverting axes controls in figure toolbar

Matlab toolstrip – part 3 (basic customization)

Matlab toolstrip – part 2 (ToolGroup App)

Matlab toolstrip – part 1

Categories
  • Desktop (45)
  • Figure window (59)
  • Guest bloggers (65)
  • GUI (165)
  • Handle graphics (84)
  • Hidden property (42)
  • Icons (15)
  • Java (174)
  • Listeners (22)
  • Memory (16)
  • Mex (13)
  • Presumed future risk (394)
    • High risk of breaking in future versions (100)
    • Low risk of breaking in future versions (160)
    • Medium risk of breaking in future versions (136)
  • Public presentation (6)
  • Semi-documented feature (10)
  • Semi-documented function (35)
  • Stock Matlab function (140)
  • Toolbox (10)
  • UI controls (52)
  • Uncategorized (13)
  • Undocumented feature (217)
  • Undocumented function (37)
Tags
AppDesigner (9) Callbacks (31) Compiler (10) Desktop (38) Donn Shull (10) Editor (8) Figure (19) FindJObj (27) GUI (141) GUIDE (8) Handle graphics (78) HG2 (34) Hidden property (51) HTML (26) Icons (9) Internal component (39) Java (178) JavaFrame (20) JIDE (19) JMI (8) Listener (17) Malcolm Lidierth (8) MCOS (11) Memory (13) Menubar (9) Mex (14) Optical illusion (11) Performance (78) Profiler (9) Pure Matlab (187) schema (7) schema.class (8) schema.prop (18) Semi-documented feature (6) Semi-documented function (33) Toolbar (14) Toolstrip (13) uicontrol (37) uifigure (8) UIInspect (12) uitable (6) uitools (20) Undocumented feature (187) Undocumented function (37) Undocumented property (20)
Recent Comments
Contact us
Captcha image for Custom Contact Forms plugin. You must type the numbers shown in the image
Undocumented Matlab © 2009 - Yair Altman
This website and Octahedron Ltd. are not affiliated with The MathWorks Inc.; MATLAB® is a registered trademark of The MathWorks Inc.
Scroll to top