Python Matplotlib: Automate Data Visualisation & Charts
If you're still opening Excel every Monday morning to manually rebuild the same bar charts and line graphs, I've got good news: you're about to get that time back. Python data visualization automation with Matplotlib and Seaborn lets you write a script once, schedule it, and never touch a chart by hand again. We're talking 5β10 hours a week β reclaimed.
In this tutorial I'll walk you through everything from installation to a full end-to-end weekly sales report that pulls data from a CSV, generates three publication-quality charts, and emails them as attachments β completely automatically. Let's get into it.
Why Automate Your Charts? The Manual Reporting Problem
Most data teams and analysts spend an absurd amount of time on chart busywork: exporting data, pasting it into spreadsheets, tweaking colours, fixing axis labels, re-exporting to PNG, attaching to emails. Repeat every week. Every month. Every quarter.
The real cost isn't just time β it's consistency. Manual charts drift. Fonts change, colours shift, someone accidentally uses the wrong date range. Automated charts are pixel-perfect and reproducible every single run.
What you save by automating:
- ~2 hrs/week rebuilding weekly reports manually β 0 minutes
- ~1 hr/week reformatting and re-styling charts β 0 minutes
- ~1 hr/week emailing reports to stakeholders β 0 minutes
- Near-zero risk of copy-paste errors in charts
Python's Matplotlib is the backbone of data visualisation in Python. Seaborn sits on top of it for statistical charts. Pandas handles your data. Together, they form the most powerful free reporting stack available.
Setup: Installing Matplotlib, Seaborn, and Pandas
Open your terminal and install the three libraries you'll need:
1pip install matplotlib seaborn pandas
If you're sending automated emails, you'll also want to confirm smtplib is available β it's part of Python's standard library, so no install needed.
Verify everything installed correctly:
1import matplotlib2import seaborn3import pandas4print(matplotlib.__version__, seaborn.__version__, pandas.__version__)
I recommend using a virtual environment to keep dependencies clean:
1python -m venv venv2source venv/bin/activate # Windows: venv\Scripts\activate3pip install matplotlib seaborn pandas
Tutorial 1: Auto-Generate a Bar Chart from a CSV File
Let's start with the most common use case: reading data from a CSV and turning it into a bar chart automatically.
Sample CSV (sales_data.csv):
month,revenue January,42000 February,38500 March,51200 April,47800 May,55300 June,60100
Script: bar_chart_from_csv.py
1import pandas as pd2import matplotlib.pyplot as plt3import matplotlib.ticker as mticker45# Load data6df = pd.read_csv("sales_data.csv")78# Create figure9fig, ax = plt.subplots(figsize=(10, 6))1011# Plot bar chart12bars = ax.bar(df["month"], df["revenue"], color="#4A90D9", edgecolor="white", linewidth=0.8)1314# Add value labels on top of each bar15for bar in bars:16 height = bar.get_height()17 ax.annotate(18 f"${height:,.0f}",19 xy=(bar.get_x() + bar.get_width() / 2, height),20 xytext=(0, 6),21 textcoords="offset points",22 ha="center",23 va="bottom",24 fontsize=10,25 fontweight="bold",26 color="#333333"27 )2829# Styling30ax.set_title("Monthly Revenue β H1 2025", fontsize=16, fontweight="bold", pad=20)31ax.set_xlabel("Month", fontsize=12, labelpad=10)32ax.set_ylabel("Revenue (USD)", fontsize=12, labelpad=10)33ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f"${x:,.0f}"))34ax.set_facecolor("#F8F9FA")35fig.patch.set_facecolor("#FFFFFF")36ax.spines[["top", "right"]].set_visible(False)37ax.tick_params(axis="x", rotation=30)3839plt.tight_layout()40plt.savefig("monthly_revenue_bar.png", dpi=150, bbox_inches="tight")41plt.close()42print("Bar chart saved.")
Run it with python bar_chart_from_csv.py and you get a clean, labelled bar chart saved as a PNG. No Excel required.
Tutorial 2: Multi-Chart Sales Dashboard (Line + Pie + Bar)
Real dashboards need multiple charts on one canvas. Matplotlib's subplot system makes this straightforward. Here's a script that generates a three-panel dashboard and saves it as a single PNG.
1import pandas as pd2import matplotlib.pyplot as plt3import matplotlib.gridspec as gridspec4import numpy as np56# Sample data7months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun"]8revenue = [42000, 38500, 51200, 47800, 55300, 60100]9targets = [40000, 40000, 45000, 45000, 52000, 58000]1011categories = ["Software", "Services", "Hardware", "Support"]12category_rev = [48200, 31500, 22800, 11400]1314# Build figure with grid layout15fig = plt.figure(figsize=(16, 10), facecolor="#FFFFFF")16gs = gridspec.GridSpec(2, 2, figure=fig, hspace=0.45, wspace=0.35)1718ax1 = fig.add_subplot(gs[0, :]) # Full-width line chart (top)19ax2 = fig.add_subplot(gs[1, 0]) # Pie chart (bottom-left)20ax3 = fig.add_subplot(gs[1, 1]) # Bar chart (bottom-right)2122# --- Chart 1: Line chart β actual vs target ---23ax1.plot(months, revenue, marker="o", linewidth=2.5, color="#4A90D9", label="Actual Revenue")24ax1.plot(months, targets, marker="s", linewidth=2, linestyle="--", color="#E8734A", label="Target")25ax1.fill_between(months, revenue, targets, alpha=0.08, color="#4A90D9")26ax1.set_title("Revenue vs Target (H1 2025)", fontsize=13, fontweight="bold")27ax1.set_ylabel("Revenue (USD)")28ax1.legend(frameon=False)29ax1.set_facecolor("#F8F9FA")30ax1.spines[["top", "right"]].set_visible(False)3132# --- Chart 2: Pie chart β revenue by category ---33colors_pie = ["#4A90D9", "#E8734A", "#5CB85C", "#9B59B6"]34wedges, texts, autotexts = ax2.pie(35 category_rev,36 labels=categories,37 autopct="%1.1f%%",38 colors=colors_pie,39 startangle=140,40 pctdistance=0.8,41 wedgeprops={"edgecolor": "white", "linewidth": 2}42)43for autotext in autotexts:44 autotext.set_fontsize(9)45 autotext.set_fontweight("bold")46ax2.set_title("Revenue by Category", fontsize=13, fontweight="bold")4748# --- Chart 3: Bar chart β monthly revenue ---49bar_colors = ["#5CB85C" if r >= t else "#E8734A" for r, t in zip(revenue, targets)]50ax3.bar(months, revenue, color=bar_colors, edgecolor="white", linewidth=0.8)51ax3.set_title("Monthly Revenue (green = on target)", fontsize=11, fontweight="bold")52ax3.set_ylabel("Revenue (USD)")53ax3.set_facecolor("#F8F9FA")54ax3.spines[["top", "right"]].set_visible(False)5556# Save57plt.savefig("sales_dashboard.png", dpi=150, bbox_inches="tight")58plt.close()59print("Dashboard saved to sales_dashboard.png")
This outputs a single professional-looking PNG with all three charts laid out cleanly β ready to drop into any email or report.
Tutorial 3: Seaborn Heatmap for Correlation Analysis
When you're exploring relationships between multiple numeric variables, a correlation heatmap is the fastest way to see what's going on. Seaborn makes this a five-liner.
1import pandas as pd2import seaborn as sns3import matplotlib.pyplot as plt45# Load or build your DataFrame6df = pd.read_csv("sales_metrics.csv") # Replace with your file78# Compute correlation matrix9corr_matrix = df.select_dtypes(include="number").corr()1011# Build heatmap12fig, ax = plt.subplots(figsize=(10, 8))13sns.heatmap(14 corr_matrix,15 annot=True,16 fmt=".2f",17 cmap="coolwarm",18 center=0,19 linewidths=0.5,20 linecolor="#EEEEEE",21 annot_kws={"size": 10},22 ax=ax23)2425ax.set_title("Feature Correlation Matrix", fontsize=15, fontweight="bold", pad=16)26plt.xticks(rotation=45, ha="right", fontsize=10)27plt.yticks(rotation=0, fontsize=10)28plt.tight_layout()29plt.savefig("correlation_heatmap.png", dpi=150, bbox_inches="tight")30plt.close()31print("Heatmap saved.")
Seaborn's heatmap function handles colour scaling, annotations, and layout automatically. The coolwarm palette makes strong positive correlations (red) and strong negative correlations (blue) instantly obvious.
Styling Charts Professionally
Default Matplotlib charts look fine, but a few tweaks make them look polished and consistent with your brand.
Key styling patterns I use on every chart:
1import matplotlib.pyplot as plt23# Set a global style baseline4plt.rcParams.update({5 "font.family": "DejaVu Sans",6 "font.size": 11,7 "axes.titlesize": 14,8 "axes.titleweight": "bold",9 "axes.facecolor": "#F8F9FA",10 "figure.facecolor": "#FFFFFF",11 "axes.spines.top": False,12 "axes.spines.right": False,13 "axes.grid": True,14 "grid.color": "#DDDDDD",15 "grid.linewidth": 0.6,16})
Put this block at the top of any script and every chart in that script inherits the same clean look. No per-chart style juggling.
Custom colour palettes β define a brand palette once and reuse it:
1BRAND_COLORS = ["#4A90D9", "#E8734A", "#5CB85C", "#9B59B6", "#F39C12"]
Saving Charts to Files and Emailing Them
Saving is one line:
1plt.savefig("chart.png", dpi=150, bbox_inches="tight")
Use dpi=150 for email attachments (crisp but not massive file size). Use dpi=300 if charts are going into printed reports or PDFs.
Emailing charts as attachments uses Python's built-in smtplib and email libraries:
1import smtplib2from email.message import EmailMessage3import os45def send_report_email(chart_paths: list, recipient: str):6 msg = EmailMessage()7 msg["Subject"] = "Automated Weekly Sales Report"8 msg["From"] = os.environ["REPORT_EMAIL_FROM"]9 msg["To"] = recipient10 msg.set_content("Hi team,\n\nPlease find this week's sales charts attached.\n\nGenerated automatically.")1112 for path in chart_paths:13 with open(path, "rb") as f:14 msg.add_attachment(15 f.read(),16 maintype="image",17 subtype="png",18 filename=os.path.basename(path)19 )2021 with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:22 smtp.login(os.environ["REPORT_EMAIL_FROM"], os.environ["REPORT_EMAIL_PASSWORD"])23 smtp.send_message(msg)24 print(f"Report emailed to {recipient}")
Store credentials in environment variables, never hardcoded in your script. On Linux/Mac, add them to ~/.bashrc or a .env file loaded with python-dotenv.
Scheduling: Cron (Linux/Mac) and Task Scheduler (Windows)
Once your script is working, schedule it to run automatically.
Linux/Mac β cron job:
1# Open crontab editor2crontab -e34# Run every Monday at 7:30 AM530 7 * * 1 /path/to/venv/bin/python /path/to/weekly_report.py >> /path/to/report.log 2>&1
Windows β Task Scheduler (PowerShell):
1$action = New-ScheduledTaskAction -Execute "C:\path\to\venv\Scripts\python.exe" `2 -Argument "C:\path\to\weekly_report.py"3$trigger = New-ScheduledTaskTrigger -Weekly -DaysOfWeek Monday -At 7:30AM4Register-ScheduledTask -TaskName "WeeklySalesReport" -Action $action -Trigger $trigger
That's the full automation loop: script generates charts β saves PNGs β emails them β repeats on schedule, forever.
Full Real-World Example: Weekly Sales Report Script
Here's a complete script that ties everything together β reads from a CSV, generates three charts, and emails them automatically.
1"""2weekly_sales_report.py3Generates a 3-chart sales report from CSV and emails it every Monday.4"""56import os7import pandas as pd8import matplotlib.pyplot as plt9import matplotlib.ticker as mticker10import seaborn as sns11import smtplib12from email.message import EmailMessage13from datetime import date1415# ββ Config ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ16CSV_PATH = "sales_data.csv"17OUTPUT_DIR = "reports"18RECIPIENT = "team@yourcompany.com"19EMAIL_FROM = os.environ["REPORT_EMAIL_FROM"]20EMAIL_PASS = os.environ["REPORT_EMAIL_PASSWORD"]21REPORT_DATE = date.today().strftime("%Y-%m-%d")2223os.makedirs(OUTPUT_DIR, exist_ok=True)2425plt.rcParams.update({26 "font.family": "DejaVu Sans",27 "axes.facecolor": "#F8F9FA",28 "figure.facecolor": "#FFFFFF",29 "axes.spines.top": False,30 "axes.spines.right": False,31 "axes.grid": True,32 "grid.color": "#DDDDDD",33 "grid.linewidth": 0.6,34})3536# ββ Load data βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ37df = pd.read_csv(CSV_PATH, parse_dates=["date"])38df_weekly = df.groupby("week")["revenue"].sum().reset_index()39df_by_rep = df.groupby("sales_rep")["revenue"].sum().sort_values(ascending=False)40df_metrics = df[["revenue", "calls_made", "deals_closed", "avg_deal_size"]]4142chart_files = []4344# ββ Chart 1: Weekly revenue trend βββββββββββββββββββββββββββββββββββββββββββββ45fig, ax = plt.subplots(figsize=(11, 5))46ax.plot(df_weekly["week"], df_weekly["revenue"], marker="o",47 linewidth=2.5, color="#4A90D9")48ax.fill_between(df_weekly["week"], df_weekly["revenue"], alpha=0.12, color="#4A90D9")49ax.yaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f"${x:,.0f}"))50ax.set_title(f"Weekly Revenue Trend β {REPORT_DATE}", fontsize=14, fontweight="bold", pad=16)51ax.set_xlabel("Week")52ax.set_ylabel("Revenue (USD)")53plt.xticks(rotation=30, ha="right")54plt.tight_layout()55path1 = f"{OUTPUT_DIR}/weekly_revenue_trend.png"56plt.savefig(path1, dpi=150, bbox_inches="tight")57plt.close()58chart_files.append(path1)5960# ββ Chart 2: Revenue by sales rep βββββββββββββββββββββββββββββββββββββββββββββ61fig, ax = plt.subplots(figsize=(10, 6))62colors = ["#4A90D9" if i == 0 else "#A8C8E8" for i in range(len(df_by_rep))]63ax.barh(df_by_rep.index, df_by_rep.values, color=colors, edgecolor="white")64ax.xaxis.set_major_formatter(mticker.FuncFormatter(lambda x, _: f"${x:,.0f}"))65ax.set_title("Revenue by Sales Rep (This Week)", fontsize=14, fontweight="bold", pad=16)66ax.set_xlabel("Total Revenue (USD)")67plt.tight_layout()68path2 = f"{OUTPUT_DIR}/revenue_by_rep.png"69plt.savefig(path2, dpi=150, bbox_inches="tight")70plt.close()71chart_files.append(path2)7273# ββ Chart 3: Correlation heatmap βββββββββββββββββββββββββββββββββββββββββββββββ74fig, ax = plt.subplots(figsize=(8, 6))75sns.heatmap(76 df_metrics.corr(), annot=True, fmt=".2f",77 cmap="coolwarm", center=0,78 linewidths=0.5, linecolor="#EEEEEE",79 annot_kws={"size": 10}, ax=ax80)81ax.set_title("Sales Metrics Correlation", fontsize=14, fontweight="bold", pad=16)82plt.tight_layout()83path3 = f"{OUTPUT_DIR}/metrics_correlation.png"84plt.savefig(path3, dpi=150, bbox_inches="tight")85plt.close()86chart_files.append(path3)8788print(f"Generated {len(chart_files)} charts.")8990# ββ Email ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ91msg = EmailMessage()92msg["Subject"] = f"Weekly Sales Report β {REPORT_DATE}"93msg["From"] = EMAIL_FROM94msg["To"] = RECIPIENT95msg.set_content(96 f"Hi team,\n\n"97 f"Attached are this week's automated sales charts ({REPORT_DATE}).\n\n"98 f"Charts included:\n"99 f" 1. Weekly revenue trend\n"100 f" 2. Revenue by sales rep\n"101 f" 3. Sales metrics correlation heatmap\n\n"102 f"Generated automatically by weekly_sales_report.py"103)104105for path in chart_files:106 with open(path, "rb") as f:107 msg.add_attachment(108 f.read(), maintype="image", subtype="png",109 filename=os.path.basename(path)110 )111112with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:113 smtp.login(EMAIL_FROM, EMAIL_PASS)114 smtp.send_message(msg)115116print(f"Report emailed to {RECIPIENT} β")
Schedule this with cron on Monday mornings and your weekly report runs itself. Every stakeholder gets accurate, consistently styled charts without anyone touching a spreadsheet.
Best Practices & Pro Tips
A few things I've learned from running these scripts in production:
- Always use
plt.close()after saving. If you're generating dozens of charts in a loop and forget this, Matplotlib holds them all in memory and your script gets slower and slower until it crashes. - Use
bbox_inches="tight"on everysavefigcall. Without it, axis labels get clipped and charts look broken. - Parameterise your date ranges. Pass
--startand--endas CLI arguments withargparseso you can regenerate historical reports without editing the script. - Log to a file when running via cron. Redirect stdout to a log file (
>> report.log 2>&1) so you can debug failures that happen overnight. - Test email credentials in a separate script before integrating. Nothing worse than a chart generation script that works perfectly but silently fails to send because of an SMTP config issue.
Conclusion
Python data visualization automation with Matplotlib and Seaborn isn't just a time-saver β it's a fundamentally better way to run reporting. Your charts are consistent, your process is repeatable, and your Monday mornings are free for actual analysis instead of busywork.
We covered the full stack today: generating bar charts from CSVs, building multi-panel dashboards, creating seaborn heatmaps, styling professionally, emailing attachments, and scheduling with cron. The full weekly sales report script at the end is production-ready β drop in your real CSV columns and email credentials and it just works.
Start with Tutorial 1, get comfortable with the pattern, then build up to the full report script. Once you've automated one report, you'll automate all of them.
Frequently Asked Questions
Can I automate charts from a database instead of a CSV?
Absolutely. Replace pd.read_csv() with a SQLAlchemy or psycopg2 query β Pandas reads directly from SQL into a DataFrame with pd.read_sql(). The rest of the charting code is identical.
How do I make Matplotlib charts look like Seaborn's default style?
Add plt.style.use("seaborn-v0_8-whitegrid") at the top of your script. This applies Seaborn's clean grid style to all Matplotlib charts without importing Seaborn itself.
What's the best format to save charts for email attachments β PNG or PDF?
Use PNG for email attachments (dpi=150 keeps file size reasonable). Use PDF for printed reports or documents where you need vector scaling. Use SVG if the charts are going into web pages.
Related articles: Automate Excel Reports with Python, Automate Weekly Email Reports with Python, Automate Data Validation with Python
Sponsored Content
Interested in advertising? Reach automation professionals through our platform.
