4

I want to optimize the following code:

\documentclass{article}
%\pagestyle{empty}

% Figure dimensions
\newcommand{\defLength}[2]{\newlength{#1}\setlength{#1}{#2}}
\defLength{\figWidth}{0.99\columnwidth}
\defLength{\figHeight}{\textheight}
%\usepackage[textwidth=\figWidth,height=\figHeight,showframe]{geometry}

% Math
\usepackage[cmex10]{mathtools}
\interdisplaylinepenalty=2500

% Required
\usepackage{graphicx}
\usepackage{tikz}
%\usetikzlibrary{matrix}
%\usetikzlibrary{shapes.geometric}
\usetikzlibrary{positioning}
\usetikzlibrary{calc}
%\usetikzlibrary{decorations.pathreplacing} % for expanding waves
%\usetikzlibrary{decorations.markings}
\usetikzlibrary{arrows.meta}
\usetikzlibrary{fit}
\usetikzlibrary{intersections}
\usetikzlibrary{backgrounds}

% https://tex.stackexchange.com/questions/54368/tikz-nodes-centering-with-small-font
\tikzset{every node/.append style={execute at begin node=\footnotesize}} 

\tikzset{%
   ball/.style = {draw, shape=circle, text width=0.15\figWidth, align=center, inner sep=2pt, outer sep=0pt},
   caixa/.style = {draw, shape=rectangle, align=center, inner sep=0pt, outer sep=0pt},
     seta/.style = {-{Stealth[round]}, shorten >=\setaShort},
     %seta/.style = {->, >=stealth},
     %seta/.style = {->,},
}

% Calculate the distance and angle between two nodes
% https://tex.stackexchange.com/questions/38473/how-can-i-compute-the-distance-between-two-coordinates-in-tikz
% https://tex.stackexchange.com/questions/39293/coordinates-a-b-compute-b-a-and-angle-between-x-and-b-a
\makeatletter      
\newcommand{\getLengthAndAngle}[4]{%
  \pgfmathanglebetweenpoints{\pgfpointanchor{#1}{#2}}
                            {\pgfpointanchor{#3}{#4}}
  \global\let\myangle\pgfmathresult % we need a global macro 
  \pgfpointdiff{\pgfpointanchor{#1}{#2}}
               {\pgfpointanchor{#3}{#4}}
  \pgf@xa=\pgf@x % no need to use a new dimen
  \pgf@ya=\pgf@y
  %\pgfmathparse{veclen(\pgf@xa,\pgf@ya)/28.45274} % to convert from pt to cm   
  \pgfmathparse{veclen(\pgf@xa,\pgf@ya)} % to convert from pt to cm   
  \global\let\mylength\pgfmathresult % we need a global macro
}
\makeatother

\defLength{\setaShort}{2pt}
\defLength{\fitWidth}{4pt}
\defLength{\reduceWidth}{1cm}
\def\nodeDist{0.05\figWidth}

% Draw arc
\def\centerarc[#1](#2)(#3:#4:#5)% [draw options] (center) (initial angle:final angle:radius) 
{ \draw[#1] ($(#2)+({#5*cos(#3)},{#5*sin(#3)})$) arc (#3:#4:#5); }

\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}


\begin{document}

\noindent%
\begin{tikzpicture}[node distance=\nodeDist, auto]

%\node [draw, shape=rectangle, text width=\figWidth, text height=0.2*\figHeight, inner sep=0pt, outer sep=0pt] (area) {};
\node [shape=rectangle, text width=\figWidth, text height=0.2*\figHeight, inner sep=0pt, outer sep=0pt] (area) {};

% Balls
\node [ball, anchor=west] at ($(area.west)+(\reduceWidth,0pt)+(\fitWidth,0)$) (ball0) {};
\node [draw, fit={(ball0)}, inner sep=\fitWidth] (fitball0) {};

\node [ball, anchor=east] at ($(area.east)+(-\reduceWidth,0pt)+(-\fitWidth,0)$) (ball1) {};
\node [draw, fit={(ball1)}, inner sep=\fitWidth] (fitball1) {};

\node [ball, below = 2*\nodeDist of ball0] (ball2) {};
\node [ball, below = of ball2] (ball3) {};
\node [draw, fit={(ball2)(ball3)}, inner sep=\fitWidth] (fitball2) {};

\node [ball, below = 2*\nodeDist of ball1] (ball4) {};
\node [ball, below = of ball4] (ball5) {};
\node [draw, fit={(ball4)(ball5)}, inner sep=\fitWidth] (fitball3) {};

\draw node[%
    ball,
    text width=0.05\figWidth,
    append after command={(\tikzlastnode.135) -- (\tikzlastnode.315) (\tikzlastnode.45) -- (\tikzlastnode.225)}, 
    inner sep=0pt] at ($(ball2)!0.4!(ball4)$) (multi0) {};

\draw node[%
    ball,
    text width=0.05\figWidth,
    append after command={(\tikzlastnode.135) -- (\tikzlastnode.315) (\tikzlastnode.45) -- (\tikzlastnode.225)}, 
    inner sep=0pt] at ($(ball3)!0.6!(ball5)$) (multi1) {};

\coordinate (auxa) at (multi0.east -| multi1.east);
\getLengthAndAngle{multi0}{west}{auxa}{center}
\coordinate (auxb) at ($(multi0.west)!0.5!(auxa)$);
\coordinate (auxc) at (auxb |- fitball3.south);
\draw node[%
    caixa,
    text width=\mylength pt, 
    text height=0.5*\mylength pt,
    anchor=north,
    append after command={([yshift=-6pt]\tikzlastnode.north) -- ++(-0.25*\mylength pt,0pt) -- ([yshift=-6pt]\tikzlastnode.north) -- ([yshift=6pt]\tikzlastnode.south) -- ++(0.25*\mylength pt,0pt)},
    ] at (auxc) (step) {};

\node [%
    caixa, 
    text width=\mylength pt, 
    %text height=0.5*\mylength pt,
    minimum height=0.5*\mylength pt,
    below = of step,
    ] (impact) {};

% Connection between nodes
\draw [seta] (ball0) -- node [pos=0.13] (ball2multi0) {} (ball1);
\path [name path=ball2multi0path] (ball2) -- node [pos=0.7] (ball3multi1) {} (multi0) (multi0) edge[seta] (ball4); % will be repeated
\path [name path=ball3multi1path] (ball3) -- (multi1) (multi1) edge[seta] (ball5); % will be repeated

\coordinate (auxd) at (ball2multi0 -| ball3multi1);
\getLengthAndAngle{ball2multi0}{center}{auxd}{center}
\draw [seta, name path=ball2multi0impactpath] (ball2multi0) |- ([yshift=-0.5*\mylength pt]impact.west);
\draw [seta, name path=ball3multi1impactpath] (ball3multi1) |- ([yshift=0.5*\mylength pt]impact.west);

\draw [seta] (impact) -- (step);

\draw [seta, name path=stepmulti0path] (step.north -| multi0) -- (multi0);
\draw [seta] (step.north -| multi1) -- (multi1);

% Intersections
\path [name intersections={of=ball2multi0impactpath and ball2multi0path,by=inter1}];
\filldraw [white] (inter1) circle (2pt);

\path [name intersections={of=ball2multi0impactpath and ball3multi1path,by=inter2}];
\filldraw [white] (inter2) circle (2pt);

\path [name intersections={of=ball3multi1impactpath and ball3multi1path,by=inter3}];
\filldraw [white] (inter3) circle (2pt);

\path [name intersections={of=stepmulti0path and ball3multi1path,by=inter4}];
\filldraw [white] (inter4) circle (2pt);

\pgfmathparse{2pt+\pgflinewidth}\let\fix=\pgfmathresult
\centerarc[](inter1)(90:-90:\fix pt)) % -90 and 270 are different
\centerarc[](inter2)(90:-90:\fix pt)
\centerarc[](inter3)(90:-90:\fix pt)
\centerarc[](inter4)(90:-90:\fix pt)

\draw (ball2) -- (multi0); % repeated
\draw (ball3) -- (multi1); % repeated

\end{tikzpicture}

\end{document}

Namely, the drawing of the arc that shows the lines are not connected is clearly not good, since I have to use many workarounds (fill white, draw lines twice, etc.). I tried a solutions from here Intersection of 2 lines not really connected in TikZ, but it didn't work. I do believe that it is also possible to improve other parts of the code and put it more legible.

enter image description here

7
  • Have you looked at the packages/libraries which specifically support drawing circuits?
    – cfr
    Feb 26, 2015 at 17:03
  • No I didn't. Can you be more specific?
    – cacamailg
    Feb 26, 2015 at 19:06
  • circuitikz...?
    – cfr
    Feb 26, 2015 at 22:05
  • Just curiosity, are you catalan? caixa?
    – Ignasi
    Feb 27, 2015 at 10:53
  • Do you know standalone? It can replace preview.
    – Ignasi
    Feb 27, 2015 at 10:54

1 Answer 1

1

What has been done?

  • Add default dimensions to caixa which is later used as base for filter (new style).
  • Declare new style filter
  • Declare new style multi
  • Use \pgfextra in after path options. This way you can use \node instead of \draw node for filter and multi nodes.
  • New style connect (taken from Mark's solution to intersection-of-2-lines-not-really-connected-in-tikz). A little mistake has been corrected and works as expected.
  • Place filter node with a syntax which doesn't use so many auxiliary coordinates.
  • Use connect to draw vertical connections without having to redraw any path.
  • Not use centerarc (connect solves the problem).

The result is just like yours:

enter image description here

Not sure if code is much more clear but here it is.

\documentclass{article}
%\pagestyle{empty}

% Figure dimensions
\newcommand{\defLength}[2]{\newlength{#1}\setlength{#1}{#2}}
\defLength{\figWidth}{0.99\columnwidth}
\defLength{\figHeight}{\textheight}
%\usepackage[textwidth=\figWidth,height=\figHeight,showframe]{geometry}

% Math
\usepackage[cmex10]{mathtools}
\interdisplaylinepenalty=2500

% Required
\usepackage{graphicx}
\usepackage{tikz}
%\usetikzlibrary{matrix}
%\usetikzlibrary{shapes.geometric}
\usetikzlibrary{positioning}
\usetikzlibrary{calc}
%\usetikzlibrary{decorations.pathreplacing} % for expanding waves
%\usetikzlibrary{decorations.markings}
\usetikzlibrary{arrows.meta}
\usetikzlibrary{fit}
\usetikzlibrary{intersections}
\usetikzlibrary{backgrounds}

% https://tex.stackexchange.com/questions/54368/tikz-nodes-centering-with-small-font
\tikzset{every node/.append style={execute at begin node=\footnotesize}} 

\tikzset{%
   ball/.style = {draw, shape=circle, text width=0.15\figWidth, align=center, 
                  inner sep=2pt, outer sep=0pt},
   caixa/.style = {draw, shape=rectangle, align=center, inner sep=0pt, outer sep=0pt,
                 text width=\mylength pt, minimum height=0.5*\mylength pt},
   seta/.style = {-{Stealth[round]}, shorten >=\setaShort},
   multi/.style = {ball, text width=0.05\figWidth, inner sep=0pt,
                append after command={
                \pgfextra
                   \draw (\tikzlastnode.135)--(\tikzlastnode.315) 
                         (\tikzlastnode.45)--(\tikzlastnode.225);
                \endpgfextra
                }},
   filter/.style={caixa, append after command={
                    \pgfextra
                       \draw ([yshift=-6pt]\tikzlastnode.north) -- %
                           ++(-0.25*\mylength pt,0pt) -- % 
                             ([yshift=-6pt]\tikzlastnode.north) -- %
                             ([yshift=6pt]\tikzlastnode.south) -- %
                         ++(0.25*\mylength pt,0pt);
                    \endpgfextra}},
    connect/.style args={(#1) to (#2) over (#3) to (#4) by #5}{
        insert path={
            \pgfextra{
                \pgfinterruptpath
                    \path [name path=a] (#1) -- (#2);
                    \path [name path=b] (#3) -- (#4);
                    \path [name intersections={of=a and b,by=inter}];
                \endpgfinterruptpath                
            }
            let \p1=($(#1)-(inter)$), \n1={veclen(\x1,\y1)}, 
                            \n2={atan2(\y1,\x1)}, \n3={abs(#5)}, \n4={#5>0 ?180:-180}  in 
                            (#1) -- ($(#1)!\n1-\n3!(inter)$)
                            arc (\n2:\n2+\n4:\n3) -- (#2)
        }
    },
     %seta/.style = {->, >=stealth},
     %seta/.style = {->,},
}

% Calculate the distance and angle between two nodes
% https://tex.stackexchange.com/questions/38473/how-can-i-compute-the-distance-between-two-coordinates-in-tikz
% https://tex.stackexchange.com/questions/39293/coordinates-a-b-compute-b-a-and-angle-between-x-and-b-a
\makeatletter      
\newcommand{\getLengthAndAngle}[4]{%
  \pgfmathanglebetweenpoints{\pgfpointanchor{#1}{#2}}
                            {\pgfpointanchor{#3}{#4}}
  \global\let\myangle\pgfmathresult % we need a global macro 
  \pgfpointdiff{\pgfpointanchor{#1}{#2}}
               {\pgfpointanchor{#3}{#4}}
  \pgf@xa=\pgf@x % no need to use a new dimen
  \pgf@ya=\pgf@y
  %\pgfmathparse{veclen(\pgf@xa,\pgf@ya)/28.45274} % to convert from pt to cm   
  \pgfmathparse{veclen(\pgf@xa,\pgf@ya)} % to convert from pt to cm   
  \global\let\mylength\pgfmathresult % we need a global macro
}
\makeatother

\defLength{\setaShort}{2pt}
\defLength{\fitWidth}{4pt}
\defLength{\reduceWidth}{1cm}
\def\nodeDist{0.05\figWidth}

% Draw arc
\def\centerarc[#1](#2)(#3:#4:#5)% [draw options] (center) (initial angle:final angle:radius) 
{ \draw[#1] ($(#2)+({#5*cos(#3)},{#5*sin(#3)})$) arc (#3:#4:#5); }

\usepackage[active,tightpage]{preview}
\PreviewEnvironment{tikzpicture}


\begin{document}

%\noindent%
\begin{tikzpicture}[node distance=\nodeDist, auto]

%\node [draw, shape=rectangle, text width=\figWidth, text height=0.2*\figHeight, inner sep=0pt, outer sep=0pt] (area) {};
\node [shape=rectangle, text width=\figWidth, text height=0.2*\figHeight, inner sep=0pt, outer sep=0pt] (area) {};

% Balls
\node [ball, anchor=west] at ($(area.west)+(\reduceWidth,0pt)+(\fitWidth,0)$) (ball0) {};
\node [draw, fit={(ball0)}, inner sep=\fitWidth] (fitball0) {};

\node [ball, anchor=east] at ($(area.east)+(-\reduceWidth,0pt)+(-\fitWidth,0)$) (ball1) {};
\node [draw, fit={(ball1)}, inner sep=\fitWidth] (fitball1) {};

\node [ball, below = 2*\nodeDist of ball0] (ball2) {};
\node [ball, below = of ball2] (ball3) {};
\node [draw, fit={(ball2)(ball3)}, inner sep=\fitWidth] (fitball2) {};

\node [ball, below = 2*\nodeDist of ball1] (ball4) {};
\node [ball, below = of ball4] (ball5) {};
\node [draw, fit={(ball4)(ball5)}, inner sep=\fitWidth] (fitball3) {};

\node[multi] at ($(ball2)!0.4!(ball4)$) (multi0) {};

\node[multi] at ($(ball3)!0.6!(ball5)$) (multi1) {};

\coordinate (auxa) at (multi0.east -| multi1.east);
\getLengthAndAngle{multi0}{west}{auxa}{center}


%\coordinate (auxb) at ($(multi0.west)!0.5!(auxa)$);
%\coordinate (auxc) at (auxb |- fitball3.south);
%\draw node[filter] at (auxc) (step) {};

%Next shows another option to place `filter` node without so many aux coordinates
\path (multi0|-fitball3.south) -- (multi1|-fitball3.south) node[midway, filter, anchor=north] (step){};

\node [caixa, below = of step] (impact) {};

% Connection between balls
\draw [seta] (ball0) -- coordinate [pos=0.13] (ball2multi0) (ball1);
\draw [name path=ball2multi0path] (ball2) -- coordinate [pos=0.7] (ball3multi1) (multi0) (multi0) edge[seta] (ball4); % will be repeated
\draw [name path=ball3multi1path] (ball3) -- (multi1) (multi1) edge[seta] (ball5); % will be repeated

% Connection from ball0 to impact
% It's drawn with two segments: 
% Segment 1 (ball2multi0) to (ball2multi0 -| ball3multi1) 
% Segment 2 (ball2multi0 -| ball3multi1) to (auxe)
\coordinate (auxd) at (ball2multi0 -| ball3multi1);
\getLengthAndAngle{ball2multi0}{center}{auxd}{center}
\coordinate (auxe) at ($(0,-0.5*\mylength pt)+(ball2multi0 |- impact.west)$);

\draw [connect={(ball2multi0) to (ball2.south-|ball2multi0) over (ball2) to (multi0) by -5pt}];

\draw [seta, connect={(ball2.south-|ball2multi0) to (auxe) over (ball3) to (multi1) by -5pt}]--(auxe-|impact.west);
%

% Connection from ball2 to impact
\coordinate (auxe) at ($(0,0.5*\mylength pt)+(ball3multi1 |- impact.west)$);

\draw [seta, connect={(ball3multi1) to (auxe) over (ball3) to (multi1) by -5pt}]--(auxe-|impact.west);

% Connection from impact to multi0
\draw [seta, connect={(step.north -| multi0) to (multi0) over (ball3) to (multi1) by 5pt}];

% Two more connections
\draw [seta] (impact) -- (step);
\draw [seta] (step.north -| multi1) -- (multi1);
\end{tikzpicture}

\end{document}
3
  • I also do not know if the code is more clear. Nevertheless, it has some insights to change my original code. Do you know other way to improve the lines like \draw [name path=ball2multi0path] (ball2) -- coordinate [pos=0.7] (ball3multi1) (multi0) (multi0) edge[seta] (ball4);, keeping the arrow in the end but without repeating (multi0)?
    – cacamailg
    Feb 27, 2015 at 11:34
  • 1
    @cacamailg: You are already using calc so a possible command to draw multiplier node and lines between balls at the same time could be \node[multi] at ($(ball2)!0.4!(ball4)$) (multi0) {} edge coordinate[pos=0.3] (ball3multi1) (ball2) edge [seta] (ball4);. This means, place node multi0 at position 0.4 between ball2.center and ball4.center. After that draw an edge between multi0 and ball2 while setting coordinate ball3multi1 at position 0.3 (0.3=1-0.7 because path starts at multi0). And after that draw another edge with style seta between multi0 and ball4.
    – Ignasi
    Feb 27, 2015 at 12:17
  • @cacamailg Previous command replaces lines \node[multi] at ($(ball2)!0.4!(ball4)$) (multi0) {}; and \draw [name path=ball2multi0path] (ball2) -- coordinate [pos=0.7] (ball3multi1) (multi0) (multi0) edge[seta] (ball4);
    – Ignasi
    Feb 27, 2015 at 12:20

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.