Parabolic Moebius map on sphere using tikz

How to Draw Parametrized Curves on a Sphere in TikZ

The technique shown here parametrizes azimuth and elevation in order to draw things in a spherical coordinate system. These are then converted to the cartesian XYZ coordinate system for plotting.

Using the technique shown below, the requested parabolic Moebius map can be drawn using the following command (the formula was provided by the OP):

\def\theX{0.5*(1-cos(deg(x)))} % 0 to 1 to 0
\def\theY{0.5*(sin(deg(x))} % 0 to 0.5 to 0 to -0.5 to 0
\addFGBGplot[domain=0:2*pi, samples=101, samples y=1, black]
(
    {sin(\i)*\theX},       % X coordinate
    {2*sin(0.5*\i)*\theY}, % Y coordinate
    {1-((1-cos(\i)))*\theX} % Z coordinate
);

And the result, when rendered from different angles:

Parabolic Moebius map in PGFPlots and TikZ


Using TikZ + PGFPlots

By combining TikZ and PGFPlots you can have a nice looking sphere and automatically hidden lines.

Image seen from multiple perspective with hidden lines automatically drawn transparent.

If you are drawing on the surface of a sphere, the sign of the z-depth directly shows whether that point is in front of or behind the sphere. The z-depth is obtained by multiplying the point in question with the view direction vector of the camera. Using the coordinate filtering mechanism of PGFPlots, you can exploit this to only draw parts of a path that are not hidden behind the sphere. This is what the sytles only background and only foreground do. Note that there is a slight overlap from depth -0.05 to depth 0.05 to avoid gaps between foreground and background parts.

Additionally, you can use TikZ to draw a nice looking sphere instead of the grid sphere shown at the bottom of this post. However, this requires aligning the PGFPlots coordinate system with the TikZ coordinate system, which is not trivial for 3D plots with variable view angles.

First you have to adjust TikZ's XYZ coordinate system to show the perspective you like. Since I haven't found anything similar to PGFPlots' view option in TikZ, I have simply implemented a viewport style that sets the x, y and z keys and accepts the same arguments as view. Using this style on an axis environment aligns the PGFPlots coordinate system with the TikZ one. But it is still necessary to assign the same parameters to the view style because otherwise PGFPlots hides parts of the plot when looking from some angles.

Then you can then use \addplot3 ({x-expr}, {y-expr}, {z-expr}); to draw your parametrized plot. In the example I defined helper macros \azimuth and \elevation to separate the definition of the curve from the coordinate transformation, such that the \draw command contain only the transformation.

Using the styles for z-depth filtering, you can then choose to draw only the visible or only the hidden parts of the plot. The macro \addFGBGplot automates this process for you by first drawing the hidden parts transparently and then drawing the visible parts using opaque lines.

\documentclass[margin=5pt, tikz]{standalone}
\usepackage{pgfplots}
\usepackage{xxcolor}
\pgfplotsset{compat=1.10}

% Declare nice sphere shading: http://tex.stackexchange.com/a/54239/12440
\pgfdeclareradialshading[tikz@ball]{ball}{\pgfqpoint{0bp}{0bp}}{%
 color(0bp)=(tikz@ball!0!white);
 color(7bp)=(tikz@ball!0!white);
 color(15bp)=(tikz@ball!70!black);
 color(20bp)=(black!70);
 color(30bp)=(black!70)}
\makeatother

% Style to set TikZ camera angle, like PGFPlots `view`
\tikzset{viewport/.style 2 args={
    x={({cos(-#1)*1cm},{sin(-#1)*sin(#2)*1cm})},
    y={({-sin(-#1)*1cm},{cos(-#1)*sin(#2)*1cm})},
    z={(0,{cos(#2)*1cm})}
}}

% Styles to plot only points that are before or behind the sphere.
\pgfplotsset{only foreground/.style={
    restrict expr to domain={rawx*\CameraX + rawy*\CameraY + rawz*\CameraZ}{-0.05:100},
}}
\pgfplotsset{only background/.style={
    restrict expr to domain={rawx*\CameraX + rawy*\CameraY + rawz*\CameraZ}{-100:0.05}
}}

% Automatically plot transparent lines in background and solid lines in foreground
\def\addFGBGplot[#1]#2;{
    \addplot3[#1,only background, opacity=0.25] #2;
    \addplot3[#1,only foreground] #2;
}

\newcommand{\ViewAzimuth}{-30}
\newcommand{\ViewElevation}{30}

\begin{document}
\begin{tikzpicture}
    % Compute camera unit vector for calculating depth
    \pgfmathsetmacro{\CameraX}{sin(\ViewAzimuth)*cos(\ViewElevation)}
    \pgfmathsetmacro{\CameraY}{-cos(\ViewAzimuth)*cos(\ViewElevation)}
    \pgfmathsetmacro{\CameraZ}{sin(\ViewElevation)}
    \path[use as bounding box] (-1,-1) rectangle (1,1); % Avoid jittering animation
    % Draw a nice looking sphere
    \begin{scope}
        \clip (0,0) circle (1);
        \begin{scope}[transform canvas={rotate=-20}]
            \shade [ball color=white] (0,0.5) ellipse (1.8 and 1.5);
        \end{scope}
    \end{scope}
    \begin{axis}[
        hide axis,
        view={\ViewAzimuth}{\ViewElevation},     % Set view angle
        every axis plot/.style={very thin},
        disabledatascaling,                      % Align PGFPlots coordinates with TikZ
        anchor=origin,                           % Align PGFPlots coordinates with TikZ
        viewport={\ViewAzimuth}{\ViewElevation}, % Align PGFPlots coordinates with TikZ
    ]
        % Plot equator and two longitude lines with occlusion
        \addFGBGplot[domain=0:2*pi, samples=100, samples y=1] ({cos(deg(x))}, {sin(deg(x))}, 0);
        \addFGBGplot[domain=0:2*pi, samples=100, samples y=1] (0, {sin(deg(x))}, {cos(deg(x))});
        \addFGBGplot[domain=0:2*pi, samples=100, samples y=1] ({sin(deg(x))}, 0, {cos(deg(x))});
        % Draw heart shape with occlusion
        \def\azimuth{deg(0.7*sin(x)+pi)}
        \def\elevation{deg(1.1*abs(sin(0.67*x))-0.4)}
        \addFGBGplot[domain=-180:180, samples=101, samples y=1, red]
        (
            {sin(\azimuth)*cos(\elevation)}, % X coordinate
            {cos(\azimuth)*cos(\elevation)}, % Y coordinate
            {sin(\elevation)}                % Z (vertical) coordinate
        );
    \end{axis}
\end{tikzpicture}
\end{document}

Using only TikZ

Using only TikZ, you can draw plots on the surface of a sphere, but there is no automatic way to hide parts which are behind the sphere.

Weeeeeeeeee!

If you don't want to use PGFPlots for whatever reason, you can also plot parametrized functions with the TikZ \draw plot command. However, the lack of a coordinate filtering mechanism means that the curve is always drawn regardless of whether it is on the visible or on the hidden side of the sphere, as you can see in the animated image above. For simple shapes on a sphere you can fix this by adjusting the domain option such that only the visible parts of the curve are actually drawn, like the equator in the example code. Of course, you have to do that every time you change the viewport.

\documentclass[tikz,margin=5pt]{standalone}
\usepackage{tikz}

% Declare nice sphere shading: http://tex.stackexchange.com/a/54239/12440
\pgfdeclareradialshading[tikz@ball]{ball}{\pgfqpoint{0bp}{0bp}}{%
 color(0bp)=(tikz@ball!0!white);
 color(7bp)=(tikz@ball!0!white);
 color(15bp)=(tikz@ball!70!black);
 color(20bp)=(black!70);
 color(30bp)=(black!70)}
\makeatother

% Style to set camera angle, like PGFPlots `view` style
\tikzset{viewport/.style 2 args={
    x={({cos(-#1)*1cm},{sin(-#1)*sin(#2)*1cm})},
    y={({-sin(-#1)*1cm},{cos(-#1)*sin(#2)*1cm})},
    z={(0,{cos(#2)*1cm})}
}}

% Convert from spherical to cartesian coordinates
\newcommand{\ToXYZ}[2]{
    {sin(#1)*cos(#2)}, % X coordinate
    {cos(#1)*cos(#2)}, % Y coordinate
    {sin(#2)}          % Z (vertical) coordinate
}

\begin{document}
\def\Rotation{-10}
\begin{tikzpicture}
    % Draw shaded circle that looks like a sphere
    \begin{scope}
        \clip (0,0) circle (1);
        \begin{scope}[transform canvas={rotate=-20}]
            \shade [ball color=white] (0,0.5) ellipse (1.8 and 1.5);
        \end{scope}
    \end{scope}
    % Draw things in actual 3D coordinates.
    \begin{scope}[viewport={\Rotation}{30}, very thin]
        % Draw equator (manually hidden behind sphere)
        \draw[domain=90-\Rotation:270-\Rotation, variable=\azimuth, smooth] plot (\ToXYZ{\azimuth}{0});
        \draw[domain=-90-\Rotation:90-\Rotation, variable=\azimuth, smooth, densely dotted] plot (\ToXYZ{\azimuth}{0});
        % Draw "poles"
        \draw[domain=0:360, variable=\azimuth, smooth] plot (\ToXYZ{\azimuth}{80});
        \draw[domain=0:360, variable=\azimuth, smooth, densely dotted] plot (\ToXYZ{\azimuth}{-80});
        % Draw two longitude lines for orientation
        \foreach \azimuth in {0,90} {
            \draw[domain=0:360, variable=\elevation, smooth] plot (\ToXYZ{\azimuth}{\elevation});
        }
        % Draw parametrized plot in spherical coordinates 
        \def\azimuth{deg(0.7*sin(\t)+pi)}
        \def\elevation{deg(1.1*abs(sin(0.67*\t))-0.4)}
        \draw[red, domain=-180:180, variable=\t, samples=101] plot (\ToXYZ{\azimuth}{\elevation});
    \end{scope}
\end{tikzpicture}
\end{document}

Using only PGFPlots

See the updated answer above. This is left here only for reference.

The pgfplots package allows you to draw parametrized 3d plots rather easily. It can also handle occlusion with z buffer=sort, but only inside a single \addplot command; later plots are simply drawn on top of earlier plots. For example, the averse side of the sphere is not drawn because there are other faces in front of it, but the equator that is added later is simply drawn on top of the sphere.

Sphere with parametrized lines

\documentclass[margin=5pt]{standalone}
\usepackage{pgfplots}
\pgfplotsset{compat=1.8}
\begin{document}
\begin{tikzpicture}
    \begin{axis}[view={60}{30}, width=15cm, axis equal image]
        % Draw sphere (example from the pgfplots manual)
        \addplot3[
            surf, z buffer=sort, colormap/cool, point meta=-z,
            samples=20, domain=-1:1, y domain=0:2*pi
        ]
        (
            {sqrt(1-x^2) * cos(deg(y))}, % X coordinate
            {sqrt(1-x^2) * sin(deg(y))}, % Y coordinate
            x                            % Z (vertical) coordinate
        );

        % Black twiddly line-thing that I just made up (parametrized)
        \def\azimuth{(sin(deg(2*x)))}
        \def\elevation{(0.5*cos(deg(x))+1)}
        \addplot3[domain=0:2*pi, samples=50, samples y=1]
        (
            {cos(deg(\azimuth))*cos(deg(\elevation))}, % X coordinate
            {sin(deg(\azimuth))*cos(deg(\elevation))}, % Y coordinate
            {sin(deg(\elevation))}                     % Z (vertical) coordinate
        );

        % Draw equator to show missing occlusion
        \addplot3[red, domain=0:2*pi, samples=20, samples y=1]
            ({cos(deg(x))}, {sin(deg(x))}, 0);
    \end{axis}
\end{tikzpicture}
\end{document}

I am still stunned by Fritz's great answer, and even more by what one can do with pgfplots. Here is a small addendum: it is possible to automatically discriminate between visible and hidden points with TikZ "only". One "only" has to modify the plot handler a bit. (This has severe side-effects: the paths get cut to tiny pieces and one can no longer be used for intersections and so on. Of course, one could just redraw the paths with a \path command, but this will certainly not win a prize for elegance.) Anyway, here is the code.

\documentclass[tikz,border=3.14mm]{standalone}
\usepackage{tikz-3dplot}
\makeatletter

% from https://tex.stackexchange.com/a/375604/121799
%along x axis
\define@key{x sphericalkeys}{radius}{\def\myradius{#1}}
\define@key{x sphericalkeys}{theta}{\def\mytheta{#1}}
\define@key{x sphericalkeys}{phi}{\def\myphi{#1}}
\tikzdeclarecoordinatesystem{x spherical}{% 
    \setkeys{x sphericalkeys}{#1}%
    \pgfpointxyz{\myradius*cos(\mytheta)}{\myradius*sin(\mytheta)*cos(\myphi)}{\myradius*sin(\mytheta)*sin(\myphi)}}

%along y axis
\define@key{y sphericalkeys}{radius}{\def\myradius{#1}}
\define@key{y sphericalkeys}{theta}{\def\mytheta{#1}}
\define@key{y sphericalkeys}{phi}{\def\myphi{#1}}
\tikzdeclarecoordinatesystem{y spherical}{% 
    \setkeys{y sphericalkeys}{#1}%
    \pgfpointxyz{\myradius*sin(\mytheta)*cos(\myphi)}{\myradius*cos(\mytheta)}{\myradius*sin(\mytheta)*sin(\myphi)}}


%along z axis
\define@key{z sphericalkeys}{radius}{\def\myradius{#1}}
\define@key{z sphericalkeys}{theta}{\def\mytheta{#1}}
\define@key{z sphericalkeys}{phi}{\def\myphi{#1}}
\tikzdeclarecoordinatesystem{z spherical}{% 
    \setkeys{z sphericalkeys}{#1}%
    \pgfmathsetmacro{\Xtest}{sin(\tdplotmaintheta)*cos(\tdplotmainphi-90)*sin(\mytheta)*cos(\myphi)
    +sin(\tdplotmaintheta)*sin(\tdplotmainphi-90)*sin(\mytheta)*sin(\myphi)
    +cos(\tdplotmaintheta)*cos(\mytheta)}
    % \Xtest is the projection of the coordinate on the normal vector of the visible plane
    \pgfmathsetmacro{\ntest}{ifthenelse(\Xtest<0,0,1)}
    \ifnum\ntest=0
    \xdef\MCheatOpa{0.3}
    \else
    \xdef\MCheatOpa{1}
    \fi
    %\typeout{\mytheta,\tdplotmaintheta;\myphi,\tdplotmainphi:\ntest}
    \pgfpointxyz{\myradius*sin(\mytheta)*cos(\myphi)}{\myradius*sin(\mytheta)*sin(\myphi)}{\myradius*cos(\mytheta)}}

%%%%%%%%%%%%%%%%%

\tikzoption{spherical smooth}[]{\let\tikz@plot@handler=\pgfplothandlersphericalcurveto}


\pgfdeclareplothandler{\pgfplothandlersphericalcurveto}{}{%
  point macro=\pgf@plot@curveto@handler@spherical@initial,
  jump macro=\pgf@plot@smooth@next@spherical@moveto,
  end macro=\pgf@plot@curveto@handler@spherical@finish
}

\def\pgf@plot@smooth@next@spherical@moveto{%
  \pgf@plot@curveto@handler@spherical@finish%
  \global\pgf@plot@startedfalse%
  \global\let\pgf@plotstreampoint\pgf@plot@curveto@handler@spherical@initial%
}

\def\pgf@plot@curveto@handler@spherical@initial#1{%
  \pgf@process{#1}%
  \ifx\tikz@textcolor\pgfutil@empty%
  \else
  \pgfsetstrokecolor{\tikz@textcolor}
  \fi
  \pgf@xa=\pgf@x%
  \pgf@ya=\pgf@y%
  \pgf@plot@first@action{\pgfqpoint{\pgf@xa}{\pgf@ya}}%
  \xdef\pgf@plot@curveto@first{\noexpand\pgfqpoint{\the\pgf@xa}{\the\pgf@ya}}%
  \global\let\pgf@plot@curveto@first@support=\pgf@plot@curveto@first%
  \global\let\pgf@plotstreampoint=\pgf@plot@curveto@handler@spherical@second%
}

\def\pgf@plot@curveto@handler@spherical@second#1{%
  \pgf@process{#1}%
  \xdef\pgf@plot@curveto@second{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}%
  \global\let\pgf@plotstreampoint=\pgf@plot@curveto@handler@spherical@third%
  \global\pgf@plot@startedtrue%
}

\def\pgf@plot@curveto@handler@spherical@third#1{%
  \pgf@process{#1}%
  \xdef\pgf@plot@curveto@current{\noexpand\pgfqpoint{\the\pgf@x}{\the\pgf@y}}%
  % compute difference vector:
  \pgf@xa=\pgf@x%
  \pgf@ya=\pgf@y%
  \pgf@process{\pgf@plot@curveto@first}
  \advance\pgf@xa by-\pgf@x%
  \advance\pgf@ya by-\pgf@y%
  % compute support directions:
  \pgf@xa=\pgf@plottension\pgf@xa%
  \pgf@ya=\pgf@plottension\pgf@ya%
  % first marshal:
  \pgf@process{\pgf@plot@curveto@second}%
  \pgf@xb=\pgf@x%
  \pgf@yb=\pgf@y%
  \pgf@xc=\pgf@x%
  \pgf@yc=\pgf@y%
  \advance\pgf@xb by-\pgf@xa%
  \advance\pgf@yb by-\pgf@ya%
  \advance\pgf@xc by\pgf@xa%
  \advance\pgf@yc by\pgf@ya%
  \@ifundefined{MCheatOpa}{}{%
  \pgf@plotstreamspecial{\pgfsetstrokeopacity{\MCheatOpa}}}
  \edef\pgf@marshal{\noexpand\pgfsetstrokeopacity{\noexpand\MCheatOpa}
  \noexpand\pgfpathcurveto{\noexpand\pgf@plot@curveto@first@support}%
    {\noexpand\pgfqpoint{\the\pgf@xb}{\the\pgf@yb}}{\noexpand\pgf@plot@curveto@second}
    \noexpand\pgfusepathqstroke
    \noexpand\pgfpathmoveto{\noexpand\pgf@plot@curveto@second}}%
  {\pgf@marshal}%
  %\pgfusepathqstroke%
  % Prepare next:
  \global\let\pgf@plot@curveto@first=\pgf@plot@curveto@second%
  \global\let\pgf@plot@curveto@second=\pgf@plot@curveto@current%
  \xdef\pgf@plot@curveto@first@support{\noexpand\pgfqpoint{\the\pgf@xc}{\the\pgf@yc}}%
}

\def\pgf@plot@curveto@handler@spherical@finish{%
  \ifpgf@plot@started%
    \pgfpathcurveto{\pgf@plot@curveto@first@support}{\pgf@plot@curveto@second}{\pgf@plot@curveto@second}%
  \fi%
}


\makeatother
\begin{document}
\pgfmathsetmacro{\RadiusSphere}{3}

\begin{tikzpicture}
\shade[ball color = gray!40, opacity = 0.5] (0,0,0) circle (\RadiusSphere);
\shade[ball color = gray!40, opacity = 0.5] (8,0,0) circle (\RadiusSphere);
\shade[ball color = gray!40, opacity = 0.5] (0,-8,0) circle (\RadiusSphere);
\shade[ball color = gray!40, opacity = 0.5] (8,-8,0) circle (\RadiusSphere);

\tdplotsetmaincoords{72}{100}

\begin{scope}[tdplot_main_coords]
% \draw[-latex] (0,0,0) -- (\RadiusSphere,0,0) node[below]{$x$};
% \draw[-latex] (0,0,0) -- (0,\RadiusSphere,0) node[left]{$y$};
% \draw[-latex] (0,0,0) -- (0,0,\RadiusSphere) node[left]{$z$};

\begin{scope}
\foreach \X in {0,20,...,180}
\draw[blue] plot[spherical smooth,variable=\x,domain=-180:180,samples=60] 
(z spherical cs: radius = \RadiusSphere, phi = \X, theta= \x);
\foreach \X in {0,20,...,180}
\draw[gray] plot[spherical smooth,variable=\x,domain=-180:180,samples=60] 
(z spherical cs: radius = \RadiusSphere, phi = \x, theta= \X);
\end{scope}

\begin{scope}[xshift=8cm]
\foreach \X in {0,20,...,180}
\draw[gray] plot[spherical smooth,variable=\x,domain=-180:180,samples=60] 
(z spherical cs: radius = \RadiusSphere, phi = \X, theta= \x);
\foreach \X in {0,20,...,180}
\draw[blue] plot[spherical smooth,variable=\x,domain=-180:180,samples=60] 
(z spherical cs: radius = \RadiusSphere, phi = \x, theta= \X);
\end{scope}

\begin{scope}[yshift=-8cm]
\draw[blue] plot[spherical smooth,variable=\x,domain=0:180,samples=360] 
(z spherical cs: radius = \RadiusSphere, phi = 18*\x, theta= \x);
\end{scope}

\begin{scope}[xshift=8cm,yshift=-8cm]
\foreach \X in {1,...,10}
{\draw[blue] plot[spherical smooth,variable=\x,domain=0:180,samples=360] 
(z spherical cs: radius = \RadiusSphere, phi = {10*\X*(1-cos(\x))}, 
theta= {10*\X*sin(\x)});
\draw[blue] plot[spherical smooth,variable=\x,domain=0:180,samples=360] 
(z spherical cs: radius = \RadiusSphere, phi = {-10*\X*(1-cos(\x))}, 
theta= {10*\X*sin(\x)});}
\end{scope}

\end{scope}

\end{tikzpicture}
\end{document}

enter image description here

Ah, important. I was using essentially the same trick here and here. If anyone reading this feels that posting a very similar answer over and over is not a good idea, I will be happy to remove this post. (Perhaps also important, the spurious almost horizontal and vertical lines are not in the PDF, they come only after the conversion to PNG, and I dunno why. They seem to be related to the shade sphere directive.)


If the idea is to simply visualize the parabolic Moebius map, you can exploit how the \tdplotsphericalsurfaceplot command in tikz-3dplot works. Here is an example:

\documentclass[tikz,border=10pt]{standalone}
\usepackage{tikz,tikz-3dplot}

\begin{document}

\tdplotsetmaincoords{135}{350}

\begin{tikzpicture}[tdplot_main_coords,fill opacity=.7,]

\tdplotsetpolarplotrange{0}{180}{0}{180}
\tdplotsphericalsurfaceplot{36}{36}{6*sin(\tdplotphi)*sin(\tdplottheta)}{black!70!red}{red}{}{}{}

\end{tikzpicture}

\end{document}

output

Of course, this is a rather crude solution. However, if you don't need anything fancier, this is an example with minimal code. The details about the \tdplotsphericalsurfaceplot command can be found in the tikz-3dplot manual.

Tags:

3D

Tikz Pgf