Reputation: 347
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.
Upvotes: 1
Views: 1564
Reputation: 2331
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.
Upvotes: 0
Reputation: 5177
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 NaN
s 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 NaN
s 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.
Upvotes: 1