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