答案1
我确信存在更优化的方法来绘制它,但在纯 tikz 中,下面是一种可能的解决方案。
很抱歉,这不是一个“好”的方法,因为它意味着要手工绘制所有内容。不过,它主要是复制粘贴。我能想到的其他可能性是将所有位置存储在一个表中,然后使用循环绘制它们foreach
,但我不确定如何在这种图表中做到这一点。
结果:
以及相应的代码:
\documentclass[border=3mm]{standalone}
\usepackage{tikz}
\usetikzlibrary{positioning}
\begin{document}
\begin{tikzpicture}[%
helpGrid/.style = {thin, gray!40},
] % End tikzpicture options
%\draw[helpGrid] (0,0) grid (10,10); % Help grid to place drawings. Comment to make it disappear
%- X - Axis (Months). Separation of two coordinates between months.
% Moths 2020
\node[align=center] (Jan20) at (0,1) {Jan \\ 2020};
\node[] (Apr20) at (2,1) {Apr};
\node[] (Jul20) at (4,1) {Jul};
\node[] (Oct20) at (6,1) {Oct};
% Months 2021
\node[align=center] (Jan21) at (8,1) {Jan \\ 2021};
\draw[-latex] (9.5,1.25) -- ++(1,0) node[midway,below] () {Apr};
%- Corresponding other nodes
% Column 1 (Jan 2020)
\node[] (a1) at (0,10) {A};
\node[] (b1) at (0,9) {B};
\node[] (c1) at (0,8) {C};
% Column 2 (Apr (2020)
\node[] (d2) at (2,10) {D};
\node[] (a2) at (2,9) {A};
% Arrows connecting column 1 and 2
\draw[-latex] (a1) to [out = 0, in = 180] (a2);
% Column 3 (Jul 2020)
\node[] (e3) at (4,7) {E};
\node[] (b3) at (4,6) {B};
\node[] (c3) at (4,5) {C};
\node[] (f3) at (4,4) {F};
% Arrows connecting column 2 and 3
\draw[-latex] (b1) to [out = 0, in = 180] (b3);
\draw[-latex] (c1) to [out = 0, in = 180] (c3);
%- Column 4 (Oct)
% Nothing
% Column 5 (Jan 2021)
\node[] (g5) at (8,10) {G};
\node[] (a5) at (8,8) {A};
\node[] (e5) at (8,7) {E};
\node[] (b5) at (8,6) {B};
\node[] (c5) at (8,3) {C};
\node[] (f5) at (8,2) {F};
% Arrows connecting columns 3 and 5
\draw[-latex] (a2) to [out = 0, in = 180] (a5);
\draw[-latex] (e3) -- (e5);
\draw[-latex] (b3) -- (b5);
\draw[-latex] (c3) to [out = 0, in = 180] (c5);
\draw[-latex] (f3) to [out = 0, in = 180] (f5);
\end{tikzpicture}
\end{document}
答案2
我已经为此工作了几天,但已经精疲力竭了。它还没有完全完成,但无论如何我还是想发布它,以防你感兴趣。
我编写了一个程序,可以接受如下输入的数据:
\nextmonth{month}{comma, separated, list, of, events}
并通过运行来解析它并智能地为你创建一个图表\makeflowdiagram
我已经包含了所有注释,大部分理论都在底部。还剩一步,我没有让代码自动插入连接箭头,我在本例中手动放置了它们。如果您更改输入的数据,它将中断连接并且不会编译,除非您注释掉线条图。
主要原则是,在输入数据时,程序会跟踪有多少不同的事件在发生,程序会努力确保有足够的空间让箭头穿过而不会越过其他节点,它通过留出空间来实现这一点。要表示事件链的终止,请+
在事件名称后使用一个符号。接下来的几个月不会为其留出空间,并且在稍后看到同一事件时,程序将开始新的链。
\documentclass{article}
\usepackage{expl3}%for latex3 code
\usepackage{xparse}%for new document command
\usepackage{graphicx}%for graphics
\usepackage{xfp}%for fpeval
\usepackage{parskip}%not needed for this but I cannot live without it
\usepackage{tikz}
\usetikzlibrary{arrows,automata,positioning,calc,backgrounds}
\usetikzlibrary{math,arrows,automata,positioning,calc,decorations.footprints,decorations.fractals,decorations.markings,decorations.pathmorphing,decorations.pathreplacing,decorations.shapes,decorations.text}
\ExplSyntaxOn
\cs_generate_variant:Nn \tl_set:Nn { Ne }
\NewExpandableDocumentCommand{\getlinecolor}{m}{
\clist_item:nn {black, mygoldcolor, black} {#1}
}
\NewExpandableDocumentCommand{\getlinethickness}{m}{
\clist_item:nn {6pt, 2.8pt, 0pt} {#1}
% \clist_item:nn {1.5pt, 0.5pt, 0.1pt} {#1}
}
\seq_new:N \monthlist%stores the list of months
\int_new:N \monthcount \int_set:Nn \monthcount {0}%counts how many months we have
\seq_new:N \eventsraw%stores a token list of the events for each month
\seq_new:N \eventsfilled%stores a token list of the events for each month, including un-terminated fills.
\seq_new:N \eventscount%stores how many events are in each month, for parsing later
\seq_new:N \openevents%this is a list of not-closed events.
\seq_new:N \tobeclosed%this is a list of the to-be-closed events
\seq_new:N \eventssorted%stores a token list of the events for each month, but they are sorted to match the event stack
\seq_new:N \eventstack%this is a global push pop stack of the events used so far
\int_new:N \eventstackcount%tis tells us the length of the event stack
%a command for adding a month to the chart, used like so
%\nextmonth[optional year]{month name}{space separated list of events}
\NewDocumentCommand{\nextmonth}{O{0} m m}{
\seq_put_right:Nn \monthlist {#2}
\seq_put_right:Nn \eventsraw {#3}
\int_incr:N \monthcount
% \counttheevents:n {}
\searchforfillers:n {}
% \manageeventstack:n {}
}
\clist_new:N \tempclist
\clist_new:N \buildclist
\seq_new:N \buildseq
\tl_new:N \templist
\tl_new:N \buildlist
% \tl_new:N \temptoken
\int_new:N \tempcount
\int_new:N \buildcount
%before anything else I need to determine how to parse token lists into words
%then I need to Identify the terminate flag + and remove it
%the open events list will be a as-you-go sorted list
%first we need to parse the new raw events
% for each event in this months eventsraw list
% if the event is in the open event list
% do nothing
% else
% add it
%need to build a filled list now
% for each event in the openevents list
% if the event is this months eventsraw list
% add the event into the eventsfilled list
% else
% add the event into the eventsfilled list and flag it as a filler
%note that this actually will produce a sorted list, using openevents as a as-you-go sorting
% Now I need to handle the termination stuff
% if the event is a terminate
% remove it from the sequence
%sequences do not have a default remove command!
%make a temp sequence
% itterate over the sequence
% if not being terminated
% pop left into temp sequence
% replace original with temp
% else
% do nothing
%I have made a mistake, because termination is handled via a + character, this produces a unique named event and adds it
%this means I need to parse for termination before I do anything else, remove the + and take make a list of to-be-closed
%and then come back eventsfilled has been populated and remove the terminated events form the openevents sequence.
%checks the current months events and compares them to the list of open events, adding fillers if needed.
\cs_generate_variant:Nn \clist_item:Nn { NV }
\cs_generate_variant:Nn \clist_remove_all:Nn { NV }
\cs_new:Nn \searchforfillers:n {
\clist_set:Nx \tempclist {\seq_item:Nn \eventsraw {\monthcount}}%parse the months raw events by comma separation
\int_set:Nn \tempcount {\clist_count:N \tempclist}%determine the length of the months comma list
Events~for~this~month~are~\newline
%first we need to parse the new raw events
\int_step_inline:nnnn { 1 } { 1 } {\tempcount}{%for each event in this months eventsraw list
\tl_set:Nx \templist {\clist_item:Nn \tempclist {##1}}%place the event into an accessable token list
\tl_use:N \templist ~ \rule{0.25in}{0in}
%very first we need to check for termination flags
\tl_if_in:NnT \templist {+} {%scan for termination chacacter
~termination~flag~spotted~%remove the termination from the clist
\tl_remove_all:Nn \templist {+}%remove the character
\clist_remove_all:NV \tempclist \templist
\clist_put_right:NV \tempclist \templist%put it back without the terminaiton marker
\seq_put_right:NV \tobeclosed \templist%store for later
~\tl_use:N \templist~\rule{0.2in}{0in}%remove the character
}
\seq_if_in:NVTF \openevents \templist {%if the event is in the open event list
~is~not~new\newline % do nothing
}{%
~is~new\newline
\seq_put_left:NV \openevents \templist% add it
}
}
The~current~openevents~list~is~
\seq_use:Nn \openevents {~and~} \newline%debug printout \eventstack
%need to build a filled list now
\int_set:Nn \tempcount {\seq_count:N \openevents}%determine the length of the openevents list
\seq_put_right:NV \eventscount \tempcount%store this length int the eventscount sequence
The~tokens~from~openevents~are~\newline
\clist_clear:N \buildclist
\int_step_inline:nnnn { 1 } { 1 } {\tempcount}{%for each event in the openevents list
\tl_set:Nx \templist {\seq_item:Nn \openevents {##1}}%place the event into an accessable token list
\tl_use:N \templist
\clist_if_in:NVTF \tempclist \templist {~no fill\newline
\clist_put_right:NV \buildclist \templist% add the event into the eventsfilled buildclist
}{~fill\newline
\clist_put_right:Nx \buildclist {*}% add the event into the eventsfilled buildclist and flag it as a filler
% \tl_put_left:Nn \templist {*}
% \clist_put_right:NV \buildclist \templist% add the event into the eventsfilled buildclist and flag it as a filler
}%if the event is this months eventsraw list
}
\seq_put_right:Nx \eventsfilled {\clist_use:Nn \buildclist {,}}%store this months built filled list
This~months~eventsfilled~list~is~\seq_item:Nn \eventsfilled {\monthcount}
\newline
% % Now I need to handle the termination stuff
\int_set:Nn \tempcount {\seq_count:N \tobeclosed}%determine the number of closing events
\int_set:Nn \buildcount {\seq_count:N \openevents}%determine the number of open events
% tempcount~is~ \int_use:N \tempcount \newline
% buildcount~is~ \int_use:N \buildcount \newline
\seq_clear:N \buildseq
\int_compare:nTF {\tempcount=0} {nothing~to~close\newline}{
\int_compare:nTF {\buildcount=0} {nothing~to~close2\newline} {
\int_step_inline:nnnn { 1 } { 1 } {\tempcount}{%for each event to be closed
##1 ~event~to~be~closed~
\tl_set:Nx \templist {\seq_item:Nn \tobeclosed {##1}}%place the event into an accessable token list
\tl_use:N \templist \newline
\int_step_inline:nnnn { 1 } { 1 } {\buildcount} {%for each openevent
\tl_set:Nx \buildlist {\seq_item:Nn \openevents {####1}}%place the event into an accessable token list
\tl_if_eq:NNF \templist \buildlist {%if not being terminated
\seq_put_right:NV \buildseq \buildlist
}
}
}
the~build~sequence~is~\seq_use:Nn \buildseq {~and~}\newline
\seq_set_eq:NN \openevents \buildseq
}
}
\seq_clear:N \tobeclosed
}
%old stuff
% %a helper function used to count the number of events in each month entry
% \cs_new:Nn \counttheevents:n {
% \clist_set:Nx \tempclist {\seq_item:Nn \eventsraw {\monthcount}}%parse the months raw events by comma separation
% \clist_use:Nnnn \tempclist {~and~}{~and~}{~and~} \newline
% \int_set:Nn \tempcount {\clist_count:N \tempclist}%determine the length of the months comma list
% \seq_put_right:NV \eventscount \tempcount%store this length int the eventscount sequence
% \rule{0in}{0in}\newline
% \int_use:N \tempcount
% \newline
% % \tl_set:Nx \templist {\seq_item:Nn \eventsraw {\monthcount}}%pull the string from the eventsraw and tokenize it
% % counting~the~events~\tl_use:N \templist
% % \tl_count:N \templist
% % \seq_put_right:Nx \eventscount {\tl_count:N \templist}
% %store the count
% }
% %checks if there are any new events in newly entered month, and if so adds them to the event stack
% \cs_new:Nn \manageeventstack:n {
% \int_set:Nn \tempcount {\tl_count:N \templist}%get the length of the current eventtlist{\monthcount}
% \int_step_inline:nnnn { 1 } { 1 } {\tempcount}{%count from 1 to the the length
% \tl_set:Nx \temptoken {\tl_item:Nn \templist {##1}}%pull the string from the eventsraw and tokenize it
% \seq_if_in:NVTF \eventstack \temptoken {} {\seq_put_left:NV \eventstack \temptoken
% \int_incr:N \eventstackcount%increment the stack cout
% }%if the string is not already in the eventstaci, add it
% }
% \rule{0in}{0in}\newline%debug newline
% \seq_use:Nn \eventstack {~and~}%debug printout \eventstack
% }
% \cs_generate_variant:Nn \tl_if_in:NnTF { NVTF }
% %for each token in \eventstack
% %if the token exists inside the eventsraw{currentmonth}
% %add it to the sorted event list
% \cs_new:Nn \sorteventstack:n {
% \seq_use:Nn \eventstack {}\newline
% \int_step_inline:nnnn { 1 } { 1 } {\monthcount}{%for each month
% \int_step_inline:nnnn { 1 } { 1 } {\eventstackcount}{%for each token in \eventstack
% \tl_set:Nx \temptoken {\seq_item:Nn \eventstack {####1}}%hold the indexed token from the eventstack
% \tl_set:Nx \templist {\seq_item:Nn \eventsraw {##1}}%hold the event token list for the current month
% \tl_if_in:NVTF \templist \temptoken {\tl_put_right:NV \buildlist \temptoken} {}%if no false case
% }
% \seq_put_right:NV \eventssorted \buildlist
% \tl_clear:N \buildlist
% }
% }
%retreival function
\NewDocumentCommand{\printmonthevents}{m}{
For~the~month~of~
\seq_item:Nn \monthlist {#1} ~%this is the space character stand in
there~are~
\seq_item:Nn \eventscount {#1}~%
events,~they~are~
\seq_item:Nn \eventsraw {#1}
}
%this gets the month from the given index
\NewExpandableDocumentCommand{\getmonthcount}{}{
\int_use:N \monthcount
}
%this gets the month from the given index
\NewExpandableDocumentCommand{\getmonth}{m}{
\seq_item:Nn \monthlist {#1}
}
%this gets the number of events for the given index
\NewExpandableDocumentCommand{\geteventscount}{m}{
\seq_item:Nn \eventscount {#1}
}
%a helper function used to count the number of events in each month entry
\NewExpandableDocumentCommand{\geteventstackcount}{}{
\int_use:N \eventstackcount
}
%this gets the list of events for the given index
\NewExpandableDocumentCommand{\geteventsraw}{m}{
\seq_item:Nn \eventsraw {#1}
}
% this gets the a single event for the given index
\NewExpandableDocumentCommand{\getevent}{m m}
{ \exp_args:Ne \tl_item:nn { \seq_item:Nn \eventsraw {#1} } {#2} }
\NewExpandableDocumentCommand{\geteventssorted}{m m}
{ \exp_args:Ne \tl_item:nn { \seq_item:Nn \eventssorted {#1} } {#2} }
\NewExpandableDocumentCommand{\geteventsfilled}{m m}
{ \exp_args:Ne \clist_item:nn { \seq_item:Nn \eventsfilled {#1} } {#2} }
% \tl_new:N \nodeprinter
% \tl_set:Nn \nodeprinter {\node[align=center] (n\m\geteventsfilled{\m}{\j}) at (\fpeval{\m*2},-\j) {\geteventsfilled{\m}{\j}};}
\cs_generate_variant:Nn \tl_if_eq:nnF { enF }
%make function to print out all the stored data
\NewDocumentCommand{\makeflowdiagram}{O{1}}{%optional scaling factor, 0 to 1
% \sorteventstack:n {}
\begin{tikzpicture}[scale=#1]
\foreach \k in {1,...,\getmonthcount}%for every month
\path (\fpeval{\k*2},0) node {\getmonth{\k}};%make a node for the month label
\foreach \m in {1,...,\getmonthcount}{%for every month
\foreach \j in {1,...,\geteventscount{\m}}{%for eachtoken in \eventsraw
\tl_if_eq:enF {\geteventsfilled{\m}{\j}} {*} {
\node[align=center] (n\m\geteventsfilled{\m}{\j}) at (\fpeval{\m*2},-\j) {\geteventsfilled{\m}{\j}};}%make a node at location (month,event number)
}
}
\draw[-latex] (n1Chicken) to [out = 0, in = 180] (n3Chicken);
\draw[-latex] (n3Chicken) to [out = 0, in = 180] (n5Chicken);
\draw[-latex] (n1Dog) to [out = 0, in = 180] (n2Dog);
\draw[-latex] (n1Moose) to [out = 0, in = 180] (n3Moose);
\draw[-latex] (n2Duck) to [out = 0, in = 180] (n5Duck);
\draw[-latex] (n3Cat) to [out = 0, in = 180] (n5Cat);
\draw[-latex] (n5Cat) to [out = 0, in = 180] (n6Cat);
\draw[-latex] (n5Dog) to [out = 0, in = 180] (n6Dog);
% \draw[-latex] (n1B) to [out = 0, in = 180] (n3B);
% \draw[-latex] (n2D) to [out = 0, in = 180] (n5A);
% \draw[-latex] (n2D) to [out = 0, in = 180] (n5B);
\end{tikzpicture}
}
\ExplSyntaxOff
\begin{document}
start of page
\nextmonth[2020]{Jan}{Dog, Chicken, Moose}
\nextmonth{April}{Dog+, Duck}
\nextmonth{July}{Cat, Chicken, Moose+}
\nextmonth{October}{}
\nextmonth[2021]{January}{Cat, Dog, Chicken, Duck}
%terminate C D
\nextmonth[2021]{Feb}{Dog+, Cat}
% \lastmonth{Jan} not yet implimented
\printmonthevents{1}
\printmonthevents{2}
\printmonthevents{3}
\printmonthevents{4}
\printmonthevents{5}
\makeflowdiagram
%in order to automatically place the nodes, the downshift needs to be calculated for each node
%A master list would need to be tallied of all the events ever entered and sorted in an order that is the result of the individual orders within each entry.
%I can only think of two ways to make this work, one, you never enter the events in a wrong order, or two, you specify an order after the data is entered, which seems much better, but you have to type it twice. Like a master key.
%G D A E B C F
%No Crossed Lines
%Top Justified nodes, to maxiize cool curvy lines
%For error rejection I need to make a master list, that can sort new nodes into a master list, while ignoring duplicates, this way if there are any errors in entry order later down the road they just get ignored. A sorted list would work. A sorted list would be really cool, but also more work than I want to do right now. Coding for this case {A D G} {A B G} {B D G} means it would require a full string analysis every time to determine the location of the B characterj
%Note that in the provided example image, the E is below the A. Because the relation of the A dnd E character is not revialed until the month afterwards, in my push pop method this will not happen properly. This is why decoding the master list from all the given new data would be cool. It could also be handled by manually creating a master sort list.
%First I should make it work with just a push pop new events on top method.
%how long does an event chain need to go on? Otherwize said, how many months do we need to leave a space for its potential arrow path? I have demonstrated manually that leaving a space for each node is the solution to, so we need to impliment a terminator, to know when we can reclaim space.
%right now the input parser doesnt actually parse by space separation! it just counts characters. I want to parse words, and the + sign shall be the terminator character, for instance \nextmonth{Jan}{ A B C D+ E} would include months A B C D E, but it would be the last D and would remove it from the sort list for the next month.
%Fillers need to be added upon a \nextmonth{} call, not when sorting. I need to know how many the last one had, and the current order. Infact a global sort wont work anymore, because terminations will allow for same-name entires to show up again later, and since their wont be room for them they will need to go at the top. Actually this true? I could technically insert a node anywhere I wanted to I think. Reguardless, filler additions must be in relation to the prior month, not to the eventstack
\end{document}