Created
January 9, 2024 09:08
-
-
Save cormullion/4c964b9d7d0a1bf9ca4729d00ad32427 to your computer and use it in GitHub Desktop.
rectangular moon calendar
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using Colors, Luxor, Dates, JSON, DataFrames, Downloads | |
theyear = 2024 | |
data_url = "https://svs.gsfc.nasa.gov/vis/a000000/a005100/a005187/mooninfo_2024.json" | |
if !isfile(string(@__DIR__, "/nasa-moon-$(theyear)-data.json")) | |
Downloads.download(data_url, "nasa-moon-2024-data.json") | |
end | |
function getdata() | |
json_string = read(string(@__DIR__, "/nasa-moon-$(theyear)-data.json"), String) | |
data = JSON.parse(json_string) | |
df = DataFrame(data) | |
df.isotime = map(dt -> DateTime(replace(dt, r" UT$" => ""), "d u y HH:MM"), df.time) | |
return df | |
end | |
function lefthemi(pt, r, col) # draw a left hemisphere | |
gsave() | |
sethue(col) | |
newpath() | |
arc(pt, r, pi / 2, -pi / 2, :fill) # positive clockwise from x axis in radians | |
grestore() | |
end | |
function righthemi(pt, r, col) | |
gsave() | |
sethue(col) | |
newpath() | |
arc(pt, r, -pi / 2, pi / 2, :fill) # positive clockwise from x axis in radians | |
grestore() | |
end | |
function elliptical(pt, r, col, horizscale) | |
gsave() | |
sethue(col) | |
scale(horizscale + 0.00000001, 1) # cairo doesn't like 0 scale :) | |
circle(pt, r, :fill) | |
grestore() | |
end | |
function moon(pt::Point, r, age, positionangle; | |
agerange = (0, 29.5)) | |
# use moon synodic period | |
age = rescale(age, agerange...) | |
# elliptical is scaled horizontally to render phases | |
@layer begin | |
translate(pt) | |
rotate(-deg2rad(positionangle)) | |
setopacity(1) | |
if 0 <= age < 0.25 | |
righthemi(O, r, foregroundwhite) | |
moonwidth = 1 - (age * 4) # goes from 1 down to 0 width (for half moon) | |
setopacity(0.5) | |
elliptical(O, r + 0.02, RGB(25 / 255, 25 / 255, 100 / 255), moonwidth + 0.02) | |
setopacity(1.0) | |
elliptical(O, r, backgroundmoon, moonwidth) | |
lefthemi(O, r, backgroundmoon) | |
elseif 0.25 <= age < 0.50 | |
lefthemi(O, r, backgroundmoon) | |
righthemi(O, r, foregroundwhite) | |
moonwidth = (age - 0.25) * 4 | |
elliptical(O, r, foregroundwhite, moonwidth) | |
elseif 0.50 <= age < 0.75 | |
lefthemi(O, r, foregroundwhite) | |
righthemi(O, r, backgroundmoon) | |
moonwidth = 1 - ((age - 0.5) * 4) | |
elliptical(O, r, foregroundwhite, moonwidth) | |
elseif 0.75 <= age <= 1.00 | |
lefthemi(O, r, foregroundwhite) | |
moonwidth = ((age - 0.75) * 4) | |
setopacity(0.5) | |
elliptical(O, r + 0.02, RGB(25 / 255, 25 / 255, 100 / 255), moonwidth + 0.02) | |
setopacity(1.0) | |
elliptical(O, r, backgroundmoon, moonwidth) | |
righthemi(O, r, backgroundmoon) | |
end | |
end | |
end | |
function rectangular_calendar(theyear) | |
currentyear = theyear | |
daterange = Date(currentyear, 1, 1):Dates.Day(1):Date(currentyear, 12, 31) | |
# calculate outer dimensions | |
# top margin, main table area, bottom margin | |
tws = boxwidth(BoundingBox() * 0.95) .* (0.12, 0.86, 0.02) | |
# months, days, right margin | |
ths = boxheight(BoundingBox() * 0.95) .* (0.18, 0.82, 0.05) | |
# rowheights, column widths | |
pagetable = Table([ths...], [tws...]) | |
df = getdata() | |
leftmargin = tws[1] | |
topmargin = ths[1] | |
cellwidth = pagetable.colwidths[2] / 32 | |
cellheight = pagetable.rowheights[2] / 12 | |
# font for days | |
fontface("JuliaMono-Bold") | |
fontsize(cellheight / 2.5) | |
# inner table; 12 rows, 31 columns | |
moontable = Table(12, 31, cellwidth, cellheight, pagetable[2, 2]) | |
cellsize = min(cellwidth, cellheight) / 2 * 0.8 | |
ageranges = extrema(df[:, :age]) | |
for everyday in daterange | |
themonth = Dates.month(everyday) | |
theday = Dates.day(everyday) | |
thedayofweekoffirstday = Dates.dayofweek(Date(theyear, themonth, 1)) | |
thetime = DateTime(everyday) + Dates.Hour(12) | |
age = first(df[df[:, :isotime] .== thetime, :age]) | |
positionangle = first(df[df[:, :isotime] .== thetime, :posangle]) | |
# moonfrac is 0 for new, 1 for full | |
# age is 15 for full, 29 for nearly new | |
# draw a moon | |
pt = moontable[themonth, theday] | |
moon(pt, cellsize, age, positionangle; agerange = ageranges) | |
@layer begin | |
if dayofweek(everyday) == 6 | |
sethue("orange") | |
fontface("JuliaMono-Bold") | |
fontsize(cellsize) | |
text(string(theday), pt + (0, 2cellsize), halign = :center) | |
elseif dayofweek(everyday) == 7 | |
sethue("orange") | |
fontface("JuliaMono-Bold") | |
fontsize(cellsize) | |
text(string(theday), pt + (0, 2cellsize), halign = :center) | |
setline(0.0) | |
# small triangle for Sunday | |
sethue("white") | |
ngon(pt + (-5.5, 2cellsize - 2), 1, 3, 0, :fill) | |
else | |
sethue("white") | |
fontface("JuliaMono-Regular") | |
fontsize(cellsize) | |
text(string(theday), pt + (0, 2cellsize), halign = :center) | |
end | |
end | |
#months | |
if day(everyday) == 1 | |
@layer begin | |
# font for months | |
fontface("BarlowCondensed-Medium") | |
fontsize(pagetable.rowheights[2] / 30) | |
sethue(foregroundwhite) | |
s = uppercase(Dates.monthname(themonth)) | |
te = textextents(s) | |
# font needs a bit of tracking | |
texttrack(s, Point(boxtopleft(BoundingBox()).x + leftmargin - te[3], pt.y + te[4] / 2), 100) | |
end | |
end | |
end | |
# year at top | |
fontface("SuperclarendonBl") | |
fontsize(pagetable.rowheights[1] * 0.8) | |
sethue(foregroundwhite) | |
text(string(theyear), boxtopcenter(BoundingBox()) + (0, pagetable.rowheights[1] / 1.7), halign = :center, valign = :middle) | |
end | |
#drawingwidth, drawingheight = (round(15cm), round(10cm)) | |
drawingheight, drawingwidth = paper_sizes["A5"] | |
backgroundblue = HSB(216, 0.65, 0.22) | |
backgroundmoon = HSB(216, 0.5, 0.1) | |
foregroundwhite = RGB(1, 1, 0.82) | |
Drawing(drawingwidth, drawingheight, "/tmp/$(theyear)-moon-phase-calendar-rect.svg") | |
origin() | |
background(backgroundblue) | |
rectangular_calendar(theyear) | |
finish() | |
preview() |
Author
cormullion
commented
Jan 9, 2024
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment