function dynamicDateTicks(axH, link, mdformat)
% DYNAMICDATETICKS is a wrapper function around DATETICK which creates
% dynamic date tick labels for plots with dates on the X-axis. The date
% ticks intelligently include year/month/day information on specific ticks
% as appropriate. The ticks are dynamic with respect to zooming and
% panning. They update as the timescale changes (from years to seconds).
% Data tips on the plot also show intelligently as dates.
%
% The function may be used with linked axes as well as with multiple
% independent date and non-date axes within a plot.
%
% USAGE:
% dynamicDateTicks()
% makes the current axes a date axes with dynamic properties
%
% dynamicDateTicks(axH)
% makes all the axes handles in vector axH dynamic date axes
%
% dynamicDateTicks(axH, 'link')
% additionally specifies that all the axes in axH are linked. This
% option should be used in conjunction with LINKAXES.
%
% dynamicDateTicks(axH, 'link', 'dd/mm')
% additionally specifies the format of all ticks that include both
% date and month information. The default value is 'mm/dd' but any
% valid date string format can be specified. The first two options
% may be empty [] if only specifying format.
%
% EXAMPLES:
% load integersignal
% dates = datenum('July 1, 2008'):1/24:datenum('May 11, 2009 1:00 PM');
% subplot(2,1,1), plot(dates, Signal1);
% dynamicDateTicks
% subplot(2,1,2), plot(dates, Signal4);
% dynamicDateTicks([], [], 'dd/mm');
%
% figure
% ax1 = subplot(2,1,1); plot(dates, Signal1);
% ax2 = subplot(2,1,2); plot(dates, Signal4);
% linkaxes([ax1 ax2], 'x');
% dynamicDateTicks([ax1 ax2], 'linked')

if nargin < 1 || isempty(axH) % If no axes is specified, use the current axes
axH = gca;
end

if nargin < 3 % Default mm/dd format
mdformat = 'mm/dd';
end

% Apply datetick to all axes in axH, and store any linking information
axesInfo.Type = 'dateaxes'; % Information stored in axes userdata indicating that these are date axes
for i = 1:length(axH)
datetick(axH(i), 'x');
if nargin > 1 && ~isempty(link) % If axes are linked,
axesInfo.Linked = axH; % Need to modify all axes at once
else
axesInfo.Linked = axH(i); % Need to modify only 1 axes
end
axesInfo.mdformat = mdformat; % Remember mm/dd format for each axes
set(axH(i), 'UserData', axesInfo); % Store the fact that this is a date axes and its link & mm/dd information in userdata
updateDateLabel('', struct('Axes', axH(i)), 0); % Call once to ensure proper formatting
end

% Set the zoom, pan and datacursor callbacks
figH = get(axH, 'Parent');
if iscell(figH)
figH = unique([figH{:}]);
end
if length(figH) > 1
error('Axes should be part of the same plot (have the same figure parent)');
end

z = zoom(figH);
p = pan(figH);
d = datacursormode(figH);

set(z,'ActionPostCallback',@updateDateLabel);
set(p,'ActionPostCallback',@updateDateLabel);
set(d,'UpdateFcn',@dateTip);

% ------------ End of dynamicDateTicks-----------------------

function output_txt = dateTip(gar, ev)
pos = ev.Position;
axHandle = get(ev.Target, 'Parent'); % Which axes is the data cursor on
axesInfo = get(axHandle, 'UserData'); % Get the axes info for that axes
try % If it is a date axes, create a date-friendly data tip
if strcmp(axesInfo.Type, 'dateaxes')
output_txt = sprintf('X: %s\nY: %0.4g', datestr(pos(1)), pos(2));
else
output_txt = sprintf('X: %0.4g\nY: %0.4g', pos(1), pos(2));
end
catch % It's not a date axes, create a generic data tip
output_txt = sprintf('X: %0.4g\nY: %0.4g', pos(1), pos(2));
end
end

function updateDateLabel(obj, ev, varargin)
ax1 = ev.Axes; % On which axes has the zoom/pan occurred
axesInfo = get(ev.Axes, 'UserData');
% Check if this axes is a date axes. If not, do nothing more (return)
try
if ~strcmp(axesInfo.Type, 'dateaxes')
return;
end
catch
return;
end

% Re-apply date ticks, but keep limits (unless called the first time)
if nargin < 3
datetick(ax1, 'x', 'keeplimits');
end

% Get the current axes ticks & labels
ticks = get(ax1, 'XTick');
labels = get(ax1, 'XTickLabel');

% Sometimes the first tick can be outside axes limits. If so, remove it & its label
if all(ticks(1) < get(ax1,'xlim'))
ticks(1) = [];
labels(1,:) = [];
end

[yr, mo, da] = datevec(ticks); % Extract year & day information (necessary for ticks on the boundary)
newlabels = cell(size(labels,1), 1); % Initialize cell array of new tick label information

if regexpi(labels(1,:), '[a-z]{3}', 'once') % Tick format is mmm

% Add year information to first tick & ticks where the year changes
ind = [1 find(diff(yr))+1];
newlabels(ind) = cellstr(datestr(ticks(ind), '/yy'));
labels = strcat(labels, newlabels);

elseif regexpi(labels(1,:), '\d\d/\d\d', 'once') % Tick format is mm/dd

% Change mm/dd to dd/mm if necessary
labels = datestr(ticks, axesInfo.mdformat);
% Add year information to first tick & ticks where the year changes
ind = [1 find(diff(yr))+1];
newlabels(ind) = cellstr(datestr(ticks(ind), '/yy'));
labels = strcat(labels, newlabels);

elseif any(labels(1,:) == ':') % Tick format is HH:MM

% Add month/day/year information to the first tick and month/day to other ticks where the day changes
ind = find(diff(da))+1;
newlabels{1} = datestr(ticks(1), [axesInfo.mdformat '/yy-']); % Add month/day/year to first tick
newlabels(ind) = cellstr(datestr(ticks(ind), [axesInfo.mdformat '-'])); % Add month/day to ticks where day changes
labels = strcat(newlabels, labels);

end

set(axesInfo.Linked, 'XTick', ticks, 'XTickLabel', labels);
end
end
%#ok<*CTCH>
%#ok<*ASGLU>
%#ok<*INUSL>
%#ok<*INUSD>