0Pricing
AI Agents · Lesson

Automated Chart and Visualization Generation

Generating matplotlib/seaborn charts and returning base64 image results.

Automated Chart and Visualization Generation is a free AI Agents lesson on CoddyKit — lesson 3 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the AI Agents learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Why Agents Generate Charts

Numbers in text are hard to parse. A chart showing sales trends across 12 months communicates in seconds what a paragraph of numbers cannot.

A data analysis agent that can generate visualizations is far more useful — it produces the same output a human analyst would: both numbers and charts.

Setting Up Non-Interactive Matplotlib

By default, matplotlib opens a GUI window. In an agent (running server-side with no display), you must switch to the Agg backend — renders to file only, no GUI.

Set this before importing pyplot to avoid display errors.

import matplotlib
matplotlib.use('Agg')  # must be set BEFORE importing pyplot
import matplotlib.pyplot as plt
import os

OUTPUT_DIR = '/tmp/agent_charts'
os.makedirs(OUTPUT_DIR, exist_ok=True)

# Verify no display is needed
print('Backend:', matplotlib.get_backend())  # Should print: Agg

Bar Chart Generation

Bar charts compare values across categories. Use them for top-N rankings, category comparisons, and before/after comparisons.

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import os

OUTPUT_DIR = '/tmp/agent_charts'
os.makedirs(OUTPUT_DIR, exist_ok=True)

def create_bar_chart(labels, values, title, xlabel, ylabel, filename):
    fig, ax = plt.subplots(figsize=(10, 6))
    bars = ax.bar(labels, values, color='steelblue', edgecolor='white')

    # Add value labels on bars
    for bar, value in zip(bars, values):
        ax.text(
            bar.get_x() + bar.get_width() / 2,
            bar.get_height() + max(values) * 0.01,
            f'{value:,.0f}', ha='center', va='bottom', fontsize=9
        )

    ax.set_title(title, fontsize=14, pad=15)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    plt.xticks(rotation=45, ha='right')
    plt.tight_layout()

    path = os.path.join(OUTPUT_DIR, filename)
    plt.savefig(path, dpi=150, bbox_inches='tight')
    plt.close()
    return path

Line Chart Generation

Line charts show trends over time. Use them for time series data: daily sales, user growth over months, performance metrics over sprints.

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import os

OUTPUT_DIR = '/tmp/agent_charts'
os.makedirs(OUTPUT_DIR, exist_ok=True)

def create_line_chart(x_values, y_values, title, xlabel, ylabel, filename, label=None):
    fig, ax = plt.subplots(figsize=(10, 5))
    ax.plot(x_values, y_values, marker='o', linewidth=2, markersize=5,
            color='darkorange', label=label or ylabel)

    # Highlight min and max points
    max_idx = y_values.index(max(y_values))
    min_idx = y_values.index(min(y_values))
    ax.annotate(f'Max: {max(y_values):,.0f}',
                xy=(x_values[max_idx], y_values[max_idx]),
                xytext=(5, 10), textcoords='offset points', color='green')

    ax.set_title(title, fontsize=14, pad=15)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.grid(True, alpha=0.3)
    plt.xticks(rotation=45, ha='right')
    plt.tight_layout()

    path = os.path.join(OUTPUT_DIR, filename)
    plt.savefig(path, dpi=150, bbox_inches='tight')
    plt.close()
    return path

Scatter Chart Generation

Scatter charts reveal relationships between two numeric variables — correlation between ad spend and revenue, or order size vs. delivery time.

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import os

OUTPUT_DIR = '/tmp/agent_charts'
os.makedirs(OUTPUT_DIR, exist_ok=True)

def create_scatter_chart(x_values, y_values, title, xlabel, ylabel, filename, add_trendline=True):
    fig, ax = plt.subplots(figsize=(8, 6))
    ax.scatter(x_values, y_values, alpha=0.6, color='royalblue', edgecolors='white', s=60)

    if add_trendline and len(x_values) > 2:
        z = np.polyfit(x_values, y_values, 1)
        p = np.poly1d(z)
        x_line = sorted(x_values)
        ax.plot(x_line, p(x_line), 'r--', alpha=0.7, label='Trend')
        ax.legend()

    # Compute and show correlation
    corr = np.corrcoef(x_values, y_values)[0, 1]
    ax.text(0.05, 0.95, f'r = {corr:.2f}', transform=ax.transAxes,
            fontsize=10, verticalalignment='top')

    ax.set_title(title, fontsize=14, pad=15)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    plt.tight_layout()

    path = os.path.join(OUTPUT_DIR, filename)
    plt.savefig(path, dpi=150, bbox_inches='tight')
    plt.close()
    return path

Histogram Generation

Histograms show the distribution of a single numeric variable — how order values are distributed, customer age distribution, or response time distribution.

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import os

OUTPUT_DIR = '/tmp/agent_charts'
os.makedirs(OUTPUT_DIR, exist_ok=True)

def create_histogram(values, title, xlabel, filename, bins=20):
    fig, ax = plt.subplots(figsize=(8, 5))
    n, bin_edges, patches = ax.hist(values, bins=bins, color='mediumseagreen',
                                     edgecolor='white', alpha=0.8)

    # Add mean and median lines
    mean_val = np.mean(values)
    median_val = np.median(values)
    ax.axvline(mean_val, color='red', linestyle='--', label=f'Mean: {mean_val:.1f}')
    ax.axvline(median_val, color='blue', linestyle='--', label=f'Median: {median_val:.1f}')

    ax.set_title(title, fontsize=14, pad=15)
    ax.set_xlabel(xlabel)
    ax.set_ylabel('Frequency')
    ax.legend()
    plt.tight_layout()

    path = os.path.join(OUTPUT_DIR, filename)
    plt.savefig(path, dpi=150, bbox_inches='tight')
    plt.close()
    return path

Base64 Encoding for Agent Response

After saving the chart as a PNG file, encode it as base64 so it can be returned as a JSON-serializable string in the tool result. The caller can then render or display it.

import base64

def file_to_base64(filepath):
    with open(filepath, 'rb') as f:
        return base64.b64encode(f.read()).decode('utf-8')

def create_chart_result(chart_path, chart_type, caption):
    b64 = file_to_base64(chart_path)
    return {
        'type': 'image',
        'chart_type': chart_type,
        'format': 'png',
        'base64': b64,
        'caption': caption,
        'file_path': chart_path
    }

# Usage after creating a chart
path = create_bar_chart(['Q1', 'Q2', 'Q3', 'Q4'], [12000, 15000, 18000, 22000],
                        'Quarterly Revenue', 'Quarter', 'Revenue ($)', 'revenue.png')
result = create_chart_result(path, 'bar', 'Quarterly revenue showing consistent growth')
print(f'Chart encoded: {len(result["base64"])} chars')

Caption Generation

A chart without a caption forces the viewer to interpret it themselves. Have the LLM generate a one-sentence caption that describes the key insight visible in the chart.

def generate_chart_caption(chart_type, data_summary, question):
    prompt = f'''A {chart_type} chart was generated to answer: "{question}"

Data summary:
{data_summary}

Write a single clear sentence describing the most important insight shown in the chart.
Focus on the key finding, not on describing what type of chart it is.

Caption:'''
    caption = llm_call(prompt).strip()
    # Ensure it ends with a period
    if caption and not caption.endswith('.'):
        caption += '.'
    return caption

# Example
caption = generate_chart_caption(
    chart_type='bar',
    data_summary='Q1: $12k, Q2: $15k, Q3: $18k, Q4: $22k (83% total growth)',
    question='How did quarterly revenue trend this year?'
)
print(caption)
# -> "Revenue grew consistently each quarter, with Q4 nearly doubling Q1 figures."

Auto Chart Type Selection

Different questions call for different chart types. Rather than hardcoding which chart to use, let the LLM decide based on the data characteristics and question type.

import json

CHART_TYPE_PROMPT = '''Choose the best chart type to answer this question.

Question: {question}
Data has columns: {columns}
Data sample: {sample}

Available chart types:
- bar: comparing values across categories
- line: trends over time
- scatter: relationship between two numeric variables
- histogram: distribution of one numeric variable
- pie: composition/proportions (use sparingly)

Return JSON: {{"chart_type": "bar", "x": "category_column", "y": "value_column",
              "title": "Chart title", "xlabel": "X label", "ylabel": "Y label"}}'''

def select_chart_type(question, df):
    sample = df.head(3).to_dict(orient='records')
    response = llm_call(CHART_TYPE_PROMPT.format(
        question=question,
        columns=list(df.columns),
        sample=json.dumps(sample, default=str)
    ))
    return json.loads(response)

Unified Chart Generation Tool

Wrap all chart types into a single tool that agents can call. The tool auto-selects the chart type if not specified, creates the chart, encodes it, and generates a caption.

import pandas as pd

def generate_chart(df_name, question, chart_type=None, filename=None):
    df = dataframes.get(df_name)
    if df is None:
        return {'error': f'{df_name!r} not found'}

    # Auto-select chart type if not specified
    if not chart_type:
        spec = select_chart_type(question, df)
    else:
        spec = {'chart_type': chart_type}

    ct = spec.get('chart_type', 'bar')
    x_col = spec.get('x', df.columns[0])
    y_col = spec.get('y', df.select_dtypes(include='number').columns[0])
    title = spec.get('title', question[:60])
    fname = filename or f'chart_{ct}.png'

    x_vals = df[x_col].tolist()
    y_vals = df[y_col].tolist()

    if ct == 'bar':
        path = create_bar_chart(x_vals, y_vals, title, x_col, y_col, fname)
    elif ct == 'line':
        path = create_line_chart(x_vals, y_vals, title, x_col, y_col, fname)
    elif ct == 'histogram':
        path = create_histogram(y_vals, title, y_col, fname)
    else:
        path = create_scatter_chart(x_vals, y_vals, title, x_col, y_col, fname)

    caption = generate_chart_caption(ct, f'{x_col} vs {y_col}', question)
    return create_chart_result(path, ct, caption)

Multi-Series and Grouped Charts

When comparing multiple groups (e.g., revenue by region over time), use grouped or multi-series charts. Matplotlib supports multiple lines in one plot and grouped bar charts with offsets.

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np
import os

OUTPUT_DIR = '/tmp/agent_charts'
os.makedirs(OUTPUT_DIR, exist_ok=True)

def create_grouped_bar_chart(categories, series_data, title, xlabel, ylabel, filename):
    n_groups = len(categories)
    n_series = len(series_data)
    bar_width = 0.8 / n_series
    x = np.arange(n_groups)

    fig, ax = plt.subplots(figsize=(12, 6))
    colors = ['steelblue', 'darkorange', 'forestgreen', 'crimson']

    for i, (label, values) in enumerate(series_data.items()):
        offset = (i - (n_series - 1) / 2) * bar_width
        ax.bar(x + offset, values, bar_width, label=label,
               color=colors[i % len(colors)], alpha=0.85)

    ax.set_xticks(x)
    ax.set_xticklabels(categories, rotation=30, ha='right')
    ax.set_title(title, fontsize=14, pad=15)
    ax.set_xlabel(xlabel)
    ax.set_ylabel(ylabel)
    ax.legend()
    plt.tight_layout()

    path = os.path.join(OUTPUT_DIR, filename)
    plt.savefig(path, dpi=150, bbox_inches='tight')
    plt.close()
    return path

Knowledge Check

Why must matplotlib's Agg backend be set before importing pyplot in a server-side agent?

Recap: Automated Chart and Visualization Generation

Data agents generate charts by: setting matplotlib Agg backend (no GUI), creating charts with plt.savefig(), base64 encoding for JSON transport, and generating LLM-written captions highlighting key insights.

Four essential chart types: bar (category comparisons), line (time series), scatter (correlations with trendline), histogram (distributions). Auto chart type selection lets the LLM choose the best visualization based on data characteristics and the user's question.

Frequently asked questions

Is the “Automated Chart and Visualization Generation” lesson free?

Yes — the full text of “Automated Chart and Visualization Generation” is free to read here on the web, and the AI Agents course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the AI Agents course, upgrade to CoddyKit PRO.

What will I learn in “Automated Chart and Visualization Generation”?

Generating matplotlib/seaborn charts and returning base64 image results. You practise AI Agents with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start AI Agents?

No prior experience is required. AI Agents on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 4, so you can start here or from the beginning and move at your own pace.

How long does the “Automated Chart and Visualization Generation” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this AI Agents lesson?

Yes. Every AI Agents lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Code Interpreter Pattern for Data Analysis
  2. Pandas-Driven Data Agent Tools
  3. Automated Chart and Visualization Generation
  4. Statistical Summary Agents
← Back to AI Agents