同じ色の水平線を自動的に接続します

同じ色の水平線を自動的に接続します

以下のような図面を自動化するにはどうすればよいでしょうか?

これは挑戦になるのではないかと思います。

これらの図はいくつかのルールに従います。上に示すように、番号が付けられた 2 つの軸フィールドのリストを含むページがあります。各フィールドは個別のものです\begin{tikzpicture}\blank ... \connectlogically\end{tikzpicture}

(ア)各フィールドには水平矢印があります。各矢印はマクロ\pointX #1,#2|#3|#4|#5(黒) または\pointK #1,#2|#3|#4|#5(灰色) によって描画されます。これにより、座標 から始まり(#1,#2)#3単位の長さ (この数値は矢印の上にも中央に表示されます) の矢印が描画され、その下または上には座標 の点があり(#1 + 0.5*#3 ,#4)、矢印の先端の右側にラベルが表示されます#5。このようなコマンドの定義例は MWE にあります。

(ロ)可能であれば、他の 2 つのコマンド、つまり\pointXoと を\pointKo作成する必要があります。これらのコマンドは、5 つではなく 4 つの引数を取ります。これらは、x coord最後に発行された によって定義された矢印の先端の x 座標に\point<K/X>、最後に発行された の矢印の先端ラベルに付けられた番号を加えたものを、それ自体として使用します。これは、引数が最後に発行された から取得され、が の最初の引数であるの\point<K/X>ような座標に相当します。(#1+#3+#5,<y coord>)\point<K/X><y coord>\point<K/X>o

(c)主な問題:Kiy 位置に矢印がありyKi、その尾が でxKi-1先端が であると仮定します。この矢印の周りには、同じ構造に従うxKi-2他の矢印、つまり矢印 があります。ここで、これらの矢印は、この条件を満たす垂直に近い矢印 ( ) に垂直線で接続する必要があります。つまり、尾が矢印の尾と先端の間にある矢印がある場合、 に最も近い矢印が垂直に接続されます。目標は、これを自動的に実行するコマンドを作成することです。矢印や描画がたくさんあると、これは非常に煩雑になるため、このコマンドを と呼びます。Kjmin(abs(yKi - yKj))xKi-1 <= xKj-1 < xKi-2jjii\connectlogically

(ニ)理想的には、接続したい 2 つのステップの開始ノード座標を指定して、同じ色の線分間の接続を描画するように LaTeX に手動で指示できる必要があります。ただし、これでは中心が折れ線で接続されます。

* 最も簡単に実装できる場合は、すべての長さと位置を離散的にすることができます。その場合、4 3/4 や -2 1/8 など、1/8 の整数倍だけが必要です。

以下は、マクロの定義\pointXと、考えられるすべてのシナリオ (と思われる) を網羅したテスト ケースを含む MWE です。その下には、コマンドを発行した後の現在の出力と目的の出力があります\connectlogically

\documentclass[border=2mm]{standalone}
\usepackage{tikz}

\tikzset{dot/.style={circle, fill, minimum size=.4em, outer sep=0pt, inner sep=0pt}}

\def\pointX #1,#2|#3|#4|#5.{%
    \draw[black, ->] (#1,#2) -- node[above]{#3} (#1+#3,#2) node[right]{#5} (#1+#3/2,#2+#4) node[dot]{};
}

\def\pointK #1,#2|#3|#4|#5.{%
    \draw[gray, ->] (#1,#2) -- node[above]{#3} (#1+#3,#2) node[right]{#5} (#1+#3/2,#2+#4) node[dot]{};
}

\newcount\blankcount
\def\blank{%
    \advance\blankcount by 1
    \draw[black!64,->](0,0)--node[left]{$x\atop y$} (0,8);
    \draw[black!32](0,3)--(16,3)node{};
    \draw[black!64,->](0,0)--node[below]{$a\atop b$} (16,0);
    \node (x\the\blankcount) at (16,8) {\the\blankcount};
}

\begin{document}
    \begin{tikzpicture}
        \blank
        \pointX 1/2,1|3|1|0.
        \pointX 7/2,1/2|2|2|-1/2. % This should be \pointXo 1/2|2|2|-1/2.
        \pointX 5,2|2|-1|0.       % This should be \pointXo 2|2|-1|0.
        \pointX 7,3/2|1|3|0.      % This should be \pointXo 3/2|1|3|0.
        \pointX 1/2,2|5/2|1|19/4.
        \pointK 2,4|2|-2|-3/4.
        \pointK 13/4,5|1|-4|0.    % This should be \pointKo 5|1|-4|0.
        \pointX 31/4,3|3|3|3.     % This should be \pointXo 3|3|3|3.
        %\connectlogically        % This should connect everything automatically
    \end{tikzpicture}
\end{document}

現在のMWE

望ましい出力

編集: 素晴らしい回答が 2 つあります。50 (ED) と 100 (GS) はどちらも賞金に値します。

答え1

エリックが言うように、これはアルゴリズムの問​​題です。そして、その通り、この問題を解決する方法はいくつかあります。ここでは、TiZ +etoolboxウェイ、広範囲etoolboxの汎用テスト機能とTiを使用Z ループステートメント。

背後にあるロジックは、i種類K( Ki) の各矢印について、その尾のx座標 ( Ki-1)が、j種類K( Kjj≠i-Kj-1が尾とKj-2先端) の他の矢印の尾と先端の間にあるかどうかをテストすることです。 がKj適合する場合、それらの間の垂直距離 ( ) をテストし、現在の垂直距離がより小さく、 でもある場合は、abs(yi-yj)最小垂直距離minvdist(最初は100cm)と比較します。 これをすべての矢印に対して実行し、ループが終了したら を実行します。 これが機能することです。minvdist\let\minvdist{abs(yi-yj)}\let\j\closerjj\draw (Kcloserj-1) |- (Ki-1);\autoconnectK

座標Ki-1の他に、矢印の中心位置を参照する座標と、矢印の先端位置を参照する座標にコマンドの最後の入力として指定された数値を加えたものもあり、この座標はコマンドを実装するために使用されます。2KiKi-node\pointKo

マクロは、\pointKo最後に定義されたKi-nodex 座標を自身の の入力として使用します<x coord>。そのため、最初の入力は ではありません\pointK。さらに、このコードで注意が必要なのは、\foreachループ内では各反復の後に変数が完全に消去されるため、次の反復のために変数を保存する必要がある場合は、 をプレフィックスとして付ける\globalか、グローバルに定義する必要があります。そうしないと、次の反復では変更が意味をなさなくなります。

OP からのリクエストとして、矢印は\pointK5 つの引数を取り、\pointKoは 4 つの引数を取るというマクロによって描画されます。

\pointK <x coord>,<y coord>|<h lenght>|<over dot v lenght>|<right label>.
\pointKo <y coord>|<h lenght>|<over dot v lenght>|<right label>.

問題を新たなレベルに引き上げるために、\newpoint{<K>}{tikz style}マクロが作成されました。このマクロは、前述のマクロをすべてまとめて作成し、さらに、矢印の先端に\showKsすべての種類の矢印の名前をKフォント\tinyと赤色で表示する という名前のマクロを作成します。これは、曲がった接続を手動で描画するのに役立ちます。以下は、すべてがどのように機能し、出力がどのようになるかを示した完全な例です。

\documentclass[11pt]{article}\usepackage{geometry,xcolor,tikz,etoolbox}\usetikzlibrary{calc}
\geometry{paperwidth=16in,paperheight=10in,left=1in,right=1in,top=1in,bottom=1in}
\colorlet{AXEScolor}{blue!64!red!32}\colorlet{LINEcolor}{red!32}
\newcount\blankcount\blankcount=0
\def\blank{\global\advance\blankcount by 1\setcounter{pointX}{-1}\setcounter{pointK}{-1}
\draw[AXEScolor,line cap=round,->](0,0)--node[left]{$a\atop b$}(0,16);
\draw[AXEScolor,line cap=round,->](0,0)--node[below]{${}\atop time$}(32,0);
\draw[LINEcolor](0,6)--(32,6)node{};
\node (x\the\blankcount) at (-1/2,-1/2) {\LARGE\textbf{\the\blankcount}};}

\tikzset{dot/.style={circle,fill,minimum size=3pt}, inner sep=0pt, outer sep=2pt} 
\newdimen\xlast\newdimen\ylast 
\newcommand*{\ExtractCoordinate}[1]{\path (#1); \pgfgetlastxy{\xlast}{\ylast}} 

\newcommand*{\closerj}{} 
\newcommand*{\findcloserj}[1]{% Serves as input for second autoconnect loop (finds the closer arrow) 
    \ifnumequal{\j}{\i}{}% If is \j the same arrow as \i do nothing, else: 
    {\ExtractCoordinate{$(#1\j-1)$};\let\jtail\xlast% 
        \ExtractCoordinate{$(#1\j-2)$};\let\jhead\xlast% 
        \ifboolexpr{test {\ifboolexpr{test {\ifdimcomp{\itail}{>}{\jtail}} or test {\ifdimcomp{\itail}{=}{\jtail}}}}% If itail is after jtail 
            and% 
            test {\ifdimcomp{\itail}{<}{\jhead}}% If itail is before jhead 
        }% If both are true do: 
        {\ifdimgreater{\yi}{\ylast}% Checks if yj is above or below yi 
            {\ifdimless{\yi-\ylast}{\minvdist}{\dimgdef\minvdist{\yi-\ylast}\global\let\closerj\j}{}}% If above, checks if yi-yj < minvdist 
            {\ifdimless{\ylast-\yi}{\minvdist}{\dimgdef\minvdist{\ylast-\yi}\global\let\closerj\j}{}}}% If below, checks if yj-yi < minvdist 
        {}% If any fails, do nothing 
    }% end of if i=j 
} 

\newcommand{\newpoint}[2]{% Creates new \point#1 commands and its friends (\point#1o and \autoconnect#1) 
    \newcounter{point#1}\setcounter{point#1}{-1}\tikzset{#1/.style={#2}}% Sets counter and style of the point 
    \expandafter\def\csname point#1\endcsname ##1,##2|##3|##4|##5.{% Creates the \point#1 command which draws the arrows 
        \stepcounter{point#1}% Steps point counter 
        \draw[#1] (##1,##2) coordinate (#1\csname thepoint#1\endcsname-1) % Arrow start position 
        (##1+##3/2,##4) node[dot]{} % Goes right halfway the arrow lenght (##3/2) and ##4 up, draws the dot 
        +(##3+##5,0) coordinate (#1\csname thepoint#1\endcsname-node) % Places a coordinate ##5 in front of the arrow 
        (#1\csname thepoint#1\endcsname-1) -- node[above]{$##3$} coordinate (#1\csname thepoint#1\endcsname) ++(##3,0) node[right]{$##5$} coordinate (#1\csname thepoint#1\endcsname-2); % Draws the arrow 
    }% 
    \expandafter\def\csname point#1o\endcsname ##1|##2|##3|##4.{% Creates the variant \point#1o 
        \ExtractCoordinate{$(#1\csname thepoint#1\endcsname-node)$};% Extract last point#1-node coordinate 
        \stepcounter{point#1}% Steps the point counter 
        \draw[#1] (\xlast,##1) coordinate (#1\csname thepoint#1\endcsname-1) % Arrow x start position comes from last point#1-node coordinate 
        (\xlast+##2/2,##3) node[dot]{} % Goes right halfway the arrow lenght (##2/2) and ##3 up, draws the dot 
        +(##2+##4,0) coordinate (#1\csname thepoint#1\endcsname-node) % Places a coordinate ##4 in front of the arrow 
        (#1\csname thepoint#1\endcsname-1) -- node[above]{$##2$} coordinate (#1\csname thepoint#1\endcsname) ++(##2,0) node[right]{$##4$} coordinate (#1\csname thepoint#1\endcsname-2); % Draws the arrow 
    }% 
    \expandafter\def\csname autoconnect#1\endcsname{% Sistematically checks for the closest tail above (if any) and connects it 
        \ifnumgreater{\csname thepoint#1\endcsname}{0}{% Checks if there were more than 1 \point#1 used 
            \foreach \i in {0,...,\csname thepoint#1\endcsname}{% Loops through all arrows i of kind #1 
                \ExtractCoordinate{$(#1\i-1)$};% Gets the arrow coordinates 
                \let\itail\xlast\let\yi\ylast\dimgdef\minvdist{100cm}% 
                \foreach \j in {0,...,\csname thepoint#1\endcsname}{\findcloserj{#1}};% Loops through all other arrows besides i and retrives the closer one 
                \ifdefempty{\closerj}{}{\draw[#1] (#1\closerj-1) -| (#1\i-1) -- (#1\i-2);}% Draws the connection if it exists 
                \gdef\closerj{}% clear the variable for next iteration 
            };\setcounter{point#1}{-1}\renewcommand*{\closerj}{}% Resets the counter and clear the variable for the next loop 
        }{}}% 
    \expandafter\def\csname show#1s\endcsname{% 
        \foreach \i in {0,...,\csname thepoint#1\endcsname}{\node[above left, font=\tiny, red] at (#1\i-2) {#1\i};}; 
    }% 
}

\newpoint{X}{-latex, black} 
\newpoint{K}{-latex, gray} 

\begin{document} 
    \begin{tikzpicture} 
      \blank
      \pointX 0,2|5|4|1.
      \pointX 3,1|5|4|1.
      \pointX 1,4|6|4|3.
      \pointX 1.5,3.5|4|3|5.
      \pointX 4,2.5|4|4|0.
      \pointXo 3|2|3|0.
      \pointXo 2|2|3|1.
      \pointK 2,7|3|-2|1.
      \pointK 4,6|3|1|2.
      \pointKo 7|2|1|0.
      \pointKo 6.5|2|-1|2.
      \pointK 6,5|4|2|5.
      \pointK 10.5,4|2|-2|1.
      \pointXo 2.5|2|3|1.
      \showKs
      \showXs
      \autoconnectK
      \autoconnectX
    \end{tikzpicture} 
\end{document}

コンパイル出力

答え2

主な問題は、最も近い水平ステップへの自動接続です。これは主にアルゴリズムの問​​題です。

1) 1 種類のステップ (X または K) のすべての開始点と終了点のリスト L を作成します。2) このリストをまず x の増加順に並べ替え、x が同一の場合は、終了点を開始点の前に置きます。3) 空のリスト S を作成します。3) L 内の各点が終了点である場合は、対応する開始点を S から削除します。開始点の場合は、a) S を経由して、最も近いステップを見つけます。b) 開始点をこのステップに接続します。c) 開始点を S に追加します。

各時点で、リスト S にはまだ完了していないステップの開始点が含まれています。

これは基本的にこれを実行するコードです。マニュアル接続。これは簡単な部分のはずです。

\documentclass[11pt]{article}

\usepackage{geometry}
\geometry{paperwidth=16in,paperheight=10in,left=1in,right=1in,top=1in,bottom=1in}

\usepackage{xcolor}
\usepackage{tikz}
\usepackage{etoolbox}

\newcount\blankcount \blankcount=0

\newcount\Xsteps
\newcount\Ksteps

\def\clearsteps#1{%
  \csdef{#1points}{}%
  \csuse{#1steps}=0
}

\colorlet{Xcolor}{black}
\colorlet{Kcolor}{black!50}

%\def\pointX{\step X{\LARGE.}}
\def\pointX{\step X{$\bullet$}}
\def\pointK{\step K{o}}


% compare two points X = (n,se,x,y) and X' = (n',se',x',y')
% where:
%   n is the step number
%   se is 1 for a start point, 0 for an end point
%   x and y are the coordinates of the points
% X < X' iff x < x' or (x = x' and (se < se' or (se = se' and y < y')))

\newif\iflessthan
\def\compare(#1,#2,#3,#4)(#5,#6,#7,#8){%
  \ifdim #3pt<#7pt\relax \lessthantrue  \else
  \ifdim #3pt>#7pt\relax \lessthanfalse \else
  % both points have the same x coordinate
  % an end point is smaller than a start point with the same x coordinate
  \ifnum #2<#6\relax \lessthantrue  \else
  \ifnum #2>#6\relax \lessthanfalse \else
  % both point are either start points or end points
  % the smaller one is the one with the smaller y coordinate
  \ifdim #4pt<#8pt\relax \lessthantrue
  \else \lessthanfalse
  \fi\fi\fi\fi\fi
}

% insert the point #2=(n,se,x,y) in the list #1points: #1 = X or K
\newtoks\lsttoks
\def\insertpoint#1#2{%
  \def\lst{#1points}%
  \let\do\relax
  \lsttoks{}%
  \edef\next{\xinsertpoint{#2}\csuse\lst\do.\relax}%
  \next
}
\protected\def\xinsertpoint#1\do#2{%
  \ifx.#2% 
    % we reached the end of the list: insert #1
    % the rest is '\relax'.
    \lsttoks\expandafter{\the\lsttoks\do{#1}}%
    \let\next\finishinsert
  \else
    % the rest of the list is '\do{p}...\do{p}\do.\relax'
    \compare #1#2%
    \iflessthan 
      % (x,se,y) <_lex (x',se',y')
      % insert #1, then #2 and finish
      \lsttoks\expandafter{\the\lsttoks\do{#1}\do{#2}}%
      \let\next\xfinishinsert
    \else
      % (x,se,y) >=_lex (x',se',y')
      % insert #2 then continue
      \lsttoks\expandafter{\the\lsttoks\do{#2}}%
      \let\next\xinsertpoint
    \fi
  \fi
  \next{#1}%
}
% finish the insertion by inserting at once the rest of the list
\def\finishinsert#1\relax{\csedef\lst{\the\lsttoks}}
\def\xfinishinsert#1#2\do.\relax{\csedef\lst{\the\lsttoks #2}}

% connect steps
\def\connectlogically#1{%
  \begingroup
  \def\splist{}% list of start points of unfinished steps
  \colorlet{stepcolor}{#1color}%
  \let\do\connectpoint
  \csuse{#1points}%
  \endgroup
}

% If #1 is a start point, connect it to the nearest overlapping step, if any,
% and add #1 to \splist.
% Otherwise, #1 is an end point: remove the start point from \splist
\def\connectpoint#1{\xconnectpoint#1} % remove the braces around the point
\newif\iffound
\def\xconnectpoint(#1,#2,#3,#4){%
  \ifnum#2=1 % start step: connect to other steps and add the starting point to \splist
    \def\xdo{\yconnect(#1,#3,#4)}%
    \foundfalse
    \splist\relax
    \iffound \draw[line width=2pt, stepcolor] (#3,#4) -- (#3,\ystep); \fi
    \let\xdo\relax
    \edef\splist{\splist\xdo(#1,#3,#4)}%
  \else % end step: remove the starting point from \splist
    \def\removesp##1\xdo(#1,##2,##3)##4\relax{\def\splist{##1##4}}%
    \expandafter\removesp\splist\relax
  \fi
}

\newdimen\yabsdiff
\newdimen\newyabsdiff
\newif\ifnewy

\iftrue

% Search for a step to connect to

% version for connecting only steps with different starting points
\def\yconnect(#1,#2,#3)(#4,#5,#6){%
  \newyfalse
  \ifdim#5pt<#2pt % the step #4 starts strictly before the step #1 and ends after #2
    \newyabsdiff=\dimexpr#3pt-#6pt\relax
    \ifdim\newyabsdiff<0pt \newyabsdiff=-\newyabsdiff\fi
    \newytrue
    \iffound
      \ifdim\newyabsdiff<\yabsdiff \else
        \newyfalse
      \fi
    \fi
    \foundtrue
  \fi
  \ifnewy
    \yabsdiff=\newyabsdiff
    \def\ystep{#6}%
  \else
    % If there are some start points left in \splist, they all have the current x coordinate.
    % The corresponding steps cannot overlap the current point.
    % We can discard all the remaining points.
    \expandafter\endconnect
  \fi
}

\else

% version for connecting steps with identical starting points
\def\yconnect(#1,#2,#3)(#4,#5,#6){%
%  \ifdim#5pt<#2pt % the step #4 starts strictly before the step #1 and ends after #2
    \newyabsdiff=\dimexpr#3pt-#6pt\relax
    \ifdim\newyabsdiff<0pt \newyabsdiff=-\newyabsdiff\fi
    \newytrue
    \iffound
      \ifdim\newyabsdiff<\yabsdiff \else
        \newyfalse
      \fi
    \fi
    \foundtrue
%  \fi
  \ifnewy
    \yabsdiff=\newyabsdiff
    \def\ystep{#6}%
  \else
    % If there are some start points left in \splist, they all have the current x coordinate.
    % We already found a step to connect to and the new one is further:
    % The other ones are even further because we sorted them in increasing y's.
    % We can discard all the remaining points.
    \expandafter\endconnect
  \fi
}

\fi

% discards all remaining points in \splist
\def\endconnect#1\relax{}

\def\blank{%
    \global\advance\blankcount by 1
    \clearsteps X%
    \clearsteps K%
    \draw[black!64,->](0,0)--node[left]{$x\atop y$} (0,8);
    \draw[black!32](0,3)--(16,3)node{};
    \draw[black!64,->](0,0)--node[below]{$a\atop b$} (16,0);
    % I didn't understand {$\csname num:\the\blankcount\endcsname$}.
    % Is it defined somewhere else?
    \node (x\the\blankcount) at (16,8) {\the\blankcount};
}

% Draw a step and add the start point and the end point to the list #1points
% Note: an additional parameter should be supplied for the textual form of
% the label above the edge.
\def\step #1#2#3,#4|#5|#6|#7.{%
   \advance\csuse{#1steps} by 1
   \edef\stepnum{\the\csuse{#1steps}}
   \draw [line width = 2pt, ->, #1color] 
     (#3,#4) node (#1start\stepnum) {} 
     -- node[above] {$#5$} 
     ++(#5,0) node[right] (#1end\stepnum) {$#7$};
   \pgfmathparse{#3+.5*#5} \let\X\pgfmathresult
   \pgfmathparse{#4+#6} \let\Y\pgfmathresult
   \node at (\X,\Y) {#2};
   \pgfmathparse{#3+#5}
   \insertpoint{#1}{(\stepnum,1,#3,#4)}
   \insertpoint{#1}{(\stepnum,0,\pgfmathresult,#4)}
}% step

\begin{document}
\begin{tikzpicture}
\blank
\pointX 0,2|5|4|1.
\pointX 3,1|5|4|1.
\pointX 1,4|6|4|3.
\pointX 1.5,3.5|4|3|5.
\pointX 4,2.5|4|4|2.
\pointK 2,7|3|2|1.
\pointK 4,6|3|1|2.
\pointK 6,5|4|2|5.
\connectlogically X
\connectlogically K
\end{tikzpicture}

\end{document}

ここに画像の説明を入力してください

関連情報