使用Matplotlib在Django中表示数据
在使用Django构建应用程序时,你可能需要使用图形和图表来呈现数据的可视化。Matplotlib是流行的Python库之一,可以让你实现这一功能。
在这篇文章中,我们将创建一个使用Matplotlib进行数据可视化的Django应用程序样本。
先决条件
- 你应该安装有
Django。你还需要熟悉创建一个简单的Django应用程序。 - 使用
pip install matplotlibcomamnd安装matplotlib库。 - 一些Python的基本知识是很重要的。
主要收获
在这篇文章中,我们将。
- 学习如何建立一个Django应用程序。
- 学习如何使用
matplotlib,在Django应用程序中实现数据的可视化。
开始学习
我们将创建一个简单的网络应用程序来记录销售和库存情况。这个应用程序持有关于客户、产品、销售人员和销售的信息。
要创建这个项目,请移动到你喜欢的文件夹并运行下面的命令。
django-admin startproject SALES
该命令将为项目创建一个基本设置,文件夹结构如图所示。
├── db.sqlite3
├── manage.py
├── SALES
│ ├── asgi.py
│ ├── __init__.py
│ ├── settings.py
│ ├── urls.py
│ └── wsgi.py
├── static
│ ├── book.jpg
│ ├── bootstrap-5.0.0-beta1-dist
│ │ ├── css
│ │ │ ├── bootstrap.css
│ │ │ ├── bootstrap.css.map
│ │ │ ├── bootstrap-grid.css
│ ├── style.css
然后我们将创建一个名为sales 的应用程序。你可以使用python3 manage.py startapp sales 命令运行它。
我们将需要编辑我们的settings.py 文件,以注册该应用程序,并配置静态文件,使其成为应用程序的风格。
编辑SALES/settings.py 文件,在INSTALLED_APPS 数组中添加这些行,如下所示。
INSTALLED_APPS = [
'sales',
'crispy_forms'
]
CRISPY_TEMPLATE_PACK = 'bootstrap4'
STATIC_URL = '/static/'
STATICFILES_DIRS = [os.path.join(BASE_DIR, 'static')]
MEDIA_URL = '/media/'
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
在上面的代码中,我们已经注册了应用程序,并添加了django-crispy-forms ,以便为我们的表单设计样式。
我们还声明,静态文件将在/static 文件夹中处理。我们将把所有上传的媒体文件存储在一个名为/media 的文件夹中。
为了路由我们的设置,我们需要编辑urls.py 文件,如下所示。
from django.conf import settings
from django.conf.urls.static import static
from django.contrib import admin
from django.urls import path, include
urlpatterns = [path('admin/', admin.site.urls)]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
构建模型
我们现在要建立模型类。编辑models.py 文件,如下所示。
from django.contrib.auth.models import User
from django.db import models
# Create your models here.
from django.utils import timezone
from sales.utils import generate_code
# Customer class to keep track of name and image
class Customer(models.Model):
name = models.CharField(max_length=120)
logo = models.ImageField(upload_to='customers')
def __str__(self):
return self.name
# SalesPerson class to keep track of the the sales person's information
class SalesPerson(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
bio = models.TextField(default='No bio yet...')
avatar = models.ImageField(upload_to='avatars')
created = models.DateTimeField(auto_now_add=True)
updated = models.DateTimeField(auto_now=True)
def __str__(self):
return f"Salesperson {self.user.username}"
# Sale class to keep track of the sales information
class Sale(models.Model):
transaction_id = models.CharField(max_length=12, blank=True)
total_price = models.FloatField(blank=True, null=True)
customer = models.ForeignKey(Customer, on_delete=models.CASCADE)
salesman = models.ForeignKey(SalesPerson, on_delete=models.CASCADE)
created = models.DateTimeField(blank=True)
updated = models.DateTimeField(auto_now=True)
def save(self, *args, **kwargs):
if self.transaction_id == "":
self.transaction_id = generate_code()
if self.created is None:
self.created = timezone.now()
return super().save(*args, **kwargs)
def __str__(self):
return f"Sales for the amount of Kshs {self.total_price}"
- 我们要用
Sale类来表示销售数据。 - 我们已经重写了
save方法,这样如果transaction_id是空的,就会自动生成一个。 - 我们还声明如果
created time没有被定义,那么时间将被设置为current time。
我们现在需要创建generate_code 函数。让我们创建一个utils.py 文件并添加以下几行代码。
import uuid
def generate_code():
return str(uuid.uuid4()).replace('-', '').upper()[:12]
generate_code() 返回一个由12个大写字母数字字符组成的随机代码。
让我们通过在admin.py 文件中添加以下代码,将模型注册到管理站点。
from django.contrib import admin
from .models import Customer, Sale, SalesPerson
# Register your models here.
admin.site.register(Customer)
admin.site.register(Sale)
admin.site.register(SalesPerson)
通过运行python3 manage.py createsuperuser 命令创建一个superuser 。
当你登录到http://127.0.0.1:8000/admin ,你应该能够创建不同的销售记录。
注意,如果
transaction_id字段是空白的,将自动生成一个新的值。
表示数据
现在我们已经将数据添加到数据库中,让我们在我们的应用程序中表现它们。
我们将首先创建一个搜索表单,允许用户搜索特定的数据。我们将根据date,transaction,customer address, 和total price 来过滤信息。
让我们创建一个forms.py 文件来定义表单字段。
from django import forms
CHART_CHOICES = (
('#1', 'Bar Graph'),
('#2', 'Pie Chart'),
('#3', 'Line Graph')
)
RESULTS_CHOICES = (
('#1', 'Transaction'),
('#2', 'Sales Date'),
('#3', 'Customer ID'),
('#4', 'Total Price')
)
class SalesSearchForm(forms.Form):
date_from = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))
date_to = forms.DateField(widget=forms.DateInput(attrs={'type': 'date'}))
chart_type = forms.ChoiceField(choices=CHART_CHOICES)
results_by = forms.ChoiceField(choices=RESULTS_CHOICES)
该表格将允许用户选择一个日期,选择图表类型,根据上述文件中概述的CHART_CHOICES 和RESULTS_CHOICES 来分组结果。
我们需要创建一些视图来检索数据到我们的网页上。在我们的views.py 文件中,让我们对其进行如下编辑。
import pandas
from django.shortcuts import render
from django.views.generic import ListView
from .forms import SalesSearchForm
def sales(request):
search_form = SalesSearchForm(request.POST or None)
context = {
'search_form': search_form,
}
return render(request, 'sales.html', context)
sales() 函数将把搜索表单渲染到HTML文件中。
创建模板
我们需要创建一个HTML文件来容纳这些可视化的内容。
让我们首先修改模板配置上的设置,如图所示编辑settings.py 。
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(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',
],
},
},
]
这个设置将告诉Django从一个叫做templates 的文件夹中寻找HTML文件。
在与manage.py 相同的级别中创建一个名为templates 的文件夹,并在其中添加新的HTML文件。
templates 文件夹的样子应该如下。
SALES/templates
└── templates
├── base.html
├── sales.html
编辑base.html ,如下所示。
templates/base.html
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="{% static 'style.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap-5.0.0-beta1-dist/css/bootstrap.css' %}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/1.11.8/semantic.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/1.11.8/semantic.min.js"></script>
<title>Report App | {% block title %} {% endblock title %}</title>
</head>
<body>
<div class="ui secondary menu">
<a class="item"><img style="height:100px;width:100px;" src="{% static 'book.jpg' %}" alt=""></a>
<a href="#" class=" active item">Home</a>
</div>
<div class="container mb-3 mt-3">
{% block content %}
{% endblock content %}
</div>
</body>
</html>
这是基本的HTML文件,将被其他文件使用。所有常用的样式都在这里定义。
这个文件使我们能够避免重写相同的代码行。我们只需要在其他需要风格化的文件中使用{% extends 'base.html' %} 。
这个基础文件链接了应用程序中的不同路由。我们有一个路由Home ,以导航到数据表示页。
sales.html 文件应该与此类似。
{% extends 'base.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% block scripts %}
{% endblock scripts %}
{% block title %}
Home
{% endblock title %}
{% block content %}
<form action="" method="post">
{% csrf_token %}
{{search_form|crispy}}
<button class="btn btn-primary mt-3" type="submit">Search</button>
</form>
{% endblock content %}
这个文件允许我们在文件中显示我们的搜索表单。用户可以填写表格并使用search 按钮提交。
路由视图
为了看到我们的视图在运行,我们将需要配置路由。
让我们创建一个urls.py 文件,它将处理路由到我们的函数和类。
上面的映射意味着请求将由这个文件处理,然后被路由到相应的视图函数和类。
在其中加入以下几行代码。
from django.urls import path
from . import views
urlpatterns = [path('', views.sales, name='sales')]
让我们在SALES/urls.py 中为我们的sales 应用程序处理路由。
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('sales.urls'))
]
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)
让我们也编辑base.html ,以访问视图。编辑它如下。
<!DOCTYPE html>
{% load static %}
<html lang="en">
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="{% static 'style.css' %}">
<link rel="stylesheet" href="{% static 'bootstrap-5.0.0-beta1-dist/css/bootstrap.css' %}">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/1.11.8/semantic.min.css"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/semantic-ui/1.11.8/semantic.min.js"></script>
<title>Report App | {% block title %} {% endblock title %}</title>
</head>
<body>
{% url 'sales' as sales_home %}
<div class="ui secondary menu">
<a class="item"><img style="height:100px;width:100px;" src="{% static 'book.jpg' %}" alt=""></a>
<a href="{{sales_home}}" class=" active item">Home</a>
</div>
<div class="container mb-3 mt-3">
{% block content %}
{% endblock content %}
</div>
</body>
</html>
当你访问http://127.0.0.1:8000 ,你应该得到以下页面。

创建图形表示法。
现在,我们要开始建立我们网页的图形表示部分。
首先,我们需要安装pandas 库,为销售数据创建数据框架。
由此产生的数据框架将被用于用matplotlib 绘图。
现在,运行pip install pandas 来安装它。
我们将在utils.py 文件中处理绘图,它将在views.py 文件中使用,如图所示。
import pandas
from django.shortcuts import render
from django.views.generic import ListView
from django.contrib import messages
from .forms import SalesSearchForm
from .models import *
# Create your views here.
from .utils import get_chart
def sales(request):
sales_df = None
chart = None
no_data = None
search_form = SalesSearchForm(request.POST or None)
if request.method == 'POST':
date_from = request.POST.get('date_from')
date_to = request.POST.get('date_to')
chart_type = request.POST.get('chart_type')
results_by = request.POST.get('results_by')
print(date_from, date_to, chart_type)
sales_qs = Sale.objects.filter(created__date__lte=date_to, created__date__gte=date_from)
if len(sales_qs) > 0:
sales_df = pandas.DataFrame(sales_qs.values())
print(sales_df)
sales_df['created'] = sales_df['created'].apply(lambda x: x.strftime('%d/%m/%Y'))
sales_df.rename({'customer_id': 'customer', 'salesman_id': 'salesman', 'id': 'sales_id'}, axis=1,
inplace=True)
chart = get_chart(chart_type, sales_df, results_by)
sales_df = sales_df.to_html()
else:
messages.warning(request, "Apparently no data available...")
context = {
'search_form': search_form,
'sales_df': sales_df,
'chart': chart,
}
return render(request, 'sales.html', context)
- 首先,该函数检查我们是否收到了一个
POST的请求。如果是,那么我们必须初始化变量以获得date_from、date_to、chart_type和results_by。 - 图表类型和结果类型的值将是
#1,#2,取决于用户的选择。 - 如果用户选择的是饼图,那么
chart_type变量将是#2,正如我们在forms.py文件中声明的那样。 - 然后我们过滤所有在
date_from和date_to之间的销售。如果有任何销售,我们就用销售查询值创建一个数据框。 - 我们还将
created year的值重置为d/m/Y的格式。 - 我们将
customer_id重命名为customer,salesman_id重命名为salesman,id重命名为sales_id。 - 然后,我们用
utils.py中定义的函数get_chart()来初始化变量chart,该函数接收图表类型、销售数据框和结果的值。 - 最后,我们将销售数据框转换为HTML格式,这样我们就可以通过将数据框和图表传递给HTML页面来在网页上显示它。
现在让我们来处理utils.py 文件,如下图所示。
import uuid, base64
from .models import *
from io import BytesIO
from matplotlib import pyplot
def generate_code():
return str(uuid.uuid4()).replace('-', '').upper()[:12]
def get_key(res_by):
if res_by == '#1':
key = 'transaction_id'
elif res_by == '#2':
key = 'created'
elif res_by == '#3':
key = 'customer'
elif res_by == '#4':
key = 'total_price'
return key
def get_graph():
buffer = BytesIO()
pyplot.savefig(buffer, format='png')
buffer.seek(0)
image_png = buffer.getvalue()
graph = base64.b64encode(image_png)
graph = graph.decode('utf-8')
buffer.close()
return graph
def get_chart(chart_type, data, results_by, **kwargs):
pyplot.switch_backend('AGG')
fig = pyplot.figure(figsize=(10, 4))
key = get_key(results_by)
d = data.groupby(key, as_index=False)['total_price'].agg('sum')
if chart_type == '#1':
print("Bar graph")
pyplot.bar(d[key], d['total_price'])
elif chart_type == '#2':
print("Pie chart")
pyplot.pie(data=d,x='total_price', labels=d[key])
elif chart_type == '#3':
print("Line graph")
pyplot.plot(d[key], d['total_price'], color='gray', marker='o', linestyle='dashed')
else:
print("Apparently...chart_type not identified")
pyplot.tight_layout()
chart = get_graph()
return chart
当函数get_chart() 被调用时,会发生以下情况。
pyplot.switch_backend('AGG')防止在屏幕上绘图。我们想把我们的图表作为图像传递。fig变量定义了绘制的图表的尺寸。- 语句
key = get_key(results_by)根据用户选择的内容设置一个关键变量。这与RESULTS CHOICES中定义的内容相对应,在forms.py。 - 第四条语句使用总价格的总和,通过关键对我们的数据框架进行分组。
- 然后根据用户的选择来绘制图表。
- 语句
pyplot.tight_layout(),将图表的大小调整为fig - 我们最后用
get_graph()函数初始化一个图表变量并返回。该方法首先创建一个缓冲区变量作为文件对象。 - 图表以图像形式保存在缓冲区中。缓冲区的内容用
base64.b64encode()函数进行编码。然后对字节进行解码并返回。缓冲区在调用close()函数时被丢弃。
我们现在将编辑我们的sales.html 文件,以便看到我们的数据框架和图表。
{% extends 'base.html' %}
{% load static %}
{% load crispy_forms_tags %}
{% block title %}
Home
{% endblock title %}
{% block content %}
{% for message in messages %}
<div role="alert" class="alert alert-warning">
{{message}}
</div>
{% endfor %}
<form action="" method="post">
{% csrf_token %}
{{search_form|crispy}}
<button class="btn btn-primary mt-3" type="submit">Search</button>
</form>
<hr>
{% if sales_df %}
<b>Sales Dataframe</b>
{{sales_df|safe}}
<hr>
<hr>
<b>Chart</b>
<img src="data:image/png;base64, {{chart|safe}}" alt="" id="img">
{% endif %}<br>
{% endblock content %}
如果我们有一个销售数据框,我们在表格后显示它。我们使用safe 过滤器来使数据框在我们的页面上更容易阅读。
data:image/png;base64 部分负责处理显示我们图表的图像。没有这个,我们就无法看到图像。
当你填写表格时,当你选择bar chart ,你应该有这样的东西http://127.0.0.1:8000/
结论
你现在已经成功创建了一个使用matplotlib来表示数据的Django应用程序。