前言
开发语言:python3.11.6、javascript、html5'、css3
开发框架:flask、plotly.js
开发系统:windows10 22H2
开发编辑器:vscode
作用:展示水产养殖水体氨氮和亚硝酸盐时间序列数据,使用LWLR、ESE、DLM模型进行滚动预测并绘制曲线
开发初衷:为了项目汇报更具象
开源性质:完全开源,任意使用
文件目录展示
图表展示
static
氨氮预测.xlsx
plotly-2.2.0.min.js
templates
es.html
index.html
lwlr.html
rw.html
main.py
效果展示
代码说明
基于flask的一个数据展示网页
main.py
导包
python
from flask import Flask,render_template,request,jsonify
import pandas as pd
import numpy as np
from statsmodels.tsa.holtwinters import SimpleExpSmoothing
from scipy.signal import savgol_filter
from werkzeug.datastructures import ImmutableMultiDict
全局变量
python
app=Flask(__name__)
excel_path='./static/氨氮预测.xlsx'
程序启动入口
python
if __name__=='__main__':
app.run(debug=True,port=5002,host='0.0.0.0')
html页面响应函数
python
@app.route('/',methods=['GET'])
def index():
return render_template('index.html')
@app.route('/lwlr',methods=['GET'])
def page_lwlr():
return render_template('lwlr.html')
@app.route('/es',methods=['GET'])
def es():
return render_template('es.html')
@app.route('/rw',methods=['GET'])
def rw():
return render_template('rw.html')
excel文件打开函数
python
def open_excel(form_data,key1='sheetname',key2='NH3-NO2'):
sheet_index=form_data[key1]
column_name=form_data[key2]
df=pd.read_excel(excel_path,f'Sheet{sheet_index}').dropna()
return df['DATE'].tolist(),df[column_name].tolist()
index.html表单数据处理函数
python
@app.route('/submit',methods=['POST'])
def submit():
# 获取表单数据
form_data=request.form
# 表单数据严格匹配审查
if not isinstance(form_data, ImmutableMultiDict):
return jsonify({"error": "Invalid form data format"}), 400
if 'sheetname' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
if 'NH3-NO2' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
# 根据表单数据打开对应excel
date,y=open_excel(form_data)
# 返回时间序列数据
return jsonify({
'date':date,
'y':y
})
lwlr.html表单数据处理函数
python
# lwlr数学定义
def lwlr(test_point,X,y,k):
m = X.shape[0]
weights = np.eye(m)
for i in range(m):
xi = X[i]
weights[i, i] = np.exp(np.dot((xi - test_point), (xi - test_point).T) / (-2.0 * k**2))
X_T = X.T
W = weights
beta = np.linalg.inv(X_T @ (W @ X)) @ (X_T @ (W @ y))
return test_point @ beta
@app.route('/submit_lwlr',methods=['POST'])
def submit_lwlr():
# 获取表单数据
form_data=request.form
# 表单数据严格匹配审查
if not isinstance(form_data, ImmutableMultiDict):
return jsonify({"error": "Invalid form data format"}), 400
if 'sheetname' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
if 'NH3-NO2' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
if 'k' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
# 根据表单数据打开对应excel
date,y=open_excel(form_data)
# 适当转化便于后续匹配
k=float(form_data['k'])
x=np.arange(len(date)).reshape(-1,1)
# 滚动预测
y_preds=[]
for i in range(2,x.shape[0]+1):
X_pred=np.array([i]).reshape(-1,1)
y_pred=[lwlr(test_point, x[:i], y[:i], k) for test_point in X_pred]
y_preds.append(y_pred[0])
# 根据需要可保留至小数2位
y_preds=[round(x,2) for x in y_preds]
# 返回原始数据和预测数据
return jsonify({
"date":date,
"y1":y,
"y2":y_preds
})
es.html表单数据处理函数
python
# 平滑滤波
def exponential_smoothing(series, alpha=0.5):
"""
series: 时间序列数据
alpha: 平滑系数
"""
result = [series[0]] # 第一项为序列的第一值
for n in range(1, len(series)):
result.append(alpha * series[n] + (1 - alpha) * result[n-1])
return result
@app.route('/submit_es',methods=['POST'])
def submit_es():
# 获取表单数据
form_data=request.form
# 表单数据严格匹配审查
if not isinstance(form_data, ImmutableMultiDict):
return jsonify({"error": "Invalid form data format"}), 400
if 'sheetname' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
if 'NH3-NO2' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
if 'k' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
k=float(form_data['k'])
if k<0.01 or k>0.99:
return jsonify({"error": "Invalid form data format"}), 400
# 根据表单数据打开对应excel
date,y=open_excel(form_data)
# 进行滚动预测
y_preds=exponential_smoothing(y,k)
# 根据需要可保留至小数2位
y_preds=[round(x,2) for x in y_preds]
# 返回原始数据和预测数据
return jsonify({
"date":date,
"y1":y,
"y2":y_preds
})
rw.html表单数据处理函数
python
@app.route('/submit_rwsg',methods=['POST'])
def submit_rwsg():
# 获取表单数据
form_data=request.form
# 表单数据严格匹配审查
if not isinstance(form_data, ImmutableMultiDict):
return jsonify({"error": "Invalid form data format"}), 400
if 'sheetname' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
if 'NH3-NO2' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
if 'k1' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
if 'k2' not in form_data:
return jsonify({"error": "Invalid form data format"}), 400
# 适当转化便于后续匹配
k1=int(form_data['k1'])
k2=int(form_data['k2'])
# 表单数据严格审查
if k1<=k2:
return jsonify({"error": "Invalid form data format"}), 400
if k1>=len(date):
return jsonify({"error": "Invalid form data format"}), 400
# 根据表单数据打开对应excel
date,y=open_excel(form_data)
# 对原始数据进行SG滤波
y=savgol_filter(res, window_length=k1, polyorder=k2)
# 滚动预测
res=[]
for i in range(2,len(y)+1):
model = SimpleExpSmoothing(y[:i])
fit = model.fit()
# 预测未来1天
forecast = fit.forecast(1)[0]
res.append(forecast)
# 对模型输出进行极小值抑制
res=[x if x>=0.05 else 0.05 for x in res ]
y_preds=res.tolist()
# 根据需要可保留至小数2位
y_preds=[round(x,2) for x in y_preds]
# 返回原始数据和预测数据
return jsonify({
"date":date,
"y1":y,
"y2":y_preds
})
index.html
整体框架
html
<body>
<nav>
<a href="/">数据集</a>
<a href="/lwlr">LWLR</a>
<a href="/es">ES</a>
<a href="/rw">RWSG</a>
</nav>
<header>
<form>
<select></select>
<select></select>
<button></button>
</form>
<button><botton>
</header>
<main>
<section>
<table id="table"></table>
</section>
<section>
<div id="plot"></div>
</section>
</main>
动画
html
@keyframes bottomIn{
from {
transform: translateY(4dvh);
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes topIn{
from {
transform: translateY(-4dvh);
}
to {
transform: translateY(0);
opacity: 1;
}
}
@keyframes rightIn{
from {
transform: translateX(30vw);
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes leftIn{
from {
transform: translateX(-30vw);
}
to {
transform: translateX(0);
opacity: 1;
}
}
nav{
opacity: 0;
animation: 1.1s topIn ease-out forwards;
animation-delay: 0.3s;
}
header{
opacity: 0;
animation: 1s topIn ease-out forwards;
animation-delay: 0.2s;
}
#left{
opacity: 0;
animation: 1.1s leftIn ease-out forwards;
animation-delay: 0.3s;
}
#right{
opacity: 0;
animation: 1.1s rightIn ease-out forwards;
animation-delay: 0.3s;
}
背景切换
用全局css变量定义各模块的前景色和背景色,点击背景切换按钮则触发js把变量的值相互替换
html
/*css*/
a:hover,button:hover,select:hover{
transform: scale(1.1);
text-shadow: 0 0 3vw var(--f-color);
box-shadow: 0 0 3vw var(--f-color);
}
select{
appearance: none;
color: var(--f-color);
background: var(--b-color);
width: 8vw;
height: 4dvh;
border: 1px solid var(--f-color);
border-radius: 1dvh;
padding-left: 1vw;
}
:root{
--f-color:#cccccc;
--b-color:#333333;
}
/*js*/
function changeTheme(){
const root=document.documentElement
const color1=window.getComputedStyle(root).getPropertyValue('--f-color').trim()
const color2=window.getComputedStyle(root).getPropertyValue('--b-color').trim()
root.style.setProperty('--f-color',color2)
root.style.setProperty('--b-color',color1)
}
响应式布局
所有模块大小使用相对视口尺寸定义
html
select{
appearance: none;
color: var(--f-color);
background: var(--b-color);
width: 8vw;
height: 4dvh;
border: 1px solid var(--f-color);
border-radius: 1dvh;
padding-left: 1vw;
}
鼠标浮动光影
html
a:hover,button:hover,select:hover{
transform: scale(1.1);
text-shadow: 0 0 3vw var(--f-color);
box-shadow: 0 0 3vw var(--f-color);
}
异步表单提交
javascript
async function submitForm(event){
event.preventDefault()
const submitButton = event.target.querySelector('button[type="submit"]')
submitButton.disabled = true
const formData=new FormData(event.target)
try{
const response=await fetch('/submit',{
method:'POST',
body:formData
})
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
const data = await response.json()
createTable(data)
createPlot(data)
}
catch(error){
console.error('Error submitting form:', error);
alert('非法输入')
}
finally{
submitButton.disabled = false
}
}
表格绘制函数
javascript
function createTable(data){
const table=document.getElementById('table')
table.innerHTML=''
const thead=document.createElement('thead')
const tr=document.createElement('tr')
const th1=document.createElement('th')
const th2=document.createElement('th')
const tbody=document.createElement('tbody')
th1.textContent='时间'
tr.appendChild(th1)
th2.textContent='index'
tr.appendChild(th2)
thead.appendChild(tr)
table.appendChild(thead)
for(let i=0;i<data.date.length;i++){
const tr=document.createElement('tr')
const td1=document.createElement('td')
const td2=document.createElement('td')
td1.textContent=data.date[i]
tr.appendChild(td1)
td2.textContent=data.y[i]
tr.appendChild(td2)
tbody.appendChild(tr)
}
table.appendChild(tbody)
}
图像数据绘制
javascript
function createPlot(data){
const y=data.y
const x=Array.from({length:y.length},(_,index)=>index)
const traces=[{
x,
y,
mode: "lines",
type: "scatter"
}]
const layout={
xaxis:{
title:'时间',
titlefont: {
color: 'green',
size:20
},
tickfont: {
color: 'green',
size:20
}
},
yaxis:{
title:'index',
titlefont: {
color: 'green',
size:20
},
tickfont: {
color: 'green',
size:20
}
},
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)'
}
Plotly.newPlot("plot",traces,layout)
}
其他html
与index.html基本相同,主要区别在画图
javascript
function createPlot(data){
data.y1.pop()
data.y2.shift()
data.y2.shift()
const y1=data.y1
const x1=Array.from({ length: y1.length }, (_, index) => index)
const y2=data.y2
const x2=Array.from({ length: y2.length }, (_, index) => index+2)
const trace1={
x:x1,
y:y1,
name:'True',
mode: "lines",
type: "scatter"
}
const trace2={
x:x2,
y:y2,
name:'lwlr-predict',
mode: "lines",
type: "scatter"
}
const y1y2=[trace1,trace2]
const layout={
xaxis:{
title:'时间',
titlefont: {
color: 'green',
size:20
},
tickfont: {
color: 'green',
size:20
}
},
yaxis:{
title:'index',
titlefont: {
color: 'green',
size:20
},
tickfont: {
color: 'green',
size:20
}
},
legend:{
font:{color:'green',size:20}
},
showlegend:true,
paper_bgcolor: 'rgba(0,0,0,0)',
plot_bgcolor: 'rgba(0,0,0,0)'
}
Plotly.newPlot("plot",y1y2,layout)
}
绘表
javascript
function createTable(data){
const table=document.getElementById('table')
table.innerHTML=''
const thead=document.createElement('thead')
const tr=document.createElement('tr')
const th1=document.createElement('th')
const th2=document.createElement('th')
const th3=document.createElement('th')
const tbody=document.createElement('tbody')
th1.textContent='时间'
tr.appendChild(th1)
th2.textContent='index'
tr.appendChild(th2)
th3.textContent='预测值'
tr.appendChild(th3)
thead.appendChild(tr)
table.appendChild(thead)
let lastDateStr = data.date[data.date.length-1]
let lastDate = new Date(lastDateStr)
lastDate.setDate(lastDate.getDate() + 1);
let newDateStr = lastDate.toISOString().split('T')[0];
data.date.push(newDateStr);
data.y1.push(null)
data.y2.unshift(null)
data.y2.unshift(null)
for(let i=0;i<data.y1.length;i++){
const tr=document.createElement('tr')
const td1=document.createElement('td')
const td2=document.createElement('td')
const td3=document.createElement('td')
td1.textContent=data.date[i]
tr.appendChild(td1)
td2.textContent=data.y1[i]
tr.appendChild(td2)
td3.textContent=data.y2[i]
tr.appendChild(td3)
tbody.appendChild(tr)
}
table.appendChild(tbody)
}