跳到主要内容Python实现中秋月相可视化 | 极客日志Python大前端算法
Python实现中秋月相可视化
用 Python 实现了一个中秋月相可视化项目:先以朔望月周期和参考新月日期计算月相值,再用 matplotlib 绘制月亮、时间轴、月相曲线和当前月相图,并通过 Base64 嵌入 HTML 页面。文章重点讲了月亮阴影、节日光晕、字体兼容和页面动画的实现思路,适合做一个兼具算法与可视化的实战小项目。
lzdxwyh6 浏览 引言
中秋节承载着团圆的文化意象,而月相变化本身又很适合用程序去表达。本文就用 Python 搭一个月相可视化小项目,把月亮的盈亏、图表展示和网页界面串在一起,让代码不只是'算出来',还能'画出来'。
项目概述
这个项目主要做了几件事:
- 基于朔望月周期计算任意日期的月相
- 用 matplotlib 绘制月亮、时间轴和曲线图
- 生成带星空、流星和光晕效果的 HTML 页面
- 融入中秋节元素,让页面更有节日氛围
项目里用到的核心思路并不复杂,关键在于两点:一是月相计算要稳定,二是可视化要足够自然,不能只把图画出来,还要让它看起来像'月亮'。
技术架构
先看整体依赖和结构。这里的重点不是堆很多库,而是把职责拆开:一个模块负责计算,一个模块负责渲染网页。
pip install numpy matplotlib
项目结构大致如下:
moon-phase-visualizer/
├── moon_calculator.py # 核心计算引擎
├── generate_html.py # HTML 生成器
└── moon_phase_2025_mid_autumn.html
实现思路
月相计算核心
月相变化可以近似看成一个 29.53 天的周期。项目里选了一个参考新月日期,然后通过目标日期与参考点的时间差,算出它在整个周期中的位置,再映射成 0 到 1 的月相值。
这个映射有个很实用的处理:前半周期表示从新月走向满月,后半周期则从满月回落到新月。这样一来,月相值就比较直观,后面绘图时也更好用。
可视化难点
真正花时间的地方其实在绘图。
matplotlib 默认的中文字体兼容性并不总是让人省心,所以这里需要做字体回退;月亮本体则用圆形配合椭圆阴影来模拟明暗面。中秋节当天再加上多层光晕和星星装饰,视觉上就会比普通的圆形图案更像一张节日海报。
核心模块
moon_calculator.py:核心计算引擎
先把计算和基础绘图能力放在一个类里,这样后面的 HTML 生成只需要调用结果,不用关心内部细节。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as patches
from datetime import datetime, timedelta
import math
import base64
import io
import warnings
from matplotlib import rcParams
warnings.filterwarnings('ignore')
plt.switch_backend('Agg')
plt.rcParams['font.sans-serif'] = [
'Microsoft YaHei', 'SimHei', ,
]
plt.rcParams[] =
:
():
.lunar_cycle =
.reference_new_moon = datetime(, , )
():
days_since_new_moon = (date - .reference_new_moon).total_seconds() / ( * )
cycle_position = (days_since_new_moon % .lunar_cycle) / .lunar_cycle
cycle_position <= :
phase = cycle_position *
:
phase = - (cycle_position * )
phase
():
days_since_new_moon = (date - .reference_new_moon).total_seconds() / ( * )
days_since_new_moon % .lunar_cycle
():
mid_autumn_2025 = datetime(, , )
((date - mid_autumn_2025).days) ==
():
year == :
datetime(, , )
year == :
datetime(, , )
year == :
datetime(, , )
datetime(, , )
'Arial Unicode MS'
'DejaVu Sans'
'axes.unicode_minus'
False
class
MoonPhaseCalculator
"""月相计算器类"""
def
__init__
self
self
29.530588853
self
2025
9
25
def
get_moon_phase
self, date
"""计算指定日期的月相值"""
self
24
3600
self
self
if
0.5
2
else
2
2
return
def
get_moon_age
self, date
"""计算月龄"""
self
24
3600
return
self
def
is_mid_autumn_festival
self, date
"""判断是否为中秋节(2025 年 10 月 6 日)"""
2025
10
6
return
abs
0
def
get_mid_autumn_date
self, year=2025
"""获取指定年份的中秋节日期"""
if
2025
return
2025
10
6
elif
2024
return
2024
9
17
elif
2026
return
2026
9
25
return
2025
10
6
lunar_cycle = 29.530588853 使用的是朔望月的近似值
get_moon_phase() 先算周期位置,再把它折算成对称的月相值
total_seconds() 用来保证日期差计算足够平滑,避免只用天数带来的粗糙感
可视化渲染类
绘图逻辑单独放在 WebMoonVisualizer 里,会更清楚。计算归计算,显示归显示,代码结构也更容易维护。
class WebMoonVisualizer:
"""Web 版月相可视化器"""
def __init__(self):
self.calculator = MoonPhaseCalculator()
def draw_moon(self, ax, phase, size=1.0, position=(0, 0), is_mid_autumn=False):
"""绘制月亮形状"""
x, y = position
circle = patches.Circle(
(x, y), size,
facecolor='lightyellow',
edgecolor='gold',
linewidth=2
)
ax.add_patch(circle)
if phase < 0.95:
shadow_width = size * 2 * (1 - phase)
if shadow_width > 0.1:
shadow = patches.Ellipse(
(x + size * 0.1, y),
shadow_width, size * 2,
facecolor='darkgray',
alpha=0.6
)
ax.add_patch(shadow)
if is_mid_autumn:
for i in range(4):
halo = patches.Circle(
(x, y),
size * (1.3 + i * 0.15),
facecolor='orange',
alpha=0.12 - i * 0.03,
edgecolor='none'
)
ax.add_patch(halo)
star_positions = [
(x - size * 2.0, y + size * 1.0),
(x + size * 2.0, y + size * 1.0),
(x - size * 1.8, y - size * 1.3),
(x + size * 1.8, y - size * 1.3),
]
for sx, sy in star_positions:
star = patches.RegularPolygon(
(sx, sy), 5, radius=size * 0.12,
facecolor='gold', alpha=0.9
)
ax.add_patch(star)
def get_phase_name(self, phase):
"""获取月相名称"""
if phase < 0.1:
return '新月'
elif phase < 0.35:
return '蛾眉月'
elif phase < 0.65:
return '上弦月'
elif phase < 0.9:
return '盈凸月'
return '满月'
这里的思路很直接:底层圆形负责'月亮',椭圆负责'阴影',再用几层半透明圆形去堆出节日氛围。它不追求严格的天文摄影效果,但足够适合做展示型页面。
四种图表实现
时间轴图表:连续月相展示
时间轴图表适合看一段时间内月相怎么变化。为了避免月亮之间互相挤在一起,这里把间距拉大了,画布也做得更宽一些。
def create_timeline_chart(self, center_date=None, days=30):
"""创建时间轴图表"""
if center_date is None:
center_date = datetime(2025, 10, 6)
start_date = center_date - timedelta(days=15)
fig, ax = plt.subplots(figsize=(24, 12))
for i in range(days):
current_date = start_date + timedelta(days=i)
phase = self.calculator.get_moon_phase(current_date)
is_mid_autumn = self.calculator.is_mid_autumn_festival(current_date)
x_pos = i * 3.5
y_pos = 0
self.draw_moon(
ax, phase, size=1.0,
position=(x_pos, y_pos),
is_mid_autumn=is_mid_autumn
)
ax.text(
x_pos, -3.0,
current_date.strftime('%m-%d'),
ha='center', va='top',
fontsize=11, fontweight='bold'
)
if is_mid_autumn:
ax.text(
x_pos, 3.5,
'2025年中秋节',
ha='center', va='bottom',
fontsize=14, fontweight='bold', color='red'
)
这个图最重要的是两个细节:x_pos = i * 3.5 拉开间距,以及用更大的画布承接整条时间轴。这样渲染出来不会显得局促。
月相曲线图:把规律画出来
如果说时间轴图是在'看样子',那曲线图就是在'看规律'。它更强调月相值随时间变化的节奏感。
def create_phase_chart(self):
"""创建月相图表"""
fig, ax = plt.subplots(figsize=(12, 8))
ax.set_facecolor('#001133')
today = datetime(2025, 10, 6)
dates_range = [today + timedelta(days=i - 15) for i in range(31)]
phase_values = [self.calculator.get_moon_phase(d) for d in dates_range]
ax.plot(
range(31), phase_values,
'gold', linewidth=4,
marker='o', markersize=6,
markerfacecolor='yellow',
markeredgecolor='orange'
)
ax.fill_between(range(31), phase_values, alpha=0.3, color='gold')
ax.axhline(y=1.0, color='red', linestyle='--', alpha=0.8, linewidth=2, label='Full Moon')
ax.axhline(y=0.0, color='silver', linestyle='--', alpha=0.8, linewidth=2, label='New Moon')
ax.axvline(x=15, color='lime', linestyle=':', alpha=0.8, linewidth=3, label='Mid-Autumn Festival')
mid_autumn_phase = self.calculator.get_moon_phase(today)
ax.plot(15, mid_autumn_phase, 'r*', markersize=15, label=f'Festival Phase({mid_autumn_phase:.2f})')
- 深蓝背景模拟夜空
- 金色曲线和填充区域增强视觉层次
- 用水平线标出满月和新月基准
- 中秋节点用红色星标强调出来
当前月相图
单独展示当天月相时,画布就不需要太复杂。重点是让月亮本身成为视觉中心。
def create_current_moon(self):
"""创建当前月相图"""
fig, ax = plt.subplots(figsize=(8, 8))
ax.set_facecolor('#001133')
today = datetime(2025, 10, 6)
phase_today = self.calculator.get_moon_phase(today)
is_today_mid_autumn = self.calculator.is_mid_autumn_festival(today)
self.draw_moon(
ax, phase_today,
size=3.0, position=(0, 0),
is_mid_autumn=is_today_mid_autumn
)
ax.set_xlim(-6, 6)
ax.set_ylim(-6, 6)
ax.set_aspect('equal')
ax.axis('off')
phase_name = self.get_phase_name(phase_today)
title = (
f'2025 Mid-Autumn Festival Moon Phase - {phase_name}\n'
f'{today.strftime("%Y-%m-%d")}\n'
f'Phase Value: {phase_today:.3f}'
)
if is_today_mid_autumn:
title += '\nHappy Mid-Autumn Festival!'
ax.text(
0, -5, title,
ha='center', va='top',
fontsize=14, color='white', fontweight='bold',
bbox=dict(boxstyle='round,pad=0.8', facecolor='darkblue', alpha=0.8)
)
这种图的优势就是干净。没有多余坐标轴干扰,月亮和信息框各自明确,适合直接放到网页首页里。
图像 Base64 编码
如果要把 matplotlib 图直接塞进 HTML,最省事的方法就是转成 Base64。
def fig_to_base64(self, fig):
"""将 matplotlib 图形转换为 base64 字符串"""
buffer = io.BytesIO()
fig.savefig(
buffer,
format='png',
facecolor=fig.get_facecolor(),
bbox_inches='tight',
dpi=150
)
buffer.seek(0)
image_png = buffer.getvalue()
buffer.close()
plt.close(fig)
graphic = base64.b64encode(image_png)
return graphic.decode('utf-8')
这里的关键是两点:一是不用落地临时文件,二是记得 plt.close(fig),不然图一多,内存很容易慢慢涨上去。
HTML 界面生成
generate_html.py:界面组装器
页面生成器的工作很像把零件装成一台完整的机器:先生成各张图,再把图像数据和页面结构拼起来。
from moon_calculator import WebMoonVisualizer
from datetime import datetime
def generate_html():
visualizer = WebMoonVisualizer()
mid_autumn_date = datetime(2025, 10, 6)
timeline_fig = visualizer.create_timeline_chart(mid_autumn_date, days=30)
timeline_img = visualizer.fig_to_base64(timeline_fig)
phase_fig = visualizer.create_phase_chart()
phase_img = visualizer.fig_to_base64(phase_fig)
current_fig = visualizer.create_current_moon()
current_img = visualizer.fig_to_base64(current_fig)
annual_fig = visualizer.create_annual_overview()
annual_img = visualizer.fig_to_base64(annual_fig)
moon_info = visualizer.get_moon_info()
CSS3 特效设计
页面视觉效果主要靠 CSS 动画撑起来。它不需要复杂到惊艳,但要克制、统一,和主题保持一致。
@keyframes twinkle {
0%, 100% { opacity: 0.3; }
50% { opacity: 1; }
}
@keyframes shooting {
0% {
transform: rotate(-45deg) translateX(0) translateY(0);
opacity: 1;
}
100% {
transform: rotate(-45deg) translateX(-800px) translateY(800px);
opacity: 0;
}
}
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-15px); }
}
.highlight {
background: linear-gradient(45deg, #ff6b35, #f7931e, #ffd700);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
animation: gradient-shift 3s ease infinite;
}
JavaScript 交互特效
JavaScript 负责'动起来'的部分,比如星星随机闪烁、流星划过、页面加载时生成氛围感。
function createStars() {
const starsContainer = document.getElementById('stars');
const numberOfStars = 120;
for (let i = 0; i < numberOfStars; i++) {
const star = document.createElement('div');
star.className = 'star';
star.style.left = Math.random() * 100 + '%';
star.style.top = Math.random() * 100 + '%';
const size = Math.random() * 3 + 1;
star.style.width = size + 'px';
star.style.height = size + 'px';
star.style.animationDelay = Math.random() * 3 + 's';
starsContainer.appendChild(star);
}
}
function createShootingStar() {
const shootingStar = document.createElement('div');
shootingStar.className = 'shooting-star';
const startX = Math.random() * window.innerWidth;
const startY = Math.random() * (window.innerHeight * 0.5);
shootingStar.style.left = startX + 'px';
shootingStar.style.top = startY + 'px';
shootingStar.style.animation = 'shooting 1.5s linear forwards';
document.body.appendChild(shootingStar);
setTimeout(() => {
shootingStar.remove();
}, 1500);
}
这部分的思路并不复杂,但很实用:随机生成星星,让页面不显得死板;流星动画播放结束后及时移除 DOM,避免无意义的节点堆积。
结语
月相可视化这个题目看起来带着一点节日气氛,实际上很适合作为一个完整的小型项目来练手:它既有算法计算,也有图形渲染,还有前端页面拼装。把这些环节串起来之后,你会发现 Python 不只是适合写脚本,也能把'看不见的规律'做成很好看的页面。
相关免费在线工具
- 加密/解密文本
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
- Gemini 图片去水印
基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online
- curl 转代码
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
- Markdown转HTML
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online