1
votes

I am looking for a way to add vertical dividing lines separating consecutive days in a dynamically updating plot of animal migration data (location vs. time). Part of the challenge is that the number of these dividers changes as the plot domain expands to display more temporal data: As the number of days in the plot increases from 3 to 5, for example, the number of dividers increases by 2.

A minimal code example, written in MATLAB, is shown below:

xcols = [1; 1];
ycols = [0; 1];

figure(4)
clf
h.divs = plot(xcols,ycols,':k');
xlabel('time')
ylabel('location')
for ii=2:6
    xcols(:,end+1) = [ii; ii];
    ycols(:,end+1) = [0; 1];
    % set(h.divs, 'XData', xcols, 'YData', ycols);
    % set(h.divs, {'XData'}, num2cell(xcols',2), {'YData'}, num2cell(ycols',2));
    drawnow
    pause(1)
end

The problem centers on the two lines that have been commented out. If I comment in the first of these to try to update XData and YData with each new set of dividers (given as the 2 x DividerCount matrices xcols and ycols), then I receive an error that these inputs "must be a vector of numeric type". If I instead comment in the second line as way of using cell arrays to get around this (per this Stack Overflow post and this MATLAB Newsgroup post), then the code returns an error that "cell array handle dimension must match handle vector length" as soon as the number of dividers changes.

Hacky solutions are certainly possible. For example, the dividers can be plotted as a single line of horizontal and vertical segments, where the horizontal segments are placed above and below the y-axis limits of the plot. Or a fixed number of dividers can be used, with some of the dividers plotted outside the x-axis limits of the plot. The question is whether there is a non-hacky approach – one that can plot a potentially changing number of lines of identical style in the same figure with each pass of the loop.

2
Welcome to StackOverflow. Since indeed your problem is covered by the top lines plus a little bit of initialization code (xcols, ycols, the actual plot, maybe the update to xcols), I would shorten the code bit to really only these essential parts. See also minimal reproducible example.zeeMonkeez
Thanks for this excellent tip. (I only included more code in my original question because I wasn't sure which details might be relevant for a general fix.) I have simplified my description and code to lead better into your answer below.SapereAude

2 Answers

1
votes

Assume we have at the beginning

xcols = [1:3; 1:3];
ycols = [0 0 0; 1 1 1];

then h.divs = plot(xcols,ycols,':k'); will create 3 line objects. Using

set(h.divs,{'XData'},num2cell(xcols',2),{'YData'},num2cell(ycols',2));

with size(xcols, 2)>3 will fail, because this assumes there are more than 3 graphics objects to set values for (but numel(h.divs) is still 3). So do we have to create a new plot for every divider?

No!, because if we insert NaNs into our data, we can introduce gaps into lines. As a first iteration, we could use this:

xcols = [1 1];
ycols = [0 1];

figure(4)
clf
h.divs = plot(xcols,ycols,':k');
for ii=2:6
    xcols(end+(1:3)) = [nan ii ii];
    ycols(end+(1:3)) = [nan 0 1];
    set(h.divs, 'XData', xcols, 'YData', ycols);
    drawnow
    pause(1)
end

Starting with one divider which we plot, we then grow the data vectors (one-dimensional!) with a new value pair separated from the old data by a nan.

This of course grows the data vectors in the loop, which is not so great. Instead, if we know how many dividers there will be, we can preallocate with NaNs and only fill in new data in the loop:

n_div = 6;
xcols = nan(3 * n_div, 1);
ycols = nan(3 * n_div, 1);
figure(4)
clf
h.divs = plot(xcols,ycols,':k');
for ii=1:6
    xcols((ii - 1)*3 + (1:2)) = [ii ii];
    ycols((ii - 1)*3 + (1:2)) = [0 1];
    set(h.divs, 'XData', xcols, 'YData', ycols);
    drawnow
    pause(1)
end

This has also the benefit that we can assign XData and YData separately, as in the new handle syntax:

h.divs.XData = xcols;
h.divs.YData = cols;

(or even better: h.divs.XData((ii - 1)*3 + (1:2)) = [ii ii];), because the lengths don't change.

0
votes

You can start with divider definition:

divider.x=[];
divider.y=[];
divider.counter=0;
h.div=line('xdata',divider.x,'ydata',divider.y,'linestyle',':','color','k');

Which will draw no line, but handle will be set.

Then, perhaps in some loop, you can call:

divider.counter=divider.counter+1;                 % increase the counter
divider.x=[divider.x,nan,divider.counter*[1 1]];   % append X coords of new divider line
divider.y=[divider.y,nan,divider.counter*[0 1]];   % append Y coords of new divider line
set(h.div,'xdata',divider.x,'ydata',divider.y)     % update dividers
drawnow                                            % update figure immediately

This approach works because NaN value can be passed to line function but will not be plotted and neither will be lines from neighbour points.