Stuart’s MATLAB Videos

Watch and Learn

Graphics challenge 10

Posted by Doug Hull,

When I teach the MATLAB graphics class for new technical support engineers at MathWorks, I have one major exercise for them to do. This is that exercise.
This is a good exercise that involved a lot of skills:
  • Lines only have one color
  • Using NaN in plotting
  • Using SET
  • Using convenince functions like XLIM
  • Difference between PLOT and LINE
  • Setting callbacks like WindowButtonMotionFcn
  • Managing state
  • Using currentpoint property of an axis
  • Using a create function and an update function
  • Choosing good architecture to simplify implementation
There is a lot going on in this example, I believe that if someone can accomplish this exercise on their own, in a clean, well formated manner, they are well versed for many graphics challenges in MATLAB. In up coming posts, I will show my solution to this, and point to older videos that give the basic skills that build up to this challenge.

10 CommentsOldest to Newest

Matt Fig replied on : 2 of 10
Here is my 80 line monstrosity. This is my initial reaction to the challenge, though I am sure it could be simplified dramatically!


function [] = dualColorPlot(x,y,lev)
% Pass in x = 0:.01:2*pi; y = sin(x); lev = rand*2-1;
% Matt Fig
fh = figure('windowbuttondownfcn',{@fh_wndbtnfcn},...
            'windowbuttonupfcn',{@fh_wndbtnfcn},...
            'windowbuttonmotionfcn',{@fh_wndmtnfcn},...
            'userdata',false);
L1 = [];  % These hold the handles to the lines.
L2 = [];
L3 = [];
V = 1:length(y);
set_colors();  % This is the function which does the work.


    function [] = set_colors(varargin)
        % Sets the appropriate colors.
        if ~isempty(L1), delete(L1); L1 = []; end
        if ~isempty(L2), delete(L2); L2 = []; end
        
        idxgt = V(y>lev);
        idxlt = setdiff(V,idxgt);
        y2 = y; x2 = x;
        y3 = y; x3 = x;
        y2(idxgt) = nan;
        y3(idxlt) = nan;
        NN = isnan(y2);
        idxn = [strfind(NN,[1 0]);strfind(NN,[0 1])];
        if numel(idxn==1),idxn = [idxn;idxn];end
        
        if isempty(idxn)  % Line is above or below the plot.
            if lev<0
                L1 = line(x3,y3,'color','r');
            else
                L2 = line(x2,y2,'color','b');
            end
            if isempty(L3)
                L3 = line([0 max(x)],[lev lev],'color','g');
            end
        else
            y2(idxn(2,:)+1) = lev;
            y2(idxn(1,:)) = lev;
            y3(idxn(1,:)+1) = lev;
            y3(idxn(2,:)) = lev;

            for jj = 1:2
                int1 = x3(idxn(jj,:));
                int2 = x3(idxn(jj,:)+1);
                M = mod(jj,2);

                for ii = 1:length(int1)  % Interpolate to match at line.
                    G =(int2(ii)-int1(ii))/(sin(int2(ii))-sin(int1(ii)))*...
                       (lev-sin(int1(ii)))+int1(ii);
                    x3(idxn(jj,ii)+M) = G;
                    x2(idxn(jj,ii)+~M) = G;
                end
            end

            L1 = line(x3,y3,'color','r');
            L2 = line(x2,y2,'color','b');
            
            if isempty(L3)
                L3 = line([0 max(x)],[lev lev],'color','g');
            end
        end
    end


    function [] = fh_wndbtnfcn(varargin)
        % Windowbutton-up/down-function for figure.
        set(fh,'userdata',~get(fh,'userdata')); % Button up or down?
    end


    function [] = fh_wndmtnfcn(varargin)
        % Windowbuttonmotionfcn for figure.
        if get(fh,'userdata') && get(fh,'currentobject')==L3
           CP = get(gca,'currentpoint');
           set(L3,'ydata',CP(:,2));
           lev = CP(1,2);
           set_colors();  
        end
    end
end

Mikko Leppänen replied on : 3 of 10
Here is my effort for this great exercise.

function dualColorPlot(x,y,lev)
%DUALCOLORPLOT function plots curve and line so that curve's color is
%   different on both sides of the line
%
%   Example:
%       x = 0:0.01:2*pi; y = sin(x); lev = rand*2 - 1;
%       dualColorPlot(x,y,lev)
%
%   Mikko Leppänen 2011

if nargin < 3
    error('Not enough input arguments, dualColorPlot function requires 3 inputs')
end
x = x(:);
y = y(:);
validateattributes(x,{'numeric'},{'finite','vector'},mfilename,'X',1)
validateattributes(y,{'numeric'},{'finite','vector'},mfilename,'Y',2)
validateattributes(lev,{'numeric'},{'finite','scalar'},mfilename,'LEV',3)
fig = figure('name','dualColorPlot',...
             'numbertitle','off');
ylow = y;
yupp = y;
ylow(y > lev) = nan;
yupp(y < lev) = nan;
h1 = line(x,ylow,'color','b');
h2 = line(x,yupp,'color','r'); 
h3 = line([min(x) max(x)],[lev lev],...
          'color','g',...
          'ButtonDownFcn',@button_down);
ax_handle = get(fig,'currentaxes');
set(ax_handle,'drawmode','fast')
axis auto
set(ax_handle,'xlim',[min(x) max(x)])

    %----------------------------------------------------------------------
    function button_down(varargin)
        sel_typ = get(fig,'selectiontype');
        if strcmp(sel_typ,'normal')
            set(fig,'WindowButtonMotionFcn',@wbmcb)
            set(fig,'WindowButtonUpFcn',@wbucb)
        end
        
        %------------------------------------------------------------------
        function wbmcb(varargin)
            cp = get(ax_handle,'currentpoint');
            lev = cp(1,2);
            ylow = y;
            yupp = y;
            ylow(y > lev) = nan;
            yupp(y < lev) = nan;
            set(h1,'ydata',ylow);
            set(h2,'ydata',yupp);
            set(h3,'ydata',[lev lev])
            drawnow
        end
        
        %------------------------------------------------------------------
        function wbucb(varargin)
            set(fig,'WindowButtonMotionFcn','')
            set(fig,'WindowButtonUpFcn','')
        end  
    end
end

Tony Jiang replied on : 4 of 10
here is my solution after reading the first two posted codes. added a 'state' variable and delegate the plotting to one single function.
function dualColorPlot(x,y,lev)
% Tony Jiang 2011
% Inspired by Matt Fig and Mikko Leppanen
%argument check
validateattributes(x,{'numeric'},{'finite','vector'},mfilename,'X',1)
validateattributes(y,{'numeric'},{'finite','vector'},mfilename,'Y',2)
validateattributes(lev,{'numeric'},{'finite','scalar'},mfilename,'LEV',3)
createPlot(x,y,lev);
state=0; %initiate a state

    function createPlot(x,y,lev)
        figure('name','dualColorPlot',...
            'numbertitle','off',...
            'windowbuttonupfcn',{@btnUp},...
            'windowbuttonmotionfcn',{@btnMove}...
            );
        updatePlot(x,y,lev);       
    end

    function updatePlot(x,y,lev)
        clf
        x = x(:);  y = y(:);      
        y1 = y;
        y2 = y;
        y1(y > lev) = nan;
        y2(y < lev) = nan;
       line(x,y1,'color','b');
       line(x,y2,'color','r');
       line([min(x) max(x)],[lev lev],...
                  'color','g',...
                  'ButtonDownFcn',@btnDn);

        xlim([min(x) max(x)]);
    end
    %----------------------------------------------------------------------
    function btnDn(varargin)
        state=1; %update state
    end
    %------------------------------------------------------------------
    function btnMove(varargin)
        if state
            cp = get(gca,'currentpoint');
            updatePlot(x,y,cp(1,2));
        end
    end
    %------------------------------------------------------------------
    function btnUp(varargin)
        state=0;       
    end
end
David McIlhagger replied on : 5 of 10
function f = dualColourPlot(x, y, lev)

    f = figure('WindowButtonDownFcn',@wbdcb,'WindowButtonUpFcn',@wbucb);
    ah = axes('DrawMode','fast');

    hold all;

    [hAbove, hBelow] = plotDualColor(x,y,lev);

    hLev = plotLev([x(1) x(end)],[lev lev]);

    grid on;

    function wbmcb(src,evnt)
        if strcmp(get(src,'SelectionType'),'normal')       
            cp = get(ah,'CurrentPoint');
            lev = cp(1,2);
            delete(hLev);
            hLev = plotLev([x(1) x(end)],[lev lev]);
            delete(hAbove);
            delete(hBelow);
            [hAbove, hBelow] = plotDualColor(x,y,lev);
        end
    end

    function [hAbove, hBelow] = plotDualColor(x,y,lev)
        y_above = y;
        y_above(y_above=lev) = nan;
        hAbove = plot(ah, x, y_above,'r');
        hBelow = plot(ah, x, y_below,'g');
    end

    function wbdcb(src,evnt)
        set(f,'WindowButtonMotionFcn',@wbmcb);
    end

    function wbucb(src,evnt)
        set(f,'WindowButtonMotionFcn',[]);
    end
end

function hLev = plotLev(x,y)
    hLev = line(x,y,'Color','k');
end
Peter replied on : 6 of 10
Hi David, there is still bug in your version ? ??? Undefined function or variable 'y_below'.
Jorge replied on : 7 of 10
Hi! Here is my fast, specific implementation:
function dualColorPlot(x,y,lev)

hFig = figure('WindowButtonDownFcn', @PressMeFcn, ...
    'WindowButtonUpFcn', @ReleaseMeFcn);

Y = [y;y];
Y([y<lev;y>lev]) = NaN;

hold on;
hRed  = plot(x, Y(1,:), 'r');
hBlue = plot(x, Y(2,:), 'b');
hLine = line(get(gca, 'XLim'), lev*[1,1], 'Color', 'g', ...
    'ButtonDownFcn', @MoveMeFcn);
yLims = get(gca, 'YLim');

    function PressMeFcn(varargin)
        set(hFig, 'WindowButtonMotionFcn', @MoveMeFcn);
    end

    function MoveMeFcn(varargin)
        currentPnt = get(gca, 'CurrentPoint');
        lev = currentPnt(1,2);
        Y = [y;y];
        Y([y<lev;y>lev])  = NaN;
        set(hLine, 'YData', max(min(lev*[1,1],yLims(2)),yLims(1)));
        set(hRed, 'YData', Y(1,:));
        set(hBlue, 'YData', Y(2,:));
    end

    function ReleaseMeFcn(varargin)
       set(hFig, 'WindowButtonMotionFcn', []) ;
    end
end
Jorge replied on : 8 of 10
Consider the following modifications if one wants to start dragging the horizontal line by only clicking on it ;)
hFig = figure('WindowButtonUpFcn', @ReleaseMeFcn);
hLine = line(get(gca, 'XLim'), lev*[1,1], 'Color', 'g', ...
    'ButtonDownFcn', @PressMeFcn);
Paulo Silva replied on : 9 of 10
This is the version I made when the video was posted, had to draw lines with . style because in the intersection parts with lev line they would connect the ends. %call the function clf dualColorPlot(0:0.1:10,sin(0:0.1:10),0.5) %the function function dualColorPlot(x,y,lev) set(gcf,'WindowButtonDownFcn',@iniciamovelinha) set(gcf,'WindowButtonUpFcn',@paramovelinha) h1=line(nan,nan,'color',[1 0 0],'LineStyle','.'); h2=line(nan,nan,'color',[0 0 1],'LineStyle','.'); h3=line(nan,nan,'Color',[0 1 0]); putlines(lev) function putlines(lev) idx1=y>lev; idx2=y<lev; set(h1,'xdata',x(idx1)) set(h1,'ydata',y(idx1)) set(h2,'xdata',x(idx2)) set(h2,'ydata',y(idx2)) set(h3,'xdata',get(gca,'xlim')) set(h3,'ydata',[lev lev]) end function iniciamovelinha(a,b) set(gcf,'WindowButtonMotionFcn',@movelinha) end function paramovelinha(a,b) set(gcf,'WindowButtonMotionFcn','') end function movelinha(a,b) p=get(gca,'currentpoint'); lev=p(1,2); set(h3,'Ydata',[lev lev]); putlines(lev) end end
David McIlhagger replied on : 10 of 10
Something very strange with the comments section/browser/windows clipboard in that the copy and pasted code didn't get pasted correctly. Line 28 is wrong, it should read:
y_above(y_above<lev) = nan;
and lines 29 and 30 were missing and should be:
y_below = y;
y_below(y_below>=lev) = nan;
Code should work now. Probably could be more efficient by updating hAbove and hBelow object rather than just deleting them and redrawing.