c15_patchwork 复合主图 (Composite) Nature Cell Lancet

Multi-Panel Patchwork Composite Figure

多面板复合论文主图 (Patchwork Composite Figure),整合分布、相关性与时间趋势,具备标准 (A)(B)(C) 序号标签与统一期刊样式。

🔬 矢量预览 (Vector Preview) 600 DPI Ready
Multi-Panel Patchwork Composite Figure

📊 统计学特性 (Statistical Features)

  • 多模态分析联合呈现
  • 子图坐标系网格对齐
  • 统一全局色彩规范
  • 共享图例与独立坐标轴

🛡️ QC 质检要点 (QC Highlights)

  • 子图序号 (A), (B), (C) 醒目统一
  • 子图间距 (Wspace/Hspace) 严格对齐
  • 总宽度符合期刊单栏/双栏标准
  • 主标题与子标题层级分明
🎨 顶刊色彩提取器 (Palette Extractor)Nature

Crisp sans-serif typography, clean borderless spines, high-contrast palette with soft muted secondary accents.

🛡️ 无障碍评分:AAA (Deuteranopia & Protanopia Compliant)

📋 Data Contract 数据契约规范 Strict Schema

输入数据必须完全符合下列列名与数据类型规范,方可通过自动化数据前检 (Pre-flight Validation)。

📥 下载规范示例 CSV 数据
字段名称 (Column) 数据类型 (Type) 约束性 语义说明与值域约束
panel categorical 必需 (Required) Target subpanel identifier (e.g. A, B, C)
category categorical 必需 (Required) Experimental condition or series identifier
x_val continuous 必需 (Required) X-axis value for subpanel
y_val continuous 必需 (Required) Y-axis value for subpanel
group categorical 可选 (Optional) Secondary group tag
组内最小样本量: n ≥ 3
最大允许缺失率: 5%

🐍 独立可复现 Python 绘图源码 Stand-alone Script

完全可复现的 Python 脚本,支持 CLI 参数 `--data`, `--style`, `--output-dir` 与模块化 `render()` 调用。

#!/usr/bin/env python3
"""FigureCraft Chart Engine: Multi-Panel Patchwork Composite Figure (c15_patchwork)."""

import os
import sys
import argparse
from pathlib import Path
from typing import List, Dict, Optional
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from scipy import stats

try:
    from engines.styles import apply_style, get_palette
except ImportError:
    sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
    from engines.styles import apply_style, get_palette

CHART_ID = "c15_patchwork"


def add_panel_letter(ax, letter: str):
    """Add bold publication subpanel letter (a, b, c) in top-left corner."""
    ax.text(
        -0.12,
        1.08,
        letter,
        transform=ax.transAxes,
        fontsize=11.0,
        fontweight="bold",
        va="top",
        ha="left",
        color="#111827",
    )


def render(
    data_path: Optional[str] = None,
    style: str = "nature",
    output_dir: str = "output",
    formats: Optional[List[str]] = None,
    dpi: int = 600,
) -> Dict[str, str]:
    if formats is None:
        formats = ["svg", "pdf", "png", "tiff"]
    if data_path is None:
        data_path = str(Path(__file__).resolve().parents[1] / "data" / f"{CHART_ID}.csv")

    apply_style(style)
    palette = get_palette(style)
    df = pd.read_csv(data_path)

    fig = plt.figure(figsize=(7.5, 6.2))
    gs = fig.add_gridspec(nrows=2, ncols=2, height_ratios=[1.0, 1.2], hspace=0.36, wspace=0.30)

    # Panel A: Summary Bar / Point Comparison (Top-Left)
    ax_a = fig.add_subplot(gs[0, 0])
    df_a = df[df["panel"] == "A"]
    groups_a = list(df_a["category"].unique())
    for i, grp in enumerate(groups_a):
        vals = df_a[df_a["category"] == grp]["y_val"].values
        mean_v = np.mean(vals)
        sem_v = stats.sem(vals) if len(vals) > 1 else 0.0
        color = palette[i % len(palette)]
        ax_a.bar(i, mean_v, yerr=sem_v, color=color, alpha=0.65, edgecolor=color, width=0.5, capsize=3.0)
        np.random.seed(42 + i)
        jitter = np.random.uniform(-0.12, 0.12, len(vals))
        ax_a.scatter(i + jitter, vals, color=color, s=20, edgecolors="#FFFFFF", lw=0.4, zorder=3)
    ax_a.set_xticks(range(len(groups_a)))
    ax_a.set_xticklabels([g.replace("_", " ") for g in groups_a], fontsize=7.5)
    ax_a.set_ylabel("Response Signal (a.u.)", fontsize=7.5)
    ax_a.set_title("Target Expression", fontsize=8.5, pad=6)
    add_panel_letter(ax_a, "a")

    # Panel B: Correlation / Multi-Variable Fit (Top-Right)
    ax_b = fig.add_subplot(gs[0, 1])
    df_b = df[df["panel"] == "B"]
    cats_b = list(df_b["category"].unique())
    for i, cat in enumerate(cats_b):
        sub_b = df_b[df_b["category"] == cat]
        x_b = sub_b["x_val"].values
        y_b = sub_b["y_val"].values
        color = palette[(i + 2) % len(palette)]
        ax_b.scatter(x_b, y_b, color=color, label=cat.replace("_", " "), s=24, alpha=0.8, edgecolors="#FFFFFF", lw=0.4)
        if len(x_b) >= 2:
            sl, inter, _, _, _ = stats.linregress(x_b, y_b)
            x_seq = np.linspace(x_b.min(), x_b.max(), 50)
            ax_b.plot(x_seq, sl * x_seq + inter, color=color, lw=1.2, linestyle="--")
    ax_b.set_xlabel("Biomarker X ($μM$)", fontsize=7.5)
    ax_b.set_ylabel("Activity Metric ($Y$)", fontsize=7.5)
    ax_b.set_title("Dose-Dependent Correlation", fontsize=8.5, pad=6)
    ax_b.legend(loc="upper right", frameon=False, fontsize=6.5)
    add_panel_letter(ax_b, "b")

    # Panel C: Longitudinal Progression Curve (Bottom Span)
    ax_c = fig.add_subplot(gs[1, :])
    df_c = df[df["panel"] == "C"]
    groups_c = list(df_c["group"].unique())
    for i, grp in enumerate(groups_c):
        sub_c = df_c[df_c["group"] == grp].sort_values("x_val")
        x_c = sub_c["x_val"].values
        y_c = sub_c["y_val"].values
        color = palette[(i + 4) % len(palette)]
        ax_c.plot(x_c, y_c, marker="o" if i == 0 else "s", color=color, lw=1.8, label=grp.replace("_", " "), markersize=4.5)
        # Add 95% CI synthetic band
        ci_w = 2.5
        ax_c.fill_between(x_c, y_c - ci_w, y_c + ci_w, color=color, alpha=0.15)
    ax_c.set_xlabel("Time Progression (Hours)", fontsize=7.5)
    ax_c.set_ylabel("Efficacy Score (%)", fontsize=7.5)
    ax_c.set_title("Longitudinal Trajectory Dynamics Across Experimental Cohorts", fontsize=8.5, pad=6)
    ax_c.legend(loc="lower right", frameon=False, fontsize=7.0)
    add_panel_letter(ax_c, "c")

    fig.suptitle("Main Manuscript Multi-Panel Composite Figure", fontsize=10.0, fontweight="bold", y=0.99)

    os.makedirs(output_dir, exist_ok=True)
    generated_files = {}
    for fmt in formats:
        out_path = os.path.join(output_dir, f"{CHART_ID}_{style}.{fmt}")
        fig.savefig(out_path, format=fmt, dpi=dpi, bbox_inches="tight")
        generated_files[fmt] = str(Path(out_path).resolve())

    plt.close(fig)
    return generated_files


if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Render Multi-Panel Patchwork Composite Figure")
    parser.add_argument("--data", type=str, default=None, help="Path to input CSV dataset")
    parser.add_argument("--style", type=str, default="nature", choices=["nature", "cell", "lancet", "ieee", "economist"], help="Journal style pack")
    parser.add_argument("--output-dir", type=str, default="output", help="Output directory")
    parser.add_argument("--formats", type=str, default="svg,pdf,png,tiff", help="Comma-separated export formats")
    parser.add_argument("--dpi", type=int, default=600, help="Raster DPI")
    args = parser.parse_args()

    fmt_list = [f.strip() for f in args.formats.split(",") if f.strip()]
    results = render(args.data, style=args.style, output_dir=args.output_dir, formats=fmt_list, dpi=args.dpi)
    for fmt, path in results.items():
        print(f"Generated {fmt.upper()}: {path}")