How to create interactive charts in Django with Plotly

How to create interactive charts in Django with Plotly

Build interactive Gantt charts in Django using Plotly and pandas. Step-by-step tutorial covering models, views, templates, and data visualization setup.

Plotly is an open-source Python library for rendering interactive charts in the browser. It supports over 40 chart types covering statistical, financial, geographic, scientific, and 3D use cases, and it integrates cleanly with Django through its offline rendering mode.

This tutorial covers integrating Plotly into a Django application by building a project tracker that visualizes task timelines as an interactive Gantt chart.

The full source code is on GitHub.

Prerequisites

  • Python 3 installed
  • A basic understanding of Django

Set up the project

Create a virtual environment to isolate dependencies from your global Python installation.

python -m venv env

Activate it:

source env/bin/activate

Install Django:

pip install django

Create a new Django project:

django-admin startproject django_plotly

Navigate into the project directory and create a Django app:

cd django_plotly
django-admin startapp charts

Add the app to INSTALLED_APPS in settings.py:

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'charts',
]

Install Plotly and pandas:

pip install plotly pandas

Create the model

The model tracks project tasks with a name, start date, finish date, assigned user, and week number.

In charts/models.py:

from django.db import models
from django.contrib.auth.models import User

class Chart(models.Model):
    name = models.CharField(max_length=200)
    start_date = models.DateField()
    responsible = models.ForeignKey(User, on_delete=models.CASCADE)
    week_number = models.CharField(max_length=2, blank=True)
    finish_date = models.DateField()

    def __str__(self):
        return str(self.name)

    def save(self, *args, **kwargs):
        if self.week_number == "":
            self.week_number = self.start_date.isocalendar()[1]
        super().save(*args, **kwargs)

The save override automatically calculates the ISO week number from start_date if none is provided.

Register the model in charts/admin.py:

from django.contrib import admin
from .models import Chart

admin.site.register(Chart)

Run migrations:

python manage.py makemigrations
python manage.py migrate

Create a superuser to access the Django admin:

python manage.py createsuperuser

Start the server and log in at http://localhost:8000/admin:

python manage.py runserver

Django admin dashboard

Create the view

The view queries all chart entries, formats them as a pandas DataFrame, and passes a rendered Plotly timeline to the template.

Create django_plotly/views.py:

from django.shortcuts import render
from charts.models import Chart
import pandas as pd
from plotly.offline import plot
import plotly.express as px

def index(request):
    qs = Chart.objects.all()
    projects_data = [
        {
            'Project': x.name,
            'Start': x.start_date,
            'Finish': x.finish_date,
            'Responsible': x.responsible.username
        } for x in qs
    ]
    df = pd.DataFrame(projects_data)
    fig = px.timeline(
        df, x_start="Start", x_end="Finish", y="Project", color="Responsible"
    )
    fig.update_yaxes(autorange="reversed")
    gantt_plot = plot(fig, output_type="div")
    context = {'plot_div': gantt_plot}
    return render(request, 'index.html', context)

What each part does:

  • Chart.objects.all() retrieves every project entry from the database
  • The list comprehension shapes the queryset into a format pandas can consume
  • px.timeline generates the Gantt chart — x_start and x_end define the horizontal axis, y sets the row labels, and color assigns distinct colors per responsible user
  • plot(fig, output_type="div") renders the chart as an HTML <div> string, which the template injects directly into the page

Create the templates

Create a templates directory in the project root. Inside it, add two files: base.html and index.html.

base.html provides the page skeleton with Bootstrap for basic styling:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">
    <title>Plotly Chart</title>
</head>
<body>
    <div class="container mt-3">
        {% block content %}
        {% endblock content %}
    </div>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
</body>
</html>

index.html renders the Plotly chart div:

{% extends "base.html" %}
{% block content %}
    {% autoescape off %}
        {{ plot_div }}
    {% endautoescape %}
{% endblock content %}

Update TEMPLATES in settings.py to point to the templates directory:

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [ BASE_DIR / 'templates' ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

Configure URLs

Update django_plotly/urls.py:

from django.contrib import admin
from django.urls import path
from .views import index

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', index)
]

Add data and test

In the Django admin, add a few project entries with different users, start dates, and finish dates.

Web project entry in Django admin

Django project entry in Django admin

Start the server:

python manage.py runserver

Visit http://localhost:8000 to see the interactive Gantt chart with each project plotted as a timeline bar, color-coded by the responsible user.

Completed interactive Gantt chart rendered with Plotly

Where to go next

This tutorial covers the core integration pattern. From here you can extend the project with date range filters, user authentication for the chart view, or additional Plotly chart types. The Plotly Python documentation covers all 40+ chart types with examples.

FAQs

1, What is Plotly and how does it work with Django?

Plotly is a Python library that generates interactive, browser-based charts. In Django, you generate a chart figure using Plotly's Python API, render it as an HTML <div> string using plotly.offline.plot with output_type="div", and pass that string to a Django template. The template injects it directly into the page with {% autoescape off %}.

2, What is a Gantt chart used for?

A Gantt chart visualizes a project schedule by plotting tasks as horizontal bars along a timeline. Each bar shows a task's start and end date. They are commonly used in project management to track task progress and resource assignment across time.

3, What does output_type="div" do in Plotly?

Setting output_type="div" in plotly.offline.plot returns the chart as a self-contained HTML string instead of opening it as a file. This string includes the chart's JavaScript and can be injected directly into a Django template, which is what makes offline Plotly rendering work in server-side web frameworks.

4, Why do I need pandas to use Plotly in Django?

Plotly's high-level plotly.express functions like px.timeline expect data as a pandas DataFrame. Pandas handles the data structuring so Plotly can map columns directly to chart axes and attributes. For simple datasets you can also pass dictionaries or lists directly to lower-level Plotly graph objects without pandas.

5, Can I use Plotly with Django REST Framework?

Yes. You can generate Plotly figures server-side and return the chart JSON using fig.to_json(), then render it on the frontend using Plotly.js. This approach works well when your frontend is a JavaScript SPA consuming a DRF API.

About author

Asjad Khan is a Developer Advocate and Technical Writer passionate about building communities and making complex technologies simple and accessible. With experience in creating technical documentation, tutorials, and hands-on demos, he bridges the gap between engineering teams and developers by delivering clear, developer-first content. He has contributed to open-source projects, hosted workshops and hackathons, and actively engages with communities to drive adoption and learning. When not creating content or coding, Asjad can usually be found watching football and exploring new ideas in tech

Book A Call!

Reach Your Technical Audience And Drive Product Adoption.

We are engineers, developer advocates, and marketers passionate about creating lasting value for SaaS teams. Partner with us to create the human-written developer marketing, SEO, demand-gen, and documentation content.

Get started

*35% less cost, risk-free, no lock-in.

Logo 1
Logo 2
Logo 3
Logo 4
Logo 5
Logo 6
Logo 7
Logo 8
Logo 9
Logo 10
Logo 11
Logo 12
Logo 13
Logo 14
Logo 15
Logo 16
Logo 17
Logo 18