5

In the following picture I want node C to be as wide as the distance between (A.north east) and (B.south west) as indicated in the picture; I tried defining a new node D with \n1 as minimum width, and also tried to use \pgfmathsetmacro to store this length to use it later, but I got errors. In the first case, I got Undefined control sequence for \n1; in the second, I got Use of \tikz@cc@stop@let doesn't match its definition. \pgfmathsetmacro, among others (such as a missing }). Here's my MWE:

\documentclass[tikz]{standalone}

\usepackage[T1]{fontenc}

\usetikzlibrary{
    calc,
    positioning
}

\begin{document}

\begin{tikzpicture}[font = {\fontsize{40}{42}\selectfont}]
    \node[draw, blue] (A) at (0,0) {NODE A};
    \node[draw, red, anchor = north] (B) at (A.mid west) {NODE B};
    \draw (A.north east) -- node[midway, sloped, anchor = south, inner sep = 0, fill = gray, opacity = 0.6] {\normalsize\bfseries This length} (B.south west);
    \node[draw, anchor = north east] (C) at (B.south west) {NODE C};
    \coordinate[above = 2.7pt of C.north west] (uno) {};
    \coordinate[above = 2.7pt of C.north east] (dos) {};
    \draw[densely dashed] (uno)  -- (dos) node [midway, solid] {\normalsize This length};
    \draw let \p1 = ($ (B.south west) - (A.north east) $), \n1 = {veclen(\x1,\y1)}
    %\pgfmathsetmacro{\len}{\n1} 
    in circle [at = (C.center), radius =\n1];
    %\node[minimum width = \n1] (D) {NODE D};
\end{tikzpicture}

\end{document}

How can I store the value of \n1 for latter use? Furthermore, I want to use this length as a scale factor later on, so I think \pgfmathscalar would be useful there, but first I need to record the length. I think it is important to note that I want to use this length obtained from nodes constructed in the above way: putting a node over the anchor of another one. I would also like to get the length without recurring to drawing a circle; is there a way of doing this without the let operation? I would imagine one would have to obtain the coordinates of the anchors, as in this answer, but I don't know how to do it without using let, which is the point.

Thanks!!!

Getting the length with <code>let</code>.

mathbekunkus
  • 1,389
  • 2
    I think what you are looking for is "smuggling", see e.g. here. Of course, you could make the macro just global with \draw let \p1 = ($ (B.south west) - (A.north east) $), \n1 = {veclen(\x1,\y1)} in \pgfextra{\xdef\mylen{\n1}};. There are also pgf functions available that just compute the distance, see here. Finally, in this very case there is no need for all that, you can do everything in one path, can't you? –  May 24 '20 at 02:00
  • One can certainly draw everything in a single "stroke", so to speak. The point is to get the length as a dimensionless number. – mathbekunkus May 24 '20 at 02:33
  • 2
    Oh, that's simple: \draw let \p1 = ($ (B.south west) - (A.north east) $), \n1 = {veclen(\x1,\y1)} in \pgfextra{\pgfmathsetmacro{\mylen}{\n1}};. Then \mylen will be dimensionless, i.e. \n1 with pt stripped off. –  May 24 '20 at 02:34

2 Answers2

4

I think I've got it, though perhaps there are other solutions, hopefully more efficient; it requires the math library.

\documentclass[
    tikz
]{standalone}

\usepackage[T1]{fontenc}

\usetikzlibrary{
    calc,
    positioning,
    math
}

\begin{document}

\begin{tikzpicture}[font = {\fontsize{40}{42}\selectfont}]
    \node[draw, blue] (A) at (0,0) {NODE A};
    \node[draw, red, anchor = north] (B) at (A.mid west) {NODE B};
    \draw (A.north east) -- node[midway, sloped, anchor = south, inner sep = 0, fill = gray, opacity = 0.6] {\normalsize\bfseries This length} (B.south west);
    \node[draw, anchor = north east] (C) at (B.south west) {NODE C};
    \coordinate[above = 2.7pt of C.north west] (uno) {};
    \coordinate[above = 2.7pt of C.north east] (dos) {};
    \draw[densely dashed] (uno)  -- (dos) node [midway, solid] {\normalsize This length};
%   \draw let \p1 = ($ (B.south west) - (A.north east) $), \n1 = {veclen(\x1,\y1)}
%   in circle [at = (C.center), radius =\n1];
    \tikzmath{
        coordinate \p;
        \p = (B.south west) - (A.north east);
        \len = veclen(\p);  
        \lencm = 0.035*\len;
    }
    \node[minimum width = {\lencm cm}, draw, purple, fill = gray!10, opacity = 0.5] (D) at (C.center) {NODE D};
    \draw (C.north east) circle (\len pt);
    \draw (D.west) circle (\lencm);
    \node[circle, inner sep = 1pt, fill] at (D.west) {};
\end{tikzpicture}

\end{document}

I also defined the same length in centimetres multiplying by the appropriate factor; I noticed, however, that I must type minimum length = {\lencm cm} to get a rectangle of the appropriate size; otherwise I get a node exactly the size of C; the unit specification is unnecessary when drawing the circle, though. Does anyone know why?

enter image description here

mathbekunkus
  • 1,389
  • TikZ checks if the radius has a unit, and distinguishes between a dimensional radius and a dimensionless radius factor. See here for a very nice discussion. –  May 24 '20 at 02:00
2

In this case you can use the path to draw all objects you use. In order to convert \n1 to a dimensionless number \mylen such that \mylen cm=\n1 you can use

\pgfextra{\pgfmathsetmacro{\mylen}{\n1/1cm}}

In this example this is not necessary, i.e. you can use \n1 for all the lengths, but here is an example.

\documentclass[tikz]{standalone}

\usepackage[T1]{fontenc}

\usetikzlibrary{
    calc,
    positioning
}

\begin{document}

\begin{tikzpicture}[font = {\fontsize{40}{42}\selectfont}]
    \node[draw, blue] (A) at (0,0) {NODE A};
    \node[draw, red, anchor = north] (B) at (A.mid west) {NODE B};
    \draw (A.north east) -- node[midway, sloped, anchor = south, inner sep = 0, fill = gray, opacity = 0.6] {\normalsize\bfseries This length} (B.south west);
    \node[draw, anchor = north east] (C) at (B.south west) {NODE C};
    \coordinate[above = 2.7pt of C.north west] (uno) {};
    \coordinate[above = 2.7pt of C.north east] (dos) {};
    \draw[densely dashed] (uno)  -- (dos) 
    node [midway, solid] {\normalsize This length};
    \draw let \p1 = ($ (B.south west) - (A.north east) $), \n1 = {veclen(\x1,\y1)}
    in \pgfextra{\pgfmathsetmacro{\mylen}{\n1/1cm}}
    node[minimum width =\n1, draw, purple, fill = gray!10,
     opacity = 0.5] (D) at (C.center) {NODE D}
    (C.north east) circle [radius =\mylen]
    (D.west) circle [radius=\mylen];
    \draw[red,dashed,thick] let \p1 = ($ (B.south west) - (A.north east) $), \n1 = {veclen(\x1,\y1)}
    in \pgfextra{\pgfmathsetmacro{\mylen}{\n1/1cm}}
    (C.north east) circle [radius =\n1]
    (D.west) circle [radius=\n1];
\end{tikzpicture}

\end{document}

enter image description here

As you can see, the circles match, i.e. the dashed red circles of radius \n1 are on top of the circles of radius \mylen. This is sort of an accident, though, since in principle TikZ distinguishes between radii, i.e. dimensionful quantities, and dimensionless radius factors, see here for a nice discussion. In the case at hand, the unit vectors have the length 1cm, which is why the results match. That is, a dimensionless radius is interpreted as (radius factor) * (length of unit vector). This means in particular that if the x and y unit vectors have different lengths, you will get an ellipse with the dimensionless radius.

ADDENDUM: Here are some comments on smuggling, i.e. the question about broadcasting a macro out of a group without making it global. This is relevant for paths and for foreach loops, for example. Smuggling has been discussed here. Henri Menke has kindly added this answer, which will be fully working in the next version of pgf (now one has to use it with a small fix). These tricks have not yet been appreciated as much as they could That is, if you use version 3.1.6 or higher, you won't need the fix. The core level macros \pgfutil@pushmacro and \pgfutil@popmacro can be built in pgf functions that allow us to smuggle. Here is an example.

\documentclass[tikz]{standalone}
\usetikzlibrary{calc,positioning}
\makeatletter
% using https://tex.stackexchange.com/a/491246/194703
% won't be needed in future versions of tikz/pgf 
% see fix https://github.com/pgf-tikz/pgf/commit/0034290cb0295bafbb45f27a12a71a67797fcdbb
\def\pgfutil@pushmacro#1{%
    \xdef\pgfutil@pushmacro@string{\string#1}%
    \ifcsname pgfutil@pushedmacro@\pgfutil@pushmacro@string\endcsname\else
        % \newcount is \outer in Plain
        \csname newcount\expandafter\endcsname\csname pgfutil@pushedmacro@\pgfutil@pushmacro@string\endcsname
    \fi
    \global\advance\csname pgfutil@pushedmacro@\pgfutil@pushmacro@string\endcsname 1\relax
    \global\expandafter\let\csname\the\csname pgfutil@pushedmacro@\pgfutil@pushmacro@string\endcsname\pgfutil@pushmacro@string\endcsname#1%
}
\tikzset{push/.code={\expandafter\edef\csname#1\endcsname{\csname#1\endcsname}%
\expandafter\pgfutil@pushmacro\csname#1\endcsname}}
\pgfmathdeclarefunction{pop}{1}{\begingroup
\expandafter\pgfutil@popmacro\csname#1\endcsname%
\expandafter\pgfmathparse\expandafter{\csname#1\endcsname}%
\pgfmathsmuggle\pgfmathresult
\endgroup}
\makeatother
\begin{document}
\begin{tikzpicture}
    \node[draw, blue] (A) at (0,0) {NODE A};
    \node[draw, red, anchor = north] (B) at (A.mid west) {NODE B};
    \draw let \p1 = ($ (B.south west) - (A.north east) $), \n1 = {veclen(\x1,\y1)}
    in \pgfextra{\pgfmathsetmacro{\mylen}{\n1/1cm}}
    [push={mylen}];
    \path (A)
    node[above=1ex]{length in cm is $\pgfmathparse{pop("mylen")}\pgfmathprintnumber\pgfmathresult$};
    \foreach \X [count=\Y] in {A,...,F}
    {\edef\mycount{\Y}
    \tikzset{push=mycount}}
    \pgfmathparse{pop("mycount")}
    \typeout{\pgfmathresult}
\end{tikzpicture}
\end{document}

enter image description here

As you can see, you can now use

 \tikzset{push=mycount}}

to push the macro \mycount and then use the pgf function pop("mycount") to retrieve its value outside of the group. pop("mycount") can be used in any expression that gets parsed by TikZ, including coordinates. Similar tricks allow one to keys in foreach of the type remember outside. That is, one of the biggest drawbacks of the \foreach loop can be overcome with Henri's answer.

  • Great answer! Didn't know about \pgfextra. Thanks! – mathbekunkus May 24 '20 at 03:34
  • 1
    @ÓscarGuajardo You are welcome! Please note that one should never put a path in a \pgfextra, but for extra codes like this it can be used. –  May 24 '20 at 03:39
  • 1
    @JohnKormylo Please try `\documentclass[tikz,border=3mm]{standalone} \usetikzlibrary{calc} \begin{document} \begin{tikzpicture} \path (0,0) coordinate (A) (2,0) coordinate (B); \path let \p1=($(B)-(A)$),\n1={veclen(\y1,\x1)} in (A) node {$\pgfmathparse{\n1/1cm}\pgfmathprintnumber\pgfmathresult$}; \end{tikzpicture}

    \begin{tikzpicture}[scale=2] \path (0,0) coordinate (A) (2,0) coordinate (B); \path let \p1=($(B)-(A)$),\n1={veclen(\y1,\x1)} in (A) node {$\pgfmathparse{\n1/1cm}\pgfmathprintnumber\pgfmathresult$}; \end{tikzpicture} \end{document}`.

    –  May 24 '20 at 04:24
  • @Schrödinger'scat Note about your note: to put a path into \pgfextra, one can use \begin{pgfinterruptpath} ... path ... \end{pgfinterruptpath}! ;-) – Paul Gaborit May 24 '20 at 06:57
  • 1
    @PaulGaborit At least I got sometimes spurious results. It is one of the things like nesting tikzpictures, which may work, or may not work, and if you have spent a lot of energy in developing something using this method, and find that the results become uncontrollable, then you may think it would have been better not to use this. Even if you interrupt a path, you still inherit pgf keys from the ambience. If you start a new path, you are safe because definitions are local. Most importantly, I do not know a single scenario where one has to use this. –  May 24 '20 at 07:02
  • @Schrödinger'scat Just an example... – Paul Gaborit May 24 '20 at 07:08
  • @Schrödinger'scat - Oops. I had thought scale acted more like \pgssetxvec. – John Kormylo May 24 '20 at 12:27
  • @JohnKormylo No. These are two different mechanisms, and two possible ways to "scale" a picture. Maybe this thread can help to make this clearer. –  May 24 '20 at 17:39
  • @PaulGaborit I think we could clean up here... –  May 24 '20 at 19:15