水位流量关系曲线是水文监测工作里最基础也最重要的一条曲线,它描述了测验断面上水位与流量之间的对应规律。在防汛调度、水资源管理以及水文资料整编中,这条曲线都是推算流量的核心依据。随着水利信息化建设的推进,越来越多的水文监测大屏需要在页面上实时展示这条曲线,并根据最新的实测数据自动重新拟合。本文以jQuery为主技术栈,从数学模型、拟合算法、前端渲染到实时数据接入,完整讲解如何在监测大屏中落地水位流量关系的动态拟合。

一、水位流量关系的数学模型与拟合原理
对于河床比较稳定、控制条件良好的断面,水位Z与流量Q之间可以用幂函数形式来描述,即Q = C × (Z − Z0)^n。其中Z0是断流水位,也就是流量恰好为零时对应的水位,C和n是待确定的系数和指数。这个模型之所以被广泛采用,是因为它物理意义明确,形式简单,而且在正常水流条件下与实测点据的吻合程度相当好。
拟合的关键在于如何求解C和n。直接对幂函数做非线性回归计算量较大,工程上普遍采用对数变换法:对等式两边取对数,得到lgQ = lgC + n × lg(Z − Z0),这样幂函数就转化成了线性方程。将实测的每一对水位流量数据变换为对数坐标点后,用最小二乘法求出斜率n和截距lgC,再反推得到C,整个过程只涉及初等运算,用JavaScript实现毫无压力。
断流水位Z0的处理需要单独说明。Z0无法直接观测,实际项目中有两种常见做法:一是根据断面历史最低水位减去一个经验余量作为固定值,这种方式简单稳定,适合监测大屏这类对实时性要求高的场合;二是在一个区间内遍历Z0的取值,取使拟合残差平方和最小的那个值,精度更高但计算量大,更适合离线整编分析。大屏项目中建议采用第一种,把Z0作为断面的配置参数写在页面初始化代码里。
二、用jQuery实现最小二乘法拟合
拟合逻辑与界面无关,建议写成独立的工具函数挂在window对象上,方便页面各处调用。下面的代码接收实测数据数组,先做对数变换,再完成最小二乘求解:
// 对数变换 + 线性最小二乘拟合
// data: [{ z: 水位, q: 流量 }, ...] z0: 断流水位
function fitZQ(data, z0) {
var sx = 0, sy = 0, sxx = 0, sxy = 0;
var pts = [];
for (var i = 0; i < data.length; i++) {
var dz = data[i].z - z0;
var q = data[i].q;
// 剔除无效点:水位必须大于断流水位且流量为正
if (dz <= 0 || q <= 0) continue;
pts.push({ x: Math.log(dz), y: Math.log(q) });
}
var n = pts.length;
if (n < 2) return null; // 有效点不足无法拟合
for (var j = 0; j < n; j++) {
sx += pts[j].x;
sy += pts[j].y;
sxx += pts[j].x * pts[j].x;
sxy += pts[j].x * pts[j].y;
}
// 最小二乘解:y = a + b*x
var b = (n * sxy - sx * sy) / (n * sxx - sx * sx);
var a = (sy - b * sx) / n;
return { C: Math.exp(a), n: b, z0: z0 };
}
// 根据拟合参数生成曲线点,供图表绘制使用
function buildCurvePoints(fit, zMin, zMax, step) {
var arr = [];
for (var z = zMin; z <= zMax; z += step) {
var dz = z - fit.z0;
if (dz <= 0) continue;
arr.push([+z.toFixed(3), +(fit.C * Math.pow(dz, fit.n)).toFixed(3)]);
}
return arr;
}这段代码有几个细节值得注意。第一,数据清洗放在变换之前,水位低于断流水位或流量非正的点必须跳过,否则对数运算会产生NaN,直接污染整条拟合结果。第二,代码里用的是自然对数,所以反推系数C时要用Math.exp(a)而不是10的幂次。第三,buildCurvePoints生成的二维数组格式与ECharts直接兼容,省去了中间的格式转换。
在jQuery页面中,把拟合调用挂在统一的更新入口上。页面加载时先通过Ajax拉取历史实测数据完成初始拟合,之后每当有新数据到达,重新拟合并触发绘图刷新即可:
$(function () {
var z0 = 10.50; // 断流水位,按断面实际情况配置
window.rawData = [];
// 初始加载历史数据并完成首次拟合
$.getJSON('/api/historicalZQ', function (res) {
rawData = res.data;
renderChart(rawData, fitZQ(rawData, z0));
});
// 实时数据到达后的统一更新入口
window.updateZQ = function (point) {
rawData.push(point);
if (rawData.length > 200) rawData.shift(); // 控制点据规模
renderChart(rawData, fitZQ(rawData, z0));
};
});三、曲线渲染方案对比与ECharts实现
渲染层面主流有两条路线:原生Canvas手绘和ECharts图表库。两者各有优劣,先看对比:
| 对比项 | 原生Canvas | ECharts |
|---|---|---|
| 依赖体积 | 零依赖,最轻量 | 压缩后约800KB |
| 开发效率 | 坐标轴、刻度、提示框全部手写 | 配置即用,自带tooltip与缩放 |
| 动画效果 | 需自行实现插值动画 | 内置平滑过渡动画 |
| 适用场景 | 极端性能要求或嵌入式环境 | 绝大多数监测大屏 |
对于水文监测大屏,推荐直接使用ECharts。大屏通常要同时展示水位过程线、流量过程线、雨量柱状图等多张图表,ECharts统一的主题风格和图表联动能力可以显著减少开发量。下面是结合拟合结果的完整配置:
function renderChart(rawData, fit) {
if (!fit || !myChart) return;
var zs = rawData.map(function (d) { return d.z; });
var zMin = Math.min.apply(null, zs) - 0.5;
var zMax = Math.max.apply(null, zs) + 0.5;
var curve = buildCurvePoints(fit, Math.max(zMin, fit.z0 + 0.05), zMax, 0.05);
var last = rawData[rawData.length - 1];
myChart.setOption({
title: { text: '水位流量关系曲线', left: 'center' },
tooltip: { trigger: 'item' },
grid: { left: 70, right: 40, bottom: 50 },
xAxis: { name: '水位(m)', type: 'value', min: zMin, max: zMax,
splitLine: { show: true, lineStyle: { opacity: 0.15 } } },
yAxis: { name: '流量(m³/s)', type: 'value', scale: true },
series: [
{ name: '拟合曲线', type: 'line', smooth: true, symbol: 'none',
lineStyle: { width: 3, color: '#00c6ff' }, data: curve },
{ name: '实测点', type: 'scatter', symbolSize: 8,
itemStyle: { color: '#ffb800' },
data: rawData.map(function (d) { return [d.z, d.q]; }) },
{ name: '当前点', type: 'effectScatter', symbolSize: 14,
rippleEffect: { scale: 3 },
itemStyle: { color: '#ff4d4f' }, data: [[last.z, last.q]] }
]
});
}这套配置里三个series分工明确:平滑折线呈现拟合曲线的整体趋势,普通散点展示实测点据分布,带涟漪特效的散点高亮当前实时点。调度人员一眼就能看出当前点落在关系线的上方还是下方,进而判断断面是否发生了冲淤变化,这正是大屏可视化要达到的效果。
四、实时数据接入与大屏适配注意事项
实时数据接入建议使用WebSocket,避免轮询带来的延迟和请求开销。需要注意的是,接收到新数据后不要立即触发整图重绘,涨水期数据推送密集时,涟漪动画会被不断打断,视觉效果反而变差。建议做一个简单的节流:把新数据放入缓冲队列,每隔几秒批量合并、重新拟合并刷新一次:
var ws = new WebSocket('ws://192.168.0.1:8080/realtime');
var buffer = [];
ws.onmessage = function (evt) {
var msg = JSON.parse(evt.data);
if (msg.type === 'ZQ') buffer.push(msg.data);
};
// 每5秒合并一次缓冲队列并重新拟合
setInterval(function () {
if (!buffer.length) return;
while (buffer.length) rawData.push(buffer.shift());
var fit = fitZQ(rawData, 10.50);
renderChart(rawData, fit);
}, 5000);拟合之前还应做异常值检查。常用做法是计算各实测点相对拟合曲线的残差,凡偏差超过三倍标准差的点标记为可疑点,不参与下一轮拟合,但仍以灰色散点显示在图上,供技术人员人工研判。传感器毛刺、人工误录这类脏数据如果直接进入拟合,会让整条曲线发生明显偏移,这一步过滤在生产环境中必不可少。
大屏适配方面,最容易被忽略的是devicePixelRatio问题。拼接屏或高分辨率LED屏上浏览器缩放比例经常不是100%,图表容器尺寸变化后必须手动调用myChart.resize(),否则会出现模糊或留白。如果大屏采用外层容器transform等比缩放的方案,还要在缩放系数变化后补一次resize,这是实施过程中高频踩坑点,务必在联调阶段验证。最后建议把每次拟合得到的参数C、n连同拟合时间一起落库,便于后续回溯断面冲淤演变过程,为水文资料整编提供数据支撑。