文章目录
上手做了一个简单的小系统,这才对flask的整个流程有所理解。主要为数据库配置及简单使用、前端bootstrap框架、前后端通过Ajax传值、前端echarts绘图。
本文对flask、bootstrap、echarts的结合使用做简单记录。
0 系统简介
系统主界面如下,主要由用户输入处方和支持度、置信度、提升度阈值,系统前端显示频繁项集表、关联规则表和复杂网络图。
(真的丑啊(不过能写出来就很开心辣
1 Flask部分
flask官方文档 http://docs.jinkan.org/docs/flask/index.html
flask教程 http://www.pythondoc.com/flask-mega-tutorial/
flask教程 https://www.w3cschool.cn/flask/flask_overview.html
以下为文件间关系:
wsgi.py为系统入口文件;config.py为配置文件,如数据库的连接
App目录则包含其他文件:algorithm目录存放后端算法;static目录存放静态文件,如js/css/image;templates目录存放前端HTML文件;models.py主要用于数据库建库;route.py为路由视图文件;test.py用于测试数据库是否成功,可忽略。
PS:由于本系统十分简单,非庞大的项目,故未涉及蓝图等内容。
1.1 wsgi.py
有时候代码中开启debug模式不太管用,需要按下面的步骤选择FLASK_DEBUG,就ok了。
1.2 config.py + models.py
配置部分主要为数据库连接
SQLALCHEMY_DATABASE_URI = 'mysql+pymysql://root:password@localhost:3306/xgfy'
#格式 'mysql+pymysql://用户名:密码@localhost:端口/数据库'
将数据库每个表重新建类,便于后端使用
查询通过filter_by
1.3 router.py
路由部分由不同路由组成,这是最简单的格式
@app.route('/') #默认路由,名称自定义
def Apriori_home(): #函数名称自定义
return render_template('Apriori.html') #此路由返回的是渲染'Apriori.html'界面
1.4 jinjia2传值至前端
为啥要用jinjia2嘞,本来前端数据都是写死的,但是需要对前端结果分析然后将后端数据动态传到前端时,就需要一种方法来表示后端的各种数据或参数。
flask官网文档中有jinjia2介绍部分
康这个例子,后端生成freqSetNew,然后传至前端生成表格
简单来说,就是看前端本来需要啥数据,换成后端的某个参数,加上俩大括号。其他的语法和python一样,如判断、循环、列表字典等。
1.5 Ajax
Ajax教程 https://www.w3school.com.cn/ajax/index.asp
jQuery-Ajax教程 https://www.w3school.com.cn/jquery/ajax_ajax.asp
flask可以实现前后端分离,前端写前端,后端写后端,可是咋把二者连接起来嘞,就需要使用Ajax
比如说,前端输入一个text,按某个button之后,后端就接收到这个text数据,对它各种操作再返回前端一个表格
下面来康康怎么通过Ajax,前端->后端传送数据,后端->前端返回表格
1.5.1 前端向后端传递数据
前端点击id为analyze_btn的按钮之后,触发click函数。获取四个参数,并封装为data
后端router通过request.form.get(“参数”) 获得该参数对应的值
1.5.2 后端向前端传递响应
该路由对前端传递的4个参数一顿操作后,返回的是freqSetNew,rulesNew,nodes,links这几个参数
前端Ajax
对应的路由,类型,数据,是否为异步,如果成功则出现相应div的界面,如果失败。
- data是上面前端的几个参数
- result是后端路由返回的freqSetNew,rulesNew,nodes,links这几个参数
- 这样就连接成功辣
注意:可以适当添加console.log(“数据”);语句(相当于print)进行测试,在chrome中右击检查可以查看。
2 bootstrap部分
2.1 下载
官网下载地址 https://v4.bootcss.com/docs/getting-started/download/
使用前直接按照官网示例引入link和script即可
2.2 使用
官网文档 https://v4.bootcss.com/docs/components/alerts/
文档中“页面内容”“布局”“组件”可供选择
下面举两个栗子
2.2.1 垂直侧边导航栏
以“组件”-“导航(Navs)”中垂直导航(即侧边导航栏)为例
把官网代码贴进去就可,导航条宽度、颜色等内容可以自由调节
2.2.2 带分页的表格
这玩意官网有点难找
戳链接 https://www.bootstrap-table.com.cn/
和之前一样,找到自己喜欢的样式,直接贴到代码里,可直接用
3 echarts
3.1 下载
echarts官网 https://echarts.apache.org/zh/index.html
社区 https://gallery.echartsjs.com/explore.html#sort=ranktimeframe=allauthor=all
引入时注意文件所在位置
3.2 使用
以该关系图为例 https://gallery.echartsjs.com/editor.html?c=x1wc1W6Eyr
使用时可以直接把示例代码贴进去
步骤可以简化为以下几点,可以将其封装为函数,Ajax中直接调用
#1、初始化一个echarts(通过id获取图片放置位置)
var myChart = echarts.init(document.getElementById('contact'));
#2、定义nodes、links数据(注意格式内容要和官网示例保持一致)
#3、对nodes、links进行操作,这部分直接复制粘贴官网示例即可
#4、设置option
myChart.setOption(option);
nodes、links数据是后端通过Ajax传值的,Ajax中如果成功则调用echarts绘图函数
4 所有代码
4.1 wsgi.py代码
from App import app
from flask_script import Manager, Server
from App import route
app = app
manager = Manager(app)
manager.add_command("runserver", Server(host='0.0.0.0', port=5000, use_debugger=True))
if __name__ == '__main__':
app.run('127.0.0.1', debug=True)
4.2 route.py代码
from App import app # 导入已创建的app
from flask import Flask,render_template,request
import json,re
from App.algorithm.Apriori import Apriori
from App.algorithm.Apriori.graph import genNodeLink
@app.route('/')
def Apriori_home():
return render_template('Apriori.html')
# 画频繁项集表和关联规则表的路由,返回新界面
@app.route('/api/tb_pic', methods=['POST'])
def associationRule():
if request.method == 'POST':
# 获取参数值
dataSet = request.form.get('dataSet')
minSup = float(request.form.get('minSup'))
minConf = float(request.form.get('minConf'))
minLift = float(request.form.get('minLift'))
dataSetAppendNew = None
typeRelated = False
maxCount = 4 # 最大频繁项集数
datasetNew = []
# 将输入处方转换为list
items = dataSet.split('|')
for item in items:
elements = re.split(r'[;;,,、]', item)
datasetNew.append(elements)
itemSet,freqSet,rules = Apriori.loadApriori(dataset=datasetNew,
dataSetAppend=dataSetAppendNew,
minSup=minSup,
minConf=minConf,
minLift=minLift,
typeRelated=typeRelated,
maxCount=maxCount)
freqSetNew = Apriori.SetsToJson(itemSet,freqSet) # 转换格式后的频繁项集
rulesNew = Apriori.RulesToJson(rules) # 转换格式后的关联规则
# freqSetDemo: [{'item': 't', 'supp': 0.5}, {'item': 'r', 'supp': 0.5}]
# rulesDemo: [{'conf': 1.0, 'supp': 0.3333333333333333, 'leftNode': 'q', 'rule': 'q-->z', 'lift': 1.2, 'rightNode': 'z'}]
nodes, links = genNodeLink(rulesNew)
return render_template("Apriori_tbpic.html",
freqSetNew=dict(enumerate(freqSetNew)),
rulesNew=dict(enumerate(rulesNew)),
nodes=nodes, links=links)
# 画图的路由,传递nodes结点和links线
@app.route('/api/tb_pic1', methods=['POST'])
def associationRule1():
if request.method == 'POST':
# 获取参数值
dataSet = request.form.get('dataSet')
minSup = float(request.form.get('minSup'))
minConf = float(request.form.get('minConf'))
minLift = float(request.form.get('minLift'))
dataSetAppendNew = None
typeRelated = False
maxCount = 4 # 最大频繁项集数
datasetNew = []
# 将输入处方转换为list
items = dataSet.split('|')
for item in items:
elements = re.split(r'[;;,,、]', item)
datasetNew.append(elements)
itemSet,freqSet,rules = Apriori.loadApriori(dataset=datasetNew,
dataSetAppend=dataSetAppendNew,
minSup=minSup,
minConf=minConf,
minLift=minLift,
typeRelated=typeRelated,
maxCount=maxCount)
freqSetNew = Apriori.SetsToJson(itemSet,freqSet) # 转换格式后的频繁项集
rulesNew = Apriori.RulesToJson(rules) # 转换格式后的关联规则
# freqSetDemo: [{'item': 't', 'supp': 0.5}, {'item': 'r', 'supp': 0.5}]
# rulesDemo: [{'conf': 1.0, 'supp': 0.3333333333333333, 'leftNode': 'q', 'rule': 'q-->z', 'lift': 1.2, 'rightNode': 'z'}]
nodes, links = genNodeLink(rulesNew)
return json.dumps({'nodes':nodes,'links':links})
# @app.errorhandler(404)
# def internal_error(error):
# return render_template('404.html'), 404
4.3 Apriori.html代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>新型冠状病毒肺炎</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/css/bootstrap.min.css"
integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.css">
<script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/popper.js@1.16.0/dist/umd/popper.min.js"
integrity="sha384-Q6E9RHvbIyZFJoft+2mJbHaEWldlvI9IOYy5n3zV9zzTtmI3UksdQRVvoxMfooAo"
crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@4.4.1/dist/js/bootstrap.min.js"
integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6"
crossorigin="anonymous"></script>
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.js"></script>
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/locale/bootstrap-table-zh-CN.min.js"></script>
<script src={{url_for("static",filename="echarts/echarts.min.js")}}></script>
<script src={{url_for("static",filename="echarts/dataTool.js")}}></script>
{# <script src="dataTool.js"></script>#}
</head>
<body>
<div class="main_content">
{# 界面头#}
<nav class="navbar navbar-dark bg-primary">
<div>
<h4></h4>
<h4>新型冠状病毒肺炎中医药推荐方分析系统</h4>
<h4></h4>
</div>
</nav>
{# 侧边导航栏#}
<div class="row">
<div class="col-2">
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist" aria-orientation="vertical">
<a class="nav-link active" id="v-pills-home-tab" data-toggle="pill" href="#v-pills-home" role="tab"
aria-controls="v-pills-home" aria-selected="true">关联分析</a>
<a class="nav-link" id="v-pills-profile-tab" data-toggle="pill" href="#v-pills-profile" role="tab"
aria-controls="v-pills-profile" aria-selected="false">聚类分析</a>
</div>
</div>
<div class="col-9">
<div class="tab-content" id="v-pills-tabContent">
{# 关联部分#}
<div class="tab-pane fade show active" id="v-pills-home" role="tabpanel"
aria-labelledby="v-pills-home-tab">
{# 输入框+查找按钮#}
<div class="input-group mb-4">
<input id="text_dataSet" type="text" class="form-control" value="麻黄,桂枝,杏仁,甘草|桂枝,芍药,甘草|麻黄,杏仁,芍药,甘草|桂枝,芍药"
aria-label="Recipient's username" aria-describedby="basic-addon2">
{# <button type="button" class="btn btn-primary">确定</button>#}
</div>
{# 三个输入框:最小支持度,最小置信度,最小提升度#}
<div class="input-group mb-3">
<input id="text_minSup" type="number" class="form-control input-sm" value="0.5" step="0.1"
aria-label="Recipient's username" aria-describedby="inputGroup-sizing-sm">
<input id="text_minConf" type="number" class="form-control input-sm" value="0.5" step="0.1"
aria-label="Recipient's username" aria-describedby="inputGroup-sizing-sm">
<input id="text_minLift" type="number" class="form-control input-sm" value="0.5" step="0.1"
aria-label="Recipient's username" aria-describedby="inputGroup-sizing-sm">
<button id="analyze_btn" type="button" class="btn btn-primary">确定</button>
</div>
{# 导航:选择 频繁项集/关联规则/复杂网络#}
<ul class="nav nav-tabs" id="myTab" role="tablist">
<li class="nav-item">
<a class="nav-link active" id="home-tab" data-toggle="tab" href="#home" role="tab"
aria-controls="home" aria-selected="true">频繁项集</a>
</li>
<li class="nav-item">
<a class="nav-link" id="profile-tab" data-toggle="tab" href="#profile" role="tab"
aria-controls="profile" aria-selected="false">关联规则</a>
</li>
<li class="nav-item" >
<a class="nav-link" id="contact-tab" data-toggle="tab" href="#contact" role="tab"
aria-controls="contact" aria-selected="false">复杂网络</a>
</li>
</ul>
{# 导航下内容#}
<div class="tab-content" id="myTabContent">
{# 频繁项集#}
<div class="tab-pane fade show active" id="id_fre" role="tabpanel" aria-labelledby="home-tab">
</div>
{# 关联规则#}
<div class="tab-pane fade" id="id_ass" role="tabpanel" aria-labelledby="profile-tab">
</div>
{# 复杂网络#}
<div class="tab-pane fade" id="id_pic" role="tabpanel" aria-labelledby="contact-tab" >
</div>
</div>
</div>
{# 聚类部分#}
<div class="tab-pane fade" id="v-pills-profile" role="tabpanel" aria-labelledby="v-pills-profile-tab">
聚类内容
</div>
</div>
</div>
</div>
</div>
</body>
<script type=text/javascript>
$('#analyze_btn').click(function () {
var dataSet = $("#text_dataSet").val();
var minSup = $("#text_minSup").val();
var minConf = $("#text_minConf").val();
var minLift = $("#text_minLift").val();
var data = {
minSup: minSup,
minConf: minConf,
minLift: minLift,
dataSet: JSON.stringify(dataSet),
};
{#console.log(data); #}
{#console.log相当于print,输出数据,chrome检查中console可查看#}
//画表
$.ajax({
url: '/api/tb_pic',
type: 'POST',
data: data,
async: false,
success: function (result) {
{#console.log(result);#}
$("#id_fre").html(result)
$("#id_ass").html(result)
{#console.log(result);#}
},
error: function (err) {
console.log(err)
{#$.messager.progress('close');#}
}
});
//画图
$.ajax({
url: '/api/tb_pic1',
type: 'POST',
data: data,
dataType: "json",
async: false,
{#传递的rusult是nodes和links,注意:要和echarts官网示例中的nodes、links格式一致#}
success: function (result) {
{#console.log(result);#}
console.log(result.nodes);
console.log(result.links);
{#调用画图函数#}
init_graph(result.nodes, result.links);
},
error: function (err) {
console.log(err)
{#$.messager.progress('close');#}
}
});
});
function init_graph(nodes, links) {
{#创建一个echarts,通过Apriori_tbpic.html中id"contact"#}
var myChart = echarts.init(document.getElementById('contact'));
let data = {
nodes: JSON.parse(JSON.stringify(nodes)),
links: links
}
{#下面内容复制粘贴官网示例即可#}
const color1 = '#006acc';
data.nodes.forEach(node => {
node.symbolSize = 50;
node.itemStyle = {
color: color1
};
});
data.links.forEach(link => {
link.label = {
align: 'center',
fontSize: 12
};
});
let categories = [{}
]
option = {
legend: [{
// selectedMode: 'single',
data: categories.map(x => x.name),
// icon: 'circle'
}],
series: [{
type: 'graph',
layout: 'force',
symbolSize: 58,
draggable: true,
roam: true,
focusNodeAdjacency: true,
edgeSymbol: ['', 'arrow'],
// edgeSymbolSize: [80, 10],
edgeLabel: {
normal: {
show: true,
textStyle: {
fontSize: 20
},
formatter(x) {
return x.data.name;
}
}
},
label: {
show: true,
},
force: {
repulsion: 2000,
edgeLength: 200
},
data: data.nodes,
links: data.links
}]
}
// 第四处:
myChart.setOption(option);
}
</script>
</html>
4.4 Apriori_tbpic.html代码
<link rel="stylesheet" href="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.css">
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/bootstrap-table.min.js"></script>
<script src="https://unpkg.com/bootstrap-table@1.15.3/dist/locale/bootstrap-table-zh-CN.min.js"></script>
{#<script src="echarts.min.js"></script>#}
{#<script src={{url_for("static",filename="echarts.js")}}></script>#}
<script src={{url_for("static",filename="echarts/echarts.min.js")}}></script>
<script src={{url_for("static",filename="echarts/dataTool.js")}}></script>
{# 导航下内容#}
<div class="tab-content" id="myTabContent">
{# 频繁项集#}
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
<table data-toggle="table" data-pagination="true" data-search="true">
<thead>
<tr>
<th scope="col">序号</th>
<th scope="col">频繁项集</th>
<th scope="col" data-sortable="true">频率</th>
</tr>
</thead>
<tbody>
{% for ind,foo in freqSetNew.items() %}
<tr>
<td scope="row">{{ ind + 1 }}</td>
<td>{{ foo['item'] }}</td>
<td>{{ foo['supp'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{# 关联规则#}
<div class="tab-pane fade" id="profile" role="tabpanel" aria-labelledby="profile-tab">
<div class="tab-pane fade show active" id="home" role="tabpanel" aria-labelledby="home-tab">
<table data-toggle="table" data-pagination="true" data-search="true">
<thead>
<tr>
<th scope="col">序号</th>
<th scope="col">规则</th>
<th scope="col" data-sortable="true">支持度</th>
<th scope="col" data-sortable="true">置信度</th>
<th scope="col" data-sortable="true">提升度</th>
</tr>
</thead>
<tbody>
{% for ind,foo in rulesNew.items() %}
<tr>
<td scope="row">{{ ind + 1 }}</td>
<td>{{ foo['rule'] }}</td>
<td>{{ foo['supp'] }}</td>
<td>{{ foo['conf'] }}</td>
<td>{{ foo['lift'] }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{# 复杂网络#}
<div class="tab-pane fade" id="contact" style="width: 1000px;height:500px;" role="tabpanel" aria-labelledby="contact-tab">
</div>
</div>