前言
开发语言:python3.11.6、javascript、html5‘、css3
开发框架:flask、plotly.js
开发系统:windows10 22H2
开发编辑器:vscode
作用:展示水产养殖水体氨氮和亚硝酸盐时间序列数据,使用LWLR、ESE、DLM模型进行滚动预测并绘制曲线
开发初衷:为了项目汇报更具象
开源性质:完全开源,任意使用
项目资源获取https://pan.baidu.com/s/1V53DPLVP7FT65-FtYVP44Q?pwd=1sdthttps://pan.baidu.com/s/1V53DPLVP7FT65-FtYVP44Q?pwd=1sdt
文件目录展示
图表展示static氨氮预测.xlsxplotly-2.2.0.min.jstemplateses.htmlindex.htmllwlr.htmlrw.htmlmain.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"}), 400if 'sheetname' not in form_data:return jsonify({"error": "Invalid form data format"}), 400if 'NH3-NO2' not in form_data:return jsonify({"error": "Invalid form data format"}), 400# 根据表单数据打开对应exceldate,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.TW = weightsbeta = 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"}), 400if 'sheetname' not in form_data:return jsonify({"error": "Invalid form data format"}), 400if 'NH3-NO2' not in form_data:return jsonify({"error": "Invalid form data format"}), 400if 'k' not in form_data:return jsonify({"error": "Invalid form data format"}), 400# 根据表单数据打开对应exceldate,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"}), 400if 'sheetname' not in form_data:return jsonify({"error": "Invalid form data format"}), 400if 'NH3-NO2' not in form_data:return jsonify({"error": "Invalid form data format"}), 400if 'k' not in form_data:return jsonify({"error": "Invalid form data format"}), 400k=float(form_data['k'])if k<0.01 or k>0.99:return jsonify({"error": "Invalid form data format"}), 400# 根据表单数据打开对应exceldate,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"}), 400if 'sheetname' not in form_data:return jsonify({"error": "Invalid form data format"}), 400if 'NH3-NO2' not in form_data:return jsonify({"error": "Invalid form data format"}), 400if 'k1' not in form_data:return jsonify({"error": "Invalid form data format"}), 400if '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"}), 400if k1>=len(date):return jsonify({"error": "Invalid form data format"}), 400# 根据表单数据打开对应exceldate,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
整体框架
<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>
动画
@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把变量的值相互替换
/*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.documentElementconst 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)}
响应式布局
所有模块大小使用相对视口尺寸定义
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;}
鼠标浮动光影
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);}
异步表单提交
async function submitForm(event){event.preventDefault()const submitButton = event.target.querySelector('button[type="submit"]')submitButton.disabled = trueconst 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}}
表格绘制函数
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)}
图像数据绘制
function createPlot(data){const y=data.yconst 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基本相同,主要区别在画图
function createPlot(data){data.y1.pop()data.y2.shift()data.y2.shift()const y1=data.y1const x1=Array.from({ length: y1.length }, (_, index) => index)const y2=data.y2const 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)}
绘表
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)}