13

I have made a wheelchart, where the labels are overlapping if there is limited space due to a small data entry. You see it happening when a single data entry is less than 3% of total (e.g. Kiwi has 3 out of 175). Take a look at the code below and you will notice that it is hard to read the names of the last few fruit items. Any suggestions on how to solve this would be highly appreciated! Some things I could think of would be to have the possibility of turning data labels inward, eastward or extending them for a bit. I would not have a clue on how to do this though.

enter image description here

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{arrows}
\usepackage{filecontents}
\usepackage{pgfplots}
\usepackage{pgfplotstable}

\begin{filecontents}{testdata.csv}
    Name,   Quantity
    "Blueberries",  56
    "Pears",    35
    "Bananas",  24
    "Grapes",   22
    "Peaches", 17
    "Mangoes", 12
    "Strawberries", 3
    "Raspberries", 3
    "Kiwis", 3
\end{filecontents}

\begin{document}

\pgfplotstableread[ignore chars=",col sep=comma]{testdata.csv}\datatable

\pgfplotstableread{
    clr
    blue!70
    green!80
    green!10!orange!90!
    yellow!50!orange!50!
    pink!30!red!60!
    purple!80
    green!60!black!40!
    black!50
    pink!30!red!60!
    blue!70
    green!10!orange!90!
    yellow!50!orange!50!
    pink!30!red!60!
}\MyColors

% Adjusts the size of the wheel:
\def\innerradius{2.8cm}
\def\outerradius{3.2cm}

\newcommand\LabelName{}
\newcommand\LabelValue{}

% The main macro
\newcommand{\wheelchart}[1]{
    % Calculate total
    \pgfmathsetmacro{\totalnum}{0}
    % get number of rows in table
    \pgfplotstablegetrowsof{#1}
    % minus 1 because indexing starts as zero
    \pgfmathsetmacro{\RowsInTable}{\pgfplotsretval-1}
    \foreach \i in {0,...,\RowsInTable} {
        \pgfplotstablegetelem{\i}{Quantity}\of{#1}
        \pgfmathparse{\pgfplotsretval+\totalnum}
        \global\let\totalnum=\pgfmathresult
    }
    \begin{center}
        \begin{tikzpicture}

        % Calculate the thickness and the middle line of the wheel
        \pgfmathsetmacro{\wheelwidth}{\outerradius-\innerradius}
        \pgfmathsetmacro{\midradius}{(\outerradius+\innerradius)/2}

        % Rotate so we start from the top
        \begin{scope}[rotate=90]

        % Loop through each value set. \cumnum keeps track of where we are in the wheel
        \pgfmathsetmacro{\cumnum}{0}


        \foreach \i in {0,...,\RowsInTable} {
            % get values from table
            \pgfplotstablegetelem{\i}{Name}\of{#1}\global\let\LabelName=\pgfplotsretval
            \pgfplotstablegetelem{\i}{Quantity}\of{#1}\renewcommand\LabelValue{\pgfplotsretval}

            \pgfmathsetmacro{\newcumnum}{\cumnum + \LabelValue/\totalnum*360}


            % Calculate the percent value
            \pgfmathsetmacro{\percentage}{\LabelValue}
            % Calculate the mid angle of the colour segments to place the labels
            \pgfmathsetmacro{\midangle}{-(\cumnum+\newcumnum)/2}


            % This is necessary for the labels to align nicely
            \pgfmathparse{
                (-\midangle<180?"west":"east")
            } \edef\textanchor{\pgfmathresult}
            \pgfmathsetmacro\labelshiftdir{ifthenelse(\RowsInTable==0,-1,1)*(1-2*(-\midangle>180))}

            % Draw the color segments. Somehow, the \midrow units got lost, so we add 'pt' at the end. Not nice...
            \pgfplotstablegetelem{\i}{clr}\of{\MyColors}
            \fill[color=\pgfplotsretval] (-\cumnum:\outerradius) arc (-\cumnum:-(\newcumnum):\outerradius) --
            (-\newcumnum:\innerradius) arc (-\newcumnum:-(\cumnum):\innerradius) -- cycle;

            % Draw the data labels
            \draw  [*-,thin] node [append after command={(\midangle:\midradius pt) -- (\midangle:\outerradius + 1ex) -- (\tikzlastnode)}] at (\midangle:\outerradius + 1ex) [xshift=\labelshiftdir*0.5cm,inner sep=0pt, outer sep=0pt, ,anchor=\textanchor]{\LabelName: \pgfmathprintnumber{\percentage}};

            % Set the old cumulated angle to the new value
            \global\let\cumnum=\newcumnum
        }

        \end{scope}
        %      \draw[gray] (0,0) circle (\outerradius) circle (\innerradius);
        \end{tikzpicture}
    \end{center}
}

% This is where the CSV should be added instead of hard-coded data entries.
\wheelchart{\datatable}

\end{document}
  • Can't you place the two upper labels a little higher by hand? – Bernard Jun 19 '17 at 16:14
  • check for intersections of node shapes with the previous one (which is much more tedious than simply modifying manually) – percusse Jun 19 '17 at 16:26
  • You could adjust the radial distance to the label to increase as the sections get smaller. See also tex.stackexchange.com/questions/338528/… – John Kormylo Jun 19 '17 at 22:27
  • @percusse How can you check for intersections of node shapes? Sounds like a good solution for many of these problems with data labels. – Raphael Spaans Jun 20 '17 at 19:57
10
+50

Here I define the multiplier that accentuates the value at the vertical poles:

    \pgfmathsetmacro\labelmag{
      1 + .9*(abs(1/sin(\midangle)))^1 - .03*(abs(1/sin(\midangle)))^2}

and then I draw as such for the data labels:

    \draw  [*-,thin] node [append after command={(\midangle:\midradius pt) -- 
     (\midangle:\outerradius + \labelmag ex) -- (\tikzlastnode)}] at
     (\midangle:\outerradius + \labelmag ex) 

I obviously had to tweak the values of .9 and -.03 in \labelmag.

I suppose it would break (overflow) if the label were precisely at 0 or 180 degrees from the vertical.

The MWE:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{arrows}
\usepackage{filecontents}
\usepackage{pgfplots}
\usepackage{pgfplotstable}

\begin{filecontents}{testdata.csv}
    Name,   Quantity
    "Blueberries",  56
    "Pears",    35
    "Bananas",  24
    "Grapes",   22
    "Peaches", 17
    "Mangoes", 12
    "Strawberries", 3
    "Raspberries", 3
    "Kiwis", 3
\end{filecontents}

\begin{document}

\pgfplotstableread[ignore chars=",col sep=comma]{testdata.csv}\datatable

\pgfplotstableread{
    clr
    blue!70
    green!80
    green!10!orange!90!
    yellow!50!orange!50!
    pink!30!red!60!
    purple!80
    green!60!black!40!
    black!50
    pink!30!red!60!
    blue!70
    green!10!orange!90!
    yellow!50!orange!50!
    pink!30!red!60!
}\MyColors

% Adjusts the size of the wheel:
\def\innerradius{2.8cm}
\def\outerradius{3.2cm}

\newcommand\LabelName{}
\newcommand\LabelValue{}

% The main macro
\newcommand{\wheelchart}[1]{
    % Calculate total
    \pgfmathsetmacro{\totalnum}{0}
    % get number of rows in table
    \pgfplotstablegetrowsof{#1}
    % minus 1 because indexing starts as zero
    \pgfmathsetmacro{\RowsInTable}{\pgfplotsretval-1}
    \foreach \i in {0,...,\RowsInTable} {
        \pgfplotstablegetelem{\i}{Quantity}\of{#1}
        \pgfmathparse{\pgfplotsretval+\totalnum}
        \global\let\totalnum=\pgfmathresult
    }
    \begin{center}
        \begin{tikzpicture}

        % Calculate the thickness and the middle line of the wheel
        \pgfmathsetmacro{\wheelwidth}{\outerradius-\innerradius}
        \pgfmathsetmacro{\midradius}{(\outerradius+\innerradius)/2}

        % Rotate so we start from the top
        \begin{scope}[rotate=90]

        % Loop through each value set. \cumnum keeps track of where we are in the wheel
        \pgfmathsetmacro{\cumnum}{0}


        \foreach \i in {0,...,\RowsInTable} {
            % get values from table
            \pgfplotstablegetelem{\i}{Name}\of{#1}\global\let\LabelName=\pgfplotsretval
            \pgfplotstablegetelem{\i}{Quantity}\of{#1}\renewcommand\LabelValue{\pgfplotsretval}

            \pgfmathsetmacro{\newcumnum}{\cumnum + \LabelValue/\totalnum*360}


            % Calculate the percent value
            \pgfmathsetmacro{\percentage}{\LabelValue}
            % Calculate the mid angle of the colour segments to place the labels
            \pgfmathsetmacro{\midangle}{-(\cumnum+\newcumnum)/2}


            % This is necessary for the labels to align nicely
            \pgfmathparse{
                (-\midangle<180?"west":"east")
            } \edef\textanchor{\pgfmathresult}
            \pgfmathsetmacro\labelshiftdir{ifthenelse(\RowsInTable==0,-1,1)*(1-2*(-\midangle>180))}

            % Draw the color segments. Somehow, the \midrow units got lost, so we add 'pt' at the end. Not nice...
            \pgfplotstablegetelem{\i}{clr}\of{\MyColors}
            \fill[color=\pgfplotsretval] (-\cumnum:\outerradius) arc (-\cumnum:-(\newcumnum):\outerradius) --
            (-\newcumnum:\innerradius) arc (-\newcumnum:-(\cumnum):\innerradius) -- cycle;

            % Draw the data labels
            \pgfmathsetmacro\labelmag{
              1 + .9*(abs(1/sin(\midangle)))^1 - .03*(abs(1/sin(\midangle)))^2}
            \draw  [*-,thin] node [append after command={(\midangle:\midradius pt) -- 
             (\midangle:\outerradius + \labelmag ex) -- (\tikzlastnode)}] at
             (\midangle:\outerradius + \labelmag ex) 
             [xshift=\labelshiftdir*0.5cm,inner sep=0pt, outer sep=0pt, ,anchor=\textanchor]{\LabelName: \pgfmathprintnumber{\percentage}};

            % Set the old cumulated angle to the new value
            \global\let\cumnum=\newcumnum
        }

        \end{scope}
        %      \draw[gray] (0,0) circle (\outerradius) circle (\innerradius);
        \end{tikzpicture}
    \end{center}
}

% This is where the CSV should be added instead of hard-coded data entries.
\wheelchart{\datatable}

\end{document}

enter image description here


FOLLOW UP

The \labelmag function was chosen above as representative of what is needed (for this dataset). However, it can be set to any function that grows at the vertical poles (when \midangle is close to 0 or pi/2). For example, here is an alternate function that will not blow up at the poles, which is accomplished by adding a small offset to the abs(sin()) value to prevent a zero denominator:

        \def\offsetmag{.1}
        \pgfmathsetmacro\labelmag{
          1 + 1.4*(1/(\offsetmag+abs(sin(\midangle))))^1 
            -.04*(1/(\offsetmag+abs(sin(\midangle))))^2}

Note that if I set Kiwis very small, for example, changing it from 3 to 1, which effectively places the Kiwi spoke at 1.04 degrees from the vertical, it still works OK with this revised function:

enter image description here

The lesson here is that one could work improve the \labelmag function to be more versatile and forgiving of polar numbers.

  • 1
    Hi Steven B. Segletes, Looks great! But as you pointed out, it does break when the label is exactly 0 or 180. If you duplicate everything within testdata.csv (and in \myColors to make sure all labels have colors), you can see this happening. They turn inward for some reason. Could you make sure that even those last two labels turn outward too? Would be awesome, because then I think it should be complete! – Raphael Spaans Jul 6 '17 at 12:14
  • @RaphaelSpaans I don't understand the issue of "inward" turning labels with the data set provided. However, the .9 and -.03 may have to be tweaked in \pgfmathsetmacro\labelmag{ 1 + .9*(abs(1/sin(\midangle)))^1 - .03*(abs(1/sin(\midangle)))^2} for a particular dataset, if the positions are very-very close to vertical, since the -.03 could push labels inward. You know, you can replace this function with any other suitable function that grows near the poles. – Steven B. Segletes Jul 7 '17 at 13:38
  • @RaphaelSpaans Here is a function that will not blow up at the poles: \def\offsetmag{.1} \pgfmathsetmacro\labelmag{ 1 + 1.4*(abs(1/(\offsetmag+sin(\midangle))))^1 -.04*(abs(1/(\offsetmag+sin(\midangle))))^2}. But even so, the particulars of the label proximity may require manual tweaking of the multipliers. FYI, I will be going offline for the weekend, starting in 1/2 hour from this comment. – Steven B. Segletes Jul 7 '17 at 13:51
  • @RaphaelSpaans Please see my FOLLOW UP – Steven B. Segletes Jul 7 '17 at 14:01
  • 1
    Great stuff, mate! All is working as expected. I have tested it thoroughly and for the first time it is unbreakable. – Raphael Spaans Jul 7 '17 at 14:28
11

A simple solution consist of changing angle 90 to move the three last segments to the left (or to the right)

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{arrows}
\usepackage{filecontents}
\usepackage{pgfplots}
\usepackage{pgfplotstable}

\begin{filecontents}{testdata.csv}
    Name,   Quantity
    "Blueberries",  56
    "Pears",    35
    "Bananas",  24
    "Grapes",   22
    "Peaches", 17
    "Mangoes", 12
    "Strawberries", 3
    "Raspberries", 3
    "Kiwis", 3
\end{filecontents}

\begin{document}

    \pgfplotstableread[ignore chars=",col sep=comma]{testdata.csv}\datatable

    \pgfplotstableread{
        clr
        blue!70
        green!80
        green!10!orange!90!
        yellow!50!orange!50!
        pink!30!red!60!
        purple!80
        green!60!black!40!
        black!50
        pink!30!red!60!
        blue!70
        green!10!orange!90!
        yellow!50!orange!50!
        pink!30!red!60!
    }\MyColors

    % Adjusts the size of the wheel:
    \def\innerradius{2.8cm}
    \def\outerradius{3.2cm}

    \newcommand\LabelName{}
    \newcommand\LabelValue{}

    % The main macro
    \newcommand{\wheelchart}[1]{
        % Calculate total
        \pgfmathsetmacro{\totalnum}{0}
        % get number of rows in table
        \pgfplotstablegetrowsof{#1}
        % minus 1 because indexing starts as zero
        \pgfmathsetmacro{\RowsInTable}{\pgfplotsretval-1}
        \foreach \i in {0,...,\RowsInTable} {
            \pgfplotstablegetelem{\i}{Quantity}\of{#1}
            \pgfmathparse{\pgfplotsretval+\totalnum}
            \global\let\totalnum=\pgfmathresult
        }
        \begin{center}
            \begin{tikzpicture}

            % Calculate the thickness and the middle line of the wheel
            \pgfmathsetmacro{\wheelwidth}{\outerradius-\innerradius}
            \pgfmathsetmacro{\midradius}{(\outerradius+\innerradius)/2}

            % Rotate so we start from the top
            \begin{scope}[rotate=150]

            % Loop through each value set. \cumnum keeps track of where we are in the wheel
            \pgfmathsetmacro{\cumnum}{0}


            \foreach \i in {0,...,\RowsInTable} {
                % get values from table
                \pgfplotstablegetelem{\i}{Name}\of{#1}\global\let\LabelName=\pgfplotsretval
                \pgfplotstablegetelem{\i}{Quantity}\of{#1}\renewcommand\LabelValue{\pgfplotsretval}

                \pgfmathsetmacro{\newcumnum}{\cumnum + \LabelValue/\totalnum*360}


                % Calculate the percent value
                \pgfmathsetmacro{\percentage}{\LabelValue}
                % Calculate the mid angle of the colour segments to place the labels
                \pgfmathsetmacro{\midangle}{-(\cumnum+\newcumnum)/2}


                % This is necessary for the labels to align nicely
                \pgfmathparse{
                    (-\midangle<240?"west":"east")
                } \edef\textanchor{\pgfmathresult}
                \pgfmathsetmacro\labelshiftdir{ifthenelse(\RowsInTable==0,-1,1)*(1-2*(-\midangle>240))}

                % Draw the color segments. Somehow, the \midrow units got lost, so we add 'pt' at the end. Not nice...
                \pgfplotstablegetelem{\i}{clr}\of{\MyColors}
                \fill[color=\pgfplotsretval] (-\cumnum:\outerradius) arc (-\cumnum:-(\newcumnum):\outerradius) --
                (-\newcumnum:\innerradius) arc (-\newcumnum:-(\cumnum):\innerradius) -- cycle;

                % Draw the data labels
                \draw  [*-,thin] node [append after command={(\midangle:\midradius pt) -- (\midangle:\outerradius + 1ex) -- (\tikzlastnode)}] at (\midangle:\outerradius + 1ex) [xshift=\labelshiftdir*0.5cm,inner sep=0pt, outer sep=0pt, ,anchor=\textanchor]{\LabelName: \pgfmathprintnumber{\percentage}};

                % Set the old cumulated angle to the new value
                \global\let\cumnum=\newcumnum
            }

            \end{scope}
            %      \draw[gray] (0,0) circle (\outerradius) circle (\innerradius);
            \end{tikzpicture}
        \end{center}
    }

    % This is where the CSV should be added instead of hard-coded data entries.
    \wheelchart{\datatable}

 \end{document}

enter image description here

  • Hi Salim! I appreciate the solution. Although if my graph has only 1 label, then the label is overlapping the wheelchart once again. That has something to do with this section: \pgfmathparse{ (-\midangle<240?"west":"east") } \edef\textanchor{\pgfmathresult} \pgfmathsetmacro\labelshiftdir{ifthenelse(\RowsInTable==0,-1,1)*(1-2*(-\midangle>240))} We need to find a way where we can add two conditions to the midangle checker (midangle>240). I think it needs to be something along these lines (midangle>240 || midangle <60). How? – Raphael Spaans Jun 20 '17 at 19:49

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.