[Tex/LaTex] Tikz: adjusting pin anchor

pgfkeystikz-pgftikz-styles

I was looking for a possibility to adjust the anchor of the text of a pin. Tikz does not allow this out of the box.

This question was already asked on the mailing list pgf-users@lists.sourceforge.net, Sept. 2009 without any reply till today or feature addition to pgf/tikz:

\documentclass[tikz]{standalone} 
\begin{document} 
    \begin{tikzpicture}[ 
        every pin/.style={anchor=west}, 
        every pin edge/.style={anchor=west}] 
        \node [pin=60:test] at (0,1) {}; 
        \node [pin={[anchor=west]60:test}] at (0,0) {}; 
    \end{tikzpicture} 
\end{document}

There is also a question with a suggested workaround here on tex.stackexchange.com by defining an aligned pin style consisting of another node positioned at the end of the pin using \tikzlastnode:

\tikzset{
    aligned pin/.style args={[#1,#2]#3:#4}{
        pin={[inner sep=0pt,%
              pin distance=#2,% new option, default = 3ex
              label={[append after command={%
                    node[inner sep=0pt,%
                        xshift=-0.1cm,% modified, default = 0
                        at=(\tikzlastnode.#3),%
                        anchor=#1,%
                    ]{#4}%
                }%
            ]center:{}}%
        ]#3:{}}%
    }
}

In this code, I have added a second unnamed option for pin distance within the square brackets.

MWE using the workaround showing the desired result:

\documentclass[tikz]{standalone} 
\tikzset{
    aligned pin/.style args={[#1,#2]#3:#4}{
        pin={[inner sep=0pt,%
            pin distance=#2,% new option, default = 3ex
            label={[append after command={%
                    node[%
                        inner sep=0pt,%
                        xshift=-0.1cm,% modified, default = 0
                        at=(\tikzlastnode.#3),%
                        anchor=#1,%
                    ]{#4}%
                }%
            ]center:{}}%
        ]#3:{}}%
    }
}
\begin{document}
\tikz[pin distance=15mm, every pin/.style={fill=blue,text=white}]
\node [circle,draw,
    aligned pin={[west,3ex]0:X-Value},%
    aligned pin={[west,3ex]45:Y-Value},%
    aligned pin={[west,3ex]90:Z-Value}] {my circle};
\end{document}

enter image description here

I would like to add further options such as pin edge to adjust the style of the edge. Another addition would be xshift to finetune the position of the text (and yshift).

Is it possible to change the workaround to allow named options with default values if not specified?

Usage could look like this (without using necessarily all options):

node[aligned pin={[pin anchor=west,pin distance=1cm,pin edge={thick}]90:{txt}}]{};

If someone is interested in the implementation of the pin feature in tikz see pgf/base/tex/generic/frontendlayer/tikz.code.tex and do a textsearch for pin.

Update after answer by Qrrbrbirlbel:

To achieve almost the same result as my MWE use following code

% use preamble from Qrrbrbirlbel answer
\begin{document} 
\begin{tikzpicture}[every pin/.append style=draw, every pin edge/.style={thick}]
\node[c] [
pin={[pin anchor=south west,pin edge pin anchor=south west,pin distance=6ex]90:Z-Value},
pin={[pin anchor=west,pin edge pin anchor=west,pin distance=6ex]45:Y-Value},
pin={[pin anchor=west,pin edge pin anchor=west,pin distance=6ex]0:X-Value}
] at (0,0) {};
\end{tikzpicture} 
\end{document}

enter image description here

However, I would like to finetune the text positioning using inner sep=0 and a slight shift:

\newlength{\xshiftlength}
\setlength{\xshiftlength}{0.5\widthof{Z}}

Following works:

\tikzset{
  pin inner xsep/.style={tikz@pin@post/.append style={inner xsep=#1}}
}

Apparently xshift cannot be added in the same way. It shifts more than only the text. How could a pin text xshift be added?

Best Answer

When TikZ places a label (or a pin) it calculates a point that is label distance (or pin distance) away from a point on the anchor-border of its parent node.

This point on the anchor-border is directly dependent on the angle/direction before the : (or the default label position/pin position). In the same direction the * distance is added.

This is the new point where the label/pin is placed. (In other words: all the options like direction/angle and distance have an influence on the newly placed node, not the pin edge.)

The new node is placed at a specific anchor which is calculated internally by TikZ (and snaps to the eight main compass directions and in one special case to the .center). This is (one of) the cause for the problems in other questions:

The pin edge (with all its options and defaults and styles) is done in an append after command (the label/pin placement itself is a append after command which \tikzlastnode has been saved in \tikz@save@last@node so that the inner append after command can actually access both nodes).

The default pin edge is a line to path (= (<parent node>) -- (<pin node>)). This is drawn with no regard to any anchors calculated or placed at. The -- line is drawn directly between the center of nodes stopping at the nodes’ borders.


The label and pin keys aren't really made for precise placement. (Don’t ask my why.)

However, with a few keys we could get what you want (or at least I assume this is what you want):

  • The label anchor key is taken from my other answer to the first linked question above.
  • The pin anchor is defined similar.
  • The pin edge pin anchor key can be used to change the anchor at which the pin edge should end (at the pin).
  • The pin edge parent anchor key can be used to change the anchor at which the pin edge should start (at the parent node).

The latter keys can also be given an empty value which resets the anchor to “automatically” (as TikZ sees fit).


I have added some auxiliary lines to the drawings that should help understand what I have said before. The arc labelled “60°” shows where the angle 60 is used and the double-arrowed line angled at 60° shows where the pin distance (3ex per default) is measured. The bottom-left start of this line points to (<parent node>.60) and the top-right end of this line points to the point that pin distance away from the start and where the pin will be placed with its (automatically determined or via pin anchor set) anchor.

The second double-arrowed line shows how TikZ connects the the nodes (via the line to edge). In the first two cases (without any pin edge * anchor set) it connects the nodes on a straight line between their centers. In the third case it uses the specified south west anchor to draw the line to. In the fourth, the additional north anchor to start the line from.

Code

\documentclass[tikz]{standalone}
\makeatletter
\newcommand*\ifStrInTF[2]{%
  \edef\tikz@temp{{#1}{#2}}%
  \expandafter\pgfutil@in@\tikz@temp
  \ifpgfutil@in@\expandafter\pgfutil@firstoftwo\else\expandafter\pgfutil@secondoftwo\fi}
\newcommand*\ifStrEmptyTF[1]{%
  \def\tikz@temp{#1}\ifx\tikz@temp\pgfutil@empty
    \expandafter\pgfutil@firstoftwo\else\expandafter\pgfutil@secondoftwo\fi}

\def\tikz@swapanchor#1.#2\tikz@stop#3#4{#1#4#3}
\newcommand*\tikzAddAnchor[2]{%
  \ifStrInTF{.}{#1}{%
    \ifStrEmptyTF{#2}
      {\edef#1{\expandafter\tikz@swapanchor#1\tikz@stop{}{}}}
      {\edef#1{\expandafter\tikz@swapanchor#1\tikz@stop{#2}{.}}}%
  }{%
    \ifStrEmptyTF{#2}{}% no true
      {\edef#1{#1.#2}}%
  }}
\tikzset{
  pin anchor/.style={tikz@pin@post/.append style={anchor=#1}},
  label anchor/.style={tikz@label@post/.append style={anchor=#1}},
  pin edge pin anchor/.style={
    append after command={\pgfextra\tikzAddAnchor{\tikzlastnode}{#1}\endpgfextra}%
  },
  pin edge parent anchor/.style={
    append after command={\pgfextra\tikzAddAnchor{\tikz@save@last@node}{#1}\endpgfextra}%
  }
}
\makeatother
\tikzset{c/.style={circle, minimum size=1cm, draw, densely dotted}}
\begin{document} 
\begin{tikzpicture}[every pin/.append style=draw, every pin edge/.style={thick}]
  \node [c] [pin=60:test] at (0,0) {};

  \node[c] [pin={[pin anchor=west]60:test}] at (2,0) {};

  \node[c] [pin={[pin anchor=south,
                  pin edge pin anchor=south west]60:test}] at (4,0) {}; 

  \node[c] [pin={[pin anchor=south,
                  pin edge pin anchor=south west,
                  pin edge parent anchor=north]60:test}] at (6,0) {}; 

\end{tikzpicture} 
\end{document}

Output

enter image description here