# -*- coding: utf-8 -*- """Gantt-style timeline for 3D OpticsFEM (target: late July 2026).""" from __future__ import annotations from datetime import date, timedelta from pathlib import Path import matplotlib.pyplot as plt import matplotlib.dates as mdates import matplotlib.patches as mpatches OUT = Path(__file__).resolve().parent plt.rcParams["font.sans-serif"] = ["Microsoft YaHei", "SimHei", "Arial"] plt.rcParams["axes.unicode_minus"] = False GREEN = "#75BD42" GREEN_D = "#4A7C3F" BLUE = "#4874CB" ORANGE = "#EE822F" GRAY = "#CCCCCC" DARK = "#2C3E50" WHITE = "#FFFFFF" # (label, start, end, color, status) # status: done | doing | plan | stretch T0 = date(2026, 6, 1) ITEMS = [ ("阶段0 工程基础", date(2026, 6, 1), date(2026, 6, 15), GREEN, "done"), ("阶段2 PEC/ELE", date(2026, 6, 10), date(2026, 6, 20), GREEN, "done"), ("阶段3 PBC 单/双周期", date(2026, 6, 15), date(2026, 6, 25), GREEN, "done"), ("阶段1 本征频率 Run/Post", date(2026, 6, 23), date(2026, 7, 5), ORANGE, "doing"), ("阶段4 BELE/源项边界", date(2026, 6, 28), date(2026, 7, 12), BLUE, "plan"), ("算例验证 & 文档整理", date(2026, 7, 8), date(2026, 7, 18), BLUE, "plan"), (".em 接口适配(基础)", date(2026, 7, 10), date(2026, 7, 25), BLUE, "plan"), ("阶段5 PML/Port(可选)", date(2026, 7, 20), date(2026, 8, 10), GRAY, "stretch"), ("阶段6 本征模式/二阶(可选)", date(2026, 8, 1), date(2026, 8, 31), GRAY, "stretch"), ] MILESTONES = [ (date(2026, 6, 25), "阶段1–3\n验收"), (date(2026, 7, 18), "MVP\n内测"), (date(2026, 7, 31), "7月下旬\n交付"), ] def main() -> None: fig, ax = plt.subplots(figsize=(14, 6.2), facecolor=WHITE) y = len(ITEMS) for i, (label, start, end, color, status) in enumerate(ITEMS): yy = y - i width = (end - start).days + 1 hatch = "" if status != "stretch" else "///" alpha = 1.0 if status != "stretch" else 0.55 ax.barh( yy, width, left=mdates.date2num(start), height=0.55, color=color, edgecolor=DARK, linewidth=0.8, hatch=hatch, alpha=alpha, ) mid = start + timedelta(days=width // 2) ax.text( mdates.date2num(mid), yy, label, ha="center", va="center", fontsize=9.5, color=WHITE if status in ("done", "doing") else DARK, fontweight="bold" if status == "doing" else "normal", ) for ms_date, ms_text in MILESTONES: x = mdates.date2num(ms_date) ax.axvline(x, color="#C0392B", ls="--", lw=1.2, alpha=0.75, zorder=0) ax.text(x, y + 0.55, ms_text, ha="center", va="bottom", fontsize=8.5, color="#C0392B", fontweight="bold") ax.set_yticks([]) ax.set_ylim(0.3, y + 1.2) ax.set_xlim(mdates.date2num(date(2026, 5, 28)), mdates.date2num(date(2026, 9, 5))) ax.xaxis.set_major_locator(mdates.WeekdayLocator(byweekday=mdates.MO, interval=1)) ax.xaxis.set_major_formatter(mdates.DateFormatter("%m/%d")) ax.xaxis.set_minor_locator(mdates.DayLocator(interval=1)) ax.grid(axis="x", alpha=0.25, ls="--") ax.spines["top"].set_visible(False) ax.spines["right"].set_visible(False) ax.spines["left"].set_visible(False) ax.set_title( "OpticsFEM 3D 扩展 — 时间节点规划(目标:2026年7月下旬交付)", fontsize=14, fontweight="bold", color=DARK, pad=14, ) legend_items = [ mpatches.Patch(facecolor=GREEN, edgecolor=DARK, label="已完成"), mpatches.Patch(facecolor=ORANGE, edgecolor=DARK, label="进行中"), mpatches.Patch(facecolor=BLUE, edgecolor=DARK, label="7月计划"), mpatches.Patch(facecolor=GRAY, edgecolor=DARK, hatch="///", alpha=0.55, label="8月及以后(扩展)"), ] ax.legend(handles=legend_items, loc="lower right", fontsize=9, framealpha=0.95) out = OUT / "timeline_july2026.png" fig.savefig(out, dpi=180, bbox_inches="tight", facecolor=WHITE) plt.close(fig) print(f"Saved {out}") if __name__ == "__main__": main()