Doug's MATLAB Video Tutorials

March 29th, 2011

Graphics challenge

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 Responses to “Graphics challenge”

  1. Paulo Silva replied on :

    That’s a great challenge, new users should start by:
    doc line

  2. Matt Fig replied on :

    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
    
  3. Mikko Leppänen replied on :
    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
    
  4. Tony Jiang replied on :

    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
    
  5. David McIlhagger replied on :
    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
    
  6. Peter replied on :

    Hi David,
    there is still bug in your version ?
    ??? Undefined function or variable ‘y_below’.

  7. Jorge replied on :

    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
    
  8. Jorge replied on :

    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);
    
  9. Paulo Silva replied on :

    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

  10. David McIlhagger replied on :

    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.

Leave a Reply

Wrap code fragments inside <pre> tags, like this:

<pre class="code">
a = magic(3);
sum(a)
</pre>

If you have a "<" character in your code, either follow it with a space or replace it with "&lt;" (including the semicolon).


MathWorks

Doug Hull is a proud MathWorker who is on a mission to help you with MATLAB.

Doug's picture

These postings are the author's and don't necessarily represent the opinions of The MathWorks.