Skip to content

Instantly share code, notes, and snippets.

@cormullion
Created January 9, 2024 09:08
Show Gist options
  • Save cormullion/4c964b9d7d0a1bf9ca4729d00ad32427 to your computer and use it in GitHub Desktop.
Save cormullion/4c964b9d7d0a1bf9ca4729d00ad32427 to your computer and use it in GitHub Desktop.
rectangular moon calendar
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()
@cormullion
Copy link
Author

2024-moon-phase-calendar-rect

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment