0

Actual
enter image description here

How can I ensure that all labeling lines are drawn out uniformly?

Target
enter image description here

(The gray verticals are guidelines; they do not have to be drawn.)

\documentclass[border=5pt, tikz]{standalone}
\usepackage{amsmath, amssymb}
\usepackage{pgfplots}
\pgfplotsset{compat=newest}
\usepgfplotslibrary{fillbetween}
\usetikzlibrary{calc}

% Input
\pgfmathsetmacro\r{5}
\pgfmathsetmacro\R{\r+3}
\pgfmathsetmacro\h{2}
\pgfmathsetmacro\Start{0}
\pgfmathsetmacro\End{0}
%
\pgfplotstableread{
Percent   Color                      Text
21           red                         1
27           cyan                       2
6            green!80!black         3
46          brown                      3
}\tabledata

\pgfplotstablegetrowsof{\tabledata}
\pgfmathsetmacro\RowNoMax{\pgfplotsretval-1}

\begin{document}
%\xdef\tempaaa{\pgfmathsetmacro\Eaand{0}}

\begin{tikzpicture}[every path/.style={thick},]
\begin{axis}[ 
clip=false,
axis lines=middle, axis equal, hide axis, 
y dir=reverse,
samples y=0,
]
\pgfplotsinvokeforeach{0,...,\RowNoMax}{% =================
\pgfplotstablegetelem{#1}{Percent}\of{\tabledata}
\pgfmathsetmacro\Percent{\pgfplotsretval}
\pgfplotstablegetelem{#1}{Color}\of{\tabledata}
\pgfmathsetmacro\Color{"\pgfplotsretval"}
\pgfplotstablegetelem{#1}{Text}\of{\tabledata}
\pgfmathsetmacro\Text{"\pgfplotsretval"}
% 
\pgfmathsetmacro\Start{\End}
\pgfmathsetmacro\End{\Start+\Percent*3.6}
\edef\temp{%%%%%%%%%%%%%%%%%% 
%% Draw
\noexpand\addplot3[name path=ui, domain=\Start:\End]({\r*sin(x)}, {\r*cos(x)}, {\h}) coordinate[pos=0](uiStart) coordinate[](uiEnd) coordinate[pos=0.5](uiMid);
\noexpand\addplot3[name path=uo, domain=\Start:\End]({\R*sin(x)}, {\R*cos(x)}, {\h}) coordinate[pos=0](uoStart) coordinate[](uoEnd) coordinate[pos=0.5](uoMid);
%% Text
\noexpand\coordinate[] (Mid) at ($(uiMid)!0.5!(uoMid)$);
\noexpand\draw[fill] (Mid) circle(2pt) -- ([xshift=33mm]Mid) node[anchor=south east]{\Text} node[anchor=north east]{\Percent\%};
}\temp}% =========================

\end{axis}
\end{tikzpicture}
\end{document}
cis
  • 8,073
  • 1
  • 16
  • 45

1 Answers1

2

In order to automatically choose the left or right side for the “caption”, it is best to convert the plot to a 2D plot, otherwise finding where to draw the line is made artificially difficult. Here is code that automatically chooses the “correct” side (based on the position relatively to a vertical line splitting both ellipses in halves):

\documentclass[border=5pt, tikz]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\usetikzlibrary{calc}

% Input
\pgfmathsetmacro\rA{5.0}
\pgfmathsetmacro\RA{7.0}
\pgfmathsetmacro\rB{1.5*\rA}
\pgfmathsetmacro\RB{1.5*\RA}
\pgfmathsetmacro\h{2}
\pgfmathsetmacro\Start{0}
\pgfmathsetmacro\End{0}
%
\pgfplotstableread{
Percent   Color           Text
21        red             1
27        cyan            2
6         green!80!black  3
46        brown           3
}\tabledata

\pgfplotstablegetrowsof{\tabledata}
\pgfmathsetmacro\RowNoMax{\pgfplotsretval-1}

\newif\ifcapleft

\begin{document}

\begin{tikzpicture}[every path/.style={thick}]
\begin{axis}[
  clip=false,
  axis lines=middle, axis equal, hide axis,
  y dir=reverse,
  samples y=0,
  ]
\pgfplotsinvokeforeach{0,...,\RowNoMax}{
  \pgfplotstablegetelem{#1}{Percent}\of{\tabledata}
  \pgfmathsetmacro\Percent{\pgfplotsretval}
  \pgfplotstablegetelem{#1}{Color}\of{\tabledata}
  \pgfmathsetmacro\Color{"\pgfplotsretval"}
  \pgfplotstablegetelem{#1}{Text}\of{\tabledata}
  \pgfmathsetmacro\Text{"\pgfplotsretval"}

  \pgfmathsetmacro\Start{\End}
  \pgfmathsetmacro\End{\Start+\Percent*3.6}
  \pgfmathtruncatemacro\tmpLeft{
    ifthenelse(Mod(0.5*(\Start+\End), 360) < 180, 0, 1)}

  \ifnum\tmpLeft=1 \caplefttrue \else \capleftfalse \fi
  \edef\Nanchor{\ifcapleft north west\else north east\fi}
  \edef\Sanchor{\ifcapleft south west\else south east\fi}

  \edef\temp{
   % Draw
   \noexpand\addplot[domain=\Start:\End, smooth]
     ({\RA*sin(x)}, {\rA*cos(x)}) coordinate[pos=0](uiStart)
     coordinate[](uiEnd) coordinate[pos=0.5](uiMid);
   \noexpand\addplot[domain=\Start:\End, smooth]
     ({\RB*sin(x)}, {\rB*cos(x)}) coordinate[pos=0](uoStart)
     coordinate[](uoEnd) coordinate[pos=0.5](uoMid);
   % Text
   \noexpand\coordinate (Mid) at ($(uiMid)!0.5!(uoMid)$);
   \noexpand\coordinate (X) at (Mid -| \ifcapleft-\fi 4cm,0);
   %
   \unexpanded{\begin{scope}[nodes={inner xsep=0pt, font=\footnotesize}]}
     \noexpand\draw[fill] (Mid) circle[radius=2pt] -- (X)
       node[anchor=\Sanchor] {\Text} node[anchor=\Nanchor] {$\Percent\,\%$};
   \noexpand\end{scope}
  }
  \temp
}
\end{axis}
\end{tikzpicture}

\end{document}

screenshot

Notes:

  • The circle(2pt) syntax is obsolete and causes problems. Use circle[radius=2pt].

  • \pgfplotsset{compat=newest} does not yield reproducible results. Better use \pgfplotsset{compat=1.16} in a place like here, where people may retry the code years later.

  • I added smooth to the ellipses plots, otherwise one could see the segments joining the sample points.

What follows is my first answer that reused the 3D plot (!) for the ellipses from the original code. Here, we add a new column to the table that says whether the “caption” should be printed on the left or on the right. Thus, the side must be manually chosen for each data point (row of the table).

\documentclass[border=5pt, tikz]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\usetikzlibrary{calc}

% Input
\pgfmathsetmacro\r{5}
\pgfmathsetmacro\R{\r+3}
\pgfmathsetmacro\h{2}
\pgfmathsetmacro\Start{0}
\pgfmathsetmacro\End{0}
%
\pgfplotstableread{
Percent   Color                      Text   LabelPos
21        red                        1      r
27        cyan                       2      r
6         green!80!black             3      r
46        brown                      3      l
}\tabledata

\pgfplotstablegetrowsof{\tabledata}
\pgfmathsetmacro\RowNoMax{\pgfplotsretval-1}

\newif\ifcapleft

\begin{document}

\begin{tikzpicture}[every path/.style={thick}]
\begin{axis}[
  clip=false,
  axis lines=middle, axis equal, hide axis,
  y dir=reverse,
  samples y=0,
  ]
\pgfplotsinvokeforeach{0,...,\RowNoMax}{
  \pgfplotstablegetelem{#1}{Percent}\of{\tabledata}
  \pgfmathsetmacro\Percent{\pgfplotsretval}
  \pgfplotstablegetelem{#1}{Color}\of{\tabledata}
  \pgfmathsetmacro\Color{"\pgfplotsretval"}
  \pgfplotstablegetelem{#1}{Text}\of{\tabledata}
  \pgfmathsetmacro\Text{"\pgfplotsretval"}
  \pgfplotstablegetelem{#1}{LabelPos}\of{\tabledata}
  \pgfmathsetmacro\LabelPos{"\pgfplotsretval"}

  \pgfmathsetmacro\Start{\End}
  \pgfmathsetmacro\End{\Start+\Percent*3.6}

  \if\LabelPos l\caplefttrue\else\capleftfalse\fi
  \edef\Nanchor{\ifcapleft north west\else north east\fi}
  \edef\Sanchor{\ifcapleft south west\else south east\fi}
  \edef\temp{
   % Draw
   \noexpand\addplot3[domain=\Start:\End, smooth]
     ({\r*sin(x)}, {\r*cos(x)}, {\h}) coordinate[pos=0](uiStart)
     coordinate[](uiEnd) coordinate[pos=0.5](uiMid);
   \noexpand\addplot3[domain=\Start:\End, smooth]
     ({\R*sin(x)}, {\R*cos(x)}, {\h}) coordinate[pos=0](uoStart)
     coordinate[](uoEnd) coordinate[pos=0.5](uoMid);
   % Text
   \noexpand\coordinate (Mid) at ($(uiMid)!0.5!(uoMid)$);
   \noexpand\coordinate (X) at (Mid -| \ifcapleft-\fi 4cm,0);
   %
   \unexpanded{\begin{scope}[nodes={inner xsep=0pt, font=\footnotesize}]}
     \noexpand\draw[fill] (Mid) circle[radius=2pt] -- (X)
       node[anchor=\Sanchor] {\Text} node[anchor=\Nanchor] {$\Percent\,\%$};
   \noexpand\end{scope}
  }
  \temp
}
\end{axis}
\end{tikzpicture}

\end{document}

screenshot

If you want to stay with the 3D plot but still have automatic placement of the “captions”, you can use the following code. Be aware that due to the projection, the chosen side is not obvious at first glance:

\documentclass[border=5pt, tikz]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.16}
\usetikzlibrary{calc}

% Input
\pgfmathsetmacro\r{5}
\pgfmathsetmacro\R{\r+3}
\pgfmathsetmacro\h{2}
\pgfmathsetmacro\Start{0}
\pgfmathsetmacro\End{0}
%
\pgfplotstableread{
Percent   Color           Text
21        red             1
27        cyan            2
6         green!80!black  3
46        brown           3
}\tabledata

\pgfplotstablegetrowsof{\tabledata}
\pgfmathsetmacro\RowNoMax{\pgfplotsretval-1}

\newif\ifcapleft

\begin{document}

\begin{tikzpicture}[every path/.style={thick}]
\begin{axis}[
  clip=false,
  axis lines=middle, axis equal, hide axis,
  y dir=reverse,
  samples y=0,
  ]
\pgfplotsinvokeforeach{0,...,\RowNoMax}{
  \pgfplotstablegetelem{#1}{Percent}\of{\tabledata}
  \pgfmathsetmacro\Percent{\pgfplotsretval}
  \pgfplotstablegetelem{#1}{Color}\of{\tabledata}
  \pgfmathsetmacro\Color{"\pgfplotsretval"}
  \pgfplotstablegetelem{#1}{Text}\of{\tabledata}
  \pgfmathsetmacro\Text{"\pgfplotsretval"}

  \pgfmathsetmacro\Start{\End}
  \pgfmathsetmacro\End{\Start+\Percent*3.6}
  \pgfmathtruncatemacro\tmpLeft{
    ifthenelse(Mod(0.5*(\Start+\End), 360) < 180, 0, 1)}

  \ifnum\tmpLeft=1 \caplefttrue \else \capleftfalse \fi
  \edef\Nanchor{\ifcapleft north west\else north east\fi}
  \edef\Sanchor{\ifcapleft south west\else south east\fi}
  \edef\temp{
   % Draw
   \noexpand\addplot3[domain=\Start:\End, smooth]
     ({\r*sin(x)}, {\r*cos(x)}, {\h}) coordinate[pos=0](uiStart)
     coordinate[](uiEnd) coordinate[pos=0.5](uiMid);
   \noexpand\addplot3[domain=\Start:\End, smooth]
     ({\R*sin(x)}, {\R*cos(x)}, {\h}) coordinate[pos=0](uoStart)
     coordinate[](uoEnd) coordinate[pos=0.5](uoMid);
   % Text
   \noexpand\coordinate (Mid) at ($(uiMid)!0.5!(uoMid)$);
   \noexpand\coordinate (X) at (Mid -| \ifcapleft-\fi 4cm,0);
   %
   \unexpanded{\begin{scope}[nodes={inner xsep=0pt, inner ysep=2pt, font=\tiny}]}
     \noexpand\draw[fill] (Mid) circle[radius=2pt] -- (X)
       node[anchor=\Sanchor] {\Text} node[anchor=\Nanchor] {$\Percent\,\%$};
   \noexpand\end{scope}
  }
  \temp
}
\end{axis}
\end{tikzpicture}

\end{document}

screenshot

frougon
  • 24,283
  • 1
  • 32
  • 55
  • Wow, this is very good; especially your if-methode. – cis Jan 31 '20 at 09:57
  • I think I can still automate the if-method ("label left or right"), without a additional column. It should work by looking at \Start at an angle greater than 180°. I have now only marginally addressed the problem; I was now concerned with the uniform label lines. – cis Jan 31 '20 at 10:05
  • +1: Also the remark regarding \pgfplotsset{compat=newest} is valuable. – Dr. Manuel Kuehner Jan 31 '20 at 10:13
  • @cis The 180° angle is not where you expect it. Try \addplot3 [only marks, mark=*, red] coordinates { ({\r*sin(0)}, {\r*cos(0)}, {\h}) }; \addplot3 [only marks, mark=*, blue] coordinates { ({\r*sin(180)}, {\r*cos(180)}, {\h}) }; before \end{axis}. This has to do with the fact that the ellipses are drawn in the original code with \addplot3 (otherwise these particular equations would give circles). – frougon Jan 31 '20 at 10:16
  • I converted the plot to 2D and added automatic placement of the “data captions” based on the \Start angle. – frougon Jan 31 '20 at 10:53
  • No no.. this has to be 3D, because it is a small part ("MWE") of a 3D-ring-diagramm. – cis Jan 31 '20 at 10:55
  • @csi Well, please see the updated answer, then. – frougon Jan 31 '20 at 11:10