From befbf6c0298f3f60c451db174044e64966ae8226 Mon Sep 17 00:00:00 2001 From: cloud Date: Wed, 19 Aug 2026 10:22:59 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D:=20=E8=BF=9E=E6=8E=A5?= =?UTF-8?q?=E7=A9=BA=E5=88=86=E7=AE=B1=E9=97=B4=E7=9A=84=E5=AE=9E=E9=99=85?= =?UTF-8?q?=E5=8A=9F=E7=8E=87=E6=9B=B2=E7=BA=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- frontend/web_app/package.json | 1 + frontend/web_app/src/pages/HomePage.jsx | 19 +++++------- frontend/web_app/src/utils/powerCurveModel.js | 6 ++++ .../web_app/src/utils/powerCurveModel.test.js | 29 +++++++++++++++++++ 4 files changed, 43 insertions(+), 12 deletions(-) create mode 100644 frontend/web_app/src/utils/powerCurveModel.test.js diff --git a/frontend/web_app/package.json b/frontend/web_app/package.json index 6fc022e..1546b33 100644 --- a/frontend/web_app/package.json +++ b/frontend/web_app/package.json @@ -7,6 +7,7 @@ "dev": "vite", "build": "vite build", "lint": "eslint .", + "test": "node --test", "preview": "vite preview" }, "dependencies": { diff --git a/frontend/web_app/src/pages/HomePage.jsx b/frontend/web_app/src/pages/HomePage.jsx index e1bd379..aff0810 100644 --- a/frontend/web_app/src/pages/HomePage.jsx +++ b/frontend/web_app/src/pages/HomePage.jsx @@ -13,7 +13,11 @@ import { startWindJob, uploadWindChunk, } from '../utils/api'; -import { alignDesignCurveToActualCurve, buildPowerCurveModel } from '../utils/powerCurveModel'; +import { + alignDesignCurveToActualCurve, + buildPowerCurveModel, + getDrawableActualCurve, +} from '../utils/powerCurveModel'; const REQUIRED_FIELDS = [ { key: 'time', label: '时间' }, @@ -817,15 +821,6 @@ function hexToRgba(color, opacity) { return `rgba(${(value >> 16) & 255}, ${(value >> 8) & 255}, ${value & 255}, ${alpha})`; } -function getVisibleActualCurve(actualCurve) { - const sorted = [...actualCurve].sort((left, right) => left.wind_speed - right.wind_speed); - const validIndexes = sorted - .map((point, index) => (Number.isFinite(point.average_power) ? index : -1)) - .filter((index) => index >= 0); - if (!validIndexes.length) return sorted; - return sorted.slice(validIndexes[0], validIndexes[validIndexes.length - 1] + 1); -} - function renderPowerCurvePng({ actualCurve, designCurve, scatterPoints, filteredPoints, showActual, showDesign, showScatter, showFiltered, options, scales }) { const width = 1800; const height = 1160; @@ -1247,7 +1242,7 @@ function PowerCurveChart({ }), [curveScatterPoints, points, validDesign]); const actualCurve = curveModel.actualCurve; const visibleActualCurve = useMemo( - () => getVisibleActualCurve(actualCurve), + () => getDrawableActualCurve(actualCurve), [actualCurve], ); const visibleDesignCurve = useMemo( @@ -1402,7 +1397,7 @@ function PowerCurveChart({ stroke: activeChartOptions.actual_color, width: 0.01, paths: uPlot.paths.spline(), - spanGaps: false, + spanGaps: true, points: { show: false }, }, { diff --git a/frontend/web_app/src/utils/powerCurveModel.js b/frontend/web_app/src/utils/powerCurveModel.js index b0f7e57..be66a13 100644 --- a/frontend/web_app/src/utils/powerCurveModel.js +++ b/frontend/web_app/src/utils/powerCurveModel.js @@ -95,6 +95,12 @@ export function buildActualCurveFromFixedBins(scatterPoints = [], binSize = 0.5) .sort((left, right) => left.wind_speed - right.wind_speed); } +export function getDrawableActualCurve(actualCurve = []) { + return actualCurve + .filter((point) => validNumber(point?.wind_speed) && validNumber(point?.average_power)) + .sort((left, right) => left.wind_speed - right.wind_speed); +} + function interpolatePowerAtWindSpeed(designCurve, windSpeed) { if (!designCurve.length || windSpeed < designCurve[0].wind_speed - EPSILON || windSpeed > designCurve[designCurve.length - 1].wind_speed + EPSILON) { diff --git a/frontend/web_app/src/utils/powerCurveModel.test.js b/frontend/web_app/src/utils/powerCurveModel.test.js new file mode 100644 index 0000000..4d0eea8 --- /dev/null +++ b/frontend/web_app/src/utils/powerCurveModel.test.js @@ -0,0 +1,29 @@ +import assert from 'node:assert/strict'; +import test from 'node:test'; +import { buildActualCurveFromDesign, getDrawableActualCurve } from './powerCurveModel.js'; + +test('getDrawableActualCurve skips empty design bins and keeps adjacent valid points', () => { + const actualCurve = buildActualCurveFromDesign( + [ + { wind_speed: 19.6, active_power: 5520 }, + { wind_speed: 20.8, active_power: 5510 }, + ], + [ + { wind_speed: 19.5, design_power: 5500 }, + { wind_speed: 20, design_power: 5500 }, + { wind_speed: 20.5, design_power: 5500 }, + { wind_speed: 21, design_power: 5500 }, + ], + ); + + assert.deepEqual( + getDrawableActualCurve(actualCurve).map((point) => ({ + wind_speed: point.wind_speed, + average_power: point.average_power, + })), + [ + { wind_speed: 19.5, average_power: 5520 }, + { wind_speed: 21, average_power: 5510 }, + ], + ); +});