feat(workout): 优化心率图表性能并移除每日总结通知功能

- 重构心率数据采样算法,采用智能采样保留峰值、谷值和变化率大的点
- 减少心率图表最大数据点数和查询限制,提升渲染性能
- 移除图表背景线样式,简化视觉呈现
- 完全移除每日总结通知功能相关代码和调用
This commit is contained in:
2025-10-11 21:53:18 +08:00
parent d43d8c692f
commit ed3a178aa0
4 changed files with 84 additions and 315 deletions

View File

@@ -46,7 +46,7 @@ interface WorkoutDetailModalProps {
const SCREEN_HEIGHT = Dimensions.get('window').height;
const SHEET_MAX_HEIGHT = SCREEN_HEIGHT * 0.9;
const HEART_RATE_CHART_MAX_POINTS = 120;
const HEART_RATE_CHART_MAX_POINTS = 80;
export function WorkoutDetailModal({
visible,
@@ -338,7 +338,7 @@ export function WorkoutDetailModal({
height={220}
fromZero={false}
yAxisSuffix="次/分"
withInnerLines
withInnerLines={false}
bezier
chartConfig={{
backgroundColor: '#FFFFFF',
@@ -352,11 +352,6 @@ export function WorkoutDetailModal({
strokeWidth: '2',
stroke: '#FFFFFF',
},
propsForBackgroundLines: {
strokeDasharray: '3,3',
stroke: '#E3E6F4',
strokeWidth: 1,
},
fillShadowGradientFromOpacity: 0.1,
fillShadowGradientToOpacity: 0.02,
}}
@@ -475,14 +470,88 @@ function trimHeartRateSeries(series: WorkoutDetailMetrics['heartRateSeries']) {
return series;
}
const step = Math.ceil(series.length / HEART_RATE_CHART_MAX_POINTS);
const reduced = series.filter((_, index) => index % step === 0);
// 智能采样算法:保留重要特征点(峰值、谷值、变化率大的点)
const result: typeof series = [];
const n = series.length;
if (reduced[reduced.length - 1] !== series[series.length - 1]) {
reduced.push(series[series.length - 1]);
// 总是保留第一个和最后一个点
result.push(series[0]);
// 计算心率变化率
const changeRates: number[] = [];
for (let i = 1; i < n; i++) {
const prevValue = series[i - 1].value;
const currValue = series[i].value;
const prevTime = dayjs(series[i - 1].timestamp).valueOf();
const currTime = dayjs(series[i].timestamp).valueOf();
const timeDiff = Math.max(currTime - prevTime, 1000); // 至少1秒避免除零
const valueDiff = Math.abs(currValue - prevValue);
changeRates.push(valueDiff / timeDiff * 1000); // 变化率:每秒变化量
}
return reduced;
// 计算变化率的阈值前75%的分位数)
const sortedRates = [...changeRates].sort((a, b) => a - b);
const thresholdIndex = Math.floor(sortedRates.length * 0.75);
const changeThreshold = sortedRates[thresholdIndex] || 0;
// 识别局部极值点
const isLocalExtremum = (index: number): boolean => {
if (index === 0 || index === n - 1) return false;
const prev = series[index - 1].value;
const curr = series[index].value;
const next = series[index + 1].value;
// 局部最大值
if (curr > prev && curr > next) return true;
// 局部最小值
if (curr < prev && curr < next) return true;
return false;
};
// 遍历所有点,选择重要点
let minDistance = Math.max(1, Math.floor(n / HEART_RATE_CHART_MAX_POINTS));
for (let i = 1; i < n - 1; i++) {
const shouldKeep =
// 1. 是局部极值点
isLocalExtremum(i) ||
// 2. 变化率超过阈值
(i > 0 && changeRates[i - 1] > changeThreshold) ||
// 3. 均匀分布的点(确保基本覆盖)
(i % minDistance === 0);
if (shouldKeep) {
// 检查与上一个选中点的距离,避免过于密集
const lastSelectedIndex = result.length > 0 ?
series.findIndex(p => p.timestamp === result[result.length - 1].timestamp) : 0;
if (i - lastSelectedIndex >= minDistance || isLocalExtremum(i)) {
result.push(series[i]);
}
}
}
// 确保最后一个点被包含
if (result[result.length - 1].timestamp !== series[n - 1].timestamp) {
result.push(series[n - 1]);
}
// 如果结果仍然太多,进行二次采样
if (result.length > HEART_RATE_CHART_MAX_POINTS) {
const finalStep = Math.ceil(result.length / HEART_RATE_CHART_MAX_POINTS);
const finalResult = result.filter((_, index) => index % finalStep === 0);
// 确保最后一个点被包含
if (finalResult[finalResult.length - 1] !== result[result.length - 1]) {
finalResult.push(result[result.length - 1]);
}
return finalResult;
}
return result;
}
function renderHeartRateZone(zone: HeartRateZoneStat) {
@@ -745,6 +814,7 @@ const styles = StyleSheet.create({
},
chartStyle: {
marginLeft: -10,
marginRight: -10,
},
chartEmpty: {
paddingVertical: 32,