引言
Flask是一个轻量级的web框架,简单、易上手,在我个人搭建的项目里,也正是采用了flask框架进行建设。在使用echrts的过程中,很重要的一步就是与后台数据进行结合,这里继续讨论一下怎样通过flask框架,从mysql数据库引入数据制作echarts图表。
在实际的业务场景中,我们可能需要统计某几个月的销售情况或者是员工们在一段时期的绩效表现。面对这种情景,一方面,我们需要观察每个月的总量变化,另一方面我们需要观察这段时间下来,谁是总量上的冠军。这对前者,我们可以用堆叠柱状图 ,后者可以用矩形树状图。
我改编了一下我工作中碰到的业务场景。现在我们公司有许多练习生,根据他们在工作中的表现,要对他们进行加分,并进行奖励。现在我已经把数据存进了mysql数据库的employee表中。

Flask后端
这里因为我们关注的是图表的制作,所以简化flask框架的搭建过程,涉及服务器的后端操作都写在一个app.py文件内。

首先写一个最基本的Flask框架。
python
from flask import Flask
# 创建flask
app = Flask(__name__)
@app.route("/")
def hello():
return "hello"
# 打开debug模式,方便调试
if __name__ == "__main__":
app.run(debug=True)
完成这一步后,一个简单的flask程序就创建成功了,在终端中打开app.py所处的文件夹,输入python app.py
,就可以启动服务。

在浏览器的地址栏输入 http://127.0.0.1:5000 访问页面可以看到hello。
不过,此时我们还没有配置好数据库。下面讲一下如何配置数据库,在我的项目中,我用SQLAlchemy进行数据库操作,代码中不用直接写SQL语句,而是通过对象来操作数据库,可以使数据库操作变得更加简单清晰。
from flask_sqlalchemy import SQLAlchemy
配置数据库: 这里的HOSTNAME = "127.0.0.1
"代表服务器在本机上,如果数据库已经部署自服务器上了,就写服务器的公网IP;PORT = 3306,mysql的默认端口是3306;DATABASE填写数据库名称.
python
HOSTNAME = "127.0.0.1"
PORT = 3306
USERNAME = "root"
PASSWORD = "" #密码
DATABASE = "" #数据库名称
DB_URI = f"mysql+pymysql://{ USERNAME }:{ PASSWORD }@{ HOSTNAME }:{ PORT }/{ DATABASE }?charset=utf8mb4"
app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI
db = SQLAlchemy(app)
对数据库进行映射,这里要注意跟数据库表中的数据对应:
python
class employee(db.Model):
__tablename__ = "employee"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100))
time = db.Column(db.DateTime)
item = db.Column(db.String(100))
score = db.Column(db.Float)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"time": self.time,
"item": self.item,
"score": self.score,
}
下面要向前端发送请求,在flask中,@app.route()会将URL绑定到视图函数中,括号中的代码主要有两个作用。一个是作为一个在浏览器输入的链接让我们访问视图界面,这里可以在结尾用.html进行区别;另一个是让我们实现前后端的交互功能。在我们的代码中,@app.route("/index.html")
用来访问一个视图页面,这里用 return render_template("index.html")
进行重定向,index.html是一个视图页面的代码,放在template文件夹下;@app.route("/data")
在后续代码中用来做前后端交互的相关功能,这里要从数据库取出数据并转化为json格式进行发送。
python
@app.route("/index.html")
def to_index():
return render_template("index.html")
@app.route("/data")
def get_data():
query = db.session.query(
employee.name, employee.time, employee.item, employee.score
)
datas = [
{"name": items[0], "time": items[1], "item": items[2], "score": items[3]}
for items in query
]
return jsonify(data=datas)
后端完整代码:
python
from flask import Flask, render_template, session, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
HOSTNAME = "127.0.0.1"
PORT = 3306
USERNAME = "root"
PASSWORD = ""
DATABASE = "bisystem"
DB_URI = f"mysql+pymysql://{ USERNAME }:{ PASSWORD }@{ HOSTNAME }:{ PORT }/{ DATABASE }?charset=utf8mb4"
app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI
db = SQLAlchemy(app)
# 映射员工表
class employee(db.Model):
__tablename__ = "employee"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100))
time = db.Column(db.DateTime)
item = db.Column(db.String(100))
score = db.Column(db.Float)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"time": self.time,
"item": self.item,
"score": self.score,
}
@app.route("/")
def hello():
return "hello"
@app.route("/index.html")
def to_index():
return render_template("index.html")
@app.route("/data")
def get_data():
query = db.session.query(
employee.name, employee.time, employee.item, employee.score
)
datas = [
{"name": items[0], "time": items[1], "item": items[2], "score": items[3]}
for items in query
]
print(datas)
return jsonify(data=datas)
if __name__ == "__main__":
app.run(debug=True)
前端代码
在前端页面,我使用ajax来接收响应,这里我使用cdn来引入ajax, <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js"></script>
我们打印一下返回的结果,语法如下:
js
<script>
$.ajax({
type: "GET",
url: "/data",
success: function (response) {
console.log(response)
}
})
</script>
用python app.py启动项目,在浏览器搜索栏输入http://127.0.0.1:5000/index.html ,可以在开发者工具看到结果如下:

下面可以来制作需要的图表了。
日期操作
根据我们的业务需求,我们要把数据根据月进行聚合,还要统计出每个月的总分。前端页面从服务器接收到的是JSON格式的数据,他不能直接应用到echarts中,我们首先要对数据进行转化,这里需要注意的是,服务器返回的是UTC标准日期,因此需要进行格式转化根据我们的要求,我们用ym表示年-月格式的日期,Date()方法会对日期进行时区修正,这里的getUTCFullYear()取得年,getMonth()取得月,这里的月是从0开始的,因此要+1。
聚合

这里还用一个if的判断对数据进行了聚合,hasOwnProperty() 方法会返回一个布尔值,指示对象自身属性中是否具有指定的属性。我们判断一个月份是否存在,如果不存在就根据这个月份创建一个对象,存在就追加数据。对象内部存在一系列属性。除了分数用累加,其他属性用数组来存储数据。
js
const agg = {}
response.data.forEach(function(item){
var name = item.name;
var time = item.time;
var items = item.item;
var score = item.score;
var fulltime = new Date(time)
var year = fulltime.getUTCFullYear();
var month = fulltime.getMonth() + 1 ;
var ym = year + "-" + (month < 10 ? "0"+month:month)
if (!agg.hasOwnProperty(ym)){
agg[ym] = {
name:[name],
time: [time],
item:[items],
scorelist:[score],
value:score
}
}
else {
agg[ym].name.push(name),
agg[ym].time.push(time),
agg[ym].item.push(items),
agg[ym].scorelist.push(score),
agg[ym].value += score
}
})
下面要取出月份作为维度放在x轴上,const months = Object.keys(agg)会取出agg的键,也就是月份。 seriesData是个月数据的具体内容, Object.values(agg)
返回 agg
对象的所有值(values)组成的数组。这里使用 map()
方法遍历该数组中的每个值,并将其转换为一个新的对象。在 map()
方法的回调函数中,每个 item
代表 agg
对象的值数组中的一个元素。代码将该元素的特定属性(name
、time
、ymd
、content
、value
)提取出来,并创建一个包含这些属性的新对象。最后,map()
方法返回包含所有新对象的数组。
js
const months = Object.keys(agg);
const seriesData = Object.values(agg).map(item => {
return {
name:item.name,
time:item.time,
ymd:item.ymd,
content:item.content,
value:item.value
}
});
我们现在需要做一个堆叠柱状图,x轴传入月份数据。这里我在做鼠标划过的提示时,用来一个 formatter:参数来自定义显示的内容。这里文章的长度比较长了,我准备后续再另一篇文章再写一下这里的操作,同时再谈谈矩形柱状图的操作。

js
var chart = echarts.init(document.querySelector("div"))
option = {
title: {
text: '每月人数统计图',
left: 'center'
},
legend: {
data: ['人数']
},
xAxis: {
type: 'category',
data: months
},
yAxis: {
type: 'value'
},
tooltip:{
trigger: 'axis',
axisPointer: {
type: 'shadow',
shadowStyle: {
color: 'rgb(73, 94, 87,0.3)' // 设置阴影颜色
}
},
formatter:
function(params) {
tooltipContent= ""
params.forEach(function(items){
if (items.value > 0){
console.log(items.data)
for (var n = 0 ; n < items.data.name.length; n++){
var names = items.data.name[n]
var ymd = items.data.ymd[n]
var content = items.data.content[n]
console.log(names)
tooltipContent += "<b>" + names + ymd +content + "</b>" + '<br>';}
}
})
return tooltipContent
}
},
series:[{
type:"bar",
data:seriesData,
itemStyle:{
color:"rgba(237, 125,49,0.7)"
},
}]
};
option && chart.setOption(option);
完整代码
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/echarts/5.4.3/echarts.min.js"></script>
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.7.1/jquery.js"></script>
</head>
<body>
<div style="width: 600px;height: 400px;"></div>
<script>
$.ajax({
type: "GET",
url: "/data",
success: function (response) {
const agg = {}
response.data.forEach(function(item){
var name = item.name;
var time = item.time;
var items = item.item;
var score = item.score;
var fulltime = new Date(time)
var year = fulltime.getUTCFullYear();
var month = fulltime.getUTCMonth() + 1 ;
var day = fulltime.getUTCDay()
var ym = year + "-" + (month < 10 ? "0"+month:month)
var ymd = year + "-" + (month < 10 ? "0"+month:month) + "-" + (day < 10 ? "0" + day:day)
if (!agg.hasOwnProperty(ym)){
agg[ym] = {
name:[name],
time: [time],
ymd:[ymd],
content:[items],
scorelist:[score],
value:score
}
}
else {
agg[ym].name.push(name),
agg[ym].time.push(time),
agg[ym].ymd.push(ymd),
agg[ym].content.push(items),
agg[ym].scorelist.push(score),
agg[ym].value += score
}
})
console.log(agg)
const months = Object.keys(agg);
const seriesData = Object.values(agg).map(item => {
return {
name:item.name,
time:item.time,
ymd:item.ymd,
content:item.content,
value:item.value
}
});
var chart = echarts.init(document.querySelector("div"))
option = {
title: {
text: '每月人数统计图',
left: 'center'
},
legend: {
data: ['人数']
},
xAxis: {
type: 'category',
data: months
},
yAxis: {
type: 'value'
},
tooltip:{
trigger: 'axis',
axisPointer: {
type: 'shadow',
shadowStyle: {
color: 'rgb(73, 94, 87,0.3)' // 设置阴影颜色
}
},
formatter:
function(params) {
tooltipContent= ""
params.forEach(function(items){
if (items.value > 0){
console.log(items.data)
for (var n = 0 ; n < items.data.name.length; n++){
var names = items.data.name[n]
var ymd = items.data.ymd[n]
var content = items.data.content[n]
console.log(names)
tooltipContent += "<b>" + names + ymd +content + "</b>" + '<br>';}
}
})
return tooltipContent
}
},
series:[{
type:"bar",
data:seriesData,
itemStyle:{
color:"rgba(237, 125,49,0.7)"
},
}]
};
option && chart.setOption(option);
}
})
</script>
</body>
</html>
python
from flask import Flask, render_template, session, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
HOSTNAME = "127.0.0.1"
PORT = 3306
USERNAME = "root"
PASSWORD = ""
DATABASE = "bisystem"
DB_URI = f"mysql+pymysql://{ USERNAME }:{ PASSWORD }@{ HOSTNAME }:{ PORT }/{ DATABASE }?charset=utf8mb4"
app.config["SQLALCHEMY_DATABASE_URI"] = DB_URI
db = SQLAlchemy(app)
# 映射员工表
class employee(db.Model):
__tablename__ = "employee"
id = db.Column(db.Integer, primary_key=True, autoincrement=True)
name = db.Column(db.String(100))
time = db.Column(db.DateTime)
item = db.Column(db.String(100))
score = db.Column(db.Float)
def to_dict(self):
return {
"id": self.id,
"name": self.name,
"time": self.time,
"item": self.item,
"score": self.score,
}
@app.route("/")
def hello():
return render_template("index.html")
@app.route("/index.html")
def to_index():
return render_template("index.html")
@app.route("/data")
def get_data():
query = db.session.query(
employee.name, employee.time, employee.item, employee.score
)
datas = [
{"name": items[0], "time": items[1], "item": items[2], "score": items[3]}
for items in query
]
print(datas)
return jsonify(data=datas)
if __name__ == "__main__":
app.run(debug=True)
完整代码