scientific-figure-assembly

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Scientific Figure Assembly (R-based)

基于R的科学图表组装

Create publication-ready multi-panel figures using R packages (patchwork, cowplot) with professional panel labels (A, B, C, D) at 300 DPI resolution.
⚠️ IMPORTANT: This workflow uses R for figure assembly. For meta-analysis projects, all figures should be generated and assembled in R.
使用R包(patchwork、cowplot)创建带有专业面板标签(A、B、C、D)、分辨率达300 DPI的可直接用于出版的多面板图表。
⚠️ 重要提示:此工作流使用R进行图表组装。对于元分析项目,所有图表都应在R中生成并组装。

When to Use

适用场景

  • Combining multiple plots into a single multi-panel figure for publication
  • Adding panel labels (A, B, C) to existing figures
  • Ensuring figures meet journal requirements (300 DPI minimum)
  • Creating consistent figure layouts for manuscripts
  • Preparing figures for Nature, Science, Cell, JAMA, Lancet submissions
  • 将多个绘图组合成单个多面板图表用于发表
  • 为现有图表添加面板标签(A、B、C)
  • 确保图表满足期刊要求(最低300 DPI)
  • 为手稿创建布局一致的图表
  • 为《Nature》《Science》《Cell》《JAMA》《Lancet》等期刊投稿准备图表

Quick Start

快速开始

Tell me:
  1. Plot objects: R plot objects (ggplot, forest plots, etc.) OR paths to PNG/JPG files
  2. Layout: Vertical (stacked), horizontal (side-by-side), or grid (2x2, 2x3, etc.)
  3. Output name: What to call the final figure
  4. Labels: Which panel labels to use (default: A, B, C, D...)
I'll create an R script using patchwork or cowplot to assemble the figure with proper spacing and labels.
请告知我:
  1. 绘图对象:R绘图对象(ggplot、森林图等)或PNG/JPG文件路径
  2. 布局:垂直(堆叠)、水平(并排)或网格(2x2、2x3等)
  3. 输出名称:最终图表的命名
  4. 标签:要使用的面板标签(默认:A、B、C、D...)
我将使用patchwork或cowplot创建R脚本,以合适的间距和标签组装图表。

R Package Approach (Recommended)

推荐的R包方法

Method 1: patchwork (For ggplot2 objects)

方法1:patchwork(适用于ggplot2对象)

The simplest and most powerful method for combining ggplot2 objects:
r
library(ggplot2)
library(patchwork)
这是组合ggplot2对象最简单且功能最强大的方法:
r
library(ggplot2)
library(patchwork)

Create or load individual plots

Create or load individual plots

p1 <- ggplot(data1, aes(x, y)) + geom_point() + ggtitle("A. First Panel") p2 <- ggplot(data2, aes(x, y)) + geom_line() + ggtitle("B. Second Panel") p3 <- ggplot(data3, aes(x, y)) + geom_bar(stat="identity") + ggtitle("C. Third Panel")
p1 <- ggplot(data1, aes(x, y)) + geom_point() + ggtitle("A. First Panel") p2 <- ggplot(data2, aes(x, y)) + geom_line() + ggtitle("B. Second Panel") p3 <- ggplot(data3, aes(x, y)) + geom_bar(stat="identity") + ggtitle("C. Third Panel")

Combine vertically

Combine vertically

combined <- p1 / p2 / p3
combined <- p1 / p2 / p3

Or combine horizontally

Or combine horizontally

combined <- p1 | p2 | p3
combined <- p1 | p2 | p3

Or grid layout (2 columns)

Or grid layout (2 columns)

combined <- (p1 | p2) / p3
combined <- (p1 | p2) / p3

Export at 300 DPI

Export at 300 DPI

ggsave("figures/figure1_combined.png", plot = combined, width = 10, height = 12, dpi = 300)
undefined
ggsave("figures/figure1_combined.png", plot = combined, width = 10, height = 12, dpi = 300)
undefined

Method 2: cowplot (For any R plots)

方法2:cowplot(适用于所有R绘图)

More flexible, works with base R plots and ggplot2:
r
library(ggplot2)
library(cowplot)
灵活性更高,可用于基础R绘图和ggplot2:
r
library(ggplot2)
library(cowplot)

Create individual plots

Create individual plots

p1 <- ggplot(data1, aes(x, y)) + geom_point() p2 <- ggplot(data2, aes(x, y)) + geom_line() p3 <- ggplot(data3, aes(x, y)) + geom_bar(stat="identity")
p1 <- ggplot(data1, aes(x, y)) + geom_point() p2 <- ggplot(data2, aes(x, y)) + geom_line() p3 <- ggplot(data3, aes(x, y)) + geom_bar(stat="identity")

Combine with automatic panel labels

Combine with automatic panel labels

combined <- plot_grid( p1, p2, p3, labels = c("A", "B", "C"), label_size = 18, ncol = 1, # Vertical stack rel_heights = c(1, 1, 1) # Equal heights )
combined <- plot_grid( p1, p2, p3, labels = c("A", "B", "C"), label_size = 18, ncol = 1, # Vertical stack rel_heights = c(1, 1, 1) # Equal heights )

Export

Export

ggsave("figures/figure1_combined.png", plot = combined, width = 10, height = 12, dpi = 300)
undefined
ggsave("figures/figure1_combined.png", plot = combined, width = 10, height = 12, dpi = 300)
undefined

Legacy Python Script Template (Not Recommended)

遗留Python脚本模板(不推荐)

⚠️ For meta-analysis projects, use R methods above instead.
If you absolutely need Python for existing PNG files:
python
#!/usr/bin/env python3
"""Legacy: Assemble multi-panel scientific figure from PNG files."""

from PIL import Image, ImageDraw, ImageFont
from pathlib import Path

def add_panel_label(img, label, position='top-left',
                   font_size=80, offset=(40, 40),
                   bg_color='white', text_color='black',
                   border=True):
    """
    Add panel label (A, B, C) to image.

    Args:
        img: PIL Image object
        label: Label text (e.g., 'A', 'B', 'C')
        position: 'top-left', 'top-right', 'bottom-left', 'bottom-right'
        font_size: Font size in pixels (80 works well for 3000px wide images)
        offset: (x, y) offset from corner in pixels
        bg_color: Background color for label box
        text_color: Label text color
        border: Whether to draw border around label box
    """
    draw = ImageDraw.Draw(img)

    # Try system fonts (macOS, then Linux)
    try:
        font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
    except:
        try:
            font = ImageFont.truetype(
                "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size
            )
        except:
            font = ImageFont.load_default()
            print(f"Warning: Using default font for label {label}")

    # Calculate label position
    x, y = offset
    if 'right' in position:
        bbox = draw.textbbox((0, 0), label, font=font)
        text_width = bbox[2] - bbox[0]
        x = img.width - text_width - offset[0]
    if 'bottom' in position:
        bbox = draw.textbbox((0, 0), label, font=font)
        text_height = bbox[3] - bbox[1]
        y = img.height - text_height - offset[1]

    # Draw background box
    bbox = draw.textbbox((x, y), label, font=font)
    padding = 10
    draw.rectangle(
        [bbox[0] - padding, bbox[1] - padding,
         bbox[2] + padding, bbox[3] + padding],
        fill=bg_color,
        outline='black' if border else None,
        width=2 if border else 0
    )

    # Draw text
    draw.text((x, y), label, fill=text_color, font=font)

    return img


def assemble_vertical(input_files, output_file, labels=None,
                     spacing=40, dpi=300):
    """
    Stack images vertically with panel labels.

    Args:
        input_files: List of paths to input images
        output_file: Path for output image
        labels: List of labels (default: A, B, C, ...)
        spacing: Vertical spacing between panels in pixels
        dpi: Output resolution
    """
    if labels is None:
        labels = [chr(65 + i) for i in range(len(input_files))]  # A, B, C, ...

    # Load all images
    images = [Image.open(f) for f in input_files]

    # Add labels
    labeled = [add_panel_label(img, label)
               for img, label in zip(images, labels)]

    # Calculate dimensions
    max_width = max(img.width for img in labeled)
    total_height = sum(img.height for img in labeled) + spacing * (len(labeled) - 1)

    # Create combined image
    combined = Image.new('RGB', (max_width, total_height), 'white')

    # Paste images
    y_offset = 0
    for img in labeled:
        combined.paste(img, (0, y_offset))
        y_offset += img.height + spacing

    # Save with specified DPI
    combined.save(output_file, dpi=(dpi, dpi))
    print(f"✅ Created {output_file}")
    print(f"   Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")

    return output_file


def assemble_horizontal(input_files, output_file, labels=None,
                       spacing=40, dpi=300):
    """Stack images horizontally with panel labels."""
    if labels is None:
        labels = [chr(65 + i) for i in range(len(input_files))]

    images = [Image.open(f) for f in input_files]
    labeled = [add_panel_label(img, label)
               for img, label in zip(images, labels)]

    max_height = max(img.height for img in labeled)
    total_width = sum(img.width for img in labeled) + spacing * (len(labeled) - 1)

    combined = Image.new('RGB', (total_width, max_height), 'white')

    x_offset = 0
    for img in labeled:
        combined.paste(img, (x_offset, 0))
        x_offset += img.width + spacing

    combined.save(output_file, dpi=(dpi, dpi))
    print(f"✅ Created {output_file}")
    print(f"   Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")

    return output_file


def assemble_grid(input_files, output_file, rows, cols,
                 labels=None, spacing=40, dpi=300):
    """
    Arrange images in a grid with panel labels.

    Args:
        rows: Number of rows
        cols: Number of columns
        Other args same as assemble_vertical
    """
    if labels is None:
        labels = [chr(65 + i) for i in range(len(input_files))]

    images = [Image.open(f) for f in input_files]
    labeled = [add_panel_label(img, label)
               for img, label in zip(images, labels)]

    # Calculate cell dimensions (use max from each row/col)
    cell_width = max(img.width for img in labeled)
    cell_height = max(img.height for img in labeled)

    # Total dimensions
    total_width = cell_width * cols + spacing * (cols - 1)
    total_height = cell_height * rows + spacing * (rows - 1)

    combined = Image.new('RGB', (total_width, total_height), 'white')

    # Place images
    for idx, img in enumerate(labeled):
        if idx >= rows * cols:
            break
        row = idx // cols
        col = idx % cols
        x = col * (cell_width + spacing)
        y = row * (cell_height + spacing)
        combined.paste(img, (x, y))

    combined.save(output_file, dpi=(dpi, dpi))
    print(f"✅ Created {output_file}")
    print(f"   Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")

    return output_file


if __name__ == '__main__':
    import sys

    # Example usage
    if len(sys.argv) < 3:
        print("Usage: python assemble_figures.py <output> <layout> <input1> <input2> ...")
        print("  layout: vertical, horizontal, or grid:RxC (e.g., grid:2x2)")
        sys.exit(1)

    output = sys.argv[1]
    layout = sys.argv[2]
    inputs = sys.argv[3:]

    if layout == 'vertical':
        assemble_vertical(inputs, output)
    elif layout == 'horizontal':
        assemble_horizontal(inputs, output)
    elif layout.startswith('grid:'):
        rows, cols = map(int, layout.split(':')[1].split('x'))
        assemble_grid(inputs, output, rows, cols)
    else:
        print(f"Unknown layout: {layout}")
        sys.exit(1)
⚠️ 对于元分析项目,请使用上述R方法。
如果因现有PNG文件确实需要使用Python:
python
#!/usr/bin/env python3
"""Legacy: Assemble multi-panel scientific figure from PNG files."""

from PIL import Image, ImageDraw, ImageFont
from pathlib import Path

def add_panel_label(img, label, position='top-left',
                   font_size=80, offset=(40, 40),
                   bg_color='white', text_color='black',
                   border=True):
    """
    Add panel label (A, B, C) to image.

    Args:
        img: PIL Image object
        label: Label text (e.g., 'A', 'B', 'C')
        position: 'top-left', 'top-right', 'bottom-left', 'bottom-right'
        font_size: Font size in pixels (80 works well for 3000px wide images)
        offset: (x, y) offset from corner in pixels
        bg_color: Background color for label box
        text_color: Label text color
        border: Whether to draw border around label box
    """
    draw = ImageDraw.Draw(img)

    # Try system fonts (macOS, then Linux)
    try:
        font = ImageFont.truetype("/System/Library/Fonts/Helvetica.ttc", font_size)
    except:
        try:
            font = ImageFont.truetype(
                "/usr/share/fonts/truetype/dejavu/DejaVuSans-Bold.ttf", font_size
            )
        except:
            font = ImageFont.load_default()
            print(f"Warning: Using default font for label {label}")

    # Calculate label position
    x, y = offset
    if 'right' in position:
        bbox = draw.textbbox((0, 0), label, font=font)
        text_width = bbox[2] - bbox[0]
        x = img.width - text_width - offset[0]
    if 'bottom' in position:
        bbox = draw.textbbox((0, 0), label, font=font)
        text_height = bbox[3] - bbox[1]
        y = img.height - text_height - offset[1]

    # Draw background box
    bbox = draw.textbbox((x, y), label, font=font)
    padding = 10
    draw.rectangle(
        [bbox[0] - padding, bbox[1] - padding,
         bbox[2] + padding, bbox[3] + padding],
        fill=bg_color,
        outline='black' if border else None,
        width=2 if border else 0
    )

    # Draw text
    draw.text((x, y), label, fill=text_color, font=font)

    return img


def assemble_vertical(input_files, output_file, labels=None,
                     spacing=40, dpi=300):
    """
    Stack images vertically with panel labels.

    Args:
        input_files: List of paths to input images
        output_file: Path for output image
        labels: List of labels (default: A, B, C, ...)
        spacing: Vertical spacing between panels in pixels
        dpi: Output resolution
    """
    if labels is None:
        labels = [chr(65 + i) for i in range(len(input_files))]  # A, B, C, ...

    # Load all images
    images = [Image.open(f) for f in input_files]

    # Add labels
    labeled = [add_panel_label(img, label)
               for img, label in zip(images, labels)]

    # Calculate dimensions
    max_width = max(img.width for img in labeled)
    total_height = sum(img.height for img in labeled) + spacing * (len(labeled) - 1)

    # Create combined image
    combined = Image.new('RGB', (max_width, total_height), 'white')

    # Paste images
    y_offset = 0
    for img in labeled:
        combined.paste(img, (0, y_offset))
        y_offset += img.height + spacing

    # Save with specified DPI
    combined.save(output_file, dpi=(dpi, dpi))
    print(f"✅ Created {output_file}")
    print(f"   Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")

    return output_file


def assemble_horizontal(input_files, output_file, labels=None,
                       spacing=40, dpi=300):
    """Stack images horizontally with panel labels."""
    if labels is None:
        labels = [chr(65 + i) for i in range(len(input_files))]

    images = [Image.open(f) for f in input_files]
    labeled = [add_panel_label(img, label)
               for img, label in zip(images, labels)]

    max_height = max(img.height for img in labeled)
    total_width = sum(img.width for img in labeled) + spacing * (len(labeled) - 1)

    combined = Image.new('RGB', (total_width, max_height), 'white')

    x_offset = 0
    for img in labeled:
        combined.paste(img, (x_offset, 0))
        x_offset += img.width + spacing

    combined.save(output_file, dpi=(dpi, dpi))
    print(f"✅ Created {output_file}")
    print(f"   Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")

    return output_file


def assemble_grid(input_files, output_file, rows, cols,
                 labels=None, spacing=40, dpi=300):
    """
    Arrange images in a grid with panel labels.

    Args:
        rows: Number of rows
        cols: Number of columns
        Other args same as assemble_vertical
    """
    if labels is None:
        labels = [chr(65 + i) for i in range(len(input_files))]

    images = [Image.open(f) for f in input_files]
    labeled = [add_panel_label(img, label)
               for img, label in zip(images, labels)]

    # Calculate cell dimensions (use max from each row/col)
    cell_width = max(img.width for img in labeled)
    cell_height = max(img.height for img in labeled)

    # Total dimensions
    total_width = cell_width * cols + spacing * (cols - 1)
    total_height = cell_height * rows + spacing * (rows - 1)

    combined = Image.new('RGB', (total_width, total_height), 'white')

    # Place images
    for idx, img in enumerate(labeled):
        if idx >= rows * cols:
            break
        row = idx // cols
        col = idx % cols
        x = col * (cell_width + spacing)
        y = row * (cell_height + spacing)
        combined.paste(img, (x, y))

    combined.save(output_file, dpi=(dpi, dpi))
    print(f"✅ Created {output_file}")
    print(f"   Dimensions: {combined.width}×{combined.height} px at {dpi} DPI")

    return output_file


if __name__ == '__main__':
    import sys

    # Example usage
    if len(sys.argv) < 3:
        print("Usage: python assemble_figures.py <output> <layout> <input1> <input2> ...")
        print("  layout: vertical, horizontal, or grid:RxC (e.g., grid:2x2)")
        sys.exit(1)

    output = sys.argv[1]
    layout = sys.argv[2]
    inputs = sys.argv[3:]

    if layout == 'vertical':
        assemble_vertical(inputs, output)
    elif layout == 'horizontal':
        assemble_horizontal(inputs, output)
    elif layout.startswith('grid:'):
        rows, cols = map(int, layout.split(':')[1].split('x'))
        assemble_grid(inputs, output, rows, cols)
    else:
        print(f"Unknown layout: {layout}")
        sys.exit(1)

Common Layouts

常见布局

Vertical (Most Common)

垂直布局(最常用)

Stack plots on top of each other - good for showing progression or related outcomes.
Example: Three forest plots (pCR, EFS, OS) stacked vertically
  • Panel A: pCR forest plot
  • Panel B: EFS forest plot
  • Panel C: OS forest plot
将绘图上下堆叠 - 适合展示过程或相关结果。
示例:三个森林图(pCR、EFS、OS)垂直堆叠
  • 面板A:pCR森林图
  • 面板B:EFS森林图
  • 面板C:OS森林图

Horizontal

水平布局

Place plots side-by-side - good for comparisons.
Example: Two funnel plots showing publication bias
  • Panel A: pCR funnel plot
  • Panel B: EFS funnel plot
将绘图并排摆放 - 适合对比展示。
示例:两个展示发表偏倚的漏斗图
  • 面板A:pCR漏斗图
  • 面板B:EFS漏斗图

Grid (2x2, 2x3, etc.)

网格布局(2x2、2x3等)

Arrange in rows and columns - good for systematic comparisons.
Example: 2x2 grid of subgroup analyses
  • Panel A: Age subgroup
  • Panel B: Sex subgroup
  • Panel C: Stage subgroup
  • Panel D: Histology subgroup
按行和列排列 - 适合系统性对比。
示例:2x2网格的亚组分析
  • 面板A:年龄亚组
  • 面板B:性别亚组
  • 面板C:分期亚组
  • 面板D:组织学亚组

R Workflow (Recommended)

推荐的R工作流

Complete Example: Meta-Analysis Forest Plots

完整示例:元分析森林图

r
#!/usr/bin/env Rscript
r
#!/usr/bin/env Rscript

assemble_forest_plots.R

assemble_forest_plots.R

Combine multiple forest plots into a single figure

Combine multiple forest plots into a single figure

library(meta) library(metafor) library(patchwork)
library(meta) library(metafor) library(patchwork)

Set working directory

Set working directory

setwd("/Users/htlin/meta-pipe/06_analysis")
setwd("/Users/htlin/meta-pipe/06_analysis")

Load extraction data

Load extraction data

data <- read.csv("../05_extraction/extraction.csv")
data <- read.csv("../05_extraction/extraction.csv")

--- Create individual forest plots ---

--- Create individual forest plots ---

Plot 1: Pathologic complete response

Plot 1: Pathologic complete response

res_pcr <- metabin( event.e = events_pcr_ici, n.e = total_ici, event.c = events_pcr_control, n.c = total_control, data = data, studlab = study_id, sm = "RR", method = "MH" )
res_pcr <- metabin( event.e = events_pcr_ici, n.e = total_ici, event.c = events_pcr_control, n.c = total_control, data = data, studlab = study_id, sm = "RR", method = "MH" )

Save as ggplot-compatible object

Save as ggplot-compatible object

p1 <- forest(res_pcr, layout = "RevMan5") + ggtitle("A. Pathologic Complete Response")
p1 <- forest(res_pcr, layout = "RevMan5") + ggtitle("A. Pathologic Complete Response")

Plot 2: Event-free survival

Plot 2: Event-free survival

res_efs <- metagen( TE = log_hr_efs, seTE = se_log_hr_efs, data = data, studlab = study_id, sm = "HR" )
p2 <- forest(res_efs) + ggtitle("B. Event-Free Survival")
res_efs <- metagen( TE = log_hr_efs, seTE = se_log_hr_efs, data = data, studlab = study_id, sm = "HR" )
p2 <- forest(res_efs) + ggtitle("B. Event-Free Survival")

Plot 3: Overall survival

Plot 3: Overall survival

res_os <- metagen( TE = log_hr_os, seTE = se_log_hr_os, data = data, studlab = study_id, sm = "HR" )
p3 <- forest(res_os) + ggtitle("C. Overall Survival")
res_os <- metagen( TE = log_hr_os, seTE = se_log_hr_os, data = data, studlab = study_id, sm = "HR" )
p3 <- forest(res_os) + ggtitle("C. Overall Survival")

--- Combine with patchwork ---

--- Combine with patchwork ---

combined <- p1 / p2 / p3 + plot_annotation( title = "Figure 1. Efficacy Outcomes with ICI vs Control", theme = theme(plot.title = element_text(size = 16, face = "bold")) )
combined <- p1 / p2 / p3 + plot_annotation( title = "Figure 1. Efficacy Outcomes with ICI vs Control", theme = theme(plot.title = element_text(size = 16, face = "bold")) )

Export at 300 DPI

Export at 300 DPI

ggsave("../07_manuscript/figures/figure1_efficacy.png", plot = combined, width = 10, height = 14, dpi = 300, bg = "white")
cat("✅ Created figure1_efficacy.png\n") cat(" Dimensions: 3000×4200 px at 300 DPI\n")
undefined
ggsave("../07_manuscript/figures/figure1_efficacy.png", plot = combined, width = 10, height = 14, dpi = 300, bg = "white")
cat("✅ Created figure1_efficacy.png\n") cat(" Dimensions: 3000×4200 px at 300 DPI\n")
undefined

Using cowplot for More Control

使用cowplot实现更多控制

r
library(cowplot)
r
library(cowplot)

Combine with explicit panel labels and alignment

Combine with explicit panel labels and alignment

combined <- plot_grid( p1, p2, p3, labels = c("A", "B", "C"), label_size = 18, label_fontface = "bold", ncol = 1, align = "v", # Vertical alignment axis = "l", # Align left axis rel_heights = c(1, 1, 1) )
combined <- plot_grid( p1, p2, p3, labels = c("A", "B", "C"), label_size = 18, label_fontface = "bold", ncol = 1, align = "v", # Vertical alignment axis = "l", # Align left axis rel_heights = c(1, 1, 1) )

Add overall title

Add overall title

title <- ggdraw() + draw_label( "Figure 1. Efficacy Outcomes with ICI vs Control", fontface = "bold", size = 16, x = 0.5, hjust = 0.5 )
title <- ggdraw() + draw_label( "Figure 1. Efficacy Outcomes with ICI vs Control", fontface = "bold", size = 16, x = 0.5, hjust = 0.5 )

Combine title and plots

Combine title and plots

final <- plot_grid( title, combined, ncol = 1, rel_heights = c(0.1, 1) )
final <- plot_grid( title, combined, ncol = 1, rel_heights = c(0.1, 1) )

Export

Export

ggsave("../07_manuscript/figures/figure1_efficacy.png", plot = final, width = 10, height = 14, dpi = 300, bg = "white")
undefined
ggsave("../07_manuscript/figures/figure1_efficacy.png", plot = final, width = 10, height = 14, dpi = 300, bg = "white")
undefined

Grid Layout (2x2 or 2x3)

网格布局(2x2或2x3)

r
library(patchwork)
r
library(patchwork)

2x2 grid

2x2 grid

combined <- (p1 | p2) / (p3 | p4) + plot_annotation(tag_levels = "A")
combined <- (p1 | p2) / (p3 | p4) + plot_annotation(tag_levels = "A")

2x3 grid

2x3 grid

combined <- (p1 | p2 | p3) / (p4 | p5 | p6) + plot_annotation(tag_levels = "A")
ggsave("figure_grid.png", width = 14, height = 10, dpi = 300)
undefined
combined <- (p1 | p2 | p3) / (p4 | p5 | p6) + plot_annotation(tag_levels = "A")
ggsave("figure_grid.png", width = 14, height = 10, dpi = 300)
undefined

Python Workflow (Legacy - For PNG Files Only)

Python工作流(遗留 - 仅适用于PNG文件)

⚠️ Only use if you have existing PNG files and cannot regenerate in R.
⚠️ 仅当你有现有PNG文件且无法在R中重新生成时使用。

Step 1: Verify Input Files

步骤1:验证输入文件

bash
undefined
bash
undefined

Check that all files exist and are PNG/JPG

Check that all files exist and are PNG/JPG

ls -lh path/to/plots/*.png
undefined
ls -lh path/to/plots/*.png
undefined

Step 2: Create Assembly Script

步骤2:创建组装脚本

Use the Python template provided in this skill.
使用本技能中提供的Python模板。

Step 3: Run Assembly

步骤3:运行组装

bash
undefined
bash
undefined

Using uv (recommended for dependency management)

使用uv(推荐用于依赖管理)

uv run python assemble_figures.py Figure1_Efficacy.png vertical
forest_plot_pCR.png
forest_plot_EFS.png
forest_plot_OS.png
uv run python assemble_figures.py Figure1_Efficacy.png vertical
forest_plot_pCR.png
forest_plot_EFS.png
forest_plot_OS.png

Or with system Python (requires PIL/Pillow)

或使用系统Python(需要安装PIL/Pillow)

python assemble_figures.py Figure1.png grid:2x2
plot1.png plot2.png plot3.png plot4.png
undefined
python assemble_figures.py Figure1.png grid:2x2
plot1.png plot2.png plot3.png plot4.png
undefined

Step 4: Verify Output

步骤4:验证输出

bash
undefined
bash
undefined

Check dimensions and file size

检查尺寸和文件大小

ls -lh Figure1_Efficacy.png
ls -lh Figure1_Efficacy.png

Verify DPI (should show 300x300)

验证DPI(应显示300x300)

file Figure1_Efficacy.png
undefined
file Figure1_Efficacy.png
undefined

Customization Options

自定义选项

Font Size Adjustment

字体大小调整

For different image sizes:
  • 3000px wide images:
    font_size=80
    (default)
  • 1500px wide images:
    font_size=40
  • 6000px wide images:
    font_size=160
针对不同图像尺寸:
  • 3000px宽图像:
    font_size=80
    (默认)
  • 1500px宽图像:
    font_size=40
  • 6000px宽图像:
    font_size=160

Label Position

标签位置

  • position='top-left'
    (default)
  • position='top-right'
  • position='bottom-left'
  • position='bottom-right'
  • position='top-left'
    (默认)
  • position='top-right'
  • position='bottom-left'
  • position='bottom-right'

Spacing Between Panels

面板间距

  • Default:
    spacing=40
    pixels
  • Tight spacing:
    spacing=20
  • Loose spacing:
    spacing=80
  • 默认:
    spacing=40
    像素
  • 紧凑间距:
    spacing=20
  • 宽松间距:
    spacing=80

Label Style

标签样式

  • White background with black border (default, best visibility)
  • Transparent background:
    bg_color=None, border=False
  • Custom colors:
    bg_color='#f0f0f0', text_color='#333333'
  • 白色背景带黑色边框(默认,可见性最佳)
  • 透明背景:
    bg_color=None, border=False
  • 自定义颜色:
    bg_color='#f0f0f0', text_color='#333333'

Journal Requirements

期刊要求

Nature, Science, Cell

《Nature》《Science》《Cell》

  • Resolution: 300-600 DPI
  • Format: TIFF or high-quality PDF preferred, PNG acceptable
  • Width: 89mm (single column) or 183mm (double column) at final size
  • Font: Arial, Helvetica, or similar sans-serif
  • Labels: Bold, 8-10pt at final size
  • 分辨率:300-600 DPI
  • 格式:优先使用TIFF或高质量PDF,PNG也可接受
  • 宽度:最终尺寸下单栏89mm或双栏183mm
  • 字体:Arial、Helvetica或类似无衬线字体
  • 标签:加粗,最终尺寸下8-10pt

Lancet, JAMA, NEJM

《Lancet》《JAMA》《NEJM》

  • Resolution: 300 DPI minimum
  • Format: TIFF, EPS, or PNG
  • Width: Fit within column width (typically 3-4 inches)
  • Labels: Clear, high contrast
  • Grayscale: Must be readable in B&W
  • 分辨率:最低300 DPI
  • 格式:TIFF、EPS或PNG
  • 宽度:适配栏宽(通常3-4英寸)
  • 标签:清晰、高对比度
  • 灰度:在黑白模式下必须可读

Quality Checklist

质量检查清单

Before submitting:
  • All figures at 300 DPI minimum
  • Panel labels (A, B, C) visible and correctly ordered
  • Labels don't obscure important data
  • All panels aligned properly
  • Spacing consistent between panels
  • File size reasonable (<10 MB for PNG)
  • Figures readable when printed at final journal size
  • Color schemes work in grayscale (if required)
提交前请确认:
  • 所有图表分辨率不低于300 DPI
  • 面板标签(A、B、C)清晰可见且顺序正确
  • 标签未遮挡重要数据
  • 所有面板对齐正确
  • 面板间间距一致
  • 文件大小合理(PNG格式小于10 MB)
  • 图表在期刊最终印刷尺寸下可读
  • 配色方案在灰度模式下可用(如要求)

Common Issues & Solutions

常见问题与解决方案

Problem: Labels too small Solution: Increase
font_size
parameter (try doubling it)
Problem: Labels obscure data Solution: Change
position
to different corner or adjust
offset
Problem: DPI too low Solution: Regenerate input plots at higher resolution first, then reassemble
Problem: Uneven spacing Solution: Crop input images to remove excess white space before assembly
Problem: File too large Solution: Use PNG compression or convert to JPEG (may lose quality)
问题:标签过小 解决方案:增大
font_size
参数(尝试翻倍)
问题:标签遮挡数据 解决方案:将
position
改为其他角落或调整
offset
问题:DPI过低 解决方案:先以更高分辨率重新生成输入绘图,再进行组装
问题:间距不均 解决方案:组装前裁剪输入图像以去除多余空白
问题:文件过大 解决方案:使用PNG压缩或转换为JPEG(可能会损失质量)

Example Use Cases

示例用例

Meta-Analysis Figures (R)

元分析图表(R)

r
undefined
r
undefined

Figure 1: Efficacy outcomes (3 vertical panels)

Figure 1: Efficacy outcomes (3 vertical panels)

library(patchwork)
combined <- p_pcr / p_efs / p_os + plot_annotation( title = "Figure 1. Efficacy Outcomes", tag_levels = "A" )
ggsave("07_manuscript/figures/figure1_efficacy.png", width = 10, height = 14, dpi = 300)
library(patchwork)
combined <- p_pcr / p_efs / p_os + plot_annotation( title = "Figure 1. Efficacy Outcomes", tag_levels = "A" )
ggsave("07_manuscript/figures/figure1_efficacy.png", width = 10, height = 14, dpi = 300)

Figure 2: Safety + Bias (2 vertical panels)

Figure 2: Safety + Bias (2 vertical panels)

combined <- p_safety / p_funnel + plot_annotation(tag_levels = "A")
ggsave("07_manuscript/figures/figure2_safety.png", width = 10, height = 10, dpi = 300)
combined <- p_safety / p_funnel + plot_annotation(tag_levels = "A")
ggsave("07_manuscript/figures/figure2_safety.png", width = 10, height = 10, dpi = 300)

Figure 3: Subgroup analysis (2x2 grid)

Figure 3: Subgroup analysis (2x2 grid)

combined <- (p_age | p_sex) / (p_stage | p_histology) + plot_annotation( title = "Figure 3. Subgroup Analyses", tag_levels = "A" )
ggsave("07_manuscript/figures/figure3_subgroups.png", width = 14, height = 12, dpi = 300)
undefined
combined <- (p_age | p_sex) / (p_stage | p_histology) + plot_annotation( title = "Figure 3. Subgroup Analyses", tag_levels = "A" )
ggsave("07_manuscript/figures/figure3_subgroups.png", width = 14, height = 12, dpi = 300)
undefined

Legacy Python Examples (Not Recommended)

遗留Python示例(不推荐)

bash
undefined
bash
undefined

Figure 1: Efficacy outcomes (3 vertical panels)

Figure 1: Efficacy outcomes (3 vertical panels)

uv run python assemble.py Figure1_Efficacy.png vertical
forest_plot_pCR.png
forest_plot_EFS.png
forest_plot_OS.png
uv run python assemble.py Figure1_Efficacy.png vertical
forest_plot_pCR.png
forest_plot_EFS.png
forest_plot_OS.png

Figure 2: Safety + Bias (2 vertical panels)

Figure 2: Safety + Bias (2 vertical panels)

uv run python assemble.py Figure2_Safety.png vertical
forest_plot_SAE.png
funnel_plot_pCR.png
undefined
uv run python assemble.py Figure2_Safety.png vertical
forest_plot_SAE.png
funnel_plot_pCR.png
undefined

Dependencies

依赖项

R Packages (Recommended)

推荐的R包

r
undefined
r
undefined

Install from CRAN

Install from CRAN

install.packages(c("patchwork", "cowplot", "ggplot2"))
install.packages(c("patchwork", "cowplot", "ggplot2"))

For meta-analysis plots

For meta-analysis plots

install.packages(c("meta", "metafor"))
undefined
install.packages(c("meta", "metafor"))
undefined

Python (Legacy - Only for PNG Assembly)

Python(遗留 - 仅用于PNG组装)

bash
undefined
bash
undefined

Install using uv (if needed for legacy workflows)

使用uv安装(如果遗留工作流需要)

uv add Pillow
uv add Pillow

Or using pip

或使用pip

pip install Pillow
undefined
pip install Pillow
undefined

Output Example

输出示例

R Output

R输出

✅ Created figure1_efficacy.png
   Dimensions: 3000×4200 px at 300 DPI
   Size: 2.3 MB
The output file will have:
  • Professional panel labels (A, B, C) automatically added by patchwork/cowplot
  • Consistent spacing between panels
  • 300 DPI resolution suitable for publication
  • Aligned axes for easy comparison
  • Publication-ready theme
✅ Created figure1_efficacy.png
   Dimensions: 3000×4200 px at 300 DPI
   Size: 2.3 MB
输出文件将包含:
  • 由patchwork/cowplot自动添加的专业面板标签(A、B、C)
  • 面板间一致的间距
  • 适合发表的300 DPI分辨率
  • 对齐的坐标轴便于对比
  • 可直接用于发表的主题样式

Python Output (Legacy)

Python输出(遗留)

✅ Created Figure1_Efficacy.png
   Dimensions: 3000×6080 px at 300 DPI
The output file will have:
  • Professional panel labels (A, B, C) in top-left corners
  • Consistent spacing between panels
  • 300 DPI resolution suitable for publication
  • White background with black border around labels for maximum visibility
✅ Created Figure1_Efficacy.png
   Dimensions: 3000×6080 px at 300 DPI
输出文件将包含:
  • 位于左上角的专业面板标签(A、B、C)
  • 面板间一致的间距
  • 适合发表的300 DPI分辨率
  • 标签带有白色背景和黑色边框以保证最高可见性

Related Skills

相关技能

  • /meta-manuscript-assembly
    - Complete meta-analysis manuscript preparation
  • /plot-publication
    - Create individual publication-ready plots
  • /figure-legends
    - Generate comprehensive figure legends
  • /meta-manuscript-assembly
    - 完整的元分析手稿准备
  • /plot-publication
    - 创建单个可直接用于发表的绘图
  • /figure-legends
    - 生成全面的图表图例

Pro Tips

专业技巧

R Workflow Tips

R工作流技巧

  1. Work in R throughout: Generate plots AND assemble in R for best results
  2. Use patchwork for ggplot2: Simplest syntax (
    p1 / p2
    for vertical)
  3. Use cowplot for mixed plots: Works with base R and ggplot2
  4. Set theme globally: Use
    theme_set(theme_minimal())
    for consistency
  5. Export once at end: Create all plots, combine, then export (faster)
  6. Check alignment: Use
    align = "v"
    and
    axis = "l"
    in cowplot
  7. Consistent sizes: Set
    base_size
    in theme for readable text
  1. 全程使用R:在R中生成并组装绘图以获得最佳效果
  2. ggplot2绘图使用patchwork:语法最简单(
    p1 / p2
    表示垂直堆叠)
  3. 混合绘图使用cowplot:适用于基础R绘图和ggplot2
  4. 全局设置主题:使用
    theme_set(theme_minimal())
    保证一致性
  5. 最后统一导出:创建所有绘图后再组合导出(速度更快)
  6. 检查对齐:在cowplot中使用
    align = "v"
    axis = "l"
  7. 统一尺寸:在主题中设置
    base_size
    以保证文本可读性

General Tips

通用技巧

  1. Generate high-quality inputs first: Assembly won't improve low-quality source plots
  2. Label systematically: A-B-C top-to-bottom or left-to-right
  3. Check in print preview: Ensure labels readable at final print size
  4. Keep R scripts: Save R code for reproducibility
  5. Version control figures: Commit both R scripts and final PNG files
  6. Test on different screens: Check readability on laptop and printed page
  1. 先生成高质量输入:组装无法提升低质量源绘图的品质
  2. 系统地添加标签:按从上到下或从左到右的顺序使用A-B-C标签
  3. 打印预览检查:确保标签在最终印刷尺寸下可读
  4. 保留R脚本:保存R代码以保证可重复性
  5. 版本控制图表:同时提交R脚本和最终PNG文件
  6. 多屏幕测试:在笔记本电脑和打印页面上检查可读性

R Package Resources

R包资源

When you need help with R packages: