3

I have created a multipart shape for use in TikZ.

The shape has three levels, with five text entries.

The top level is a rectangle whose dimensions are only based on the text in that rectangle.

The bottom level is a set of three rectangles that should have equal heights (based on the largest height in the set) and equal widths (based on the largest width in the set).

The middle level is a single rectangle that should have the same width as the bottom level (so I need to have the width chosen as the maximum of the middle level or the set of three on the lower level).

In order to make this work, I wanted to have code used in multiple \savedanchors, so I defined a couple of functions (one for the width of the shape, one for the height of the bottom layer) as described in How can I define a calculation of a dimension that I can reuse in a \savedanchor declaration.

So after much trial and error, I developed a multipart shape. I have a WE below (it's probably not an MWE, because I have lots of anchors -- I don't need that many for an MWE.

Here are some questions/comments that I arrived at in my coding odyssey. Can anyone tell me if they are real, or if I'm just suffering from cargo cult programming?

  1. I need a \savedanchor for every point that I want to use for drawing the background.
  2. I created (but may not need) a \savedanchor for the text in each part of the shape. Do I need those \savedanchors?
  3. I am not sure when (or if) I need to use \pgf@process{\mysavedanchor} as opposed to \mysavedanchor when defining anchors. I have examples of both.
  4. I feel like this code is more closely related to assembly language programming than to, say C or Python. Does my code exhibit good practices, or is it really bad?

I know the example is long, and if I need to, I can strip out all of the anchors that are not savedanchors. But I would like to get some code review if possible.

CODE

    \documentclass{article}

    \usepackage{tikz}

    \newbox\pgfnodepartlowerbox
    \newbox\pgfnodeparttotalbox
    \newbox\pgfnodepartchildbox
    \newbox\pgfnodepartlevelbox
    \newbox\pgfnodepartsiblingbox


    %
    % A five-part shape that looks somewhat like a house, with three parts
    % on the lowest level, one part on the middle level, and one part on 
    % the top level.
    %
    %
    % Parts: text, level, child, sibling, total

    \makeatletter
    \def\my@widthcode{
          \pgfmathsetlength{\pgf@xa}{\pgfkeysvalueof{/pgf/inner xsep}}%
          % get the widths of each of the three rectangles on the lower level
          \pgfutil@tempdima=\wd\pgfnodepartlevelbox%
          \advance\pgfutil@tempdima by 2\pgf@xa      
          \pgf@xb=\wd\pgfnodepartchildbox%
          \advance\pgf@xb by 2\pgf@xa %
          \ifdim\pgf@xb>\pgfutil@tempdima%
            \pgfutil@tempdima=\pgf@xb%
          \fi%
          \pgf@xc=\wd\pgfnodepartsiblingbox%
          \advance\pgf@xc by 2\pgf@xa %
          \ifdim\pgf@xc>\pgfutil@tempdima%
            \pgfutil@tempdima=\pgf@xc%
          \fi%
          % account for the 3 boxes on the lower level
          \pgfutil@tempdima = 3\pgfutil@tempdima%
          \advance \pgfutil@tempdima by 2\pgflinewidth% two lines in the middle of the row
          % see if the main level box is wider
          \pgf@xb = \wd\pgfnodeparttextbox%
          \advance \pgf@xb by 2\pgf@xa%
          \ifdim\pgf@xb>\pgfutil@tempdima%
            \pgfutil@tempdima=\pgf@xb%
          \fi%
          % See if the total box is wider
          \pgf@xb=\wd\pgfnodeparttotalbox%
          \advance \pgf@xb by 2\pgf@xa%
          \ifdim\pgf@xb>\pgfutil@tempdima%
            \pgfutil@tempdima=\pgf@xb%
          \fi%
       }

    \def\my@heightcode {
          % get the heights of each of the three rectangles on the lower level
          % return values: \pgfutil@tempdima is total box height
          %                        \pgfutil@tempdimb is depth
          \pgfutil@tempdima=\ht\pgfnodepartlevelbox%
          \pgfutil@tempdimb=\dp\pgfnodepartlevelbox%
          \advance\pgfutil@tempdima by \pgfutil@tempdimb%
          \pgf@yb=\ht\pgfnodepartchildbox%
          \pgf@ya = \dp\pgfnodepartchildbox %
          \advance\pgf@yb by \pgf@ya  %   
          \ifdim\pgf@yb>\pgfutil@tempdima%
            \pgfutil@tempdima=\pgf@yb%
          \fi%
          \ifdim\pgf@ya>\pgfutil@tempdimb%
            \pgfutil@tempdimb=\pgf@ya%
          \fi%
          \pgf@yc=\ht\pgfnodepartsiblingbox%
          \pgf@ya = \dp\pgfnodepartsiblingbox %
          \advance\pgf@yc by \pgf@ya  %   
           \ifdim\pgf@yc>\pgfutil@tempdima%
            \pgfutil@tempdima=\pgf@yc%
          \fi%
          \ifdim\pgf@ya>\pgfutil@tempdimb%
            \pgfutil@tempdimb=\pgf@ya%
          \fi%
       }

    \pgfdeclareshape{variety}
    {
      %
      % Node parts
      %
      \nodeparts{text,total,level,sibling,child}

      %
      % Saved Anchors
      %
      \savedanchor\centerpoint{%
        \pgf@x=.5\wd\pgfnodeparttextbox%
        \pgf@y = .5\ht\pgfnodeparttextbox%
        \advance\pgf@y by-.5\dp\pgfnodeparttextbox%
      }%
         \savedanchor\totalanchor{%
        \pgf@x=-.5\wd\pgfnodeparttotalbox%
        \advance\pgf@x by.5\wd\pgfnodeparttextbox%
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \pgf@y=2\pgf@y%
        \advance\pgf@y by\ht\pgfnodeparttextbox%
        \advance\pgf@y by\pgflinewidth%
        \advance\pgf@y by\dp\pgfnodeparttotalbox%  
      }
        \savedanchor\childanchor{%
        \pgf@x=-.5\wd\pgfnodepartchildbox%
        \advance\pgf@x by.5\wd\pgfnodeparttextbox%
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
         \pgf@y=-2\pgf@y%  
        \advance\pgf@y by-\dp\pgfnodeparttextbox%
        \advance\pgf@y by-\pgflinewidth%
        \my@heightcode
        \pgf@ya = \pgfutil@tempdima
        \advance\pgf@y by -\pgf@ya%
        \advance\pgf@y by \pgfutil@tempdimb%
        %\immediate\write16{ childanchor \the\pgf@x \the\pgf@y}
      }
      \savedanchor\levelanchor{%
        \pgfmathsetlength{\pgf@x} {.5\wd\pgfnodeparttextbox}% get to center of text box
        \my@widthcode%
        \advance\pgf@x by-.33\pgfutil@tempdima % go to 1/6 of the total width
        \advance\pgf@x by -.5\wd\pgfnodepartlevelbox% get to left end of level box
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}% 
         \pgf@y=-2\pgf@y%  I start with the separations
        \advance\pgf@y by-\dp\pgfnodeparttextbox%
        \advance\pgf@y by-\pgflinewidth%
        \my@heightcode
        \advance\pgf@y by -\pgfutil@tempdima%
        \advance\pgf@y by \pgfutil@tempdimb%
     }
      \savedanchor\siblinganchor{%
        \pgfmathsetlength{\pgf@x}{.5\wd\pgfnodeparttextbox}% get to center of text box
        \my@widthcode%
        \advance\pgf@x by .33\pgfutil@tempdima % go to 5/6 of the total width
        \advance\pgf@x by -.5\wd\pgfnodepartsiblingbox% get to left end of sibling box
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}% 
         \pgf@y=-2\pgf@y%  I start with the separations
        \advance\pgf@y by-\dp\pgfnodeparttextbox%
        \advance\pgf@y by-\pgflinewidth%
        \my@heightcode
        \advance\pgf@y by -\pgfutil@tempdima%
        \advance\pgf@y by \pgfutil@tempdimb%
     }

      \savedanchor\southwest{%
        \pgf@x=.5\wd\pgfnodeparttextbox%
        \my@widthcode%
        \advance\pgf@x by-.5\pgfutil@tempdima%
        \advance\pgf@x by -.5\pgflinewidth%
        \advance\pgf@x by -\pgfkeysvalueof{/pgf/outer xsep}%
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \pgf@y=-3\pgf@y%
        \advance\pgf@y by-\dp\pgfnodeparttextbox%
        \my@heightcode%
        \advance\pgf@y by-\pgfutil@tempdima%
        \advance\pgf@y by-1.5\pgflinewidth%
        \advance\pgf@y by -\pgfkeysvalueof{/pgf/outer ysep}%
      } %
      \savedanchor\siblingsouthwest{%
        \pgf@x=.5\wd\pgfnodeparttextbox%
        \my@widthcode%
        \advance\pgf@x by .166\pgfutil@tempdima%
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \pgf@y=-3\pgf@y%
        \advance\pgf@y by-\dp\pgfnodeparttextbox%
        \my@heightcode%
        \advance\pgf@y by-\pgfutil@tempdima%
        \advance\pgf@y by-1.5\pgflinewidth%
        \advance\pgf@y by -\pgfkeysvalueof{/pgf/outer ysep}%
      } %
      \savedanchor\levelnortheast{%
        \pgf@x=.5\wd\pgfnodeparttextbox%
        \my@widthcode%
        \advance\pgf@x by-.167\pgfutil@tempdima%
        \advance\pgf@x by -.5\pgflinewidth%
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \pgf@y=-\pgf@y%
        \advance\pgf@y by-\dp\pgfnodeparttextbox%
        \advance\pgf@y by-.5\pgflinewidth%
      } %
      \savedanchor\textnortheast{%
        \pgf@x=.5\wd\pgfnodeparttextbox%
        \my@widthcode%
        \advance\pgf@x by .5\pgfutil@tempdima%
        \advance\pgf@x by \pgfkeysvalueof{/pgf/outer xsep}%
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \advance\pgf@y by \ht\pgfnodeparttextbox%
        \advance\pgf@y by .5\pgflinewidth%
        \advance\pgf@y by \pgfkeysvalueof{/pgf/outer ysep}%
      } %
      \savedanchor\textsoutheast{%
        \pgf@x=.5\wd\pgfnodeparttextbox%
        \my@widthcode%
        \advance\pgf@x by .5\pgfutil@tempdima%
        \advance\pgf@x by \pgfkeysvalueof{/pgf/outer xsep}%
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \pgf@y=-\pgf@y%
        \advance\pgf@y by -\dp\pgfnodeparttextbox%
        \advance\pgf@y by -.5\pgflinewidth%
      } %
      \savedanchor\totalsouthwest{%
        \pgfmathsetlength{\pgf@x}{\pgfkeysvalueof{/pgf/inner xsep}}%
        \pgf@x=-\pgf@x%
        \advance \pgf@x by .5\wd\pgfnodeparttextbox%
        \advance\pgf@x by -.5\wd\pgfnodeparttotalbox% get to left end of total box
        \advance\pgf@x by -.5\pgflinewidth%
        \advance\pgf@x by -\pgfkeysvalueof{/pgf/outer xsep}% 
        % 
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \advance\pgf@y by \ht\pgfnodeparttextbox%
        \advance\pgf@y by .5\pgflinewidth%
        \advance\pgf@y by \pgfkeysvalueof{/pgf/outer ysep}%
      } %
      \savedanchor\totalnortheast{%
        \pgfmathsetlength{\pgf@x}{\pgfkeysvalueof{/pgf/inner xsep}}%
        \advance\pgf@x by .5\wd\pgfnodeparttextbox%
        \advance\pgf@x by .5\wd\pgfnodeparttotalbox% get to right end of total box
        \advance\pgf@x by .5\pgflinewidth%
        \advance\pgf@x by \pgfkeysvalueof{/pgf/outer xsep}% 
        % 
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \pgf@y=3\pgf@y%
        \advance \pgf@y by \ht\pgfnodeparttextbox%
        % 
       \advance \pgf@y by \dp\pgfnodeparttotalbox%
       \advance \pgf@y by \ht\pgfnodeparttotalbox%
        \advance\pgf@y by 1.5\pgflinewidth%
        \advance\pgf@y by \pgfkeysvalueof{/pgf/outer ysep}%
      } %
      \savedanchor\northeast{%
        \pgf@x=.5\wd\pgfnodeparttextbox%
        \my@widthcode%
        \advance\pgf@x by .5\pgfutil@tempdima%
        \advance\pgf@x by \pgfkeysvalueof{/pgf/outer xsep}%
        \pgfmathsetlength{\pgf@y}{\pgfkeysvalueof{/pgf/inner ysep}}%
        \pgf@y=3\pgf@y% above text, above and below total
        \advance\pgf@y by \ht\pgfnodeparttextbox%
        \advance\pgf@y by \ht\pgfnodeparttotalbox%
        \advance\pgf@y by \dp\pgfnodeparttotalbox%
        \advance\pgf@y by .5\pgflinewidth%
        \advance\pgf@y by \pgfkeysvalueof{/pgf/outer ysep}%
      } %

      %
      % Anchors
      % 
      % Non-boundary
      \anchor{center}{\centerpoint}
      \anchor{mid}{\pgf@process{\centerpoint}\pgfmathsetlength\pgf@y{.5ex}}
      \anchor{base}{\pgf@process{\centerpoint}\pgf@y=0pt}
      % Boundary -- clockwise from southwest
      \anchor{south west}{\southwest}
      \anchor{text south west}{%
        \pgf@process{\levelnortheast}
        \pgf@ya = \pgf@y
        \pgf@process{\southwest}
        \pgf@y = \pgf@ya
        }
      \anchor{west}{
        \pgf@process{\textnortheast}%
        \pgf@ya=.5\pgf@y%
        \pgf@process{\textsoutheast}%
        \advance \pgf@ya by .5\pgf@y
        \pgf@process{\southwest}%
        \pgf@y= \pgf@ya%
      }
      \anchor{text north west}{%
        \pgf@process{\textnortheast}
        \pgf@ya = \pgf@y
        \pgf@process{\southwest}
        \pgf@y = \pgf@ya
        }
      \anchor{north west}{%
        \pgf@process{\totalnortheast}
        \pgf@ya = \pgf@y
        \pgf@process{\southwest}
        \pgf@y= \pgf@ya
        }
      \anchor{total north west}{%
        \pgf@process{\totalnortheast}
        \pgf@ya = \pgf@y
        \pgf@process{\totalsouthwest}
        \pgf@y = \pgf@ya
        }
      \anchor{total south west} {\totalsouthwest}
      \anchor{total south east} {%
        \pgf@process{\totalsouthwest}
        \pgf@ya = \pgf@y
        \pgf@process{\totalnortheast}
        \pgf@y = \pgf@ya
      }
       \anchor{north}{
        \pgf@process{\totalsouthwest}%
        \pgf@xa=.5\pgf@x%
        \pgf@process{\totalnortheast}%
        \pgf@x=.5\pgf@x%
        \advance\pgf@x by \pgf@xa%
      }
      \anchor{total north east} {\totalnortheast}
      \anchor{north east}{\northeast}%
      \anchor{text north east}{\textnortheast}%
      \anchor{east}{%
        \pgf@process{\textnortheast}%
        \pgf@ya=.5\pgf@y%
        \pgf@process{\textsoutheast}%
        \pgf@y=.5\pgf@y%
        \advance\pgf@y by \pgf@ya%
      }
      \anchor{text south east} {\textsoutheast}
      \anchor{south east}{
        \pgf@process{\textsoutheast}
        \pgf@xa=\pgf@x
        \southwest
        \pgf@x=\pgf@xa
      }
      \anchor{sibling south west} {\siblingsouthwest}
      \anchor{south}{
        \pgf@process{\textsoutheast}%
        \pgf@xa=.5\pgf@x%
        \pgf@process{\southwest}%
        \pgf@x=.5\pgf@x%
        \advance\pgf@x by \pgf@xa%
      }
      \anchor{south}{
        \pgf@process{\textsoutheast}%
        \pgf@xa=.5\pgf@x%
        \pgf@process{\southwest}%
        \pgf@x=.5\pgf@x%
        \advance\pgf@x by \pgf@xa%
      }
      \anchor{level south east}{
        \pgf@process{\levelnortheast}
        \pgf@xa=\pgf@x
        \southwest
        \pgf@x=\pgf@xa
      }
      \anchor{level north east}{\levelnortheast}

      \anchor{total west}{%
        \pgf@process{\totalanchor}%
        \pgf@ya = \pgf@y%
        \pgf@process{\totalsouthwest}%
        \pgf@y=\pgf@ya}
      \anchor{total east}{%
        \pgf@process{\totalanchor}%
        \pgf@ya = \pgf@y%
        \pgf@process{\totalnortheast}%
        \pgf@y=\pgf@ya}
      \anchor{base west}{\southwest\pgf@y=0pt}
      \anchor{base east}{\northeast\pgf@y=0pt}
      \anchor{lower west}{%
        \pgf@process{\levelanchor}%
        \pgf@ya = \pgf@y%
        \pgf@process{\southwest}%
        \pgf@y=\pgf@ya}
      \anchor{lower east}{%
        \pgf@process{\levelanchor}%
        \pgf@ya = \pgf@y%
        \pgf@process{\northeast}%
        \pgf@y=\pgf@ya}
      \anchor{center}{\centerpoint}
      \anchor{total}{\totalanchor}
      \anchor{child}{\childanchor}
      \anchor{level}{\levelanchor}
      \anchor{sibling}{\siblinganchor}
      \anchorborder{%
        \pgf@xb=\pgf@x% xb/yb is target
        \pgf@yb=\pgf@y%
        \southwest%
        \pgf@xa=\pgf@x% xa/ya is se
        \pgf@ya=\pgf@y%
        \northeast%
        \advance\pgf@x by-\pgf@xa%
        \advance\pgf@y by-\pgf@ya%
        \pgf@xc=.5\pgf@x% x/y is half width/height
        \pgf@yc=.5\pgf@y%
        \advance\pgf@xa by\pgf@xc% xa/ya becomes center
        \advance\pgf@ya by\pgf@yc%
        \edef\pgf@marshal{%
          \noexpand\pgfpointborderrectangle
          {\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}
          {\noexpand\pgfqpoint{\the\pgf@xc}{\the\pgf@yc}}%
        }%
        \pgf@process{\pgf@marshal}%
        \advance\pgf@x by\pgf@xa%
        \advance\pgf@y by\pgf@ya%
      }

      %
      % Background path
      %
      \backgroundpath{
         \pgfpathrectanglecorners
        {\pgfpointadd{\southwest}{\pgfpoint{\pgfkeysvalueof{/pgf/outer xsep}}{\pgfkeysvalueof{/pgf/outer ysep}}}}
        {\pgfpointadd{\textnortheast}{\pgfpointscale{-1}{\pgfpoint{\pgfkeysvalueof{/pgf/outer xsep}}{\pgfkeysvalueof{/pgf/outer ysep}}}}}
        \pgfmathsetlength{\pgf@yc}{\pgfkeysvalueof{/pgf/outer ysep}}
        \pgf@yc = -\pgf@yc
        \pgfpathrectanglecorners
        {\pgfpointadd{\totalnortheast}{\pgfpointscale{-1}{\pgfpoint{\pgfkeysvalueof{/pgf/outer xsep}}{\pgfkeysvalueof{/pgf/outer ysep}}}}}
        {\pgfpointadd{\totalsouthwest}{\pgfpoint{\pgfkeysvalueof{/pgf/outer xsep}}{\pgf@yc}}}
        \pgfpathrectanglecorners%
         {\pgfpointadd{\southwest}{\pgfpoint{\pgfkeysvalueof{/pgf/outer xsep}}{\pgfkeysvalueof{/pgf/outer ysep}}}}
         {\pgfpointadd{\textsoutheast}%
                              {\pgfpointscale{-1}{\pgfpoint{\pgfkeysvalueof{/pgf/outer xsep}}{0 pt}}}}
      \pgfpathrectanglecorners%
      {\pgfpointadd{\southwest}{\pgfpoint{\pgfkeysvalueof{/pgf/outer xsep}}{\pgfkeysvalueof{/pgf/outer ysep}}}}
      {\levelnortheast}
      \pgfpathrectanglecorners%
         {\pgfpointadd{\siblingsouthwest}%
                              {\pgfpoint{0 pt}{\pgfkeysvalueof{/pgf/outer ysep}}}}
         {\pgfpointadd{\textsoutheast}%
                              {\pgfpointscale{-1}{\pgfpoint{\pgfkeysvalueof{/pgf/outer xsep}}{0 pt}}}}
    %  \pgfusepath{stroke,fill}
      }
      \inheritanchorborder[from=rectangle]
    }
    \makeatother

    \begin{document}

    \begin{tikzpicture}
    \Huge
    \node[name=s,shape=variety, draw=black!10 ,fill=yellow!20, line width=4pt] {Variety\vrule width 1pt height 2cm
    \nodepart{total}Total\nodepart{level}Level\nodepart{child}Child\nodepart{sibling}Sibling}; 
    \foreach \anchor/\placement in
        {north west/above left, north/above, north east/above right,
         west/left, center/above, east/right,
         text south west/left, text north west/left, total north west/above left,
         total south west/left, total south east/right,
          total north east/above right,
         total west/left, total east/right,
         text north east/right, text south east/right,
         sibling south west/below right, level south east/below left,
         total/below right, child/below, level/below, sibling/below,
         base/below, mid/above, base west/left, base east/right,
          south west/below left, south/below, south east/below right, text/left,
          lower west/left, lower east/right,
          level north east/right%
    }
    \draw[shift=(s.\anchor)] plot[mark=x] coordinates{(0,0)}
           node[\placement] {\scriptsize\texttt{(s.\anchor)}};
    \end{tikzpicture}
    \end{document}

OUTPUT Shape Demonstration

  • 2
    I think overall your code looks good except that you may want to be a bit more careful in avoiding stray spaces. – user194703 Apr 22 at 18:20
  • @Schrödinger's cat, what is the definition of 'stray spaces'? Should I never include spaces in my code? Or only include spaces when I need them to end a macro name? I'm used to adding whitespace for readability when I program, and I have learned that it sometime causes problems in tex programming. So some guidance would be helpful to me. – Carl Sorensen Apr 22 at 18:27
  • 3
    What I mean is that if one compares your code to what one finds in the libraries, you leave spaces e.g. after by and sometimes miss a % at the end of the line as in \pgf@process{\levelnortheast}. Overall you avoid these spaces to a very large extent. – user194703 Apr 22 at 18:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.