2

I'm working on a package and I want to draw constructions similar to the following:

wanted --- not default

This features 2 nodes. The small, circular node is located at some anchor on the border of the rectangular node, adjusted so that its centre aligns with the middle of the total height or width (as applicable) of the line outlining the rectangular one. In this particular case, the circular node is placed at the north anchor of the rectangular one, adjusted as described.

If I do the obvious thing and place the circular node at the rectangular node's appropriate anchor,

  \node (x) [text node] at (0,1) {Hello World};
  \node [circ node] at (x.north) {};

I get

default --- not wanted

which is not what I want.

I could force outer sep=0pt for the rectangular node, as suggested by Andrew Swann. However, I also need to draw connections between nodes and I want people to be able to draw additional connections in standard ways. Hence, this isn't really an option.

I can obtain the result I want by using instead

  \node (t) [text node] {Hello World};
  \pgflinewidthsaved=\pgflinewidth
  \pgflinewidth=0pt
  \node (p) [text node, phantom] at (t.center) {\phantom{Hello World}};
  \node (c) [circ node] at (p.north) {};
  \pgflinewidth=\pgflinewidthsaved

where the phantom style is defined as follows

    phantom/.style={line width=0pt, fill=none, draw=none, anchor=center},

In order to ensure a match, I have to apply the main node's style to the phantom one and then override relevant settings in the phantom one. However, this is very fragile and a bit tricky. I need, for example, to eliminate any xshift, yshift, shift etc. in order for it to stand a chance and this can't be done by just setting these to zero, since such things are cumulative.

Is there a better way to achieve this kind of construct? Is it possible, for example, to create a modified version of the rectangle shape which would have additional anchors which take account of the line width? I looked at the code for rectangle but I don't know how this gets compiled with the line width or where this adjustment takes place. (I think the shape is stored more-or-less statically except for deferred, border anchors etc., so that this would not be possible, but I'm not certain.) Or is it possible to do this in any other relatively sane and more streamlined way?

Note that the code I'm giving here is greatly simplified. In the real case, I do not know at which of the rectangular node's anchors the circular one should be placed. Nor do I know the width of the line outlining the rectangular node or whether any line outlines it at all. That is, line width could have any value for the rectangular node, including 0pt. So I can't just shift the circular node a bit as I don't know by how much or in which direction to shift it. Nor do I know the size of the rectangular node or whether it is placed relative to something else. (I do know it is placed with the \node ... at (...) ... syntax.) I do not know whether any transformations have been applied to the node, such as xshift, rotate or whatever.

However, I'm prepared to prohibit use of rotate, if necessary, and I'm certainly not expecting this to work with anything strange, such as a canvas transformation or a switch of coordinate system. But I do need it to work with things like xshift. Right now, I'm just trying to drop these, but I'm not sure I have a good strategy for doing that. (That is, I have a strategy, but it assumes people will read the documentation, which I'd rather it didn't.)

MWE

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\newdimen\pgflinewidthsaved
\begin{document}
\begin{tikzpicture}
  [
    opacity=.5,
    text node/.style={draw, line width=2pt, anchor=north},
    phantom/.style={line width=0pt, fill=none, draw=none, anchor=center},
    circ node/.style={fill, anchor=center, circle, minimum size=4pt, inner sep=0pt},
  ]
  \node (t) [text node] {Hello World};
  \pgflinewidthsaved=\pgflinewidth
  \pgflinewidth=0pt
  \node (p) [text node, phantom] at (t.center) {\phantom{Hello World}};
  \node (c) [circ node] at (p.north) {};
  \pgflinewidth=\pgflinewidthsaved
  \node (x) [text node] at (0,1) {Hello World};
  \node [circ node] at (x.north) {};
  \node [font=\itshape, anchor=mid west] at (x.mid east) {default --- not wanted};
  \node [font=\itshape, anchor=mid west] at (t.mid east) {wanted --- not default};
\end{tikzpicture}
\end{document}
  • if you set outer sep=0pt in text node style, then default position of circle is as you like to have. – Zarko Aug 11 '17 at 0:20
  • @Zarko Yes, I know. That's Andrew Swann's suggestion, which I referenced above. But it is problematic when the nodes are connected, as mentioned by both Andrew and above by me. – cfr Aug 11 '17 at 0:21
  • 2
    Sašo Živanović already designed a new shape for a rectangle with rounded corners and anchors over its real border. May be you can start from there to add new anchors to a rectangle or define a new rectangle shape with old and new anchors. – Ignasi Aug 11 '17 at 8:15
  • "[...] but it assumes people will read the documentation [...]" made me laugh :) – Dr. Manuel Kuehner Aug 11 '17 at 12:21
  • @Ignasi Thanks. My problem is that I couldn't figure out a way to have the border anchors take account of the line width. That is, I need to move them half the width of the line width in, but every time I test the line width in the anchor code for the shape, I get either an undefined error or a width of zero. Should I instead be trying to use outer sep? – cfr Aug 11 '17 at 15:12
2

The following code defines a series of anchors called middle north east, middle south west, etc.

These anchors work independently of the original anchors. You can use them at the same time. PGF transformations and canvas transformations work as usual.

However, there could be only one \pgfpointanchorborder. So you have to use \tikzset{middle anchorborder=true/false} to turn it on/off.

\documentclass[border=9pt,tikz]{standalone}

\begin{document}

\makeatletter
\def\pgf@sm@shape@name{rectangle}
\pgf@sh@savedanchor\middlenortheast{%
    \pgf@x=\the\wd\pgfnodeparttextbox%
    \pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/inner xsep}}\advance\pgf@x by 2\pgf@xc%
    \pgfmathsetlength\pgf@xb{\pgfkeysvalueof{/pgf/minimum width}}\ifdim\pgf@x<\pgf@xb\pgf@x=\pgf@xb\fi%
    \pgf@x=.5\pgf@x\advance\pgf@x by.5\wd\pgfnodeparttextbox%
    \pgf@y=\ht\pgfnodeparttextbox\advance\pgf@y by\dp\pgfnodeparttextbox%
    \pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/inner ysep}}\advance\pgf@y by 2\pgf@yc%
    \pgfmathsetlength\pgf@yb{\pgfkeysvalueof{/pgf/minimum height}}\ifdim\pgf@y<\pgf@yb\pgf@y=\pgf@yb\fi%
    \pgf@y=.5\pgf@y\advance\pgf@y by-.5\dp\pgfnodeparttextbox\advance\pgf@y by.5\ht\pgfnodeparttextbox%
}
\pgf@sh@savedanchor\middlesouthwest{%
    \pgf@x=\wd\pgfnodeparttextbox%
    \pgfmathsetlength\pgf@xc{\pgfkeysvalueof{/pgf/inner xsep}}\advance\pgf@x by 2\pgf@xc%
    \pgfmathsetlength\pgf@xb{\pgfkeysvalueof{/pgf/minimum width}}\ifdim\pgf@x<\pgf@xb\pgf@x=\pgf@xb\fi%
    \pgf@x=-.5\pgf@x\advance\pgf@x by.5\wd\pgfnodeparttextbox%
    \pgf@y=\ht\pgfnodeparttextbox\advance\pgf@y by\dp\pgfnodeparttextbox%
    \pgfmathsetlength\pgf@yc{\pgfkeysvalueof{/pgf/inner ysep}}\advance\pgf@y by 2\pgf@yc%
    \pgfmathsetlength\pgf@yb{\pgfkeysvalueof{/pgf/minimum height}}\ifdim\pgf@y<\pgf@yb\pgf@y=\pgf@yb\fi%
    \pgf@y=-.5\pgf@y\advance\pgf@y by-.5\dp\pgfnodeparttextbox\advance\pgf@y by.5\ht\pgfnodeparttextbox%
}
\pgf@sh@anchor{middle north east}{\middlenortheast}
\pgf@sh@anchor{middle south west}{\middlesouthwest}
\pgf@sh@anchor{middle south east}{\middlenortheast\pgf@xa=\pgf@x\middlesouthwest\pgf@x=\pgf@xa}
\pgf@sh@anchor{middle north west}{\middlesouthwest\pgf@xa=\pgf@x\middlenortheast\pgf@x=\pgf@xa}
\pgf@sh@anchor{middle north}{
    \pgf@process{\middlesouthwest}\pgf@xa=.5\pgf@x\pgf@process{\middlenortheast}\pgf@x=.5\pgf@x\advance\pgf@x by \pgf@xa
}
\pgf@sh@anchor{middle south}{
    \pgf@process{\middlenortheast}\pgf@xa=.5\pgf@x\pgf@process{\middlesouthwest}\pgf@x=.5\pgf@x\advance\pgf@x by \pgf@xa
}
\pgf@sh@anchor{middle west}{
    \pgf@process{\middlenortheast}\pgf@ya=.5\pgf@y\pgf@process{\middlesouthwest}\pgf@y=.5\pgf@y\advance\pgf@y by \pgf@ya
}
\pgf@sh@anchor{middle east}{%
    \pgf@process{\middlesouthwest}\pgf@ya=.5\pgf@y\pgf@process{\middlenortheast}\pgf@y=.5\pgf@y\advance\pgf@y by \pgf@ya
}

\newif\ifmiddleanchorborder
\tikzset{middle anchorborder/.is if=middleanchorborder}
\pgf@sh@anchorborder{
    \pgf@xb=\pgf@x\pgf@yb=\pgf@y\ifmiddleanchorborder\middlesouthwest\else\southwest\fi%
    \pgf@xa=\pgf@x\pgf@ya=\pgf@y\ifmiddleanchorborder\middlenortheast\else\northeast\fi%
    \advance\pgf@x by-\pgf@xa\advance\pgf@y by-\pgf@ya\pgf@xc=.5\pgf@x\pgf@yc=.5\pgf@y%
    \advance\pgf@xa by\pgf@xc\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%
}

\tikz[middle anchorborder]{
    \node[draw,line width=4,opacity=.5,rotate=30](s){\Huge Bye Bye};
    \foreach\anchor in{%
        middle north west,%
        middle north,%
        middle north east,%
        middle west,%
        middle east,%
        middle south west,%
        middle south,%
        middle south east,%
        130
    }{
        \draw[shift=(s.\anchor)]plot[mark=x]coordinates{(0,0)}node[right]{\tiny\texttt{(s.\anchor)}};
    }
}

\end{document}

  • Wow! This is fantastic. Could you possibly explain what is doing the work? I think I understand pretty much what the code does considered in isolation (though maybe not and that's the problem), but how does this correspond to the value of the line width? – cfr Aug 11 '17 at 21:40
  • It's somehow using information about the inner sep, but I thought that was inside, so to speak. – cfr Aug 11 '17 at 21:44
  • I didn't know that you could add to the shape code like this, either. I thought I needed to declare a new shape (inheriting stuff, maybe, but with a complete definition). – cfr Aug 11 '17 at 21:46
  • 1
    @cfr You do not have to understand the code. The code in the \pgf@sh@savedanchor\middlenortheast part is copied from pgfmoduleshapes.code.tex line 897, but two lines related to the outer sep are dropped. The result is that \middlenortheast will work exactly the same as \northeast as if outer sep=0pt. – Symbol 1 Aug 11 '17 at 23:35
  • 1
    Sure it is totally OK. (I think a much simpler way is \saveddimen\outerxsep{\pgfmathsetlength\pgf@x{\pgfkeysvalueof{/pgf/outer xsep}}}. Never mind.) – Symbol 1 Aug 12 '17 at 0:02

Your Answer

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.