Obtenir des résultats de type ggplot matplotlib avec la crate Rust plotters

Le code Rust suivant est le plus proche que j’ai pu obtenir de l’exemple ggplot de matplotlib en utilisant la crate Rust plotters.

Code Rust

src/main.rs
use plotters::prelude::*;
use plotters::style::{RGBColor, ShapeStyle};
use std::error::Error;

const GGPLOT_RED: RGBColor = RGBColor(0xE2, 0x4A, 0x33);

fn main() -> Result<(), Box<dyn Error>> {
    // Créer une zone de dessin et configurer le graphique
    let root = BitMapBackend::new("sine_wave_plot.png", (1024, 768)).into_drawing_area();
    // arrière-plan global de la figure (le garder blanc)
    root.fill(&WHITE)?;

    // Définir la zone du graphique (nous allons rendre le panneau de tracé gris clair pour servir d'arrière-plan de grille)
    let mut chart = ChartBuilder::on(&root)
        .caption("Sine Wave", ("sans-serif", 30).into_font())
        .margin(24)
        .x_label_area_size(50)
        .y_label_area_size(50)
        .build_cartesian_2d(0f64..2f64 * std::f64::consts::PI, -1.2f64..1.2f64)?;

    // Remplir la zone de tracé avec un gris clair pour servir d'arrière-plan de grille
    // matplotlib ggplot : axes.facecolor = E5E5E5
    chart.plotting_area().fill(&RGBColor(0xE5, 0xE5, 0xE5))?;

    // Configurer la grille (style de type ggplot avec des lignes de grille subtiles)
    chart.configure_mesh()
        .x_labels(10)
        .y_labels(5)
        .x_label_formatter(&|x| format!("{:.1}π", x / std::f64::consts::PI))
        .y_label_formatter(&|y| format!("{:.1}", y))
        .x_desc("Angle (radians)")
        .y_desc("Amplitude")
        // matplotlib ggplot : axes.labelcolor/xtick.color/ytick.color = 555555
        .label_style(TextStyle::from(("sans-serif", 12).into_font()).color(&RGBColor(0x55, 0x55, 0x55)))
        .axis_desc_style(TextStyle::from(("sans-serif", 16).into_font()).color(&RGBColor(0x55, 0x55, 0x55)))
        // matplotlib ggplot : grid.color = white (grille principale) ; la grille secondaire est désactivée par défaut
        .max_light_lines(0)
        .bold_line_style(ShapeStyle::from(&WHITE).stroke_width(1))
        .light_line_style(ShapeStyle::from(&WHITE).stroke_width(1))
        // matplotlib ggplot : axes.edgecolor = white, axes.linewidth = 1
        .axis_style(ShapeStyle::from(&WHITE).stroke_width(1))
        // garantir des graduations vers l'extérieur (une valeur négative pointerait vers l'intérieur)
        .set_all_tick_mark_size(5)
        .draw()?;

    // Générer les points de données de l'onde sinusoïdale
    let mut sine_data = Vec::new();
    for i in 0..=1000 {
        let x = i as f64 * 2.0 * std::f64::consts::PI / 1000.0;
        sine_data.push((x, x.sin()));
    }

    // Dessiner l'onde sinusoïdale
    chart.draw_series(LineSeries::new(
        sine_data,
        ShapeStyle::from(&GGPLOT_RED).stroke_width(2),
    ))?
    .label("sin(x)")
    .legend(|(x, y)| PathElement::new(vec![(x, y), (x + 20, y)], ShapeStyle::from(&GGPLOT_RED).stroke_width(3)));

    // Dessiner la légende
    chart.configure_series_labels()
        .background_style(&RGBColor(0xE5, 0xE5, 0xE5).mix(0.9))
        .border_style(&WHITE)
        .label_font(("sans-serif", 14))
        .position(SeriesLabelPosition::UpperRight)
        .draw()?;

    Ok(())
}
Cargo.toml
[package]
name = "plottest"
version = "0.1.0"
edition = "2021"

[dependencies]
plotters = "0.3.5"

sine wave plot py.avif

Code Python de référence

sine_wave_plot.py
#!/usr/bin/env python3
"""Générer un tracé d'onde sinusoïdale dans le style ggplot et l'enregistrer dans `sine_wave_plot_py.png`.

Utilisation :
    python3 plot_sine.py

Installer les dépendances :
    python3 -m pip install -r requirements.txt
"""

import numpy as np
import matplotlib.pyplot as plt
from matplotlib.ticker import FuncFormatter

plt.style.use("ggplot")

# Données
x = np.linspace(0, 2 * np.pi, 1000)
y = np.sin(x)

# Figure dimensionnée pour approximer 1024x768 à 100 DPI
fig, ax = plt.subplots(figsize=(10.24, 7.68))
ax.plot(x, y, color="red", alpha=0.8, linewidth=2, label="sin(x)")

# Formater les graduations x en multiples de π
def pi_label(value, _pos):
    val = value / np.pi
    if abs(val) < 1e-6:
        return "0"
    return f"{val:.1f}π"

ax.xaxis.set_major_locator(plt.MaxNLocator(10))
ax.xaxis.set_major_formatter(FuncFormatter(pi_label))

# Étiquettes, titre, légende
ax.set_xlabel("Angle (radians)", fontsize=16)
ax.set_ylabel("Amplitude", fontsize=16)
ax.set_title("Sine Wave", fontsize=24, fontweight="bold")
ax.legend(fontsize=12)

# Grille et mise en page
ax.grid(True)
plt.tight_layout()

# Enregistrer le fichier
plt.savefig("sine_wave_plot_py.png", dpi=100)
# Décommenter pour afficher de manière interactive
# plt.show()

sine wave plot rust.avif

Discussion des différences

Note : Je n’ai pas essayé de faire correspondre la taille de police, etc., de quelque manière que ce soit.

Comme vous pouvez le voir, avec un peu d’effort manuel, les deux tracés se ressemblent beaucoup. Cependant, comme l’anticrénelage n’est pas implémenté dans la crate plotters pour le moment (au 2026-01-30), le tracé Rust apparaît nettement plus « crénelé » que celui de matplotlib.

Quand on parle de tracés de qualité publication, c’est un inconvénient important et pour cette raison je ne peux pas le recommander. J’espère que l’anticrénelage sera implémenté à l’avenir.

Gros plan sur l’anticrénelage de Python

Python Anti Aliasing.avif

Gros plan sur l’« anticrénelage » de Rust (aucun)

Rust Anti Aliasing.avif


Check out similar posts by category: Rust, Python, Data Visualization