diff options
author | Norbert Preining <norbert@preining.info> | 2021-01-28 03:01:03 +0000 |
---|---|---|
committer | Norbert Preining <norbert@preining.info> | 2021-01-28 03:01:03 +0000 |
commit | 580a384585ecc80778ebca5845ce3bd7ae60cb48 (patch) | |
tree | 31feee0337728a2e742fddbdefd62ffc17c45008 /graphics/pgf/contrib/sankey | |
parent | 71fd3749ed2ec982d2ef2863bf8e99760d33b562 (diff) |
CTAN sync 202101280301
Diffstat (limited to 'graphics/pgf/contrib/sankey')
-rw-r--r-- | graphics/pgf/contrib/sankey/README | 16 | ||||
-rw-r--r-- | graphics/pgf/contrib/sankey/sankey-doc.pdf | bin | 0 -> 672933 bytes | |||
-rw-r--r-- | graphics/pgf/contrib/sankey/sankey-doc.tex | 1599 | ||||
-rw-r--r-- | graphics/pgf/contrib/sankey/sankey.sty | 722 | ||||
-rw-r--r-- | graphics/pgf/contrib/sankey/tikzlibrarydubins.code.tex | 361 |
5 files changed, 2698 insertions, 0 deletions
diff --git a/graphics/pgf/contrib/sankey/README b/graphics/pgf/contrib/sankey/README new file mode 100644 index 0000000000..ae6f9721be --- /dev/null +++ b/graphics/pgf/contrib/sankey/README @@ -0,0 +1,16 @@ +sankey +====== + +LaTeX package to build Sankey diagram. + + +license +======= + +Copyright (C) 2016-2021 by Paul Gaborit + +This file may be distributed and/or modified + + 1. under the LaTeX Project Public License and/or + + 2. under the GNU Public License. diff --git a/graphics/pgf/contrib/sankey/sankey-doc.pdf b/graphics/pgf/contrib/sankey/sankey-doc.pdf Binary files differnew file mode 100644 index 0000000000..f8d77bd13b --- /dev/null +++ b/graphics/pgf/contrib/sankey/sankey-doc.pdf diff --git a/graphics/pgf/contrib/sankey/sankey-doc.tex b/graphics/pgf/contrib/sankey/sankey-doc.tex new file mode 100644 index 0000000000..768e2d0be1 --- /dev/null +++ b/graphics/pgf/contrib/sankey/sankey-doc.tex @@ -0,0 +1,1599 @@ +% -*- coding: utf-8; -*- + +% Copyright (C) 2016-2021 by Paul Gaborit +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% +% 2. under the GNU Public License. + +\documentclass[a4paper,onecolumn,10pt]{article} +%\usepackage[rgb]{xcolor} +\usepackage[paper=a4paper,vmargin=1.5cm,hmargin=4cm]{geometry} +\usepackage[utf8]{inputenc} +\usepackage[T1]{fontenc} +\usepackage{xcolor} +\usepackage[colorlinks]{hyperref} +\usepackage{fancyvrb} +\usepackage{listings} +\usepackage{enumitem} +\usepackage{pgfilecontents} +\usepackage{microtype} +\usepackage{bookmark} +\usepackage{cmap} +\usepackage{sankey} +\usepackage{siunitx} +\usepackage{mdframed} +\usepackage{footnote} +\usepackage{accsupp} +\usetikzlibrary{positioning,patterns.meta} +\usepackage[british]{babel} + +\colorlet{fondcode}{gray!5!white} +\colorlet{keyword}{blue!50!cyan!50!black} +\colorlet{comment}{red!75!black} + +\newcommand\emptyaccsupp[1]{\BeginAccSupp{ActualText={}}#1\EndAccSupp{}} + +\lstset{ + literate= + {á}{{\'a}}1 {é}{{\'e}}1 {í}{{\'i}}1 {ó}{{\'o}}1 {ú}{{\'u}}1 + {Á}{{\'A}}1 {É}{{\'E}}1 {Í}{{\'I}}1 {Ó}{{\'O}}1 {Ú}{{\'U}}1 + {à}{{\`a}}1 {è}{{\`e}}1 {ì}{{\`i}}1 {ò}{{\`o}}1 {ù}{{\`u}}1 + {À}{{\`A}}1 {È}{{\'E}}1 {Ì}{{\`I}}1 {Ò}{{\`O}}1 {Ù}{{\`U}}1 + {ä}{{\"a}}1 {ë}{{\"e}}1 {ï}{{\"i}}1 {ö}{{\"o}}1 {ü}{{\"u}}1 + {Ä}{{\"A}}1 {Ë}{{\"E}}1 {Ï}{{\"I}}1 {Ö}{{\"O}}1 {Ü}{{\"U}}1 + {â}{{\^a}}1 {ê}{{\^e}}1 {î}{{\^i}}1 {ô}{{\^o}}1 {û}{{\^u}}1 + {Â}{{\^A}}1 {Ê}{{\^E}}1 {Î}{{\^I}}1 {Ô}{{\^O}}1 {Û}{{\^U}}1 + {œ}{{\oe}}1 {Œ}{{\OE}}1 {æ}{{\ae}}1 {Æ}{{\AE}}1 {ß}{{\ss}}1 + {ç}{{\c c}}1 {Ç}{{\c C}}1 {ø}{{\o}}1 {å}{{\r a}}1 {Å}{{\r A}}1 + {€}{{\texteuro}}1 {£}{{\pounds}}1 {°}{{\degres}}1 + {’}{{\textquoteright}}1 {‘}{{\textquoteleft}}1 + {«}{{<<}}1 {»}{{>>}}1 + {—}{{---}}1 {−}{{$-$}}1 +} + +\lstset{ + fancyvrb=true, + %fvcmdparams=samepage, + escapechar=`, % pour introduire du latex dans un environnement verbatim + extendedchars=true, + fontadjust=true, + columns=fullflexible, % pour pouvoir copier/coller les chaines de caractères + flexiblecolumns=true, + % frame=leftline, + inputencoding=utf8, + keepspaces=true, % keeps spaces in text, code indentation + backgroundcolor=\color{fondcode}, + basicstyle=\mdseries\ttfamily, + keywordstyle=\color{keyword}\bfseries,% + stringstyle=\ttfamily\color{green!50!black},% + commentstyle=\color{comment}\itshape,% + emphstyle=\bfseries\color{red}, + numbersep=5pt, + %numbers=left, + numberstyle=\tiny\emptyaccsupp, + showstringspaces=false, + upquote=true, % pour pouvoir copier/coller le code avec des quotes + aboveskip=\parskip, + belowskip=\parskip, + framexleftmargin=1pt, + framexrightmargin=1pt, +} + +\lstdefinestyle{textsmall}{basicstyle=\color{black}\small\mdseries\ttfamily} +\lstdefinestyle{textfootnotesize}{basicstyle=\color{black}\footnotesize\mdseries\ttfamily} +\lstdefinestyle{textscriptsize}{basicstyle=\color{black}\scriptsize\mdseries\ttfamily} +\lstdefinestyle{texttiny}{basicstyle=\color{black}\tiny\mdseries\ttfamily} + +\lstdefinestyle{LaTeX}{ + language=[LaTeX]TeX, + %backgroundcolor=\color{latexbg}, + moretexcs={ + coordinate, + definecolor, + draw, + fill, + foreach, + hashband, + node, + sankeyadvance, + sankeydubins, + sankeyend, + sankeyend, + sankeyfork, + sankeynode, + sankeynodealias, + sankeynodeend, + sankeynodestart, + sankeyoutin, + sankeyset, + sankeystart, + sankeyturn, + sisetup, + tikzset, + turnandstop, + usepackage, + } +} +\lstdefinestyle{LaTeXsmall}{style=LaTeX,style=textsmall} +\lstdefinestyle{LaTeXfootnotesize}{style=LaTeX,style=textfootnotesize} +\lstdefinestyle{LaTeXscriptsize}{style=LaTeX,style=textscriptsize} +\lstdefinestyle{LaTeXtiny}{style=LaTeX,style=texttiny} + +\def\code{\lstinline[basicstyle=\mdseries\ttfamily\color{red!50!black}]} + +\edef\samplecodename{\jobname-code.vrb} + +\newcommand\constant[1]{\textcolor{violet}{\texttt{#1}}} +\newcommand\ARG[1]{\texttt{\{#1\}}} +\newcommand\OPTARG[1]{\textcolor{green!50!black}{\texttt{[#1]}}} +\newcommand\VAR[1]{\textit{\texttt{<#1>}}} +\newcommand\NOTE[1]{\leavevmode\marginpar{#1}} + +\makeatletter +\def\mynobreakpar{\par\nobreak\@afterheading} +\makeatother +\def\docprefix#1{\texttt{\textcolor{gray}{#1}}} +\def\sankeykeysprefix{/sankey} + +\newenvironment{sankeyoption}[5][]{% [prefix] key, val, default, initially + % \begingroup + \bgroup% + % + \def\path{#1}% + \def\key{#2}% + \def\val{#3}% + \def\default{#4}% + \def\initially{#5}% + %\setlength\parindent{\dimexpr.5\parindent\relax}% + % \vspace{.25\baselineskip plus .25\baselineskip minus 0mm} + \parfillskip 0pt plus 1fil% + \leavevmode% + \ttfamily% + \hspace*{-1cm}\textcolor{red!75!black}{\texttt{\docprefix{\ifdefempty{\path}{\sankeykeysprefix/}{\path/}}\key}}% + \ifdefempty{\val}{}{=\textit{\val}}% + \hfill + \ifdefempty{\default}{\null}{(default:\,\constant{\default})}% + \ifdefempty{\initially}{\null}{(initially:\,\constant{\initially})}% + \mynobreakpar\egroup% + \parskip=.5\baselineskip plus .25\baselineskip minus .25\baselineskip + \parfillskip=30pt plus 1fil + \itemize[topsep=0pt,partopsep=0pt,itemsep=0pt]\item[] +}{% + \enditemize% +} + +\newcommand\MACRO{\hspace*{-1cm}} + +\newlist{sankeyconstantsdesc}{description}{1} +\setlist[sankeyconstantsdesc]{align=right,labelindent=1.5em,labelsep=.5em,leftmargin=!,font=\normalfont} + +\newlength{\myparskip} +\setlength{\myparskip}{.75\baselineskip plus 8\baselineskip minus .25\baselineskip} +\usepackage[skip=\myparskip]{parskip} +%\setlength{\parskip}{\myparskip} +\newenvironment{miniblock}{% + \vspace{.5\parskip}% + \csname minipage*\endcsname[t]{1.0\linewidth}% + \setlength{\parskip}{\myparskip}% +}{% + \csname endminipage*\endcsname% +} + + +\makeatletter +\title{How to use the \texttt{sankey} package\\ to build Sankey diagrams\footnote{This file described version v\sankey@version.}} +\date{Released \sankey@date} +\author{Paul Gaborit\footnote{\href{mailto:paul.gaborit@gmail.com}{\texttt{paul.gaborit@gmail.com}}}} + +\hypersetup{ + pdftitle=How to use the sankey package to buid Sankey diagrams (v\sankey@version), + pdfauthor=Paul Gaborit, +} +\makeatother + + +\begin{document} +\maketitle + +\begin{abstract} + The \texttt{sankey} package provides macros and environments to build + \emph{Sankey diagrams}% + \footnote{\url{https://en.wikipedia.org/wiki/Sankey_diagram}} (or + \emph{flow diagram} in which the width of the arrows is proportional + to the flow rate). The idea cames out from + \href{https://tex.stackexchange.com/q/40159/14500}{this question} on + \href{https://tex.stackexchange.com/}{TeX.StackExchange}. + % +\end{abstract} + +{ + \setlength{\parskip}{0pt} + \tableofcontents + \listoffigures +} + +% ------------------------------------------------------------ +\section{Preamble} +% ------------------------------------------------------------ + +To use the \code{sankey} package, insert the following line in your preamble: + +\begin{lstlisting}[style=LaTeXsmall] +\usepackage{sankey} +\end{lstlisting} + +% ------------------------------------------------------------ +\section{The \texttt{sankeydiagram} environment} +% ------------------------------------------------------------ + +A \code{sankeydiagram} environment nested in a \code{tikzpicture} +environment activates the sankey macros: + +\begin{lstlisting}[style=LaTeXsmall] +\begin{tikzpicture} + \begin{sankeydiagram}[... options ...] + ... sankey macros ... + \end{sankeydiagram} +\end{tikzpicture} +\end{lstlisting} + +% ------------------------------------------------------------ +\section{Sankey diagram options} +% ------------------------------------------------------------ + +The \code{sankey} package uses \code{pgfkeys} to define options via +\emph{key=value} pairs. + +The options can be defined via the optional argument of the +\code{sankeydiagram} environment: + +\begin{lstlisting}[style=LaTeXsmall] +\begin{sankeydiagram}[debug=true] + \sankeynode{name=a,quantity=1,angle=0,at={0,0}} +\end{sankeydiagram} +\end{lstlisting} + +The options can also be modified via the \code{\sankeyset} macro: + +\begin{lstlisting}[style=LaTeXsmall] +\begin{sankeydiagram} + \sankeyset{debug=true} + \sankeynode{name=a,quantity=1,angle=0,at={0,0}} +\end{sankeydiagram} +\end{lstlisting} + +The options can be temporarily modified for a single macro: +\begin{lstlisting}[style=LaTeXsmall] +\begin{sankeydiagram} + \sankeynode[debug=true]{name=a,quantity=1,angle=0,at={0,0}} +\end{sankeydiagram} +\end{lstlisting} +% ------------------------------------------------------------ +\subsection{Keys to choose the scale} +% ------------------------------------------------------------ + +The scale or ratio of the Sankey diagram is the ratio between the % +\code{ratio length} and the \code{ratio quantity}. + +\begin{sankeyoption}{ratio quantity}{\VAR{number}}{}{10} + Quantity (in units of flow) to define ratio. The \VAR{number} can be + any math expression. +\end{sankeyoption} + +\begin{sankeyoption}{ratio length}{\VAR{distance}}{}{1cm} + Distance (a graphical distance) to define scale. +\end{sankeyoption} + +\begin{sankeyoption}{ratio}{\VAR{distance}/\VAR{number}}{}{1cm/10} + Fix the ratio to \VAR{distance}/\VAR{number}. +\end{sankeyoption} + +The initial ratio is 1\,cm/10 units. + + +\textbf{Note:} the \code{sankey} package uses the \code{xfp} package to +evaluate all math expressions that use quantities (in units of +flow). You can therefore use quantities of a very large or very small +order of magnitude. In contrast, for graphic distances, the sankey +package uses the \code{pgfmath} package (all calculations must not +exceed $\pm 16383.99999$). + +% ------------------------------------------------------------ +\subsection{Keys to define drawing parameters} +% ------------------------------------------------------------ + +\begin{sankeyoption}{minimum radius}{\VAR{distance}}{}{5mm} + The minimum radius used by \code{\sankeyturn} and + \code{\sankeydubins}. +\end{sankeyoption} + +\begin{sankeyoption}{outin steps}{\VAR{integer}}{}{10} + Number of steps used by the \code{\sankeyoutin} macro to simulate flow + lanes with constant width. +\end{sankeyoption} + +% ------------------------------------------------------------ +\subsection{Keys to choose drawing styles} +% ------------------------------------------------------------ + +\begin{sankeyoption}{fill/.style}{\VAR{style}}{}{{line width=0pt,fill=white}} + This TikZ style is used to \emph{fill} all sankey paths. +\end{sankeyoption} + +\begin{sankeyoption}{draw/.style}{\VAR{style}}{}{draw=black,line width=.4pt} + This TikZ style is used to \emph{draw} all sankey paths. +\end{sankeyoption} + +\begin{sankeyoption}{start style}{\VAR{style name}}{}{none} + There are three predefined \emph{start} styles: \constant{none}, + \constant{simple}, \constant{arrow}. +\end{sankeyoption} + +\begin{sankeyoption}{end style}{\VAR{style name}}{}{none} + There are three predefined \emph{end} styles: \constant{none}, + \constant{simple}, \constant{arrow}. +\end{sankeyoption} + +% ------------------------------------------------------------ +\subsection{Keys to define new \emph{start} and \emph{end} styles} +% ------------------------------------------------------------ + +\begin{sankeyoption}{new start style}{\{\VAR{name}\}\{\VAR{fill + path}\}\{\VAR{draw path}\}}{}{} + Define the new start style named \VAR{name} with its \VAR{fill + path} and its \VAR{draw path}. +\end{sankeyoption} + +\begin{sankeyoption}{new end style}{\{\VAR{name}\}\{\VAR{fill + path}\}\{\VAR{draw path}\}}{}{} + Define the new end style named \VAR{name} with its \VAR{fill + path} and its \VAR{draw path}. +\end{sankeyoption} + +\emph{Fill} and \emph{draw} paths are composed in a TikZ scope where +the origin is the current Sankey node center (and its name is +\code{\name}) and the coordinate system is rotated by its orientation. + +% ------------------------------------------------------------ +\subsection{The \emph{debug} key} +% ------------------------------------------------------------ + +\begin{sankeyoption}{debug}{\VAR{boolean}}{true}{false} + To debug a sankey diagram. +\end{sankeyoption} + +% ------------------------------------------------------------ +\section{Sankey nodes and flows} +% ------------------------------------------------------------ + +% ------------------------------------------------------------ +\subsection{Create Sankey nodes} +% ------------------------------------------------------------ + +% ------------------------------------------------------------ + +\begin{miniblock} +\MACRO\code{\sankeynode}\OPTARG{\VAR{options}}\ARG{\VAR{node parameters}} + +The \code{\sankeynode} macro defines a Sankey node. The \VAR{options} +can be any Sankey diagram keys. To define a Sankey node, you must +provide a \emph{name}, a \emph{quantity}, an \emph{angle} and a +\emph{position} as \VAR{node parameters}. + +\begin{sankeyoption}[/sankey/node parameters]{name}{\VAR{name}}{}{} + The \VAR{name} of the new Sankey node (and the associated TikZ node). +\end{sankeyoption} + +\begin{sankeyoption}[/sankey/node parameters]{quantity}{\VAR{quantity}}{}{} + The quantity (in flow unit) of the new Sankey node. The \VAR{quantity} + can be any math expression. +\end{sankeyoption} + +\begin{sankeyoption}[/sankey/node parameters]{angle}{\VAR{angle}}{}{} + The orientation of the flow (0 points to the right) of the new Sankey + node. +\end{sankeyoption} + +\begin{sankeyoption}[/sankey/node parameters]{at}{\VAR{at}}{}{} + The position of the new Sankey node (a TikZ coordinate \emph{without} + round brackets or parentheses). +\end{sankeyoption} + +\begin{sankeyoption}[/sankey/node parameters]{anchor}{\VAR{anchor}}{center}{center} + Specify the anchor of the Sankey node. Possible values are + \constant{center}, \constant{left} or \constant{right}. +\end{sankeyoption} + +\begin{sankeyoption}[/sankey/node parameters]{as}{\VAR{name}}{}{} + Copy the \emph{name}, the \emph{quantity}, the \emph{angle} and the + \emph{position} of the Sankey node named \VAR{name}. +\end{sankeyoption} + +A Sankey node is also a Tikz node but with only three anchors: +\code{left}, \code{center} and \code{right}. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[debug] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynode{name=a,quantity=10,angle=0,at={0,0}} + \sankeynode{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeynode{name=c,quantity=20,angle=-90,at={5,1},anchor=right} + + \foreach \nodename/\pos in {a/left,b/below left,c/above}{ + \foreach \ancname in {left,center,right}{ + \node[node font=\ttfamily\footnotesize,\pos=1mm of \nodename.\ancname, + inner sep=0pt,rotate=\sankeygetnodeorient{\nodename},anchor=east] + {\ancname\vphantom{g}}; + \fill[black] (\nodename.\ancname) circle(1pt); + } + } + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} +% ------------------------------------------------------------ + +\begin{miniblock} +\MACRO\code{\sankeynodestart}\OPTARG{\VAR{options}}\ARG{\VAR{node parameters}} + +The \code{\sankeynodestart} creates and fills/draws a starting Sankey node: + + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[start style=arrow,fill/.style={fill=orange!20}] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynodestart{name=a,quantity=10,angle=0,at={0,0}} + \sankeynodestart{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeynodestart{name=c,quantity=20,angle=-90,at={5,1},anchor=right} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} +% ------------------------------------------------------------ + + +\begin{miniblock} +\MACRO\code{\sankeynodeend}\OPTARG{\VAR{options}}\ARG{\VAR{node + parameters}} + +The \code{\sankeynodeend} creates +and fills/draws an ending Sankey node: + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[end style=simple,fill/.style={fill=orange!20}] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynodeend{name=a,quantity=10,angle=0,at={0,0}} + \sankeynodeend{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeynodeend{name=c,quantity=20,angle=-90,at={5,1},anchor=right} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\subsection{Retrieve informations from a Sankey node} + +\MACRO\code{\sankeygetnodeqty}\ARG{\VAR{node name}} + +The expandable command \code{\sankeygetnodeqty} returns the quantity +assigned to the Sankey node named \VAR{node name}. + +\MACRO\code{\sankeygetnodeorient}\ARG{\VAR{node name}} + +The expandable command \code{\sankeygetnodeorient} returns the angle +(orientation) assigned to the Sankey node named \VAR{node name}. + +% ------------------------------------------------------------ +\newpage +\subsection{Move nodes} +% ------------------------------------------------------------ + +The \code{\sankeyadvance} and \code{\sankeyturn} commands move a Sankey +node (and fill/draw a portion of the Sankey flow). Then the previous +position of the Sankey node is accessible via the \code{-old} suffix (if +you move the \code{a} node, its previous position is the \code{a-old} +node). + +\subsubsection{forward commands} + +\begin{miniblock} +\MACRO\code{\sankeyadvance}\OPTARG{\VAR{options}}\ARG{\VAR{node + name}}\ARG{\VAR{distance}} + +The \code{\sankeyadvance} moves the sankey node straight ahead and +fills/draws this portion of the sankey path. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[start style=arrow,fill/.style={fill=orange!20}] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynodestart{name=a,quantity=10,angle=0,at={0,0}} + \sankeyadvance{a}{1cm} + \sankeynode[debug]{as=a} + \sankeynode[debug]{as=a-old} + \sankeynodestart{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeyadvance{b}{1cm} + \sankeynodestart{name=c,quantity=10,angle=-90,at={5,1},anchor=right} + \sankeyadvance{c}{5mm} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\begin{miniblock} +\MACRO\code{\sankeyturn}\OPTARG{\VAR{options}}\ARG{\VAR{node + name}}\ARG{\VAR{angle}} + +The \code{\sankeyturn} macro moves the sankey node by turning to one +side or the other and fills/draws this portion of the sankey path. A +\emph{positive} \VAR{angle} turns left while a \emph{negative} +\VAR{angle} turns right. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[start style=arrow,fill/.style={fill=orange!20}] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynodestart{name=a,quantity=10,angle=0,at={0,0}} + \sankeyturn{a}{90} + \sankeynode[debug]{as=a} + \sankeynode[debug]{as=a-old} + \sankeynodestart{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeyturn[minimum radius=1cm]{b}{-45} + \sankeynodestart{name=c,quantity=10,angle=-90,at={5,1},anchor=right} + \sankeyturn{c}{180} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\subsubsection{backward commands} + +\begin{miniblock} +\MACRO\code{\sankeyadvance*}\OPTARG{\VAR{options}}\ARG{\VAR{node + name}}\ARG{\VAR{distance}} + +The \code{\sankeyadvance*} moves the sankey node straight back and +fills/draws this portion of the sankey path. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[end style=arrow,fill/.style={fill=orange!20}] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynodeend{name=a,quantity=10,angle=0,at={0,0}} + \sankeyadvance*{a}{1cm} + \sankeynode[debug]{as=a} + \sankeynode[debug]{as=a-old} + \sankeynodeend{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeyadvance*{b}{1cm} + \sankeynodeend{name=c,quantity=10,angle=-90,at={5,1},anchor=right} + \sankeyadvance*{c}{5mm} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\begin{miniblock} +\MACRO\code{\sankeyturn*}\OPTARG{\VAR{options}}\ARG{\VAR{node + name}}\ARG{\VAR{angle}} + +The \code{\sankeyturn*} macro moves the sankey node backwards by turning +right or left and fills/draws this portion of the sankey path. A +\emph{positive} \VAR{angle} turns left while a \emph{negative} +\VAR{angle} turns right. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[end style=simple,fill/.style={fill=orange!20}] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynodeend{name=a,quantity=10,angle=0,at={0,0}} + \sankeyturn*{a}{90} + \sankeynode[debug]{as=a} + \sankeynode[debug]{as=a-old} + \sankeynodeend{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeyturn*[minimum radius=1cm]{b}{-45} + \sankeynodeend{name=c,quantity=10,angle=-90,at={5,1},anchor=right} + \sankeyturn*{c}{180} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\subsection{Links between nodes} + +\begin{miniblock} + \MACRO\code{\sankeyoutin}\OPTARG{\VAR{options}}\ARG{\VAR{node + A}}\ARG{\VAR{node B}} + + The \code{\sankeyoutin} macro fills/draws a lane between \VAR{node A} + and \VAR{node B} using a Bézier curve with regular steps (10 steps by + default) to simulate constant width lane. + + The constant width and the minimum curvature are \emph{not} guaranted! +\end{miniblock} + +\begin{miniblock} + \MACRO\code{\sankeydubins}\OPTARG{\VAR{options}}\ARG{\VAR{node + A}}\ARG{\VAR{node B}} + + The \code{\sankeydubins} macro fills/draws a lane between \VAR{node A} + and \VAR{node B} using a Dubins + path\footnote{\url{https://en.wikipedia.org/wiki/Dubins_path}}. + + The constant width and the minimum curvature are guaranted. +\end{miniblock} + +\subsubsection{Comparison betwen \texttt{outin} and \texttt{dubins} + paths} + +\begin{miniblock} +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[start style=simple,end style=arrow] + \draw[gray!30] (0,-1) grid (10,3.5); + % + \sankeyset{fill/.style={fill=orange!20}} + \sankeynodestart{name=a,at={1,0},angle=0,quantity=5} + \sankeynodeend{name=b,at={5,3},angle=0,quantity=5} + \sankeyoutin{a}{b} + % + \sankeyset{fill/.style={fill=red!20}} + \sankeynodestart{name=a,at={3,0},angle=0,quantity=5} + \sankeynodeend{name=b,at={7,3},angle=0,quantity=5} + \sankeyoutin[outin steps=2]{a}{b} + % + \sankeyset{fill/.style={fill=lime!40}} + \sankeynodestart{name=a,at={5,0},angle=0,quantity=5} + \sankeynodeend{name=b,at={(9,3},angle=0,quantity=5} + \sankeydubins[minimum radius=5mm]{a}{b} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\begin{miniblock} +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[start style=simple,end style=arrow] + \draw[gray!30] (0,0) grid (8,4.5); + % + \sankeyset{fill/.style={fill=orange!20}} + \sankeynodestart{name=a,at={1,1},angle=0,quantity=5} + \sankeynodeend{name=b,at={3,4},angle=0,quantity=5} + \sankeyoutin{a}{b} + % + \sankeyset{fill/.style={fill=red!20}} + \sankeynodestart{name=a,at={3,1},angle=0,quantity=5} + \sankeynodeend{name=b,at={5,4},angle=0,quantity=5} + \sankeyoutin[outin steps=2]{a}{b} + % + \sankeyset{fill/.style={fill=lime!40}} + \sankeynodestart{name=a,at={5,1},angle=0,quantity=5} + \sankeynodeend{name=b,at={(7,4},angle=0,quantity=5} + \sankeydubins[minimum radius=5mm]{a}{b} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\begin{miniblock} +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[start style=simple,end style=arrow] + \draw[gray!30] (0,0) grid ((6,4.5); + % + \sankeyset{fill/.style={fill=orange!20}} + \sankeynodestart{name=a,at={1,1},angle=0,quantity=5} + \sankeynodeend{name=b,at={1,4},angle=0,quantity=5} + \sankeyoutin{a}{b} + % + \sankeyset{fill/.style={fill=red!20}} + \sankeynodestart{name=a,at={3,1},angle=0,quantity=5} + \sankeynodeend{name=b,at={3,4},angle=0,quantity=5} + \sankeyoutin[outin steps=2]{a}{b} + % + \sankeyset{fill/.style={fill=lime!40}} + \sankeynodestart{name=a,at={5,1},angle=0,quantity=5} + \sankeynodeend{name=b,at={(5,4},angle=0,quantity=5} + \sankeydubins[minimum radius=5mm]{a}{b} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\subsubsection{Examples of \texttt{dubins} paths} + +\begin{miniblock} +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[start style=simple,end style=arrow] + \draw[gray!30] (-1,-1) grid (10,2.5); + + \sankeyset{ + fill/.style={fill=lime!40}, + draw/.style={draw=green!50!black,line width=2pt}, + } + + \sankeynodestart{name=a,at={0,0},angle=0,quantity=5} + \sankeynodeend{name=b,at={0,2},angle=0,quantity=5} + \sankeydubins[minimum radius=2mm]{a}{b} + + \fill[green!50!black] (2,0) coordinate (c) circle(2pt); + \sankeynodestart{name=a,at={[shift={(c)}]90:5mm},angle=0,quantity=5} + \sankeynodeend{name=b,at={[shift={(c)}]150:5mm},angle=60,quantity=5} + \sankeydubins[minimum radius=2.5mm]{a}{b} + + \sankeynodestart{name=a,at={3.5,0},angle=0,quantity=5} + \sankeynodeend{name=b,at={3.5,.75},angle=-180,quantity=5} + \sankeydubins[minimum radius=5mm]{a}{b} + + \sankeynodestart{name=a,at={7,1},angle=0,quantity=5} + \sankeynodeend{name=b,at={7,1.75},angle=0,quantity=5} + \sankeydubins[minimum radius=2.5mm]{a}{b} + + \sankeynodestart{name=a,at={9,0},angle=0,quantity=5} + \sankeynodeend{name=b,at={9,2},angle=180,quantity=5} + \sankeydubins[minimum radius=2.5mm]{a}{b} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\subsection{Pure filling/drawing macros} + +\begin{miniblock} +\MACRO\code{\sankeystart}\OPTARG{\VAR{options}}\ARG{\VAR{name}} + +The \code{\sankeystart} fills/draws a starting extremity attached to the +preexistent Sankey node \VAR{name}: + + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram} + [start style=arrow,fill/.style={fill=cyan!20},draw/.style={draw=blue}] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynode{name=a,quantity=10,angle=0,at={0,0}} + \sankeystart{a} + \sankeynode{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeystart{b} + \sankeynode{name=c,quantity=20,angle=-90,at={5,1},anchor=right} + \sankeystart{c} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} +% ------------------------------------------------------------ + + +\begin{miniblock} + \MACRO\code{\sankeyend}\OPTARG{\VAR{options}}\ARG{\VAR{name}} + + The \code{\sankeyend} fills/draws an ending extremity attached to the + preexistent Sankey node \VAR{name}: + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram} + [end style=simple,fill/.style={fill=cyan!20},draw/.style={draw=blue}] + \draw[gray!30] (-1,-1) grid (8,2); + \sankeynode{name=a,quantity=10,angle=0,at={0,0}} + \sankeyend{a} + \sankeynode{name=b,quantity=5,angle=45,at={2,1},anchor=left} + \sankeyend{b} + \sankeynode{name=c,quantity=20,angle=-90,at={5,1},anchor=right} + \sankeyend{c} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\subsection{Forked node} + +\begin{miniblock} + \MACRO\code{\sankeyfork}\OPTARG{\VAR{options}}\ARG{\VAR{node}}\ARG{\VAR{quantity/name pairs}} + + The \code{\sankeyfork} macro splits a Sankey node in a list of new + Sankey nodes. The \VAR{quantity/name pairs} is a comma separated list + of \emph{quantity/name} pairs. The sum of all quantities \emph{must} + be equal to the quantity of the node to fork. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram}[debug] + \draw[gray!30] (-3,-2) grid (3,1); + \sankeynode{name=a,quantity=50,angle=-80,at={0,0}} + \sankeyfork{a}{30/b,10/c,10/d} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \begin{sankeydiagram} + \sankeyset{ + start style=arrow,end style=arrow, + fill/.style={fill=yellow!10,line width=0pt,draw=yellow!10}, + } + \draw[gray!30] (-4,-3) grid (4,4); + \sankeynode{name=a,quantity=40,angle=-90,at={0,0}} + \sankeyfork{a}{25/b,10/c,5/d} + \sankeyturn*{b}{90} + \sankeyturn*[minimum radius=2cm]{c}{-45} + \sankeyturn*[minimum radius=2cm]{c}{45} + \sankeyturn*[minimum radius=5mm]{d}{-90} + \sankeyadvance{a}{2cm} + \foreach \nodename in {b,c,d}{ + \sankeystart{\nodename} + } + \sankeyend{a} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +{\centering\input{\samplecodename}\par} +\end{miniblock} + +\section{Miscellaneous} + +\subsection{The \texttt{debug} layer} + +The \code{debug} option key uses the \code{sankeydebug} layer to draw +above the \code{main} TikZ layer (via \code{\pgfsetlayers}, the +\code{sankey} package installs four layers: \code{background}, +\code{main}, \code{foreground}, \code{sankeydebug}). + +The four following styles define how to display debug informations: + + +\begin{lstlisting}[style=LaTeXsmall] +\sankeyset{ + % debug color used by all debug macros + debug color/.style={/utils/exec={\colorlet{debug color}{#1}}}, + debug color=red!75!black, + % debug line between left and right anchors + debug line/.style={draw=debug color,|-|}, + % debug line between center and label + debug normal/.style={draw=debug color}, + % debug node label + debug label/.style={ + draw, + font=\ttfamily\tiny, + text=debug color,text opacity=1, + inner sep=.1em, + fill=white,fill opacity=1, + rounded corners=.1em, + }, +} +\end{lstlisting} + +\subsection{The \texttt{dubins} TikZ library} + +The \code{sankey} package uses the \code{dubins} TikZ library (the +\code{tikzlibrarydubins.code.tex} file) to compute Dubins paths. The +documentation for this library does not yet exist. + +\subsection{How to duplicate a Sankey node} + +\MACRO\code{\sankeynodealias}\ARG{\VAR{origname}}\ARG{\VAR{clonename}} + +The \code{\sankeynodealias} macro clones the Sankey node named +\VAR{origname} into the Sankey node named \VAR{clonename}. + +So, you can clone a Sankey node via two methods: + +\begin{lstlisting}[style=LaTeXsmall] +\sankeynode{name=a,quantity=10,angle=0,at={0,0}} +\sankeynode{as=a,name=b} +\end{lstlisting} + +\begin{lstlisting}[style=LaTeXsmall] +\sankeynode{name=a,quantity=10,angle=0,at={0,0}} +\sankeynodealias{a}{b} +\end{lstlisting} + +\subsection{How to define new start and end styles} + +Here are the definitions of the \code{arrow} styles: + +\begin{lstlisting}[style=LaTeXsmall] +\sankeyset{ + % arrow style + new start style={arrow}{ + (\name.left) -- ++(-10pt,0) + -- ([xshift=-10pt/6]\name.center) + -- ([xshift=-10pt]\name.right) + -- (\name.right) -- cycle + }{ + (\name.left) -- ++(-10pt,0) + -- ([xshift=-10pt/6]\name.center) + -- ([xshift=-10pt]\name.right) + -- (\name.right) + }, + new end style={arrow}{ + (\name.left) -- ([yshift=1mm]\name.left) + -- ([xshift=10pt]\name.center) + -- ([yshift=-1mm]\name.right) -- (\name.right) -- cycle + }{ + (\name.left) -- ([yshift=1mm]\name.left) + -- ([xshift=10pt]\name.center) + -- ([yshift=-1mm]\name.right) -- (\name.right) + }, +} +\end{lstlisting} + +\section{History} + +\begin{itemize} +\item[v2.0] (2021/01/27) + \begin{itemize} + \item First public version (on CTAN). + \end{itemize} +\item[v1.0] (2016/03/06) + \begin{itemize} + \item First version. + \end{itemize} +\end{itemize} + +\clearpage +\section{Examples} + +\subsection{Simple example} + +See figure~\ref{fig:simple-example}. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \colorlet{cold}[rgb]{cyan!75!blue!50!white} + \colorlet{hot}[rgb]{red!50!orange!75!white} + \begin{sankeydiagram} + \sankeyset{ + ratio=90pt/6, + minimum radius=15pt, + start style=simple, + end style=simple, + % debug, + draw/.style={ + draw=blue!50!cyan, + line width=1pt, + line cap=round,line join=round, + }, + cold/.style={ + fill/.style={ + draw=cold, + line width=0pt, + fill=cold, + }, + }, + cold to hot/.style={ + fill/.style={ + fill=none, + top color=cold, + bottom color=hot, + middle color=yellow, + }, + }, + hot/.style={ + fill/.style={ + draw=hot, + line width=0pt, + fill=hot, + }, + }, + } + + \sankeyset{cold} + \sankeynodestart{name=p0,at={0,100},angle=-90,quantity=6} + \sankeyadvance{p0}{50pt} + + \sankeyfork{p0}{3/p1,3/p2} + + \sankeyturn{p1}{90} + \sankeyadvance{p1}{20pt} + + \sankeyadvance{p2}{60pt} + + \sankeyfork{p2}{2/p3,1/p4} + + \sankeyturn{p3}{90} + \sankeyadvance{p3}{50pt} + + \sankeyfork{p3}{1/p5,1/p6} + + \sankeyadvance{p5}{70pt} + + \sankeyfork{p1}{1/p7,1/p8,1/p9} + \sankeyadvance{p7}{50pt} + \sankeyadvance{p9}{50pt} + + \sankeyadvance{p4}{40pt} + \sankeyturn{p4}{90} + \sankeyadvance{p4}{65pt} + + \sankeyadvance{p7}{40pt} + + \sankeynode{name=p11,at={[shift={(50pt,-15pt)}]p7},angle=0,quantity=3} + \sankeyfork{p11}{1/p7a,1/p9a,1/p5a} + \sankeyoutin{p7}{p7a} + \sankeyoutin{p9}{p9a} + \sankeyoutin{p5}{p5a} + \sankeyadvance{p11}{30pt} + \sankeyend{p11} + + \sankeyturn{p8}{-90} + \sankeyturn{p6}{-90} + \sankeyturn{p4}{-90} + + \sankeyset{hot} + + \sankeyadvance[cold to hot]{p8}{40pt} + + \sankeynode{name=p10,at={[shift={(-15pt,-60pt)}]p8},angle=-90,quantity=3} + \sankeyfork{p10}{1/p8a,1/p6a,1/p4a} + \sankeyoutin[cold to hot]{p4}{p4a} + \sankeyoutin[cold to hot]{p6}{p6a} + \sankeyoutin{p8}{p8a} + \sankeyadvance{p10}{30pt} + \sankeyend{p10} + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +\begin{figure}[b!] + \centering + \input{\samplecodename} + \caption{Simple example} + \label{fig:simple-example} +\end{figure} +\clearpage +\subsection{Example from question on TeX.se} + +This example cames from +\href{https://tex.stackexchange.com/q/40159/14500}{this question} on +\href{https://tex.stackexchange.com/}{TeX.StackExchange}. + +See figure~\ref{fig:texse-example}. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + \renewcommand*\sfdefault{txss} + + \begin{sankeydiagram} + + \sankeyset{ + ratio=4cm/524.3, + minimum radius=3mm, + start style=arrow, + end style=arrow, + % debug, + fill/.style={ + line width=0pt, + fill=cyan!50!blue!50!black, + draw=cyan!50!blue!50!black, + }, + draw/.style={draw=none}, + } + + \sankeynodestart{name=B,at={-.5,0},angle=-90,quantity=7.2} + \coordinate[below=1mm of B.center] (B label); + \sankeyadvance{B}{5mm} + \sankeynodestart{name=GI,at={1,0},angle=-90,quantity=137.3} + \coordinate[below=1mm of GI.center] (GI label); + \sankeyadvance{GI}{5mm} + \sankeynodestart{name=I,at={4,0},angle=-90,quantity=397.8} + \coordinate[below=1mm of I.center] (I label); + \sankeynode{name=EI,at={2.86,-3},angle=-90,quantity=542.3} + \sankeyfork{EI}{397.8/Ia,137.3/GIa,7.2/Ba} + \sankeydubins[minimum radius=1.2cm]{I}{Ia} + \sankeyoutin{GI}{GIa} + \sankeyoutin{B}{Ba} + \sankeyadvance{EI}{5mm} + \coordinate (EI label) at (EI); + \sankeyadvance{EI}{5mm} + \sankeyfork{EI}{63.1/EB,479.2/P} + + \sankeyturn{EB}{90} + \sankeyadvance{EB}{4cm} + \coordinate (EB label) at ($(EB)!.5!(EB-old)$); + \sankeyend{EB} + + \sankeyadvance{P}{10mm} + \coordinate (P label) at (P); + \sankeyadvance{P}{5mm} + + \sankeyfork{P}{33.5/NV,445.7/P} + + { + \colorlet{NV color}{cyan!80!lime!50!gray} + \sankeyset{fill/.append style={fill=NV color,draw=NV color}} + \sankeyturn{NV}{90} + \sankeynode{as=NV,name=NV2,at=NV -| EB} + \sankeyoutin{NV}{NV2} + \coordinate (NV label) at (NV -| EB label); + \sankeyend{NV2} + } + + \sankeyadvance{P}{10mm} + \sankeyfork{P}{118.1/U,327.6/P} + + { + \sankeyset{ + fill/.style={fill=orange!70!gray!50,draw=orange!70!gray!50} + } + \sankeyturn{U}{90} + \sankeynode{as=U,name=U2,at=U -| EB} + \sankeyoutin{U}{U2} + \coordinate (U label) at (U -| EB label); + \sankeyend{U2} + } + + \sankeyadvance{P}{10mm} + + \sankeyfork{P}{327.2/P,0.4/SD} + + { + \sankeyturn{SD}{-90} + \sankeyadvance{SD}{15mm} + \coordinate (SD label) at (SD); + \sankeyadvance{SD}{15mm} + \sankeyend{SD} + } + + \sankeyadvance{P}{8mm} + \sankeyfork{P}{18.8/VE,308.4/E} + + { + \sankeyset{fill/.append style={orange!70!gray!30}} + \sankeyturn{VE}{90} + \sankeynode{as=VE,name=VE2,at=VE -| EB} + \sankeyoutin{VE}{VE2} + \coordinate (VE label) at (VE -| EB label); + \sankeyend{VE2} + } + + \sankeyadvance{E}{8mm} + \coordinate (E label) at (E); + \sankeyadvance{E}{20mm} + + \sankeyfork{E}{135.1/H+GHD,87.2/V,86.1/In} + + \sankeyturn{In}{-90} + \sankeyadvance{In}{10mm} + \sankeyturn{In}{90} + \sankeyadvance{In}{5mm} + \coordinate (In label) at (In); + \sankeyadvance{In}{10mm} + \sankeyend{In} + + \sankeynode{as=V,name=V2,at=V|-In label} + \sankeyoutin{V}{V2} + \coordinate (V label) at (V2); + \sankeyadvance{V2}{10mm} + \sankeyend{V2} + + \sankeyturn{H+GHD}{90} + \sankeyadvance{H+GHD}{5mm} + \sankeyfork{H+GHD}{47.0/GHD,88.1/H} + + \sankeyturn{H}{-90} + \sankeynode{as=H,name=H2,at=H|-In label} + \sankeyoutin{H}{H2} + \coordinate (H label) at (H2); + \sankeyadvance{H2}{10mm} + \sankeyend{H2} + + \sankeyadvance{GHD}{30mm} + \sankeyturn{GHD}{-90} + \sankeynode{as=GHD,name=GHD2,at=GHD|-In label} + \sankeyoutin{GHD}{GHD2} + \coordinate (GHD label) at (GHD2); + \sankeyadvance{GHD2}{10mm} + \sankeyend{GHD2} + \end{sankeydiagram} + + % labels + \tikzset{ + label/.style={ + fill=white,fill opacity=.8,text opacity=1, + inner sep=1mm, + text=cyan!50!blue!50!black, + inner xsep=2mm, + font=\sffamily\bfseries\footnotesize, + align=center, + }, + } + \node[label,anchor=north] (B label) at (B label) {7.2}; + \node[label,left=1mm of B label] {Bestands-\\entnahme}; + \node[label,anchor=north] at (GI label) {137.3}; + \node[label,above=5mm of GI label] {Gewinnung\\im Inland}; + \node[label,anchor=north] at (I label) {397.8}; + \node[label,above=5mm of I label] {Import}; + + \node[label] at (EI label) {542.3\\Energieaufkommen im Inland}; + + \node[label,anchor=center] (EB label) at (EB label) {63.1}; + \node[label,above=1mm of EB label] {Export und\\Bunkerung}; + + \node[label] at (P label) {479.2\\Primärenergieverbrauch}; + + \node[label,anchor=center] (NV label) at (NV label) {33.5}; + \node[label,above=0mm of NV label] {Nichtenergetischer Verbrauch}; + + \node[label,anchor=center] (U label) at (U label) {118.1}; + \node[label,below=3mm of U label] {Umwandlungsverluste}; + + \node[label,anchor=center] (SD label) at (SD label) {0.4}; + \node[label,above=0mm of SD label] {Statistische\\Differenzen}; + + \node[label,anchor=center] (VE label) at (VE label) {18.8}; + \node[label,below=0mm of VE label] {Verbrauch in den\\Energiesktoren}; + + \node[label,anchor=north] (E label) at (E label) + {308.4\\Endenergieverbrauch}; + + \node[label,anchor=north] (In label) at (In label) {86.1}; + \node[label,anchor=north,below=1cm of In label] {Industrie}; + + \node[label,anchor=north] (V label) at (V label) {87.2}; + \node[label,anchor=north,below=1cm of V label] {Verkehr}; + + \node[label,anchor=north] (H label) at (H label) {88.1}; + \node[label,anchor=north,below=1cm of H label] {Haushalte}; + + \node[label,anchor=north] (GHD label) at (GHD label) {47.0}; + \node[label,anchor=north,below=1cm of GHD label] + {Gewerbe, Handel\\Diensleistungen}; +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[style=LaTeXsmall]{\samplecodename} +% +\begin{figure} + \centering + %\rule{\linewidth}{1pt}\\ + \input{\samplecodename} + \caption{Example from TeX.se question} + \label{fig:texse-example} +\end{figure} + +\clearpage +\subsection{Very nice example -- Nadieh Bremer creation} + +Graphic designer Nadieh Bremer created this very nice Sankey +diagram\footnote{\url{https://www.visualcinnamon.com/portfolio/adyen-report-2019/}} +for Adyen's second half 2018 report to shareholders. It is coded here +with her kind permission. + +You can change the data values between lines 28 and 53. + +See figure~\ref{fig:nice-example}. + +\begin{filecontents*}{\samplecodename} +\begin{tikzpicture} + % + \renewcommand*\sfdefault{txss} + \sffamily + \sisetup{ + detect-all=true, + group-separator={,}, + group-minimum-digits=4, + } + % storage of labels + \newcommand\LabSet[2]{% none name, label + \expandafter\edef\csname #1Lab\endcsname{#2}} + \newcommand\Lab[1]{% node name + \csname #1Lab\endcsname} + % storage of quantities + \newcommand\QtySet[2]{% node name, quantity + \expandafter\edef\csname #1Qty\endcsname{\fpeval{#2}}} + \newcommand\Qty[1]{% node name + \csname #1Qty\endcsname} + % all nodes with their name, label and quantity + \sankeyset{ + def data/.code args={#1/#2/#3}{% node name/label/values + \LabSet{#1}{#2} + \QtySet{#1}{#3} + \typeout{#1: \Qty{#1}€ (\Lab{#1})} + }, + def data/.list={ + {Pf/Processing\\fees/71713}, + {Sog/Sales of\\good/4547}, + {Sf/Settlement\\fees/842075}, + {Os/Other\\services/37532}, + {R/Revenues/\Qty{Pf}+\Qty{Sog}+\Qty{Sf}+\Qty{Os}}, + {Coi/Cost of Inventory/5151}, + {Ciffi/Cost insecure from financial institutions/758234}, + {Nr/Net revenue/\Qty{R}-\Qty{Coi}-\Qty{Ciffi}}, + {Aadotaifa/Amortization and\\depreciation of tangible and\\% + intangible fixed assets/4688}, + {Ssapc/Social securities and\\pension costs/7860}, + {Was/Wages and salaries/35627}, + {Ooe/Other operating expenses/37346}, + {Nr2/-/\Qty{Nr}-\Qty{Aadotaifa}-\Qty{Ssapc}-\Qty{Was}-\Qty{Ooe}}, + {Oi/Other income/47}, + {Ibiiieait/Income before interest income,\\interest expense and % + income taxes/\Qty{Nr2}+\Qty{Oi}}, + {Fe/Finance expense/561}, + {Ofr/Other financial results/2533}, + {Ibiiieait2/-/\Qty{Ibiiieait}-\Qty{Fe}-\Qty{Ofr}}, + {Fi/Finance income/204}, + {Ibit/Income before income taxes/\Qty{Ibiiieait2}+\Qty{Fi}}, + {It/Income taxes/21134}, + {Niftp/Net income for the period/\Qty{Ibit}-\Qty{It}}, + {Octa/Other currency\\translation adjustments/785}, + {Tci/Total comprehensive income/\Qty{Niftp}+\Qty{Octa}} + }, + } + + \definecolor{mygreen}{RGB}{9,192,82} + \tikzset{ + cost node/.style 2 args={ + overlay, + align=flush center, + node font=\footnotesize\sffamily\bfseries, + inner sep=0, + node contents={% + {\mdseries€\,\num{#1}}\\% + #2\vphantom{g}% + }, + }, + white hash/.style={ + draw=none,fill=none, + pattern={Lines[angle=60,line width=2pt,distance=4pt]}, + pattern color=white, + }, + line sep/.style={draw=white,line width=1pt}, + left label/.style={left=#1,align=flush right,anchor=north east}, + right label/.style={right=#1,align=flush left}, + right label hashed/.style={ + right=1mm of $(#1.left)!.5!(#1-old.left)$,align=flush left, + }, + left label hashed/.style={ + left=1mm of $(#1.right)!.5!(#1-old.right)$,align=flush right, + }, + } + + \newcommand\turnandstop[1]{ + \sankeyturn[green to greenwhite]{#1}{-90} + \sankeynode{as=#1,name=#1-e,at={#1 -| Coi}} + \sankeyoutin[greenwhite to white]{#1}{#1-e} + \node[cost node={\Qty{#1}}{\Lab{#1}},left label={1mm of #1-e.right}]; + } + + \begin{sankeydiagram} + \sankeyset{ + ratio=28em/1000000, + minimum radius=2cm, + start style=none, + % default fill and draw styles + fill/.style={ + line width=0pt, + fill=mygreen, + }, + draw/.style={draw=none}, + % specific fill and draw styles + green to greenwhite/.style={ + fill/.style={ + line width=0pt, + right color=mygreen, + left color=mygreen!20!white, + } + }, + greenwhite to white/.style={ + fill/.style={ + line width=0pt, + right color=mygreen!20!white, + left color=mygreen!5!white, + } + }, + dashed/.style={draw/.style={draw=mygreen,dashed}}, + % debug, + } + + \coordinate (top) at (0,2em); + + \sankeynodestart{name=Pf,quantity=\Qty{Pf}, + at={0,0},angle=-90} + \node[cost node={\Qty{Pf}}{\Lab{Pf}},above=.5em of Pf.center]; + + \sankeynodestart{name=Sog,quantity=\Qty{Sog}, + at={[xshift=4em]Pf.left},angle=-90,anchor=right} + \node[cost node={\Qty{Sog}}{\Lab{Sog}},above=.5em of Sog.center]; + + \sankeynodestart{name=Sf,quantity=\Qty{Sf}, + at={[xshift=2em]Sog.left},angle=-90,anchor=right} + \node[cost node={\Qty{Sf}}{\Lab{Sf}},above=.5em of Sf.center]; + + \sankeynodestart{name=Os,quantity=\Qty{Os}, + at={[xshift=2em]Sf.left},angle=-90,anchor=right} + \node[cost node={\Qty{Os}}{\Lab{Os}},above=.5em of Os.center]; + + \sankeynode{name=R,quantity=\Qty{R}, + at={[yshift=-10em]Sf.center},angle=-90} + \sankeyfork{R}{\Qty{Os}/Os-a,\Qty{Sf}/Sf-a,\Qty{Sog}/Sog-a,\Qty{Pf}/Pf-a} + + \foreach \nodename in {Pf,Sog,Sf,Os}{ + \sankeyoutin[fill/.style={top color=white,bottom color=mygreen}] + {\nodename}{\nodename-a} + } + + \sankeyadvance{R}{1em} + \node[cost node={\Qty{R}}{\Lab{R}},right label hashed=R]; + + \sankeyfork{R}{\Qty{Ciffi}/Ciffi,\Qty{Nr}/Nr,\Qty{Coi}/Coi} + + \sankeyturn[minimum radius=1.cm]{Ciffi}{90} + \node[cost node={\Qty{Ciffi}}{\Lab{Ciffi}}, + at={([shift={(1mm,0)}]Ciffi.center)},rotate=-90, + anchor=south,align=flush left,node font=\scriptsize\sffamily\bfseries]; + + \sankeyturn[green to greenwhite]{Coi}{-90} + \sankeyadvance[greenwhite to white]{Coi}{1em} + \node[cost node={\Qty{Coi}}{\Lab{Coi}}, + left={1mm of [yshift=.75ex]Coi.left}, + align=flush right,anchor=north east,overlay]; + + \sankeyadvance{Nr}{9em} + \sankeyadvance{Nr}{1em} + \node[cost node={\Qty{Nr}}{\Lab{Nr}}, + left label hashed=Nr,node font=\large\sffamily\bfseries]; + + \sankeyfork{Nr}{\Qty{Nr2}/Nr2,\Qty{Ooe}/Ooe, + \Qty{Was}/Was,\Qty{Ssapc}/Ssapc,\Qty{Aadotaifa}/Aadotaifa} + + \turnandstop{Aadotaifa} + + \sankeyadvance{Ssapc}{5em} + \turnandstop{Ssapc} + + \sankeyadvance{Was}{9em} + \turnandstop{Was} + + \sankeyadvance{Ooe}{12em} + \turnandstop{Ooe} + + \sankeyadvance{Nr2}{15em} + \sankeynode{name=Ibiiieait,quantity=\Qty{Ibiiieait}, + angle=-90,anchor=right,at={Nr2.right}} + \sankeyfork{Ibiiieait}{\Qty{Oi}/Oi,\Qty{Nr2}/Nr2-e} + + \sankeyturn*[minimum radius=1cm,dashed]{Oi}{90} + \node[cost node={\Qty{Oi}}{\Lab{Oi}},right label=1mm of Oi.left]; + + \sankeyadvance{Ibiiieait}{1em} + \node[cost node={\Qty{Ibiiieait}}{\Lab{Ibiiieait}}, + right label hashed=Ibiiieait]; + + \sankeyfork{Ibiiieait} + {\Qty{Ibiiieait2}/Ibiiieait2,\Qty{Ofr}/Ofr,\Qty{Fe}/Fe} + + \turnandstop{Fe} + + \sankeyadvance{Ofr}{3em} + \turnandstop{Ofr} + + \sankeyadvance{Ibiiieait2}{6em} + \sankeynode{name=Ibit,quantity={\Qty{Ibiiieait2}+\Qty{Fi}}, + angle=-90,anchor=right,at={Ibiiieait2.right}} + \sankeyfork{Ibit}{\Qty{Fi}/Fi,\Qty{Ibiiieait2}/Ibiiieait2-e} + + \sankeyturn*[minimum radius=1cm,dashed]{Fi}{90} + \node[cost node={\Qty{Fi}}{\Lab{Fi}},right label=1mm of Fi.left]; + + \sankeyadvance{Ibit}{1em} + \node[cost node={\Qty{Ibit}}{\Lab{Ibit}},right label hashed=Ibit]; + + \sankeyfork{Ibit}{\Qty{Niftp}/Niftp,\Qty{It}/It} + + \turnandstop{It} + + \sankeyadvance{Niftp}{6em} + + \sankeyadvance{Niftp}{1em} + \node[cost node={\Qty{Niftp}}{\Lab{Niftp}},right label hashed=Niftp]; + + \sankeynode{name=Tci,quantity=\Qty{Niftp}+\Qty{Octa}, + angle=-90,anchor=right,at={[yshift=-6em]Niftp.right}} + \sankeyfork{Tci}{\Qty{Octa}/Octa,\Qty{Niftp}/Niftp-e} + \sankeyoutin{Niftp}{Niftp-e} + + \sankeyturn*[minimum radius=1cm]{Octa}{90} + \node[cost node={\Qty{Octa}}{\Lab{Octa}},right label=1mm of Octa.left]; + + \sankeyadvance{Tci}{1em} + \node[cost node={\Qty{Tci}}{\Lab{Tci}},right label hashed=Tci]; + + \newcommand\hashband[1]{ + \draw[line sep] (#1-old.right) -- (#1-old.left); + \draw[line sep] (#1.right) -- (#1.left); + \path[white hash] (#1-old.right) rectangle (#1.left); + } + + \foreach \nodename in {R,Nr,Nr,Ibiiieait,Ibit,Niftp,Tci}{ + \hashband{\nodename} + } + + \end{sankeydiagram} +\end{tikzpicture} +\end{filecontents*} +% +\lstinputlisting[numbers=left,style=LaTeXsmall]{\samplecodename} +% +\begin{figure} + \centering + \input{\samplecodename} + \caption{Very nice example -- Nadieh Bremer's creation} + (from + \href{https://www.visualcinnamon.com/portfolio/adyen-report-2019/}{Adyen's + Shareholder Report}) + \label{fig:nice-example} +\end{figure} +\end{document} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: t +%%% End: diff --git a/graphics/pgf/contrib/sankey/sankey.sty b/graphics/pgf/contrib/sankey/sankey.sty new file mode 100644 index 0000000000..4c28a1fe0d --- /dev/null +++ b/graphics/pgf/contrib/sankey/sankey.sty @@ -0,0 +1,722 @@ +% Copyright (C) 2016-2021 by Paul Gaborit +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% +% 2. under the GNU Public License. + +\def\sankey@version{2.0} +\def\sankey@date{2021/01/27} +\ProvidesPackage{sankey}[\sankey@date\space\sankey@version\space sankey package] + +\RequirePackage{xparse} +\RequirePackage{xfp} +\RequirePackage{tikz} +\usetikzlibrary{calc,decorations.markings,dubins} +\usepackage{etoolbox} + +% add a new layer to debug sankey diagrams +\pgfdeclarelayer{background} +\pgfdeclarelayer{foreground} +\pgfdeclarelayer{sankeydebug} +\pgfsetlayers{background,main,foreground,sankeydebug} + +% flag to debug sankey diagrams +\newtoggle{sankey debug} +\newtoggle{sankey node start} +\newtoggle{sankey node end} + +% macros to store and to retrieve the orientation and the quantity +% associated with each sankey node + +\def\sankeysetnodeqty#1#2{\expandafter\xdef\csname sankey@node@#1@qty\endcsname{#2}} + +\def\sankeygetnodeqty#1{% + \ifcsmacro{sankey@node@#1@qty}{% + \csname sankey@node@#1@qty\endcsname% + }{% + \PackageWarning{sankey}{Unknown sankey node '#1'}% + }% +} + +\def\sankeysetnodeorient#1#2{\expandafter\xdef\csname sankey@node@#1@orient\endcsname{#2}} + +\def\sankeygetnodeorient#1{% + \ifcsmacro{sankey@node@#1@orient}{% + \csname sankey@node@#1@orient\endcsname% + }{% + \PackageWarning{sankey}{Unknown sankey node '#1'}% + }% +} + +\def\sankeysetstartfill#1#2{\expandafter\gdef\csname sankey@start@fill@#1\endcsname{#2}} +\def\sankeysetstartdraw#1#2{\expandafter\gdef\csname sankey@start@draw@#1\endcsname{#2}} +\def\sankeysetendfill#1#2{\expandafter\gdef\csname sankey@end@fill@#1\endcsname{#2}} +\def\sankeysetenddraw#1#2{\expandafter\gdef\csname sankey@end@draw@#1\endcsname{#2}} +% +\def\sankeygetstartfill#1{\csname sankey@start@fill@#1\endcsname} +\def\sankeygetstartdraw#1{\csname sankey@start@draw@#1\endcsname} +\def\sankeygetendfill#1{\csname sankey@end@fill@#1\endcsname} +\def\sankeygetenddraw#1{\csname sankey@end@draw@#1\endcsname} + +% new shape +\pgfdeclareshape{sankey node}{ + \inheritsavedanchors[from=rectangle] + \inheritanchor[from=rectangle]{center} + %\inheritanchorborder[from=rectangle] + \anchor{left}{\pgf@process{\northeast}} + \anchor{right}{\pgf@process{\southwest}} +} + +% pgfkeys family 'sankey' +\pgfkeys{/sankey/.is family} + +\NewDocumentCommand\sankeyset{m}{\pgfkeys{sankey,#1}} + +\sankeyset{ + ratio length/.code={ + \pgfmathsetmacro\sankeytotallen{#1} + \edef\sankeytotallen{\sankeytotallen pt} + }, + ratio length/.value required, + ratio quantity/.code={\edef\sankeytotalqty{\fpeval{#1}}}, + ratio quantity/.value required, + ratio/.style args={#1/#2}{ + ratio length=#1, + ratio quantity=#2, + }, + ratio/.value required, + minimum radius/.estore in=\sankeyminradius, + minimum radius/.value required, + outin steps/.estore in=\sankeystepoutin, + outon steps/.value required, + % + debug/.is choice, + debug/true/.code={\toggletrue{sankey debug}}, + debug/false/.code={\togglefalse{sankey debug}}, + debug/.default=true, + % + start style/.is choice, + % + end style/.is choice, + % default values + default parameters/.style={ + % default values + ratio=1cm/10, + minimum radius=5mm,% + outin steps=10, + debug=false, + start style=none, + end style=none, + }, + % to make node + node parameters/.style={ + quantity/.code={% + %\typeout{quantity=##1}% + \edef\qty{\fpeval{##1}}% + %\typeout{qty=\qty}% + }, + quantity/.value required, + angle/.code={% + %\typeout{angle:##1}% + \pgfmathsetmacro\orient{##1}% + }, + angle/.value required, + name/.store in=\name, + name/.value required, + at/.store in=\pos, + at/.value required, + as/.style={ + name=##1, + quantity=\sankeygetnodeqty{##1}, + angle=\sankeygetnodeorient{##1}, + at={##1.center}, + }, + as/.value required, + % + anchor/.is choice, + anchor/left/.code={\def\sankeyanchor{left}}, + anchor/right/.code={\def\sankeyanchor{right}}, + anchor/center/.code={\def\sankeyanchor{center}}, + anchor=center, + % + start/.is choice, + start/true/.code={\toggletrue{sankey node start}}, + start/false/.code={\togglefalse{sankey node start}}, + start/.default=true, + start=false, + % + end/.is choice, + end/true/.code={\toggletrue{sankey node end}}, + end/false/.code={\togglefalse{sankey node end}}, + end/.default=true, + end=false, + }, +} + +% some styles +\sankeyset{ + node/.style={ + shape=sankey node, + inner sep=0,minimum height={\sankeyqtytolen{#1}}, + minimum width=0,draw=none,line width=0pt, + node contents={}, + }, + fill/.style={line width=0pt,fill=white}, + draw/.style={draw=black,line width=.4pt}, + % debug color used by all debug macros + debug color/.style={/utils/exec={\colorlet{debug color}{#1}}}, + debug color=red!75!black, + % debug line between left and right anchors + debug line/.style={draw=debug color,|-|}, + % debug line between center and label + debug normal/.style={draw=debug color}, + % debug node label + debug label/.style={ + draw, + font=\ttfamily\tiny, + text=debug color,text opacity=1, + inner sep=.1em, + fill=white,fill opacity=1, + rounded corners=.1em, + }, +} + +\sankeyset{ + new start style/.code n args={3}{% name, fill path, draw path + \sankeysetstartfill{#1}{#2} + \sankeysetstartdraw{#1}{#3} + \sankeyset{start style/#1/.code={\def\sankeystartstyle{#1}}} + }, + new end style/.code n args={3}{% name, fill path, draw path + \sankeysetendfill{#1}{#2} + \sankeysetenddraw{#1}{#3} + \sankeyset{end style/#1/.code={\def\sankeyendstyle{#1}}} + }, +} + +% the new 'sankeydiagram' environment +\NewDocumentEnvironment{sankeydiagram}{O{}+b}{ + + \newcommand\sankeyarc[2]{% sn, en + \begingroup + \pgfmathsetmacro\startangle{\sankeygetnodeorient{##1}} + \pgfmathsetmacro\endangle{\sankeygetnodeorient{##2}} + \pgfmathsetmacro\rotateangle{\endangle-\startangle} + \pgfmathtruncatemacro\acwrotate{\rotateangle>0?1:0} + \path let + \p1=(##1.left),\p2=(##1.right), + \p3=(##2.left),\p4=(##2.right), + \n1={\sankeyqtytolen{\sankeygetnodeqty{##1}}}, + \n{maxr}={\sankeyminradius+\n1}, + \n{minr}={\sankeyminradius} + in \pgfextra{ + \ifnumequal{\acwrotate}{1}{ + \begin{pgfinterruptpath} + % fill the region + \path[/sankey/fill] + (\p1) arc(\startangle-90:\endangle-90:\n{minr}) -- (\p3) -- + (\p4) arc(\endangle-90:\startangle-90:\n{maxr}) -- (\p2) -- cycle; + % draw left and right borders + \path[/sankey/draw] + (\p1) arc(\startangle-90:\endangle-90:\n{minr}) + (\p4) arc(\endangle-90:\startangle-90:\n{maxr}); + \end{pgfinterruptpath} + }{ + \begin{pgfinterruptpath} + % fill the region + \path[/sankey/fill] + (\p1) arc(\startangle+90:\endangle+90:\n{maxr}) -- (\p3) -- + (\p4) arc(\endangle+90:\startangle+90:\n{minr}) -- (\p2) -- cycle; + % draw left and right borders + \path[/sankey/draw] + (\p1) arc(\startangle+90:\endangle+90:\n{maxr}) + (\p4) arc(\endangle+90:\startangle+90:\n{minr}); + \end{pgfinterruptpath} + } + }; + \endgroup + } + + \newcommand\sankeymakenodenodebug{ + \begingroup + \sankeysetnodeqty{\name}{\qty} + \sankeysetnodeorient{\name}{\orient} + % \typeout{\qty,\orient} + \ifundef{\sankeyanchor}{\def\sankeyanchor{center}}{} + \node[/sankey/node=\qty,rotate=\orient,at/.expanded={(\pos)},name=\name,anchor=\sankeyanchor]; + \endgroup + } + + \newcommand\sankeymakenode{ + \begingroup + \sankeymakenodenodebug{} + \iftoggle{sankey debug}{ + \begin{pgfonlayer}{sankeydebug} + \pgfset{ + number format/relative*=-1, + number format/precision=1, + number format/fixed, + } + \path[/sankey/debug line] (\name.left) -- (\name.right); + \pgfmathsetmacro{\len}{\sankeyqtytolen{\qty}/3} + \path[/sankey/debug normal] (\name.center) + -- ($(\name.center)!\len pt!90:(\name.right)$) + node[/sankey/debug label,rotate=\orient+90,anchor=north] {\name:\pgfmathprintnumber{\qty}}; + \end{pgfonlayer} + }{} + \endgroup + } + + \newcommand\sankeynodenodebug[1]{ + \begingroup + \sankeyset{node parameters,##1} + %\typeout{sankeynodenodebug: '\name' at(\pos) angle(\orient) qty(\qty)} + \sankeymakenodenodebug{} + \endgroup + } + + % ============================================================ + % user commands + % ============================================================ + + \NewDocumentCommand\sankeydubins{O{}mm}{% options, sn, en + \begingroup + \sankeyset{##1} + \pgfmathsetmacro\startangle{\sankeygetnodeorient{##2}} + \pgfmathsetmacro\endangle{\sankeygetnodeorient{##3}} + \edef\sqty{\sankeygetnodeqty{##2}} + \edef\eqty{\sankeygetnodeqty{##3}} + \ifdefstrequal{\sqty}{\eqty}{}{ + \PackageError{sankey}% + {^^J*** \string\sankeyoutin: quantities differ between ##2 (\sqty) and ##3 (\eqty)^^J}% + {The quantities of the two Sankey nodes must be equal.} + } + \pgfmathsetmacro\qty{\sankeygetnodeqty{##2}} + \pgfmathsetmacro\width{\sankeyqtytolen{\qty}} + %\typeout{startangle:\startangle,endangle:\endangle,qty:\qty,width:\width} + \dubinspathset{ + sankey/.style={ + start point=##2.center,start angle=\startangle, + end point=##3.center,end angle=\endangle, + minimum radius=\sankeyminradius + .5 * \width pt, + }, + } + \dubinspathcalc{sankey,store=sankey} + + \dubinspathset{ + left border/.style={ + sankey, use store=sankey, + left and right minimum radii= + {\sankeyminradius} and {\sankeyminradius + \width pt}, + }, + right border/.style={ + sankey, use store=sankey, + left and right minimum radii= + {\sankeyminradius + \width pt} and {\sankeyminradius}, + }, + } + + \path let + \p1=(##2.left),\p2=(##2.right), + \p3=(##3.left),\p4=(##3.right) + in \pgfextra{ + \begin{pgfinterruptpath} + % fill the region + \path[/sankey/fill] + (\p1) + \dubinspath{left border} + -- (\p3) -- + (\p4) \dubinspath{right border,reverse} + -- (\p2) -- cycle; + + % draw left and right borders + \path[/sankey/draw] + (\p1) + \dubinspath{left border} + (\p2) + \dubinspath{right border} + ; + \end{pgfinterruptpath} + }; + \endgroup + } + + + \NewDocumentCommand\sankeyoutin{O{}mm}{% options, sn, en + \begingroup + \sankeyset{##1} + \edef\sn{##2} + \edef\en{##3} + \edef\sqty{\sankeygetnodeqty{\sn}} + \edef\eqty{\sankeygetnodeqty{\en}} + \ifdefstrequal{\sqty}{\eqty}{}{ + \PackageError{sankey}% + {^^J*** \string\sankeyoutin: quantities differ between \sn (\sqty) and \en (\eqty)^^J}% + {The quantities of the two Sankey nodes must be equal.} + } + %\typeout{*** sankeyoutin: \sn\space to \en\space (\sankeystepoutin)} + \pgfmathsetmacro\qty{\sankeygetnodeqty{\sn}} + \pgfmathsetmacro\mylength{\sankeyqtytolen{\qty}/2} + \pgfmathsetmacro\mystep{1/\sankeystepoutin} + %\typeout{*** a} + \pgfmathsetmacro\mybound{1-.5*\mystep} + %\typeout{*** b} + \pgfmathsetmacro\laststep{int(\sankeystepoutin-1)} + %\typeout{sankeystepoutin(\sankeystepoutin) mystep(\mystep) mybound(\mybound) laststep(\laststep)} + %\typeout{*** sankeyoutin: \startangle to \endangle} + \path[decorate,overlay,decoration={ + markings, + mark=between positions \mystep and \mybound step \mystep with { + \def\sankeyoutinmidptname{sankeyoutinmidpt \pgfkeysvalueof{/pgf/decoration/mark info/sequence number}} + \path + (0,0) coordinate(\sankeyoutinmidptname) + (0,-\mylength pt) coordinate (\sankeyoutinmidptname\space r) + (0,\mylength pt) coordinate (\sankeyoutinmidptname\space l) + ; + } + }](\sn.center) to[out=\sankeygetnodeorient{\sn},in=\sankeygetnodeorient{\en}+180] (\en.center); + \foreach \myptnum in {1,...,\laststep}{ + \def\sankeyoutinmidptname{sankeyoutinmidpt \myptnum} + \anglebetween\sankeyoutinmidptangle{\sankeyoutinmidptname\space r}{\sankeyoutinmidptname\space l} + \sankeynodenodebug{quantity=\qty,angle=\sankeyoutinmidptangle-90,name=\sankeyoutinmidptname,at=\sankeyoutinmidptname} + } + \sankeynodenodebug{quantity=\sankeygetnodeqty{\sn},angle=\sankeygetnodeorient{\sn},name={sankeyoutinmidpt 0},at={\sn}} + \sankeynodenodebug{quantity={\sankeygetnodeqty{\en}},angle={\sankeygetnodeorient{\en}},name={sankeyoutinmidpt \sankeystepoutin},at={\en}} + + %\typeout{*********************} + \path[/sankey/fill,looseness=1] + (sankeyoutinmidpt 0.left) + \foreach \curpt[remember=\curpt as \prevpt (initially 0)] in {1,...,\sankeystepoutin}{ + to[out=\sankeygetnodeorient{sankeyoutinmidpt \prevpt},in=\sankeygetnodeorient{sankeyoutinmidpt \curpt}+180] + (sankeyoutinmidpt \curpt.left) + } + -- + (sankeyoutinmidpt \sankeystepoutin.right) + \foreach \curpt[remember=\curpt as \prevpt (initially \sankeystepoutin)] in {\laststep,...,0}{ + to[out=\sankeygetnodeorient{sankeyoutinmidpt \prevpt}+180,in=\sankeygetnodeorient{sankeyoutinmidpt \curpt}] + (sankeyoutinmidpt \curpt.right) + } + -- cycle; + + \path[/sankey/draw,looseness=1] + (sankeyoutinmidpt 0.left) + \foreach \curpt[remember=\curpt as \prevpt (initially 0)] in {1,...,\sankeystepoutin}{ + to[out=\sankeygetnodeorient{sankeyoutinmidpt \prevpt},in=\sankeygetnodeorient{sankeyoutinmidpt \curpt}+180] + (sankeyoutinmidpt \curpt.left) + } + % + (sankeyoutinmidpt \sankeystepoutin.right) + \foreach \curpt[remember=\curpt as \prevpt (initially \sankeystepoutin)] in {\laststep,...,0}{ + to[out=\sankeygetnodeorient{sankeyoutinmidpt \prevpt}+180,in=\sankeygetnodeorient{sankeyoutinmidpt \curpt}] + (sankeyoutinmidpt \curpt.right) + } + ; + + \endgroup + } + + \NewDocumentCommand\sankeynodealias{mm}{%name, alias + \path [late options={name=##1,alias=##2}]; + \sankeysetnodeqty{##2}{\sankeygetnodeqty{##1}} + \sankeysetnodeorient{##2}{\sankeygetnodeorient{##1}} + } + + \newcommand\sankeynode[2][]{ + \begingroup + \sankeyset{##1} + \sankeyset{node parameters,##2} + %\typeout{sankeynode: '\name' at(\pos) angle(\orient) qty(\qty)} + \sankeymakenode{} + \endgroup + } + + \def\sankeyfilldrawstart{ + \begin{scope}[shift={(\name)},rotate=\orient] + \path[/sankey/fill] \sankeygetstartfill{\sankeystartstyle}; + \path[/sankey/draw] \sankeygetstartdraw{\sankeystartstyle}; + \end{scope} + } + + \NewDocumentCommand\sankeystart{O{}m}{ + \begingroup + \sankeyset{##1} + \edef\name{##2} + \edef\orient{\sankeygetnodeorient{##2}} + \edef\qty{\sankeygetnodeqty{##2}} + \sankeyfilldrawstart + \endgroup + } + + \NewDocumentCommand\sankeynodestart{O{}m}{ + \begingroup + %\typeout{sankeynodestart: ##2} + \sankeyset{##1} + \sankeyset{node parameters,##2} + %\typeout{sankeynodestart: '\name' at(\pos) angle(\orient) qty(\qty)} + \sankeymakenode{} + %\typeout{sankeynodestart: sankeynode ok} + %\typeout{sankeynodestart: '\name' at(\pos) angle(\orient) qty(\qty)} + \sankeyfilldrawstart + \endgroup + } + + \def\sankeyfilldrawend{ + \begin{scope}[shift={(\name)},rotate=\sankeygetnodeorient{\name}] + \path[/sankey/fill] \sankeygetendfill{\sankeyendstyle}; + \path[/sankey/draw] \sankeygetenddraw{\sankeyendstyle}; + \end{scope} + } + + \NewDocumentCommand\sankeyend{O{}m}{%name + \begingroup + \sankeyset{##1} + \edef\name{##2} + \edef\orient{\sankeygetnodeorient{##2}} + \edef\qty{\sankeygetnodeqty{##2}} + \sankeyfilldrawend + \endgroup + } + + \NewDocumentCommand\sankeynodeend{O{}m}{ + \begingroup + %\typeout{sankeynodestart: ##2} + \sankeyset{##1} + \sankeyset{node parameters,##2} + %\typeout{sankeynodestart: '\name' at(\pos) angle(\orient) qty(\qty)} + \sankeymakenode{} + %\typeout{sankeynodestart: sankeynode ok} + %\typeout{sankeynodestart: '\name' at(\pos) angle(\orient) qty(\qty)} + \sankeyfilldrawend + \endgroup + } + + + + \NewDocumentCommand\sankeyadvance{sO{}mm}{% *(reverse), options, name, distance + \begingroup + \sankeyset{##2} + \def\newname{##3} + \def\oldname{##3-old} + \sankeynodealias{\newname}{\oldname} + % \typeout{*** sankeyadvance: ok (\oldname => \newname)} + \IfBooleanTF{##1}{ + % reverse advance + \sankeynode{ + at={$(\oldname.center)!##4!90:(\oldname.left)$}, + angle=\sankeygetnodeorient{\oldname}, + quantity=\sankeygetnodeqty{\oldname}, + name=\newname, + } + \path[/sankey/fill] + (\newname.left) -- (\oldname.left) + -- + (\oldname.right) -- (\newname.right) + -- cycle; + \path[/sankey/draw] + (\newname.left) -- (\oldname.left) + (\oldname.right) -- (\newname.right); + \endgroup + }{ + % advance + \sankeynode{ + at={$(\oldname.center)!##4!-90:(\oldname.left)$}, + angle=\sankeygetnodeorient{\oldname}, + quantity=\sankeygetnodeqty{\oldname}, + name=\newname, + } + \path[/sankey/fill] + (\oldname.left) -- (\newname.left) + -- + (\newname.right) -- (\oldname.right) + -- cycle; + \path[/sankey/draw] + (\oldname.left) -- (\newname.left) + (\newname.right) -- (\oldname.right); + \endgroup + } + } + + \NewDocumentCommand\sankeyturn{sO{}mm}{% *(reverse), options, name, angle + \begingroup + \sankeyset{##2} + \def\name{##3} + \def\oldname{##3-old} + \sankeynodealias{\name}{\oldname} + \pgfmathsetmacro\qty{\sankeygetnodeqty{\oldname}} + \pgfmathsetmacro\oldangle{\sankeygetnodeorient{\oldname}} + \IfBooleanTF{##1}{ + % turn in reverse + \pgfmathsetmacro\orient{\oldangle-##4} + % \typeout{TURN: \oldangle=>\orient} + \ifnumgreater{##4}{0}{% anti-clockwise turn + \path let + \p1=(##3.left), \p2=(##3.right), + % center of rotation + \p{c}=($(\p1)!-\sankeyminradius!(\p2)$), + % starting position + \p{s}=(##3.center), + % ending position + \p{e}=($(\p{c})!1!-##4:(\p{s})$) + in \pgfextra{ + \begin{pgfinterruptpath} + % \typeout{acw turn:\qty:\orient} + \def\pos{\p{e}} + \sankeymakenode{} + \sankeyarc{\name}{\oldname} + \end{pgfinterruptpath} + }; + }{ % clockwise turn + \path let + \p1=(##3.right), \p2=(##3.left), + % center of rotation + \p{c}=($(\p1)!-\sankeyminradius!(\p2)$), + % starting position + \p{s}=(##3.center), + % ending position + \p{e}=($(\p{c})!1!-##4:(\p{s})$) + in \pgfextra{ + % \typeout{cw turn:\qty:\orient} + \begin{pgfinterruptpath} + \def\pos{\p{e}} + \sankeymakenode{} + \sankeyarc{\name}{\oldname} + \end{pgfinterruptpath} + }; + } + }{ + % turn + \pgfmathsetmacro\orient{\oldangle+##4} + % \typeout{TURN: \oldangle=>\orient} + \ifnumgreater{##4}{0}{% anti-clockwise turn + \path let + \p1=(##3.left), \p2=(##3.right), + % center of rotation + \p{c}=($(\p1)!-\sankeyminradius!(\p2)$), + % starting position + \p{s}=(##3.center), + % ending position + \p{e}=($(\p{c})!1!##4:(\p{s})$) + in \pgfextra{ + \begin{pgfinterruptpath} + % \typeout{acw turn:\qty:\orient} + \def\pos{\p{e}} + \sankeymakenode{} + \sankeyarc{\oldname}{\name} + \end{pgfinterruptpath} + }; + }{ % clockwise turn + \path let + \p1=(##3.right), \p2=(##3.left), + % center of rotation + \p{c}=($(\p1)!-\sankeyminradius!(\p2)$), + % starting position + \p{s}=(##3.center), + % ending position + \p{e}=($(\p{c})!1!##4:(\p{s})$) + in \pgfextra{ + % \typeout{cw turn:\qty:\orient} + \begin{pgfinterruptpath} + \def\pos{\p{e}} + \sankeymakenode{} + \sankeyarc{\oldname}{\name} + \end{pgfinterruptpath} + }; + } + } + \endgroup + } + + \NewDocumentCommand\sankeyfork{mm}{%name,list of forks + \def\listofforks{##2} + \xdef\sankeytot{0} + \xdef\sankeycalculus{0} + \pgfmathsetmacro\iqty{\sankeygetnodeqty{##1}} + \pgfmathsetmacro\iorient{\sankeygetnodeorient{##1}} + \pgfmathsetmacro\orient{\iorient} + %\typeout{iname:##1, iqty:\iqty,iorient:\iorient} + \foreach \qty/\name[count=\c] in \listofforks { + %\typeout{sankeyfork foreach: qty(\qty) name(\name) count(\c)} + \path + let + \p1=(##1.left),\p2=(##1.right), + \p{start}=($(\p1)!\fpeval{\sankeytot/\iqty}!(\p2)$), + \n{nexttot}={\fpeval{\sankeytot+\qty}}, + \p{end}=($(\p1)!\fpeval{\n{nexttot}/\iqty}!(\p2)$), + \p{mid}=($(\p{start})!.5!(\p{end})$) + in \pgfextra{ + %\typeout{new fork:\name} + %\typeout{qty:\qty,name:\name,count:\c,pos=\p{mid}} + \xdef\sankeytot{\n{nexttot}} + \xdef\sankeycalculus{\fpeval{\sankeycalculus+\qty}} + \begin{pgfinterruptpath} + \def\pos{\p{mid}} + \sankeymakenode{} + \end{pgfinterruptpath} + }; + } + \edef\diff{\fpeval{abs(\iqty-\sankeytot)}} + %\typeout{sankeyfork diff: \diff} + %\pgfmathsetmacro{\diff}{abs(\iqty-\sankeytot)} + %\pgfmathtruncatemacro{\finish}{\diff<0.01?1:0} + \ifnumequal{\diff}{0}{}{ + \PackageWarning{sankey}{^^J*** Warning: bad sankey fork: \iqty\space!=\space\sankeycalculus(=\sankeytot)^^J##2} + } + } + + \def\sankeyqtytolen##1{\fpeval{(##1)/\sankeytotalqty*\sankeytotallen}} + + \sankeyset{ + % % default values, + % /tikz/declare function={ + % % sankeyqtytolen(\qty)=\qty/\sankeytotalqty*\sankeytotallen; + % % sankeylentoqty(\len)=\len/\sankeytotallen*\sankeytotalqty; + % },% + default parameters,% + % user values + #1} + + %\typeout{sankeytotallen: \sankeytotallen} + + #2 % body of sankeydiagram environment +} + + +\sankeyset{ + % none style + new start style={none}{}{}, + new end style={none}{}{}, + % simple style + new start style={simple}{ + (\name.left) -- ([xshift=-.5\pgflinewidth]\name.left) -- ([xshift=-.5\pgflinewidth]\name.right) -- (\name.right) -- cycle + }{ + (\name.left) -- ([xshift=-.5\pgflinewidth]\name.left) -- ([xshift=-.5\pgflinewidth]\name.right) -- (\name.right) + }, + new end style={simple}{ + (\name.left) -- ([xshift=2mm]\name.center) -- (\name.right) -- cycle + }{ + (\name.left) -- ([xshift=2mm]\name.center) -- (\name.right) + }, + % arrow style + new start style={arrow}{ + (\name.left) -- ++(-10pt,0) + -- ([xshift=-10pt/6]\name.center) + -- ([xshift=-10pt]\name.right) + -- (\name.right) -- cycle + }{ + (\name.left) -- ++(-10pt,0) + -- ([xshift=-10pt/6]\name.center) + -- ([xshift=-10pt]\name.right) + -- (\name.right) + }, + new end style={arrow}{ + (\name.left) -- ([yshift=1mm]\name.left) + -- ([xshift=10pt]\name.center) + -- ([yshift=-1mm]\name.right) -- (\name.right) -- cycle + }{ + (\name.left) -- ([yshift=1mm]\name.left) + -- ([xshift=10pt]\name.center) + -- ([yshift=-1mm]\name.right) -- (\name.right) + }, +}
\ No newline at end of file diff --git a/graphics/pgf/contrib/sankey/tikzlibrarydubins.code.tex b/graphics/pgf/contrib/sankey/tikzlibrarydubins.code.tex new file mode 100644 index 0000000000..da1dc120dc --- /dev/null +++ b/graphics/pgf/contrib/sankey/tikzlibrarydubins.code.tex @@ -0,0 +1,361 @@ +% Copyright (C) 2016-2021 by Paul Gaborit +% +% This file may be distributed and/or modified +% +% 1. under the LaTeX Project Public License and/or +% +% 2. under the GNU Public License. + +\usetikzlibrary{calc} + +\RequirePackage{etoolbox} +\RequirePackage{etextools} +\RequirePackage{xfp} + +\newbool{dubinspathreverse} + +\def\ifpgfmathcond#1{% + \pgfmathparse{(#1)?1:0}% + \ifnumequal{\pgfmathresult}{1}% +} + +\def\anglebetween#1#2#3{%macro, s, t + \path let + \p{s}=(#2), \p{t}=(#3), + \n{angle}={atan2(\y{t}-\y{s},\x{t}-\x{s})} + in \pgfextra { + \pgfmathsetmacro#1{\n{angle}} + \aftergroup@def#1 + }; +} + +\def\distancebetween#1#2#3{%macro, s, t + \path let + \p{s}=(#2), \p{t}=(#3), + %\n{dist}={veclen(\x{t}-\x{s},\y{t}-\y{s}))} + \n{dist}={\fpeval{sqrt((\x{t}-\x{s})*(\x{t}-\x{s})+(\y{t}-\y{s})*(\y{t}-\y{s}))}} + in \pgfextra { + %\typeout{n{dist}:\n{dist}} + \pgfmathsetmacro#1{\n{dist}} + \aftergroup@def#1 + }; +} + +\newcommand\dubinspath@rsr{% s, sa, t, ta, as, len, at, r + let + \p{tr}=([shift={(\dbp@angb-90:\dbp@radius pt)}]\dbp@b), + \n1={\dbp@anga+90}, + \n2={\dbp@angb+90}, + \n3={\n2+\dbp@lastangle} + in + arc(\n1:\n1-\dbp@firstangle:\dbp@rradius pt) + -- ([shift={(\p{tr})}]\n3:\dbp@rradius pt) + arc(\n3:\n2:\dbp@rradius pt) +} + +\newcommand\dubinspath@lsl{% s, sa, t, ta, as, len, at, r + let + \p{tl}=([shift={(\dbp@angb+90:\dbp@radius pt)}]\dbp@b), + \n1={\dbp@anga-90},\n2={\n1+\dbp@firstangle}, + \n3={\dbp@angb-90},\n4={\n3-\dbp@lastangle} + in + arc(\n1:\n2:\dbp@lradius pt) + -- ([shift={(\p{tl})}]\n4:\dbp@lradius pt) + arc(\n4:\n3:\dbp@lradius pt) +} + +\newcommand\dubinspath@rsl{% s, sa, t, ta, as, len, at, r + let + \p{tl}=([shift={(\dbp@angb+90:\dbp@radius pt)}]\dbp@b), + \n1={\dbp@anga+90},\n2={\n1-\dbp@firstangle}, + \n3={\dbp@angb-90},\n4={\n3-\dbp@lastangle} + in + arc(\n1:\n2:\dbp@rradius pt) + -- ([shift={(\p{tl})}]\n4:\dbp@lradius pt) + arc(\n4:\n3:\dbp@lradius pt) +} + +\newcommand\dubinspath@lsr{% s, sa, t, ta, as, len, at, r + let + \p{tr}=([shift={(\dbp@angb-90:\dbp@radius pt)}]\dbp@b), + \n1={\dbp@anga-90},\n2={\n1+\dbp@firstangle}, + \n3={\dbp@angb+90},\n4={\n3+\dbp@lastangle} + in + arc(\n1:\n2:\dbp@lradius pt) + -- ([shift={(\p{tr})}]\n4:\dbp@rradius pt) + arc(\n4:\n3:\dbp@rradius pt) +} + +\newcommand\dubinspath@lrl{% s, sa, t, ta, as, ai, at, r + let + \n1={\dbp@anga-90},\n2={\n1+\dbp@firstangle}, + \n3={\dbp@angb-90},\n4={\n3-\dbp@lastangle} + in + arc(\n1:\n2:\dbp@lradius pt) + arc(\n2+180:\n2+180-\dbp@midparam:\dbp@rradius pt) + arc(\n4:\n3:\dbp@lradius pt) +} + +\newcommand\dubinspath@rlr{% s, sa, t, ta, as, ai, at, r + let + \n1={\dbp@anga+90},\n2={\n1-\dbp@firstangle}, + \n3={\dbp@angb+90},\n4={\n3+\dbp@lastangle} + in + arc(\n1:\n2:\dbp@rradius pt) + arc(\n2+180:\n2+180+\dbp@midparam:\dbp@lradius pt) + arc(\n4:\n3:\dbp@rradius pt) +} +\newcommand\dubinspath@rev@lsl{\dubinspath@rsr} +\newcommand\dubinspath@rev@rsr{\dubinspath@lsl} +\newcommand\dubinspath@rev@lsr{\dubinspath@lsr} +\newcommand\dubinspath@rev@rsl{\dubinspath@rsl} +\newcommand\dubinspath@rev@lrl{\dubinspath@rlr} +\newcommand\dubinspath@rev@rlr{\dubinspath@lrl} + + +\newcommand\dubinspath[1]{% + \pgfextra{ + \dubinspathset{#1} + \ifbool{dubinspathreverse}{ + \edef\newa{\dbp@b} + \edef\newb{\dbp@a} + \pgfmathsetmacro\newanga{180+\dbp@angb} + \pgfmathsetmacro\newangb{180+\dbp@anga} + \edef\newfirstangle{\dbp@lastangle} + \edef\newlastangle{\dbp@firstangle} + \edef\newmethod{rev@\dbp@method} + \edef\newlradius{\dbp@rradius} + \edef\newrradius{\dbp@lradius} + \dubinspathset{ + start point=\newa, + end point=\newb, + start angle=\newanga, + end angle=\newangb, + first angle=\newfirstangle, + last angle=\newlastangle, + left and right minimum radii=\newlradius pt and \newrradius pt, + method=\newmethod, + } + }{} + %\typeout{dbp@lradius:\dbp@lradius} + %\typeout{dbp@rradius:\dbp@rradius} + } + \csname dubinspath@\dbp@method\endcsname% +} + + +% solution +% \xdef\dubinspathmethod{} % type of dubins path +% \xdef\dubinspathlength{} % lenght (pt) +% \xdef\dubinspathfirstangle{} % first angle +% \xdef\dubinspathmiddleparam{} % intermediate angle or straight distance +% \xdef\dubinspathlastangle{} % last angle + +\def\dubinspathstore#1#2{% + \expandafter\xdef\csname dubinspath@store@#1@#2\endcsname% + {\csname dubinspath#2\endcsname}% +} +\def\dubinspathget#1#2{% + \csname dubinspath@store@#1@#2\endcsname% +} + +% +\def\dubinspathsetparams#1#2#3#4#5{% method, length, fisrt angle, middle param, last angle + \edef\dubinspathmethod{#1} + \edef\dubinspathlength{#2} + \edef\dubinspathfirstangle{#3} + \edef\dubinspathmiddleparam{#4} + \edef\dubinspathlastangle{#5} + \ifdef{\dbp@storename}{ + \foreach \p in {method,length,firstangle,middleparam,lastangle}{ + \dubinspathstore{\dbp@storename}{\p} + } + }{} +} + +\def\dubinspathupdateparams#1#2#3#4#5{ + \ifpgfmathcond{#2<\dubinspathlength}{ + \dubinspathsetparams{#1}{#2}{#3}{#4}{#5} + }{} +} + +\tikzset{ + dubins path/.is family, + dubins path, + start point/.store in=\dbp@a, + start angle/.store in=\dbp@anga, + end point/.store in=\dbp@b, + end angle/.store in=\dbp@angb, + store/.store in=\dbp@storename, + use store/.style={ + method=\dubinspathget{#1}{method}, + first angle=\dubinspathget{#1}{firstangle}, + last angle=\dubinspathget{#1}{lastangle}, + middle param=\dubinspathget{#1}{middleparam}, + }, + minimum radius/.code={ + \pgfmathsetmacro\dbp@radius{#1} + \pgfmathsetmacro\dbp@rradius{#1} + \pgfmathsetmacro\dbp@lradius{#1} + }, + left and right minimum radii/.code args={#1 and #2}{ + \pgfmathsetmacro\dbp@lradius{#1} + \pgfmathsetmacro\dbp@rradius{#2} + \pgfmathsetmacro\dbp@radius{(\dbp@lradius + \dbp@rradius)/2} + }, + method/.store in=\dbp@method, + first angle/.store in=\dbp@firstangle, + last angle/.store in=\dbp@lastangle, + middle param/.store in=\dbp@midparam, + reverse/.is if=dubinspathreverse, +} + +\newcommand\dubinspathset[1]{\tikzset{dubins path,#1}} + +\newcommand\dubinspathcalc[1]{% + \begingroup + \dubinspathset{#1} + \tikzset{ + declare function={ + angtodist(\a,\r)={abs(\a)*.01745329*\r}; + modangr(\a,\b)={ + (Mod(\a,360)<Mod(\b,360)?Mod(\a,360):Mod(\a,360)-360)+\b-Mod(\b,360) + }; + modangl(\a,\b)={ + (Mod(\a,360)<Mod(\b,360)?Mod(\a,360)+360:Mod(\a,360))+(\b)-Mod(\b,360) + }; + }, + } + + \pgfmathsetmacro\radius{\dbp@rradius} + \pgfmathsetmacro\anga{mod((\dbp@anga)+180,360)-180} + \pgfmathsetmacro\angb{mod((\dbp@angb)+180,360)-180} + %\typeout{radius:\radius,anga:\anga,angb:\angb} + \path + let + \p{a}=(\dbp@a), + \p{b}=(\dbp@b), + \p{ar}=($(\p{a}) + (\anga-90:\radius pt)$), + \p{al}=($(\p{a}) + (\anga+90:\radius pt)$), + \p{br}=($(\p{b}) + (\angb-90:\radius pt)$), + \p{bl}=($(\p{b}) + (\angb+90:\radius pt)$) + in \pgfextra{ + \pgfinterruptpath + + % RSR (ar et br) + %\typeout{RSR begin} + \anglebetween\rsrarbr{\p{ar}}{\p{br}} + \distancebetween\rsrdarbr{\p{ar}}{\p{br}} + \pgfmathsetmacro\rsrangone{Mod(\anga-\rsrarbr,360)} + \pgfmathsetmacro\rsrangtwo{Mod(\rsrarbr-\angb,360)} + \pgfmathsetmacro\rsrlen{\rsrdarbr} + \pgfmathsetmacro\rsrdist{ + angtodist(\rsrangone,\radius) + +\rsrlen + +angtodist(\rsrangtwo,\radius) + } + \dubinspathsetparams{rsr}{\rsrdist}{\rsrangone}{\rsrlen}{\rsrangtwo} + + % LSL (al et bl) + %\typeout{LSL begin} + \anglebetween\lslalbl{\p{al}}{\p{bl}} + \distancebetween\lsldalbl{\p{al}}{\p{bl}} + \pgfmathsetmacro{\lslangone}{mod(\lslalbl-\anga+720,360)} + \pgfmathsetmacro{\lslangtwo}{mod(\angb-\lslalbl+720,360)} + \pgfmathsetmacro\lsllen{\lsldalbl} + \pgfmathsetmacro\lsldist{ + angtodist(\lslangone,\radius) + +\lsllen + +angtodist(\lslangtwo,\radius) + } + \dubinspathupdateparams{lsl}{\lsldist}{\lslangone}{\lsllen}{\lslangtwo} + + % RSL (ar et bl) + %\typeout{RSL begin} + \distancebetween\rsldarbl{\p{ar}}{\p{bl}} + \pgfmathtruncatemacro\rslok{(\rsldarbl>=2*\radius)?1:0} + \ifnumequal{\rslok}{1}{ + \anglebetween\rslarbl{\p{ar}}{\p{bl}} + \pgfmathsetmacro\rslanglesup{ + asin(\radius/\rsldarbl*2)} + \pgfmathsetmacro\rslangone{Mod(\anga-\rslarbl+\rslanglesup,360)} + \pgfmathsetmacro\rslangtwo{Mod(\angb-\rslarbl+\rslanglesup,360)} + \pgfmathsetmacro\rsllen{veclen(\rsldarbl,\radius)} + \pgfmathsetmacro\rsldist{ + angtodist(\rslangone,\radius) + +\rsllen + +angtodist(\rslangtwo,\radius) + } + + \dubinspathupdateparams{rsl}{\rsldist}{\rslangone}{\rsllen}{\rslangtwo} + }{} + + % LSR (al et br) + %\typeout{LSR begin} + \distancebetween\lsrdalbr{\p{al}}{\p{br}} + \pgfmathtruncatemacro\lsrok{(\lsrdalbr>=2*\radius)?1:0} + \ifnumequal{\lsrok}{1}{ + \anglebetween\lsralbr{\p{al}}{\p{br}} + \pgfmathsetmacro\lsranglesup{ + asin(\radius/\lsrdalbr*2)} + \pgfmathsetmacro\lsrangone{Mod(\lsralbr+\lsranglesup-\anga,360)} + \pgfmathsetmacro\lsrangtwo{Mod(\lsralbr+\lsranglesup-\angb,360)} + \pgfmathsetmacro\lsrlen{veclen(\lsrdalbr,\radius)} + \pgfmathsetmacro\lsrdist{ + angtodist(\lsrangone,\radius) + +\lsrlen + +angtodist(\lsrangtwo,\radius) + } + \dubinspathupdateparams{lsr}{\lsrdist}{\lsrangone}{\lsrlen}{\lsrangtwo} + }{} + + % LRL (al et bl) + %\typeout{LRL begin} + \distancebetween\lrldalbl{\p{al}}{\p{bl}} + \pgfmathtruncatemacro\lrlok{(\lrldalbl<=4*\radius)?1:0} + \ifnumequal{\lrlok}{1}{ + % \debug{LRL} + \anglebetween\lrlalbl{\p{al}}{\p{bl}} + \pgfmathsetmacro\lrlangsup{acos(\lrldalbl/\radius/4)} + \pgfmathsetmacro\lrlangone{ + modangl(\lrlalbl+\lrlangsup,\anga-90)-(\anga-90)} + \pgfmathsetmacro\lrlangtwo{% + (\angb-90)-modangr(\lrlalbl+180-\lrlangsup,\angb-90)} + \pgfmathsetmacro\lrlangthree{360-2*(90-\lrlangsup)} + \pgfmathsetmacro\lrldist{ + angtodist(\lrlangone,\radius) + +angtodist(\lrlangthree,\radius) + +angtodist(\lrlangtwo,\radius) + } + \dubinspathupdateparams{lrl}{\lrldist}{\lrlangone}{\lrlangthree}{\lrlangtwo} + }{} + + % RLR (ar et br) + %\typeout{RLR begin} + \distancebetween\rlrdarbr{\p{ar}}{\p{br}} + \pgfmathtruncatemacro\rlrok{(\rlrdarbr<=4*\radius)?1:0} + \ifnumequal{\rlrok}{1}{ + \anglebetween\rlrarbr{\p{ar}}{\p{br}} + \pgfmathsetmacro\rlrangsup{acos(\rlrdarbr/\radius/4)} + \pgfmathsetmacro\rlrangone{ + (\anga+90)-modangr(\rlrarbr-\rlrangsup,\anga+90)} + \pgfmathsetmacro\rlrangtwo{% + modangl(\rlrarbr+180+\rlrangsup,\angb+90)-(\angb+90)} + \pgfmathsetmacro\rlrangthree{360-2*(90-\rlrangsup)} + \pgfmathsetmacro\rlrdist{ + angtodist(\rlrangone,\radius) + +angtodist(\rlrangthree,\radius) + +angtodist(\rlrangtwo,\radius) + } + \dubinspathupdateparams{rlr}{\rlrdist}{\rlrangone}{\rlrangthree}{\rlrangtwo} + }{} + + \endpgfinterruptpath + }; + \endgroup +} + +%%% Local Variables: +%%% mode: latex +%%% TeX-master: t +%%% End: |