5

I am trying to debug a problem with an issue on circuitikz that I have been able to reduce to this MWE --- sorry if it's not so short...

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\makeatletter
\pgfdeclareshape{sline}{
    \anchor{center}{
        \pgfpointorigin
    }
    \anchor{left}{\pgf@x=-0.5cm\pgf@y=0pt}
    \anchor{right}{\pgf@x=0.5cm\pgf@y=0pt}
    \backgroundpath{
        \pgfscope
        \pgfsetcolor{red}
        \pgfpathmoveto{\pgfpoint{-0.5cm}{0pt}}
        \pgfpathlineto{\pgfpoint{0.5cm}{0pt}}
        \pgfusepath{draw}
        \endpgfscope
    }
}
\def\goforpaths{
    \pgfextra{
        \pgfmathanglebetweenpoints{\tikztostart}{\tikztotarget}
        \edef\mydirection{\pgfmathresult}%Calculate direction(angle) of path
    }
    ($(\tikztostart)!0.5!(\tikztotarget)$) node[sline, rotate=\mydirection](N){}
    (\tikztostart) -- (N.left) (N.right) -- (\tikztotarget)
    \tikztonodes
}
\tikzset{slineto/.style={/tikz/to path=\goforpaths}}

\makeatother
\begin{document}
\begin{tikzpicture}[
    scale=1.2,
    % scale=2
    ]
    \draw (24,-1) to[slineto] ++(0,2);
\end{tikzpicture}
\end{document}

The MWE mimics what circuitikz is doing to place components along paths; in this case, the component is a simple red line.

Now, when the scale factor is not an integer I have this very strange behavior in the joins:

displaced joint

(Notice that I tested with different PDF viewers).

For integer scale factors or no scale, the result is perfect:

perfect joint

(this is the same snippet with scale=2). Also, if you change the 24 in the coordinate to 0, the problem almost disappears.

I suppose this is some kind of accumulated error on calculations of coordinates --- but it's quite strange, so the probability that I am doing something wrong is very high. Can anyone spot where my error is?

4
  • @js-bibra I removed the circuitikz tag because this specific question does not involve it. Thanks anyway!
    – Rmano
    Feb 19 '20 at 13:02
  • I do not see you doing anything wrong. Doing the same thing with different techniques, and placing other nodes than the shape you define confirm always your observation. It seems that TikZ does some approximation when scaling. In connection with the large x coordinate, 24, this leads to the problem.
    – user194703
    Feb 19 '20 at 15:54
  • I think the problem is \pgfmathreciprocal@....
    – user194703
    Feb 19 '20 at 16:26
  • 2
    If you use 0 instead of 24, the result is perfect. I you use 240 instead of 24, the result is perfectly bad! Don't use scale !!! (Ok, it's not a solution...) Feb 19 '20 at 16:30
3

The problem is \pgfmathreciprocal@, which gets used to invert the transformations. Before showing a local version below, let me first present a global fix, which replaces \pgfmathreciprocal@ everywhere by an fpu variant. While this is fine in the example at hand, it might not be fine in every possible example. (Naively one may think it should always work in plain TikZ, but the devil is in the details. In any case, a local version, which should be absolutely safe, can be found below.)

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{fpu}

\makeatletter
\def\pgfmathreciprocal@#1{%
    \begingroup
    % FIXME optimize
    \pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}%
    \pgfmathparse{1/#1}%
    \pgfmath@smuggleone\pgfmathresult
    \endgroup
}%
\pgfdeclareshape{sline}{
    \anchor{center}{
        \pgfpointorigin
    }
    \anchor{left}{\pgf@x=-0.5cm\pgf@y=0pt}
    \anchor{right}{\pgf@x=0.5cm\pgf@y=0pt}
    \backgroundpath{
        \pgfscope
        \pgfsetcolor{red}
        \pgfpathmoveto{\pgfpoint{-0.5cm}{0pt}}
        \pgfpathlineto{\pgfpoint{0.5cm}{0pt}}
        \pgfusepath{draw}
        \endpgfscope
    }
}
\def\goforpaths{
    let \p1=($(\tikztostart)-(\tikztotarget)$),\n1={atan2(\y1,\x1)} in
    ($(\tikztostart)!0.5!(\tikztotarget)$)
     node[sline, rotate=\n1,anchor=center](N){}
    (\tikztostart) -- (N.left) 
    (N.right) -- (\tikztotarget)
    \tikztonodes
}
\tikzset{slineto/.style={/tikz/to path=\goforpaths}}

\makeatother
\begin{document}
\begin{tikzpicture}[
    scale=1.2,
    %scale=2
    ]
    \draw (24,-1) to[slineto] ++(0,2);
\end{tikzpicture}
\end{document}

enter image description here

Please note also that I use the calc syntax to compute the rotation angle. This is unrelated to the problem, and I am not claiming that this way of doing that is better.

What you can always do is to confine the changes to some scope or path. This version should not do harm.

\documentclass[border=10pt]{standalone}
\usepackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{fpu}

\makeatletter
\tikzset{use fpu reciprocal/.code={%
\def\pgfmathreciprocal@##1{%
    \begingroup
    % FIXME optimize
    \pgfkeys{/pgf/fpu=true,/pgf/fpu/output format=fixed}%
    \pgfmathparse{1/##1}%
    \pgfmath@smuggleone\pgfmathresult
    \endgroup
}}}%
\pgfdeclareshape{sline}{
    \anchor{center}{
        \pgfpointorigin
    }
    \anchor{left}{\pgf@x=-0.5cm\pgf@y=0pt}
    \anchor{right}{\pgf@x=0.5cm\pgf@y=0pt}
    \backgroundpath{
        \pgfscope
        \pgfsetcolor{red}
        \pgfpathmoveto{\pgfpoint{-0.5cm}{0pt}}
        \pgfpathlineto{\pgfpoint{0.5cm}{0pt}}
        \pgfusepath{draw}
        \endpgfscope
    }
}
\def\goforpaths{
    \pgfextra{
        \pgfmathanglebetweenpoints{\tikztostart}{\tikztotarget}
        \edef\mydirection{\pgfmathresult}%Calculate direction(angle) of path
    }
    ($(\tikztostart)!0.5!(\tikztotarget)$) node[sline, rotate=\mydirection](N){}
    (\tikztostart) -- (N.left) (N.right) -- (\tikztotarget)
    \tikztonodes
}
\tikzset{slineto/.style={/tikz/to path=\goforpaths}}


\makeatother
\begin{document}
\begin{tikzpicture}[
    scale=1.2,
    %scale=2
    ]
    \draw[use fpu reciprocal] (24,-1) to[slineto] ++(0,2);
\end{tikzpicture}
\end{document}

enter image description here

You may build this into the definition of whatever you really want to do.

(Note to myself: maybe using this trick for the other core functions may allow us to get rid of the dimension too large problems that haunt decorations and nonlinear transformations by installing these changes locally?)

2
  • Wow. How did you got this? Thanks a lot. I'll add your use fpu reciprocal option to circuitikz if you don't mind (with proper attribution, obviously!)
    – Rmano
    Feb 19 '20 at 19:16
  • 1
    @Rmano Of course you can use this in any way. How did I get this? I double checked that the transformations are (relatively) precise, so it was plausible that the inverse transformations are to blame. Their code is rather simple, especially for scale transformations, and together with you noticing that integers are fine, this strongly suggested that the reciprocal is to blame. (This also suggests that you could just scale by 6 and 1/5, and might be fine, but this won’t be a general solution to the problem.) 😸
    – user194703
    Feb 19 '20 at 19:32

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.