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:
Using TikZ + PGFPlots
By combining TikZ and PGFPlots you can have a nice looking sphere and automatically hidden lines.
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.
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.
\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}
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}
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.