Code covered by the BSD License  

Highlights from
Conditionally colored line plot

image thumbnail

Conditionally colored line plot

by

Matt Tearle

 

Plots (2D line) graph split into two colors above and below a given threshold value

splitcolorplot(x,y,val,fmt1,fmt2)
function splitcolorplot(x,y,val,fmt1,fmt2)
% SPLITCOLORPLOT Plots graph split into two colors above and below a given threshold value
%   SPLITCOLORPLOT(X,Y,VAL) plots vector Y against vector X, using one
%   color when Y > VAL and another when Y < VAL.  If VAL is not specified,
%   a dividing value of 0 is used.
%   As with PLOT, SPLITCOLORPLOT(Y,VAL) plots Y against index numbers,
%   instead of X values.
%   SPLITCOLORPLOT(X,Y,VAL,FMT1,FMT2) uses standard PLOT formatting strings
%   to define the color, linestyle, and marker of the two lines.  FMT1
%   defines the formatting for the line when Y > VAL.
%
%   Acceptable calling syntaxes:
%     SPLITCOLORPLOT(Y)                     [index used for X & VAL = 0]
%     SPLITCOLORPLOT(X,Y)                   [VAL = 0]
%     SPLITCOLORPLOT(Y,VAL)                 [index used for X]
%     SPLITCOLORPLOT(X,Y,VAL)
%     SPLITCOLORPLOT(X,Y,FMT1,FMT2)         [VAL = 0]
%     SPLITCOLORPLOT(Y,VAL,FMT1,FMT2)       [index used for X]
%     SPLITCOLORPLOT(X,Y,VAL,FMT1,FMT2)
%
%   Example:
%   >> x = 0:0.1:10;
%   >> y = sin(4*x);
%   >> splitcolorplot(x,y,0.4)
%   >> splitcolorplot(y,-0.2,'rx--','k*:')

%  Parse inputs (nested function used to set x, y, val, and all necessary
%  format options)
checkinputs(nargin)

%  Find zero crossings ("zero" = specified level)
if isrow(y)
    idx = [0,find(sign(y(2:end)-val)~=sign(y(1:end-1)-val)),lngth];
else
    idx = [0;find(sign(y(2:end)-val)~=sign(y(1:end-1)-val));lngth];
end
nidx = length(idx);

%  Make an invisible plot -- this means that splitcolorplot has the same
%  overwriting behavior as plot
plot(x,y,'visible','off')

%  Loop over each segment
for k=2:nidx
    %  Get first and last indices
    k1 = idx(k-1)+1;
    k2 = idx(k);
    %  Choose format, depending on whether y > level or not
    fmt = (y(k1)<=val) + 1;
    %  Plot line segment
    line(x(k1:k2),y(k1:k2),'color',clr{fmt},'marker',mrkr{fmt},'linestyle',lntp{fmt})
    %  Plot linear interpolation to beginning of next line segment
    if k<nidx
        %  Start point is last point of previous line segment
        %  End point is beginning of next segment
        x1 = x(k2);
        x2 = x(k2+1);
        %  Get associated y values
        y1 = y(k2);
        y2 = y(k2+1);
        %  Slope
        m = (y2-y1)/(x2-x1);
        %  Find point on line where y = val
        x0 = x1 + (val-y1)/m;
        %  Plot from end of first segment to y = val
        line([x1,x0],[y1,val],'color',clr{fmt},'linestyle',lntp{fmt})
        %  Change formats and plot from y = val to start of 2nd segment
        fmt = mod(fmt,2) + 1;
        line([x0,x2],[val,y2],'color',clr{fmt},'linestyle',lntp{fmt})
    end
end

    %  Parse all the inputs
    function checkinputs(n)
        %  As long as we have some inputs, get the length of the first
        %  input (sort out other possible errors later)
        if n
            lngth = length(x);
        end
        %  Assign values and defaults as best we can (and trust the later
        %  error checking to sort out any problems)
        %  How many Romans?!
        switch n
            %  i
            case 1
                % Single input is vector of y values
                y = x;
                % Everything else is default
                x = 1:lngth;
                val = 0;
                fmt1 = 'o-';
                fmt2 = 'o-';
            %  ite!
            case 2
                % Two inputs could be x & y or y & level
                % Two equal-sized vectors => x & y
                if isequal(size(x),size(y))
                    val = 0;
                % Scalar y => y is level (and x is actually y)
                elseif numel(y)==1
                    val = y;
                    y = x;
                    x = 1:lngth;
                else
                    error('x & y must be same size, and level must be a scalar')
                end
                fmt1 = 'o-';
                fmt2 = 'o-';
            %  if you don't understand these comments, you seriously need
            %  to watch "Life of Brian"
            case 3
                % x, y, and level are all set, so set default formats
                fmt1 = 'o-';
                fmt2 = 'o-';
            case 4
                % Four inputs => last two are formats
                fmt2 = fmt1;
                fmt1 = val;
                % First two inputs could be x & y or y & level
                % Two equal-sized vectors => x & y
                if isequal(size(x),size(y))
                    val = 0;
                % Scalar y => y is level (and x is actually y)
                elseif numel(y)==1
                    val = y;
                    y = x;
                    x = 1:lngth;
                else
                    error('x & y must be same size, and level must be a scalar')
                end
            case 5
                % Nothing to do here
            otherwise
                % No.
                error('Wrong number of inputs')
        end
        %  Now error-check the hell out of the x, y, val, fmt1, & fmt2 that
        %  we ended up with.
        if ~isequal(size(x),size(y))
            error('x & y must be same size')
        elseif ~isvector(x)
            error('x & y must be vectors')
        elseif ~isscalar(val)
            error('Level must be a scalar')
        elseif ~isnumeric(x) || ~isnumeric(y) || ~isnumeric(val)
            error('x, y, and level must be numeric')
        elseif ~ischar(fmt1) || ~ischar(fmt2)
            error('Formats must be strings')
        end
        %  Parse and deconstruct the format strings
        %  Look for color specifiers first
        [clr{1},fmt1] = getcolor(fmt1,'b');
        [clr{2},fmt2] = getcolor(fmt2,[0 0.5 0]);
        %  And now for markers
        [mrkr{1},fmt1] = getmarker(fmt1);
        [mrkr{2},fmt2] = getmarker(fmt2);
        %  And what's left is a linestyle
        fmt1 = getlnstyle(fmt1,mrkr{1});
        fmt2 = getlnstyle(fmt2,mrkr{2});
        lntp = {fmt1,fmt2};
    end

    function [c,fmt] = getcolor(fmt,def)
        %  Possible values
        clrs = '[bgrcymkw]';
        %  See if fmt1 includes any of the possible values
        idx = regexp(fmt,clrs);
        if isempty(idx)
            % No?  Set default
            c = def;
        else
            % Yes?  Cool, that's the color.  Remove all color specifiers
            % from the string.
            if numel(idx)~=1
                warning('SPLITCOLORPLOT:multipleColors',...
                    'More than one color detected')
            end
            c = fmt(idx(1));
            fmt(idx) = [];
        end
    end

    function [m,fmt] = getmarker(fmt)
        marks = '[.ox+*sdv^<>ph]';
        idx = regexp(fmt,marks);
        %  Special case: if there's a dot (.), we need to make sure it
        %  isn't part of the linestyle specifier -.
        if ~isempty(regexp(fmt,'.','once'))
            %  Go through all possibilities (.s) in reverse order --
            %  reverse because we will remove any that shouldn't be there,
            %  and that will mess up the indexing
            for j=length(idx):-1:1
                %  If there's a - in front of the ., remove it from the
                %  list of possibilities (it's a line specifier, not a
                %  marker)
                if idx(j)>1 && strcmp(fmt(idx(j)-1:idx(j)),'-.')
                    idx(j) = [];
                end
            end
        end
        % OK, now go through and work out marker
        if isempty(idx)
            % Nothing given?  Set default
            m = 'none';
        else
            % Otherwise, that's the marker.  Remove all marker specifiers
            % from the string.
            if numel(idx)~=1
                warning('SPLITCOLORPLOT:multipleMarkers',...
                    'More than one marker type detected')
            end
            m = fmt(idx(1));
            fmt(idx) = [];
        end
    end

    function fmt = getlnstyle(fmt,m)
        %  If there's nothing left, no linestyle was specified.  However,
        %  the depends on whether a marker was given
        if isempty(fmt)
            % No marker (and no linestyle) => default solid line
            if strcmp(m,'none')
                fmt = '-';
            % Marker given but no linestyle => no line (just marker)
            else
                fmt = 'none';
            end
        %  Something was specified.  Was it valid?
        elseif ~ismember(fmt,{'-','--',':','-.'})
            error('Unknown format specification')
        end
    end

end

Contact us