# -*- coding: utf-8 -*-
"""复现: Nature Communications 2026, doi:10.1038/s41467-026-77288-4, Fig. 4a
唾液 sfRNA 抑制 IFN 应答: Huh7 细胞 IFN-β 相对基因表达量(24h/48h, L/M/H 唾液 sfRNA 浓度组)

数据: 真实数据, 来自论文 Source Data 文件 "Source data for graph.xlsx" 的 'Fig. 4a' sheet
      (IFN-β relative expression in Huh7 cells infected with saliva; 每组 2 个生物学重复)。
      图中水平虚线为 mock 感染组值, Source Data 未给出数值, 按原图刻度估计约为 1.0(代码中 mock_value=1.0 明确标注为估计值)。
显著性字母 a/a/a/ab/b/c 为原文 Fig. 4a 标注的 Fisher LSD 事后检验结果分组(按原图转录, 非本脚本计算)。
沙箱实测: python3 reproduce_fig.py -> reproduce_py.png, 无报错。
"""
import os
import numpy as np
import pandas as pd
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties
import matplotlib.patheffects as pe

BOLD = [pe.withStroke(linewidth=2.2, foreground="black")]  # 单字重 TTF 的仿粗体

HERE = os.path.dirname(os.path.abspath(__file__))
FONT = FontProperties(fname=os.path.join(HERE, "NotoSansSC-Regular.ttf"))
plt.rcParams["font.family"] = FONT.get_name()
plt.rcParams["axes.unicode_minus"] = False

# ---------------- 数据 ----------------
df = pd.read_csv(os.path.join(HERE, "data.csv"))
LEVELS = ["L", "M", "H"]
TIMES = ["24h", "48h"]
COLORS = {"L": "#EECCF9", "M": "#B856D7", "H": "#76069A"}  # 原图逐像素采样
LETTERS = {("24h", "L"): "a", ("24h", "M"): "a", ("24h", "H"): "a",
           ("48h", "L"): "ab", ("48h", "M"): "b", ("48h", "H"): "c"}
MOCK_VALUE = 1.0  # 估计值: 原图虚线位置经刻度校准约 1.0, Source Data 未提供

stats = {}
for t in TIMES:
    for lv in LEVELS:
        v = df[(df.timepoint == t) & (df.sfRNA_level == lv)]["value"].to_numpy()
        stats[(t, lv)] = {"mean": v.mean(), "sem": v.std(ddof=1) / np.sqrt(len(v)), "vals": v}

# ---------------- 作图 ----------------
X24 = np.array([0.0, 1.0, 2.0])
X48 = np.array([3.2, 4.2, 5.2])
XPOS = {"24h": X24, "48h": X48}
W = 0.72

fig, ax = plt.subplots(figsize=(6.4, 6.0), dpi=300)
rng = np.random.default_rng(42)

for t in TIMES:
    for i, lv in enumerate(LEVELS):
        x = XPOS[t][i]
        s = stats[(t, lv)]
        hatch = "////" if t == "48h" else None  # 原图 48h 柱为同色系 + 黑色斜线
        ax.bar(x, s["mean"], width=W, color=COLORS[lv], edgecolor="black",
               linewidth=1.4, hatch=hatch, zorder=2)
        ax.errorbar(x, s["mean"], yerr=s["sem"], fmt="none", ecolor="black",
                    elinewidth=1.6, capsize=5, capthick=1.6, zorder=4)
        # 生物学重复散点(原图为空心圆)
        jx = rng.uniform(-0.14, 0.14, size=len(s["vals"]))
        ax.scatter(np.full_like(s["vals"], x) + jx, s["vals"], s=42,
                   facecolor="white", edgecolor="black", linewidth=1.2, zorder=5)
        # Fisher LSD 显著性字母(按原图转录)
        ax.text(x, s["mean"] + s["sem"] + 0.55, LETTERS[(t, lv)], ha="center",
                va="bottom", fontsize=13, fontproperties=FONT)

# mock 感染组虚线
ax.hlines(MOCK_VALUE, -0.6, 5.9, colors="black", linestyles=(0, (6, 3)),
          linewidth=1.6, zorder=3)

ax.set_xlim(-0.7, 5.9)
ax.set_ylim(0, 16.2)
ax.set_xticks(list(X24) + list(X48))
ax.set_xticklabels(["L", "M", "H", "L", "M", "H"], fontsize=13, fontproperties=FONT)
ax.set_yticks([0, 5, 10, 15])
ax.set_yticklabels(["0", "5", "10", "15"], fontsize=13, fontproperties=FONT)
ax.set_ylabel("Relative gene\nexpression", fontsize=15, fontproperties=FONT, labelpad=8)

# 分组标注: 24 / 48 hpi
ax.text(1.0, -1.45, "24", ha="center", va="top", fontsize=13, fontproperties=FONT,
        clip_on=False)
ax.text(4.2, -1.45, "48", ha="center", va="top", fontsize=13, fontproperties=FONT,
        clip_on=False)
ax.text(1.0, -2.75, "hpi", ha="center", va="top", fontsize=13, fontproperties=FONT,
        clip_on=False)
ax.text(4.2, -2.75, "hpi", ha="center", va="top", fontsize=13, fontproperties=FONT,
        clip_on=False)

# 标题区(仿原图): 面板标号 a / 细胞系 Huh7 / 基因 IFN-β
ax.text(-0.62, 17.3, "a", fontsize=24, fontproperties=FONT,
        path_effects=BOLD, transform=ax.transData, clip_on=False)
ax.text(1.05, 18.0, "Huh7", fontsize=21, fontproperties=FONT, path_effects=BOLD)
ax.text(0.55, 13.9, "IFN-β", fontsize=18, fontproperties=FONT)

ax.spines["top"].set_visible(False)
ax.spines["right"].set_visible(False)
ax.spines["left"].set_linewidth(1.6)
ax.spines["bottom"].set_linewidth(1.6)
ax.tick_params(width=1.6, length=6, direction="out")

fig.tight_layout()
fig.savefig(os.path.join(HERE, "reproduce_py.png"), dpi=300)
print("saved reproduce_py.png")
