March 4, 2026 5 min read

Make Matplotlib Figures Publication Quality

Your journal rejection might not be about your science—it's about pixelated plots and low-res JPEGs. This step-by-step guide transforms basic Matplotlib figures into publication-grade visuals using global rcParams, LaTeX math rendering, and vector exports that meet Nature, IEEE, and Elsevier standards.

Your Matplotlib Plots Are Getting Your Papers Rejected — Here’s the Fix

You’ve spent weeks perfecting your research, written a solid manuscript, and submitted to your target journal. Then the rejection email arrives: “Figures do not meet publication standards.”

The problem isn’t your science—it’s your pixelated plots, inconsistent fonts, and low-resolution JPEGs. Journal editors see hundreds of submissions monthly. Poor figure quality signals careless work, even when your data is groundbreaking.

This tutorial transforms a basic, rejection-prone Matplotlib plot into a crisp, publication-ready figure that meets Nature, IEEE, and Elsevier standards. You’ll have a reusable workflow that produces journal-grade figures in 10 minutes instead of 2 hours fighting with Inkscape.

What You’ll Learn

  • Set global rcParams for consistent styling across all figures
  • Integrate LaTeX rendering for mathematical expressions
  • Differentiate data using color + line style (critical for colorblind readers and grayscale printing)
  • Export as vector formats (PDF/SVG) or high-DPI rasters (300+ dpi PNG)
  • Avoid the five mistakes that trigger desk rejections

Prerequisites

Required:

  • Python 3.8+
  • Matplotlib 3.5+ (tested on 3.7.1)
  • NumPy (any recent version)

Optional:

  • LaTeX distribution (TeX Live, MiKTeX, or MacTeX) for full math rendering

Check your version:

import matplotlib
print(matplotlib.__version__)

If below 3.5:

pip install --upgrade matplotlib

Setup

Create a project folder:

mkdir publication_figures
cd publication_figures

You’ll create two files:

  • config_matplotlib.py — global settings (reusable)
  • figure_demo.py — your plotting script

Step 1: See What NOT to Do

Create figure_bad.py:

import matplotlib.pyplot as plt
import numpy as np

x = np.linspace(0, 6, 100)
y1 = np.sin(x)
y2 = np.cos(x)

plt.plot(x, y1)
plt.plot(x, y2)
plt.xlabel('x')
plt.ylabel('y')
plt.title('Plot')
plt.legend(['Line 1', 'Line 2'])
plt.savefig('bad_figure.jpg', dpi=72)
plt.show()

Run it:

python figure_bad.py

Open bad_figure.jpg and zoom in. Notice:

  • Pixelated text and lines (72 dpi = unusable for print)
  • JPEG compression artifacts around text
  • Vague labels with no units or context
  • No visual distinction beyond color (fails in grayscale)

This figure gets rejected immediately.

Step 2: Configure Global Settings

Create config_matplotlib.py:

import matplotlib.pyplot as plt

# Font sizes
plt.rcParams['font.size'] = 12
plt.rcParams['axes.labelsize'] = 14
plt.rcParams['axes.titlesize'] = 16
plt.rcParams['xtick.labelsize'] = 11
plt.rcParams['ytick.labelsize'] = 11
plt.rcParams['legend.fontsize'] = 11

# Figure quality
plt.rcParams['figure.dpi'] = 100  # Screen display
plt.rcParams['savefig.dpi'] = 300  # Export (minimum for publication)

# LaTeX rendering (comment out if not installed)
plt.rcParams['text.usetex'] = True
plt.rcParams['font.family'] = 'serif'

# Line defaults
plt.rcParams['lines.linewidth'] = 2
plt.rcParams['lines.markersize'] = 6

Why this matters: These settings ensure consistency across every figure. Import this file once, and all plots inherit publication standards.

No LaTeX? Comment out these lines:

# plt.rcParams['text.usetex'] = True
# plt.rcParams['font.family'] = 'serif'

Step 3: Use plt.subplots() for Control

Create figure_good.py:

import numpy as np
import matplotlib.pyplot as plt
import config_matplotlib  # Apply global settings

# Data
x = np.linspace(0, 2*np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# Create figure with explicit axis object
fig, ax = plt.subplots(figsize=(8, 5))

# Plot with color AND linestyle
ax.plot(x, y1, color='blue', linestyle='-', label=r'$\sin(x)$')
ax.plot(x, y2, color='red', linestyle='--', label=r'$\cos(x)$')

plt.show()

Why subplots()? Even for single plots, it gives you the ax object for precise control. Essential for multi-panel figures later.

Notice the x-range changed from 0–6 to 0–2π—mathematically meaningful for trig functions.

Step 4: Add Descriptive Labels with Units

Replace vague labels:

ax.set_xlabel(r'Angle $\theta$ (radians)', fontsize=14)
ax.set_ylabel(r'Amplitude ($\mu$V)', fontsize=14)
ax.set_title(r'Trigonometric Functions: $\sin(x)$ vs $\cos(x)$', fontsize=16)

LaTeX syntax:

  • Wrap math in $ $
  • Use raw strings (r'...') to avoid backslash escaping
  • Greek letters: \alpha, \beta, \mu, \theta

Without LaTeX, use plain text: 'Angle (radians)'

Step 5: Use π-Based Tick Labels

For trig plots:

ax.set_xticks([0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi])
ax.set_xticklabels([r'$0$', r'$\frac{\pi}{2}$', r'$\pi$', 
                     r'$\frac{3\pi}{2}$', r'$2\pi$'])
ax.minorticks_on()

This transforms a generic axis into something reviewers expect.

Step 6: Add Grid and Position Legend

ax.grid(True, alpha=0.3, linestyle='--')
ax.legend(loc='upper right')

The alpha=0.3 makes the grid visible but subtle. The loc parameter prevents the legend from covering data.

Step 7: Set Explicit Limits

ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1.2, 1.2)

Prevents Matplotlib from adding unnecessary padding.

Step 8: Export as Vector Format

Replace JPEG with:

plt.savefig('good_figure.pdf', format='pdf', bbox_inches='tight')

Why bbox_inches='tight'? Removes excess whitespace.

Format guide:

  • PDF — Best for LaTeX documents and most journals
  • SVG — Best for Inkscape/Illustrator editing
  • PNG — Only if required: plt.savefig('figure.png', dpi=300, bbox_inches='tight')

Never use JPEG for scientific figures—lossy compression destroys text clarity.

Complete Publication-Ready Script

import numpy as np
import matplotlib.pyplot as plt
import config_matplotlib

# Data
x = np.linspace(0, 2*np.pi, 100)
y1 = np.sin(x)
y2 = np.cos(x)

# Create figure
fig, ax = plt.subplots(figsize=(8, 5))

# Plot with color AND linestyle
ax.plot(x, y1, color='blue', linestyle='-', label=r'$\sin(x)$')
ax.plot(x, y2, color='red', linestyle='--', label=r'$\cos(x)$')

# Labels with units
ax.set_xlabel(r'Angle $\theta$ (radians)', fontsize=14)
ax.set_ylabel(r'Amplitude ($\mu$V)', fontsize=14)
ax.set_title(r'Trigonometric Functions: $\sin(x)$ vs $\cos(x)$', fontsize=16)

# Custom ticks
ax.set_xticks([0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi])
ax.set_xticklabels([r'$0$', r'$\frac{\pi}{2}$', r'$\pi$', 
                     r'$\frac{3\pi}{2}$', r'$2\pi$'])

# Limits and minor ticks
ax.set_xlim(0, 2*np.pi)
ax.set_ylim(-1.2, 1.2)
ax.minorticks_on()

# Grid and legend
ax.grid(True, alpha=0.3, linestyle='--')
ax.legend(loc='upper right')

# Export
plt.savefig('good_figure.pdf', format='pdf', bbox_inches='tight')
plt.show()

Run it:

python figure_good.py

Open good_figure.pdf and zoom in. Text stays crisp, lines are smooth, math renders correctly.

Common Issues & Fixes

“LaTeX not found” error

Symptom:

RuntimeError: Failed to process string with tex

Fix: Comment out in config_matplotlib.py:

# plt.rcParams['text.usetex'] = True
# plt.rcParams['font.family'] = 'serif'

Legend covers data

Fix: Try auto-positioning:

ax.legend(loc='best')

Or place outside:

ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')

Font sizes too small in multi-panel figures

Fix: Increase base sizes in config_matplotlib.py:

plt.rcParams['font.size'] = 14
plt.rcParams['axes.labelsize'] = 16

Lines indistinguishable in grayscale

Fix: Always combine color with linestyle:

ax.plot(x, y1, color='blue', linestyle='-')    # Solid
ax.plot(x, y2, color='red', linestyle='--')    # Dashed
ax.plot(x, y3, color='green', linestyle='-.')  # Dash-dot

PNG still blurry at 300 dpi

Fix: Increase figure size:

fig, ax = plt.subplots(figsize=(10, 6))

A 2×2 inch figure at 300 dpi is only 600×600 pixels.

Pre-Submission Checklist

Before submitting to any journal:

  1. Resolution: PDF/SVG preferred; PNG at 300+ dpi minimum
  2. Differentiation: Every series distinguishable by color AND linestyle/marker
  3. Context: Axis labels include units; titles describe what the plot shows

Make It Reusable

Save config_matplotlib.py in a central location:

import sys
sys.path.append('/path/to/scripts')
import config_matplotlib

Now every project inherits publication standards automatically.


What’s your biggest Matplotlib frustration right now—fonts, layout, or export quality? Reply and let me know what you’d like covered next.

Comments