Stuart’s MATLAB Videos

Watch and Learn

This is machine translation

Translated by Microsoft
Mouseover text to see original. Click the button below to return to the English version of the page.

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.