# Fichier: python_cheats/cheatsheets/matplotlib_seaborn.txt
# Cheatsheet Matplotlib & Seaborn - Visualisation de Données Python


[OK] INSTALLATION

# Matplotlib
pip install matplotlib

# Seaborn (inclut matplotlib)
pip install seaborn

# Installation complète pour data science
pip install matplotlib seaborn pandas numpy

# Versions spécifiques
pip install matplotlib==3.8.0 seaborn==0.13.0

# Dans environnement conda
conda install matplotlib seaborn


[OK] IMPORTS STANDARDS

import matplotlib.pyplot as plt
import matplotlib as mpl
import seaborn as sns
import numpy as np
import pandas as pd

# Vérifier versions
print(matplotlib.__version__)
print(sns.__version__)


[OK] CONFIGURATION DE BASE

# === Matplotlib ===

# Style
plt.style.use('default')        # Style par défaut
plt.style.use('seaborn-v0_8')   # Style seaborn
plt.style.use('ggplot')         # Style ggplot
plt.style.use('fivethirtyeight')
plt.style.use('dark_background')

# Lister styles disponibles
print(plt.style.available)

# Taille de figure par défaut
plt.rcParams['figure.figsize'] = (10, 6)

# DPI (résolution)
plt.rcParams['figure.dpi'] = 100
plt.rcParams['savefig.dpi'] = 300

# Police
plt.rcParams['font.size'] = 12
plt.rcParams['font.family'] = 'sans-serif'
plt.rcParams['font.sans-serif'] = ['Arial', 'Helvetica']

# Grille
plt.rcParams['axes.grid'] = True
plt.rcParams['grid.alpha'] = 0.3

# Configuration complète
plt.rcParams.update({
    'figure.figsize': (10, 6),
    'figure.dpi': 100,
    'font.size': 12,
    'axes.labelsize': 14,
    'axes.titlesize': 16,
    'xtick.labelsize': 12,
    'ytick.labelsize': 12,
    'legend.fontsize': 12,
    'lines.linewidth': 2
})

# Réinitialiser configuration
plt.rcdefaults()

# === Seaborn ===

# Style
sns.set_style('darkgrid')       # darkgrid, whitegrid, dark, white, ticks
sns.set_style('whitegrid')
sns.set_style('ticks')

# Contexte (échelle)
sns.set_context('notebook')     # paper, notebook, talk, poster
sns.set_context('talk')         # Pour présentations
sns.set_context('poster')       # Pour posters

# Palette de couleurs
sns.set_palette('deep')         # deep, muted, bright, pastel, dark, colorblind
sns.set_palette('husl')         # Palette HSL
sns.set_palette('Set2')         # ColorBrewer

# Configuration complète
sns.set_theme(
    style='darkgrid',
    palette='deep',
    context='notebook',
    font='sans-serif',
    font_scale=1.2
)

# Réinitialiser
sns.reset_defaults()

# Mode interactif (Jupyter)
%matplotlib inline              # Affichage statique
%matplotlib notebook            # Interactif
%matplotlib widget              # Widget interactif


[OK] ANATOMIE D'UNE FIGURE

# === Structure de base ===
# Figure: conteneur principal
# Axes: zone de traçage (subplot)
# Axis: axes X et Y
# Artist: tous les éléments visuels

# Créer figure et axes
fig, ax = plt.subplots()

# Figure avec plusieurs subplots
fig, (ax1, ax2) = plt.subplots(1, 2)
fig, axes = plt.subplots(2, 2)

# Taille personnalisée
fig, ax = plt.subplots(figsize=(12, 8))

# === Composants principaux ===
# - Title (titre)
# - X-axis label (label axe X)
# - Y-axis label (label axe Y)
# - Legend (légende)
# - Grid (grille)
# - Spines (bordures)
# - Ticks (graduations)
# - Line/Bar/Scatter/etc. (tracés)


[OK] CRÉER DES FIGURES

# === Méthode 1: pyplot (simple) ===
plt.plot([1, 2, 3], [1, 4, 9])
plt.show()

# === Méthode 2: orienté objet (recommandé) ===
fig, ax = plt.subplots()
ax.plot([1, 2, 3], [1, 4, 9])
plt.show()

# === Subplots ===

# Grille simple
fig, axes = plt.subplots(2, 2, figsize=(12, 10))
axes[0, 0].plot(x, y1)
axes[0, 1].plot(x, y2)
axes[1, 0].plot(x, y3)
axes[1, 1].plot(x, y4)

# Avec partage d'axes
fig, (ax1, ax2) = plt.subplots(2, 1, sharex=True)

# GridSpec (layout complexe)
from matplotlib.gridspec import GridSpec

fig = plt.figure(figsize=(12, 8))
gs = GridSpec(3, 3, figure=fig)

ax1 = fig.add_subplot(gs[0, :])      # Première ligne complète
ax2 = fig.add_subplot(gs[1, :-1])    # Deuxième ligne, 2 colonnes
ax3 = fig.add_subplot(gs[1:, -1])    # Dernière colonne, 2 lignes
ax4 = fig.add_subplot(gs[-1, 0])     # Dernière ligne, première colonne
ax5 = fig.add_subplot(gs[-1, -2])    # Dernière ligne, avant-dernière colonne

# Ajuster espacement
plt.tight_layout()
plt.subplots_adjust(hspace=0.3, wspace=0.3)


[OK] GRAPHIQUES LINÉAIRES

# === Matplotlib ===

# Plot simple
x = np.linspace(0, 10, 100)
y = np.sin(x)

fig, ax = plt.subplots()
ax.plot(x, y)
plt.show()

# Plusieurs lignes
ax.plot(x, np.sin(x), label='sin')
ax.plot(x, np.cos(x), label='cos')
ax.legend()

# Style de ligne
ax.plot(x, y, color='red', linestyle='--', linewidth=2, marker='o', 
        markersize=5, markerfacecolor='blue', markeredgecolor='black',
        alpha=0.7, label='Données')

# Styles de ligne: '-', '--', '-.', ':', ''
# Marqueurs: 'o', 's', '^', 'v', '<', '>', 'd', 'p', '*', 'x', '+'

# Format raccourci
ax.plot(x, y, 'ro-')  # red, circle, solid line
ax.plot(x, y, 'b^--') # blue, triangle, dashed line

# Couleurs
# Noms: 'red', 'blue', 'green', etc.
# Codes: 'r', 'b', 'g', 'c', 'm', 'y', 'k', 'w'
# Hex: '#FF5733'
# RGB tuple: (0.5, 0.2, 0.8)
# RGBA tuple: (0.5, 0.2, 0.8, 0.5)

# Remplissage
ax.fill_between(x, y1, y2, alpha=0.3)
ax.fill_betweenx(y, x1, x2, alpha=0.3)

# === Seaborn ===

# Line plot
sns.lineplot(data=df, x='time', y='value')

# Plusieurs catégories
sns.lineplot(data=df, x='time', y='value', hue='category')

# Avec intervalle de confiance
sns.lineplot(data=df, x='time', y='value', ci=95)  # 95% CI
sns.lineplot(data=df, x='time', y='value', ci='sd')  # Standard deviation

# Style
sns.lineplot(data=df, x='x', y='y', hue='category', 
             style='type', markers=True, dashes=False)


[OK] NUAGES DE POINTS (SCATTER)

# === Matplotlib ===

x = np.random.randn(100)
y = np.random.randn(100)
colors = np.random.rand(100)
sizes = 1000 * np.random.rand(100)

fig, ax = plt.subplots()
scatter = ax.scatter(x, y, c=colors, s=sizes, alpha=0.5, 
                     cmap='viridis', edgecolors='black', linewidth=0.5)

# Colorbar
plt.colorbar(scatter, ax=ax, label='Valeur')

# Marqueurs personnalisés
markers = ['o', 's', '^', 'v']
for i, marker in enumerate(markers):
    ax.scatter(x[i*25:(i+1)*25], y[i*25:(i+1)*25], 
               marker=marker, s=100, label=f'Type {i+1}')

ax.legend()

# === Seaborn ===

# Scatter plot simple
sns.scatterplot(data=df, x='x', y='y')

# Avec catégories
sns.scatterplot(data=df, x='x', y='y', hue='category', 
                style='type', size='value')

# Taille personnalisée
sns.scatterplot(data=df, x='x', y='y', size='population', 
                sizes=(20, 200), hue='category')

# Transparence
sns.scatterplot(data=df, x='x', y='y', alpha=0.6)

# Regression plot (scatter + ligne de régression)
sns.regplot(data=df, x='x', y='y')
sns.regplot(data=df, x='x', y='y', order=2)  # Polynomiale ordre 2

# LM plot (regplot avec facettes)
sns.lmplot(data=df, x='x', y='y', hue='category', col='region')


[OK] GRAPHIQUES À BARRES

# === Matplotlib ===

categories = ['A', 'B', 'C', 'D']
values = [25, 40, 30, 55]

fig, ax = plt.subplots()

# Barres verticales
ax.bar(categories, values, color='steelblue', alpha=0.8)

# Barres horizontales
ax.barh(categories, values)

# Barres groupées
x = np.arange(len(categories))
width = 0.35

values1 = [25, 40, 30, 55]
values2 = [30, 35, 45, 50]

ax.bar(x - width/2, values1, width, label='Groupe 1')
ax.bar(x + width/2, values2, width, label='Groupe 2')
ax.set_xticks(x)
ax.set_xticklabels(categories)
ax.legend()

# Barres empilées
ax.bar(categories, values1, label='Groupe 1')
ax.bar(categories, values2, bottom=values1, label='Groupe 2')

# Couleurs personnalisées par barre
colors = ['red', 'green', 'blue', 'orange']
ax.bar(categories, values, color=colors)

# Annotations sur barres
for i, v in enumerate(values):
    ax.text(i, v + 1, str(v), ha='center', va='bottom')

# === Seaborn ===

# Bar plot (moyenne + barre d'erreur)
sns.barplot(data=df, x='category', y='value')

# Avec sous-catégories
sns.barplot(data=df, x='category', y='value', hue='subcategory')

# Count plot (comptage)
sns.countplot(data=df, x='category')
sns.countplot(data=df, x='category', hue='subcategory')

# Orientation horizontale
sns.barplot(data=df, x='value', y='category', orient='h')

# Estimateur personnalisé
sns.barplot(data=df, x='category', y='value', estimator=np.median)

# Sans barre d'erreur
sns.barplot(data=df, x='category', y='value', ci=None)


[OK] HISTOGRAMMES

# === Matplotlib ===

data = np.random.randn(1000)

fig, ax = plt.subplots()
ax.hist(data, bins=30, color='steelblue', alpha=0.7, edgecolor='black')

# Plusieurs distributions
ax.hist([data1, data2], bins=30, alpha=0.5, label=['Dist 1', 'Dist 2'])
ax.legend()

# Histogramme normalisé (densité)
ax.hist(data, bins=30, density=True, alpha=0.7)

# Type d'histogramme
ax.hist(data, bins=30, histtype='step')      # Contour
ax.hist(data, bins=30, histtype='stepfilled') # Rempli
ax.hist(data, bins=30, histtype='bar')        # Barres (défaut)

# Empilé
ax.hist([data1, data2], bins=30, stacked=True)

# Bins personnalisés
bins = [0, 1, 2, 5, 10, 20, 50, 100]
ax.hist(data, bins=bins)

# === Seaborn ===

# Histogramme simple
sns.histplot(data=df, x='value')

# Avec KDE (densité)
sns.histplot(data=df, x='value', kde=True)

# Plusieurs distributions
sns.histplot(data=df, x='value', hue='category')

# Empilé
sns.histplot(data=df, x='value', hue='category', multiple='stack')

# Côte à côte (dodge)
sns.histplot(data=df, x='value', hue='category', multiple='dodge')

# Bins personnalisés
sns.histplot(data=df, x='value', bins=50)
sns.histplot(data=df, x='value', binwidth=0.5)

# Distribution 2D
sns.histplot(data=df, x='x', y='y')
sns.histplot(data=df, x='x', y='y', cbar=True)

# KDE plot (densité lissée)
sns.kdeplot(data=df, x='value')
sns.kdeplot(data=df, x='value', hue='category', fill=True, alpha=0.5)

# Distribution plot (obsolète, utiliser displot)
sns.displot(data=df, x='value', kde=True)
sns.displot(data=df, x='value', kind='kde')
sns.displot(data=df, x='value', kind='ecdf')  # ECDF


[OK] BOX PLOTS & VIOLIN PLOTS

# === Matplotlib ===

data = [np.random.randn(100) for _ in range(4)]

fig, ax = plt.subplots()

# Box plot
bp = ax.boxplot(data, labels=['A', 'B', 'C', 'D'])

# Horizontal
ax.boxplot(data, vert=False)

# Personnalisation
bp = ax.boxplot(data, 
                notch=True,           # Encoche
                showmeans=True,       # Afficher moyenne
                meanline=True,        # Ligne de moyenne
                patch_artist=True)    # Couleurs personnalisées

# Colorier les boîtes
for patch in bp['boxes']:
    patch.set_facecolor('lightblue')

# === Seaborn ===

# Box plot
sns.boxplot(data=df, x='category', y='value')

# Avec sous-catégories
sns.boxplot(data=df, x='category', y='value', hue='subcategory')

# Horizontal
sns.boxplot(data=df, x='value', y='category', orient='h')

# Avec points
sns.boxplot(data=df, x='category', y='value')
sns.swarmplot(data=df, x='category', y='value', color='black', alpha=0.5)

# Violin plot (distribution + box plot)
sns.violinplot(data=df, x='category', y='value')

# Séparé par sous-catégorie
sns.violinplot(data=df, x='category', y='value', hue='subcategory', split=True)

# Avec quartiles
sns.violinplot(data=df, x='category', y='value', inner='quartile')

# Box + violin combinés
sns.violinplot(data=df, x='category', y='value', inner='box')

# Strip plot (points)
sns.stripplot(data=df, x='category', y='value')
sns.stripplot(data=df, x='category', y='value', jitter=True)

# Swarm plot (points non-superposés)
sns.swarmplot(data=df, x='category', y='value')


[OK] HEATMAPS & MATRICES

# === Matplotlib ===

data = np.random.rand(10, 12)

fig, ax = plt.subplots(figsize=(10, 8))
im = ax.imshow(data, cmap='viridis', aspect='auto')

# Colorbar
plt.colorbar(im, ax=ax)

# Labels
ax.set_xticks(np.arange(data.shape[1]))
ax.set_yticks(np.arange(data.shape[0]))
ax.set_xticklabels(['Col' + str(i) for i in range(data.shape[1])])
ax.set_yticklabels(['Row' + str(i) for i in range(data.shape[0])])

# Annotations
for i in range(data.shape[0]):
    for j in range(data.shape[1]):
        text = ax.text(j, i, f'{data[i, j]:.2f}',
                       ha="center", va="center", color="w")

# pcolor/pcolormesh (plus flexible)
ax.pcolormesh(data, cmap='coolwarm')

# === Seaborn ===

# Heatmap simple
sns.heatmap(data)

# Avec annotations
sns.heatmap(data, annot=True, fmt='.2f')

# Colormap personnalisée
sns.heatmap(data, cmap='coolwarm', center=0)
sns.heatmap(data, cmap='YlGnBu')

# Colorbar
sns.heatmap(data, cbar_kws={'label': 'Valeur'})

# Sans colorbar
sns.heatmap(data, cbar=False)

# Masquer triangle
mask = np.triu(np.ones_like(data, dtype=bool))
sns.heatmap(data, mask=mask, annot=True)

# Lignes de séparation
sns.heatmap(data, linewidths=0.5, linecolor='white')

# Limites de couleur
sns.heatmap(data, vmin=-1, vmax=1)

# Matrice de corrélation
corr = df.corr()
sns.heatmap(corr, annot=True, cmap='coolwarm', center=0,
            square=True, linewidths=1, cbar_kws={'shrink': 0.8})

# Clustermap (avec dendrogramme)
sns.clustermap(data, cmap='viridis', figsize=(10, 10))


[OK] GRAPHIQUES STATISTIQUES

# === Pair plot (matrice de scatter plots) ===
sns.pairplot(df)
sns.pairplot(df, hue='category')
sns.pairplot(df, diag_kind='kde')
sns.pairplot(df, corner=True)  # Moitié seulement

# === Joint plot (scatter + distributions marginales) ===
sns.jointplot(data=df, x='x', y='y')
sns.jointplot(data=df, x='x', y='y', kind='hex')
sns.jointplot(data=df, x='x', y='y', kind='kde')
sns.jointplot(data=df, x='x', y='y', kind='reg')

# === Categorical plots ===

# Point plot (moyenne + CI)
sns.pointplot(data=df, x='category', y='value')

# Strip plot avec violin
fig, ax = plt.subplots()
sns.violinplot(data=df, x='category', y='value', ax=ax)
sns.stripplot(data=df, x='category', y='value', 
              color='black', alpha=0.3, ax=ax)

# Cat plot (figure-level)
sns.catplot(data=df, x='category', y='value', kind='box')
sns.catplot(data=df, x='category', y='value', kind='violin', col='region')

# === ECDF (Fonction de répartition empirique) ===
sns.ecdfplot(data=df, x='value')
sns.ecdfplot(data=df, x='value', hue='category')

# === Rug plot (ticks de densité) ===
sns.rugplot(data=df, x='value')


[OK] GRAPHIQUES TEMPORELS

import matplotlib.dates as mdates
from datetime import datetime

# Données temporelles
dates = pd.date_range('2024-01-01', periods=100, freq='D')
values = np.cumsum(np.random.randn(100))

# === Matplotlib ===

fig, ax = plt.subplots(figsize=(12, 6))
ax.plot(dates, values)

# Formater axe X (dates)
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
ax.xaxis.set_major_locator(mdates.DayLocator(interval=10))
plt.xticks(rotation=45)

# Formateurs courants
# DateFormatter('%Y-%m-%d')         # 2024-01-15
# DateFormatter('%d/%m/%Y')         # 15/01/2024
# DateFormatter('%b %d')            # Jan 15
# DateFormatter('%Y-%m')            # 2024-01

# Locators
# DayLocator()                      # Chaque jour
# WeekdayLocator()                  # Chaque semaine
# MonthLocator()                    # Chaque mois
# YearLocator()                     # Chaque année
# HourLocator()                     # Chaque heure

# Zone d'ombre
ax.axvspan(dates[20], dates[40], alpha=0.2, color='red', label='Événement')

# Ligne verticale à date spécifique
ax.axvline(dates[50], color='green', linestyle='--', label='Limite')

# === Seaborn ===

df_time = pd.DataFrame({'date': dates, 'value': values})

sns.lineplot(data=df_time, x='date', y='value')
plt.xticks(rotation=45)

# Avec intervalle de confiance sur séries temporelles
sns.lineplot(data=df_time, x='date', y='value', ci=95)


[OK] GRAPHIQUES AVANCÉS

# === Contour plots ===
x = np.linspace(-3, 3, 100)
y = np.linspace(-3, 3, 100)
X, Y = np.meshgrid(x, y)
Z = np.sin(X) * np.cos(Y)

fig, ax = plt.subplots()
contour = ax.contour(X, Y, Z, levels=20, cmap='viridis')
ax.clabel(contour, inline=True, fontsize=8)

# Filled contour
ax.contourf(X, Y, Z, levels=20, cmap='viridis')
plt.colorbar(contour, ax=ax)

# === 3D plots ===
from mpl_toolkits.mplot3d import Axes3D

fig = plt.figure(figsize=(10, 8))
ax = fig.add_subplot(111, projection='3d')

# Surface
ax.plot_surface(X, Y, Z, cmap='viridis', alpha=0.8)

# Wireframe
ax.plot_wireframe(X, Y, Z, color='black', linewidth=0.5)

# Scatter 3D
x = np.random.randn(100)
y = np.random.randn(100)
z = np.random.randn(100)
ax.scatter(x, y, z, c=z, cmap='viridis', s=50)

ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')

# === Pie chart ===
labels = ['A', 'B', 'C', 'D']
sizes = [15, 30, 45, 10]
explode = (0, 0.1, 0, 0)  # Séparer une tranche

fig, ax = plt.subplots()
ax.pie(sizes, labels=labels, explode=explode, autopct='%1.1f%%',
       shadow=True, startangle=90)
ax.axis('equal')

# Donut
ax.pie(sizes, labels=labels, autopct='%1.1f%%',
       wedgeprops=dict(width=0.5))

# === Polar plot ===
theta = np.linspace(0, 2*np.pi, 100)
r = 1 + np.sin(4*theta)

fig, ax = plt.subplots(subplot_kw={'projection': 'polar'})
ax.plot(theta, r)

# === Stream plot (champs vectoriels) ===
Y, X = np.mgrid[-3:3:100j, -3:3:100j]
U = -1 - X**2 + Y
V = 1 + X - Y**2

fig, ax = plt.subplots()
ax.streamplot(X, Y, U, V, density=2, color='blue', linewidth=1)

# === Quiver plot (flèches) ===
x = np.linspace(-2, 2, 10)
y = np.linspace(-2, 2, 10)
X, Y = np.meshgrid(x, y)
U = -Y
V = X

fig, ax = plt.subplots()
ax.quiver(X, Y, U, V)

# === Hexbin (agrégation hexagonale) ===
x = np.random.randn(10000)
y = np.random.randn(10000)

fig, ax = plt.subplots()
hexbin = ax.hexbin(x, y, gridsize=30, cmap='Blues')
plt.colorbar(hexbin, ax=ax)

# === Error bars ===
x = np.arange(0, 10, 1)
y = np.sin(x)
yerr = 0.1 + 0.2 * np.random.rand(len(x))

fig, ax = plt.subplots()
ax.errorbar(x, y, yerr=yerr, fmt='o-', capsize=5, capthick=2)


[OK] PERSONNALISATION

# === Titres et labels ===
ax.set_title('Titre du graphique', fontsize=16, fontweight='bold')
ax.set_xlabel('Axe X', fontsize=14)
ax.set_ylabel('Axe Y', fontsize=14)

# Position du titre
ax.set_title('Titre', loc='left')   # left, center, right
ax.set_title('Titre', pad=20)       # Espacement

# === Légende ===
ax.legend()
ax.legend(loc='upper right')  # upper/lower/center + left/right/center
ax.legend(loc='best')         # Position automatique
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')  # Hors du graphique

# Personnalisation légende
ax.legend(frameon=True, shadow=True, fancybox=True,
          fontsize=12, title='Catégories', title_fontsize=14)

# Colonnes
ax.legend(ncol=2)

# Retirer légende
ax.get_legend().remove()

# === Limites des axes ===
ax.set_xlim(0, 10)
ax.set_ylim(-1, 1)

# Auto avec marge
ax.margins(x=0.1, y=0.1)

# === Échelle ===
ax.set_xscale('log')
ax.set_yscale('log')
ax.set_xscale('linear')

# Échelle symétrique log
ax.set_yscale('symlog')

# === Grille ===
ax.grid(True)
ax.grid(True, which='both', alpha=0.3)
ax.grid(True, axis='x')  # Seulement X
ax.grid(True, axis='y')  # Seulement Y

# Style de grille
ax.grid(True, linestyle='--', linewidth=0.5, color='gray', alpha=0.7)

# === Graduations (ticks) ===
ax.set_xticks([0, 2, 4, 6, 8, 10])
ax.set_xticklabels(['A', 'B', 'C', 'D', 'E', 'F'])

# Rotation
ax.tick_params(axis='x', rotation=45)
plt.xticks(rotation=45, ha='right')

# Taille
ax.tick_params(axis='both', labelsize=12)

# Sens
ax.tick_params(direction='in')   # in, out, inout

# Minor ticks
ax.minorticks_on()
ax.tick_params(which='minor', length=4, color='gray')

# === Spines (bordures) ===
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

# Couleur et épaisseur
ax.spines['bottom'].set_color('blue')
ax.spines['left'].set_linewidth(2)

# Position
ax.spines['left'].set_position(('data', 0))
ax.spines['bottom'].set_position(('data', 0))

# === Texte et annotations ===
ax.text(5, 0.5, 'Texte', fontsize=12, ha='center', va='center')

# Annotation avec flèche
ax.annotate('Point important', 
            xy=(3, 0.5),              # Point annoté
            xytext=(4, 0.8),          # Position du texte
            arrowprops=dict(facecolor='black', shrink=0.05, width=2),
            fontsize=12, ha='center')

# Style de flèche personnalisé
ax.annotate('Annotation', xy=(2, 1), xytext=(3, 1.5),
            arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0.3',
                          color='red', lw=2))

# Styles de flèches: '->', '-[', '-|>', '<->', '<|-|>', 'fancy', 'simple', 'wedge'

# Boîte de texte
bbox_props = dict(boxstyle='round,pad=0.5', facecolor='yellow', alpha=0.5)
ax.text(5, 0.5, 'Texte encadré', bbox=bbox_props)

# === Couleurs et styles ===

# Colormap
from matplotlib import cm
colors = cm.viridis(np.linspace(0, 1, 10))

# Cycles de couleurs
from cycler import cycler
ax.set_prop_cycle(cycler('color', ['r', 'g', 'b', 'y']))

# === Axes secondaires ===

# Axe Y secondaire
ax2 = ax.twinx()
ax2.plot(x, y2, 'r-')
ax2.set_ylabel('Axe Y secondaire', color='r')

# Axe X secondaire
ax3 = ax.twiny()
ax3.plot(x2, y, 'b-')
ax3.set_xlabel('Axe X secondaire', color='b')

# === Insets (graphiques insérés) ===
from mpl_toolkits.axes_grid1.inset_locator import inset_axes

axins = inset_axes(ax, width="40%", height="40%", loc='upper right')
axins.plot(x, y)

# Zoom sur une région
from mpl_toolkits.axes_grid1.inset_locator import mark_inset

x1, x2, y1, y2 = 2, 4, 0.3, 0.7
axins.set_xlim(x1, x2)
axins.set_ylim(y1, y2)
mark_inset(ax, axins, loc1=2, loc2=4, fc="none", ec="0.5")


[OK] PALETTES DE COULEURS

# === Matplotlib colormaps ===

# Séquentielles
# viridis, plasma, inferno, magma, cividis
# Blues, Greens, Reds, Purples, Oranges, Greys
# YlOrBr, YlOrRd, OrRd, PuRd, RdPu, BuPu, GnBu, PuBu, YlGnBu, PuBuGn, BuGn, YlGn

# Divergentes
# coolwarm, bwr, seismic, RdBu, RdYlBu, RdYlGn, Spectral, PiYG, PRGn, BrBG, PuOr, RdGy

# Qualitatives
# tab10, tab20, tab20b, tab20c
# Pastel1, Pastel2, Paired, Accent, Dark2, Set1, Set2, Set3

# Inverser colormap
plt.cm.viridis_r

# Créer colormap personnalisée
from matplotlib.colors import LinearSegmentedColormap

colors_list = ['darkblue', 'blue', 'white', 'red', 'darkred']
n_bins = 100
cmap = LinearSegmentedColormap.from_list('custom', colors_list, N=n_bins)

# === Seaborn palettes ===

# Afficher palette
sns.color_palette()
sns.color_palette('husl', 8)  # 8 couleurs

# Palettes qualitatives
# deep, muted, bright, pastel, dark, colorblind
# tab10, tab20
# Set1, Set2, Set3, Paired

# Palettes séquentielles
# Blues, BuGn, BuPu, GnBu, Greens, Greys, Oranges, OrRd,
# PuBu, PuBuGn, PuRd, Purples, RdPu, Reds, YlGn, YlGnBu, YlOrBr, YlOrRd

# Palettes divergentes
# RdBu, RdYlBu, RdYlGn, Spectral, coolwarm, bwr, seismic

# Palette personnalisée
custom_palette = sns.color_palette(['#FF5733', '#33FF57', '#3357FF'])
sns.set_palette(custom_palette)

# Dégradé (blend)
sns.blend_palette(['red', 'blue'], n_colors=10)

# Light/dark palette
sns.light_palette('navy', n_colors=8)
sns.dark_palette('purple', n_colors=8)

# Palette divergente personnalisée
sns.diverging_palette(220, 20, n=11)  # Bleu à rouge

# Voir toutes les palettes
sns.palplot(sns.color_palette('husl', 8))


[OK] SAUVEGARDE

# === Formats de sortie ===

# PNG (raster)
plt.savefig('figure.png', dpi=300)

# PDF (vectoriel)
plt.savefig('figure.pdf')

# SVG (vectoriel)
plt.savefig('figure.svg')

# EPS (vectoriel)
plt.savefig('figure.eps')

# JPEG
plt.savefig('figure.jpg', quality=95)

# === Options de sauvegarde ===

plt.savefig('figure.png',
            dpi=300,                    # Résolution
            bbox_inches='tight',        # Rogner espace blanc
            pad_inches=0.1,             # Marge
            facecolor='white',          # Couleur fond
            edgecolor='none',           # Couleur bordure
            transparent=False,          # Fond transparent
            format='png')               # Format explicite

# Sauvegarde en mémoire (BytesIO)
from io import BytesIO

buf = BytesIO()
plt.savefig(buf, format='png')
buf.seek(0)

# Plusieurs formats
for fmt in ['png', 'pdf', 'svg']:
    plt.savefig(f'figure.{fmt}', dpi=300, bbox_inches='tight')


[OK] THEMES ET STYLES

# === Styles matplotlib prédéfinis ===

plt.style.use('default')
plt.style.use('classic')
plt.style.use('seaborn-v0_8')
plt.style.use('seaborn-v0_8-darkgrid')
plt.style.use('seaborn-v0_8-whitegrid')
plt.style.use('seaborn-v0_8-dark')
plt.style.use('seaborn-v0_8-white')
plt.style.use('seaborn-v0_8-ticks')
plt.style.use('ggplot')
plt.style.use('fivethirtyeight')
plt.style.use('bmh')
plt.style.use('dark_background')
plt.style.use('grayscale')

# Combiner styles
plt.style.use(['seaborn-v0_8-paper', 'seaborn-v0_8-darkgrid'])

# Style temporaire
with plt.style.context('dark_background'):
    plt.plot(x, y)
    plt.show()

# === Thèmes Seaborn ===

# Styles
sns.set_style('darkgrid')      # Grille foncée
sns.set_style('whitegrid')     # Grille blanche
sns.set_style('dark')          # Fond foncé sans grille
sns.set_style('white')         # Fond blanc sans grille
sns.set_style('ticks')         # Avec graduations

# Contextes (échelle globale)
sns.set_context('paper')       # Plus petit (articles)
sns.set_context('notebook')    # Taille normale (défaut)
sns.set_context('talk')        # Plus grand (présentations)
sns.set_context('poster')      # Très grand (posters)

# Échelle personnalisée
sns.set_context('notebook', font_scale=1.5)

# Style temporaire
with sns.axes_style('darkgrid'):
    sns.lineplot(data=df, x='x', y='y')

# === Thème complet ===

# Configuration publication
plt.rcParams.update({
    'figure.figsize': (10, 6),
    'figure.dpi': 100,
    'savefig.dpi': 300,
    'font.size': 12,
    'font.family': 'serif',
    'font.serif': ['Times New Roman'],
    'axes.labelsize': 14,
    'axes.titlesize': 16,
    'axes.linewidth': 1.5,
    'xtick.labelsize': 12,
    'ytick.labelsize': 12,
    'xtick.major.width': 1.5,
    'ytick.major.width': 1.5,
    'legend.fontsize': 12,
    'legend.frameon': True,
    'legend.shadow': False,
    'lines.linewidth': 2,
    'lines.markersize': 8,
    'grid.alpha': 0.3,
    'grid.linewidth': 0.8
})

# Configuration présentation
sns.set_theme(
    style='whitegrid',
    context='talk',
    palette='deep',
    font='sans-serif',
    font_scale=1.3,
    rc={
        'figure.figsize': (14, 8),
        'axes.spines.top': False,
        'axes.spines.right': False
    }
)


[OK] FACETS & SUBPLOTS AVANCÉS

# === FacetGrid (Seaborn) ===

# Grille de graphiques
g = sns.FacetGrid(df, col='category', row='subcategory', 
                  height=4, aspect=1.2)
g.map(sns.scatterplot, 'x', 'y')

# Avec hue
g = sns.FacetGrid(df, col='category', hue='type', height=4)
g.map(sns.lineplot, 'x', 'y')
g.add_legend()

# Limite de colonnes
g = sns.FacetGrid(df, col='category', col_wrap=3, height=4)
g.map(sns.histplot, 'value')

# Partage d'axes
g = sns.FacetGrid(df, col='category', sharex=False, sharey=False)

# Personnalisation
g = sns.FacetGrid(df, col='category', height=5, aspect=1.5,
                  margin_titles=True, despine=True)
g.map_dataframe(sns.scatterplot, x='x', y='y', alpha=0.6)
g.set_axis_labels('X Label', 'Y Label')
g.set_titles(col_template='{col_name}', row_template='{row_name}')

# === PairGrid (matrice personnalisée) ===

g = sns.PairGrid(df, hue='category')
g.map_upper(sns.scatterplot)
g.map_lower(sns.kdeplot)
g.map_diag(sns.histplot)
g.add_legend()

# Personnalisation
g = sns.PairGrid(df, vars=['x', 'y', 'z'], hue='category',
                 height=3, aspect=1.2, corner=True)
g.map_upper(sns.scatterplot, alpha=0.5)
g.map_diag(sns.histplot, kde=True)
g.add_legend()

# === JointGrid (scatter + marginaux personnalisés) ===

g = sns.JointGrid(data=df, x='x', y='y', height=8)
g.plot_joint(sns.scatterplot, alpha=0.5)
g.plot_marginals(sns.histplot, kde=True)

# Avec régression
g = sns.JointGrid(data=df, x='x', y='y')
g.plot_joint(sns.regplot)
g.plot_marginals(sns.histplot)

# === Subplots matplotlib avancés ===

# Taille différente par subplot
fig = plt.figure(figsize=(12, 8))
gs = fig.add_gridspec(2, 2, width_ratios=[2, 1], height_ratios=[1, 2])

ax1 = fig.add_subplot(gs[0, :])
ax2 = fig.add_subplot(gs[1, 0])
ax3 = fig.add_subplot(gs[1, 1])

# Mosaic (matplotlib 3.3+)
fig, axes = plt.subplot_mosaic(
    [['upper left', 'upper right'],
     ['lower left', 'lower right']],
    figsize=(12, 8)
)
axes['upper left'].plot(x, y)

# Layout complexe
fig, axes = plt.subplot_mosaic(
    [['A', 'B'],
     ['A', 'C'],
     ['D', 'D']],
    figsize=(12, 10)
)


[OK] INTERACTIVITÉ

# === Matplotlib widgets ===

from matplotlib.widgets import Slider, Button, CheckButtons

fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)

# Slider
ax_slider = plt.axes([0.2, 0.1, 0.6, 0.03])
slider = Slider(ax_slider, 'Fréquence', 0.1, 10.0, valinit=1.0)

def update(val):
    freq = slider.val
    # Mettre à jour graphique
    
slider.on_changed(update)

# Button
ax_button = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(ax_button, 'Reset')

def reset(event):
    slider.reset()
    
button.on_clicked(reset)

# CheckButtons
ax_check = plt.axes([0.05, 0.4, 0.1, 0.15])
check = CheckButtons(ax_check, ['Line 1', 'Line 2'])

def toggle_line(label):
    # Toggle visibilité
    pass
    
check.on_clicked(toggle_line)

# === Événements ===

def onclick(event):
    print(f'x={event.xdata}, y={event.ydata}')
    
fig.canvas.mpl_connect('button_press_event', onclick)

def onkey(event):
    print(f'Key: {event.key}')
    
fig.canvas.mpl_connect('key_press_event', onkey)

# === Plotly (graphiques interactifs) ===

import plotly.express as px
import plotly.graph_objects as go

# Scatter interactif
fig = px.scatter(df, x='x', y='y', color='category', 
                 hover_data=['value'], title='Interactive Scatter')
fig.show()

# Line interactif
fig = px.line(df, x='date', y='value', color='category')
fig.update_traces(mode='lines+markers')
fig.show()

# Bar interactif
fig = px.bar(df, x='category', y='value', color='subcategory')
fig.show()

# 3D interactif
fig = px.scatter_3d(df, x='x', y='y', z='z', color='category')
fig.show()

# === Bokeh (alternative) ===

from bokeh.plotting import figure, show, output_file
from bokeh.io import output_notebook

output_notebook()  # Jupyter

p = figure(title='Bokeh Plot', x_axis_label='X', y_axis_label='Y')
p.circle(x, y, size=10, color='navy', alpha=0.5)
show(p)


[OK] ANIMATIONS

from matplotlib.animation import FuncAnimation

# Animation simple
fig, ax = plt.subplots()
line, = ax.plot([], [], 'b-')

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

def init():
    line.set_data([], [])
    return line,

def animate(frame):
    x = np.linspace(0, 2*np.pi, 100)
    y = np.sin(x + frame/10)
    line.set_data(x, y)
    return line,

anim = FuncAnimation(fig, animate, init_func=init,
                     frames=100, interval=50, blit=True)

plt.show()

# Sauvegarder animation
anim.save('animation.gif', writer='pillow', fps=20)
anim.save('animation.mp4', writer='ffmpeg', fps=30)

# Animation scatter
fig, ax = plt.subplots()
scat = ax.scatter([], [])

ax.set_xlim(-5, 5)
ax.set_ylim(-5, 5)

def animate(frame):
    n = 50
    x = np.random.randn(n)
    y = np.random.randn(n)
    scat.set_offsets(np.c_[x, y])
    return scat,

anim = FuncAnimation(fig, animate, frames=100, interval=100)


[OK] OPTIMISATION & PERFORMANCE

# === Données volumineuses ===

# Rasterisation (graphiques vectoriels lourds)
ax.plot(x, y, rasterized=True)

# Downsampling
from matplotlib.path import Path

# Simplification de lignes
ax.plot(x, y, path_effects=[path_effects.SimplePatchShadow()])

# === Rendu rapide ===

# Désactiver anti-aliasing
plt.rcParams['lines.antialiased'] = False
plt.rcParams['patch.antialiased'] = False

# Utiliser Agg backend (non-interactif)
import matplotlib
matplotlib.use('Agg')

# === Mémoire ===

# Fermer figures
plt.close('all')
plt.close(fig)

# Nettoyer cache
import gc
gc.collect()

# === Batch generation ===

import matplotlib
matplotlib.use('Agg')  # Backend non-interactif

for i in range(100):
    fig, ax = plt.subplots()
    ax.plot(data[i])
    plt.savefig(f'plot_{i}.png', dpi=150, bbox_inches='tight')
    plt.close(fig)  # Libérer mémoire


[OK] EXEMPLES COMPLETS

# === 1. Graphique scientifique publication ===

import numpy as np
import matplotlib.pyplot as plt

# Configuration
plt.rcParams.update({
    'font.size': 12,
    'font.family': 'serif',
    'axes.labelsize': 14,
    'axes.titlesize': 16,
    'xtick.labelsize': 12,
    'ytick.labelsize': 12,
    'legend.fontsize': 11,
    'figure.dpi': 100,
    'savefig.dpi': 300
})

# Données
x = np.linspace(0, 10, 100)
y1 = np.sin(x)
y2 = np.cos(x)
y1_err = 0.1 * np.ones_like(y1)

# Graphique
fig, ax = plt.subplots(figsize=(8, 6))

ax.errorbar(x[::5], y1[::5], yerr=y1_err[::5], 
            fmt='o', color='steelblue', capsize=3, 
            label='Données expérimentales')
ax.plot(x, y1, '-', color='steelblue', alpha=0.6, 
        label='Ajustement sinusoïdal')
ax.plot(x, y2, '--', color='coral', label='Modèle théorique')

ax.set_xlabel('Temps (s)', fontweight='bold')
ax.set_ylabel('Amplitude (V)', fontweight='bold')
ax.set_title('Analyse de signal périodique', fontweight='bold', pad=20)

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

ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)

plt.tight_layout()
plt.savefig('publication_plot.pdf', bbox_inches='tight')
plt.savefig('publication_plot.png', dpi=300, bbox_inches='tight')
plt.show()

# === 2. Dashboard multi-graphiques ===

import seaborn as sns
import pandas as pd

# Données
np.random.seed(42)
df = pd.DataFrame({
    'date': pd.date_range('2024-01-01', periods=100),
    'value': np.cumsum(np.random.randn(100)),
    'category': np.random.choice(['A', 'B', 'C'], 100),
    'metric1': np.random.randn(100),
    'metric2': np.random.randn(100)
})

# Configuration seaborn
sns.set_theme(style='whitegrid', context='notebook', font_scale=1.1)

# Dashboard
fig = plt.figure(figsize=(16, 10))
gs = fig.add_gridspec(3, 3, hspace=0.3, wspace=0.3)

# Graphique principal (timeline)
ax1 = fig.add_subplot(gs[0, :])
sns.lineplot(data=df, x='date', y='value', hue='category', ax=ax1, linewidth=2)
ax1.set_title('Évolution temporelle', fontsize=16, fontweight='bold')
ax1.set_xlabel('')

# Distributions
ax2 = fig.add_subplot(gs[1, 0])
sns.histplot(data=df, x='metric1', kde=True, ax=ax2, color='steelblue')
ax2.set_title('Distribution Métrique 1')

ax3 = fig.add_subplot(gs[1, 1])
sns.boxplot(data=df, x='category', y='value', ax=ax3, palette='Set2')
ax3.set_title('Valeurs par catégorie')

ax4 = fig.add_subplot(gs[1, 2])
sns.violinplot(data=df, x='category', y='metric2', ax=ax4, palette='muted')
ax4.set_title('Distribution Métrique 2')

# Corrélation et scatter
ax5 = fig.add_subplot(gs[2, :2])
sns.scatterplot(data=df, x='metric1', y='metric2', hue='category', 
                size='value', sizes=(20, 200), alpha=0.6, ax=ax5)
ax5.set_title('Corrélation Métriques')

# Heatmap
pivot = df.groupby(['category']).agg({
    'value': 'mean',
    'metric1': 'mean',
    'metric2': 'mean'
})
ax6 = fig.add_subplot(gs[2, 2])
sns.heatmap(pivot, annot=True, fmt='.2f', cmap='coolwarm', ax=ax6, center=0)
ax6.set_title('Moyennes par catégorie')

plt.savefig('dashboard.png', dpi=300, bbox_inches='tight')
plt.show()

# === 3. Analyse exploratoire complète ===

# Charger données
df = sns.load_dataset('iris')

# Pairplot
g = sns.pairplot(df, hue='species', diag_kind='kde', 
                 plot_kws={'alpha': 0.6, 's': 50},
                 diag_kws={'alpha': 0.7})
g.fig.suptitle('Analyse exploratoire Iris Dataset', y=1.02, fontsize=16)
plt.savefig('exploratory_pairplot.png', dpi=300, bbox_inches='tight')

# Matrice de corrélation
fig, ax = plt.subplots(figsize=(10, 8))
corr = df.select_dtypes(include=[np.number]).corr()
mask = np.triu(np.ones_like(corr, dtype=bool))
sns.heatmap(corr, mask=mask, annot=True, fmt='.2f', cmap='coolwarm',
            center=0, square=True, linewidths=1, cbar_kws={'shrink': 0.8},
            ax=ax)
ax.set_title('Matrice de corrélation', fontsize=16, fontweight='bold', pad=20)
plt.tight_layout()
plt.savefig('correlation_matrix.png', dpi=300, bbox_inches='tight')

# FacetGrid détaillé
g = sns.FacetGrid(df, col='species', height=5, aspect=1.2)
g.map_dataframe(sns.scatterplot, x='sepal_length', y='sepal_width', alpha=0.7)
g.map_dataframe(sns.kdeplot, x='sepal_length', y='sepal_width', 
                levels=5, color='red', linewidths=1.5)
g.set_axis_labels('Longueur sépale (cm)', 'Largeur sépale (cm)')
g.set_titles(col_template='{col_name}', fontweight='bold')
g.fig.suptitle('Distribution par espèce', y=1.02, fontsize=16)
plt.savefig('facet_analysis.png', dpi=300, bbox_inches='tight')


[OK] TROUBLESHOOTING

# === Problème: Figures ne s'affichent pas ===
plt.show()  # Toujours appeler à la fin

# Vérifier backend
import matplotlib
print(matplotlib.get_backend())

# Changer backend
matplotlib.use('TkAgg')  # Interactif
matplotlib.use('Agg')    # Non-interactif

# === Problème: Texte coupé ===
plt.tight_layout()
plt.savefig('plot.png', bbox_inches='tight')

# === Problème: Polices manquantes ===
# Régénérer cache de polices
import matplotlib.font_manager
matplotlib.font_manager._rebuild()

# Lister polices disponibles
from matplotlib.font_manager import FontManager
fm = FontManager()
for font in fm.ttflist:
    print(font.name)

# === Problème: Mémoire ===
plt.close('all')  # Fermer toutes les figures
import gc
gc.collect()

# === Problème: Seaborn override matplotlib ===
sns.reset_defaults()  # Réinitialiser
plt.rcdefaults()

# === Problème: Graphique flou (faible résolution) ===
plt.rcParams['figure.dpi'] = 100  # Affichage
plt.savefig('plot.png', dpi=300)  # Sauvegarde haute qualité

# === Problème: Légende hors du cadre ===
ax.legend(bbox_to_anchor=(1.05, 1), loc='upper left')
plt.savefig('plot.png', bbox_inches='tight')

# === Problème: Dates mal formatées ===
import matplotlib.dates as mdates
ax.xaxis.set_major_formatter(mdates.DateFormatter('%Y-%m-%d'))
plt.xticks(rotation=45)
plt.tight_layout()


[OK] BONNES PRATIQUES

# 1. Toujours utiliser l'API orientée objet
# [OK] Recommandé
fig, ax = plt.subplots()
ax.plot(x, y)

# [X] À éviter (pyplot)
plt.plot(x, y)

# 2. Fermer les figures après sauvegarde
fig, ax = plt.subplots()
ax.plot(x, y)
plt.savefig('plot.png')
plt.close(fig)

# 3. Configuration au début du script
plt.rcParams.update({'font.size': 12, 'figure.dpi': 100})
sns.set_theme(style='whitegrid', context='notebook')

# 4. Labels et titres explicites
ax.set_xlabel('Temps (secondes)', fontsize=12)
ax.set_ylabel('Amplitude (V)', fontsize=12)
ax.set_title('Signal mesuré', fontsize=14, fontweight='bold')

# 5. Légendes claires
ax.plot(x, y1, label='Condition A')
ax.plot(x, y2, label='Condition B')
ax.legend(loc='best')

# 6. Grilles pour la lisibilité
ax.grid(True, alpha=0.3)

# 7. Sauvegarder en vectoriel pour publications
plt.savefig('figure.pdf')  # PDF vectoriel
plt.savefig('figure.svg')  # SVG vectoriel
plt.savefig('figure.png', dpi=300)  # PNG haute résolution

# 8. tight_layout pour éviter chevauchements
plt.tight_layout()

# 9. Couleurs accessibles (colorblind-friendly)
sns.set_palette('colorblind')

# 10. Documenter vos graphiques
# Ajouter annotations, unités, sources de données


[OK] RESSOURCES

# Documentation officielle
# Matplotlib: https://matplotlib.org/stable/contents.html
# Seaborn: https://seaborn.pydata.org/
# Matplotlib gallery: https://matplotlib.org/stable/gallery/index.html
# Seaborn gallery: https://seaborn.pydata.org/examples/index.html

# Tutoriels
# Nicolas Rougier: https://github.com/rougier/matplotlib-tutorial
# Real Python: https://realpython.com/python-matplotlib-guide/

# Choix de couleurs
# ColorBrewer: https://colorbrewer2.org/
# Coolors: https://coolors.co/

# Outils complémentaires
# Plotly: https://plotly.com/python/
# Bokeh: https://bokeh.org/
# Altair: https://altair-viz.github.io/
# Plotnine (ggplot2 pour Python): https://plotnine.readthedocs.io/