17

My aim is to provide tools to draw some irregular shapes more easily. Of course, I am aware of the smooth plots provided by TikZ but my aim is to make to allow for an alternative approach. Very often, the shapes are more or less defined by some extreme points, at which the distance to the barycenter of the shape is extremal (examples come below). I was able to get some shapes but only at the expense of borrowing routines from some other places, which I quote, and these routines appear to be more complex than necessarily needed. Further, my code only treats 4 coordinates. In principle, I now how to extend it but I feel I should simplify things before. This is my code so far:

\documentclass{article}
\usepackage{tikz}
\usetikzlibrary{calc}
\usetikzlibrary{hobby}
\makeatletter % from https://tex.stackexchange.com/a/283273/121799
% Here we define the comparison macro for pairs (a,b)
% We assume decimal numbers acceptable to \ifdim tests
\long\def\xintdothis #1#2\xintorthat #3{\fi #1}%
\let\xintorthat \@firstofone

\long\def\@thirdoffour  #1#2#3#4{#3}%
\long\def\@fourthoffour #1#2#3#4{#4}%

\def\IfFirstPairIsGreaterTF #1#2{\@IfFirstPairIsGreaterTF #1,#2,}%

\def\@IfFirstPairIsGreaterTF #1,#2,#3,#4,{%
    \ifdim #1\p@=#3\p@
       \xintdothis{%
         \ifdim #2\p@>#4\p@\expandafter\@firstoftwo
         \else\expandafter\@secondoftwo\fi}\fi
    \ifdim #1\p@>#3\p@\expandafter\@thirdoffour
                      \else\expandafter\@fourthoffour\fi
    \xintorthat{}%
}%

% not needed for numerical inputs
% \catcode`! 3
% \catcode`? 3

% Here there is a very strange \romannumeral0\romannumeral0, this is
% due to some convoluted scheme to avoid double spaces or no spaces
% in between coordinate pairs. Trust me.
\def\QSpairs {\romannumeral0\romannumeral0\qspairs }%
% first we check if empty list
\def\qspairs   #1{\expandafter\qspairs@a\romannumeral-`0#1(!)(?)}%
\def\qspairs@a #1(#2{\ifx!#2\expandafter\qspairs@abort\else
                        \expandafter\qspairs@b\fi (#2}%
\edef\qspairs@abort #1(?){\space\space}%
%
% we check if empty of single and if not pick up the first as Pivot:
\def\qspairs@b #1(#2)#3(#4){\ifx?#4\xintdothis\qspairs@empty\fi
                   \ifx!#4\xintdothis\qspairs@single\fi
                   \xintorthat \qspairs@separate {}{}{#2}(#4)}%
\def\qspairs@empty  #1(?){ }%
\edef\qspairs@single #1#2#3#4(?){\space\space(#3)}%
\def\qspairs@separate #1#2#3#4(#5)%
{%
    \ifx!#5\expandafter\qspairs@separate@done\fi
    \IfFirstPairIsGreaterTF {#5}{#3}%
          \qspairs@separate@appendtogreater
          \qspairs@separate@appendtosmaller {#5}{#1}{#2}{#3}%
}%
%
\def\qspairs@separate@appendtogreater #1#2{\qspairs@separate {#2 (#1)}}%
\def\qspairs@separate@appendtosmaller #1#2#3{\qspairs@separate {#2}{#3 (#1)}}%
%
\def\qspairs@separate@done\IfFirstPairIsGreaterTF #1#2%
    \qspairs@separate@appendtogreater
    \qspairs@separate@appendtosmaller #3#4#5#6(?)%
{%
    \expandafter\qspairs@f\expandafter
    {\romannumeral0\qspairs@b #4(!)(?)}{\qspairs@b #5(!)(?)}{ (#2)}%
}%
%
\def\qspairs@f #1#2#3{#2#3#1}%
%
% \catcode`! 12
% \catcode`? 12

\makeatother
\makeatletter % from https://tex.stackexchange.com/a/412901/121799
\newcommand{\Distance}[3]{% % from https://tex.stackexchange.com/q/56353/121799
\tikz@scan@one@point\pgfutil@firstofone($#1-#2$)\relax  
\pgfmathsetmacro{#3}{veclen(\the\pgf@x,\the\pgf@y)/28.45274}
}
\makeatother 
\newcount\nbofwords
\makeatletter% from https://tex.stackexchange.com/a/12819/121799
\def\myutil@empty{}
\def\multiwords#1 #2\@nil{% 
 \def\NextArg{#2}%
 \advance\nbofwords by  1 %   
 \expandafter\edef\csname word\@alph\nbofwords\endcsname{#1}% 
 \ifx\myutil@empty\NextArg
     \let\next\@gobble
 \fi
 \next#2\@nil
}%    
\def\GetWords#1{%
   \let\next\multiwords 
   \nbofwords=0 %
   \expandafter\next#1 \@nil %
}% 
\makeatother

\long\def\First(#1,#2){#1}
\long\def\Second(#1,#2){#2}
\tikzset{declare
function={interpolator(\x,\xmin,\xmax,\rmin,\rmax)=(\rmin+\rmax)/2+((\rmin-\rmax)/2)*cos((\x-\xmin)*(180/(\xmax-\xmin)));}}
%\tikzset{declare function={PotatoeRadius(\x,\angleA,\angleB,\angleC,\angleD,\distanceA,\distanceB,\distanceC,\distanceD)=\distanceA+(\x-\angleA)*((\distanceB-\distanceA)/(\angleB-\angleA)+(\x-\angleB)*(((-1)*((\distanceB-\distanceA)/(\angleB-\angleA))+(\distanceC-\distanceB)/(\angleC-\angleB))/(\angleC-\angleA)+(((-1)*(((-1)*((\distanceB-\distanceA)/(\angleB-\angleA))+(\distanceC-\distanceB)/(\angleC-\angleB))/(\angleC-\angleA))+((-1)*((\distanceC-\distanceB)/(\angleC-\angleB))+(\distanceD-\distanceC)/(\angleD-\angleC))/(\angleD-\angleB))*(\x-\angleC))/(\angleD-\angleA)));}}
%(\angleC*(\angleC-\angleD)*\angleD*((\distanceA-\distanceB)*(\angleC-\x)*(\angleD-\x)*\x+pow(\angleA,3)*(-(\angleC*pow(\angleD,2)*\distanceB)+\angleD*(\distanceB-\distanceC)*(\angleD-\x)*\x+\angleC*(\distanceB-\distanceD)*pow(\x,2)+pow(\angleC,2)*(\angleD*\distanceB+(-\distanceB+\distanceD)*\x))+pow(\angleB,3)*(-(\angleA*pow(\angleD,2)*\distanceC)-\angleD*(\distanceA-\distanceC)*(\angleD-\x)*\x+\angleA*(\distanceC-\distanceD)*pow(\x,2)+pow(\angleC,2)*(-(\angleD*\distanceA)+\angleA*\distanceD+\distanceA*\x-\distanceD*\x)+pow(\angleA,2)*(\angleD*\distanceC-\distanceC*\x+\distanceD*\x)+\angleC*(pow(\angleD,2)*\distanceA-pow(\angleA,2)*\distanceD+(-\distanceA+\distanceD)*pow(\x,2)))+pow(\angleA,2)*(-(\angleD*(\distanceB-\distanceC)*(\angleD-\x)*\x*(\angleD+\x))-pow(\angleC,3)*(\angleD*\distanceB+(-\distanceB+\distanceD)*\x)+\angleC*(pow(\angleD,3)*\distanceB+(-\distanceB+\distanceD)*pow(\x,3)))+\angleA*(pow(\angleD,2)*(\distanceB-\distanceC)*(\angleD-\x)*pow(\x,2)+pow(\angleC,3)*(pow(\angleD,2)*\distanceB+(-\distanceB+\distanceD)*pow(\x,2))-pow(\angleC,2)*(pow(\angleD,3)*\distanceB+(-\distanceB+\distanceD)*pow(\x,3)))+pow(\angleB,2)*(\angleD*(\distanceA-\distanceC)*(\angleD-\x)*\x*(\angleD+\x)+pow(\angleC,3)*(\angleD*\distanceA-\angleA*\distanceD-\distanceA*\x+\distanceD*\x)-pow(\angleA,3)*(\angleD*\distanceC+(-\distanceC+\distanceD)*\x)+\angleC*(-(pow(\angleD,3)*\distanceA)+pow(\angleA,3)*\distanceD+(\distanceA-\distanceD)*pow(\x,3))+\angleA*(pow(\angleD,3)*\distanceC+(-\distanceC+\distanceD)*pow(\x,3)))+\angleB*(-(pow(\angleD,2)*(\distanceA-\distanceC)*(\angleD-\x)*pow(\x,2))+pow(\angleC,3)*(-(pow(\angleD,2)*\distanceA)+pow(\angleA,2)*\distanceD+(\distanceA-\distanceD)*pow(\x,2))+pow(\angleA,3)*(pow(\angleD,2)*\distanceC+(-\distanceC+\distanceD)*pow(\x,2))+pow(\angleC,2)*(pow(\angleD,3)*\distanceA-pow(\angleA,3)*\distanceD+(-\distanceA+\distanceD)*pow(\x,3))-pow(\angleA,2)*(pow(\angleD,3)*\distanceC+(-\distanceC+\distanceD)*pow(\x,3))))/((\angleA-\angleB)*(\angleA-\angleC)*(\angleB-\angleC)*(\angleA-\angleD)*(\angleB-\angleD)*(\angleC-\angleD)));}}
\newcommand{\DrawArcAngle}[6][]{% just for emergencies
\pgfmathanglebetweenpoints{\pgfpointanchor{#3}{center}}{\pgfpointanchor{#2}{center}}
\xdef\angleA{\pgfmathresult}
\pgfmathanglebetweenpoints{\pgfpointanchor{#3}{center}}{\pgfpointanchor{#4}{center}}
\xdef\angleB{\pgfmathresult}
\draw[#1] ($(#3)+(\angleA:#5)$) arc [start angle=\angleA,end angle=\angleB,radius=#5]
#6;
}
\newcommand{\DrawPotato}[5][]{
\coordinate (PotatoCenter) at (barycentric cs:#2=1,#3=1,#4=1,#5=1);
\pgfmathanglebetweenpoints{\pgfpointanchor{PotatoCenter}{center}}{\pgfpointanchor{#2}{center}}
\xdef\angleA{\pgfmathresult}
\pgfmathanglebetweenpoints{\pgfpointanchor{PotatoCenter}{center}}{\pgfpointanchor{#3}{center}}
\xdef\angleB{\pgfmathresult}
\pgfmathanglebetweenpoints{\pgfpointanchor{PotatoCenter}{center}}{\pgfpointanchor{#4}{center}}
\xdef\angleC{\pgfmathresult}
\pgfmathanglebetweenpoints{\pgfpointanchor{PotatoCenter}{center}}{\pgfpointanchor{#5}{center}}
\xdef\angleD{\pgfmathresult}

\Distance{(PotatoCenter)}{(#2)}{\distanceA}
\Distance{(PotatoCenter)}{(#3)}{\distanceB}
\Distance{(PotatoCenter)}{(#4)}{\distanceC}
\Distance{(PotatoCenter)}{(#5)}{\distanceD}
\xdef\coordList{(\angleA,\distanceA) (\angleB,\distanceB) (\angleC,\distanceC) (\angleD,\distanceD)}%
\typeout{\coordList}
\xdef\sortedList{\QSpairs{\coordList}}%
\GetWords{\sortedList}
\xdef\NewList{\worda,\wordb,\wordc,\wordd}%
\xdef\NewList{\expandafter\First\worda/\expandafter\Second\worda, 
\expandafter\First\wordb/\expandafter\Second\wordb,
\expandafter\First\wordc/\expandafter\Second\wordc,
\expandafter\First\wordd/\expandafter\Second\wordd}% this list is not used
\xdef\angleA{\expandafter\First\worda}%
\xdef\distanceA{\expandafter\Second\worda}%
\xdef\angleB{\expandafter\First\wordb}%
\xdef\distanceB{\expandafter\Second\wordb}%
\xdef\angleC{\expandafter\First\wordc}%
\xdef\distanceC{\expandafter\Second\wordc}%
\xdef\angleD{\expandafter\First\wordd}%
\xdef\distanceD{\expandafter\Second\wordd}%
\begin{scope}[shift=(PotatoCenter)]
\draw[#1,smooth,samples=50] plot[variable=\x,domain=\angleA:\angleB] %
(\x:{interpolator(\x,\angleA,\angleB,\distanceA,\distanceB)})
-- 
plot[variable=\x,domain=\angleB:\angleC] %
(\x:{interpolator(\x,\angleB,\angleC,\distanceB,\distanceC)})
--
plot[variable=\x,domain=\angleC:\angleD] %
(\x:{interpolator(\x,\angleC,\angleD,\distanceC,\distanceD)})
--
plot[variable=\x,domain=\angleD:{\angleA+360}] %
(\x:{interpolator(\x,\angleD,{\angleA+360},\distanceD,\distanceA)});
\end{scope}
}
\begin{document}
\begin{tabular}{cc}
potato & potato (ordering) \\
\begin{tikzpicture}
\coordinate (A) at (-1,1);
\coordinate (B) at (3,0.5);
\coordinate (C) at (4,-1);
\coordinate (D) at (1,-1);
\foreach \p in {A,B,C,D}
{\draw[fill=black] (\p) circle (1pt);}
\DrawPotato[blue]{A}{B}{C}{D}
\draw[red] plot[smooth cycle,tension=1] coordinates{(A) (B) (C) (D)};
\end{tikzpicture}& 
\begin{tikzpicture}
\coordinate (A) at (-1,1);
\coordinate (B) at (3,0.5);
\coordinate (C) at (4,-1);
\coordinate (D) at (1,-1);
\foreach \p in {A,B,C,D}
{\draw[fill=black] (\p) circle (1pt);}
\DrawPotato[blue]{A}{C}{B}{D}
\draw[purple] plot[smooth cycle,tension=1] coordinates{(A) (C) (B) (D)};
\end{tikzpicture}
\end{tabular}
\begin{tabular}{ll}
filled potato&filled peanut\\
\begin{tikzpicture}
\coordinate (A) at (-1,1);
\coordinate (B) at (3,0.5);
\coordinate (C) at (4,-1);
\coordinate (D) at (1,-1);
\foreach \p in {A,B,C,D}
{\draw[fill=black] (\p) circle (1pt);}
\DrawPotato[fill=blue]{A}{C}{B}{D}
\end{tikzpicture}
&
\begin{tikzpicture}
\coordinate (A) at (-1,1);
\coordinate (B) at (2,0.5);
\coordinate (C) at (4,-1);
\coordinate (D) at (1,-1);
\foreach \p in {A,B,C,D}
{\draw[fill=black] (\p) circle (1pt);}
\DrawPotato[blue,fill=red]{A}{B}{C}{D}
\begin{scope}[closed hobby]
\draw[thick] plot  coordinates{(A) (B) (C) (D)};
\end{scope}
\end{tikzpicture}
\end{tabular}
\end{document}

enter image description here

It is not hard to see that the code is a real mess, but it does roughly what I want, namely it draws potatoes defined by the extreme points (marked by black bullets). For comparison (comparisons are never fair;-) I also draw the results of the above-mentioned smooth plots. My real question is how one can make things simpler, most importantly

  • the angular ordering of the coordinates,

  • my way of making the shapes "fillable" is certainly not optimal,

  • the parsing of the ordered coordinates to a macro that accepts an arbitrary number of points (i.e. 4 or more points). Any comments are highly welcome.

EDIT: Added the output of the Hobby algorithm in the peanut figure for comparison.

UPDATE: Corrected "potatoe". It is clear to me that with some fair amount of additional adjustments one can recreate these shapes with smooth plots or Hobby plots. However, the aim here is to avoid these additional adjustments. Of course, if someone could point me to a dictionary that allows me to translate the potato code into Hobby language, that would be great.

SECOND UPDATE: I added a simple animation that illustrates what the code does so far.

\documentclass{beamer}
\usepackage{tikz}
\usetikzlibrary{calc}
%\usetikzlibrary{hobby}
\makeatletter % from https://tex.stackexchange.com/a/283273/121799
% Here we define the comparison macro for pairs (a,b)
% We assume decimal numbers acceptable to \ifdim tests
\long\def\xintdothis #1#2\xintorthat #3{\fi #1}%
\let\xintorthat \@firstofone

\long\def\@thirdoffour  #1#2#3#4{#3}%
\long\def\@fourthoffour #1#2#3#4{#4}%

\def\IfFirstPairIsGreaterTF #1#2{\@IfFirstPairIsGreaterTF #1,#2,}%

\def\@IfFirstPairIsGreaterTF #1,#2,#3,#4,{%
    \ifdim #1\p@=#3\p@
       \xintdothis{%
         \ifdim #2\p@>#4\p@\expandafter\@firstoftwo
         \else\expandafter\@secondoftwo\fi}\fi
    \ifdim #1\p@>#3\p@\expandafter\@thirdoffour
                      \else\expandafter\@fourthoffour\fi
    \xintorthat{}%
}%

% not needed for numerical inputs
% \catcode`! 3
% \catcode`? 3

% Here there is a very strange \romannumeral0\romannumeral0, this is
% due to some convoluted scheme to avoid double spaces or no spaces
% in between coordinate pairs. Trust me.
\def\QSpairs {\romannumeral0\romannumeral0\qspairs }%
% first we check if empty list
\def\qspairs   #1{\expandafter\qspairs@a\romannumeral-`0#1(!)(?)}%
\def\qspairs@a #1(#2{\ifx!#2\expandafter\qspairs@abort\else
                        \expandafter\qspairs@b\fi (#2}%
\edef\qspairs@abort #1(?){\space\space}%
%
% we check if empty of single and if not pick up the first as Pivot:
\def\qspairs@b #1(#2)#3(#4){\ifx?#4\xintdothis\qspairs@empty\fi
                   \ifx!#4\xintdothis\qspairs@single\fi
                   \xintorthat \qspairs@separate {}{}{#2}(#4)}%
\def\qspairs@empty  #1(?){ }%
\edef\qspairs@single #1#2#3#4(?){\space\space(#3)}%
\def\qspairs@separate #1#2#3#4(#5)%
{%
    \ifx!#5\expandafter\qspairs@separate@done\fi
    \IfFirstPairIsGreaterTF {#5}{#3}%
          \qspairs@separate@appendtogreater
          \qspairs@separate@appendtosmaller {#5}{#1}{#2}{#3}%
}%
%
\def\qspairs@separate@appendtogreater #1#2{\qspairs@separate {#2 (#1)}}%
\def\qspairs@separate@appendtosmaller #1#2#3{\qspairs@separate {#2}{#3 (#1)}}%
%
\def\qspairs@separate@done\IfFirstPairIsGreaterTF #1#2%
    \qspairs@separate@appendtogreater
    \qspairs@separate@appendtosmaller #3#4#5#6(?)%
{%
    \expandafter\qspairs@f\expandafter
    {\romannumeral0\qspairs@b #4(!)(?)}{\qspairs@b #5(!)(?)}{ (#2)}%
}%
%
\def\qspairs@f #1#2#3{#2#3#1}%
%
% \catcode`! 12
% \catcode`? 12

\makeatother
\makeatletter % from https://tex.stackexchange.com/a/412901/121799
\newcommand{\Distance}[3]{% % from https://tex.stackexchange.com/q/56353/121799
\tikz@scan@one@point\pgfutil@firstofone($#1-#2$)\relax  
\pgfmathsetmacro{#3}{veclen(\the\pgf@x,\the\pgf@y)/28.45274}
}
\makeatother 
\newcount\nbofwords
\makeatletter% from https://tex.stackexchange.com/a/12819/121799
\def\myutil@empty{}
\def\multiwords#1 #2\@nil{% 
 \def\NextArg{#2}%
 \advance\nbofwords by  1 %   
 \expandafter\edef\csname word\@alph\nbofwords\endcsname{#1}% 
 \ifx\myutil@empty\NextArg
     \let\next\@gobble
 \fi
 \next#2\@nil
}%    
\def\GetWords#1{%
   \let\next\multiwords 
   \nbofwords=0 %
   \expandafter\next#1 \@nil %
}% 
\makeatother

\long\def\First(#1,#2){#1}
\long\def\Second(#1,#2){#2}
\tikzset{declare
function={interpolator(\x,\xmin,\xmax,\rmin,\rmax)=(\rmin+\rmax)/2+((\rmin-\rmax)/2)*cos((\x-\xmin)*(180/(\xmax-\xmin)));}}
%\tikzset{declare function={PotatoeRadius(\x,\angleA,\angleB,\angleC,\angleD,\distanceA,\distanceB,\distanceC,\distanceD)=\distanceA+(\x-\angleA)*((\distanceB-\distanceA)/(\angleB-\angleA)+(\x-\angleB)*(((-1)*((\distanceB-\distanceA)/(\angleB-\angleA))+(\distanceC-\distanceB)/(\angleC-\angleB))/(\angleC-\angleA)+(((-1)*(((-1)*((\distanceB-\distanceA)/(\angleB-\angleA))+(\distanceC-\distanceB)/(\angleC-\angleB))/(\angleC-\angleA))+((-1)*((\distanceC-\distanceB)/(\angleC-\angleB))+(\distanceD-\distanceC)/(\angleD-\angleC))/(\angleD-\angleB))*(\x-\angleC))/(\angleD-\angleA)));}}
%(\angleC*(\angleC-\angleD)*\angleD*((\distanceA-\distanceB)*(\angleC-\x)*(\angleD-\x)*\x+pow(\angleA,3)*(-(\angleC*pow(\angleD,2)*\distanceB)+\angleD*(\distanceB-\distanceC)*(\angleD-\x)*\x+\angleC*(\distanceB-\distanceD)*pow(\x,2)+pow(\angleC,2)*(\angleD*\distanceB+(-\distanceB+\distanceD)*\x))+pow(\angleB,3)*(-(\angleA*pow(\angleD,2)*\distanceC)-\angleD*(\distanceA-\distanceC)*(\angleD-\x)*\x+\angleA*(\distanceC-\distanceD)*pow(\x,2)+pow(\angleC,2)*(-(\angleD*\distanceA)+\angleA*\distanceD+\distanceA*\x-\distanceD*\x)+pow(\angleA,2)*(\angleD*\distanceC-\distanceC*\x+\distanceD*\x)+\angleC*(pow(\angleD,2)*\distanceA-pow(\angleA,2)*\distanceD+(-\distanceA+\distanceD)*pow(\x,2)))+pow(\angleA,2)*(-(\angleD*(\distanceB-\distanceC)*(\angleD-\x)*\x*(\angleD+\x))-pow(\angleC,3)*(\angleD*\distanceB+(-\distanceB+\distanceD)*\x)+\angleC*(pow(\angleD,3)*\distanceB+(-\distanceB+\distanceD)*pow(\x,3)))+\angleA*(pow(\angleD,2)*(\distanceB-\distanceC)*(\angleD-\x)*pow(\x,2)+pow(\angleC,3)*(pow(\angleD,2)*\distanceB+(-\distanceB+\distanceD)*pow(\x,2))-pow(\angleC,2)*(pow(\angleD,3)*\distanceB+(-\distanceB+\distanceD)*pow(\x,3)))+pow(\angleB,2)*(\angleD*(\distanceA-\distanceC)*(\angleD-\x)*\x*(\angleD+\x)+pow(\angleC,3)*(\angleD*\distanceA-\angleA*\distanceD-\distanceA*\x+\distanceD*\x)-pow(\angleA,3)*(\angleD*\distanceC+(-\distanceC+\distanceD)*\x)+\angleC*(-(pow(\angleD,3)*\distanceA)+pow(\angleA,3)*\distanceD+(\distanceA-\distanceD)*pow(\x,3))+\angleA*(pow(\angleD,3)*\distanceC+(-\distanceC+\distanceD)*pow(\x,3)))+\angleB*(-(pow(\angleD,2)*(\distanceA-\distanceC)*(\angleD-\x)*pow(\x,2))+pow(\angleC,3)*(-(pow(\angleD,2)*\distanceA)+pow(\angleA,2)*\distanceD+(\distanceA-\distanceD)*pow(\x,2))+pow(\angleA,3)*(pow(\angleD,2)*\distanceC+(-\distanceC+\distanceD)*pow(\x,2))+pow(\angleC,2)*(pow(\angleD,3)*\distanceA-pow(\angleA,3)*\distanceD+(-\distanceA+\distanceD)*pow(\x,3))-pow(\angleA,2)*(pow(\angleD,3)*\distanceC+(-\distanceC+\distanceD)*pow(\x,3))))/((\angleA-\angleB)*(\angleA-\angleC)*(\angleB-\angleC)*(\angleA-\angleD)*(\angleB-\angleD)*(\angleC-\angleD)));}}
\newcommand{\DrawArcAngle}[6][]{% just for emergencies
\pgfmathanglebetweenpoints{\pgfpointanchor{#3}{center}}{\pgfpointanchor{#2}{center}}
\xdef\angleA{\pgfmathresult}
\pgfmathanglebetweenpoints{\pgfpointanchor{#3}{center}}{\pgfpointanchor{#4}{center}}
\xdef\angleB{\pgfmathresult}
\draw[#1] ($(#3)+(\angleA:#5)$) arc [start angle=\angleA,end angle=\angleB,radius=#5]
#6;
}
\newcommand{\DrawPotato}[5][]{
\coordinate (PotatoCenter) at (barycentric cs:#2=1,#3=1,#4=1,#5=1);
\pgfmathanglebetweenpoints{\pgfpointanchor{PotatoCenter}{center}}{\pgfpointanchor{#2}{center}}
\xdef\angleA{\pgfmathresult}
\pgfmathanglebetweenpoints{\pgfpointanchor{PotatoCenter}{center}}{\pgfpointanchor{#3}{center}}
\xdef\angleB{\pgfmathresult}
\pgfmathanglebetweenpoints{\pgfpointanchor{PotatoCenter}{center}}{\pgfpointanchor{#4}{center}}
\xdef\angleC{\pgfmathresult}
\pgfmathanglebetweenpoints{\pgfpointanchor{PotatoCenter}{center}}{\pgfpointanchor{#5}{center}}
\xdef\angleD{\pgfmathresult}

\Distance{(PotatoCenter)}{(#2)}{\distanceA}
\Distance{(PotatoCenter)}{(#3)}{\distanceB}
\Distance{(PotatoCenter)}{(#4)}{\distanceC}
\Distance{(PotatoCenter)}{(#5)}{\distanceD}
\xdef\coordList{(\angleA,\distanceA) (\angleB,\distanceB) (\angleC,\distanceC) (\angleD,\distanceD)}%
\typeout{\coordList}
\xdef\sortedList{\QSpairs{\coordList}}%
\GetWords{\sortedList}
\xdef\NewList{\worda,\wordb,\wordc,\wordd}%
\xdef\NewList{\expandafter\First\worda/\expandafter\Second\worda, 
\expandafter\First\wordb/\expandafter\Second\wordb,
\expandafter\First\wordc/\expandafter\Second\wordc,
\expandafter\First\wordd/\expandafter\Second\wordd}% this list is not used
\xdef\angleA{\expandafter\First\worda}%
\xdef\distanceA{\expandafter\Second\worda}%
\xdef\angleB{\expandafter\First\wordb}%
\xdef\distanceB{\expandafter\Second\wordb}%
\xdef\angleC{\expandafter\First\wordc}%
\xdef\distanceC{\expandafter\Second\wordc}%
\xdef\angleD{\expandafter\First\wordd}%
\xdef\distanceD{\expandafter\Second\wordd}%
\begin{scope}[shift=(PotatoCenter)]
\draw[#1,smooth,samples=50] plot[variable=\x,domain=\angleA:\angleB] %
(\x:{interpolator(\x,\angleA,\angleB,\distanceA,\distanceB)})
-- 
plot[variable=\x,domain=\angleB:\angleC] %
(\x:{interpolator(\x,\angleB,\angleC,\distanceB,\distanceC)})
--
plot[variable=\x,domain=\angleC:\angleD] %
(\x:{interpolator(\x,\angleC,\angleD,\distanceC,\distanceD)})
--
plot[variable=\x,domain=\angleD:{\angleA+360}] %
(\x:{interpolator(\x,\angleD,{\angleA+360},\distanceD,\distanceA)});
\end{scope}
}
\begin{document}
\begin{frame}
\frametitle{What does the current code do?}
\begin{overlayarea}{\textwidth}{\textheight}
\begin{tikzpicture}
\path[use as bounding box] (-5,-3) rectangle (7,5);
\node[text width=8cm] (text) at (0,3){%
\only<1>{input: four points, here A, B, C \& D}
\only<2>{step one: compute the barycenter of those points}
\only<3>{imagine now a circle around the barycenter with radius equal to the 
average distance}
\only<4>{the code simply adds some sine functions to the radius such that the
contour runs through the points and the distance is extremal at those points}
\only<5>{however, the code is extremely clumsy and slow, and I have the feeling
that I'm re--inventing the wheel when dealing with lists etc.}
};
\coordinate (A) at (-1,1);
\coordinate (B) at (2,0.5);
\coordinate (C) at (4,-1);
\coordinate (D) at (1,-1);
\coordinate (Center) at (barycentric cs:A=1,B=1,C=1,D=1);
\pgfmathsetmacro{\TotalDistance}{0}
\xdef\DistanceList{}
\foreach \p in {A,B,C,D}
{\draw[fill=black] (\p) circle (1pt) node[below]{\p};
}
\Distance{(Center)}{(A)}{\DistanceA}
\Distance{(Center)}{(B)}{\DistanceB}
\Distance{(Center)}{(C)}{\DistanceC}
\Distance{(Center)}{(D)}{\DistanceD}
\pgfmathsetmacro{\AverageDistance}{(\DistanceA+\DistanceB+\DistanceC+\DistanceD)/4}
\pause
\draw[fill=black] (Center) circle (1pt) node[below]{Center};
\pause
\draw[-,dashed] (Center) circle (\AverageDistance);
\pause
\only<4->{
\DrawPotato[blue]{A}{B}{C}{D}
}
\end{tikzpicture}


\end{overlayarea}
\end{frame}
\end{document}

enter image description here

  • why not using hobby library – percusse Feb 11 '18 at 2:15
  • @percusse I do not think that hobby does that, or does it? I thought it does a (polynomial and smooth) interpolation through the points. – user121799 Feb 11 '18 at 2:21
  • I have no idea what your long complex code does. Why can you not just draw the shape with control points? See answer by @Mark Wibrow on how to easily make a smooth closed curve: tex.stackexchange.com/a/255258/8650 – hpekristiansen Feb 11 '18 at 4:52
  • Wouldn't it be easier to read Hobby's docs and use the stuff it provides? You're not using any of the keys which allow you to influence the curves drawn. At least, I don't think so. 'potato' not 'potatoe' by the way ;). – cfr Feb 11 '18 at 5:02
  • There is no hobby language you just draw the points and it adjusts the smooth connections. I probably don't understand the goal here. – percusse Feb 11 '18 at 13:22
3

With the math and calc libraries, it's possible to do it slightly simpler. You can flip any two coordinates to see that it also works for coordinates that are not already ordered (I only tested flipping A and B but it should work for all of them).

I didn't change anything between \begin{document} and \end{document} so \Distance is still there, but I don't use it in my definition of \DrawPotato so you don't really need it.

enter image description here

\documentclass{beamer}
\usepackage{tikz}
\usetikzlibrary{calc,math}

% I don't use this in my \DrawPotato
\makeatletter % from https://tex.stackexchange.com/a/412901/121799
\newcommand{\Distance}[3]{% % from https://tex.stackexchange.com/q/56353/121799
    \tikz@scan@one@point\pgfutil@firstofone($#1-#2$)\relax  
    \pgfmathsetmacro{#3}{veclen(\the\pgf@x,\the\pgf@y)/28.45274}
}
\makeatother

\tikzset{
    declare function={
        interpolator(\x,\xmin,\xmax,\rmin,\rmax)=
            1/28.4527*((\rmin+\rmax)/2+((\rmin-\rmax)/2)*cos((\x-\xmin)*(180/(\xmax-\xmin))));
    }
}

\newcommand\DrawPotato[5][]{%
    \tikzmath{
        coordinate \p,\cent;
        \p0 = {(#2)};
        \p1 = {(#3)};
        \p2 = {(#4)};
        \p3 = {(#5)};
        \cent = ({(\px0+\px1+\px2+\px3)/4},{(\py0+\py1+\py2+\py3)/4}); % Average point
        real \angl,\dist;
        int \i,\j;
        for \i in {0,1,2,3}{
            \angl{\i} = atan2(\py{\i}-\centy,\px{\i}-\centx);
            \dist{\i} = veclen(\py{\i}-\centy,\px{\i}-\centx);
        };
        for \i in {0,1,2,3}{
            for \j in {0,1,2,3}{
                if min(\angl{0},\angl{1},\angl{2},\angl{3}) == \angl{\j} then {
                    \sortang{\i} = \angl{\j};
                    \sortind{\i} = \j;
                };
            };
            \angl{\sortind{\i}} = 500; % Arbitrary large value
            \sortdis{\i} = \dist{\sortind{\i}};
        };
    }
    \draw[#1,smooth,samples=50,shift={(\cent)}] 
        plot[variable=\x,domain=\sortang{0}:\sortang{1}] %
            (\x:{interpolator(\x,{\sortang{0}},{\sortang{1}},{\sortdis{0}},{\sortdis{1}})}) --
        plot[variable=\x,domain=\sortang{1}:\sortang{2}] %
            (\x:{interpolator(\x,{\sortang{1}},{\sortang{2}},{\sortdis{1}},{\sortdis{2}})}) --
        plot[variable=\x,domain=\sortang{2}:\sortang{3}] %
            (\x:{interpolator(\x,{\sortang{2}},{\sortang{3}},{\sortdis{2}},{\sortdis{3}})}) --
        plot[variable=\x,domain=\sortang{3}:\sortang{0}+360] %
            (\x:{interpolator(\x,{\sortang{3}},{\sortang{0}+360},{\sortdis{3}},{\sortdis{0}})}) --
        cycle ;
}

\begin{document}
\begin{frame}
\frametitle{What does the current code do?}
\begin{overlayarea}{\textwidth}{\textheight}
\begin{tikzpicture}
\path[use as bounding box] (-5,-3) rectangle (7,5);
\node[text width=8cm] (text) at (0,3){%
\only<1>{input: four points, here A, B, C \& D}
\only<2>{step one: compute the barycenter of those points}
\only<3>{imagine now a circle around the barycenter with radius equal to the 
average distance}
\only<4>{the code simply adds some sine functions to the radius such that the
contour runs through the points and the distance is extremal at those points}
\only<5>{however, the code is extremely clumsy and slow, and I have the feeling
that I'm re--inventing the wheel when dealing with lists etc.}
};
\coordinate (A) at (-1,1);
\coordinate (B) at (2,0.5);
\coordinate (C) at (4,-1);
\coordinate (D) at (1,-1);
\coordinate (Center) at (barycentric cs:A=1,B=1,C=1,D=1);
\pgfmathsetmacro{\TotalDistance}{0}
\xdef\DistanceList{}
\foreach \p in {A,B,C,D}
{\draw[fill=black] (\p) circle (1pt) node[below]{\p};
}
\Distance{(Center)}{(A)}{\DistanceA}
\Distance{(Center)}{(B)}{\DistanceB}
\Distance{(Center)}{(C)}{\DistanceC}
\Distance{(Center)}{(D)}{\DistanceD}
\pgfmathsetmacro{\AverageDistance}{(\DistanceA+\DistanceB+\DistanceC+\DistanceD)/4}
\pause
\draw[fill=black] (Center) circle (1pt) node[below]{Center};
\pause
\draw[-,dashed] (Center) circle (\AverageDistance);
\pause
\only<4->{
\DrawPotato[blue]{A}{B}{C}{D}
}
\end{tikzpicture}


\end{overlayarea}
\end{frame}
\end{document}
  • Thanks a lot, also for looking at this ancient question! ;-) – user121799 Oct 23 '18 at 16:13
  • @marmot It was my pleasure :) maybe we could automate it to accept more or less points. – Max Oct 23 '18 at 16:25
  • Yes, maybe. I am still wondering if there is some means (built in TikZ) to sort lists according to some entry. (I guess now I would not ask the question in this form any more but I am really grateful for your answer!) – user121799 Oct 23 '18 at 16:31
  • @marmot Yes I think you could ask that as a separate question. I don't know of any way, but I'm not really a Tikzpert. – Max Oct 23 '18 at 16:38
  • 1
    You are a TikZpert but I did not mean to bug you with an additional request. Please do not spend more time on this, I am very happy with your nice answer! – user121799 Oct 23 '18 at 16:42

Your Answer

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