How to link different scaled axes for zooming in App Designer?

11 views (last 30 days)
Hamid Kilic
Hamid Kilic on 21 Sep 2021
Commented: Adam Danz on 28 Sep 2021
In App Designer, I have two axes. On the first axis, I load an image and show it with imagesc. On the second axis, I plot the intensity plot on the y-direction of the image.
Now I would like to link the axis and when I zoom in on the image I would like to zoom the second axis too. I used linkaxes but it didn't work for me because the scales of the two axes are different and it doesn't zoom in on the intended area. Also, one problem with linkaxes is that it connects the axis two ways so I want the second axis to be affected when I zoom in on the image axis but I don't want it the other way around. I only need to link the two axes in the x-direction.

Accepted Answer

Adam Danz
Adam Danz on 21 Sep 2021
Edited: Adam Danz on 27 Sep 2021
linkaxes has a second input that allows you to link the x,y,or both axes. However, since your y-axes have different scales, the linkaxes function will not be useful to you.
Linking axes with different scales is demonstrated in this answer but since that answer uses overlaid axes and "links" both x and y axies, I've made some adjustments to that demo which is shown below which links the y-axes between two plots.
The only "input" in the entire code is ax which is a 1x2 vector of axis handles [ax1,ax2] where ax1 is the left axes and ax2 is the right-axes handles.
The process is not straight-forward since you have to manually scale the axes, use a listener, and adjust the behavior of the "restore view" toolbar button. If needed, see the answer mentioned above for a detailed description and comments. Tested in R2020b and R2021a.
Attachments
  • linkedYAxesDiffScale.m - Full demo of this answer.
  • linkedYAxesDiffScale_UI.m - Full demo of this answer modified to use UIFigure and UIAxes.
0. Set up demo figure
The goal will be to link the y-axes which have different scales .
figure()
tiledlayout(1,3)
ax = gobjects(1,2);
ax(1) = nexttile(1:2);
x = randn(1,500)*100;
y = randn(1,500)*50+500;
plot(x,y,'o')
grid on
ax(2) = nexttile(3);
histogram(y-mean(y),20,'Orientation','Horizontal')
grid on
Important: before proceeding, the yscales should be set using ylim() in both axes (not shown). The y-limits are already ideally set in the simple demo using auto Y-limit-mode.
1. Turn off interactions for axes #2 (right)
All zooming / panning should be done in axes #1 on the left.
ax(2).Interactions = [];
ax(2).Toolbar.Visible = 'off';
2. Set up a listener that will respond to y-axis limit changes in axes #1 (left)
% Compute scaling factor to convert ax2 y-scale from ax1 y-scale
xyscale = range(ax(2).YLim) / range(ax(1).YLim);
% Store original y-axis limits for both axes
axBaseLim = [ax(1).YLim; ax(2).YLim];
% Assign listener to ax1
ax(1).UserData.Listener = addlistener(ax(1),{'YLim'}, 'PostSet', ...
@(~,~)axisLimitListener([], [], [ax(1),ax(2)], xyscale, axBaseLim));
3. Address the "restore view" button problem
When you press the "restore view" button in the axis #1 toolbar, this listener will not respond. To address that, this block below and the "myRestoreButtonCallbackFcn" block below will update axes #2 when the "restore view" button is pressed in axes #1.
axTB = axtoolbar(ax(1),'default');
isRestoreButton = strcmpi({axTB.Children.Icon},'restoreview');
if any(isRestoreButton)
restoreButtonHandle = axTB.Children(isRestoreButton);
originalRestoreFcn = restoreButtonHandle.ButtonPushedFcn;
restoreButtonHandle.ButtonPushedFcn = ...
{@myRestoreButtonCallbackFcn, ax(2), originalRestoreFcn, xyscale, axBaseLim};
end
4. Define listener that responds to changes to y-axis limits in axes #1
function axisLimitListener(~,~,ax,scalingFactor,axBaseLim)
% Listener callback that responds to y-axis limit changes to ax1 and
% updates the y-axis limits to ax2.
% INPUTS
% ax: 1x2 array of axis handles to [ax1,ax2]
% scalingFactor: (see description of xyscale above)
% axBaseLim: (see description of axBaseLim above)
% Convert the ax2 ylim from ax1 to values normalized by the original axis range.
normLowerLimit = (ax(1).YLim(1) - axBaseLim(1,1))./range(axBaseLim(1,:));
% Compute the new lower limits to ax2.
newLimits = normLowerLimit.*range(axBaseLim(2,:)) + axBaseLim(2,1);
% Compute the new upper limits ax1.
newLimits(:,2) = newLimits + range(ax(1).YLim).*scalingFactor;
% Update ax1 limits
set(ax(2), 'YLim', newLimits)
end
5. Define the "restore view" callback that responds to axis #1 toolbar > Restore View
function myRestoreButtonCallbackFcn(hobj, event, ax2, originalCallback, xyscale, axBaseLim)
% Responds to pressing the restore button in the ax1 toolbar.
% originalCallback is a function handle to the original callback
% function for this button.
% xyscale and axBaseLim are defined elsewhere.
originalCallback(hobj,event) % reset ax2
axisLimitListener([],[],[event.Axes,ax2],xyscale,axBaseLim) % update ax1
end
  8 Comments

Sign in to comment.

More Answers (0)

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!