「Date に休日情報を追加した派生クラスを作ってカレンダーを表示する」に「月の満ち欠けの簡易表示」と月相を求める処理を追加して、背景に目安となる月相を表示してみる。
月相の計算 (JavaScript)
/*
* 月相の計算
*
* 計算方法は下記のものを使用しています
* 書籍:天体の位置計算(増補版) [長沢 工 著]
* 【7-2表】月の位置の略算式(海上保安庁水路部による)
* 【7-3表】太陽の位置の略算式(海上保安庁水路部による)
*/
class MoonPhaseRC {
static Tbase = Date.UTC(1975, 1-1, 0);
/*
* コンストラクタ
* 引数は日時(Date コンストラクタに渡される)
*/
constructor() {
const S = MoonPhaseRC;
const date = new Date(...arguments);
const t = (date - S.Tbase) / (365.25 * 86400000);
const T = t + (0.0317 * t + 1.43) / 1000000;
const sun = S.sunPosition(T);
const moon = S.moonPosition(T);
const phase = (moon.longitude - sun.longitude) % 360;
this.T = T;
this.sun = sun;
this.moon = moon;
this.phase = phase + ((phase < 0) ? 360 : 0);
}
/*
* 月の視黄経・視黄緯の算出
*/
static moonPosition(T) {
const degToRad = ((deg) => (deg % 360) * Math.PI / 180);
const accumeSin = ((a, v) => a + v[0] * Math.sin(degToRad(v[1] + v[2] * T)));
const accumeCos = ((a, v) => a + v[0] * Math.cos(degToRad(v[1] + v[2] * T)));
const A = [
[0.0040, 93.80, -1.330],
[0.0020, 248.60, -19.340],
[0.0006, 66.00, 0.200],
[0.0006, 249.00, -19.300],
].reduce(accumeSin, 0);
const B = [
[0.0267, 68.64, -19.341],
[0.0043, 342.00, -19.360],
[0.0040, 93.80, -1.330],
[0.0020, 248.60, -19.340],
[0.0005, 358.00, -19.400],
].reduce(accumeSin, 0);
const longitude = [
[1.2740, 107.248, -4133.3536],
[0.6583, 51.668, 8905.3422],
[0.2136, 317.831, 9543.9773],
[0.1856, 176.531, 359.9905],
[0.1143, 292.463, 9664.0404],
[0.0588, 86.160, 638.6350],
[0.0572, 103.780, -3773.3630],
[0.0533, 30.580, 13677.3310],
[0.0459, 124.860, -8545.3520],
[0.0410, 342.380, 4411.9980],
[0.0348, 25.830, 4452.6710],
[0.0305, 155.450, 5131.9790],
[0.0153, 240.790, 758.6980],
[0.0125, 271.380, 14436.0290],
[0.0110, 226.450, -4892.0520],
[0.0107, 55.580,-13038.6960],
[0.0100, 296.750, 14315.9660],
[0.0085, 34.500, -8266.7100],
[0.0079, 290.700, -4493.3400],
[0.0068, 228.200, 9265.3300],
[0.0052, 133.100, 319.3200],
[0.0050, 202.400, 4812.6600],
[0.0048, 68.600, -19.3400],
[0.0040, 34.100, 13317.3400],
[0.0040, 9.500, 18449.3200],
[0.0040, 93.800, -1.3300],
[0.0039, 103.300, 17810.6800],
[0.0037, 65.100, 5410.6200],
[0.0027, 321.300, 9183.9900],
[0.0026, 174.800,-13797.3900],
[0.0024, 82.700, 998.6300],
[0.0024, 4.700, 9224.6600],
[0.0022, 121.400, -8185.3600],
[0.0021, 134.400, 9903.9700],
[0.0021, 173.100, 719.9800],
[0.0021, 100.300, -3413.3700],
[0.0020, 248.600, -19.3400],
[0.0018, 98.100, 4013.2900],
[0.0016, 344.100, 18569.3800],
[0.0012, 52.100,-12678.7100],
[0.0011, 250.300, 19208.0200],
[0.0009, 81.000, -8586.0000],
[0.0008, 207.000, 14037.3000],
[0.0008, 31.000, -7906.7000],
[0.0007, 346.000, 4052.0000],
[0.0007, 294.000, -4853.3000],
[0.0007, 90.000, 278.6000],
[0.0006, 237.000, 1118.7000],
[0.0005, 82.000, 22582.7000],
[0.0005, 276.000, 19088.0000],
[0.0005, 73.000,-17450.7000],
[0.0005, 112.000, 5091.3000],
[0.0004, 116.000, -398.7000],
[0.0004, 25.000, -120.1000],
[0.0004, 181.000, 9584.7000],
[0.0004, 18.000, 720.0000],
[0.0003, 60.000, -3814.0000],
[0.0003, 13.000, -3494.7000],
[0.0003, 13.000, 18089.3000],
[0.0003, 152.000, 5492.0000],
[0.0003, 317.000, -40.7000],
[0.0003, 348.000, 23221.3000],
].reduce(
accumeSin,
+ 124.8754 + 4812.67881 * T
+ 6.2887 * Math.sin(degToRad(338.915 + 4771.9886 * T + A))
);
const latitude = [
[0.2806, 215.147, 9604.0088],
[0.2777, 77.316, 60.0316],
[0.1732, 4.563, -4073.3220],
[0.0554, 308.980, 8965.3740],
[0.0463, 343.480, 698.6670],
[0.0326, 287.900, 13737.3620],
[0.0172, 194.060, 14375.9970],
[0.0093, 25.600, -8845.3100],
[0.0088, 98.400, -4711.9600],
[0.0082, 1.100, -3713.3300],
[0.0043, 322.400, 5470.6600],
[0.0042, 266.800, 18509.3500],
[0.0034, 188.000, -4433.3100],
[0.0025, 312.500, 8605.3800],
[0.0022, 291.400, 13377.3700],
[0.0021, 340.000, 1058.6600],
[0.0019, 218.600, 9244.0200],
[0.0018, 291.800, -8206.6800],
[0.0018, 52.800, 5192.0100],
[0.0017, 168.700, 14496.0600],
[0.0016, 73.800, 420.0200],
[0.0015, 262.100, 9284.6900],
[0.0015, 31.700, 9964.0000],
[0.0014, 260.800, -299.9600],
[0.0013, 239.700, 4472.0300],
[0.0013, 30.400, 379.3500],
[0.0012, 304.900, 4812.6800],
[0.0012, 12.400, -4851.3600],
[0.0011, 173.000, 19147.9900],
[0.0010, 312.900,-12978.6600],
[0.0008, 1.000, 17870.7000],
[0.0008, 190.000, 9724.1000],
[0.0007, 22.000, 13098.7000],
[0.0006, 117.000, 5590.7000],
[0.0006, 47.000,-13617.3000],
[0.0005, 22.000, -8485.3000],
[0.0005, 150.000, 4193.4000],
[0.0004, 119.000, -9483.9000],
[0.0004, 246.000, 23281.3000],
[0.0004, 301.000, 10242.6000],
[0.0004, 126.000, 9325.4000],
[0.0004, 104.000, 14097.4000],
[0.0003, 340.000, 22642.7000],
[0.0003, 270.000, 18149.4000],
[0.0003, 358.000, -3353.3000],
[0.0003, 148.000, 19268.0000],
].reduce(
accumeSin,
+ 5.1282 * Math.sin(degToRad(236.231 + 4832.0202 * T + B))
);
const sine = Math.sin(degToRad([
[0.0518, 338.920, 4771.9890],
[0.0095, 287.200, -4133.3500],
[0.0078, 51.700, 8905.3400],
[0.0028, 317.800, 9543.9800],
[0.0009, 31.000, 13677.3000],
[0.0005, 305.000, -8545.4000],
[0.0004, 284.000, -3773.4000],
[0.0003, 342.000, 4412.0000],
].reduce(accumeCos, 0.9507)));
return {
longitude: longitude % 360,
latitude: latitude,
distance: 6378.140 / sine, // km
}
}
/*
* 太陽の視黄経・視黄緯の算出
*/
static sunPosition(T) {
const degToRad = ((deg) => (deg % 360) * Math.PI / 180);
const accumeSin = ((a, v) => a + v[0] * Math.sin(degToRad(v[1] + v[2] * T)));
const accumeCos = ((a, v) => a + v[0] * Math.cos(degToRad(v[1] + v[2] * T)));
const longitude = [
[+0.0200, 356.531, 719.981],
[-0.0048, 248.640, -19.341],
[+0.0020, 285.000, 329.640],
[+0.0018, 334.200, -4452.670],
[+0.0018, 293.700, -0.200],
[+0.0015, 242.400, 450.370],
[+0.0013, 211.100, 225.180],
[+0.0008, 208.000, 659.290],
[+0.0007, 53.500, 90.380],
[+0.0007, 12.100, -30.350],
[+0.0006, 239.100, 337.180],
[+0.0005, 10.100, -1.500],
[+0.0005, 99.100, -22.810],
[+0.0004, 264.800, 315.560],
[+0.0004, 233.800, 299.300],
[-0.0004, 198.100, 720.020],
[+0.0003, 349.600, 1079.970],
[+0.0003, 241.200, -44.430],
].reduce(accumeSin,
+ (279.0358 + 360.00769 * T)
+ (1.9159 - 0.00005 * T)
* Math.sin(degToRad(356.531 + 359.991 * T))
);
const q = [
[-0.000091, 353.1, 719.98],
[+0.000013, 205.8, 4452.67],
[+0.000007, 62.0, 450.40],
[+0.000007, 105.0, 329.60],
].reduce(accumeCos,
+ (-0.007261 + 0.0000002 * T)
* Math.cos(degToRad(356.53 + 359.991 * T))
+ 0.000030
);
const au = Math.pow(10, q);
return {
longitude: longitude % 360,
latitude: 0,
distance: au * 149597870.700, // km
}
}
}
See the Pen カレンダー (月/15週/年 + 月相) by Ikiuo (@ikiuo) on CodePen.
Google Chrome 拡張機能は moonphase ブランチとした。
HTML ファイル版
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="utf-8">
<title>カレンダー</title>
<style>
body {
background-color: #000000;
color: #ffffff;
}
button {
border: solid 1px #d3d3d3;
background-color: #000000;
}
button:hover {
background-color: #0000bf;
}
.selector {
text-align: center;
padding: 4px;
}
.monthly {
margin-left: auto;
margin-right: auto;
width: 200px;
}
.yearly {
margin-left: auto;
margin-right: auto;
width: 600px;
}
table.jhcalyear {
border: solid 1px #d3d3d3;
}
td.jhcalyhbox {
cursor: default;
text-align: center;
font-size: x-large;
}
span.jhcalyallow:hover {
color: #ff0000;
}
span.jhcalyheader:hover {
color: #ff0000;
}
td.jhcalymonth {
padding: 2px;
}
table.jhcalmonth {
border: solid 1px #d3d3d3;
border-right: 0px;
border-bottom: 0px;
border-collaspe: collaspe;
border-spacing: 0;
}
table.jhcalmonth td {
border: solid 1px #d3d3d3;
border-left: 0px;
border-top: 0px;
}
td.jhcalmheader {
cursor: default;
text-align: center;
padding: 4px;
}
td.jhcalmheader:hover {
color: red;
}
td.jhcalmarrow {
cursor: default;
text-align: center;
}
td.jhcalmarrow:hover {
color: #ff0000;
}
td.jhcalwodbox {
cursor: default;
background-color: #4f4f4f;
text-align: center;
}
div.jhcalweekofday {
font-size: small;
}
div.jhcalwsunday {
color: #ff0000;
}
div.jhcalwmonday {
/**/
}
div.jhcalwtuesday {
/**/
}
div.jhcalwwednesday {
/**/
}
div.jhcalwthursday {
/**/
}
div.jhcalwfriday {
/**/
}
div.jhcalwsaturday {
color: #009fff;
}
td.jhcaldaybox {
position: relative;
padding: 0px;
}
div.jhcalday {
cursor: default;
border: solid 1px rgba(0, 0, 0, 0);
text-align: right;
text-shadow: 1px 1px 5px #000000;
padding: 4px;
}
div.jhcaltoday {
border: solid 1px #00ffff;
background-color: #0000bf;
}
div.jhcalholiday {
color: #ff00bf;
font-weight: bold;
}
div.jhcalinfo {
color: #ff0000;
}
div.jhcalsunday {
color: #ff0000;
}
div.jhcalmonday {
/**/
}
div.jhcaltuesday {
/**/
}
div.jhcalwednesday {
/**/
}
div.jhcalthursday {
/**/
}
div.jhcalfriday {
/**/
}
div.jhcalsaturday {
color: #00bfff;
}
div.jhcalother {
color: #bfbfbf;
}
td.jhcalwhbox {
cursor: default;
text-align: center;
}
td.jhcalwhbox:hover {
color: #ff0000;
}
div.jhcalmeven {
background-color: #00005f;
}
div.jhcalmodd {
/**/
}
</style>
</head>
<body>
<div class="selector">
<button id="tagButton0" onclick="onSelect(0)">月間</button>
<button id="tagButton1" onclick="onSelect(1)">15週</button>
<button id="tagButton2" onclick="onSelect(2)">年間</button>
</div>
<div id="tagMonthly" class="monthly"></div>
<div id="tagWeekly" class="monthly"></div>
<div id="tagYearly" class="yearly"></div>
<script>
/*
* 月相の計算
*
* 計算方法は下記のものを使用しています
* 書籍:天体の位置計算(増補版) [長沢 工 著]
* 【7-2表】月の位置の略算式(海上保安庁水路部による)
* 【7-3表】太陽の位置の略算式(海上保安庁水路部による)
*/
class MoonPhaseRC {
static Tbase = Date.UTC(1975, 1-1, 0);
/*
* コンストラクタ
* 引数は日時(Date コンストラクタに渡される)
*/
constructor() {
const S = MoonPhaseRC;
const date = new Date(...arguments);
const t = (date - S.Tbase) / (365.25 * 86400000);
const T = t + (0.0317 * t + 1.43) / 1000000;
const sun = S.sunPosition(T);
const moon = S.moonPosition(T);
const phase = (moon.longitude - sun.longitude) % 360;
this.T = T;
this.sun = sun;
this.moon = moon;
this.phase = phase + ((phase < 0) ? 360 : 0);
}
/*
* 月の視黄経・視黄緯の算出
*/
static moonPosition(T) {
const degToRad = ((deg) => (deg % 360) * Math.PI / 180);
const accumeSin = ((a, v) => a + v[0] * Math.sin(degToRad(v[1] + v[2] * T)));
const accumeCos = ((a, v) => a + v[0] * Math.cos(degToRad(v[1] + v[2] * T)));
const A = [
[0.0040, 93.80, -1.330],
[0.0020, 248.60, -19.340],
[0.0006, 66.00, 0.200],
[0.0006, 249.00, -19.300],
].reduce(accumeSin, 0);
const B = [
[0.0267, 68.64, -19.341],
[0.0043, 342.00, -19.360],
[0.0040, 93.80, -1.330],
[0.0020, 248.60, -19.340],
[0.0005, 358.00, -19.400],
].reduce(accumeSin, 0);
const longitude = [
[1.2740, 107.248, -4133.3536],
[0.6583, 51.668, 8905.3422],
[0.2136, 317.831, 9543.9773],
[0.1856, 176.531, 359.9905],
[0.1143, 292.463, 9664.0404],
[0.0588, 86.160, 638.6350],
[0.0572, 103.780, -3773.3630],
[0.0533, 30.580, 13677.3310],
[0.0459, 124.860, -8545.3520],
[0.0410, 342.380, 4411.9980],
[0.0348, 25.830, 4452.6710],
[0.0305, 155.450, 5131.9790],
[0.0153, 240.790, 758.6980],
[0.0125, 271.380, 14436.0290],
[0.0110, 226.450, -4892.0520],
[0.0107, 55.580,-13038.6960],
[0.0100, 296.750, 14315.9660],
[0.0085, 34.500, -8266.7100],
[0.0079, 290.700, -4493.3400],
[0.0068, 228.200, 9265.3300],
[0.0052, 133.100, 319.3200],
[0.0050, 202.400, 4812.6600],
[0.0048, 68.600, -19.3400],
[0.0040, 34.100, 13317.3400],
[0.0040, 9.500, 18449.3200],
[0.0040, 93.800, -1.3300],
[0.0039, 103.300, 17810.6800],
[0.0037, 65.100, 5410.6200],
[0.0027, 321.300, 9183.9900],
[0.0026, 174.800,-13797.3900],
[0.0024, 82.700, 998.6300],
[0.0024, 4.700, 9224.6600],
[0.0022, 121.400, -8185.3600],
[0.0021, 134.400, 9903.9700],
[0.0021, 173.100, 719.9800],
[0.0021, 100.300, -3413.3700],
[0.0020, 248.600, -19.3400],
[0.0018, 98.100, 4013.2900],
[0.0016, 344.100, 18569.3800],
[0.0012, 52.100,-12678.7100],
[0.0011, 250.300, 19208.0200],
[0.0009, 81.000, -8586.0000],
[0.0008, 207.000, 14037.3000],
[0.0008, 31.000, -7906.7000],
[0.0007, 346.000, 4052.0000],
[0.0007, 294.000, -4853.3000],
[0.0007, 90.000, 278.6000],
[0.0006, 237.000, 1118.7000],
[0.0005, 82.000, 22582.7000],
[0.0005, 276.000, 19088.0000],
[0.0005, 73.000,-17450.7000],
[0.0005, 112.000, 5091.3000],
[0.0004, 116.000, -398.7000],
[0.0004, 25.000, -120.1000],
[0.0004, 181.000, 9584.7000],
[0.0004, 18.000, 720.0000],
[0.0003, 60.000, -3814.0000],
[0.0003, 13.000, -3494.7000],
[0.0003, 13.000, 18089.3000],
[0.0003, 152.000, 5492.0000],
[0.0003, 317.000, -40.7000],
[0.0003, 348.000, 23221.3000],
].reduce(
accumeSin,
+ 124.8754 + 4812.67881 * T
+ 6.2887 * Math.sin(degToRad(338.915 + 4771.9886 * T + A))
);
const latitude = [
[0.2806, 215.147, 9604.0088],
[0.2777, 77.316, 60.0316],
[0.1732, 4.563, -4073.3220],
[0.0554, 308.980, 8965.3740],
[0.0463, 343.480, 698.6670],
[0.0326, 287.900, 13737.3620],
[0.0172, 194.060, 14375.9970],
[0.0093, 25.600, -8845.3100],
[0.0088, 98.400, -4711.9600],
[0.0082, 1.100, -3713.3300],
[0.0043, 322.400, 5470.6600],
[0.0042, 266.800, 18509.3500],
[0.0034, 188.000, -4433.3100],
[0.0025, 312.500, 8605.3800],
[0.0022, 291.400, 13377.3700],
[0.0021, 340.000, 1058.6600],
[0.0019, 218.600, 9244.0200],
[0.0018, 291.800, -8206.6800],
[0.0018, 52.800, 5192.0100],
[0.0017, 168.700, 14496.0600],
[0.0016, 73.800, 420.0200],
[0.0015, 262.100, 9284.6900],
[0.0015, 31.700, 9964.0000],
[0.0014, 260.800, -299.9600],
[0.0013, 239.700, 4472.0300],
[0.0013, 30.400, 379.3500],
[0.0012, 304.900, 4812.6800],
[0.0012, 12.400, -4851.3600],
[0.0011, 173.000, 19147.9900],
[0.0010, 312.900,-12978.6600],
[0.0008, 1.000, 17870.7000],
[0.0008, 190.000, 9724.1000],
[0.0007, 22.000, 13098.7000],
[0.0006, 117.000, 5590.7000],
[0.0006, 47.000,-13617.3000],
[0.0005, 22.000, -8485.3000],
[0.0005, 150.000, 4193.4000],
[0.0004, 119.000, -9483.9000],
[0.0004, 246.000, 23281.3000],
[0.0004, 301.000, 10242.6000],
[0.0004, 126.000, 9325.4000],
[0.0004, 104.000, 14097.4000],
[0.0003, 340.000, 22642.7000],
[0.0003, 270.000, 18149.4000],
[0.0003, 358.000, -3353.3000],
[0.0003, 148.000, 19268.0000],
].reduce(
accumeSin,
+ 5.1282 * Math.sin(degToRad(236.231 + 4832.0202 * T + B))
);
const sine = Math.sin(degToRad([
[0.0518, 338.920, 4771.9890],
[0.0095, 287.200, -4133.3500],
[0.0078, 51.700, 8905.3400],
[0.0028, 317.800, 9543.9800],
[0.0009, 31.000, 13677.3000],
[0.0005, 305.000, -8545.4000],
[0.0004, 284.000, -3773.4000],
[0.0003, 342.000, 4412.0000],
].reduce(accumeCos, 0.9507)));
return {
longitude: longitude % 360,
latitude: latitude,
distance: 6378.140 / sine, // km
}
}
/*
* 太陽の視黄経・視黄緯の算出
*/
static sunPosition(T) {
const degToRad = ((deg) => (deg % 360) * Math.PI / 180);
const accumeSin = ((a, v) => a + v[0] * Math.sin(degToRad(v[1] + v[2] * T)));
const accumeCos = ((a, v) => a + v[0] * Math.cos(degToRad(v[1] + v[2] * T)));
const longitude = [
[+0.0200, 356.531, 719.981],
[-0.0048, 248.640, -19.341],
[+0.0020, 285.000, 329.640],
[+0.0018, 334.200, -4452.670],
[+0.0018, 293.700, -0.200],
[+0.0015, 242.400, 450.370],
[+0.0013, 211.100, 225.180],
[+0.0008, 208.000, 659.290],
[+0.0007, 53.500, 90.380],
[+0.0007, 12.100, -30.350],
[+0.0006, 239.100, 337.180],
[+0.0005, 10.100, -1.500],
[+0.0005, 99.100, -22.810],
[+0.0004, 264.800, 315.560],
[+0.0004, 233.800, 299.300],
[-0.0004, 198.100, 720.020],
[+0.0003, 349.600, 1079.970],
[+0.0003, 241.200, -44.430],
].reduce(accumeSin,
+ (279.0358 + 360.00769 * T)
+ (1.9159 - 0.00005 * T)
* Math.sin(degToRad(356.531 + 359.991 * T))
);
const q = [
[-0.000091, 353.1, 719.98],
[+0.000013, 205.8, 4452.67],
[+0.000007, 62.0, 450.40],
[+0.000007, 105.0, 329.60],
].reduce(accumeCos,
+ (-0.007261 + 0.0000002 * T)
* Math.cos(degToRad(356.53 + 359.991 * T))
+ 0.000030
);
const au = Math.pow(10, q);
return {
longitude: longitude % 360,
latitude: 0,
distance: au * 149597870.700, // km
}
}
}
/*
* 月相の形状を生成する
*/
class MoonPhaseShape {
/*
* コンストラクタ
*
* 中心座標を(0,0)として生成する
*
* 引数 (プロパティとして保存)
* radius: 円半径
* phase: 月光の位置
* 0 (新月) → 180 (満月) → 360 (新月)
* 範囲外は [0,360) の範囲に変更される
* distance: 視距離
* null の場合は 380000 とする
* sphereSize: 球体半径
* null の場合は 1738 とする
*
* インスタンス プロパティ
* lightPath: 月光部分の Path2D (null の場合あり)
* shadowPath: 月影部分の Path2D (null の場合あり)
*/
constructor(radius, phase, distance, sphereSize) {
const S = MoonPhaseShape;
phase = phase ?? 0;
if ((phase %= 360) < 0)
phase += 360;
this.S = S;
this.radius = radius;
this.phase = phase;
this.distance = distance ?? 380000;
this.sphereSize = sphereSize ?? 1738;
this.latitudeMax = Math.acos(this.sphereSize / this.distance);
this.initCircle();
this.initPhase();
this.lightPath = S.createPath(this.light);
this.shadowPath = S.createPath(this.shadow);
}
initCircle() {
const PI = Math.PI;
const quaterPI = PI * 0.25;
const radius = this.radius;
const count = radius >> 1;
const co1 = [...Array(count + 1)].map(function(_, n){
const r = quaterPI * n / count;
return [radius * Math.sin(r), radius * Math.cos(r)]
});
const co2 = co1.map(v => [v[1], v[0]]);
const cq1 = co1.concat(co2.toReversed().slice(1));
const cq2 = cq1.map(v => [v[0], -v[1]]);
const ch1 = cq1.concat(cq2.toReversed().slice(1));
const ch2 = ch1.map(v => [-v[0], v[1]]);
this.circleLeft = ch2;
this.circleRight = ch1;
}
initPhase() {
const PI = Math.PI;
const halfPI = PI * 0.5;
const twoPI = PI * 2.0;
const radius = this.radius;
const distance = this.distance;
const sphereSize = this.sphereSize;
const mr = halfPI - this.latitudeMax;
{
const x = sphereSize * Math.cos(mr);
const z = distance - sphereSize * Math.sin(mr);
this.viewScale = radius * z / x;
}
const pl = this.phase * PI / 180;
const pr = (pl >= PI) ? pl - PI : pl + PI;
const ql = pl - mr;
const qr = pr - mr;
const qm = PI - mr * 2;
const qn = PI + mr;
let ptype, sep;
if (0 <= qr && qr < qm) {
ptype = 0;
sep = qr;
} else if (0 <= ql && ql < qm) {
ptype = 1;
sep = ql;
} else {
ptype = 2 | (0 <= ql && ql < qn);
}
const cl = this.circleLeft;
const cr = this.circleRight;
if ((ptype & 2) != 0) {
const cd = cr.concat(cl.toReversed().slice(1, -1));
this.light = (ptype & 1) ? cd : null;
this.shadow = (ptype & 1) ? null : cd;
} else {
const cc = this.getSeparator(sep);
const vr = cr.concat(cc.toReversed());
const vl = cc.concat(cl.toReversed());
this.light = (ptype & 1) ? vr : vl;
this.shadow = (ptype & 1) ? vl : vr;
}
}
getSeparator(sep) {
const radius = this.radius;
const distance = this.distance;
const sphereSize = this.sphereSize;
const viewScale = this.viewScale;
const latitudeMax = this.latitudeMax;
const sepCos = Math.cos(sep);
const sepSin = Math.sin(sep);
const count = radius >> 0;
const shp = [...Array(count + 1)].map(function(_, n) {
const t = latitudeMax * n / count;
const x = sphereSize * Math.cos(t) * sepCos;
const y = sphereSize * Math.sin(t);
const z = distance - sphereSize * Math.cos(t) * sepSin;
const v = viewScale / z;
return [v * x, v * y];
});
const shm = shp.map(v => [v[0], -v[1]]);
return shp.toReversed().concat(shm.slice(1))
}
static createPath(pos) {
if (pos == null)
return null;
const plen = pos.length;
if (!plen)
return null;
const path = new Path2D();
const p0 = pos[0];
path.moveTo(p0[0], p0[1]);
for (let i = 1; i < plen; i++) {
const p = pos[i];
path.lineTo(p[0], p[1]);
}
path.closePath();
return path;
}
createBitmap(option) {
const width = option.width ?? this.radius * 2 + 4;
const height = option.height ?? width;
const cx = option.offset_x ?? width / 2;
const cy = option.offset_y ?? height / 2;
const background = option.background ?? '#00000000';
const shadow = option.shadow ?? '#00000000';
const light = option.light ?? '#00000000';
const rotate = option.rotate ?? 0;
const canvas = new OffscreenCanvas(width, height);
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = background;
ctx.fillRect(0, 0, width, height);
ctx.translate(cx, cy);
ctx.rotate(rotate * Math.PI / 180);
if (this.shadowPath) {
ctx.fillStyle = shadow;
ctx.fill(this.shadowPath);
}
if (this.lightPath) {
ctx.fillStyle = light;
ctx.fill(this.lightPath);
}
return canvas.convertToBlob();
}
static createPhaseTable(option) {
if (option == null)
option = {}
const count = option.count ?? 60;
const width = option.width ?? 128;
const height = option.height ?? width;
const radius = Math.max(4, Math.min(width, height) - 1) / 2;
const rotate = option.rotate ?? 0;
const popt = {
width: width,
height: height,
offset_x: option.offset_x,
offset_y: option.offset_y,
background: option.background,
shadow: option.shadow,
light: option.light,
}
return Promise.all([...Array(count)].map((_, i) => {
popt.rotate = rotate * (1 - 2 * i / count);
const mps = new MoonPhaseShape(radius, i * 360 / count);
return mps.createBitmap(popt).then(o => URL.createObjectURL(o));
}));
}
}
/*
* 休日データ
*/
class JHoliday {
static #startDate = (new Date(1970, 1-1, 1)).getTime() * 10;
static #offsetVE = JHoliday.#startDate + 68619916800;
static #offsetAE = JHoliday.#startDate + 229674407040;
static #yEquinox = 315569255616;
static #monthTable = [...Array(12)].map(() => ({}));
static #createParameter(name, month, day, start, end) {
const S = JHoliday;
month -= 1;
const holidy = {
name: name,
month: month,
day: day,
start: Math.max(1948, (start ?? 0)),
end: end ?? 10000,
}
const table = S.#monthTable;
const mtab = table[month];
if (!mtab[day])
mtab[day] = [];
mtab[day].push(holidy);
return holidy;
}
static #mainTable = [
['元日', 1, 1],
['成人の日', 1, 15, 0, 1999],
['成人の日', 1, 'M2', 2000],
['建国記念の日', 2, 11],
['春分の日', 3, 'VE'],
['憲法記念日', 5, 3],
['みどりの日', 5, 4, 2007],
['こどもの日', 5, 5],
['海の日', 7, 20, 1996, 2002],
['海の日', 7, 'M3', 2003],
['山の日', 8, 11, 2016],
['敬老の日', 9, 15, 1966, 2002],
['敬老の日', 9, 'M3', 2003],
['秋分の日', 9, 'AE'],
['体育の日', 10, 10, 0, 1999],
['体育の日', 10, 'M2', 2000, 2019],
['スポーツの日', 10, 'M2', 2020],
['文化の日', 11, 3],
['勤労感謝の日', 11, 23],
/* 昭和 */
['天皇誕生日', 4, 29, 0, 1988],
['みどりの日', 4, 29, 1989, 2006],
['昭和の日', 4, 29, 2007],
/* 平成 */
['天皇誕生日', 12, 23, 1989, 2018],
/* 令和 */
['天皇の即位の日', 5, 1, 2019, 2019],
['即位礼正殿の儀が行われる日', 10, 22, 2019, 2019],
['天皇誕生日', 2, 23, 2020],
].map((v) => JHoliday.#createParameter(...v));
static #altname = '休日';
static #getBasic(date) {
const S = JHoliday;
const year = date.getFullYear();
const month = date.getMonth();
const mday = date.getDate();
const wday = date.getDay();
const mtab = S.#monthTable[month]
const dtab = mtab[mday];
if (dtab) {
const t = dtab.filter((h) =>
(h.start <= year && year <= h.end));
if (t.length)
return t[0].name;
}
if (wday == 1) {
const mweek = Math.trunc((mday - 1) / 7);
const monday = mtab[`M${mweek+1}`];
if (monday) {
const t = monday.filter((h) =>
(h.start <= year && year <= h.end));
if (t.length)
return t[0].name;
}
}
const ve = mtab['VE'];
if (ve) {
const equniox = S.getVernalEquinox(year);
if (mday == equniox.getDate())
return ve[0].name;
}
const ae = mtab['AE'];
if (ae) {
const equniox = S.getAutumnEquinox(year);
if (mday == equniox.getDate())
return ae[0].name;
}
return undefined;
}
static #weekCache = {}
static #getWeek(date) {
const S = JHoliday;
const year = date.getFullYear();
const month = date.getMonth();
const mday = date.getDate();
const wday = date.getDay();
const wtop = new Date(year, month, 1).getDay();
const mweek = Math.trunc((wtop + mday - 1) / 7);
const index = (year * 12 + month) * 6 + mweek;
const cache = S.#weekCache[index];
if (cache)
return cache;
const line = [...Array(8)].map((_, i) =>
S.#getBasic(new Date(year, month, mday - wday + i)));
if (year >= 1973 && line[0]) {
for (let i = 1; i < 7; i++) {
if (!line[i]) {
line[i] = S.#altname;
break;
}
}
}
if (year >= 1986)
for (let i = 1; i < 7; i++)
if (!line[i] && line[i - 1] && line[i + 1])
line[i] = S.#altname;
S.#weekCache[index] = line;
return line;
}
// 年から春分の日(Date型)を取得.
static getVernalEquinox(year) {
const S = JHoliday;
return new Date(((year - 1970) * S.#yEquinox + S.#offsetVE) / 10);
}
// 年から秋分の日(Date型)を取得.
static getAutumnEquinox(year) {
const S = JHoliday;
return new Date(((year - 1970) * S.#yEquinox + S.#offsetAE) / 10);
}
// Date 型から休日を取得.
static getHoliday(date) {
const S = JHoliday;
return S.#getWeek(date)[date.getDay()];
}
}
/*
* 休日ありの Date 派生クラス
*/
class JHDate extends Date {
#holiday;
constructor() {
super(...arguments);
this.#holiday = JHoliday.getHoliday(this);
}
// 休日を取得.
getHoliday() {
return this.#holiday;
}
}
/*
* 週/月
*/
// リストを週単位の行列に変換.
function toWeekly(l) {
return [...Array(Math.trunc(l.length / 7))]
.map((_, w) => l.slice(w * 7, w * 7 + 7));
}
// 月間 JHDate のスリとを取得.
function getMonthlyJHDates(date) {
const year = date.getFullYear();
const month = date.getMonth();
return [...Array(new Date(year, month + 1, 0).getDate())]
.map((_, i) => new JHDate(year, month, i + 1));
}
// 週の前方を補間するためのリストを取得.
function getWeeklyPrefix(date) {
const y = date.getFullYear();
const m = date.getMonth();
const ns = date.getDay();
return [...Array(ns)]
.map((_, i) => new JHDate(y, m, 1 + i - ns));
}
// 週の後方を補間するためのリストを取得.
function getWeeklySuffix(date) {
const y = date.getFullYear();
const m = date.getMonth();
return [...Array(6 - date.getDay())]
.map((_, i) => new JHDate(y, m + 1, 1 + i));
}
// 月間カレンダー用 JHDate 行列の取得.
function getMonthlyMatrix(date) {
const mlist = getMonthlyJHDates(date);
const s = mlist[0];
const e = mlist[mlist.length - 1];
const pfx = getWeeklyPrefix(s);
const sfx = getWeeklySuffix(e);
return toWeekly(pfx.concat(mlist, sfx));
}
/*
* DOM 操作
*/
const createTag = {};
[
'div', 'p', 'span',
'table', 'td', 'tr',
'img',
].forEach((name) =>
createTag[name] = (() => document.createElement(name))
);
// 月間カレンダー
class JHMonthlyCalendar {
constructor(date, option) {
this.today = new Date();
this.date = date ?? this.today;
this.option = option ?? {};
this.getHeader = (this.option.month_header ??
((y, m) => `${y}年${m}月`));
const cpfx = this.option.prefix ?? '';
this.className = {
month: {
table: `${cpfx}month`,
},
arrow: {
td: `${cpfx}marrow`,
},
header: {
td: `${cpfx}mheader`,
},
weekofday: {
td: `${cpfx}wodbox`,
div: `${cpfx}weekofday`,
},
wodheader: [
{ div: `${cpfx}wsunday` },
{ div: `${cpfx}wmonday` },
{ div: `${cpfx}wtuesday` },
{ div: `${cpfx}wwednesday` },
{ div: `${cpfx}wthursday` },
{ div: `${cpfx}wfriday` },
{ div: `${cpfx}wsaturday` },
],
day: {
td: `${cpfx}daybox`,
div: `${cpfx}day`,
},
today: {
div: `${cpfx}today`,
},
holiday: {
div: `${cpfx}holiday`,
},
popup: {
div: `${cpfx}info`,
},
other: {
div: `${cpfx}other`,
},
week: [
{ div: `${cpfx}sunday` },
{ div: `${cpfx}monday` },
{ div: `${cpfx}tuesday` },
{ div: `${cpfx}wednesday` },
{ div: `${cpfx}thursday` },
{ div: `${cpfx}friday` },
{ div: `${cpfx}saturday` },
],
}
this.tagTable = createTag.table();
this.tagTable.className = this.className.month.table;
this.setCalendar();
}
clearTable() {
while (this.tagTable.firstChild)
this.tagTable.removeChild(this.tagTable.firstChild);
}
setCalendar() {
this.year = this.date.getFullYear();
this.month = this.date.getMonth();
this.weekly = getMonthlyMatrix(this.date);
this.tagHeader = null;
this.header = null;
this.setHeader();
this.setWeek();
this.setMatrix();
}
updateCalendar() {
this.clearTable()
this.setCalendar()
}
setHeader() {
const header = this.getHeader(this.year, this.month + 1);
if (!header || !header.length)
return;
this.header = header;
const fixed = this.option.fixed;
const tr = createTag.tr();
if (!fixed) {
const td = createTag.td();
td.className = this.className.arrow.td;
td.innerText = '←';
td.onclick = (() => this.prevMonth());
tr.appendChild(td);
}
{
const td = createTag.td();
this.tagHeader = td;
td.className = this.className.header.td;
td.setAttribute('colspan', fixed ? 7 : 5);
td.innerText = header;
if (!fixed)
td.onclick = (() => this.moveToday());
tr.appendChild(td);
}
if (!fixed) {
const td = createTag.td();
td.className = this.className.arrow.td;
td.innerText = '→';
td.onclick = (() => this.nextMonth());
tr.appendChild(td);
}
this.tagTable.appendChild(tr);
}
setWeek() {
if (!this.option.weekofday)
return;
const tr = createTag.tr();
[...'日月火水木金土'].forEach((c, i) => {
const td = createTag.td();
td.className = this.className.weekofday.td;
const cn_weekofday = this.className.weekofday.div;
const cn_wodheader = this.className.wodheader[i].div;
const div = createTag.div();
div.className = `${cn_weekofday} ${cn_wodheader}`.trim();
div.innerText = c;
td.appendChild(div);
tr.appendChild(td);
});
this.tagTable.appendChild(tr);
}
setMatrix() {
const today = this.today;
const chktoday = ((today.getFullYear() == this.year &&
today.getMonth() == this.month)
? today.getDate() : null);
const tagHeader = this.tagHeader;
const c_day_td = this.className.day.td;
const c_day_div = this.className.day.div;
const c_today_div = this.className.today.div;
const c_holiday_div = this.className.holiday.div;
const c_week = this.className.week;
const c_other_div = this.className.other.div;
const moon = this.option.moonshape;
const moonshapes = (moon != null) ? moon.length : 0;
this.weekly.forEach((week, y) => {
const tr = createTag.tr();
week.forEach((day, x) => {
const holiday = day.getHoliday();
const td = createTag.td();
td.className = c_day_td;
{
const div = createTag.div();
const cn_other = ((day.getMonth() != this.month) ? c_other_div : null);
const cn_holiday = (holiday ? c_holiday_div : null);
const cn_week = c_week[day.getDay()].div;
const cn_day = cn_other ?? cn_holiday ?? cn_week;
const cn_today = ((day.getDate() == chktoday) ? c_today_div : '');
div.className = `${c_day_div} ${cn_day} ${cn_today}`.trim();
div.innerText = day.getDate();
if (moonshapes > 0) {
const phase = new MoonPhaseRC(day + 43200000).phase;
const ipos = Math.round(moonshapes * phase / 360) % moonshapes;
const url = moon[ipos + (ipos < 0 ? moonshapes : 0)];
div.style.backgroundPosition = 'center center';
div.style.backgroundRepeat = 'no-repeat';
div.style.backgroundSize = 'auto';
div.style.backgroundImage = `url(${url})`;
}
td.appendChild(div);
}
if (holiday && tagHeader) {
td.onmouseenter = (() => this.tagHeader.innerText = holiday);
td.onmouseleave = (() => this.tagHeader.innerText = this.header);
}
tr.appendChild(td);
});
this.tagTable.appendChild(tr);
});
}
// Event
prevMonth() {
this.moveMonth(new Date(this.year, this.month - 1, 1));
}
nextMonth() {
this.moveMonth(new Date(this.year, this.month + 1, 1));
}
moveMonth(date) {
this.today = new Date();
this.date = date;
this.updateCalendar();
}
moveToday() {
this.today = new Date();
this.date = this.today;
this.updateCalendar();
}
}
// 年間カレンダー
class JHYearlyCalendar {
constructor(date, option) {
this.today = new Date();
this.date = date ?? this.today;
this.option = option ?? {};
const cpfx = this.option.prefix ?? '';
this.className = {
year: {
table: `${cpfx}year`,
},
arrow: {
span: `${cpfx}yallow`,
},
header: {
td: `${cpfx}yhbox`,
span: `${cpfx}yheader`,
},
month: {
td: `${cpfx}ymonth`,
},
}
this.tagTable = createTag.table();
this.tagTable.className = this.className.year.table;
this.setCalendar();
}
clearTable() {
while (this.tagTable.firstChild)
this.tagTable.removeChild(this.tagTable.firstChild);
}
setCalendar() {
this.year = this.date.getFullYear();
const prefix = this.option.prefix ?? '';
const year = this.date.getFullYear();
this.yearly = [...Array(12)].map((_, m) =>
new JHMonthlyCalendar(new Date(this.year, m), {
prefix: prefix,
month_header: (y, m) => `${m}月`,
fixed: true,
moonshape: this.option.moonshape,
}).tagTable);
this.tagHeader = null;
this.header = null;
this.setHeader();
this.setMatrix();
}
updateCalendar() {
this.clearTable()
this.setCalendar()
}
setHeader() {
const space = ' '.repeat(4);
const tr = createTag.tr();
const td = createTag.td();
td.className = this.className.header.td;
td.setAttribute('colspan', '3');
{
const span = createTag.span();
span.className = this.className.arrow.span;
span.innerText = '←';
span.onclick = (() => this.prevYear());
td.appendChild(span);
}
td.insertAdjacentHTML('beforeend', `<span>${space}</span>`)
{
const span = createTag.span();
span.className = this.className.header.span;
span.innerText = `${this.year}年`;
span.onclick = (() => this.moveToday());
td.appendChild(span);
}
td.insertAdjacentHTML('beforeend', `<span>${space}</span>`)
{
const span = createTag.span();
span.className = this.className.arrow.span;
span.onclick = (() => this.nextYear());
span.innerText = '→';
td.appendChild(span);
}
tr.appendChild(td);
this.tagTable.appendChild(tr);
}
setMatrix() {
for (let y = 0; y < 4; y++) {
const tr = createTag.tr();
this.yearly.slice(y * 3, y * 3 + 3).forEach((m) => {
const td = createTag.td();
td.className = this.className.month.td;
td.style.setProperty('vertical-align', 'top');
td.appendChild(m);
tr.appendChild(td);
});
this.tagTable.appendChild(tr);
}
}
// Event
prevYear() {
this.moveYear(new Date(this.year - 1, 0, 1));
}
nextYear() {
this.moveYear(new Date(this.year + 1, 0, 1));
}
moveYear(date) {
this.today = new Date();
this.date = date;
this.updateCalendar();
}
moveToday() {
this.today = new Date();
this.date = this.today;
this.updateCalendar();
}
}
// 15週カレンダー
class JHWeek15Calendar {
constructor(date, option) {
this.today = new Date();
this.date = date ?? this.today;
this.option = option ?? {};
this.getHeader = (this.option.weeks_header ??
((d) => `${d.getFullYear()}年${d.getMonth()+1}月${d.getDate()}日`));
const cpfx = this.option.prefix ?? '';
this.className = {
weeks: {
table: `${cpfx}month`,
},
arrow: {
td: `${cpfx}marrow`,
},
header: {
td: `${cpfx}whbox`,
span: `${cpfx}hinfo`,
},
weekofday: {
td: `${cpfx}wodbox`,
div: `${cpfx}weekofday`,
},
wodheader: [
{ div: `${cpfx}wsunday` },
{ div: `${cpfx}wmonday` },
{ div: `${cpfx}wtuesday` },
{ div: `${cpfx}wwednesday` },
{ div: `${cpfx}wthursday` },
{ div: `${cpfx}wfriday` },
{ div: `${cpfx}wsaturday` },
],
month: [
{ div: `${cpfx}modd` },
{ div: `${cpfx}meven` },
],
day: {
td: `${cpfx}daybox`,
div: `${cpfx}day`,
},
today: {
div: `${cpfx}today`,
},
holiday: {
div: `${cpfx}holiday`,
},
popup: {
div: `${cpfx}info`,
},
other: {
div: `${cpfx}other`,
},
week: [
{ div: `${cpfx}sunday` },
{ div: `${cpfx}monday` },
{ div: `${cpfx}tuesday` },
{ div: `${cpfx}wednesday` },
{ div: `${cpfx}thursday` },
{ div: `${cpfx}friday` },
{ div: `${cpfx}saturday` },
],
}
this.pastWeeks = this.option.past ?? 7;
this.nextWeeks = this.option.next ?? 7;
this.totalWeeks = this.pastWeeks + 1 + this.nextWeeks;
this.tagTable = createTag.table();
this.tagTable.className = this.className.weeks.table;
this.setCalendar();
}
clearTable() {
while (this.tagTable.firstChild)
this.tagTable.removeChild(this.tagTable.firstChild);
}
setCalendar() {
this.year = this.date.getFullYear();
this.month = this.date.getMonth();
const mday = this.date.getDate();
const wday = this.date.getDay();
const cday = mday - wday;
const sday = cday - this.pastWeeks * 7;
this.weekly = [...Array(this.totalWeeks)].map(
(_, y) => [...Array(7)].map((_, x) =>
new JHDate(this.year, this.month, sday + y * 7 + x)));
this.tagHeader = null;
this.header = null;
this.setHeader();
this.setWeek();
this.setMatrix();
}
setHeader() {
const header = this.getHeader(this.today);
if (!header || !header.length)
return;
this.header = header + '(本日)';
const fixed = true;
const tr = createTag.tr();
const td = createTag.td();
this.tagHeader = td;
td.className = this.className.header.td;
td.setAttribute('colspan', 7);
td.innerText = this.header;
td.onclick = (() => this.moveToday());
tr.appendChild(td);
this.tagTable.appendChild(tr);
}
setWeek() {
if (!this.option.weekofday)
return;
const tr = createTag.tr();
[...'日月火水木金土'].forEach((c, i) => {
const td = createTag.td();
td.className = this.className.weekofday.td;
const cn_weekofday = this.className.weekofday.div;
const cn_wodheader = this.className.wodheader[i].div;
const div = createTag.div();
div.className = `${cn_weekofday} ${cn_wodheader}`.trim();
div.innerText = c;
td.appendChild(div);
tr.appendChild(td);
});
this.tagTable.appendChild(tr);
}
setMatrix() {
const today = this.today;
const tagHeader = this.tagHeader;
const c_day_td = this.className.day.td;
const c_day_div = this.className.day.div;
const c_month = this.className.month;
const c_today_div = this.className.today.div;
const c_holiday_div = this.className.holiday.div;
const c_week = this.className.week;
const moon = this.option.moonshape;
const moonshapes = (moon != null) ? moon.length : 0;
this.weekly.forEach((w, y) => {
const tr = createTag.tr();
w.forEach((day, x) => {
const holiday = day.getHoliday();
const td = createTag.td();
td.className = this.className.day.td;
{
const div = createTag.div();
const cn_holiday = (holiday ? c_holiday_div : null);
const cn_week = c_week[day.getDay()].div;
const cn_day = cn_holiday ?? cn_week;
const cn_month = c_month[day.getMonth() & 1].div;
const cn_today = ((today.getFullYear() == day.getFullYear() &&
today.getMonth() == day.getMonth() &&
today.getDate() == day.getDate())
? c_today_div : '');
div.className = `${c_day_div} ${cn_day} ${cn_month} ${cn_today}`.trim();
div.innerText = day.getDate();
if (this.header) {
div.onmouseenter = (() => this.tagHeader.innerText = holiday ?? this.getHeader(day));
div.onmouseleave = (() => this.tagHeader.innerText = this.header);
}
div.onclick = (() => this.moveDay(day));
if (moonshapes > 0) {
const phase = new MoonPhaseRC(day + 43200000).phase;
const ipos = Math.round(moonshapes * phase / 360) % moonshapes;
const url = moon[ipos + (ipos < 0 ? moonshapes : 0)];
div.style.backgroundPosition = 'center center';
div.style.backgroundRepeat = 'no-repeat';
div.style.backgroundSize = 'auto';
div.style.backgroundImage = `url(${url})`;
}
td.appendChild(div);
}
/**/
tr.appendChild(td);
});
this.tagTable.appendChild(tr);
});
}
updateCalendar() {
this.clearTable()
this.setCalendar()
}
moveDay(date) {
this.today = new Date();
this.date = date;
this.updateCalendar();
}
moveToday() {
this.today = new Date();
this.date = this.today;
this.updateCalendar();
}
}
function onSelect(n) {
[tagMonthly, tagWeekly, tagYearly].forEach((tag, i) => tag.hidden = (i != n));
[tagButton0, tagButton1, tagButton2].forEach((tag, i) =>
tag.style.setProperty('color', (i == n) ? 'red' : 'white'));
}
function onLoad(moonshape) {
const today = new Date();
const option = {
prefix: 'jhcal', // クラス名の前置文字.
// header_format: (y, m) => `${m}月`, // 年月部の文字列.
// fixed: true, // 月移動しない.
weekofday: true, // 曜日を表示.
moonshape: moonshape, // 月相
}
tagMonthly.appendChild(new JHMonthlyCalendar(today, option).tagTable);
tagWeekly.appendChild(new JHWeek15Calendar(today, option).tagTable);
tagYearly.appendChild(new JHYearlyCalendar(today, option).tagTable);
onSelect(2);
}
window.onload = function() {
MoonPhaseShape.createPhaseTable({
width: 24, height: 20,
light: '#FFFF00A0',
shadow: '#0000FFA0',
}).then(onLoad);
}
</script>
</body>
</html>