前言
之前在做项目的时候,遇到了一个问题:就是我想将单片机板子的传感器数据,实时上传到物联网平台,然后进行数字大屏显示。一开始使用的是onenet平台→多协议接入→应用管理来进行设置,奈何里面的一个组件少的可怜!
又看到它家的‘数据可视化View’功能!被他酷炫的数字大屏模板深深吸引了。
心想这不就我要找的模板吗?于是信誓旦旦的准备大概一场的时候:在我们选择一个2D项目模板,准备创建项目的时候,它跳出来一个对话框“提示氪金”(ps:新用户有7天的专业版试用资格,我当时要做项目的时候,已经过了!)
眼看着比赛的时间节点就快到了,于是咬咬牙,就买了两个月!
买了一个企业版的会员,买完回来发现:“里面的有些功能不能用!需要升级专业版会员!(腾讯svip直呼:“内行”)”没有办法了,只能展示用这个了。勉强将项目如期完成!
数字项目大屏项目做完后,我再想能不能自己来写一个数字大屏demo呢?恰好DF的‘’Mind+Python编程与智能设计大赛'正在进行中,于是就开始着手去实现这个想法!本教程将会带领你从零开始搭建一个:基于Pyecharts + Flask + Pinpong 智能家居数字大屏系统的项目!
准备阶段
步骤1 安装pyecharts
概况
Echarts 是一个由百度开源的数据可视化,凭借着良好的交互性,精巧的图表设计,得到了众多开发者的认可。而 Python 是一门富有表达力的语言,很适合用于数据处理。当数据分析遇上数据可视化时,pyecharts 诞生了。
特性
简洁的 API 设计,使用如丝滑般流畅,支持链式调用囊括了 30+ 种常见图表,应有尽有支持主流 Notebook 环境,Jupyter Notebook 和 JupyterLab可轻松集成至 Flask,Django 等主流 Web 框架高度灵活的配置项,可轻松搭配出精美的图表详细的文档和示例,帮助开发者更快的上手项目多达 400+ 地图文件以及原生的百度地图,为地理数据可视化提供强有力的支持 具体开发文档详见pyecharts开发文档
如何安装?
方法一:
在mind+的python模式下:点击“库管理”→"图表"→"pyecharts"点击安装即可
方法二:pip安裝
在mind+的python模式下:点击“库管理”→"pip模式"下:
输入:pip install pyecharts
(ps:因为我这里已经安装了,界面中提示了欢迎已经存在)
步骤2 安装falsk
简介:
Flask是一个轻量级的可定制框架,使用Python语言编写,较其他同类型框架更为灵活、轻便、安全且容易上手。它可以很好地结合MVC模式进行开发,开发人员分工合作,小型团队在短时间内就可以完成功能丰富的中小型网站或Web服务的实现。另外,Flask还有很强的定制性,用户可以根据自己的需求来添加相应的功能,在保持核心功能简单的同时实现功能的丰富与扩展,其强大的插件库可以让用户实现个性化的网站定制,开发出功能强大的网站。
基本模式:
Flask的基本模式为在程序里将一个视图函数分配给一个URL,每当用户访问这个URL时,系统就会执行给该URL分配好的视图函数,获取函数的返回值并将其显示到浏览器上,其工作过程见图。
特点:
1、简单,Flask的路由以及路由函数由修饰器设定,开发人员不需要借助其他文件匹配;2、配置灵活,有多种方法配置,不同环境的配置也非常方便;环境部署简单,Flask运行不需要借助其他任何软件,只需要安装了Python的IDE,在命令行运行即可。只需要在Python中导入相应包即可满足所有需求;3、入门简单,通过官方指南便可以清楚的了解Flask的运行流程;4、低耦合,Flask可以兼容多种数据库、模板。 更多详情:flask开发文档
如何安装Flask?
在mind+的python模式下:点击“库管理”→"pip模式"下:
输入:pip install Flask
步骤3 安裝pinpong
简介:
pinpong库是一套控制开源硬件主控板的Python库,基于Firmata协议并兼容MicroPython语法,5分钟即可让你上手使用Python控制开源硬件。
借助于pinpong库,直接用Python代码就能给各种常见的开源硬件编程。其原理是给开源硬件烧录一个特定的固件,使开源硬件可以通过串口与电脑通讯,执行各种命令。
pinpong库的名称由“Pin”和“Pong”组成,“Pin”指引脚,“PinPong”为“乒乓球”的谐音,指信号的往复。
特点:
pinpong库的设计,是为了让开发者在开发过程中不用被繁杂的硬件型号束缚,而将重点转移到软件的实现。哪怕程序编写初期用Arduino开发,部署时改成了掌控板,只要修改一下硬件的参数就能正常运行,实现了“一次编写处处运行”。
当前PinPong库正在快速更新中,已支持Arduino系列uno、leonardo、mega2560,ESP32系列掌控板(handpy)以及micro:bit板,传感器支持50+,其他主控板及更多扩展库将逐步支持。
更多详情:pinpong开发手册
如何安装pinpong?
方法一:
在mind+的python模式下:点击“库管理”→"推荐库"→"硬件控制"→"pinpong"点击安装即可
方法二:
在mind+的python模式下:点击“库管理”→"pip模式"下:
输入:pip install pinpong
程序设计
step1:新建一个flask项目
在mind+的右侧项目文件:
1、新建一个文件 pyecharts-flask-demo
2、在pyecharts-flask-demo文件夹下 创建 templates文件夹
Step 2: 拷贝 pyecharts 模板
将 pyecharts 模板,位于 “C:\Users\83731\Documents\mindplus-py\environment\Python3.6.5-64\Lib\site-packages\pyecharts\render\templates”拷贝至刚新建的 templates 文件夹
(ps:这里的index.html文件是我自己新建的,后面会讲到)
Flask 前后端分离
Step 3: 新建一个 HTML 文件
新建 HTML 文件保存位于项目根目录的 templates 文件夹,这里以如下 index.html 为例. 主要用到了 jquery 和 pyecharts 管理的 echarts.min.js 依赖!后面用到水球图和词云需要用到echarts-liquidfill.min.js,echarts-wordcloud.min.js
这里你可能需要一点HTML,CSS,javascript等前端的知识,有兴趣的老师可以去w3school了解一下!(PS:本人也是小白一个,所设计的界面很low,希望大神勿喷!)
前端主动向后端进行数据刷新
定时刷新的核心在于 HTML 的 setInterval 方法。
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>智能家居</title>
<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts.min.js"></script>
<script type="text/javascript" src="https://cdn.bootcss.com/jquery/3.0.0/jquery.min.js"></script>
<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts-liquidfill.min.js"></script>
<script type="text/javascript" src="https://assets.pyecharts.org/assets/echarts-wordcloud.min.js"></script>
<style>
h1{font-family:"隶书";color:#9AB3F5;font-size:50px;margin-align:center;line-height:80px;}
div{box-shadow:0 10px 20px rgba(191, 216, 184,0.19), 0 6px 6px rgba(191, 216, 184,0.23);border-radius:5px;background:#E8F6EF;position:relative; }
#liquid{width:800px; height:400px;margin-left:50px; margin-top:20px;float: left;}
#pressure{width:500px; height:400px;margin-left:40px; margin-top:20px; float: left;}
#altitude{width:500px; height:400px;margin-left:40px; margin-top:20px;float: left;}
#line{width:800px; height:400px;margin-top:20px;margin-left:50px;float: left;}
#bar{width:500px; height:400px;margin-left:40px;margin-top:20px;float: left;}
#wordcloud{width:500px; height:400px;margin-left:40px;margin-top:20px;float: left;}
.ttop{width:100%;height:90px;text-align:center; margin-top:-20px;background:#6F69AC;}
</style>
</head>
<body >
<div class="ttop" style="">
<h1 > 基于Pyecharts + Flask + Pingpong 智能家居数字大屏系统</h1>
</div>
<div id="liquid" ></div>
<div id="pressure"></div>
<div id="altitude"></div>
<div id="line"></div>
<div id="bar"> </div>
<div id="wordcloud"> </div>
<script>
var liquid = echarts.init(document.getElementById('liquid'), 'white', {renderer: 'canvas'});
$(
function () {
fetchliquidData(liquid);
setInterval(fetchliquidData, 2000);
}
);
function fetchliquidData() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:5000/liquidChart",
dataType:'json',
success: function (result) {
var jsonResult = JSON.stringify(result);
var jsonResult = JSON.parse(jsonResult,function(key,value){
if(key === "formatter"){
console.log(value);
return eval('('+value+')');
}else{
return value;
}
});
console.log(jsonResult);
liquid.setOption(jsonResult);
},
});
}
</script>
<script>
var pressure = echarts.init(document.getElementById('pressure'), 'white', {renderer: 'canvas'});
$(
function () {
fetchpressureData(pressure);
setInterval(fetchpressureData, 2000);
}
);
function fetchpressureData() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:5000/pressureChart",
dataType:'json',
success: function (result) {
pressure.setOption(result);
},
});
}
</script>
<script>
var altitude = echarts.init(document.getElementById('altitude'), 'white', {renderer: 'canvas'});
$(
function () {
fetchaltitudeData(altitude);
setInterval(fetchaltitudeData, 2000);
}
);
function fetchaltitudeData() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:5000/altitudeChart",
dataType:'json',
success: function (result) {
altitude.setOption(result);
},
});
}
</script>
<script>
var line = echarts.init(document.getElementById('line'), 'white', {renderer: 'canvas'});
var old_data = [];
$(
function () {
fetchlineData(line);
setInterval(getDynamicData, 2000);
}
);
function fetchlineData() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:5000/lineChart",
dataType: "json",
success: function (result) {
line.setOption(result);
old_data = line.getOption().series[0].data;
}
});
}
function getDynamicData() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:5000/lineDynamicData",
dataType: "json",
success: function (result) {
old_data.push([result.name, result.value]);
line.setOption({
series: [{data: old_data}]
});
}
});
}
</script>
<script>
var bar = echarts.init(document.getElementById('bar'), 'white', {renderer: 'canvas'});
$(
function () {
fetchbarData(bar);
setInterval(fetchbarData, 2000);
}
);
function fetchbarData() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:5000/barChart",
dataType:'json',
success: function (result) {
bar.setOption(result);
},
});
}
</script>
<script>
var wordcloud = echarts.init(document.getElementById('wordcloud'), 'white', {renderer: 'canvas'});
$(
function () {
fetchwordcloudData(wordcloud);
setInterval(fetchwordcloudData, 2000);
}
);
function fetchwordcloudData() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:5000/wordcloudChart",
dataType:'json',
success: function (result) {
wordcloud.setOption(result);
},
});
}
</script>
</body>
</html>
Step 4: 新建app.py文件
请将下面的代码保存为 app.py 文件并移至项目的根目录下。
该程序主要用pingpong来获取传感器的数据,然后将数据放到pyecharts的chart中。然后通过falsk web框架每个两秒将数据发送到前端!浏览器输入:http://127.0.0.1:5000/。进行页面显示。主要用到了get请求来获取数据。
from random import randrange
# 导入Flask 相关库文件
from flask import Flask, render_template
from flask.json import jsonify
# 导入pyecharts 相关库文件
from pyecharts import options as opts
from pyecharts.charts import Grid, Liquid,Gauge,Line,Pie,Bar,WordCloud
from pyecharts.faker import Faker
from pyecharts.commons.utils import JsCode
from pyecharts.globals import ThemeType
# 导入pinpong库相关文件
from pinpong.board import Board,Pin
from pinpong.libs.dfrobot_bme280 import BME280
import time
Board("uno").begin()
# BME280 初始化
bme = BME280()
app = Flask(__name__, static_folder="templates")
date=[0,0,0,0]
# 创建水球图
def create_liquid_charts(temp,humi) ->Liquid:
l1 = (
Liquid()
.add("湿度", # 系列名称
[humi], # 系列数值
center=["48%", "50%"], # 设置x,y坐标值
label_opts=opts.LabelOpts( # 标签配置项
font_size=35, # 标签大小
position="inside",
)
)
.set_global_opts(title_opts=opts.TitleOpts(title="温湿度电量水球图")) # 提示框组件配置项
)
l2 = (
Liquid()
.add(
"温度",
[temp],
center=["18%", "50%"],
color=["#FFA500"],
label_opts=opts.LabelOpts(
font_size=35,
formatter=JsCode(
"""function (param) {
return (Math.floor(param.value * 10000) / 100) + '℃';
}"""
),
position="inside",
),
)
)
l3=(
Liquid()
.add("电量", [0.8],
center=["78%", "50%"],
color=["#00FA9A"], # 水球图波浪颜色
label_opts=opts.LabelOpts(
font_size=35,
position="inside",
)
)
)
# 通过grid 并行多图将三个水球合并在一张图上
grid = Grid().add(l1, grid_opts=opts.GridOpts()).add(l2, grid_opts=opts.GridOpts()).add(l3, grid_opts=opts.GridOpts())
return grid
# 创建仪表盘
def creat_pressure_chart(press) -> Gauge:
c = (
Gauge()
.add("",
[("压强", press)],
min_=['0'], # 最小值
max_=['2000'], # 最大值
split_number=5,
axisline_opts=opts.AxisLineOpts( # 设置仪表盘颜色及宽度
linestyle_opts=opts.LineStyleOpts(
color=[(0.3, "#67e0e3"), (0.7, "#37a2da"), (1, "#fd666d")], width=30
)
),
detail_label_opts=opts.LabelOpts(formatter="{value} Pa"),) # 格式
.set_global_opts(title_opts=opts.TitleOpts(title="压强仪表盘"))
)
return c
# 创建海拔仪表盘
def creat_altitude_chart(alti) -> Gauge:
c1 =(
Gauge()
.add("",
[("海拔", alti)],
min_=['0'], # 最小值
max_=['1000'], # 最大值
split_number=5,
axisline_opts=opts.AxisLineOpts( # 设置仪表盘颜色及宽度
linestyle_opts=opts.LineStyleOpts(
color=[(0.3, "#F3EAC2"), (0.7, "#F5B461"), (1, "#EC524B")], width=30
)
),
detail_label_opts=opts.LabelOpts(formatter="{value} M"),) # 格式
.set_global_opts(title_opts=opts.TitleOpts(title="海拔仪表盘"))
)
return c1
# 创建折现图
def line_base() -> Line:
line = (
Line()
.add_xaxis(["{}".format(i) for i in range(10)])
.add_yaxis(
series_name="",
y_axis=[randrange(20, 35) for _ in range(10)],
is_smooth=True,
label_opts=opts.LabelOpts(is_show=False),
)
.set_global_opts(
title_opts=opts.TitleOpts(title="温度动态数据显示"),
xaxis_opts=opts.AxisOpts(type_="value"),
yaxis_opts=opts.AxisOpts(type_="value"),
)
)
return line
# 创建主创图
def creat_bar_chart(temp,humi,press,alti) -> Bar:
c = (
Bar(init_opts=opts.InitOpts(theme=ThemeType.LIGHT))
.add_xaxis(["温度","湿度","压强","海拔"])
.add_yaxis("小明家", [temp,humi,press,alti])
.set_global_opts(title_opts=opts.TitleOpts(title="BME280数据显示"))
)
return c
# 词条
data = [
("hockel", "2004"),
("智能家居", "1500"),
("少儿编程", "777"),
("人工智能", "666"),
("python", "200"),
("mind+", "555"),
("Erised", "1204"),
("温度", "50"),
("湿度", "40"),
("压强", "21"),
("海拔", "30"),
("BME280", "21"),
("pyecharts", "99"),
("arduino", "50"),
("flask", "80"),
("html", "60"),
("css", "20"),
("js", "70"),
("pinpong", "120"),
]
# 创建词云
def creat_wordcloud_chart() -> WordCloud:
c = (
WordCloud()
.add(series_name="所用技术", data_pair=data, word_size_range=[6, 66])
.set_global_opts(
title_opts=opts.TitleOpts(
title="所用技术", title_textstyle_opts=opts.TextStyleOpts(font_size=23)
),
tooltip_opts=opts.TooltipOpts(is_show=True),
)
)
return c
@app.route("/")
def index():
return render_template("index.html")
@app.route("/liquidChart")
def get_liquid_chart():
date[0] = bme.temp_c()
date[1] = bme.humidity()
date[2] = bme.press_pa()
date[3] = bme.cal_altitudu()
temp = date[0] / 100
humi = date[1] / 100
c = create_liquid_charts(temp,humi)
return c.dump_options_with_quotes()
# 压强仪表盘
@app.route("/pressureChart")
def get_pressure_chart():
press = date[2] / 100
c = creat_pressure_chart(press)
return c.dump_options_with_quotes()
# 海拔仪表盘
@app.route("/altitudeChart")
def get_altitude_chart():
alti = date[3]
c = creat_altitude_chart(alti)
return c.dump_options_with_quotes()
# 动态图
@app.route("/lineChart")
def get_line_chart():
c = line_base()
return c.dump_options_with_quotes()
idx = 9
@app.route("/lineDynamicData")
def update_line_data():
global idx
idx = idx + 1
return jsonify({"name": idx, "value": date[0]})
# 柱状图
@app.route("/barChart")
def get_bar_chart():
c = creat_bar_chart(date[0],date[1],date[2]/100,date[3])
return c.dump_options_with_quotes()
# 词云
@app.route("/wordcloudChart")
def get_wordcloud_chart():
c = creat_wordcloud_chart()
return c.dump_options_with_quotes()
if __name__ == "__main__":
app.run()
演示效果
FAQ :
1、Gauge 仪表板显示图例与数据重叠!
这个是pyecharts V1.9.0的BUG!在V2.0.0将被修复!
解决方法:
找到pyecharts的安装目录(右击右侧项目:打开文件所在位置即可找到):
C:\Users\83731\Documents\mindplus-py\environment\Python3.6.5-64\Lib\site-packages\
然后修改pyecharts\charts\basic_charts\chart.py文件
2、pyecharts中的水球图不显示问题:
在遇到这个问题之后,了解一下echarts,才知道echarts-liquidfill是第三方插件,需要单独导入
需要在index.html文件中标签内加入:
3、水球图在进行数据精度显示是时:单位不能显示!
在看了pyecharts文档时有一处写到:
注: 目前由于 json 数据类型的问题,无法将 pyecharts 中的 JSCode 类型的数据转换成 json 数据格式返回到前端页面中使用。因此在使用前后端分离的情况下尽量避免使用 JSCode 进行画图。
l2 = (
Liquid()
.add(
"温度",
[0.3254],
center=["18%", "50%"],
color=["#FFA500"],
label_opts=opts.LabelOpts(
font_size=35,
formatter=JsCode(
"""function (param) {
return (Math.floor(param.value * 10000) / 100) + '℃';
}"""
),
position="inside",
),
)
)
我这这里jscode返回的对象是str:str! 然而我们需要调用jscode的函数!我们需要在html的文档中进行数据处理:将str:str 转换成 str:function().找到我们调用的liquid的script标签进行如下更改!
function fetchliquidData() {
$.ajax({
type: "GET",
url: "http://127.0.0.1:5000/liquidChart",
dataType:'json',
success: function (result) {
var jsonResult = JSON.stringify(result);
jsonResult = JSON.parse(jsonResult,function(key,value){
if(key === "formatter"){
console.log(value);
return eval('('+value+')');
}else{
return value;
}
});
console.log(jsonResult);
liquid.setOption(jsonResult);
},
4.用grid组件无法将liquid和gauge并列合并在同一图上!
使用grid时,一般情况下,永远不要先给grid加入一个没有x y轴的图,比如饼图、仪表盘、地图等等。
如果你想加的,可以参考这篇文章:pyecharts丨页面布局工具——grid注意事项和overlap的使用
总结
整个项目做下来!被Flask的前后端分离数据显示问题搞得崩溃!主要还是因为自己的前端知识有点薄弱。为此自己恶补了一晚的前端的html,css,js等知识!对falsk web框架和pyecharts有了进一步的了解!屏幕前的你,有没有学吐了呢?反正我是秃了!
更多教程欢迎关注个人博客:www.hockel.club
微信公众账号:跟着hockel玩科创
白凡2021.09.27
大佬666
赵文栋2021.09.20
我这边是做科技教育培训行业的,想要获得你的联系方式,是否方便添加一下微信13701338504,这个是我的微信,看合作一下
许培享2021.09.09
碉!我想学可能学不来啊
Hockel2021.09.09
没有没有😂我也是从零开始。遇到很多大坑,项目差点就搁浅了。🤭
木子呢2021.09.09
赞
WSY2021.09.09
优秀!