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.
By
Doug Hull
Doug first used MATLAB in 1994, could not figure it out until he got some help in 1995. He is now dedicated to making sure that no one else wastes a year of their life not knowing MATLAB like he did.
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
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
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
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
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
That’s a great challenge, new users should start by:
doc line
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 endHere 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 endhere 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 endfunction 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'); endHi David,
there is still bug in your version ?
??? Undefined function or variable ‘y_below’.
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 endConsider 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);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
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:
and lines 29 and 30 were missing and should be:
Code should work now. Probably could be more efficient by updating hAbove and hBelow object rather than just deleting
them and redrawing.