% pb-diagram.sty: LaTeX commutative diagram macros % Author: Paul Burchard % Version Number: 4.1 % Version Date: 24 Jan 1997 % % Copyright (c) 1990, 1992, 1995, 1997 by Paul Burchard % % Anyone may use this file or its programs for educational or % non-profit purposes provided this original file remains % unaltered. All other rights reserved. I would like to % acknowledge inspiration from a macro package of Marc-Paul % van der Hulst, and the thorough topological torture-testing % of this package by Bill Richter. % % Please see documentation file "pb-manual.tex" for an % explanation of how to use the "diagram" environment. % % For those not inclined to read manuals, here is a short example: % % \[ % \begin{diagram} % a b* \node{A} \arrow{e,t}{a} % A ---> B* ---> C \arrow{s,l}{c} \arrow{ese} % | \____ _____/ | \node{B^*} \arrow{e,t}{b^*} % c | ___X____ | d \node{C} \arrow{s,r}{d} \arrow{wsw}\\ % v `/_ _\' v \node{D} \arrow[2]{e,b}{e} % D -----------> E \node[2]{E} % e \end{diagram} % \] % % % % ---USER-ADJUSTABLE PARAMETERS--- % \newskip\dgARROWLENGTH \dgARROWLENGTH=2.5em\relax \newskip\dgTEXTARROWLENGTH \dgTEXTARROWLENGTH=1.1em\relax \newskip\dgHORIZPAD \dgHORIZPAD=1em\relax \newskip\dgVERTPAD \dgVERTPAD=2ex\relax \newskip\dgLABELOFFSET \dgLABELOFFSET=.7ex\relax \newcount\dgARROWPARTS \dgARROWPARTS=4\relax \newcommand{\dgeverynode}{\displaystyle} \newcommand{\dgeverylabel}{\scriptstyle} % % for plain LaTeX mode \newskip\dgDOTSPACING \dgDOTSPACING=0.35em \newskip\dgDOTSIZE \dgDOTSIZE=1.5\fontdimen8\tenln % % obsolete---for compatibility with version 1.0 \newcount\dgMAXSQUARE \dgMAXSQUARE=4\relax \newcount\dgMINDBLSQ \dgMINDBLSQ=10\relax \newskip\dgCOLUMNWIDTH \dgCOLUMNWIDTH=2em\relax % % % ----------------------------------------------------------------- % -------------------------CUSTOMIZABLE CODE----------------------- % ----------------------------------------------------------------- % % ### Do not change this file!!! Create your own style file to ### % ### override or add to these definitions. ### % % % ---ARROW STYLE OPTIONS--- %. % The \dgo@XXX commands set arrow geometry and style parameters % such as \dg@VECTOR and \dg@LBLPOS. The \dgo@ command should % set default values for all option parameters. % % See sample customizations below for more options. % \chardef\f@ur=4 \@namedef{dgo@}{\let\dg@VECTOR=\vector \dg@LBLPOS=\dgARROWPARTS \divide\dg@LBLPOS\tw@}% \@namedef{dgo@-}{\let\dg@VECTOR=\line}% \@namedef{dgo@!}{\let\dg@VECTOR=\dg@novector}% \@namedef{dgo@1}{\dg@LBLPOS=\@ne\relax}% \@namedef{dgo@2}{\dg@LBLPOS=\tw@\relax}% \@namedef{dgo@3}{\dg@LBLPOS=\thr@@\relax}% \@namedef{dgo@4}{\dg@LBLPOS=\f@ur\relax}% \@namedef{dgo@5}{\dg@LBLPOS=5\relax}% \@namedef{dgo@6}{\dg@LBLPOS=6\relax}% \@namedef{dgo@7}{\dg@LBLPOS=7\relax}% \@namedef{dgo@8}{\dg@LBLPOS=8\relax}% \@namedef{dgo@9}{\dg@LBLPOS=9\relax}% % % % ---ARROW TYPES (directions)--- %. % These macros specify the geometry of a simple arrow in % grid coordinates, in the style of LaTeX's \vector command. % They set the parameters: % \dg@DX, \dg@DY, \dg@SIZE, % \def\dgt@e{\dg@DX=\@ne \dg@DY=\z@ \dg@SIZE=\@ne}% \def\dgt@w{\dg@DX=\m@ne \dg@DY=\z@ \dg@SIZE=\@ne}% \def\dgt@n{\dg@DX=\z@ \dg@DY=\@ne \dg@SIZE=\@ne}% \def\dgt@s{\dg@DX=\z@ \dg@DY=\m@ne \dg@SIZE=\@ne}% \def\dgt@ne{\dg@DX=\@ne \dg@DY=\@ne \dg@SIZE=\@ne}% \def\dgt@se{\dg@DX=\@ne \dg@DY=\m@ne \dg@SIZE=\@ne}% \def\dgt@nw{\dg@DX=\m@ne \dg@DY=\@ne \dg@SIZE=\@ne}% \def\dgt@sw{\dg@DX=\m@ne \dg@DY=\m@ne \dg@SIZE=\@ne}% \def\dgt@nne{\dg@DX=\@ne \dg@DY=\tw@ \dg@SIZE=\@ne}% \def\dgt@nnw{\dg@DX=\m@ne \dg@DY=\tw@ \dg@SIZE=\@ne}% \def\dgt@sse{\dg@DX=\@ne \dg@DY=-\tw@ \dg@SIZE=\@ne}% \def\dgt@ssw{\dg@DX=\m@ne \dg@DY=-\tw@ \dg@SIZE=\@ne}% \def\dgt@ene{\dg@DX=\tw@ \dg@DY=\@ne \dg@SIZE=\tw@}% \def\dgt@ese{\dg@DX=\tw@ \dg@DY=\m@ne \dg@SIZE=\tw@}% \def\dgt@wnw{\dg@DX=-\tw@ \dg@DY=\@ne \dg@SIZE=\tw@}% \def\dgt@wsw{\dg@DX=-\tw@ \dg@DY=\m@ne \dg@SIZE=\tw@}% % % % ---GRID GEOMETRY--- % % Computes the grid geom params \dg@XGRID, \dg@YGRID, and % \unitlength, with the result that unit grid rects will % be 1000*XGRID by 1000*YGRID \unitlengths in size. % Don't set XGRID, YGRID too big or TeX will get arithmetic % overflow. Also, the possible aspect ratios XGRID:YGRID % are always restricted by the slopes of the arrow font. % % As inputs, use the pre-supplied values of XGRID and YGRID, % which are the calculated required minimum dims for % unit grid rects (in sp's). % \def\dggeometry{% \dg@ZTEMP=\dg@XGRID \multiply\dg@ZTEMP\tw@ \ifnum\dg@YGRID=\z@ \dg@ZTEMP=\tw@ \else \divide\dg@ZTEMP\dg@YGRID \fi % font limit cutoffs \ifnum\dg@ZTEMP>\f@ur \dg@ZTEMP=\f@ur \fi \ifnum\dg@ZTEMP<\@ne \dg@ZTEMP=\@ne \fi \unitlength=1sp\relax \ifnum\dg@ZTEMP<\tw@ % round aspect ratio up toward 2:2, widen rect \advance\dg@ZTEMP\@ne \multiply\unitlength\dg@YGRID \dg@XGRID=\dg@ZTEMP \dg@YGRID=\tw@ \dg@rmcommondiv\tw@\dg@XGRID\dg@YGRID \divide\unitlength\dg@YGRID \else % round aspect ratio down toward 2:2, tallen rect \multiply\unitlength\dg@XGRID \dg@XGRID=\dg@ZTEMP \dg@YGRID=\tw@ \dg@rmcommondiv\tw@\dg@XGRID\dg@YGRID \divide\unitlength\dg@XGRID \fi \divide\unitlength\@m\relax} % % Bugwards-compatibility mode for versions 3.6 and earlier. % This mode is enabled with \let\dggeometry=\dgoldgeometry % \def\dgoldgeometry{% \dg@ZTEMP=\dg@XGRID \multiply\dg@ZTEMP\tw@ \ifnum\dg@YGRID=\z@ \dg@ZTEMP=\tw@ \else \divide\dg@ZTEMP\dg@YGRID \fi % font limit cutoffs \ifnum\dg@ZTEMP>\f@ur \dg@ZTEMP=\f@ur \fi \ifnum\dg@ZTEMP<\@ne \dg@ZTEMP=\@ne \fi % The following calculation is garbled! \unitlength=2sp\relax \ifnum\dg@ZTEMP<\tw@ % round aspect ratio up toward 2:2, widen rect \advance\dg@ZTEMP\@ne \multiply\unitlength\dg@YGRID \else % round aspect ratio down toward 2:2, tallen rect \multiply\unitlength\dg@XGRID \divide\unitlength\dg@ZTEMP \fi \dg@XGRID=\dg@ZTEMP \dg@YGRID=\tw@ \dg@rmcommondiv\tw@\dg@XGRID\dg@YGRID \divide\unitlength\dg@YGRID \divide\unitlength\@m\relax} % % % % ---SAMPLE CUSTOMIZATION: TWO-HEADED ARROWS FOR PLAIN LaTeX--- % % Two-headed arrow command that works like \vector. % Uses temp vars XTEMP, YTEMP. % Usage: \dg@twoheadedvector(DX,DY){SIZE} % \@namedef{dgo@<>}{\let\dg@VECTOR=\dg@twoheadedvector}% \def\dg@twoheadedvector(#1,#2)#3{% \begingroup \dg@XTEMP=#1\relax\multiply\dg@XTEMP\m@ne\relax \dg@YTEMP=#2\relax\multiply\dg@YTEMP\m@ne\relax \begin{picture}(0,0)% \thinlines \put(0,0){\vector(#1,#2){#3}}% \put(0,0){\vector(\dg@XTEMP,\dg@YTEMP){0}}% \end{picture}% \endgroup}% % % % % ---SAMPLE CUSTOMIZATION: DOTTED ARROWS FOR PLAIN LaTeX--- % % Dotted arrow command that works like \vector. % Uses temp vars XTEMP, YTEMP, ZTEMP, XEND, WEND. % Usage: \dg@dotvector(DX,DY){SIZE} % \@namedef{dgo@..}{\let\dg@VECTOR=\dg@dotvector}% \def\dg@dotvector(#1,#2)#3{% \begingroup \dg@XTEMP=#1\relax \dg@YTEMP=#2\relax \let\dg@NDOTS=\dg@XEND \let\dg@DOTDIAM=\dg@WEND % Find number of dots: make x-spacing be DOTSPACING for arrows % of |slope| <= 1, and make y-spacing be DOTSPACING otherwise. % Thus, true spacing is never more than 30% off from DOTSPACING. \dg@NDOTS=\unitlength \multiply\dg@NDOTS #3\relax \dg@ZTEMP=\dg@YTEMP \dg@changesign\dg@YTEMP\dg@ZTEMP \ifnum\dg@XTEMP>\z@ \ifnum\dg@YTEMP>\dg@XTEMP \multiply\dg@NDOTS\dg@YTEMP \divide\dg@NDOTS\dg@XTEMP \fi \else\ifnum\dg@XTEMP<\z@ \ifnum\dg@YTEMP>-\dg@XTEMP \multiply\dg@NDOTS\dg@YTEMP \divide\dg@NDOTS-\dg@XTEMP \fi \fi\fi \dg@YTEMP=\dg@ZTEMP \divide\dg@NDOTS\dgDOTSPACING \ifnum\dg@NDOTS>\z@\else \dg@NDOTS=\@ne \fi % Compute increment vector between dots; round to \unitlength's. % Use NDOTS not DOTSPACING, since DOTSPACING not exactly obeyed. \dg@ZTEMP=\unitlength \multiply\dg@ZTEMP #3\relax \divide\dg@ZTEMP\dg@NDOTS \ifnum\dg@XTEMP=\z@ \dg@changesign\dg@ZTEMP\dg@YTEMP \dg@YTEMP=\dg@ZTEMP \else \dg@changesign\dg@ZTEMP\dg@XTEMP \multiply\dg@YTEMP\dg@ZTEMP \divide\dg@YTEMP\dg@XTEMP \dg@XTEMP=\dg@ZTEMP \fi \divide\dg@XTEMP\unitlength \divide\dg@YTEMP\unitlength % Draw dotted line with \multiput % and arrowhead as zero-length \vector \begin{picture}(0,0)% \dg@DOTDIAM=\dgDOTSIZE \divide\dg@DOTDIAM\unitlength \multiput(0,0)(\dg@XTEMP,\dg@YTEMP){\dg@NDOTS}{% \circle*{\dg@DOTDIAM}}% \multiply\dg@XTEMP\dg@NDOTS \multiply\dg@YTEMP\dg@NDOTS \put(\dg@XTEMP,\dg@YTEMP){\vector(#1,#2){0}}% \end{picture}% \endgroup}% % % % ---------------------------------------------------------------- % ----------------------LaTeX ENHANCEMENTS------------------------ % ---------------------------------------------------------------- % % % Allow user-defined commands and environments to have an % optional first arg. % % For environments, also allow ``*-form'' which ignores spaces % after the \end{...} command. (P.S. LaTeX2e has now usurped the % ``*-form'' for other purposes in its \newenvironment macro.) % % Usage: % \newoptcommand{\COMMAND}{DEFAULT}[NARGS]{TEXT} % \renewoptcommand{\COMMAND}{DEFAULT}[NARGS]{TEXT} % \newoptenvironment{NAME}{DEFAULT}[NARGS]{B_TEXT}{E_TEXT} % \newoptenvironment*{NAME}{DEFAULT}[NARGS]{B_TEXT}{E_TEXT} % \renewoptenvironment{NAME}{DEFAULT}[NARGS]{B_TEXT}{E_TEXT} % \renewoptenvironment*{NAME}{DEFAULT}[NARGS]{B_TEXT}{E_TEXT} % % For example, after defining % \newoptcommand{\glug}{defval}[2]{% % First is ``#1'', 2nd is ``#2''.} % the user can do % \glug{hahaha}\\ % \glug[oof]{bzzzz} % to get % First is ``defval'', 2nd is ``hahaha''. % First is ``oof'', 2nd is ``bzzzz''. % \def\newoptcommand#1#2{% \@ifnextchar [{\@optargdef#1#2}{\@optargdef#1#2[1]}} \def\renewoptcommand#1#2{% \edef\@tempa{\expandafter\@cdr\string#1\@nil}% \@ifundefined{\@tempa}{% \@latexerr{\string#1\space undefined}\@ehc}{}% \@ifnextchar [{\@reoptargdef#1#2}{\@reoptargdef#1#2[1]}} \long\def\@optargdef#1#2[#3]#4{% \@ifdefinable #1{\@reoptargdef#1#2[#3]{#4}}} \long\def\@reoptargdef#1#2[#3]#4{% \@tempcnta#3\relax \@tempcntb \@ne \let#1\relax \let\@tempa\relax \edef\@tempb{\long\def\csname\string#1\endcsname [\@tempa\the\@tempcntb]}% \advance\@tempcntb \@ne \advance\@tempcnta \m@ne \@whilenum\@tempcnta>0\do{% \edef\@tempb{\@tempb\@tempa\the\@tempcntb}% \advance\@tempcntb \@ne \advance\@tempcnta \m@ne}% \let\@tempa=##\@tempb{#4}\let\@tempa\relax \def#1{\@ifnextchar [{\csname\string#1\endcsname}{% \csname\string#1\endcsname[#2]}}} \def\newoptenvironment{% \@ifnextchar *{\@@newoptenv{\global\@ignoretrue}}{% \@@newoptenv{}*}} \def\@@newoptenv#1*#2#3{% \@ifnextchar [{\@newoptenv{#1}{#2}{#3}}{% \@newoptenv{#1}{#2}{#3}[0]}} \long\def\@newoptenv#1#2#3[#4]#5#6{% \expandafter\newoptcommand\csname#2\endcsname{#3}[#4]{#5}% \expandafter\long\expandafter\def\csname end#2\endcsname{#6#1}} \def\renewoptenvironment{% \@ifnextchar *{\@@renewoptenv{\global\@ignoretrue}}{% \@@renewoptenv{}*}} \def\@@renewoptenv#1*#2#3{% \@ifnextchar [{\@renewoptenv{#1}{#2}{#3}}{% \@renewoptenv{#1}{#2}{#3}[0]}} \long\def\@renewoptenv#1#2#3[#4]#5#6{% \expandafter\renewoptcommand\csname#2\endcsname{#3}[#4]{#5}% \expandafter\long\expandafter\def\csname end#2\endcsname{#6#1}} % % % ---------------------------------------------------------------- % -------------------------CORE CODE------------------------------ % ---------------------------------------------------------------- % % % ---INTERNAL VARIABLES--- % \newcount\dg@HORIZ \newcount\dg@VERT \newcount\dg@XLPAD \newcount\dg@YBPAD \newcount\dg@XRPAD \newcount\dg@YTPAD \newcount\dg@X \newcount\dg@Y \newcount\dg@XGRID \newcount\dg@YGRID \newcount\dg@SIZE \newcount\dg@USERSIZE \newcount\dg@DX \newcount\dg@DY \newcount\dg@XLBL \newcount\dg@YLBL \newcount\dg@XOFFSET \newcount\dg@YOFFSET \newcount\dg@LBLOFF \newcount\dg@XLBLOFF \newcount\dg@YLBLOFF \newcount\dg@LBLPOS \newcount\dg@XTEMP \newcount\dg@YTEMP \newcount\dg@ZTEMP \newcount\dg@XNODE \newcount\dg@YNODE \newcount\dg@XEND \newcount\dg@YEND \newcount\dg@WEND \newcount\dg@HEND \newcount\dg@COUNT \newbox\dg@NODEBOX \newbox\dg@LABONEBOX \newbox\dg@LABTWOBOX % % % ---THE DIAGRAM ENVIRONMENT--- % % Ignores spaces following the \end{diagram}, % just like most LaTeX built-in environments do (thus the `*'). % If optional arg supplied, reverts to version 1.0 behavior. % \newoptenvironment*{diagram}{}[1]{% \global\advance\dg@COUNT\@ne \message{[diagram \the\dg@COUNT}% \let\node=\dg@node \let\\=\dg@cr \let\arrow=\dg@arrow % % COMPATIBILITY MODE FOR VERSION 1.0. % % If opt arg given, use it as "BIGNODE" in old-style grid % geometry calculation. \def\dg@BIGNODE{#1}% \ifx\dg@BIGNODE\@empty\else \setbox\dg@NODEBOX=\hbox{$\dgeverynode{#1}$}% \dg@XTEMP=\wd\dg@NODEBOX \dg@YTEMP=\ht\dg@NODEBOX \advance\dg@YTEMP\dp\dg@NODEBOX \ifnum\dg@YTEMP=\z@ \dg@ZTEMP=\z@ \else \dg@ZTEMP=\dg@XTEMP \divide\dg@ZTEMP\dg@YTEMP\fi \ifnum\dg@ZTEMP>\dgMAXSQUARE \ifnum\dg@ZTEMP<\dgMINDBLSQ \dg@XGRID=\thr@@ \dg@YGRID=\tw@ \else \dg@XGRID=\tw@ \dg@YGRID=\@ne \fi \else \dg@XGRID=\@ne \dg@YGRID=\@ne \fi \advance\dg@XTEMP\dgHORIZPAD \advance\dg@YTEMP\dgVERTPAD \dg@XLPAD=\dg@XTEMP \divide\dg@XLPAD\tw@ \dg@XRPAD=\dg@XTEMP \divide\dg@XRPAD\tw@ \dg@YBPAD=\dg@YTEMP \divide\dg@YBPAD\tw@ \dg@YTPAD=\dg@YTEMP \divide\dg@YTPAD\tw@ \advance\dg@XTEMP\dgARROWLENGTH \divide\dg@XTEMP\dg@XGRID \divide\dg@XTEMP\@m \unitlength=1sp\relax \multiply\unitlength\dg@XTEMP \fi % % PASS (1): SAVE UP NODES AND ARROWS. % \message{.}% \dg@X=-\@ne \dg@Y=\z@ \dg@HORIZ=\z@\relax \let\dg@SLIST=\@empty \let\dg@NLIST=\@empty \let\dg@ALIST=\@empty \let\dg@PASS=\dg@savepass }{%\enddiagram % Compute size of diagram. \dg@VERT=-\dg@Y\relax % % PASS (2): CALCULATE GRID GEOMETRY. % Since arrows may terminate at nodes later in the diagram, % this requires node data to be saved up in an earlier pass. % Don't need geom pass in compatibility mode. % \message{.}% \ifx\dg@BIGNODE\@empty % Accumulate data. \dg@XGRID=\z@ \dg@YGRID=\z@ \dg@XLPAD=\z@ \dg@XRPAD=\z@ \dg@YBPAD=\z@ \dg@YTPAD=\z@ \let\dg@PASS=\dg@geompass \dg@NLIST \dg@ALIST % Calculate \unitlength and normalized XGRID,YGRID. % catch any bogus results along the way \ifnum\dg@XGRID>\z@\else \dg@XGRID=\dgHORIZPAD \fi \ifnum\dg@YGRID>\z@\else \dg@YGRID=\dgVERTPAD \fi \dggeometry \ifdim\unitlength>\z@\else \unitlength=\dgARROWLENGTH \divide\unitlength\@m \fi \ifnum\dg@XGRID>\z@\else \dg@XGRID=\@ne \fi \ifnum\dg@YGRID>\z@\else \dg@YGRID=\@ne \fi \fi % Now convert drawing dimensions to \unitlength's. \dg@LBLOFF=\dgLABELOFFSET \divide\dg@LBLOFF\unitlength \multiply\dg@HORIZ\@m \multiply\dg@HORIZ\dg@XGRID \multiply\dg@VERT\@m \multiply\dg@VERT\dg@YGRID \divide\dg@XLPAD\unitlength \divide\dg@XRPAD\unitlength \divide\dg@YBPAD\unitlength \divide\dg@YTPAD\unitlength % % PASS (3): DRAW SAVED NODES AND ARROWS USING GRID GEOMETRY. % \message{.}% \let\dg@PASS=\dg@drawpass \hspace*{\dg@XLPAD\unitlength}% \vcenter{% \hsize=0pt\relax\parindent=0pt\relax \parskip=0pt\relax\baselineskip=0pt\relax \vspace*{\dg@YTPAD\unitlength}% \begin{picture}(0,0)(0,0)\dg@NLIST\dg@ALIST\end{picture}% % Tell TeX how high diagram is. \raisebox{\z@}[\z@][\dg@VERT\unitlength]{}% \vspace*{\dg@YBPAD\unitlength}% } % Tell TeX how wide diagram is. \hspace*{\dg@HORIZ\unitlength}% \hspace*{\dg@XRPAD\unitlength}% \message{]}}% \def\dg@savepass{s} \def\dg@geompass{g} \def\dg@drawpass{d} % % % ---MAKING NODES--- % % In draw pass, just place centered FORMULA at current coords. % % In geometry pass, find how far nodes at boundary of diagram % stick out over the edge; update XLPAD, XRPAD, YBPAD, YTPAD. % % In save pass, measure FORMULA, saving incremented position and % padded width and height (in scaled points) on \dg@SLIST like so: % ...,\dg@XNODE=\relax\dg@YNODE=\relax % \dg@XTEMP=\relax\dg@YTEMP=\relax,... % and saving node itself (for later drawing) on \dg@NLIST like so: % ...\dg@X=\relax\dg@Y=\relax % \dg@node{FORMULA}... % Also update HORIZ (horizontal size of diagram). % % Usage: \dg@node[NCOLS]{FORMULA} \newoptcommand{\dg@node}{\@ne}[2]{% \ifx\dg@PASS\dg@savepass % % Update coordinates and XMAX. \dg@XTEMP=#1\relax \ifnum\dg@XTEMP<\@ne \dg@XTEMP=\@ne\fi \advance\dg@X\dg@XTEMP \ifnum\dg@HORIZ<\dg@X \dg@HORIZ=\dg@X \fi % % Measure padded node. \setbox\dg@NODEBOX=\hbox{$\dgeverynode{#2}$}% \dg@XTEMP=\wd\dg@NODEBOX \advance\dg@XTEMP\dgHORIZPAD \dg@YTEMP=\ht\dg@NODEBOX \advance\dg@YTEMP\dp\dg@NODEBOX \advance\dg@YTEMP\dgVERTPAD % % Save up info in NLIST and SLIST. \toks\z@=\expandafter{\dg@SLIST}% \edef\dg@SLIST{\the\toks\z@ ,\noexpand\dg@XNODE=\number\dg@X\noexpand\relax \noexpand\dg@YNODE=\number\dg@Y\noexpand\relax \noexpand\dg@XTEMP=\number\dg@XTEMP\noexpand\relax \noexpand\dg@YTEMP=\number\dg@YTEMP\noexpand\relax}% \toks\z@=\expandafter{\dg@NLIST}% \toks\tw@={\dg@node{#2}}% \edef\dg@NLIST{\the\toks\z@ \noexpand\dg@X=\number\dg@X\noexpand\relax \noexpand\dg@Y=\number\dg@Y\noexpand\relax \the\toks\tw@}% \else\ifx\dg@PASS\dg@geompass % % If on boundary, compare padding against half of % padded node size saved in SLIST. \ifnum\dg@X=\z@ \dg@getnodesize {\dg@SLIST}{\dg@X}{\dg@Y}{\dg@WEND}{\dg@HEND}% \divide\dg@WEND\tw@ \ifnum\dg@XLPAD<\dg@WEND \dg@XLPAD=\dg@WEND \fi\fi \ifnum\dg@X=\dg@HORIZ \dg@getnodesize {\dg@SLIST}{\dg@X}{\dg@Y}{\dg@WEND}{\dg@HEND}% \divide\dg@WEND\tw@ \ifnum\dg@XRPAD<\dg@WEND \dg@XRPAD=\dg@WEND \fi\fi \ifnum\dg@Y=\z@ \dg@getnodesize {\dg@SLIST}{\dg@X}{\dg@Y}{\dg@WEND}{\dg@HEND}% \divide\dg@HEND\tw@ \ifnum\dg@YTPAD<\dg@HEND \dg@YTPAD=\dg@HEND \fi\fi \ifnum\dg@Y=-\dg@VERT\relax \dg@getnodesize {\dg@SLIST}{\dg@X}{\dg@Y}{\dg@WEND}{\dg@HEND}% \divide\dg@HEND\tw@ \ifnum\dg@YBPAD<\dg@HEND \dg@YBPAD=\dg@HEND \fi\fi \else\ifx\dg@PASS\dg@drawpass % % Get scaled picture coords from grid coords. \dg@XNODE=\dg@X \multiply\dg@XNODE\@m \multiply\dg@XNODE\dg@XGRID \dg@YNODE=\dg@Y \multiply\dg@YNODE\@m \multiply\dg@YNODE\dg@YGRID % % Place centered formula. \setbox\dg@NODEBOX=\hbox{$\dgeverynode{#2}$}% \put(\dg@XNODE,\dg@YNODE){\dg@makebox{\box\dg@NODEBOX}}% \fi\fi\fi}% % Increment coordinates to beginning of NROWS-th next % grid row (only in save pass). % Usage: \dg@cr[NROWS] \newoptcommand{\dg@cr}{\@ne}[1]{% \ifx\dg@PASS\dg@savepass \dg@YTEMP=#1\relax \ifnum\dg@YTEMP<\@ne \dg@YTEMP=\@ne \fi \advance\dg@Y -\dg@YTEMP\relax \dg@X=-\@ne\relax\fi}% % % % ---ARROW COMMAND FRONT ENDS--- % % Usage: \dg@arrow[USERSIZE]{ARROW_SPECS}LABELS... \newoptcommand{\dg@arrow}{\@ne}[2]{% % Keep all arrow geometry parameters local. % The \endgroup is in each \dg@process command. \begingroup % Get optional arrow USERSIZE. \dg@USERSIZE=#1\relax \ifnum\dg@USERSIZE<\@ne \dg@USERSIZE=\@ne \fi % Parse arrow specification. \dg@parse{#2}% % In draw pass, calculate all the arrow geometry parameters % and draw the arrow. % In geometry pass, calculate minimum size of grid rectangle % which gives all arrows ARROWLENGTH of room. % In save mode, just save up the arrow in ALIST. \ifx\dg@PASS\dg@savepass % Arrow saved with orig LBLTYPE, so don't re-position args. \ifx\dg@label\dgl@b \let\dg@label=\dgl@t \fi \ifx\dg@label\dgl@r \let\dg@label=\dgl@l \fi \let\dg@process=\dg@save \else\ifx\dg@PASS\dg@geompass \let\dg@process=\dg@ignore \dg@geomcalc \else\ifx\dg@PASS\dg@drawpass \let\dg@process=\dg@draw \dg@drawcalc \fi\fi\fi \dg@label{\dg@process{#1}{#2}}}% % Also make \arrow command available for use outside of diagrams. \newoptcommand{\arrow}{\@ne}[2]{% % Parse arrow specification just to check how many labels. \dg@parse{#2}% % Text arrow uses original LBLTYPE, so don't re-position args. \ifx\dg@label\dgl@b \let\dg@label=\dgl@t \fi \ifx\dg@label\dgl@r \let\dg@label=\dgl@l \fi \dg@label{\dg@textarrow{#1}{#2}}}% \def\dg@textarrow#1#2#3#4{% \mathrel{{% \dgHORIZPAD=0pt\relax\dgVERTPAD=0pt\relax \dgARROWLENGTH=\dgTEXTARROWLENGTH\relax \begin{diagram} \node{}\arrow[#1]{#2}{#3}{#4}\node{} \end{diagram}}}} % Parse arrow specification: comma-separated list of features. % Usage: \dg@parse{TYPE,ONEORTWOLABEL,OPTION,...} \def\dg@parse#1{% % Set default labeling and options. \let\dg@label=\dgl@ \dgo@ % Scan list, reading TYPE, LBLTYPE, and OPTIONs in turn. \let\dg@type=\@empty \let\dg@lbltype=\@empty \@for\dg@list:=#1\do{% \ifx\dg@type\@empty \let\dg@type=\dg@list \else\ifx\dg@lbltype\@empty \let\dg@lbltype=\dg@list % In case LBLTYPE not given, try it as an OPTION. % Note: LBLTYPE and OPTION names must not conflict. \@ifundefined{dgo@\dg@list}{}{\@nameuse{dgo@\dg@list}}% \else % Process OPTIONs in turn; ignore bad OPTIONs. \@ifundefined{dgo@\dg@list}{}{\@nameuse{dgo@\dg@list}}% \fi\fi}% % TYPE must be specified (default to "e" on error). % Get raw geometry parameters from TYPE. \@ifundefined{dgt@\dg@type}{\dgt@e}{\@nameuse{dgt@\dg@type}}% % If LBLTYPE valid, get arg handler. \@ifundefined{dgl@\dg@lbltype}{}{% \dg@letname\dg@label{dgl@\dg@lbltype}}} % % % ---ARROW PROCESSORS (draw, save, ignore)--- % % Draw labelled arrow, using computed arrow geometry parameters. % The \endgroup matches \dg@arrow's \begingroup. % Usage: \dg@draw{IGNORE}{IGNORE}{LABEL1}{LABEL2} \def\dg@draw#1#2#3#4{% % Typeset labels in advance of all the positioning yoga, % since TeX only allows boxes to nest 40 deep by default. \setbox\dg@LABONEBOX=\hbox{$\dgeverylabel{#3}$}% \setbox\dg@LABTWOBOX=\hbox{$\dgeverylabel{#4}$}% \put(\dg@X,\dg@Y){\dg@makebox{% \begin{picture}(0,0)% \thinlines \put(\dg@XOFFSET,\dg@YOFFSET){% \dg@VECTOR(\dg@DX,\dg@DY){\dg@SIZE}}% \put(\dg@XLBL,\dg@YLBL){\dg@makebox{% \begin{picture}(0,0)% \put(\dg@XLBLOFF,\dg@YLBLOFF){% \dg@makebox[\dg@LBLONE]{\box\dg@LABONEBOX}}% \put(-\dg@XLBLOFF,-\dg@YLBLOFF){% \dg@makebox[\dg@LBLTWO]{\box\dg@LABTWOBOX}}% \end{picture}}}% \end{picture}}}% \endgroup}% % Instead of drawing arrow now, save it on \dg@ALIST, like so: % ...\dg@X=\relax\dg@Y=\relax % \dg@arrow[]{}... % If either LABEL1 or LABEL2 is empty, it will simply be ignored. % Usage: \dg@save{SIZE}{SPEC}{LABEL1}{LABEL2} \def\dg@save#1#2#3#4{% \endgroup % to match \dg@arrow's \begingroup \toks\z@=\expandafter{\dg@ALIST}% \toks\tw@={\dg@arrow[#1]{#2}{#3}{#4}}% \edef\dg@ALIST{\the\toks\z@% \noexpand\dg@X=\number\dg@X\noexpand\relax \noexpand\dg@Y=\number\dg@Y\noexpand\relax \the\toks\tw@}}% % No-op arrow processor. % Usage: \dg@ignore{SIZE}{SPEC}{LABEL1}{LABEL2} \def\dg@ignore#1#2#3#4{\endgroup} % % % ---ARROW GEOMETRY CALCULATIONS--- % % Calc minimum size of grid rectangle (in sp's) so that all % arrows will have horizontal extent at least ARROWMINX := % ( |DX|/(|DX|+|DY|) )*ARROWLENGTH and the vertical extent % at least ARROWMINY := ( |DY|/(|DX|+|DY|) )*ARROWLENGTH. % We estimate the horizontal and vertical extents from node % corner to node corner (which indeed gives lower bounds). % Thus for example XGRID must be at least % ( (WSOURCE+WTARGET)/2 + ARROWMINX ) / (SIZE*USERSIZE) % where the width of source (target) node is WSOURCE (WTARGET). % There is a similar formula for YGRID. % % Updates (globally so we see it later!!!): % XGRID, YGRID % using these arrow parameters: % X, Y, USERSIZE, SIZE, DX, DY % using also the stored node dims in SLIST. % Uses temp vars XOFFSET, YOFFSET, XTEMP, YTEMP, ZTEMP, % XEND, YEND, WEND, HEND. No args. % \def\dg@geomcalc{% % Find other end of arrow (in grid coords). \dg@XEND=\dg@SIZE \multiply\dg@XEND\dg@USERSIZE \ifnum\dg@DX=\z@ \dg@YEND=\dg@XEND \dg@XEND=\z@ \dg@changesign\dg@YEND\dg@DY \else \dg@changesign\dg@XEND\dg@DX \dg@YEND=\dg@XEND \multiply\dg@YEND\dg@DY \divide\dg@YEND\dg@DX \fi \advance\dg@XEND\dg@X \advance\dg@YEND\dg@Y % Get size of node there. \dg@getnodesize {\dg@SLIST}{\dg@XEND}{\dg@YEND}{\dg@WEND}{\dg@HEND}% \dg@XOFFSET=\dg@WEND \dg@YOFFSET=\dg@HEND % Now average in size of current node. \dg@getnodesize {\dg@SLIST}{\dg@X}{\dg@Y}{\dg@WEND}{\dg@HEND}% \advance\dg@XOFFSET\dg@WEND \divide\dg@XOFFSET\tw@ \advance\dg@YOFFSET\dg@HEND \divide\dg@YOFFSET\tw@ % Now add in required arrow length. \dg@XTEMP=\dgARROWLENGTH \dg@YTEMP=\dgARROWLENGTH \ifnum\dg@DX>\z@ \dg@ZTEMP=\dg@DX \multiply\dg@XTEMP\dg@DX \else \dg@ZTEMP=-\dg@DX \multiply\dg@XTEMP -\dg@DX \fi \ifnum\dg@DY>\z@ \advance\dg@ZTEMP\dg@DY \multiply\dg@YTEMP\dg@DY \else \advance\dg@ZTEMP -\dg@DY \multiply\dg@YTEMP -\dg@DY\fi \ifnum\dg@ZTEMP=\z@\else \divide\dg@XTEMP\dg@ZTEMP \divide\dg@YTEMP\dg@ZTEMP \advance\dg@XOFFSET\dg@XTEMP \advance\dg@YOFFSET\dg@YTEMP \fi % Divide by number of grid units to get unit size. \divide\dg@XOFFSET\dg@SIZE \divide\dg@XOFFSET\dg@USERSIZE \divide\dg@YOFFSET\dg@SIZE \divide\dg@YOFFSET\dg@USERSIZE \ifnum\dg@DX=\z@ \dg@XOFFSET=\z@ \fi \ifnum\dg@DY=\z@ \dg@YOFFSET=\z@ \fi \ifnum\dg@XGRID<\dg@XOFFSET \global\dg@XGRID=\dg@XOFFSET\fi \ifnum\dg@YGRID<\dg@YOFFSET \global\dg@YGRID=\dg@YOFFSET\fi \relax} % Calculate all the arrow geometry parameters for drawing: % X, Y, DX, DY, SIZE, XOFFSET, YOFFSET, VECTOR, % XLBL, YLBL, XLBLOFF, YLBLOFF, LBLONE, LBLTWO, % from the (preliminary) values of these basic arrow parameters: % X, Y, USERSIZE, SIZE, DX, DY, VECTOR, LBLPOS % using also the grid geom and diagram parameters: % XGRID, YGRID, LBLOFF, SLIST, ARROWPARTS. % No args. Uses temp vars XTEMP, YTEMP, ZTEMP, XEND, WEND. \def\dg@drawcalc{% % Get size of node at other end of arrow (in picture coords). \dg@XEND=\dg@SIZE \multiply\dg@XEND\dg@USERSIZE \ifnum\dg@DX=\z@ \dg@YEND=\dg@XEND \dg@XEND=\z@ \dg@changesign\dg@YEND\dg@DY \else \dg@changesign\dg@XEND\dg@DX \dg@YEND=\dg@XEND \multiply\dg@YEND\dg@DY \divide\dg@YEND\dg@DX \fi \advance\dg@XEND\dg@X \advance\dg@YEND\dg@Y \dg@getnodesize {\dg@SLIST}{\dg@XEND}{\dg@YEND}{\dg@WEND}{\dg@HEND}% \divide\dg@WEND\unitlength \divide\dg@HEND\unitlength % Now adjust arrow dir to grid aspect ratio (XGRID:YGRID). \multiply\dg@DX\dg@XGRID \multiply\dg@DY\dg@YGRID \dg@rmcommondiv\tw@\dg@DX\dg@DY \dg@rmcommondiv\tw@\dg@DX\dg@DY %[sic] \dg@rmcommondiv\thr@@\dg@DX\dg@DY % Scale arrow to requested USERSIZE. % Prepare to convert size from grid to picture coords. \multiply\dg@SIZE\dg@USERSIZE \multiply\dg@SIZE\@m \ifnum\dg@DX=\z@ % % Vertical arrow. % % SIZE measures vertical extent in \unitlengths. Arrow % shortened by (HSOURCE+HTARGET)/2, offset by HSOURCE/2. % Labels at (HSOURCE/2 + SIZE*LBLPOS/ARROWPARTS). \multiply\dg@SIZE\dg@YGRID \divide\dg@HEND\tw@ \advance\dg@SIZE -\dg@HEND \dg@getnodesize {\dg@SLIST}{\dg@X}{\dg@Y}{\dg@WEND}{\dg@YOFFSET}% \divide\dg@YOFFSET\unitlength \divide\dg@YOFFSET\tw@ \advance\dg@SIZE -\dg@YOFFSET \dg@XOFFSET=\z@ \def\dg@LBLONE{r}\def\dg@LBLTWO{l}% \dg@XLBL=\z@ \dg@YLBL=\dg@SIZE \multiply\dg@YLBL\dg@LBLPOS \divide\dg@YLBL\dgARROWPARTS\relax \advance\dg@YLBL\dg@YOFFSET \dg@changesign\dg@YLBL\dg@DY \dg@changesign\dg@YOFFSET\dg@DY \else % Nonvertical arrow. % SIZE measures horizontal extent in \unitlengths. \multiply\dg@SIZE\dg@XGRID \ifnum\dg@DY=\z@ % % Horizontal arrow. % % Arrow shortened by (WSOURCE+WTARGET)/2, % and offset by WSOURCE/2. Labels at % (WSOURCE/2 + SIZE*LBLPOS/ARROWPARTS). \divide\dg@WEND\tw@ \advance\dg@SIZE -\dg@WEND \dg@getnodesize {\dg@SLIST}{\dg@X}{\dg@Y}{\dg@XOFFSET}{\dg@HEND}% \divide\dg@XOFFSET\unitlength \divide\dg@XOFFSET\tw@ \advance\dg@SIZE -\dg@XOFFSET \dg@YOFFSET=\z@ \def\dg@LBLONE{b}\def\dg@LBLTWO{t}% \dg@YLBL=\z@ \dg@XLBL=\dg@SIZE \multiply\dg@XLBL\dg@LBLPOS \divide\dg@XLBL\dgARROWPARTS\relax \advance\dg@XLBL\dg@XOFFSET \dg@changesign\dg@XLBL\dg@DX \dg@changesign\dg@XOFFSET\dg@DX \else % % Diagonal arrow. % % Arrow offset in its own direction, with horiz comp % XOFFSET := MIN( WSOURCE/2, |DX/DY|*HSOURCE/2 ), and % shortened in its own direction, with horiz comp % XOFFSET + MIN( WTARGET/2, |DX/DY|*HTARGET/2 ). % Labels at ( XOFFSET+-SIZE*LBLPOS/ARROWPARTS , % YOFFSET+-SIZE*LBLPOS*DY/(ARROWPARTS*DX) ) \divide\dg@WEND\tw@ \divide\dg@HEND\tw@ \multiply\dg@HEND\dg@DX \divide\dg@HEND\dg@DY \ifnum\dg@HEND<\z@ \multiply\dg@HEND\m@ne \fi \ifnum\dg@WEND<\dg@HEND \advance\dg@SIZE -\dg@WEND \else \advance\dg@SIZE -\dg@HEND \fi \dg@getnodesize {\dg@SLIST}{\dg@X}{\dg@Y}{\dg@WEND}{\dg@HEND}% \divide\dg@WEND\unitlength \divide\dg@WEND\tw@ \divide\dg@HEND\unitlength \divide\dg@HEND\tw@ \multiply\dg@HEND\dg@DX \divide\dg@HEND\dg@DY \ifnum\dg@HEND<\z@ \multiply\dg@HEND\m@ne \fi \ifnum\dg@WEND<\dg@HEND \dg@XOFFSET=\dg@WEND \else \dg@XOFFSET=\dg@HEND \fi \advance\dg@SIZE -\dg@XOFFSET \dg@changesign\dg@XOFFSET\dg@DX \dg@YOFFSET=\dg@XOFFSET \multiply\dg@YOFFSET\dg@DY \divide\dg@YOFFSET\dg@DX \def\dg@LBLONE{br}\def\dg@LBLTWO{tl}% \ifnum\dg@DX<\z@ \ifnum\dg@DY>\z@ \def\dg@LBLONE{bl}\def\dg@LBLTWO{tr}\fi\fi \ifnum\dg@DX>\z@ \ifnum\dg@DY<\z@ \def\dg@LBLONE{bl}\def\dg@LBLTWO{tr}\fi\fi \dg@XLBL=\dg@SIZE \multiply\dg@XLBL\dg@LBLPOS \divide\dg@XLBL\dgARROWPARTS\relax \dg@changesign\dg@XLBL\dg@DX \dg@YLBL=\dg@XLBL \multiply\dg@YLBL\dg@DY \divide\dg@YLBL\dg@DX \advance\dg@XLBL\dg@XOFFSET \advance\dg@YLBL\dg@YOFFSET \fi \fi % (XLBL,YLBL) is the displacement from the source NODE % to the point along the arrow where the labels will be % attached. The offset of first label from this attachment % point is the vector % (XLBLOFF,YLBLOFF) = LBLOFF*sgn(DX)*(-DY,DX)/max(|DX|,|DY|) % if DX is nonzero, and otherwise % (XLBLOFF,YLBLOFF) = LBLOFF*(-1,0); % The 2nd label is offset by the vector (-XLBLOFF,-YLBLOFF). \dg@XLBLOFF=-\dg@DY \dg@changesign\dg@XLBLOFF\dg@DX \dg@YLBLOFF=\dg@DX \dg@changesign\dg@YLBLOFF\dg@DX \ifnum\dg@DX=\z@ \dg@XLBLOFF=\m@ne \fi \dg@XTEMP=\dg@DX \dg@changesign\dg@XTEMP\dg@DX \dg@YTEMP=\dg@DY \dg@changesign\dg@YTEMP\dg@DY \ifnum\dg@YTEMP>\dg@XTEMP \dg@XTEMP=\dg@YTEMP \fi \ifnum\dg@XTEMP=\z@ \dg@XTEMP=\@ne \fi \multiply\dg@XLBLOFF\dg@LBLOFF \divide\dg@XLBLOFF\dg@XTEMP \multiply\dg@YLBLOFF\dg@LBLOFF \divide\dg@YLBLOFF\dg@XTEMP % % Change node location from grid coords to picture coords. \multiply\dg@X\@m \multiply\dg@X\dg@XGRID \multiply\dg@Y\@m \multiply\dg@Y\dg@YGRID \relax}% % % % ---UTILITY ROUTINES--- % % Remove common divisor DIV from two number registers % \NUM1 and \NUM2. % Usage: \dg@rmcommondiv{DIV}{\NUM1}{\NUM2} % NOTE: uses XTEMP and YTEMP. \def\dg@rmcommondiv#1#2#3{% \dg@XTEMP=#2\relax \divide\dg@XTEMP #1\relax \multiply\dg@XTEMP #1\relax \dg@YTEMP=#3\relax \divide\dg@YTEMP #1\relax \multiply\dg@YTEMP #1\relax \ifnum\dg@XTEMP=#2\relax \ifnum\dg@YTEMP=#3\relax \divide#2#1\relax \divide#3#1\relax \fi\fi}% % Multiply number register \NUM1 by sign of \NUM2. % Usage: \dg@changesign\NUM1\NUM2 \def\dg@changesign#1#2{% \ifnum #2<\z@ \multiply#1\m@ne \else\ifnum #2=\z@ #1=\z@ \fi\fi}% % Determine from SLIST the padded width and height (in scaled % points) of the node at (XCOORD,YCOORD). Place the answer % in number registers \WIDTH and \HEIGHT, resp (if no such % node, set \WIDTH=\HEIGHT=0). SLIST must be comma-separated % list of TeX code segments, each of which puts the node % coords in (XNODE,YNODE) and the node dims in(XTEMP,YTEMP). % Uses temp vars XTEMP, YTEMP. % Usage: \dg@getnodesize{SLIST}{XCOORD}{YCOORD}{\WIDTH}{\HEIGHT} \def\dg@getnodesize#1#2#3#4#5{% #4=\z@\relax #5=\z@\relax % loop through saved nodes \expandafter\@for\expandafter\dg@trynode \expandafter:\expandafter=#1\do{% \dg@XNODE=\m@ne % impossible (in case \dg@trynode is empty) \dg@trynode \ifnum #2=\dg@XNODE \ifnum #3=\dg@YNODE #4=\dg@XTEMP\relax #5=\dg@YTEMP\relax\fi\fi}}% % Like \makebox(0,0)[POS]{TEXT}, with POS expanded beforehand % and bottom alignment at true bottom rather than baseline. % % Usage: \dg@makebox[POS]{TEXT} \long\def\dg@sinkbaseline#1{% \leavevmode\hbox{\vbox{% \lineskiplimit=16383pt\relax\lineskip=0pt\relax \baselineskip=-1000pt\relax \parindent=0pt\relax\parskip=0pt\relax \hbox{#1}\rule{0pt}{0pt}}}}% \newoptcommand{\dg@makebox}{}[2]{\dg@sinkbaseline{% \expandafter\makebox\expandafter(\expandafter 0\expandafter,\expandafter0\expandafter)\expandafter [#1]{#2}}}% % Produces nothing. % Usage:\dg@novector(X,Y){LaTeX_LENGTH} \def\dg@novector(#1,#2)#3{}% % \let command \CMD equal funny-name command \NAME. % Usage: \dg@letname{\CMD}{NAME}. \def\dg@letname#1#2{% \relax\expandafter \let\expandafter #1\csname #2\endcsname\relax}% % Arg handlers, one for each LBLTYPE. % Grab labels from following input and feed them to \PROCESS. % Usage: \dgl@LBLTYPE{\PROCESS}... \def\dgl@#1{#1{}{}}% \def\dgl@t#1#2{#1{#2}{}}% \def\dgl@b#1#2{#1{}{#2}}% \def\dgl@tb#1#2#3{#1{#2}{#3}}% \def\dgl@l#1#2{#1{#2}{}}% \def\dgl@r#1#2{#1{}{#2}}% \def\dgl@lr#1#2#3{#1{#2}{#3}}%