Dance of Venus (and variations) in TikZ/PGF
Fascinating and lots of fun. This essentially one-liner:
\documentclass{article}
\usepackage[halfletter,margin=0.6in]{geometry}
\usepackage{tikz}
\begin{document}
\begin{tikzpicture}
\foreach \x in {0,5,...,2880}{
\draw[help lines] (\x:5) -- ({(\x/8)*13}:4);%% <<<--- avoids arithmetic overflow
}
\end{tikzpicture}
\end{document}
yields this beautiful graphic:
Update
Well, it had to happen. Here is a macro that will do the drawing, along with the ability to play with the parameters:
\documentclass[tikz,border=20pt]{standalone}
\usepackage{keyval}
\newlength{\outerlength}
\setlength{\outerlength}{4in}
\newlength{\innerlength}
\setlength{\innerlength}{3in}
\def\outerrate{8}
\def\innerrate{13}
\def\maxtimes{2880}
\def\increment{2}
\makeatletter
\define@key{venus}{outerlength}{\setlength{\outerlength}{#1}}
\define@key{venus}{innerlength}{\setlength{\innerlength}{#1}}
\define@key{venus}{outerrate}{\def\outerrate{#1}}
\define@key{venus}{innerrate}{\def\innerrate{#1}}
\define@key{venus}{maxtimes}{\def\maxtimes{#1}}
\define@key{venus}{increment}{\def\increment{#1}}
\newcommand{\makevenus}[1][]{%
\setkeys{venus}{#1}
\begin{tikzpicture}
\foreach \x in {0,\increment,...,\maxtimes}{%
\draw[help lines] (\x:\outerlength) --
({(\x/\outerrate)*\innerrate}:\innerlength);%% <<<--- avoids arithmetic overflow
}%
\end{tikzpicture}%
}
\begin{document}
\makevenus[innerrate=7,innerlength=2.25in,increment=3]
\end{document}
which produces:
while \makevenus[outerrate=5,innerrate=13,innerlength=3.75in,increment=1]
produces (I love my new toy!):
Update 2
I was curious about what would happen if the centerpoint of each line drawn was shown. Interestingly, as the macro \newmakevenus
shows, it traces the shapes formed by the intersections of the lines.
\RequirePackage[rgb]{xcolor}
\documentclass[tikz,border=20pt]{standalone}
\usepackage{xparse}
\usepackage{keyval}
\usepackage{fp}
\newlength{\outerlength}
\setlength{\outerlength}{4in}
\newlength{\innerlength}
\setlength{\innerlength}{2.8933in}
\def\outerrate{8}
\def\innerrate{13}
\def\maxtimes{2880}
\def\increment{2}
\colorlet{mycolor}{black}
\makeatletter
\define@key{venus}{outerlength}{\setlength{\outerlength}{#1}}
\define@key{venus}{innerlength}{\setlength{\innerlength}{#1}}
\define@key{venus}{outerrate}{\def\outerrate{#1}}
\define@key{venus}{innerrate}{\def\innerrate{#1}}
\define@key{venus}{maxtimes}{\def\maxtimes{#1}}
\define@key{venus}{increment}{\def\increment{#1}}
\define@key{venus}{venuscolor}{\colorlet{mycolor}{#1}}
\makeatother
\NewDocumentCommand{\makevenus}{sO{}}{%
\setkeys{venus}{#2}
\begin{tikzpicture}
\foreach \x in {0,\increment,...,\maxtimes}{%
\IfBooleanT{#1}{%
\pgfmathsetmacro{\huenum}{\x/\maxtimes}
\definecolor{mycolor}{hsb}{\huenum,0.5,1}
}%
\draw[help lines,mycolor] (\x:\outerlength) --
({(\x/\outerrate)*\innerrate}:\innerlength);%% <<<--- avoids arithmetic overflow
}%
\end{tikzpicture}%
}
\NewDocumentCommand{\newmakevenus}{sO{}}{%
\setkeys{venus}{#2}
\begin{tikzpicture}
\foreach \x in {0,\increment,...,\maxtimes}{%
\IfBooleanT{#1}{%
\pgfmathsetmacro{\huenum}{\x/\maxtimes}
\definecolor{mycolor}{hsb}{\huenum,0.5,1}
}%
\draw(\x:\outerlength) --node[circle,fill=orange,pos=.5]{}
({(\x/\outerrate)*\innerrate}:\innerlength);%% <<<--- avoids arithmetic overflow
}%
\end{tikzpicture}%
}
\begin{document}
%%\makevenus*[outerrate=8,innerrate=13,increment=2,maxtimes=2880,innerlength=2.9in]
\newmakevenus[outerrate=8,innerrate=13,increment=3,innerlength=2.9in,maxtimes=2880]
\end{document}
An optional *
will colorize the graphic so that \makevenus*[outerrate=8,innerrate=13,increment=2,maxtimes=2880,innerlength=2.9in]
will produce:
2880 is simply 8*360.
A festive holiday update
Colorizing the graphic is simple: using the hsb
colorspace and changing the hue of each line as it is drawn (requires \RequirePackage[rgb]{xcolor}
before \documentclass
command). While interesting, I wondered what would happen if the number of color cycles (red-yellow->green->blue->purple->red) were to cycle as many times as the lobes of the pattern generated -- turns out this is simply abs(\innerrate-\outerrate)
-- this works best if either/both \innerrate
or \outerrate
are prime numbers. I was stunned by just how effective this is:
\RequirePackage[rgb]{xcolor}
\documentclass[tikz,border=20pt]{standalone}
\usepackage{xparse}
\usepackage{keyval}
\newlength{\outerlength}
\setlength{\outerlength}{4in}
\newlength{\innerlength}
\setlength{\innerlength}{2.8933in}
\def\outerrate{8}
\def\innerrate{13}
\def\maxtimes{2880}
\def\increment{2}
\def\changephase{0}
\colorlet{mycolor}{black}
\makeatletter
\define@key{venus}{outerlength}{\setlength{\outerlength}{#1}}
\define@key{venus}{innerlength}{\setlength{\innerlength}{#1}}
\define@key{venus}{outerrate}{\def\outerrate{#1}}
\define@key{venus}{innerrate}{\def\innerrate{#1}}
\define@key{venus}{increment}{\def\increment{#1}}
\define@key{venus}{changephase}{\def\changephase{#1}}
\define@key{venus}{venuscolor}{\colorlet{mycolor}{#1}}
\makeatother
\NewDocumentCommand{\makevenus}{sO{}}{%
\setkeys{venus}{#2}
\begin{tikzpicture}
\pgfmathsetmacro{\nmaxtimes}{360*\outerrate}
\foreach \x in {0,\increment,...,\nmaxtimes}{%
\IfBooleanT{#1}{%
\pgfmathsetmacro{\huenum}{%
abs(sin((360/\nmaxtimes)*\x*abs(\innerrate-\outerrate)+\changephase))
}%
\definecolor{mycolor}{hsb}{\huenum,1,1}
}%
\draw[help lines,mycolor] (\x:\outerlength) --
({(\x/\outerrate)*\innerrate}:\innerlength);%% <<<--- avoids arithmetic overflow
}%
\end{tikzpicture}%
}
\begin{document}
\makevenus*[outerrate=8,innerrate=17,increment=2,innerlength=3.5in,changephase=60]
\end{document}
It is also possible to change the phase of the color cycling using changephase
so that
\makevenus*[outerrate=8,innerrate=13,increment=2,innerlength=3.5in,changephase=60]
produces:
Hope you have fun with this -- I know I did.
A Festive New Year's Fireworks Update
Most importantly, a terrific vote of thanks to @Thruston on two counts: first for the elegant one-liner that does the drawing and, second, for the wonderful MP code that translates HSL to RGB.
The following code outputs three styles of graphics: 1: a monochrome version; 2: Thruston's original version of coloring; 3: phased coloring that repeats the spectrum (inner rate - outer rate) times. Note that I am not a MP expert by any manner of means, so that, no doubt, all sorts of improvements could be made to this code.
\documentclass[border=10pt]{standalone}
\usepackage{luamplib}
\usepackage{xparse}
\usepackage{graphicx}
\mplibnumbersystem{double}
%% TO DO: use keyval.sty
%% [#1 step delta]; #2 inner r; #3 outer r;
%% #4 [cycle offset]
%% #5 inner rate; #6 outer rate;
%% #7[pen scale]; #8*=color (& #9* phased color)
%% #3 (outer r) is the radius of the finished graphic, assuming that it is
%% greater than #2 (inner r).
\NewDocumentCommand{\makevenusL}{O{1} m m O{0} m m O{.125} s s}{%
\begin{mplibcode}
vardef hsv_color_cyc(expr h,s,v) = % HSB->rgb following wikipedia article on "HSL and HSV"
save chroma, hh, x, m;
chroma = v*s;
%% The following cycles through color spectrum (\innerrate-\outerrate) times
%% a constant can be added to shift the cycle.
hh = (((#6-#5)*h + #4) mod 360)/60;
x = chroma * (1-abs(hh mod 2 - 1));
m = v - chroma;
if hh < 1: (chroma,x,0)+(m,m,m)
elseif hh < 2: (x,chroma,0)+(m,m,m)
elseif hh < 3: (0,chroma,x)+(m,m,m)
elseif hh < 4: (0,x,chroma)+(m,m,m)
elseif hh < 5: (x,0,chroma)+(m,m,m)
else: (chroma,0,x)+(m,m,m)
fi
enddef;
vardef hsv_color(expr h,s,v) = % HSB->rgb following wikipedia article on "HSL and HSV"
save chroma, hh, x, m;
chroma = v*s;
%% The following cycles through color range once
hh = h/60;
x = chroma * (1-abs(hh mod 2 - 1));
m = v - chroma;
if hh < 1: (chroma,x,0)+(m,m,m)
elseif hh < 2: (x,chroma,0)+(m,m,m)
elseif hh < 3: (0,chroma,x)+(m,m,m)
elseif hh < 4: (0,x,chroma)+(m,m,m)
elseif hh < 5: (x,0,chroma)+(m,m,m)
else: (chroma,0,x)+(m,m,m)
fi
enddef;
\IfBooleanTF{#8}
{%
\IfBooleanTF{#9}
{% cycle through spectrum multiple times
beginfig(1);
for t=0 step #1 until 360:
draw #2 dir #5t -- #3 dir #6t
withpen pencircle scaled #7 withcolor hsv_color_cyc(t, 1, 1);
endfor;
draw (288,0) .. (0,288) .. (-288,0) .. (0,-288) .. cycle;
endfig;
}%
{% cycle through spectrum once
beginfig(1);
for t=0 step #1 until 360:
draw #2 dir #5t -- #3 dir #6t
withpen pencircle scaled #7 withcolor hsv_color(t, 1, 1);
endfor;
draw (288,0) .. (0,288) .. (-288,0) .. (0,-288) .. cycle;
endfig;
}%
}%
{% one color
beginfig(1);
for t=0 step #1 until 360:
draw #2 dir #5t -- #3 dir #6t
%% << change (r,g,b) spec to suit
withpen pencircle scaled #7 withcolor (0.2,0.5,0.5); %% (0,0,0) for black
endfor;
draw (288,0) .. (0,288) .. (-288,0) .. (0,-288) .. cycle;
endfig;
}%
\end{mplibcode}
}
\begin{document}
\makevenusL[.2]{260}{288}{8}{13}[0.125]
\makevenusL[.2]{260}{288}{8}{13}[0.125]*
\makevenusL[.2]{260}{288}[225]{8}{13}[0.125]**
\end{document}
%%% Local Variables:
%%% coding: utf-8
%%% mode: latex
%%% TeX-engine: luatex
%%% End:
Here is a Metapost "one-liner" that draws the geocentric view of the orbit. Compile with lualatex
:
\documentclass[border=5mm]{standalone}
\usepackage{luamplib}
\begin{document}
\begin{mplibcode}
beginfig(1);
draw for t=0 upto 359: 108 dir 13t - 147 dir 8t .. endfor cycle;
endfig;
\end{mplibcode}
\end{document}
So what is going on here? First note that the Earth is 147 million km from the sun, and Venus is 108 million km. So I have used these distances as my radius (scaled down so that 1 pt = 1 million km). And then that Venus does 13 orbits to Earth's 8 orbits.
Now note, that in MP you can write polar coordinates as r * dir theta
. So for a given angle t
, Venus will be at 108 * dir 13t
when Earth is at 147 * dir 8t
. (And because MP allows implied multiplication between a known constant and a function we can actually write 108 dir 13t
and 147 dir 8t
directly.)
To get the geocentric view we want to know where Venus is from Earth, so the simplest way to get a point representing that is to subtract the pair representing the position of Earth from the pair representing the position of Venus. So at a given angle t
, one way to represent the position of Venus from Earth is
108 dir 13t - 147 dir 8t
The rest of the one liner just wraps this up into an inline for loop.
for t=0 upto 359: 108 dir 13t - 147 dir 8t .. endfor cycle
using ..
to connect each point to the previous, and cycle
to close the path at the end. This could be saved as a path
variable. Or as I have shown, you can pass it straight to draw
. The path comes back on itself at t=360
,
Heliocentric
And another that does the corresponding heliocentric view (where each line connects Earth to Venus...)
\documentclass[border=5mm]{standalone}
\usepackage{luamplib}
\begin{document}
\mplibnumbersystem{double}
\begin{mplibcode}
beginfig(1);
for t=0 step 1/4 until 359: draw 108 dir 13t -- 147 dir 8t withpen pencircle scaled 1/8; endfor;
endfig;
\end{mplibcode}
\end{document}
Note the need for the enhanced number system here... again compile with lualatex
. This one is similar to the first, except that I have moved the draw
statement inside the loop, and instead of subtracting the Earth point from the Venus point, I am drawing a line segment between them, as in the OP animation at the top.
Some HSV colours help a bit here. In particular the five cardoids appear more clearly like this:
\documentclass[border=5mm]{standalone}
\usepackage{luamplib}
\begin{document}
\mplibnumbersystem{double}
\begin{mplibcode}
vardef hsv_color(expr h,s,v) =
% following wikipedia article on "HSL and HSV"
save chroma, hh, x, m;
chroma = v*s;
hh = h/60;
x = chroma * (1-abs(hh mod 2 - 1));
m = v - chroma;
if hh < 1: (chroma,x,0)+(m,m,m)
elseif hh < 2: (x,chroma,0)+(m,m,m)
elseif hh < 3: (0,chroma,x)+(m,m,m)
elseif hh < 4: (0,x,chroma)+(m,m,m)
elseif hh < 5: (x,0,chroma)+(m,m,m)
else: (chroma,0,x)+(m,m,m)
fi
enddef;
beginfig(1);
for t=0 step 1/4 until 360-1/4:
draw 108 dir 13t -- 147 dir 8t
withpen pencircle scaled 1/8 withcolor hsv_color(t, .4, .8); endfor;
endfig;
\end{mplibcode}
\end{document}
Almost good enough for a bank note now....