In MATLAB: How can XData and YData be updated with a changing number of lines? - matlab

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.

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.

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.

Related

How to make previous inputs progressively fade out in a Matlab plot when I add new inputs

Let's say I have this very simple loop
for i=1:10
[xO, yO, xA, yA, xB, yB, xC, yC] = DoSomething(i);
line([xO,xA,xB,xC],[yO,yA,yB,yC]);
pause(0.1);
end
The coordinates that I am plotting correspond to the joints of a multibody system, and I am simulating their positions over time (please see a sample of the plot here):
Since some of the links move in a periodic way, it gets confusing to keep track visually of the movement. For this reason, now comes the question: how can I plot the lines in a way that, when a new line is plotted, the previous lines are faded progressively? In other words, so that I have a gradient from the most recently plotted data (most opaque) to the oldest data (increasingly transparent until it completely fades out).
This way when a new line is drawn in the same position as very old data, I will notice that it is a new one.
You can do this by modifying the 4th Color attribute of past lines.
Here's a demo resulting gif, where I faded out 10% of the transparency each frame, so only the most recent 10 lines are visible.
Here is the code, see my comments for details:
% Set up some demo values for plotting around a circle
a = 0:0.1:2*pi; n = numel(a);
[x,y] = pol2cart( a, ones(1,n) );
% Initialise the figure, set up axes etc
f = figure(1); clf; xlim([-1,1]); ylim([-1,1]);
% Array of graphics objects to store the lines. Could use a cell array.
lines = gobjects( 1, n );
% "Buffer" size, number of historic lines to keep, and governs the
% corresponding fade increments.
nFade = 10;
% Main plotting loop
for ii = 1:n
% Plot the line
lines(ii) = line( [0,x(ii)], [0,y(ii)] );
% Loop over past lines.
% Note that we only need to go back as far as ii-nFade, earlier lines
% will already by transparent with this method!
for ip = max(1,ii-nFade):ii
% Set the 4th Color attribute value (the alpha) as a percentage
% from the current index. Could do this various ways.
lines(ip).Color(4) = max( 0, 1 - (ii-ip)/nFade );
end
% Delay for animation
pause(0.1);
end
You may want to do some plot/memory management if you've got many lines. You can delete transparent lines by adding something like
if lines(ii).Color(4) < 0.01
delete(lines(ii));
end
Within the loop. This way your figure won't have loads of transparent remnants.
Notes:
I generated the actual gif using imwrite in case that's of interest too.
Apparently the 4th Color value 'feature' has been depreciated in R2018b (not sure it was ever officially documented).
Got enough upvotes to motivate making a slightly more fun demo...
Solution for Matlab 2018a or later (or earlier, later than 2012a at least)
Since the fourth color parameter as alpha value is no longer supported in Matlab 2018a (and apparently was never supposed to as Cris Luengo pointed out), here a solution that works in Matlab 2018a using the patchline function from the file exchange (credits to Brett Shoelson).
% init the figure
figure(); axes();
hold on; xlim([-1 0.5]); ylim([0 1]);
% set fraction of alpha value to take
alpha_fraction = 0.7;
n_iterations = 200;
% looping variable to prevent deleting and calling already deleted lines
% i.e. to keep track of which lines are already deleted
delete_from = 1;
for i=1:n_iterations
% your x, y data
[x, y] = doSomething(i);
% create line with transparency using patchline
p(i) = patchline(x,y, 'linewidth', 1, 'edgecolor', 'k');
% set alpha of line to fraction of previous alpha value
% only do when first line is already plotted
if i > 1
% loop over all the previous created lines up till this iteration
% when it still exists (delete from that index)
for j = delete_from:i-1
% Update the alpha to be a fraction of the previous alpha value
p(j).EdgeAlpha = p(j).EdgeAlpha*alpha_fraction;
% delete barely visible lines
if p(j).EdgeAlpha < 0.01 && delete_from > j
delete(p(j));
% exclude deleted line from loop, so edgealpha is not
% called again
delete_from = j;
end
end
end
% pause and behold your mechanism
pause(0.1);
end
I included the deletion of barely visible lines, as suggested by #Wolfie (my own, perhaps less elegant implementation)
And here a demonstration of a quick release mechanism:
I'm adding a 2nd answer to clearly separate two completely different approaches. My 1st answer uses the undocumented (and as of 2018b, depreciated) transparency option for lines.
This answer offers a different approach for line drawing which has no compatibility issues (these two 'features' could be implemented independently):
Create a fixed n lines and update their position, rather than creating a growing number of lines.
Recolour the lines, fading to white, rather than changing transparency.
Here is the code, see comments for details:
% "Buffer" size, number of historic lines to keep, and governs the
% corresponding fade increments.
nFade = 100;
% Set up some demo values for plotting around a circle
dt = 0.05; a = 0:dt:2*pi+(dt*nFade); n = numel(a); b = a.*4;
[x1,y1] = pol2cart( a, ones(1,n) ); [x2,y2] = pol2cart( b, 0.4*ones(1,n) );
x = [zeros(1,n); x1; x1+x2]; y = [zeros(1,n); y1; y1+y2];
% Initialise the figure, set up axes etc
f = figure(1); clf; xlim([-1.5,1.5]); ylim([-1.5,1.5]);
% Draw all of the lines, initially not showing because NaN vs NaN
lines = arrayfun( #(x)line(NaN,NaN), 1:nFade, 'uni', 0 );
% Set up shorthand for recolouring all the lines
recolour = #(lines) arrayfun( #(x) set( lines{x},'Color',ones(1,3)*(x/nFade) ), 1:nFade );
for ii = 1:n
% Shift the lines around so newest is at the start
lines = [ lines(end), lines(1:end-1) ];
% Overwrite x/y data for oldest line to be newest line
set( lines{1}, 'XData', x(:,ii), 'YData', y(:,ii) );
% Update all colours
recolour( lines );
% Pause for animation
pause(0.01);
end
Result:

Removing connection between each section of data in a plot: MATLAB

As seen on the figure below, I have a collection of data from different time sets. However, there is a line connecting the end of each data section to the start of the next.
Is there any way of suppressing this connection without altering the data?
Intro
Currently MATLAB is drawing lines between all consecutive points in your arrays, including when there is a big jump in x value.
An equivalent example would be this plot:
% Create some x with points spaced by 0.1, except at 2 jumps of 5
x = [0:0.1:10, 15:0.1:25, 30:0.1:40];
y = sin(x);
plot(x,y)
Notice the straight line joins between our 3 sections (10-15, 25-30).
Method 1: hold on for multiple plots
We can use the find and diff to get the regions where x jumps, and then plot each region individually.
% Include indices for the first (we're going to +1, so start at 0) and last elements
% You can change this 1 to any tolerance you like, it might be of the order 10^4 for you!
idx = [0, find(diff(x) > 1), numel(x)];
% Hold on for multiple plots
hold on;
% Loop over sections and plot
for ii = 2:numel(idx)
% Plots will loop through colours if you don't specify one, specified blue ('b') here
plot(x(idx(ii-1)+1:idx(ii)), y(idx(ii-1)+1:idx(ii)), 'b')
end
hold off; % good practise so you don't accidentally plot more on this fig!
Method 2: using NaN
You could use also use NaN values to break up your data. NaN values aren't plotted by MATLAB, so if we include one in each break, there will be nothing to connect the line to and we'll see a break:
% Notice the NaNs!
x = [0:0.1:10, NaN, 15:0.1:25, NaN, 30:0.1:40];
y = sin(x);
plot(x,y)
Automation: You may want to use the diff function to find these jumps on the fly for some pre-existing x, like so
% Define some x and y as before, without NaNs, with big gaps in x
x = [0:0.1:10, 15:0.1:25, 30:0.1:40];
y = sin(x);
% Create anonymous function to insert NaN at vector index 'n'
insertNaN = #(v, n) [v(1:n), NaN, v(n+1:end)];
% Get indices where gap is bigger than some value (in this case 1)
idx = find(diff(x) > 1);
% Loop *backwards* through indices (so they don't shift up by 1 as we go)
for ii = numel(idx):-1:1
x = insertNaN(x,idx(ii));
y = insertNaN(y,idx(ii));
end
% Plot appears the same as the second plot above
plot(x,y);
Note: If you want to do further processing on x and y, it might not be best to add random NaN values into the array! You can either:
Remove them afterwards (assuming there weren't pre-existing NaNs)
x = x(~isnan(x));
y = y(~isnan(y));
Use temporary variables for plotting
xplot = x;
% then adding NaNs to xplot for plotting ...

plotting from cell array of different lengths

this might come as an eco in this forum but I couldn't find a solution that I could apply to my problem. I have a cell of res_A1 of size (1x500) and in each cell there is a vector (1xlength) where length varies. I would like to plot in the same graph every vector or maybe a handful of them. All lines can be in the same color. I have tried following but the graph make no sense
data=res_A1(1,:,end);
plot(cell2mat(data)');
Also I would like to plot the average of the 500 vectors, preferably this should be in the same graph in another color. Is there a nice way of doing this?
You can use cat to combine the vectors along the first dimension. Then you can pass the transpose of this matrix to plot and each column will be plotted as it's own plot.
plot(cat(1, data{:}).');
If we create some example data, this will yield.
data = arrayfun(#(x)rand(1,10), ones(1, 5), 'uni', 0);
plot(cat(1, data{:}).');
If you want specific ones (i.e. [1 3 5]), you can replace : above with the indices of the ones you want.
plot(cat(1, data{[1 3 5]}).');
If you want to plot the average, simply use mean on the result of the call to cat.
avg = mean(cat(1, data{:}), 1);
plot(avg);
And if you wanted it in the same plot:
alldata = cat(1, data{:});
avg = mean(alldata, 1);
% Plot all of the curves
plot(alldata.');
hold on
% Plot the average curve
plot(avg, 'LineWidth', 3, 'Color', [0.5 0.5 0.5], 'LineStyle', '--')
Update
If your data is all different lengths, You have two options, you could plot everything with a loop.
hax = axes;
hold(hax, 'on');
for k = 1:numel(data)
plot(data{k}, 'Parent', hax);
end
Or you could still try to combine everything into one matrix, padding with NaN values.
% Find the longest vector length
maxlength = max(cellfun(#(x)numel(x), data));
alldata = nan(maxlength, numel(data));
for k = 1:numel(data)
alldata(1:numel(data{k}),k) = data{k};
end
Then you can plot this and take the mean using nanmean.
plot(alldata);
avg = nanmean(alldata, 2);

Is it possible to plot rows of a matrix without a for loop?

I have a matrix that stores multiple functions in its rows each evaluated against the interval [0,20]. I'm running through a for loop to output them at the moment. Is there a better way of doing this or is this the only way of doing it in MATLAB?
h = 0.1;
xLine = 0:h:20;
nGrid = length(xLine);
nu = [ 1, 2, 3 ];
nNu = length(nu);
b = zeros(nNu,nGrid);
for i=1:nNu
b(i:i,1:nGrid) = besselj(nu(i), xLine);
end
hFig = figure(1);
hold on
set(hFig, 'Position', [1000 600 800 500]);
for i=1:nNu
plot(xLine, b(i:i,1:nGrid))
end
You can use plot vectorized. Specifically, you can supply b directly into plot but you need to make sure that the larger of the two dimensions in b matches the total number of elements in the vector xLine. This is what you have, so we're good. Therefore, because each unique signal occupies a row in your matrix, just supply b into your plot call and use it a single time.
hFig = figure(1);
hold on
set(hFig, 'Position', [1000 600 800 500]);
plot(xLine, b);
This will plot each row as a separate colour. If you tried doing this, you'll see that the plots are the same in comparison to the for loop approach.
Check out the documentation for plot for more details: http://www.mathworks.com/help/matlab/ref/plot.html
Replace for loop with:
plot(xLine, b(:,1:nGrid))
Note: I can't perfectly recall but some older versions of Matlab may want everything in columns and you'd want to transpose the matrices:
plot(xLine.', b(:,1:nGrid).')

How can thin horizontal lines be added between each row in a grayscale image?

I need to create an nth-order Hadamard matrix, row double it, within each row randomly permute the elements of the matrix, and then display it. So far, I have accomplished all of these things. What I end up with when I imshow(matrix) is a nice picture of black and white boxes. But I haven't figured out how to insert a fine line to divide each row. I can create something like the first image on the left, but not the image on the right (these are Figures 1 and 2 from this paper)
Any help or comments would be thoroughly appreciated.
I've found using vector approaches (e.g., patch and rectangle) for this sort of problem unnecessarily challenging. I think that it's more straightforward to build a new image. This avoids floating-point rounding issues and other things that crop up with vector graphics. My solution below relies on some functions in the Image Processing Toolbox, but is simple and fast:
% Create data similarly to #TryHard
H = hadamard(48);
C = (1+[H;-H])/2;
rng(0); % Set seed
C(:) = C(randperm(numel(C))); % For demo, just permute all values, not rows
% Scale image and lines
scl = 10; % Amount to vertically scale each row
pad = 2; % Number of pixels to add between each row
C = imresize(C,scl,'nearest');
C = blockproc(C,[scl size(C,2)],#(x)[x.data;zeros(pad,size(C,2))]);
C = C(1:end-pad,:); % Remove last line added
% Dispay image
imshow(C)
This results in an image like this
The scl and pad parameters can be easily adjusted to obtain different sizes and relative sizes. You can call imresize(...,'nearest') again after adding the lines to further scale the image if desired. The blocproc line could potentially be made more efficient with various options (see the help). It could also be replaced by calls to im2col and col2im, which possibly could be faster, if messier.
I did not try the code, but I think that something like that should work:
sizeOfACube = 6;
numberOfRows = 47;
RGB = imread('image.png');
RGB = imresize(A, [(numRows+numberOfRows) numCols]);
for i=1:1:NumberOfRows
RGB(i*6,:,:) = 0;
end
imagesc(RGB);
imwrite(RGB,'newImage.png');
with:
sizeOfAcube the size of one cube on the QRcode.
numRows and numCols the number of Rows and Column of the original image.
One solution is to use patches, for instance as follows:
% set up example array
xl = 24; yl = xl;
[X Y] = find(hadamard(xl)==1);
% generate figure
figure, hold on
for ii=1:length(X)
patch(X(ii) + [0 0 1 1],Y(ii) + [0.1 0.9 0.9 0.1],[1 1 1],'Edgecolor',[1 1 1])
end
axis([0 xl+1 0 yl+1])
axis('square')
The patch command patch(x,y, color) accepts the vertices of the polygon element as x and y. In this example you can modify the term [0.1 0.9 0.9 0.1] to set the thickness of the bounding black line.
This generates
Edited
For the particular instance provided by the OP:
H=Hadamard(48); %# now to row-double the matrix
A=(1+H)/2;
B=(1-H)/2;
C=[A; B]; %# the code below randomly permutes elements within the rows of the matrix
[nRows,nCols] = size(C);
[junk,idx] = sort(rand(nRows,nCols),2); %# convert column indices into linear indices
idx = (idx-1)*nRows + ndgrid(1:nRows,1:nCols); %# rearrange whatever matrix
E = C;
E(:) = E(idx);
[X Y] = find(logical(E));
xl = length(X);
yl = length(Y);
figure, hold on
for ii=1:xl
rectangle('Position',[X(ii) Y(ii)+.2 1 0.8],'facecolor',[1 1 1],'edgecolor',[1 1 1])
end
axis([0 max(X)+1 0 max(Y)+1])
axis('square')
set(gca,'color',[0 0 0])
set(gca,'XTickLabel',[],'YTickLabel',[],'XTick',[],'YTick',[])
This example uses rectangle instead of patch to generate sharp corners.
The image: