随着数据可视化技术的发展,越来越多的企业开始重视数据的直观展示,以便更好地理解业务状况和发展趋势。本文将通过一个实战项目,展示如何使用Python Flask框架结合前端技术,创建一个螺蛳粉销售数据的大屏展示系统。我们将使用MySQL数据库存储销售数据,并利用ECharts等图表库实现数据的可视化。
项目概述
本项目的主要目标是开发一个Web应用程序,该程序能够从MySQL数据库中读取螺蛳粉的销售数据,并以图表的形式展示这些数据。具体功能包括:
- 完成数据大屏的基本布局。
- 实现柱状图、折线图和地图的数据显示。
- 统计不同口味螺蛳粉的销量,并以饼图形式展示。
- 使用词云图展示螺蛳粉销售的城市分布。
- 动态更新大屏上的销售数据。
- 添加交互式元素,比如下拉菜单,允许用户选择查看特定口味的数据
技术栈
- 后端:Python Flask
- 前端:HTML, CSS, JavaScript (jQuery)
- 数据库:MySQL
- 图表库:ECharts
- 数据处理:Pandas
开发环境准备
确保你的开发环境中已经安装了以下软件:
- Python 3.x
- MySQL Server
- Flask
- Pandas
- ECharts
数据库设计
为了方便演示,我们假设已经有了两个表:sale_products
和 national_network
。sale_products
表存储了不同口味螺蛳粉的销售记录,而 national_network
表则包含了螺蛳粉在全国各地的销售网点信息。
sale_products 表结构
national_network 表结构
后端开发
初始化Flask应用
首先,我们需要创建一个基本的Flask应用来处理HTTP请求。
from flask import Flask, jsonify, request
import pymysql
import pandas as pd
app = Flask(__name__)
数据库连接配置:
db_config = {
'host': 'localhost',
'port': 3306,
'user': 'root',
'password': 'password',
'database': 'sales_db'
}
def connect_db():
return pymysql.connect(**db_config)
@app.route('/taste', methods=['GET'])
def get_taste_sales():
# 这里添加查询逻辑,统计不同口味螺蛳粉的销量
pass
@app.route('/today', methods=['GET'])
def get_today_sales():
# 查询最后一天的销量
pass
@app.route('/total', methods=['GET'])
def get_total_sales():
# 查询累计销量
pass
if __name__ == '__main__':
app.run(debug=True)
实现数据查询逻辑
接下来,我们需要实现上述路由中的数据查询逻辑。这里以统计不同口味螺蛳粉的销量为例:
@app.route('/taste', methods=['GET'])
def get_taste_sales():
conn = connect_db()
query = """
SELECT taste, SUM(sales) AS total_sales
FROM sale_products
WHERE date BETWEEN '2021-09-01' AND '2021-09-23'
GROUP BY taste
"""
df = pd.read_sql(query, conn)
conn.close()
return jsonify(df.to_dict(orient='records'))
地图数据路由
为了实现地图的交互功能,我们需要添加一个新的路由来处理地图数据的查询。
@app.route('/map', methods=['GET'])
def get_map_data():
product_type = request.args.get('product_type', 'all')
conn = connect_db()
if product_type == 'all':
query = """
SELECT province, SUM(sales) AS total_sales
FROM sale_products
GROUP BY province
"""
else:
query = f"""
SELECT province, SUM(sales) AS total_sales
FROM sale_products
WHERE taste = '{product_type}'
GROUP BY province
"""
df = pd.read_sql(query, conn)
conn.close()
return jsonify(df.to_dict(orient='records'))
前端开发
创建HTML页面
在项目的模板目录下创建一个HTML文件,用于展示数据大屏。(html文件)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>螺蛳粉销售数据分析</title>
<!-- 引入ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.1/dist/echarts.min.js"></script>
</head>
<body>
<!-- 数据大屏布局 -->
<div id="main" style="width: 100%; height: 600px;"></div>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<script>
// 使用Ajax请求获取数据
$.get('/taste', function(data) {
var chart = echarts.init(document.getElementById('main'));
var option = {
// 配置图表选项
};
chart.setOption(option);
});
</script>
</body>
</html>
实现图表显示
根据不同的图表类型,我们需要配置相应的ECharts选项。这里以饼图为例:
$.get('/taste', function(data) {
var chart = echarts.init(document.getElementById('main'));
var option = {
title: {
text: '螺蛳粉口味销量占比',
subtext: '2021-09-01 至 2021-09-23',
left: 'center'
},
tooltip: {
trigger: 'item'
},
series: [
{
name: '销量',
type: 'pie',
radius: '50%',
data: data.map(item => ({value: item.total_sales, name: item.taste})),
emphasis: {
itemStyle: {
shadowBlur: 10,
shadowOffsetX: 0,
shadowColor: 'rgba(0, 0, 0, 0.5)'
}
}
}
]
};
chart.setOption(option);
});
扩展功能
动态数据更新
为了让数据大屏上的数据实时更新,我们可以使用AJAX周期性地向后端发送请求,获取最新的数据。
地图交互
对于地图部分,可以通过监听下拉菜单的变化事件,动态改变发送给后端的请求参数,从而更新地图上显示的数据。
$('#product-type').change(function() {
var selectedType = $(this).val();
$.get(`/map?product_type=${selectedType}`, function(data) {
updateMap(data);
});
});
为了使数据大屏的界面更加美观和用户友好,我们可以添加一些CSS样式来美化页面。以下是HTML文件,包括了CSS样式:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>螺蛳粉销售数据分析</title>
<!-- 引入ECharts -->
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.1/dist/echarts.min.js"></script>
<!-- 引入jQuery -->
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
}
.container {
display: flex;
justify-content: space-around;
align-items: center;
height: 100vh;
padding: 20px;
}
.chart {
width: 45%;
height: 500px;
background-color: white;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
border-radius: 8px;
padding: 20px;
box-sizing: border-box;
}
.chart h2 {
margin-top: 0;
font-size: 1.5em;
color: #333;
}
.chart canvas {
width: 100%;
height: 100%;
}
#product-type {
position: fixed;
top: 20px;
left: 20px;
padding: 10px;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1em;
}
#sales-numbers {
position: fixed;
top: 20px;
right: 20px;
padding: 10px;
background-color: white;
border: 1px solid #ccc;
border-radius: 4px;
font-size: 1em;
}
#sales-numbers p {
margin: 0;
font-weight: bold;
}
</style>
</head>
<body>
<div class="container">
<div class="chart" id="bar-chart">
<h2>柱状图</h2>
</div>
<div class="chart" id="line-chart">
<h2>折线图</h2>
</div>
<div class="chart" id="pie-chart">
<h2>饼图</h2>
</div>
<div class="chart" id="map-chart">
<h2>地图</h2>
</div>
</div>
<select id="product-type">
<option value="all">所有</option>
<option value="spicy">加辣款</option>
</select>
<div id="sales-numbers">
<p>今日销量: <span id="today-sales">0</span></p>
<p>累计销量: <span id="total-sales">0</span></p>
</div>
<script>
function fetchAndDisplayChart(url, chartId, chartType, dataProcessor) {
$.get(url, function(data) {
var chart = echarts.init(document.getElementById(chartId));
var processedData = dataProcessor(data);
var option = {
title: { text: chartType },
tooltip: {},
xAxis: { data: processedData.categories },
yAxis: {},
series: [{ type: 'bar', data: processedData.values }]
};
if (chartType === '饼图') {
option.series = [{
type: 'pie',
data: processedData.pieData
}];
} else if (chartType === '地图') {
option.geo = {
map: 'china',
roam: true,
zoom: 1.2
};
option.series = [{
type: 'map',
map: 'china',
label: { show: true },
data: processedData.mapData
}];
}
chart.setOption(option);
});
}
function processBarChartData(data) {
return {
categories: data.map(item => item.taste),
values: data.map(item => item.total_sales)
};
}
function processLineChartData(data) {
return {
categories: data.map(item => item.date),
values: data.map(item => item.sales)
};
}
function processPieChartData(data) {
return {
pieData: data.map(item => ({ value: item.total_sales, name: item.taste }))
};
}
function processMapData(data) {
return {
mapData: data.map(item => ({ name: item.province, value: item.total_sales }))
};
}
$(document).ready(function() {
fetchAndDisplayChart('/taste', 'pie-chart', '饼图', processPieChartData);
fetchAndDisplayChart('/sales/bar', 'bar-chart', '柱状图', processBarChartData);
fetchAndDisplayChart('/sales/line', 'line-chart', '折线图', processLineChartData);
fetchAndDisplayChart('/map', 'map-chart', '地图', processMapData);
$('#product-type').change(function() {
var selectedType = $(this).val();
fetchAndDisplayChart(`/map?product_type=${selectedType}`, 'map-chart', '地图', processMapData);
});
});
// 动态更新今日销量和累计销量
function updateSalesNumbers() {
$.get('/today', function(data) {
$('#today-sales').text(data[0].today_sales);
});
$.get('/total', function(data) {
$('#total-sales').text(data[0].total_sales);
});
}
$(document).ready(function() {
updateSalesNumbers();
setInterval(updateSalesNumbers, 60000); // 每分钟更新一次
});
</script>
</body>
</html>
CSS 样式解释
-
全局样式:
body
设置了字体、背景颜色、去除默认的内外边距。.container
使用 Flexbox 布局,使得图表容器均匀分布在页面上,并且居中对齐。
-
图表容器样式:
.chart
设置了宽度、高度、背景颜色、阴影效果、圆角、内边距。.chart h2
设置了图表标题的样式,包括字体大小和颜色。.chart canvas
确保图表绘制区域占满整个容器。
-
下拉菜单样式:
#product-type
设置了固定位置、内边距、边框、圆角和字体大小。
-
销售数据显示样式:
#sales-numbers
设置了固定位置、内边距、背景颜色、边框、圆角和字体大小。#sales-numbers p
设置了段落的样式,包括去除默认的上下边距和加粗字体。
这是最是出来的效果图
结语
通过以上步骤,我们成功构建了一个螺蛳粉销售数据分析的大屏展示系统。这个系统不仅可以帮助我们更直观地了解不同口味螺蛳粉的销售情况,还能通过动态的数据更新和交互功能提升用户体验。希望这篇文章对你有所帮助!