tomwalters@0: function [h,yy,zz] = arrow(varargin) tomwalters@0: % ARROW Draw a line with an arrowhead. tomwalters@0: % tomwalters@0: % ARROW(Start,Stop) draws a line with an arrow from Start to Stop (points tomwalters@0: % should be vectors of length 2 or 3, or matrices with 2 or 3 tomwalters@0: % columns), and returns the graphics handle of the arrow(s). tomwalters@0: % tomwalters@0: % ARROW uses the mouse (click-drag) to create an arrow. tomwalters@0: % tomwalters@0: % ARROW DEMO & ARROW DEMO2 show 3-D & 2-D demos of the capabilities of ARROW. tomwalters@0: % tomwalters@0: % ARROW may be called with a normal argument list or a property-based list. tomwalters@0: % ARROW(Start,Stop,Length,BaseAngle,TipAngle,Width,Page,CrossDir) is tomwalters@0: % the full normal argument list, where all but the Start and Stop tomwalters@0: % points are optional. If you need to specify a later argument (e.g., tomwalters@0: % Page) but want default values of earlier ones (e.g., TipAngle), tomwalters@0: % pass an empty matrix for the earlier ones (e.g., TipAngle=[]). tomwalters@0: % tomwalters@0: % ARROW('Property1',PropVal1,'Property2',PropVal2,...) creates arrows with the tomwalters@0: % given properties, using default values for any unspecified or given as tomwalters@0: % 'default' or NaN. Some properties used for line and patch objects are tomwalters@0: % used in a modified fashion, others are passed directly to LINE, PATCH, tomwalters@0: % or SET. For a detailed properties explanation, call ARROW PROPERTIES. tomwalters@0: % tomwalters@0: % Start The starting points. B tomwalters@0: % Stop The end points. /|\ ^ tomwalters@0: % Length Length of the arrowhead in pixels. /|||\ | tomwalters@0: % BaseAngle Base angle in degrees (ADE). //|||\\ L| tomwalters@0: % TipAngle Tip angle in degrees (ABC). ///|||\\\ e| tomwalters@0: % Width Width of the base in pixels. ////|||\\\\ n| tomwalters@0: % Page Use hardcopy proportions. /////|D|\\\\\ g| tomwalters@0: % CrossDir Vector || to arrowhead plane. //// ||| \\\\ t| tomwalters@0: % NormalDir Vector out of arrowhead plane. /// ||| \\\ h| tomwalters@0: % Ends Which end has an arrowhead. //<----->|| \\ | tomwalters@0: % ObjectHandles Vector of handles to update. / base ||| \ V tomwalters@0: % E angle||<-------->C tomwalters@0: % ARROW(H,'Prop1',PropVal1,...), where H is a |||tipangle tomwalters@0: % vector of handles to previously-created arrows ||| tomwalters@0: % and/or line objects, will update the previously- ||| tomwalters@0: % created arrows according to the current view -->|A|<-- width tomwalters@0: % and any specified properties, and will convert tomwalters@0: % two-point line objects to corresponding arrows. ARROW(H) will update tomwalters@0: % the arrows if the current view has changed. Root, figure, or axes tomwalters@0: % handles included in H are replaced by all descendant Arrow objects. tomwalters@0: % tomwalters@0: % A property list can follow any specified normal argument list, e.g., tomwalters@0: % ARROW([1 2 3],[0 0 0],36,'BaseAngle',60) creates an arrow from (1,2,3) to tomwalters@0: % the origin, with an arrowhead of length 36 pixels and 60-degree base angle. tomwalters@0: % tomwalters@0: % The basic arguments or properties can generally be vectorized to create tomwalters@0: % multiple arrows with the same call. This is done by passing a property tomwalters@0: % with one row per arrow, or, if all arrows are to have the same property tomwalters@0: % value, just one row may be specified. tomwalters@0: % tomwalters@0: % You may want to execute AXIS(AXIS) before calling ARROW so it doesn't change tomwalters@0: % the axes on you; ARROW determines the sizes of arrow components BEFORE the tomwalters@0: % arrow is plotted, so if ARROW changes axis limits, arrows may be malformed. tomwalters@0: % tomwalters@0: % This version of ARROW uses features of MATLAB 5 and is incompatible with tomwalters@0: % earlier MATLAB versions (ARROW for MATLAB 4.2c is available separately); tomwalters@0: % some problems with perspective plots still exist. tomwalters@0: % Copyright (c)1995-2002, Dr. Erik A. Johnson , 11/15/02 tomwalters@0: % Revision history: tomwalters@0: % 11/15/02 EAJ Accomodate how MATLAB 6.5 handles NaN and logicals tomwalters@0: % 7/28/02 EAJ Tried (but failed) work-around for MATLAB 6.x / OpenGL bug tomwalters@0: % if zero 'Width' or not double-ended tomwalters@0: % 11/10/99 EAJ Add logical() to eliminate zero index problem in MATLAB 5.3. tomwalters@0: % 11/10/99 EAJ Corrected warning if axis limits changed on multiple axes. tomwalters@0: % 11/10/99 EAJ Update e-mail address. tomwalters@0: % 2/10/99 EAJ Some documentation updating. tomwalters@0: % 2/24/98 EAJ Fixed bug if Start~=Stop but both colinear with viewpoint. tomwalters@0: % 8/14/97 EAJ Added workaround for MATLAB 5.1 scalar logical transpose bug. tomwalters@0: % 7/21/97 EAJ Fixed a few misc bugs. tomwalters@0: % 7/14/97 EAJ Make arrow([],'Prop',...) do nothing (no old handles) tomwalters@0: % 6/23/97 EAJ MATLAB 5 compatible version, release. tomwalters@0: % 5/27/97 EAJ Added Line Arrows back in. Corrected a few bugs. tomwalters@0: % 5/26/97 EAJ Changed missing Start/Stop to mouse-selected arrows. tomwalters@0: % 5/19/97 EAJ MATLAB 5 compatible version, beta. tomwalters@0: % 4/13/97 EAJ MATLAB 5 compatible version, alpha. tomwalters@0: % 1/31/97 EAJ Fixed bug with multiple arrows and unspecified Z coords. tomwalters@0: % 12/05/96 EAJ Fixed one more bug with log plots and NormalDir specified tomwalters@0: % 10/24/96 EAJ Fixed bug with log plots and NormalDir specified tomwalters@0: % 11/13/95 EAJ Corrected handling for 'reverse' axis directions tomwalters@0: % 10/06/95 EAJ Corrected occasional conflict with SUBPLOT tomwalters@0: % 4/24/95 EAJ A major rewrite. tomwalters@0: % Fall 94 EAJ Original code. tomwalters@0: % Things to be done: tomwalters@0: % - segment parsing, computing, and plotting into separate subfunctions tomwalters@0: % - change computing from Xform to Camera paradigms tomwalters@0: % + this will help especially with 3-D perspective plots tomwalters@0: % + if the WarpToFill section works right, remove warning code tomwalters@0: % + when perpsective works properly, remove perspective warning code tomwalters@0: % - add cell property values and struct property name/values (like get/set) tomwalters@0: % - get rid of NaN as the "default" data label tomwalters@0: % + perhaps change userdata to a struct and don't include (or leave tomwalters@0: % empty) the values specified as default; or use a cell containing tomwalters@0: % an empty matrix for a default value tomwalters@0: % - add functionality of GET to retrieve current values of ARROW properties tomwalters@0: % Many thanks to Keith Rogers for his many excellent tomwalters@0: % suggestions and beta testing. Check out his shareware package MATDRAW tomwalters@0: % (at ftp://ftp.mathworks.com/pub/contrib/v5/graphics/matdraw/) -- he has tomwalters@0: % permission to distribute ARROW with MATDRAW. tomwalters@0: % Permission is granted to distribute ARROW with the toolboxes for the book tomwalters@0: % "Solving Solid Mechanics Problems with MATLAB 5", by F. Golnaraghi et al. tomwalters@0: % (Prentice Hall, 1999). tomwalters@0: % global variable initialization tomwalters@0: global ARROW_PERSP_WARN ARROW_STRETCH_WARN ARROW_AXLIMITS tomwalters@0: if isempty(ARROW_PERSP_WARN ), ARROW_PERSP_WARN =1; end; tomwalters@0: if isempty(ARROW_STRETCH_WARN), ARROW_STRETCH_WARN=1; end; tomwalters@0: % Handle callbacks tomwalters@0: if (nargin>0 & isstr(varargin{1}) & strcmp(lower(varargin{1}),'callback')), tomwalters@0: arrow_callback(varargin{2:end}); return; tomwalters@0: end; tomwalters@0: % Are we doing the demo? tomwalters@0: c = sprintf('\n'); tomwalters@0: if (nargin==1 & isstr(varargin{1})), tomwalters@0: arg1 = lower(varargin{1}); tomwalters@0: if strncmp(arg1,'prop',4), arrow_props; tomwalters@0: elseif strncmp(arg1,'demo',4) tomwalters@0: clf reset tomwalters@0: demo_info = arrow_demo; tomwalters@0: if ~strncmp(arg1,'demo2',5), tomwalters@0: hh=arrow_demo3(demo_info); tomwalters@0: else, tomwalters@0: hh=arrow_demo2(demo_info); tomwalters@0: end; tomwalters@0: if (nargout>=1), h=hh; end; tomwalters@0: elseif strncmp(arg1,'fixlimits',3), tomwalters@0: arrow_fixlimits(ARROW_AXLIMITS); tomwalters@0: ARROW_AXLIMITS=[]; tomwalters@0: elseif strncmp(arg1,'help',4), tomwalters@0: disp(help(mfilename)); tomwalters@0: else, tomwalters@0: error([upper(mfilename) ' got an unknown single-argument string ''' deblank(arg1) '''.']); tomwalters@0: end; tomwalters@0: return; tomwalters@0: end; tomwalters@0: % Check # of arguments tomwalters@0: if (nargout>3), error([upper(mfilename) ' produces at most 3 output arguments.']); end; tomwalters@0: % find first property number tomwalters@0: firstprop = nargin+1; tomwalters@0: for k=1:length(varargin), if ~isnumeric(varargin{k}), firstprop=k; break; end; end; tomwalters@0: lastnumeric = firstprop-1; tomwalters@0: % check property list tomwalters@0: if (firstprop<=nargin), tomwalters@0: for k=firstprop:2:nargin, tomwalters@0: curarg = varargin{k}; tomwalters@0: if ~isstr(curarg) | sum(size(curarg)>1)>1, tomwalters@0: error([upper(mfilename) ' requires that a property name be a single string.']); tomwalters@0: end; tomwalters@0: end; tomwalters@0: if (rem(nargin-firstprop,2)~=1), tomwalters@0: error([upper(mfilename) ' requires that the property ''' ... tomwalters@0: varargin{nargin} ''' be paired with a property value.']); tomwalters@0: end; tomwalters@0: end; tomwalters@0: % default output tomwalters@0: if (nargout>0), h=[]; end; tomwalters@0: if (nargout>1), yy=[]; end; tomwalters@0: if (nargout>2), zz=[]; end; tomwalters@0: % set values to empty matrices tomwalters@0: start = []; tomwalters@0: stop = []; tomwalters@0: len = []; tomwalters@0: baseangle = []; tomwalters@0: tipangle = []; tomwalters@0: wid = []; tomwalters@0: page = []; tomwalters@0: crossdir = []; tomwalters@0: ends = []; tomwalters@0: ax = []; tomwalters@0: oldh = []; tomwalters@0: ispatch = []; tomwalters@0: defstart = [NaN NaN NaN]; tomwalters@0: defstop = [NaN NaN NaN]; tomwalters@0: deflen = 16; tomwalters@0: defbaseangle = 90; tomwalters@0: deftipangle = 16; tomwalters@0: defwid = 0; tomwalters@0: defpage = 0; tomwalters@0: defcrossdir = [NaN NaN NaN]; tomwalters@0: defends = 1; tomwalters@0: defoldh = []; tomwalters@0: defispatch = 1; tomwalters@0: % The 'Tag' we'll put on our arrows tomwalters@0: ArrowTag = 'Arrow'; tomwalters@0: % check for oldstyle arguments tomwalters@0: if (firstprop==2), tomwalters@0: % assume arg1 is a set of handles tomwalters@0: oldh = varargin{1}(:); tomwalters@0: if isempty(oldh), return; end; tomwalters@0: elseif (firstprop>9), tomwalters@0: error([upper(mfilename) ' takes at most 8 non-property arguments.']); tomwalters@0: elseif (firstprop>2), tomwalters@0: s = str2mat('start','stop','len','baseangle','tipangle','wid','page','crossdir'); tomwalters@0: for k=1:firstprop-1, eval([deblank(s(k,:)) '=varargin{k};']); end; tomwalters@0: end; tomwalters@0: % parse property pairs tomwalters@0: extraprops={}; tomwalters@0: for k=firstprop:2:nargin, tomwalters@0: prop = varargin{k}; tomwalters@0: val = varargin{k+1}; tomwalters@0: prop = [lower(prop(:)') ' ']; tomwalters@0: if strncmp(prop,'start' ,5), start = val; tomwalters@0: elseif strncmp(prop,'stop' ,4), stop = val; tomwalters@0: elseif strncmp(prop,'len' ,3), len = val(:); tomwalters@0: elseif strncmp(prop,'base' ,4), baseangle = val(:); tomwalters@0: elseif strncmp(prop,'tip' ,3), tipangle = val(:); tomwalters@0: elseif strncmp(prop,'wid' ,3), wid = val(:); tomwalters@0: elseif strncmp(prop,'page' ,4), page = val; tomwalters@0: elseif strncmp(prop,'cross' ,5), crossdir = val; tomwalters@0: elseif strncmp(prop,'norm' ,4), if (isstr(val)), crossdir=val; else, crossdir=val*sqrt(-1); end; tomwalters@0: elseif strncmp(prop,'end' ,3), ends = val; tomwalters@0: elseif strncmp(prop,'object',6), oldh = val(:); tomwalters@0: elseif strncmp(prop,'handle',6), oldh = val(:); tomwalters@0: elseif strncmp(prop,'type' ,4), ispatch = val; tomwalters@0: elseif strncmp(prop,'userd' ,5), %ignore it tomwalters@0: else, tomwalters@0: % make sure it is a valid patch or line property tomwalters@0: eval('get(0,[''DefaultPatch'' varargin{k}]);err=0;','err=1;'); errstr=lasterr; tomwalters@0: if (err), eval('get(0,[''DefaultLine'' varargin{k}]);err=0;','err=1;'); end; tomwalters@0: if (err), tomwalters@0: errstr(1:max(find(errstr==setstr(13)|errstr==setstr(10)))) = ''; tomwalters@0: error([upper(mfilename) ' got ' errstr]); tomwalters@0: end; tomwalters@0: extraprops={extraprops{:},varargin{k},val}; tomwalters@0: end; tomwalters@0: end; tomwalters@0: % Check if we got 'default' values tomwalters@0: start = arrow_defcheck(start ,defstart ,'Start' ); tomwalters@0: stop = arrow_defcheck(stop ,defstop ,'Stop' ); tomwalters@0: len = arrow_defcheck(len ,deflen ,'Length' ); tomwalters@0: baseangle = arrow_defcheck(baseangle,defbaseangle,'BaseAngle' ); tomwalters@0: tipangle = arrow_defcheck(tipangle ,deftipangle ,'TipAngle' ); tomwalters@0: wid = arrow_defcheck(wid ,defwid ,'Width' ); tomwalters@0: crossdir = arrow_defcheck(crossdir ,defcrossdir ,'CrossDir' ); tomwalters@0: page = arrow_defcheck(page ,defpage ,'Page' ); tomwalters@0: ends = arrow_defcheck(ends ,defends ,'' ); tomwalters@0: oldh = arrow_defcheck(oldh ,[] ,'ObjectHandles'); tomwalters@0: ispatch = arrow_defcheck(ispatch ,defispatch ,'' ); tomwalters@0: % check transpose on arguments tomwalters@0: [m,n]=size(start ); if any(m==[2 3])&(n==1|n>3), start = start'; end; tomwalters@0: [m,n]=size(stop ); if any(m==[2 3])&(n==1|n>3), stop = stop'; end; tomwalters@0: [m,n]=size(crossdir); if any(m==[2 3])&(n==1|n>3), crossdir = crossdir'; end; tomwalters@0: % convert strings to numbers tomwalters@0: if ~isempty(ends) & isstr(ends), tomwalters@0: endsorig = ends; tomwalters@0: [m,n] = size(ends); tomwalters@0: col = lower([ends(:,1:min(3,n)) ones(m,max(0,3-n))*' ']); tomwalters@0: ends = NaN*ones(m,1); tomwalters@0: oo = ones(1,m); tomwalters@0: ii=find(all(col'==['non']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*0; end; tomwalters@0: ii=find(all(col'==['sto']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*1; end; tomwalters@0: ii=find(all(col'==['sta']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*2; end; tomwalters@0: ii=find(all(col'==['bot']'*oo)'); if ~isempty(ii), ends(ii)=ones(length(ii),1)*3; end; tomwalters@0: if any(isnan(ends)), tomwalters@0: ii = min(find(isnan(ends))); tomwalters@0: error([upper(mfilename) ' does not recognize ''' deblank(endsorig(ii,:)) ''' as a valid ''Ends'' value.']); tomwalters@0: end; tomwalters@0: else, tomwalters@0: ends = ends(:); tomwalters@0: end; tomwalters@0: if ~isempty(ispatch) & isstr(ispatch), tomwalters@0: col = lower(ispatch(:,1)); tomwalters@0: patchchar='p'; linechar='l'; defchar=' '; tomwalters@0: mask = col~=patchchar & col~=linechar & col~=defchar; tomwalters@0: if any(mask), tomwalters@0: error([upper(mfilename) ' does not recognize ''' deblank(ispatch(min(find(mask)),:)) ''' as a valid ''Type'' value.']); tomwalters@0: end; tomwalters@0: ispatch = (col==patchchar)*1 + (col==linechar)*0 + (col==defchar)*defispatch; tomwalters@0: else, tomwalters@0: ispatch = ispatch(:); tomwalters@0: end; tomwalters@0: oldh = oldh(:); tomwalters@0: % check object handles tomwalters@0: if ~all(ishandle(oldh)), error([upper(mfilename) ' got invalid object handles.']); end; tomwalters@0: % expand root, figure, and axes handles tomwalters@0: if ~isempty(oldh), tomwalters@0: ohtype = get(oldh,'Type'); tomwalters@0: mask = strcmp(ohtype,'root') | strcmp(ohtype,'figure') | strcmp(ohtype,'axes'); tomwalters@0: if any(mask), tomwalters@0: oldh = num2cell(oldh); tomwalters@0: for ii=find(mask)', tomwalters@0: oldh(ii) = {findobj(oldh{ii},'Tag',ArrowTag)}; tomwalters@0: end; tomwalters@0: oldh = cat(1,oldh{:}); tomwalters@0: if isempty(oldh), return; end; % no arrows to modify, so just leave tomwalters@0: end; tomwalters@0: end; tomwalters@0: % largest argument length tomwalters@0: [mstart,junk]=size(start); [mstop,junk]=size(stop); [mcrossdir,junk]=size(crossdir); tomwalters@0: argsizes = [length(oldh) mstart mstop ... tomwalters@0: length(len) length(baseangle) length(tipangle) ... tomwalters@0: length(wid) length(page) mcrossdir length(ends) ]; tomwalters@0: args=['length(ObjectHandle) '; ... tomwalters@0: '#rows(Start) '; ... tomwalters@0: '#rows(Stop) '; ... tomwalters@0: 'length(Length) '; ... tomwalters@0: 'length(BaseAngle) '; ... tomwalters@0: 'length(TipAngle) '; ... tomwalters@0: 'length(Width) '; ... tomwalters@0: 'length(Page) '; ... tomwalters@0: '#rows(CrossDir) '; ... tomwalters@0: '#rows(Ends) ']; tomwalters@0: if (any(imag(crossdir(:))~=0)), tomwalters@0: args(9,:) = '#rows(NormalDir) '; tomwalters@0: end; tomwalters@0: if isempty(oldh), tomwalters@0: narrows = max(argsizes); tomwalters@0: else, tomwalters@0: narrows = length(oldh); tomwalters@0: end; tomwalters@0: if (narrows<=0), narrows=1; end; tomwalters@0: % Check size of arguments tomwalters@0: ii = find((argsizes~=0)&(argsizes~=1)&(argsizes~=narrows)); tomwalters@0: if ~isempty(ii), tomwalters@0: s = args(ii',:); tomwalters@0: while ((size(s,2)>1)&((abs(s(:,size(s,2)))==0)|(abs(s(:,size(s,2)))==abs(' ')))), tomwalters@0: s = s(:,1:size(s,2)-1); tomwalters@0: end; tomwalters@0: s = [ones(length(ii),1)*[upper(mfilename) ' requires that '] s ... tomwalters@0: ones(length(ii),1)*[' equal the # of arrows (' num2str(narrows) ').' c]]; tomwalters@0: s = s'; tomwalters@0: s = s(:)'; tomwalters@0: s = s(1:length(s)-1); tomwalters@0: error(setstr(s)); tomwalters@0: end; tomwalters@0: % check element length in Start, Stop, and CrossDir tomwalters@0: if ~isempty(start), tomwalters@0: [m,n] = size(start); tomwalters@0: if (n==2), tomwalters@0: start = [start NaN*ones(m,1)]; tomwalters@0: elseif (n~=3), tomwalters@0: error([upper(mfilename) ' requires 2- or 3-element Start points.']); tomwalters@0: end; tomwalters@0: end; tomwalters@0: if ~isempty(stop), tomwalters@0: [m,n] = size(stop); tomwalters@0: if (n==2), tomwalters@0: stop = [stop NaN*ones(m,1)]; tomwalters@0: elseif (n~=3), tomwalters@0: error([upper(mfilename) ' requires 2- or 3-element Stop points.']); tomwalters@0: end; tomwalters@0: end; tomwalters@0: if ~isempty(crossdir), tomwalters@0: [m,n] = size(crossdir); tomwalters@0: if (n<3), tomwalters@0: crossdir = [crossdir NaN*ones(m,3-n)]; tomwalters@0: elseif (n~=3), tomwalters@0: if (all(imag(crossdir(:))==0)), tomwalters@0: error([upper(mfilename) ' requires 2- or 3-element CrossDir vectors.']); tomwalters@0: else, tomwalters@0: error([upper(mfilename) ' requires 2- or 3-element NormalDir vectors.']); tomwalters@0: end; tomwalters@0: end; tomwalters@0: end; tomwalters@0: % fill empty arguments tomwalters@0: if isempty(start ), start = [Inf Inf Inf]; end; tomwalters@0: if isempty(stop ), stop = [Inf Inf Inf]; end; tomwalters@0: if isempty(len ), len = Inf; end; tomwalters@0: if isempty(baseangle ), baseangle = Inf; end; tomwalters@0: if isempty(tipangle ), tipangle = Inf; end; tomwalters@0: if isempty(wid ), wid = Inf; end; tomwalters@0: if isempty(page ), page = Inf; end; tomwalters@0: if isempty(crossdir ), crossdir = [Inf Inf Inf]; end; tomwalters@0: if isempty(ends ), ends = Inf; end; tomwalters@0: if isempty(ispatch ), ispatch = Inf; end; tomwalters@0: % expand single-column arguments tomwalters@0: o = ones(narrows,1); tomwalters@0: if (size(start ,1)==1), start = o * start ; end; tomwalters@0: if (size(stop ,1)==1), stop = o * stop ; end; tomwalters@0: if (length(len )==1), len = o * len ; end; tomwalters@0: if (length(baseangle )==1), baseangle = o * baseangle ; end; tomwalters@0: if (length(tipangle )==1), tipangle = o * tipangle ; end; tomwalters@0: if (length(wid )==1), wid = o * wid ; end; tomwalters@0: if (length(page )==1), page = o * page ; end; tomwalters@0: if (size(crossdir ,1)==1), crossdir = o * crossdir ; end; tomwalters@0: if (length(ends )==1), ends = o * ends ; end; tomwalters@0: if (length(ispatch )==1), ispatch = o * ispatch ; end; tomwalters@0: ax = o * gca; tomwalters@0: % if we've got handles, get the defaults from the handles tomwalters@0: if ~isempty(oldh), tomwalters@0: for k=1:narrows, tomwalters@0: oh = oldh(k); tomwalters@0: ud = get(oh,'UserData'); tomwalters@0: ax(k) = get(oh,'Parent'); tomwalters@0: ohtype = get(oh,'Type'); tomwalters@0: if strcmp(get(oh,'Tag'),ArrowTag), % if it's an arrow already tomwalters@0: if isinf(ispatch(k)), ispatch(k)=strcmp(ohtype,'patch'); end; tomwalters@0: % arrow UserData format: [start' stop' len base tip wid page crossdir' ends] tomwalters@0: start0 = ud(1:3); tomwalters@0: stop0 = ud(4:6); tomwalters@0: if (isinf(len(k))), len(k) = ud( 7); end; tomwalters@0: if (isinf(baseangle(k))), baseangle(k) = ud( 8); end; tomwalters@0: if (isinf(tipangle(k))), tipangle(k) = ud( 9); end; tomwalters@0: if (isinf(wid(k))), wid(k) = ud(10); end; tomwalters@0: if (isinf(page(k))), page(k) = ud(11); end; tomwalters@0: if (isinf(crossdir(k,1))), crossdir(k,1) = ud(12); end; tomwalters@0: if (isinf(crossdir(k,2))), crossdir(k,2) = ud(13); end; tomwalters@0: if (isinf(crossdir(k,3))), crossdir(k,3) = ud(14); end; tomwalters@0: if (isinf(ends(k))), ends(k) = ud(15); end; tomwalters@0: elseif strcmp(ohtype,'line')|strcmp(ohtype,'patch'), % it's a non-arrow line or patch tomwalters@0: convLineToPatch = 1; %set to make arrow patches when converting from lines. tomwalters@0: if isinf(ispatch(k)), ispatch(k)=convLineToPatch|strcmp(ohtype,'patch'); end; tomwalters@0: x=get(oh,'XData'); x=x(~isnan(x(:))); if isempty(x), x=NaN; end; tomwalters@0: y=get(oh,'YData'); y=y(~isnan(y(:))); if isempty(y), y=NaN; end; tomwalters@0: z=get(oh,'ZData'); z=z(~isnan(z(:))); if isempty(z), z=NaN; end; tomwalters@0: start0 = [x(1) y(1) z(1) ]; tomwalters@0: stop0 = [x(end) y(end) z(end)]; tomwalters@0: else, tomwalters@0: error([upper(mfilename) ' cannot convert ' ohtype ' objects.']); tomwalters@0: end; tomwalters@0: ii=find(isinf(start(k,:))); if ~isempty(ii), start(k,ii)=start0(ii); end; tomwalters@0: ii=find(isinf(stop( k,:))); if ~isempty(ii), stop( k,ii)=stop0( ii); end; tomwalters@0: end; tomwalters@0: end; tomwalters@0: % convert Inf's to NaN's tomwalters@0: start( isinf(start )) = NaN; tomwalters@0: stop( isinf(stop )) = NaN; tomwalters@0: len( isinf(len )) = NaN; tomwalters@0: baseangle( isinf(baseangle)) = NaN; tomwalters@0: tipangle( isinf(tipangle )) = NaN; tomwalters@0: wid( isinf(wid )) = NaN; tomwalters@0: page( isinf(page )) = NaN; tomwalters@0: crossdir( isinf(crossdir )) = NaN; tomwalters@0: ends( isinf(ends )) = NaN; tomwalters@0: ispatch( isinf(ispatch )) = NaN; tomwalters@0: % set up the UserData data (here so not corrupted by log10's and such) tomwalters@0: ud = [start stop len baseangle tipangle wid page crossdir ends]; tomwalters@0: % Set Page defaults tomwalters@0: page = ~isnan(page) & trueornan(page); tomwalters@0: % Get axes limits, range, min; correct for aspect ratio and log scale tomwalters@0: axm = zeros(3,narrows); tomwalters@0: axr = zeros(3,narrows); tomwalters@0: axrev = zeros(3,narrows); tomwalters@0: ap = zeros(2,narrows); tomwalters@0: xyzlog = zeros(3,narrows); tomwalters@0: limmin = zeros(2,narrows); tomwalters@0: limrange = zeros(2,narrows); tomwalters@0: oldaxlims = zeros(narrows,7); tomwalters@0: oneax = all(ax==ax(1)); tomwalters@0: if (oneax), tomwalters@0: T = zeros(4,4); tomwalters@0: invT = zeros(4,4); tomwalters@0: else, tomwalters@0: T = zeros(16,narrows); tomwalters@0: invT = zeros(16,narrows); tomwalters@0: end; tomwalters@0: axnotdone = logical(ones(size(ax))); tomwalters@0: while (any(axnotdone)), tomwalters@0: ii = min(find(axnotdone)); tomwalters@0: curax = ax(ii); tomwalters@0: curpage = page(ii); tomwalters@0: % get axes limits and aspect ratio tomwalters@0: axl = [get(curax,'XLim'); get(curax,'YLim'); get(curax,'ZLim')]; tomwalters@0: oldaxlims(min(find(oldaxlims(:,1)==0)),:) = [curax reshape(axl',1,6)]; tomwalters@0: % get axes size in pixels (points) tomwalters@0: u = get(curax,'Units'); tomwalters@0: axposoldunits = get(curax,'Position'); tomwalters@0: really_curpage = curpage & strcmp(u,'normalized'); tomwalters@0: if (really_curpage), tomwalters@0: curfig = get(curax,'Parent'); tomwalters@0: pu = get(curfig,'PaperUnits'); tomwalters@0: set(curfig,'PaperUnits','points'); tomwalters@0: pp = get(curfig,'PaperPosition'); tomwalters@0: set(curfig,'PaperUnits',pu); tomwalters@0: set(curax,'Units','pixels'); tomwalters@0: curapscreen = get(curax,'Position'); tomwalters@0: set(curax,'Units','normalized'); tomwalters@0: curap = pp.*get(curax,'Position'); tomwalters@0: else, tomwalters@0: set(curax,'Units','pixels'); tomwalters@0: curapscreen = get(curax,'Position'); tomwalters@0: curap = curapscreen; tomwalters@0: end; tomwalters@0: set(curax,'Units',u); tomwalters@0: set(curax,'Position',axposoldunits); tomwalters@0: % handle non-stretched axes position tomwalters@0: str_stretch = { 'DataAspectRatioMode' ; ... tomwalters@0: 'PlotBoxAspectRatioMode' ; ... tomwalters@0: 'CameraViewAngleMode' }; tomwalters@0: str_camera = { 'CameraPositionMode' ; ... tomwalters@0: 'CameraTargetMode' ; ... tomwalters@0: 'CameraViewAngleMode' ; ... tomwalters@0: 'CameraUpVectorMode' }; tomwalters@0: notstretched = strcmp(get(curax,str_stretch),'manual'); tomwalters@0: manualcamera = strcmp(get(curax,str_camera),'manual'); tomwalters@0: if ~arrow_WarpToFill(notstretched,manualcamera,curax), tomwalters@0: % give a warning that this has not been thoroughly tested tomwalters@0: if 0 & ARROW_STRETCH_WARN, tomwalters@0: ARROW_STRETCH_WARN = 0; tomwalters@0: strs = {str_stretch{1:2},str_camera{:}}; tomwalters@0: strs = [char(ones(length(strs),1)*sprintf('\n ')) char(strs)]'; tomwalters@0: warning([upper(mfilename) ' may not yet work quite right ' ... tomwalters@0: 'if any of the following are ''manual'':' strs(:).']); tomwalters@0: end; tomwalters@0: % find the true pixel size of the actual axes tomwalters@0: texttmp = text(axl(1,[1 2 2 1 1 2 2 1]), ... tomwalters@0: axl(2,[1 1 2 2 1 1 2 2]), ... tomwalters@0: axl(3,[1 1 1 1 2 2 2 2]),''); tomwalters@0: set(texttmp,'Units','points'); tomwalters@0: textpos = get(texttmp,'Position'); tomwalters@0: delete(texttmp); tomwalters@0: textpos = cat(1,textpos{:}); tomwalters@0: textpos = max(textpos(:,1:2)) - min(textpos(:,1:2)); tomwalters@0: % adjust the axes position tomwalters@0: if (really_curpage), tomwalters@0: % adjust to printed size tomwalters@0: textpos = textpos * min(curap(3:4)./textpos); tomwalters@0: curap = [curap(1:2)+(curap(3:4)-textpos)/2 textpos]; tomwalters@0: else, tomwalters@0: % adjust for pixel roundoff tomwalters@0: textpos = textpos * min(curapscreen(3:4)./textpos); tomwalters@0: curap = [curap(1:2)+(curap(3:4)-textpos)/2 textpos]; tomwalters@0: end; tomwalters@0: end; tomwalters@0: if ARROW_PERSP_WARN & ~strcmp(get(curax,'Projection'),'orthographic'), tomwalters@0: ARROW_PERSP_WARN = 0; tomwalters@0: warning([upper(mfilename) ' does not yet work right for 3-D perspective projection.']); tomwalters@0: end; tomwalters@0: % adjust limits for log scale on axes tomwalters@0: curxyzlog = [strcmp(get(curax,'XScale'),'log'); ... tomwalters@0: strcmp(get(curax,'YScale'),'log'); ... tomwalters@0: strcmp(get(curax,'ZScale'),'log')]; tomwalters@0: if (any(curxyzlog)), tomwalters@0: ii = find([curxyzlog;curxyzlog]); tomwalters@0: if (any(axl(ii)<=0)), tomwalters@0: error([upper(mfilename) ' does not support non-positive limits on log-scaled axes.']); tomwalters@0: else, tomwalters@0: axl(ii) = log10(axl(ii)); tomwalters@0: end; tomwalters@0: end; tomwalters@0: % correct for 'reverse' direction on axes; tomwalters@0: curreverse = [strcmp(get(curax,'XDir'),'reverse'); ... tomwalters@0: strcmp(get(curax,'YDir'),'reverse'); ... tomwalters@0: strcmp(get(curax,'ZDir'),'reverse')]; tomwalters@0: ii = find(curreverse); tomwalters@0: if ~isempty(ii), tomwalters@0: axl(ii,[1 2])=-axl(ii,[2 1]); tomwalters@0: end; tomwalters@0: % compute the range of 2-D values tomwalters@0: curT = get(curax,'Xform'); tomwalters@0: lim = curT*[0 1 0 1 0 1 0 1;0 0 1 1 0 0 1 1;0 0 0 0 1 1 1 1;1 1 1 1 1 1 1 1]; tomwalters@0: lim = lim(1:2,:)./([1;1]*lim(4,:)); tomwalters@0: curlimmin = min(lim')'; tomwalters@0: curlimrange = max(lim')' - curlimmin; tomwalters@0: curinvT = inv(curT); tomwalters@0: if (~oneax), tomwalters@0: curT = curT.'; tomwalters@0: curinvT = curinvT.'; tomwalters@0: curT = curT(:); tomwalters@0: curinvT = curinvT(:); tomwalters@0: end; tomwalters@0: % check which arrows to which cur corresponds tomwalters@0: ii = find((ax==curax)&(page==curpage)); tomwalters@0: oo = ones(1,length(ii)); tomwalters@0: axr(:,ii) = diff(axl')' * oo; tomwalters@0: axm(:,ii) = axl(:,1) * oo; tomwalters@0: axrev(:,ii) = curreverse * oo; tomwalters@0: ap(:,ii) = curap(3:4)' * oo; tomwalters@0: xyzlog(:,ii) = curxyzlog * oo; tomwalters@0: limmin(:,ii) = curlimmin * oo; tomwalters@0: limrange(:,ii) = curlimrange * oo; tomwalters@0: if (oneax), tomwalters@0: T = curT; tomwalters@0: invT = curinvT; tomwalters@0: else, tomwalters@0: T(:,ii) = curT * oo; tomwalters@0: invT(:,ii) = curinvT * oo; tomwalters@0: end; tomwalters@0: axnotdone(ii) = zeros(1,length(ii)); tomwalters@0: end; tomwalters@0: oldaxlims(oldaxlims(:,1)==0,:)=[]; tomwalters@0: % correct for log scales tomwalters@0: curxyzlog = xyzlog.'; tomwalters@0: ii = find(curxyzlog(:)); tomwalters@0: if ~isempty(ii), tomwalters@0: start( ii) = real(log10(start( ii))); tomwalters@0: stop( ii) = real(log10(stop( ii))); tomwalters@0: if (all(imag(crossdir)==0)), % pulled (ii) subscript on crossdir, 12/5/96 eaj tomwalters@0: crossdir(ii) = real(log10(crossdir(ii))); tomwalters@0: end; tomwalters@0: end; tomwalters@0: % correct for reverse directions tomwalters@0: ii = find(axrev.'); tomwalters@0: if ~isempty(ii), tomwalters@0: start( ii) = -start( ii); tomwalters@0: stop( ii) = -stop( ii); tomwalters@0: crossdir(ii) = -crossdir(ii); tomwalters@0: end; tomwalters@0: % transpose start/stop values tomwalters@0: start = start.'; tomwalters@0: stop = stop.'; tomwalters@0: % take care of defaults, page was done above tomwalters@0: ii=find(isnan(start(:) )); if ~isempty(ii), start(ii) = axm(ii)+axr(ii)/2; end; tomwalters@0: ii=find(isnan(stop(:) )); if ~isempty(ii), stop(ii) = axm(ii)+axr(ii)/2; end; tomwalters@0: ii=find(isnan(crossdir(:) )); if ~isempty(ii), crossdir(ii) = zeros(length(ii),1); end; tomwalters@0: ii=find(isnan(len )); if ~isempty(ii), len(ii) = ones(length(ii),1)*deflen; end; tomwalters@0: ii=find(isnan(baseangle )); if ~isempty(ii), baseangle(ii) = ones(length(ii),1)*defbaseangle; end; tomwalters@0: ii=find(isnan(tipangle )); if ~isempty(ii), tipangle(ii) = ones(length(ii),1)*deftipangle; end; tomwalters@0: ii=find(isnan(wid )); if ~isempty(ii), wid(ii) = ones(length(ii),1)*defwid; end; tomwalters@0: ii=find(isnan(ends )); if ~isempty(ii), ends(ii) = ones(length(ii),1)*defends; end; tomwalters@0: % transpose rest of values tomwalters@0: len = len.'; tomwalters@0: baseangle = baseangle.'; tomwalters@0: tipangle = tipangle.'; tomwalters@0: wid = wid.'; tomwalters@0: page = page.'; tomwalters@0: crossdir = crossdir.'; tomwalters@0: ends = ends.'; tomwalters@0: ax = ax.'; tomwalters@0: % given x, a 3xN matrix of points in 3-space; tomwalters@0: % want to convert to X, the corresponding 4xN 2-space matrix tomwalters@0: % tomwalters@0: % tmp1=[(x-axm)./axr; ones(1,size(x,1))]; tomwalters@0: % if (oneax), X=T*tmp1; tomwalters@0: % else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1; tomwalters@0: % tmp2=zeros(4,4*N); tmp2(:)=tmp1(:); tomwalters@0: % X=zeros(4,N); X(:)=sum(tmp2)'; end; tomwalters@0: % X = X ./ (ones(4,1)*X(4,:)); tomwalters@0: % for all points with start==stop, start=stop-(verysmallvalue)*(up-direction); tomwalters@0: ii = find(all(start==stop)); tomwalters@0: if ~isempty(ii), tomwalters@0: % find an arrowdir vertical on screen and perpendicular to viewer tomwalters@0: % transform to 2-D tomwalters@0: tmp1 = [(stop(:,ii)-axm(:,ii))./axr(:,ii);ones(1,length(ii))]; tomwalters@0: if (oneax), twoD=T*tmp1; tomwalters@0: else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T(:,ii).*tmp1; tomwalters@0: tmp2=zeros(4,4*length(ii)); tmp2(:)=tmp1(:); tomwalters@0: twoD=zeros(4,length(ii)); twoD(:)=sum(tmp2)'; end; tomwalters@0: twoD=twoD./(ones(4,1)*twoD(4,:)); tomwalters@0: % move the start point down just slightly tomwalters@0: tmp1 = twoD + [0;-1/1000;0;0]*(limrange(2,ii)./ap(2,ii)); tomwalters@0: % transform back to 3-D tomwalters@0: if (oneax), threeD=invT*tmp1; tomwalters@0: else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=invT(:,ii).*tmp1; tomwalters@0: tmp2=zeros(4,4*length(ii)); tmp2(:)=tmp1(:); tomwalters@0: threeD=zeros(4,length(ii)); threeD(:)=sum(tmp2)'; end; tomwalters@0: start(:,ii) = (threeD(1:3,:)./(ones(3,1)*threeD(4,:))).*axr(:,ii)+axm(:,ii); tomwalters@0: end; tomwalters@0: % compute along-arrow points tomwalters@0: % transform Start points tomwalters@0: tmp1=[(start-axm)./axr;ones(1,narrows)]; tomwalters@0: if (oneax), X0=T*tmp1; tomwalters@0: else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1; tomwalters@0: tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:); tomwalters@0: X0=zeros(4,narrows); X0(:)=sum(tmp2)'; end; tomwalters@0: X0=X0./(ones(4,1)*X0(4,:)); tomwalters@0: % transform Stop points tomwalters@0: tmp1=[(stop-axm)./axr;ones(1,narrows)]; tomwalters@0: if (oneax), Xf=T*tmp1; tomwalters@0: else, tmp1=[tmp1;tmp1;tmp1;tmp1]; tmp1=T.*tmp1; tomwalters@0: tmp2=zeros(4,4*narrows); tmp2(:)=tmp1(:); tomwalters@0: Xf=zeros(4,narrows); Xf(:)=sum(tmp2)'; end; tomwalters@0: Xf=Xf./(ones(4,1)*Xf(4,:)); tomwalters@0: % compute pixel distance between points tomwalters@0: D = sqrt(sum(((Xf(1:2,:)-X0(1:2,:)).*(ap./limrange)).^2)); tomwalters@0: D = D + (D==0); %eaj new 2/24/98 tomwalters@0: % compute and modify along-arrow distances tomwalters@0: len1 = len; tomwalters@0: len2 = len - (len.*tan(tipangle/180*pi)-wid/2).*tan((90-baseangle)/180*pi); tomwalters@0: slen0 = zeros(1,narrows); tomwalters@0: slen1 = len1 .* ((ends==2)|(ends==3)); tomwalters@0: slen2 = len2 .* ((ends==2)|(ends==3)); tomwalters@0: len0 = zeros(1,narrows); tomwalters@0: len1 = len1 .* ((ends==1)|(ends==3)); tomwalters@0: len2 = len2 .* ((ends==1)|(ends==3)); tomwalters@0: % for no start arrowhead tomwalters@0: ii=find((ends==1)&(D0), set(H,extraprops{:}); end; tomwalters@0: % handle choosing arrow Start and/or Stop locations if unspecified tomwalters@0: [H,oldaxlims,errstr] = arrow_clicks(H,ud,x,y,z,ax,oldaxlims); tomwalters@0: if ~isempty(errstr), error([upper(mfilename) ' got ' errstr]); end; tomwalters@0: % set the output tomwalters@0: if (nargout>0), h=H; end; tomwalters@0: % make sure the axis limits did not change tomwalters@0: if isempty(oldaxlims), tomwalters@0: ARROW_AXLIMITS = []; tomwalters@0: else, tomwalters@0: lims = get(oldaxlims(:,1),{'XLim','YLim','ZLim'})'; tomwalters@0: lims = reshape(cat(2,lims{:}),6,size(lims,2)); tomwalters@0: mask = arrow_is2DXY(oldaxlims(:,1)); tomwalters@0: oldaxlims(mask,6:7) = lims(5:6,mask)'; tomwalters@0: ARROW_AXLIMITS = oldaxlims(find(any(oldaxlims(:,2:7)'~=lims)),:); tomwalters@0: if ~isempty(ARROW_AXLIMITS), tomwalters@0: warning(arrow_warnlimits(ARROW_AXLIMITS,narrows)); tomwalters@0: end; tomwalters@0: end; tomwalters@0: else, tomwalters@0: % don't create the patch, just return the data tomwalters@0: h=x; tomwalters@0: yy=y; tomwalters@0: zz=z; tomwalters@0: end; tomwalters@0: function out = arrow_defcheck(in,def,prop) tomwalters@0: % check if we got 'default' values tomwalters@0: out = in; tomwalters@0: if ~isstr(in), return; end; tomwalters@0: if size(in,1)==1 & strncmp(lower(in),'def',3), tomwalters@0: out = def; tomwalters@0: elseif ~isempty(prop), tomwalters@0: error([upper(mfilename) ' does not recognize ''' in(:)' ''' as a valid ''' prop ''' string.']); tomwalters@0: end; tomwalters@0: function [H,oldaxlims,errstr] = arrow_clicks(H,ud,x,y,z,ax,oldaxlims) tomwalters@0: % handle choosing arrow Start and/or Stop locations if necessary tomwalters@0: errstr = ''; tomwalters@0: if isempty(H)|isempty(ud)|isempty(x), return; end; tomwalters@0: % determine which (if any) need Start and/or Stop tomwalters@0: needStart = all(isnan(ud(:,1:3)'))'; tomwalters@0: needStop = all(isnan(ud(:,4:6)'))'; tomwalters@0: mask = any(needStart|needStop); tomwalters@0: if ~any(mask), return; end; tomwalters@0: ud(~mask,:)=[]; ax(:,~mask)=[]; tomwalters@0: x(:,~mask)=[]; y(:,~mask)=[]; z(:,~mask)=[]; tomwalters@0: % make them invisible for the time being tomwalters@0: set(H,'Visible','off'); tomwalters@0: % save the current axes and limits modes; set to manual for the time being tomwalters@0: oldAx = gca; tomwalters@0: limModes=get(ax(:),{'XLimMode','YLimMode','ZLimMode'}); tomwalters@0: set(ax(:),{'XLimMode','YLimMode','ZLimMode'},{'manual','manual','manual'}); tomwalters@0: % loop over each arrow that requires attention tomwalters@0: jj = find(mask); tomwalters@0: for ii=1:length(jj), tomwalters@0: h = H(jj(ii)); tomwalters@0: axes(ax(ii)); tomwalters@0: % figure out correct call tomwalters@0: if needStart(ii), prop='Start'; else, prop='Stop'; end; tomwalters@0: [wasInterrupted,errstr] = arrow_click(needStart(ii)&needStop(ii),h,prop,ax(ii)); tomwalters@0: % handle errors and control-C tomwalters@0: if wasInterrupted, tomwalters@0: delete(H(jj(ii:end))); tomwalters@0: H(jj(ii:end))=[]; tomwalters@0: oldaxlims(jj(ii:end),:)=[]; tomwalters@0: break; tomwalters@0: end; tomwalters@0: end; tomwalters@0: % restore the axes and limit modes tomwalters@0: axes(oldAx); tomwalters@0: set(ax(:),{'XLimMode','YLimMode','ZLimMode'},limModes); tomwalters@0: function [wasInterrupted,errstr] = arrow_click(lockStart,H,prop,ax) tomwalters@0: % handle the clicks for one arrow tomwalters@0: fig = get(ax,'Parent'); tomwalters@0: % save some things tomwalters@0: oldFigProps = {'Pointer','WindowButtonMotionFcn','WindowButtonUpFcn'}; tomwalters@0: oldFigValue = get(fig,oldFigProps); tomwalters@0: oldArrowProps = {'EraseMode'}; tomwalters@0: oldArrowValue = get(H,oldArrowProps); tomwalters@0: set(H,'EraseMode','background'); %because 'xor' makes shaft invisible unless Width>1 tomwalters@0: global ARROW_CLICK_H ARROW_CLICK_PROP ARROW_CLICK_AX ARROW_CLICK_USE_Z tomwalters@0: ARROW_CLICK_H=H; ARROW_CLICK_PROP=prop; ARROW_CLICK_AX=ax; tomwalters@0: ARROW_CLICK_USE_Z=~arrow_is2DXY(ax)|~arrow_planarkids(ax); tomwalters@0: set(fig,'Pointer','crosshair'); tomwalters@0: % set up the WindowButtonMotion so we can see the arrow while moving around tomwalters@0: set(fig,'WindowButtonUpFcn','set(gcf,''WindowButtonUpFcn'','''')', ... tomwalters@0: 'WindowButtonMotionFcn',''); tomwalters@0: if ~lockStart, tomwalters@0: set(H,'Visible','on'); tomwalters@0: set(fig,'WindowButtonMotionFcn',[mfilename '(''callback'',''motion'');']); tomwalters@0: end; tomwalters@0: % wait for the button to be pressed tomwalters@0: [wasKeyPress,wasInterrupted,errstr] = arrow_wfbdown(fig); tomwalters@0: % if we wanted to click-drag, set the Start point tomwalters@0: if lockStart & ~wasInterrupted, tomwalters@0: pt = arrow_point(ARROW_CLICK_AX,ARROW_CLICK_USE_Z); tomwalters@0: feval(mfilename,H,'Start',pt,'Stop',pt); tomwalters@0: set(H,'Visible','on'); tomwalters@0: ARROW_CLICK_PROP='Stop'; tomwalters@0: set(fig,'WindowButtonMotionFcn',[mfilename '(''callback'',''motion'');']); tomwalters@0: % wait for the mouse button to be released tomwalters@0: eval('waitfor(fig,''WindowButtonUpFcn'','''');','wasInterrupted=1;'); tomwalters@0: if wasInterrupted, errstr=lasterr; end; tomwalters@0: end; tomwalters@0: if ~wasInterrupted, feval(mfilename,'callback','motion'); end; tomwalters@0: % restore some things tomwalters@0: set(gcf,oldFigProps,oldFigValue); tomwalters@0: set(H,oldArrowProps,oldArrowValue); tomwalters@0: function arrow_callback(varargin) tomwalters@0: % handle redrawing callbacks tomwalters@0: if nargin==0, return; end; tomwalters@0: str = varargin{1}; tomwalters@0: if ~isstr(str), error([upper(mfilename) ' got an invalid Callback command.']); end; tomwalters@0: s = lower(str); tomwalters@0: if strcmp(s,'motion'), tomwalters@0: % motion callback tomwalters@0: global ARROW_CLICK_H ARROW_CLICK_PROP ARROW_CLICK_AX ARROW_CLICK_USE_Z tomwalters@0: feval(mfilename,ARROW_CLICK_H,ARROW_CLICK_PROP,arrow_point(ARROW_CLICK_AX,ARROW_CLICK_USE_Z)); tomwalters@0: drawnow; tomwalters@0: else, tomwalters@0: error([upper(mfilename) ' does not recognize ''' str(:).' ''' as a valid Callback option.']); tomwalters@0: end; tomwalters@0: function out = arrow_point(ax,use_z) tomwalters@0: % return the point on the given axes tomwalters@0: if nargin==0, ax=gca; end; tomwalters@0: if nargin<2, use_z=~arrow_is2DXY(ax)|~arrow_planarkids(ax); end; tomwalters@0: out = get(ax,'CurrentPoint'); tomwalters@0: out = out(1,:); tomwalters@0: if ~use_z, out=out(1:2); end; tomwalters@0: function [wasKeyPress,wasInterrupted,errstr] = arrow_wfbdown(fig) tomwalters@0: % wait for button down ignoring object ButtonDownFcn's tomwalters@0: if nargin==0, fig=gcf; end; tomwalters@0: errstr = ''; tomwalters@0: % save ButtonDownFcn values tomwalters@0: objs = findobj(fig); tomwalters@0: buttonDownFcns = get(objs,'ButtonDownFcn'); tomwalters@0: mask=~strcmp(buttonDownFcns,''); objs=objs(mask); buttonDownFcns=buttonDownFcns(mask); tomwalters@0: set(objs,'ButtonDownFcn',''); tomwalters@0: % save other figure values tomwalters@0: figProps = {'KeyPressFcn','WindowButtonDownFcn'}; tomwalters@0: figValue = get(fig,figProps); tomwalters@0: % do the real work tomwalters@0: set(fig,'KeyPressFcn','set(gcf,''KeyPressFcn'','''',''WindowButtonDownFcn'','''');', ... tomwalters@0: 'WindowButtonDownFcn','set(gcf,''WindowButtonDownFcn'','''')'); tomwalters@0: lasterr(''); tomwalters@0: wasInterrupted=0; eval('waitfor(fig,''WindowButtonDownFcn'','''');','wasInterrupted=1;'); tomwalters@0: wasKeyPress = ~wasInterrupted & strcmp(get(fig,'KeyPressFcn'),''); tomwalters@0: if wasInterrupted, errstr=lasterr; end; tomwalters@0: % restore ButtonDownFcn and other figure values tomwalters@0: set(objs,'ButtonDownFcn',buttonDownFcns); tomwalters@0: set(fig,figProps,figValue); tomwalters@0: function [out,is2D] = arrow_is2DXY(ax) tomwalters@0: % check if axes are 2-D X-Y plots tomwalters@0: % may not work for modified camera angles, etc. tomwalters@0: out = logical(zeros(size(ax))); % 2-D X-Y plots tomwalters@0: is2D = out; % any 2-D plots tomwalters@0: views = get(ax(:),{'View'}); tomwalters@0: views = cat(1,views{:}); tomwalters@0: out(:) = abs(views(:,2))==90; tomwalters@0: is2D(:) = out(:) | all(rem(views',90)==0)'; tomwalters@0: function out = arrow_planarkids(ax) tomwalters@0: % check if axes descendents all have empty ZData (lines,patches,surfaces) tomwalters@0: out = logical(ones(size(ax))); tomwalters@0: allkids = get(ax(:),{'Children'}); tomwalters@0: for k=1:length(allkids), tomwalters@0: kids = get([findobj(allkids{k},'flat','Type','line') tomwalters@0: findobj(allkids{k},'flat','Type','patch') tomwalters@0: findobj(allkids{k},'flat','Type','surface')],{'ZData'}); tomwalters@0: for j=1:length(kids), tomwalters@0: if ~isempty(kids{j}), out(k)=logical(0); break; end; tomwalters@0: end; tomwalters@0: end; tomwalters@0: function arrow_fixlimits(axlimits) tomwalters@0: % reset the axis limits as necessary tomwalters@0: if isempty(axlimits), disp([upper(mfilename) ' does not remember any axis limits to reset.']); end; tomwalters@0: for k=1:size(axlimits,1), tomwalters@0: if any(get(axlimits(k,1),'XLim')~=axlimits(k,2:3)), set(axlimits(k,1),'XLim',axlimits(k,2:3)); end; tomwalters@0: if any(get(axlimits(k,1),'YLim')~=axlimits(k,4:5)), set(axlimits(k,1),'YLim',axlimits(k,4:5)); end; tomwalters@0: if any(get(axlimits(k,1),'ZLim')~=axlimits(k,6:7)), set(axlimits(k,1),'ZLim',axlimits(k,6:7)); end; tomwalters@0: end; tomwalters@0: function out = arrow_WarpToFill(notstretched,manualcamera,curax) tomwalters@0: % check if we are in "WarpToFill" mode. tomwalters@0: out = strcmp(get(curax,'WarpToFill'),'on'); tomwalters@0: % 'WarpToFill' is undocumented, so may need to replace this by tomwalters@0: % out = ~( any(notstretched) & any(manualcamera) ); tomwalters@0: function out = arrow_warnlimits(axlimits,narrows) tomwalters@0: % create a warning message if we've changed the axis limits tomwalters@0: msg = ''; tomwalters@0: switch (size(axlimits,1)) tomwalters@0: case 1, msg=''; tomwalters@0: case 2, msg='on two axes '; tomwalters@0: otherwise, msg='on several axes '; tomwalters@0: end; tomwalters@0: msg = [upper(mfilename) ' changed the axis limits ' msg ... tomwalters@0: 'when adding the arrow']; tomwalters@0: if (narrows>1), msg=[msg 's']; end; tomwalters@0: out = [msg '.' sprintf('\n') ' Call ' upper(mfilename) ... tomwalters@0: ' FIXLIMITS to reset them now.']; tomwalters@0: function arrow_copyprops(fm,to) tomwalters@0: % copy line properties to patches tomwalters@0: props = {'EraseMode','LineStyle','LineWidth','Marker','MarkerSize',... tomwalters@0: 'MarkerEdgeColor','MarkerFaceColor','ButtonDownFcn', ... tomwalters@0: 'Clipping','DeleteFcn','BusyAction','HandleVisibility', ... tomwalters@0: 'Selected','SelectionHighlight','Visible'}; tomwalters@0: lineprops = {'Color', props{:}}; tomwalters@0: patchprops = {'EdgeColor',props{:}}; tomwalters@0: patch2props = {'FaceColor',patchprops{:}}; tomwalters@0: fmpatch = strcmp(get(fm,'Type'),'patch'); tomwalters@0: topatch = strcmp(get(to,'Type'),'patch'); tomwalters@0: set(to( fmpatch& topatch),patch2props,get(fm( fmpatch& topatch),patch2props)); %p->p tomwalters@0: set(to(~fmpatch&~topatch),lineprops, get(fm(~fmpatch&~topatch),lineprops )); %l->l tomwalters@0: set(to( fmpatch&~topatch),lineprops, get(fm( fmpatch&~topatch),patchprops )); %p->l tomwalters@0: set(to(~fmpatch& topatch),patchprops, get(fm(~fmpatch& topatch),lineprops) ,'FaceColor','none'); %l->p tomwalters@0: function arrow_props tomwalters@0: % display further help info about ARROW properties tomwalters@0: c = sprintf('\n'); tomwalters@0: disp([c ... tomwalters@0: 'ARROW Properties: Default values are given in [square brackets], and other' c ... tomwalters@0: ' acceptable equivalent property names are in (parenthesis).' c c ... tomwalters@0: ' Start The starting points. For N arrows, B' c ... tomwalters@0: ' this should be a Nx2 or Nx3 matrix. /|\ ^' c ... tomwalters@0: ' Stop The end points. For N arrows, this /|||\ |' c ... tomwalters@0: ' should be a Nx2 or Nx3 matrix. //|||\\ L|' c ... tomwalters@0: ' Length Length of the arrowhead (in pixels on ///|||\\\ e|' c ... tomwalters@0: ' screen, points on a page). [16] (Len) ////|||\\\\ n|' c ... tomwalters@0: ' BaseAngle Angle (degrees) of the base angle /////|D|\\\\\ g|' c ... tomwalters@0: ' ADE. For a simple stick arrow, use //// ||| \\\\ t|' c ... tomwalters@0: ' BaseAngle=TipAngle. [90] (Base) /// ||| \\\ h|' c ... tomwalters@0: ' TipAngle Angle (degrees) of tip angle ABC. //<----->|| \\ |' c ... tomwalters@0: ' [16] (Tip) / base ||| \ V' c ... tomwalters@0: ' Width Width of the base in pixels. Not E angle ||<-------->C' c ... tomwalters@0: ' the ''LineWidth'' prop. [0] (Wid) |||tipangle' c ... tomwalters@0: ' Page If provided, non-empty, and not NaN, |||' c ... tomwalters@0: ' this causes ARROW to use hardcopy |||' c ... tomwalters@0: ' rather than onscreen proportions. A' c ... tomwalters@0: ' This is important if screen aspect --> <-- width' c ... tomwalters@0: ' ratio and hardcopy aspect ratio are ----CrossDir---->' c ... tomwalters@0: ' vastly different. []' c... tomwalters@0: ' CrossDir A vector giving the direction towards which the fletches' c ... tomwalters@0: ' on the arrow should go. [computed such that it is perpen-' c ... tomwalters@0: ' dicular to both the arrow direction and the view direction' c ... tomwalters@0: ' (i.e., as if it was pasted on a normal 2-D graph)] (Note' c ... tomwalters@0: ' that CrossDir is a vector. Also note that if an axis is' c ... tomwalters@0: ' plotted on a log scale, then the corresponding component' c ... tomwalters@0: ' of CrossDir must also be set appropriately, i.e., to 1 for' c ... tomwalters@0: ' no change in that direction, >1 for a positive change, >0' c ... tomwalters@0: ' and <1 for negative change.)' c ... tomwalters@0: ' NormalDir A vector normal to the fletch direction (CrossDir is then' c ... tomwalters@0: ' computed by the vector cross product [Line]x[NormalDir]). []' c ... tomwalters@0: ' (Note that NormalDir is a vector. Unlike CrossDir,' c ... tomwalters@0: ' NormalDir is used as is regardless of log-scaled axes.)' c ... tomwalters@0: ' Ends Set which end has an arrowhead. Valid values are ''none'',' c ... tomwalters@0: ' ''stop'', ''start'', and ''both''. [''stop''] (End)' c... tomwalters@0: ' ObjectHandles Vector of handles to previously-created arrows to be' c ... tomwalters@0: ' updated or line objects to be converted to arrows.' c ... tomwalters@0: ' [] (Object,Handle)' c ]); tomwalters@0: function out = arrow_demo tomwalters@0: % demo tomwalters@0: % create the data tomwalters@0: [x,y,z] = peaks; tomwalters@0: [ddd,out.iii]=max(z(:)); tomwalters@0: out.axlim = [min(x(:)) max(x(:)) min(y(:)) max(y(:)) min(z(:)) max(z(:))]; tomwalters@0: tomwalters@0: % modify it by inserting some NaN's tomwalters@0: [m,n] = size(z); tomwalters@0: m = floor(m/2); tomwalters@0: n = floor(n/2); tomwalters@0: z(1:m,1:n) = NaN*ones(m,n); tomwalters@0: tomwalters@0: % graph it tomwalters@0: clf('reset'); tomwalters@0: out.hs=surf(x,y,z); tomwalters@0: out.x=x; out.y=y; out.z=z; tomwalters@0: xlabel('x'); ylabel('y'); tomwalters@0: tomwalters@0: function h = arrow_demo3(in) tomwalters@0: % set the view tomwalters@0: axlim = in.axlim; tomwalters@0: axis(axlim); tomwalters@0: zlabel('z'); tomwalters@0: %set(in.hs,'FaceColor','interp'); tomwalters@0: view(viewmtx(-37.5,30,20)); tomwalters@0: title(['Demo of the capabilities of the ARROW function in 3-D']); tomwalters@0: tomwalters@0: % Normal blue arrow tomwalters@0: h1 = feval(mfilename,[axlim(1) axlim(4) 4],[-.8 1.2 4], ... tomwalters@0: 'EdgeColor','b','FaceColor','b'); tomwalters@0: tomwalters@0: % Normal white arrow, clipped by the surface tomwalters@0: h2 = feval(mfilename,axlim([1 4 6]),[0 2 4]); tomwalters@0: t=text(-2.4,2.7,7.7,'arrow clipped by surf'); tomwalters@0: tomwalters@0: % Baseangle<90 tomwalters@0: h3 = feval(mfilename,[3 .125 3.5],[1.375 0.125 3.5],30,50); tomwalters@0: t2=text(3.1,.125,3.5,'local maximum'); tomwalters@0: tomwalters@0: % Baseangle<90, fill and edge colors different tomwalters@0: h4 = feval(mfilename,axlim(1:2:5)*.5,[0 0 0],36,60,25, ... tomwalters@0: 'EdgeColor','b','FaceColor','c'); tomwalters@0: t3=text(axlim(1)*.5,axlim(3)*.5,axlim(5)*.5-.75,'origin'); tomwalters@0: set(t3,'HorizontalAlignment','center'); tomwalters@0: tomwalters@0: % Baseangle>90, black fill tomwalters@0: h5 = feval(mfilename,[-2.9 2.9 3],[-1.3 .4 3.2],30,120,[],6, ... tomwalters@0: 'EdgeColor','r','FaceColor','k','LineWidth',2); tomwalters@0: tomwalters@0: % Baseangle>90, no fill tomwalters@0: h6 = feval(mfilename,[-2.9 2.9 1.3],[-1.3 .4 1.5],30,120,[],6, ... tomwalters@0: 'EdgeColor','r','FaceColor','none','LineWidth',2); tomwalters@0: tomwalters@0: % Stick arrow tomwalters@0: h7 = feval(mfilename,[-1.6 -1.65 -6.5],[0 -1.65 -6.5],[],16,16); tomwalters@0: t4=text(-1.5,-1.65,-7.25,'global mininum'); tomwalters@0: set(t4,'HorizontalAlignment','center'); tomwalters@0: tomwalters@0: % Normal, black fill tomwalters@0: h8 = feval(mfilename,[-1.4 0 -7.2],[-1.4 0 -3],'FaceColor','k'); tomwalters@0: t5=text(-1.5,0,-7.75,'local minimum'); tomwalters@0: set(t5,'HorizontalAlignment','center'); tomwalters@0: tomwalters@0: % Gray fill, crossdir specified, 'LineStyle' -- tomwalters@0: h9 = feval(mfilename,[-3 2.2 -6],[-3 2.2 -.05],36,[],27,6,[],[0 -1 0], ... tomwalters@0: 'EdgeColor','k','FaceColor',.75*[1 1 1],'LineStyle','--'); tomwalters@0: tomwalters@0: % a series of normal arrows, linearly spaced, crossdir specified tomwalters@0: h10y=(0:4)'/3; tomwalters@0: h10 = feval(mfilename,[-3*ones(size(h10y)) h10y -6.5*ones(size(h10y))], ... tomwalters@0: [-3*ones(size(h10y)) h10y -.05*ones(size(h10y))], ... tomwalters@0: 12,[],[],[],[],[0 -1 0]); tomwalters@0: tomwalters@0: % a series of normal arrows, linearly spaced tomwalters@0: h11x=(1:.33:2.8)'; tomwalters@0: h11 = feval(mfilename,[h11x -3*ones(size(h11x)) 6.5*ones(size(h11x))], ... tomwalters@0: [h11x -3*ones(size(h11x)) -.05*ones(size(h11x))]); tomwalters@0: tomwalters@0: % series of magenta arrows, radially oriented, crossdir specified tomwalters@0: h12x=2; h12y=-3; h12z=axlim(5)/2; h12xr=1; h12zr=h12z; ir=.15;or=.81; tomwalters@0: h12t=(0:11)'/6*pi; tomwalters@0: h12 = feval(mfilename, ... tomwalters@0: [h12x+h12xr*cos(h12t)*ir h12y*ones(size(h12t)) ... tomwalters@0: h12z+h12zr*sin(h12t)*ir],[h12x+h12xr*cos(h12t)*or ... tomwalters@0: h12y*ones(size(h12t)) h12z+h12zr*sin(h12t)*or], ... tomwalters@0: 10,[],[],[],[], ... tomwalters@0: [-h12xr*sin(h12t) zeros(size(h12t)) h12zr*cos(h12t)],... tomwalters@0: 'FaceColor','none','EdgeColor','m'); tomwalters@0: tomwalters@0: % series of normal arrows, tangentially oriented, crossdir specified tomwalters@0: or13=.91; h13t=(0:.5:12)'/6*pi; tomwalters@0: locs = [h12x+h12xr*cos(h13t)*or13 h12y*ones(size(h13t)) h12z+h12zr*sin(h13t)*or13]; tomwalters@0: h13 = feval(mfilename,locs(1:end-1,:),locs(2:end,:),6); tomwalters@0: tomwalters@0: % arrow with no line ==> oriented downwards tomwalters@0: h14 = feval(mfilename,[3 3 .100001],[3 3 .1],30); tomwalters@0: t6=text(3,3,3.6,'no line'); set(t6,'HorizontalAlignment','center'); tomwalters@0: tomwalters@0: % arrow with arrowheads at both ends tomwalters@0: h15 = feval(mfilename,[-.5 -3 -3],[1 -3 -3],'Ends','both','FaceColor','g', ... tomwalters@0: 'Length',20,'Width',3,'CrossDir',[0 0 1],'TipAngle',25); tomwalters@0: tomwalters@0: h=[h1;h2;h3;h4;h5;h6;h7;h8;h9;h10;h11;h12;h13;h14;h15]; tomwalters@0: function h = arrow_demo2(in) tomwalters@0: axlim = in.axlim; tomwalters@0: dolog = 1; tomwalters@0: if (dolog), set(in.hs,'YData',10.^get(in.hs,'YData')); end; tomwalters@0: shading('interp'); tomwalters@0: view(2); tomwalters@0: title(['Demo of the capabilities of the ARROW function in 2-D']); tomwalters@0: hold on; [C,H]=contour(in.x,in.y,in.z,20,'-'); hold off; tomwalters@0: for k=H', tomwalters@0: set(k,'ZData',(axlim(6)+1)*ones(size(get(k,'XData'))),'Color','k'); tomwalters@0: if (dolog), set(k,'YData',10.^get(k,'YData')); end; tomwalters@0: end; tomwalters@0: if (dolog), axis([axlim(1:2) 10.^axlim(3:4)]); set(gca,'YScale','log'); tomwalters@0: else, axis(axlim(1:4)); end; tomwalters@0: tomwalters@0: % Normal blue arrow tomwalters@0: start = [axlim(1) axlim(4) axlim(6)+2]; tomwalters@0: stop = [in.x(in.iii) in.y(in.iii) axlim(6)+2]; tomwalters@0: if (dolog), start(:,2)=10.^start(:,2); stop(:,2)=10.^stop(:,2); end; tomwalters@0: h1 = feval(mfilename,start,stop,'EdgeColor','b','FaceColor','b'); tomwalters@0: tomwalters@0: % three arrows with varying fill, width, and baseangle tomwalters@0: start = [-3 -3 10; -3 -1.5 10; -1.5 -3 10]; tomwalters@0: stop = [-.03 -.03 10; -.03 -1.5 10; -1.5 -.03 10]; tomwalters@0: if (dolog), start(:,2)=10.^start(:,2); stop(:,2)=10.^stop(:,2); end; tomwalters@0: h2 = feval(mfilename,start,stop,24,[90;60;120],[],[0;0;4],'Ends',str2mat('both','stop','stop')); tomwalters@0: set(h2(2),'EdgeColor',[0 .35 0],'FaceColor',[0 .85 .85]); tomwalters@0: set(h2(3),'EdgeColor','r','FaceColor',[1 .5 1]); tomwalters@0: h=[h1;h2]; tomwalters@0: function out = trueornan(x) tomwalters@0: if isempty(x), tomwalters@0: out=x; tomwalters@0: else, tomwalters@0: out = isnan(x); tomwalters@0: out(~out) = x(~out); tomwalters@0: end;