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

3

16

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):

Periodic plot

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.

Boardwalk answered 3/12, 2018 at 10:28 Comment(2)
What version of MATLAB are you using?Saenz
Sorta, kinda related.Coal
S
6

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:

demo

Saenz answered 4/12, 2018 at 10:27 Comment(3)
Very nice alternative, though I personally would prefer using transparency. In another (perhaps very specific case) where you maybe want to fade in lines, this would mean that you get a slight interruption in the a 'solid' line since it a 'more white' line is drawn over it. Totally not relevant to the question, but just a thought. Very much depends on the specific mechanism, linewidth, and to what degree you are a nitpicker.Azrael
@Azrael Agreed, true transparency is king when doable. I didn't do any real performance tests but in making the above, I think this is slower than the existing answers using transparency too.Saenz
Thanks for your answers, Wolfie. Although I like more the transparency approach, I prefer to this solution since it doesn't use depreciated parameters. Awesome!Boardwalk
S
19

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.

fade plot demo

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...

fader

Saenz answered 3/12, 2018 at 11:1 Comment(3)
It seems that line objects are not supposed to support transparency: mathworks.com/help/matlab/ref/alpha.html#buvaucs-1-objJamshedpur
No, I don't. I don't understand why they think only patches need transparency, it certainly is useful for lines as well (had a colleague in the past who used it effectively with parallel coordinates, for example). In this case it doesn't really need to be transparent, you could instead change the RGB values to approach white.Jamshedpur
@Cris transparency has advantages over colour fading when things overlap for example... Brice's answer suggested colour fading but he deleted it on my suggestion transparency was simpler (before realising it's no longer supported)Saenz
A
17

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:

Quick-release-mechanism

Azrael answered 3/12, 2018 at 11:2 Comment(2)
Nice solution for newer MATLAB versions, annoying how MathWorks removed the alpha functionality, leaving us the FileExchange patchline and not a simple native replacement! Thanks for expanding on this alternative in your edit.Saenz
Someone copy-pasted your solution in a FileExchange submission, without attribution: mathworks.com/matlabcentral/fileexchange/69816-fadeitJamshedpur
S
6

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:

demo

Saenz answered 4/12, 2018 at 10:27 Comment(3)
Very nice alternative, though I personally would prefer using transparency. In another (perhaps very specific case) where you maybe want to fade in lines, this would mean that you get a slight interruption in the a 'solid' line since it a 'more white' line is drawn over it. Totally not relevant to the question, but just a thought. Very much depends on the specific mechanism, linewidth, and to what degree you are a nitpicker.Azrael
@Azrael Agreed, true transparency is king when doable. I didn't do any real performance tests but in making the above, I think this is slower than the existing answers using transparency too.Saenz
Thanks for your answers, Wolfie. Although I like more the transparency approach, I prefer to this solution since it doesn't use depreciated parameters. Awesome!Boardwalk

© 2022 - 2024 — McMap. All rights reserved.