7

I'd like to define a new node (or maybe a new shape) with many anchor points. In particular I need of a rectangular shape with up to 15 anchor points on the top and the bottom of the rectangle. That's because I'd like to draw something like this easily: Two nodes

The red dots are just examples of the position of the possible anchor points, they should not appear in the definitive picture. If it is possible, all the edges should come out perpendicular to the node (but I guess this must be added as an option in the edges).

\node[mynode,minimum size=5mm] (A1) at (0,0) {Some Text};
\node[mynode,minimum size=5mm,rotate=-45] (A2) at (-1,-1) {};
\draw (A1.t4) -- (A2.b1);
%OR
%\path (A1.t4) edge [out=90,in=90] (A2.b1);

Where t4 and b1 should represent the anchor points (If I understood the syntax correctly).

This is because I'd like to draw pictures of generalized trace diagrams like those in http://elishapeterson.wikidot.com/tikz:diagrams

I found already something similar, but I cannot modify it to make it work for me, e.g.: https://tex.stackexchange.com/a/75515/58947 https://tex.stackexchange.com/a/33156/58947

I thought the new rectangular node could inherit all the structure of a standard rectangle but adding 15 anchor points e.g. from t1 to t15 for the top and b1 to b15 for the bottom. But any suggestion is welcome!! The dream would be that the edges automatically connect to the closer anchor point. Or, even better, the distance between two anchor points change depending on the number of edges connected to the node (namely, if there are only 6 edges connected to the top of the node, they would be separated by l/7 where l is the length of the long side of the rectangle)

7

The following example defines a new shape rectangle16. It inherits the anchors from the shape rectangle and adds anchors t0 (= north west) to t16 (= north east) and b0 (= south west) to b16 (= south east).

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{topaths}
\usetikzlibrary{calc}
\usetikzlibrary{hobby}

\makeatletter
\newcommand*{\define@anchor@t}[2]{%
  \anchor{t#1}{%
    \pgf@process{\southwest}%
    \pgf@xa=\pgf@x
    \pgf@process{\northeast}%
    \pgf@x=\dimexpr\pgf@xa + (\pgf@x-\pgf@xa)*#1/#2\relax
  }%
}
\newcommand*{\define@anchor@b}[2]{%
  \anchor{b#1}{%
    \pgf@process{\northeast}%
    \pgf@xa=\pgf@x
    \pgf@process{\southwest}%
    \pgf@x=\dimexpr\pgf@x + (\pgf@xa-\pgf@x)*#1/#2\relax
  }%
}
\pgfdeclareshape{rectangle16}{%
  \inheritsavedanchors[from=rectangle]
  \inheritanchorborder[from=rectangle]
  \inheritanchor[from=rectangle]{north}
  \inheritanchor[from=rectangle]{north west}
  \inheritanchor[from=rectangle]{center}
  \inheritanchor[from=rectangle]{west}
  \inheritanchor[from=rectangle]{east}
  \inheritanchor[from=rectangle]{mid}
  \inheritanchor[from=rectangle]{mid west}
  \inheritanchor[from=rectangle]{mid east}
  \inheritanchor[from=rectangle]{base}
  \inheritanchor[from=rectangle]{base west}
  \inheritanchor[from=rectangle]{base east}
  \inheritanchor[from=rectangle]{south}
  \inheritanchor[from=rectangle]{south east}
  \inheritbackgroundpath[from=rectangle]
  \count@=0 %
  \@whilenum\count@<17 \do{%
    \expandafter\define@anchor@t\expandafter{\the\count@}{16}%
    \expandafter\define@anchor@b\expandafter{\the\count@}{16}%
    \advance\count@\@ne
  }%
}
\makeatother

\begin{document}
  \begin{tikzpicture}
    \node[draw,rectangle16] (N)
      {Rectangular node with additional anchors at the top and bottom};
    \foreach \i in {0, ..., 16} {
      \draw[<-, node font=\footnotesize]
        (N.t\i) -- ++(0, .5) node[above] {t\i}
      ;
      \draw[<-, node font=\footnotesize]
        (N.b\i) -- ++(0, -.5) node[below] {b\i}
      ;
    }
    \node[draw, rectangle16] (ST) at (N.center |- 0, -3) {Some Text};
    \draw
      \foreach \i in {1, ..., 14} { (ST.t\i) -- ++(0, .5) }
      (ST.south east) ++(-2, -1)
      node[
        rectangle16,
        draw,
        rotate=-45,
        minimum width=15mm,
        minimum height=5mm,
      ] (R) {}
      (ST.b1) to (R.t4)
    ;
    \draw[use Hobby shortcut]
      (ST.t15)
      -- ([out angle=90]$(ST.t15) + (0, .1)$)
      .. ($(ST.east) + (.5, 0)$)
      .. ([in angle=-90]$(ST.b15) + (0, -.1)$)
      -- (ST.b15)
    ;
    \fill[
      red,
      radius=.5pt,
    ]
      \foreach \i in {0, ..., 16} {
        \foreach \tb in {t, b} {
          (R.\tb\i) circle[]
        }
      }
    ;
  \end{tikzpicture}
\end{document}

Result

Generalization

The following example defines \declareshaperectxy{<h>}{<v>} with two arguments for the horizontal and vertical number of anchors for the four sides of a rectangular shape and defines the shape with the name rectangle <h>x<v>. For example, after \declareshaperectxy{16}{8} the shape rectangle 16x8 can be used, which provides the additional anchors t0 to t16 at the top, b0 to b16 at the bottom, l0 to l8 at the left side and r0 to r8 at the right side.

\documentclass{article}
\usepackage{tikz}

\makeatletter
\newcommand*{\rectxy@anchor@top}[2]{%
  \anchor{t#1}{%
    \pgf@process{\southwest}%
    \pgf@xa=\pgf@x
    \pgf@process{\northeast}%
    \pgf@x=\dimexpr\pgf@xa + (\pgf@x-\pgf@xa)*#1/#2\relax
  }%
}
\newcommand*{\rectxy@anchor@bottom}[2]{%
  \anchor{b#1}{%
    \pgf@process{\northeast}%
    \pgf@xa=\pgf@x
    \pgf@process{\southwest}%
    \pgf@x=\dimexpr\pgf@x + (\pgf@xa-\pgf@x)*#1/#2\relax
  }%
}
\newcommand*{\rectxy@anchor@left}[2]{%
  \anchor{l#1}{%
    \pgf@process{\northeast}%
    \pgf@ya=\pgf@y
    \pgf@process{\southwest}%
    \pgf@y=\dimexpr\pgf@y + (\pgf@ya-\pgf@y)*#1/#2\relax
  }%
}
\newcommand*{\rectxy@anchor@right}[2]{%
  \anchor{r#1}{%
    \pgf@process{\southwest}%
    \pgf@ya=\pgf@y
    \pgf@process{\northeast}%
    \pgf@y=\dimexpr\pgf@ya + (\pgf@y-\pgf@ya)*#1/#2\relax
  }%
}
\newcommand*{\declareshaperectxy}[2]{%
  \pgfdeclareshape{rectangle #1x#2}{%
    \inheritsavedanchors[from=rectangle]
    \inheritanchorborder[from=rectangle]
    \inheritanchor[from=rectangle]{north}
    \inheritanchor[from=rectangle]{north west}
    \inheritanchor[from=rectangle]{center}
    \inheritanchor[from=rectangle]{west}
    \inheritanchor[from=rectangle]{east}
    \inheritanchor[from=rectangle]{mid}
    \inheritanchor[from=rectangle]{mid west}
    \inheritanchor[from=rectangle]{mid east}
    \inheritanchor[from=rectangle]{base}
    \inheritanchor[from=rectangle]{base west}
    \inheritanchor[from=rectangle]{base east}
    \inheritanchor[from=rectangle]{south}
    \inheritanchor[from=rectangle]{south east}
    \inheritbackgroundpath[from=rectangle]
    \count@=\m@ne
    \@whilenum\count@<#1 \do{%
      \advance\count@\@ne
      \expandafter\rectxy@anchor@top\expandafter{\the\count@}{#1}%
      \expandafter\rectxy@anchor@bottom\expandafter{\the\count@}{#1}%
    }%
    \count@=\m@ne
    \@whilenum\count@<#2 \do{%
      \advance\count@\@ne
      \expandafter\rectxy@anchor@left\expandafter{\the\count@}{#2}%
      \expandafter\rectxy@anchor@right\expandafter{\the\count@}{#2}%
    }%
  }%
}
\makeatother

\declareshaperectxy{16}{8}
\declareshaperectxy{5}{3}

\begin{document}
  \begin{tikzpicture}
    \node[
      draw,
      rectangle 16x8,
      minimum width=80mm,
      minimum height=30mm,
    ] (N) {};
    \foreach \i in {0, ..., 16} {
      \draw[<-, node font=\footnotesize]
        (N.t\i) -- ++(0, .5) node[above] {t\i}
      ;
      \draw[<-, node font=\footnotesize]
        (N.b\i) -- ++(0, -.5) node[below] {b\i}
      ;
    }
    \foreach \i in {0, ..., 8} {
      \draw[<-, node font=\footnotesize]
        (N.l\i) -- ++(-.5, 0) node[left] {l\i}
      ;
      \draw[<-, node font=\footnotesize]
        (N.r\i) -- ++(.5, 0) node[right] {r\i}
      ;
    }
    \node[
      draw,
      rectangle 5x3,
      minimum width=80mm,
      minimum height=30mm,
      at={(0, -55mm)},
    ] (N) {};
    \foreach \i in {0, ..., 5} {
      \draw[<-, node font=\footnotesize]
        (N.t\i) -- ++(0, .5) node[above] {t\i}
      ;
      \draw[<-, node font=\footnotesize]
        (N.b\i) -- ++(0, -.5) node[below] {b\i}
      ;
    }
    \foreach \i in {0, ..., 3} {
      \draw[<-, node font=\footnotesize]
        (N.l\i) -- ++(-.5, 0) node[left] {l\i}
      ;
      \draw[<-, node font=\footnotesize]
        (N.r\i) -- ++(.5, 0) node[right] {r\i}
      ;
    }
  \end{tikzpicture}
\end{document}

Result generalization

  • A suppose, that in similar way ones can determine more (beside west and east) anchors on west and east size of node. What should I change in your example for this? – Zarko May 31 '15 at 13:23
  • @Zarko See updated answer. – Heiko Oberdiek May 31 '15 at 15:33
  • thank you! This solves some my problems (where i do not need so many anchors, but more than they are defined in original shape). – Zarko May 31 '15 at 17:17
4

Here is a generic solution with the use of generic anchors.

This has the advantage that it works for every shape without the need to define a new shape.

We define a macro \pgfDeclareGenericAnchorsLinear (and a key define linear anchors that forwards it arguments to the macro) that takes five arguments:

  1. an anchor (we call it A),
  2. another anchor (let’s call it B),
  3. an integer number (should already be evaluated),
  4. the prefix for the new anchors, and
  5. a PGFmath function name.

The PGFmath function must have two and only two arguments (see examples).

If the PGFmath function evaluates to 0, the anchor A will be selected. If it evaluates to 1, the anchor B will be selected, this works very similar to calc’s

($(<node>.A)!<value>!(<node>.B)$)

This solution is best suited for nodes with the same amount of additional anchors (say six at the top, five at the bottom and two on the left side).

If you want to define a number of additional anchors on a node-by-node basis, additional work has to be done (i.e. storing this number with the node itself).

Code

\documentclass[tikz]{standalone}
\makeatletter
\newcommand*\pgfDeclareGenericAnchorsLinear[5]{%
  % #1 = anchor 1
  % #2 = anchor 2
  % #3 = max number (1 ... #3)
  % #4 = name
  % #5 = function name
  \pgfmathloop
    \csname pgfmath#5@\endcsname{\pgfmathcounter}{#3}%
    \edef\pgf@temp{%
      \noexpand\pgfdeclaregenericanchor{#4\space \pgfmathcounter}{%
        \noexpand\pgfpointlineattime
          {+\pgfmathresult}%
          {\noexpand\pgf@sh@reanchor{########1}{#1}}%
          {\noexpand\pgf@sh@reanchor{########1}{#2}}%
      }}%
    \pgf@temp
    \ifnum\pgfmathcounter<#3\relax
  \repeatpgfmathloop}
\makeatother
\pgfset{
  define linear anchors/.code n args=5{\pgfDeclareGenericAnchorsLinear{#1}{#2}{#3}{#4}{#5}},
  declare function={
    funcNormalPadding(\i,\n)=\i/(\n+1);
    funcHalfPadding(\i,\n)=(\i-.5)/(\n);
  }}

\tikzset{
  define linear anchors={north west}{north east}{6}{top}        {funcNormalPadding},
  define linear anchors={south west}{south east}{6}{bottom half}{funcHalfPadding},
  define linear anchors={south west}{south east}{6}{bottom}     {funcNormalPadding},
}
\begin{document}
\begin{tikzpicture}[mynode/.style={draw}]
\node[mynode,minimum size=5mm] (A1) at (0,0) {Some Text};
\node[mynode,minimum size=5mm,rotate=-45] (A2) at (-1,-1) {Teeext};
\foreach \v in {1,...,6}
  \draw (A1.top \v) -- ++ (up:1) (A2.bottom half \v) -- ++ (45:-.25);
\draw (A1.bottom 1) to[out=-90, in=45, looseness=.5] (A2.top 4);
\end{tikzpicture}
\end{document}

Output

enter image description here

3

You can use the standard anchors to define new points using calc.

For example, our node is named a, so we can define:

($(a.north west)!.1!(a.north east)$)

This calculates a point between the top left and the top right at the coordinate expressed by the number, from 0 to 1. Of course, you can increase this by writing !.47! and so you can virtually have 100 coordinates per side.

I say virtually because using all 100 simultaneously is not feasible unless the paths are extremely thin (unlikely). They should be enough for your use though. Here's an animation showing them (you'll only see 98 because the first and the last appear outside of the border of the node and I excluded them):

enter image description here

Output

figure 1

Code

\documentclass[tikz,margin=10pt]{standalone}

\usetikzlibrary{calc, positioning}

\begin{document}
\begin{tikzpicture}

    \node[draw] (a) {Some text};


    \draw ($(a.north west)!.1!(a.north east)$) --++ (0,1);
    \draw ($(a.north west)!.2!(a.north east)$) --++ (0,1);
    \draw ($(a.north west)!.3!(a.north east)$) --++ (0,1);
    \draw ($(a.north west)!.4!(a.north east)$) --++ (0,1);
    \draw ($(a.north west)!.42!(a.north east)$) --++ (0,1);
    \draw (a.north) --++ (0,1);
    \draw ($(a.north west)!.6!(a.north east)$) --++ (0,1);
    \draw ($(a.north west)!.7!(a.north east)$) --++ (0,1);
    \draw ($(a.north west)!.8!(a.north east)$) --++ (0,1);
    \draw[rounded corners=6pt] ($(a.north west)!.9!(a.north east)$) --++ (0,.2) --++ (.5,0) 
    --++ (0,-.9) --++ (-.5,0) -- ($(a.south west)!.9!(a.south east)$);

    \node[draw,below left=2 and .5 of a, rotate=-35] (b) {Other text};

    \draw ($(b.north west)!.3!(b.north east)$) edge[bend right] ($(a.south west)!.8!(a.south east)$);

\end{tikzpicture}
\end{document}
  • Very nice, thank you. That's not precisely what I had in mind, but it is really easy and useful! Since it is important that the edges are orthogonal to the node, I would suggest this \draw ($(b.north west)!.3!(b.north east)$) edge[out=90-35,in=-90] ($(a.south west)!.8!(a.south east)$); BTW do you now if there is an automatic way of getting them orthogonal? Unfortunately the "relative" command make it relative to the line not to the node. – Podantoro May 30 '15 at 23:25
  • @Podantoro I'll edit the answer tomorrow, but I was already thinking about some kind of simplified version of this. :) – Alenanno May 31 '15 at 1:37

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.