function akZoom(h_ax)
% allows direct zooming and panning with the mouse in 2D plots.
%
% Scroll wheel: zoom in/out
% Left Mouse Button: select an ROI to zoom in
% Middle Mouse Button: pan view
% Right Click: reset view to default view
%
% SYNTAX:
% akZoom
% akZoom(h_ax)
%
% DESCRIPTION:
% akZoom activates mouse control for all axes-objects in the current figure.
%
% akZoom(h_ax) activates mouse control for all axes given by the handle
% array h_ax. The axes can be subplots or even in different figures and are
% automatically linked. This means that when zooming or panning one axis
% all others will be affected to.
%
%
% EXAMPLES:
% a) Simple Plot
% x = linspace(-1, 1, 10000);
% y = sin(1./x);
% figure
% plot(x, y);
% akZoom();
%
% b) Plotyy (linked axes)
% x = linspace(-1, 1, 10000);
% y = sin(1./x);
% y2 = -2*sin(1./(x-0.1));
% figure
% ax = plotyy(x,y,x,y2);
% akZoom(ax);
%
% c) Plotyy (independent axes)
% x = linspace(-1, 1, 10000);
% y = sin(1./x);
% y2 = -2*sin(1./(x-0.1));
% figure
% ax = plotyy(x,y,x,y2);
% akZoom();
%
% d) Image
% figure
% imagesc(magic(40));
% akZoom();
%
% e) Subplots (independent axes)
% figure
% for k = 1:4
% y = rand(1,15);
% subplot(2, 2, k);
% plot(y);
% end
% akZoom();
%
% e) Subplots (linked axes)
% figure
% ax = NaN(4,1);
% for k = 1:4
% y = rand(1,15);
% ax(k) = subplot(2, 2, k);
% plot(y);
% end
% akZoom(ax);
%
% f) Different figures (linked)
% x = linspace(-1, 1, 10000);
% y = sin(1./x);
% figure
% plot(x, y)
% ax(1) = gca;
% figure
% plot(x, y)
% ax(2) = gca;
% akZoom(ax);
%
%
% KNOWN BUGS
% a) Strange double tick marks appear while you draw the ROI-rectangle in figures with an image in it.
% This happens in old Matlab-Versions and is a bug of the Matlab OpenGl-Renderer
% You can avoid this by switching to software rendering: opengl software
%
%
% Author: Alexander Kessel
% Affiliation: Max-Planck-Institut fr Quantenoptik, Garching, Munich
% Contact : alexander.kessel <at> mpq.mpg.de
% Revision: April 2013
%
% Credits go to Rody P.S. Oldenhuis for his mouse_figure function which
% served as the template for akZoom and to Kang Zhao for the gpos function.
% Specify axes and figures
if nargin == 0
h_fig = get(0,'CurrentFigure');
if isempty(h_fig)
error('akZoom:no_figure_open', 'There is no open figure.');
end
h_ax = findobj(gcf,'type','axes'); % use all axes in figure
linkAxes = false; % by default do not link axes
else
h_fig = NaN(size(h_ax));
for j=1:numel(h_ax)
h_fig(j) = get(h_ax(j), 'Parent'); % get figures of all axes
end
linkAxes = true; % link specified axes
end
linkaxes(h_ax, 'off'); % turn off matlab-linking if it is on. akZoom is linking axes its own
% check if plot is 2D plot
if ~any(is2D(h_ax)) % is2D might disappear in a future release...
error('akZoom:plot3D_not_supported', 'akZoom() only works for 2-D plots.');
end
% Initialize variables for use across all nested functions
cx = []; % clicked x-coordinate
cy = []; % clicked y-coordinate
mode = ''; % navigation mode, e.g. 'pan'
ROI = []; % This will later hold the patch object that marks the zoom area.
% save original limits
original_xlim = NaN(numel(h_ax),2);
original_ylim = NaN(size(original_xlim));
for j=1:numel(h_ax)
original_xlim(j,:) = get(h_ax(j), 'xlim');
original_ylim(j,:) = get(h_ax(j), 'ylim');
end
% set callbacks for all figures
for j=1:numel(h_fig)
set(h_fig(j), ...
'WindowScrollWheelFcn' , @scroll_zoom,...
'WindowButtonDownFcn' , @MouseDown,...
'WindowButtonUpFcn' , @MouseUp,...
'WindowButtonMotionFcn', @MouseMotion);
end
% zoom in to cursor point with the mouse wheel
function scroll_zoom(varargin)
currAx = h_ax( get(gcf, 'currentaxes') == h_ax );
if isempty(currAx), return, end %return if the current axis is not one of the axes specified at the beginning
[x,y]=gpos(gcf, currAx);
[x_rel, y_rel] = abs2relCoords(currAx, x, y);
wheel_zoomFactor = 1+varargin{2}.VerticalScrollCount/5;
for i = affectedAxes()
[x, y] = rel2absCoords(h_ax(i), x_rel, y_rel);
XLim = get(h_ax(i), 'xlim');
new_XLim = (XLim-x)*wheel_zoomFactor + x;
set(h_ax(i),'xlim',new_XLim);
YLim = get(h_ax(i), 'ylim');
new_YLim = (YLim-y)*wheel_zoomFactor + y;
set(h_ax(i),'ylim',new_YLim);
end
end
function MouseDown(varargin)
currAx = h_ax( get(gcf, 'currentaxes') == h_ax);
if isempty(currAx), return, end %return if the current axis is not one of the axes specified at the beginning
% save clicked coordinates to cx and cy
[cx, cy] = GetClickedCoords(currAx);
switch lower(get(gcf, 'selectiontype'))
case 'extend' %middle button
mode = 'pan';
case 'alt' %right button
for i = affectedAxes()
set(h_ax(i), 'Xlim', original_xlim(i,:), 'Ylim', original_ylim(i,:));
end
case 'normal' %left button
mode = 'selectROI';
% create ROI rectangle object
ROI = patch();%'Parent', currAx);
% set position and appearance of ROI rectangle
set(ROI, ...
'XData', [cx cx cx cx], ...
'YData', [cy cy cy cy], ...
'EdgeColor', 'k', ...
'FaceColor', 'r', ...
'FaceAlpha', 0.1, ...
'LineWidth', 0.5, ...
'LineStyle', '-');
end
end
function MouseUp(varargin)
currAx = h_ax( get(gcf, 'currentaxes') == h_ax);
if isempty(currAx), return, end %return if the current axis is not one of the axes specified at the beginning
if strcmp(mode, 'selectROI')
% get corner points of ROI in relative coordinates
x = get(ROI, 'XData');
y = get(ROI, 'YData');
[x_rel1, y_rel1] = abs2relCoords(currAx, x(1), y(1));
[x_rel2, y_rel2] = abs2relCoords(currAx, x(2), y(3));
for i = affectedAxes()
% calc absolute coordinates of ROI corners
[x1, y1] = rel2absCoords(h_ax(i), x_rel1, y_rel1);
[x2, y2] = rel2absCoords(h_ax(i), x_rel2, y_rel2);
new_xlim = sort([x1, x2]);
new_ylim = sort([y1, y2]);
if diff(new_xlim) && diff(new_ylim) % check valid limits
% set limits
set(h_ax(i), 'xlim', new_xlim, 'ylim', new_ylim)
end
end
delete(ROI);
end
mode = '';
end
function MouseMotion(varargin)
if isempty(cx), return, end % return if there is no clicked point set
currAx = h_ax( get(gcf, 'currentaxes') == h_ax);
if isempty(currAx), return, end %return if the current axis is not one of the axes specified at the beginning
[x,y] = GetClickedCoords(currAx);
if strcmp(mode, 'pan')
xlim = get(currAx, 'xlim');
ylim = get(currAx, 'ylim');
% find change in position
delta_x = x - cx;
delta_y = y - cy;
% calculate relative change in position
delta_x_rel = delta_x/diff(xlim);
delta_y_rel = delta_y/diff(ylim);
for i = affectedAxes()
xlim = get(h_ax(i), 'xlim');
ylim = get(h_ax(i), 'ylim');
% adjust limits
new_xlim = xlim - delta_x_rel*diff(xlim);
new_ylim = ylim - delta_y_rel*diff(ylim);
% set new limits
set(h_ax(i), 'Xlim', new_xlim, 'Ylim', new_ylim);
end
% save new position
[cx,cy] = GetClickedCoords(currAx);
elseif strcmp(mode, 'selectROI')
% resize ROI rectangle
set(ROI, ...
'XData', [cx x x cx], ...
'YData', [cy cy y y]);
else % no mode
return;
end
end
function i_ax = affectedAxes()
currAx = h_ax( get(gcf, 'currentaxes') == h_ax);
if isempty(currAx)
i_ax = [];
else
if linkAxes
i_ax = 1:numel(h_ax); % use all axes
else
i_ax = get(gcf, 'currentaxes') == h_ax; % use only current axis
end
end
end
end
function [x, y, z] = GetClickedCoords(h_ax)
crd = get(h_ax, 'CurrentPoint');
x = crd(2,1);
y = crd(2,2);
z = crd(2,3);
end
function [x_rel, y_rel] = abs2relCoords(h_ax, x, y)
XLim = get(h_ax, 'xlim');
x_rel = (x-XLim(1))/(XLim(2)-XLim(1));
YLim = get(h_ax, 'ylim');
y_rel = (y-YLim(1))/(YLim(2)-YLim(1));
end
function [x, y] = rel2absCoords(h_ax, x_rel, y_rel)
XLim = get(h_ax, 'xlim');
x = x_rel*diff(XLim)+XLim(1);
YLim = get(h_ax, 'ylim');
y = y_rel*diff(YLim)+YLim(1);
end
function [x,y]=gpos(h_figure, h_axes)
% Written by Kang Zhao,DLUT,Dalian,CHINA. 2003-11-19
% E-mail:kangzhao@student.dlut.edu.cn
units_figure = get(h_figure,'units');
units_axes = get(h_axes,'units');
if_units_consistent = 1;
if ~strcmp(units_figure,units_axes)
if_units_consistent=0;
set(h_axes,'units',units_figure); % To be sure that units of figure and axes are consistent
end
% Position of origin in figure [left bottom]
pos_axes_unitfig = get(h_axes,'position');
width_axes_unitfig = pos_axes_unitfig(3);
height_axes_unitfig = pos_axes_unitfig(4);
xDir_axes=get(h_axes,'XDir');
yDir_axes=get(h_axes,'YDir');
% Cursor position in figure
pos_cursor_unitfig = get( h_figure, 'currentpoint'); % [left bottom]
if strcmp(xDir_axes,'normal')
left_origin_unitfig = pos_axes_unitfig(1);
x_cursor2origin_unitfig = pos_cursor_unitfig(1) - left_origin_unitfig;
else
left_origin_unitfig = pos_axes_unitfig(1) + width_axes_unitfig;
x_cursor2origin_unitfig = -( pos_cursor_unitfig(1) - left_origin_unitfig );
end
if strcmp(yDir_axes,'normal')
bottom_origin_unitfig = pos_axes_unitfig(2);
y_cursor2origin_unitfig = pos_cursor_unitfig(2) - bottom_origin_unitfig;
else
bottom_origin_unitfig = pos_axes_unitfig(2) + height_axes_unitfig;
y_cursor2origin_unitfig = -( pos_cursor_unitfig(2) - bottom_origin_unitfig );
end
xlim_axes=get(h_axes,'XLim');
width_axes_unitaxes=xlim_axes(2)-xlim_axes(1);
ylim_axes=get(h_axes,'YLim');
height_axes_unitaxes=ylim_axes(2)-ylim_axes(1);
x = xlim_axes(1) + x_cursor2origin_unitfig / width_axes_unitfig * width_axes_unitaxes;
y = ylim_axes(1) + y_cursor2origin_unitfig / height_axes_unitfig * height_axes_unitaxes;
% Recover units of axes,if original units of figure and axes are not consistent.
if ~if_units_consistent
set(h_axes,'units',units_axes);
end
end