Skip to main content
01 September, 2025
# Topics

Turn Spreadsheets into Visual Dashboards

01 September, 2025

Want to visually represent your data from spreadsheets in a dashboard?

I built a free, lightweight Python script that is designed to automatically generate interactive dashboards from your data files (CSV or Excel). Instead of manually writing code to build charts, you simply provide:

  • A CSV or XLSX file containing your dataset.
  • A short instruction or description of the type of charts/visuals you want.
  • (Optional) The name of the output HTML file.

The script then uses the power of an LLM (Large Language Model) combined with Plotly + Dash to:

  • Analyze your dataset.
  • Dynamically generate Python plotting code tailored to your file.
  • Render a fully interactive dashboard in your browser, where you can explore charts, hover for tooltips, zoom in/out, and filter your data.

Benefits

  • No coding required – just describe what you want, and the script does the heavy lifting.
  • Supports multiple data formats (CSV and Excel).
  • Customizable output – you control the look and type of charts through natural language instructions.
  • Interactive dashboards – unlike static charts, you can drill down into your data dynamically.
  • Time-saving – great for analysts, small businesses, or anyone who needs to quickly visualize data without building a full BI solution.

Python Script:

import os
import sys
import pandas as pd
import plotly.express as px
import plotly.graph_objects as go
import openai
from openai import OpenAI
import json
import traceback
import webbrowser
import threading
import itertools
import time

# --------------------------
# CONFIG: Set your API Key
# --------------------------
API_KEY = os.getenv("OPENAI_API_KEY")
client = OpenAI(api_key=API_KEY)
openai.api_key = API_KEY

# --------------------------
# Spinner helper for terminal
# --------------------------
class Spinner:
    def __init__(self, message="Processing"):
        self.message = message
        self._running = False
        self._thread = None

    def start(self):
        self._running = True
        self._thread = threading.Thread(target=self._spin)
        self._thread.start()

    def _spin(self):
        for c in itertools.cycle('|/-\\'):
            if not self._running:
                break
            print(f'\r{self.message}... {c}', end='', flush=True)
            time.sleep(0.1)
        print('\r', end='', flush=True)

    def stop(self):
        self._running = False
        if self._thread is not None:
            self._thread.join()

# --------------------------
# Helper functions
# --------------------------
def sanitize_dataframe(df: pd.DataFrame) -> pd.DataFrame:
    """Convert dict/list columns to strings to avoid unhashable errors."""
    df_copy = df.copy()
    for col in df_copy.columns:
        if df_copy[col].apply(lambda x: isinstance(x, (dict, list))).any():
            df_copy[col] = df_copy[col].astype(str)
    return df_copy

def generate_dashboard_code(df: pd.DataFrame, instructions: str = "", error_feedback: str = None) -> str:
    """
    Ask the LLM to generate Python code for a dashboard given a DataFrame.
    If error_feedback is provided, the LLM will attempt to fix the previous code.
    """
    df = sanitize_dataframe(df)
    schema = df.dtypes.apply(lambda x: str(x)).to_dict()
    row_count = len(df)

    # Safely handle categorical summary
    cat_summary = {}
    if any(df.select_dtypes(include=["object", "category"]).columns):
        cat_summary = {
            col: df[col].unique()[:5].tolist()
            for col in df.select_dtypes(include=["object", "category"]).columns
        }

    # Safely handle numeric summary
    if any(df.select_dtypes(include="number").columns):
        num_summary = df.describe(include="number").to_dict()
    else:
        num_summary = {}

    sample_rows = df.head(5).to_dict(orient="records")

    description = f"""
Dataset schema: {schema}
Row count: {row_count}
Sample rows: {sample_rows}
Categorical example values: {cat_summary}
Numeric summary stats: {num_summary}
"""

    error_note = (
        f"\nPrevious code failed with error:\n{error_feedback}\nPlease fix it and ensure it runs correctly with 'df'."
        if error_feedback
        else ""
    )

    prompt = f"""
You are a Python data visualization assistant.
Generate Python code using matplotlib, plotly, or dash that creates an HTML dashboard visualization of the DataFrame 'df'.
Use 'df' directly; do not invent totals.
If you generate pie charts in subplots, use specs=[{{'type':'domain'}}].

{description}

Extra instructions: {instructions}
{error_note}

Requirements:
- Output must be a variable called 'html_output' containing HTML
- Do not call plt.show() or app.run_server()
- Be fully self-contained and executable
"""

    response = openai.chat.completions.create(
        model="gpt-4.1",
        messages=[
            {"role": "system", "content": "You generate raw Python code with no markdown formatting or comments. Return only executable code."},
            {"role": "user", "content": prompt},
        ],
        response_format={"type": "text"}
    )

    return response.choices[0].message.content


def create_dashboard(df: pd.DataFrame, instructions: str = "", output_file: str = "dashboard.html", max_retries: int = 3):
    """
    Generate dashboard HTML by running the code from the LLM safely.
    Retries if execution fails, sending the traceback back to the LLM.
    """
    df = sanitize_dataframe(df)
    error_feedback = None

    for attempt in range(1, max_retries + 1):
        spinner = Spinner("Generating dashboard code with LLM")
        spinner.start()
        try:
            code = generate_dashboard_code(df, instructions, error_feedback)
        finally:
            spinner.stop()

        print(f"\n--- Attempt {attempt} ---\nGenerated code:\n{code}\n")
        
        local_vars = {"df": df, "pd": pd, "px": px, "go": go}
        try:
            exec(code, globals(), local_vars)
            html_output = local_vars.get("html_output", "No dashboard generated")
            with open(output_file, "w", encoding="utf-8") as f:
                f.write(html_output)
            print(f"✅ Dashboard saved to {output_file}")

            # Auto-open in default browser
            abs_path = os.path.abspath(output_file)
            webbrowser.open(f"file://{abs_path}")
            print(f"🌐 Dashboard opened in browser: {abs_path}")

            return html_output
        except Exception:
            error_feedback = traceback.format_exc()
            print(f"⚠️ Attempt {attempt} failed with error:\n{error_feedback}\n")

    print("❌ All attempts failed. Generated code could not be executed.")
    return "Dashboard generation failed."


# --------------------------
# Main: handle command-line arguments
# --------------------------
if __name__ == "__main__":
    if len(sys.argv) < 3:
        print("Usage: python generate_charts.py   [output_file]")
        sys.exit(1)

    file_path = sys.argv[1]
    instructions = sys.argv[2]
    output_file = sys.argv[3] if len(sys.argv) >= 4 else "dashboard.html"

    # Load file depending on extension
    if file_path.lower().endswith(".csv"):
        df = pd.read_csv(file_path)
    elif file_path.lower().endswith((".xlsx", ".xls")):
        df = pd.read_excel(file_path)
    else:
        print("Unsupported file type. Please provide a CSV or Excel file.")
        sys.exit(1)

    # Sanitize
    df = sanitize_dataframe(df)

    # Generate dashboard
    create_dashboard(df, instructions=instructions, output_file=output_file, max_retries=3)