3

I am trying to draw a vector clock diagram (primarily for showing causality in distributed systems). What I want is something like this (but not exactly, this is merely an example):

example of diagram

Here is what I have so far:

enter image description here

\documentclass{standalone}
\usepackage{tikz}
\usepackage{amsmath}
\usetikzlibrary{arrows.meta}

\newcommand{\clockdiagram}[3]{
% #1: number of processes
% #2: list of events in the format (process_id, x_pos, label (can be scalar or vector))
% #3: list of messages in the format (from_event_index, to_event_index), where event_index refers to index in #2

\begin{tikzpicture}[
>=Stealth,
event/.style={circle, fill},
timeline/.style={->, thick},
connection/.style={->, dashed}
]

% Create timelines and events
\foreach \process [count=\i from 1] in {1,...,#1} {
\node (start) at (0, -2*\i) {P\i};
\node (end) at (10, -2*\i) {};
\draw[->, thick] (start) -- (end);
}
\foreach \event [count=\j from 0] in #2 {
\pgfmathsetmacro{\proc}{{\event}[0]}
\pgfmathsetmacro{\xpos}{{\event}[1]}
\pgfmathsetmacro{\eventindex}{{\event}[2]}
\node[event,label=above:{\eventindex}] at (\xpos,-2*\proc) (e\j) {};
}
\foreach \conn in #3 {
\pgfmathsetmacro{\from}{{\conn}[0]}
\pgfmathsetmacro{\to}{{\conn}[1]}
\pgfmathsetmacro{\fromtime}{{#2}[\from][2]}
\draw[connection] (e\from) -- (e\to) node[midway,above] {\fromtime};
}

\end{tikzpicture}
}

\begin{document}

% Example usage
\clockdiagram{2}{{
(1, 2, {1, 0}), % TODO: Display as [1, 0] instead of 10
(2, 5, 5) 
}}{{
(0,1)
}}

\end{document}

I want to ensure that [1, 0] is displayed in the form of a vector (any representation works, it just has to be semantically different from a scalar), and not as 10.

Edit: This is my first attempt to tikz properly, and so there might be some mistakes. The DSL has basically come out of experimentation, and I am happy to learn better ways to do the same thing. Essentially, what I want is a macro for which I can specify the events with time, along with messages between events.

15
  • your code fails to compile due to undefined macros. how is \pgfmathprintmatrix defined?
    – cfr
    Commented Aug 10 at 0:25
  • 1
    Can you complete your question by showing what would be the ideal DSL, syntax, to define such kind of diagrams?
    – projetmbc
    Commented Aug 10 at 8:03
  • 1
    @projetmbc the complete DSL can be seen in the last few lines of the MWE.
    – mon
    Commented Aug 10 at 8:26
  • 1
    what is DSL? ..
    – cfr
    Commented Aug 10 at 11:44
  • 1
    @projetmbc Thank you. (something specific to Distributed Systems also seemed possible.)
    – cfr
    Commented Aug 10 at 14:45

1 Answer 1

5

Here is one way to do it.

The process is bottum-up (i.e. starting simple) combined with refactoring (generalize multiplicates). Any such code can be considered transitory, i.e. as a kind of snapshot, which works and still can be made more universal.

With two typos ...

result

Bottom-up idea

  • draw the three lines AND put nodes there via pos
  • move the nodes names a, b, ... inside the nodes to reduce visual noise
  • put the vector-labels
  • connect all relevant nodes
  • put some extras, like P1..P3
  • on the way introduce styles as needed (which is already one act of refactoring)

Universal macro as you intend

Following this approach I suggest using a mixed approach. First, let's have a look at a section where refactoring/generalization is simple to do:

% ~~~ refactored ~~~~~~~~~~~
\newcommand\vlab[3]{\foreach \v [count=\j] in {#2}%
        \node[#3] at (#1\j) {\v};}
...
    % ~~~ vector labels ~~~~~~~~~~~~~~~~
    %% BEFORE refactoring
%   \foreach \v [count=\j] in {(1,0,0), (2,0,0), (3,1,0), 
%                              (4,1,0), (5,1,2), (6,1,2), (7,1,2)}
%       \node[vlu] at (A\j) {\v};
%       
%   \foreach \v [count=\j] in {(0,1,0), (2,2,0), (6,3,2)}
%       \node[vld] at (B\j) {\v};
%       
%   \foreach \v [count=\j] in {(1,0,1), (0,0,2)}
%       \node[vld] at (C\j) {\v};
        
    %% AFTER refactoring
    \vlab{A}{((1,0,0), (2,0,0), (3,1,0), (4,1,0), (5,1,2), (6,1,2), (7,1,2))}{vlu}
    \vlab{B}{(0,1,0), (2,2,0), (6,3,2)}{vld}
    \vlab{C}{(1,0,1), (0,0,2)}{vld}

I started with the 3 \foreach loops, which could be absorbed easily by macro \vlab. The order of parameters is historical: it may be better to chose a different order (which is a refactoring consideration).

Whether or not you want to even absorb the nodes "name" A, B, C also, is a matter of preference. In this place code would look more complicated afterwards with little to no benefit (which is a refactoring decision).

Second, a place where it's harder to do:

    % ~~~ lines with events ~~~~~~~~~
    \draw (0,4) --  node[ev,pos=.1] (A1) {a}
                    node[ev,pos=.3] (A2) {b}
                    node[ev,pos=.4] (A3) {c}
                    node[ev,pos=.6] (A4) {d}
                    node[ev,pos=.7] (A5) {f}
                    node[ev,pos=.8] (A6) {g}
                    node[ev,pos=.95] (A7) {h}
        +(10,0);
...

AFAIK, these nodes can't be absorbed by a \foreach loop from Tikz. But you can use other loop constructs, if you want. My preference would be: use it as-is, copy&paste & modify as needed. I.e. leave as-is (which is a refactoring decision).

If you follow this approach, with frequent compile, back-up and refactoring, you'll approach the macro you started out more and more, but in an organic way. And it certainly will look very diffent in the end on code level.

By intention I left some places without refactoring:

  • this happens anyway
  • is both transitory and a demo

Here, moving the nodes options into a style is what a refactorer can't overlook ;-)

    % ~~~ axis labels ~~~~~~~~~
    %     unrefactored
    \node[anchor=east] at (0,4) {P1};
    \node[anchor=east] at (0,2) {P2};
    \node[anchor=east] at (0,0) {P3};

The rest is pretty straight forward ...


\documentclass[10pt,border=3mm,tikz]{standalone}
\usetikzlibrary{arrows.meta}

% ~~~ refactored ~~~~~~~~~~~
\newcommand\vlab[3]{\foreach \v [count=\j] in {#2}%
        \node[#3] at (#1\j) {\v};}

% ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
\begin{document}
 \begin{tikzpicture}[
    ev/.style={circle,fill=white,draw,font=\small},
    >={Stealth},
    vlu/.style={cyan,yshift=6mm,font=\small},   % task; refactor vlu+vld
    vld/.style={cyan,yshift=-6mm,font=\small},
 ]
    % ~~~ lines with events ~~~~~~~~~
    \draw (0,4) --  node[ev,pos=.1] (A1) {a}
                    node[ev,pos=.3] (A2) {b}
                    node[ev,pos=.4] (A3) {c}
                    node[ev,pos=.6] (A4) {d}
                    node[ev,pos=.7] (A5) {f}
                    node[ev,pos=.8] (A6) {g}
                    node[ev,pos=.95] (A7) {h}
        +(10,0);

    \draw (0,2) --  node[ev,pos=.3] (B1) {i}
                    node[ev,pos=.4] (B2) {j}
                    node[ev,pos=.88] (B3) {k}
        +(10,0);
        
    \draw (0,0) --  node[ev,pos=.2] (C1) {l}
                    node[ev,pos=.5] (C2) {m}
        +(10,0);
    
    % ~~~ vector labels ~~~~~~~~~~~~~~~~
    %% BEFORE refactoring
%   \foreach \v [count=\j] in {(1,0,0), (2,0,0), (3,1,0), 
%                              (4,1,0), (5,1,2), (6,1,2), (7,1,2)}
%       \node[vlu] at (A\j) {\v};
%       
%   \foreach \v [count=\j] in {(0,1,0), (2,2,0), (6,3,2)}
%       \node[vld] at (B\j) {\v};
%       
%   \foreach \v [count=\j] in {(1,0,1), (0,0,2)}
%       \node[vld] at (C\j) {\v};
        
    %% AFTER refactoring
    \vlab{A}{((1,0,0), (2,0,0), (3,1,0), (4,1,0), (5,1,2), (6,1,2), (7,1,2))}{vlu}
    \vlab{B}{(0,1,0), (2,2,0), (6,3,2)}{vld}
    \vlab{C}{(1,0,1), (0,0,2)}{vld}
        
    % ~~~ connectors ~~~~~~~~~~~
    \draw[->] (A2) -- (B2);
    \draw[->] (B1) -- (A3);
    \draw[->] (C2) -- node[pos=.7,anchor=west] {note} (A5);
    \draw[->] (A6) -- (B3);
    
    % ~~~ axis labels ~~~~~~~~~
    %     unrefactored
    \node[anchor=east] at (0,4) {P1};
    \node[anchor=east] at (0,2) {P2};
    \node[anchor=east] at (0,0) {P3};
    
 \end{tikzpicture}
\end{document}
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .