diff --git a/public/abpm-report-template.html b/public/abpm-report-template.html index 8e49199..5f75da1 100644 --- a/public/abpm-report-template.html +++ b/public/abpm-report-template.html @@ -204,7 +204,7 @@ .scatter-chart-item .chart { width: 100%; - height: 320px; + height: 400px; border: 1px solid #000; } @@ -910,7 +910,6 @@
-
血压和心率平均值趋势图
@@ -1657,7 +1656,17 @@ // 计算血压数据的最大值(收缩压和舒张压合并考虑) const bpValues = [...systolicValues, ...diastolicValues]; const bpMaxValue = getYAxisMax(bpValues, 20); - + // 新增:计算刻度间隔为10、20、30、40 + let bpInterval = 10; + if (bpMaxValue > 300) { + bpInterval = 40; + } else if (bpMaxValue > 200) { + bpInterval = 30; + } else if (bpMaxValue > 100) { + bpInterval = 20; + } else { + bpInterval = 10; + } // 计算心率数据的最大值 const hrMaxValue = getYAxisMax(heartRateValues, 10); @@ -1824,8 +1833,8 @@ axisLabel: { formatter: (value) => { const date = new Date(value); - // 只在整点显示 - if (date.getMinutes() === 0) { + // 只在整点且为偶数小时显示 + if (date.getMinutes() === 0 && date.getHours() % 2 === 0) { return `${date.getHours().toString().padStart(2, '0')}`; } return ''; @@ -1859,7 +1868,7 @@ position: 'left', min: 0, max: bpMaxValue, - interval: Math.max(10, Math.round(bpMaxValue / 10)), + interval: bpInterval, axisLabel: { color: '#666' }, @@ -1989,60 +1998,86 @@ function renderHistogram(elementId, data, field, title) { const chart = echarts.init(document.getElementById(elementId)); + // 获取有效数据 + const validData = data.filter(item => item[field] != null && !isNaN(item[field])); + + if (validData.length === 0) { + // 如果没有有效数据,显示空图表 + const option = { + title: { text: '暂无数据', left: 'center', top: 'middle' }, + grid: { left: 50, right: 20, bottom: 40, top: 30 } + }; + chart.setOption(option); + return; + } + + // 计算数据的最小值和最大值 + const values = validData.map(item => item[field]); + const minValue = Math.min(...values); + const maxValue = Math.max(...values); + let mmHgRanges, labels, counts, colors; + + // 根据字段类型和实际数据范围动态生成区间 if (field === 'systolic') { - mmHgRanges = [130, 140, 150, 160, 170, 180, 190]; + // 收缩压:从实际最小值开始,以10为间隔,确保覆盖所有数据 + const start = Math.floor(minValue / 10) * 10; + const end = Math.ceil(maxValue / 10) * 10; + mmHgRanges = []; + for (let i = start; i <= end; i += 10) { + mmHgRanges.push(i); + } const kPaRanges = mmHgRanges.map(v => (v / 7.5).toFixed(1)); labels = mmHgRanges.map((v, i) => `${v} (${kPaRanges[i]})`); - counts = Array(mmHgRanges.length).fill(0); - data.forEach(item => { - const value = item[field]; - for (let i = 0; i < mmHgRanges.length; i++) { - if (i === mmHgRanges.length - 1) { - if (value >= mmHgRanges[i]) counts[i]++; - } else if (value >= mmHgRanges[i] && value < mmHgRanges[i + 1]) { - counts[i]++; - break; - } - } - }); colors = '#5bc0de'; } else if (field === 'diastolic') { - mmHgRanges = [70, 80, 90, 100, 110, 120, 130, 140, 150, 160]; + // 舒张压:从实际最小值开始,以10为间隔,确保覆盖所有数据 + const start = Math.floor(minValue / 10) * 10; + const end = Math.ceil(maxValue / 10) * 10; + mmHgRanges = []; + for (let i = start; i <= end; i += 10) { + mmHgRanges.push(i); + } const kPaRanges = mmHgRanges.map(v => (v / 7.5).toFixed(1)); labels = mmHgRanges.map((v, i) => `${v} (${kPaRanges[i]})`); - counts = Array(mmHgRanges.length).fill(0); - data.forEach(item => { - const value = item[field]; - for (let i = 0; i < mmHgRanges.length; i++) { - if (i === mmHgRanges.length - 1) { - if (value >= mmHgRanges[i]) counts[i]++; - } else if (value >= mmHgRanges[i] && value < mmHgRanges[i + 1]) { - counts[i]++; - break; - } - } - }); colors = '#5bc0de'; } else { - // heartRate - mmHgRanges = [60, 70, 80, 90, 100, 110, 120]; + // 心率:从实际最小值开始,以10为间隔,确保覆盖所有数据 + const start = Math.floor(minValue / 10) * 10; + const end = Math.ceil(maxValue / 10) * 10; + mmHgRanges = []; + for (let i = start; i <= end; i += 10) { + mmHgRanges.push(i); + } labels = mmHgRanges.map(v => `${v}`); - counts = Array(mmHgRanges.length).fill(0); - data.forEach(item => { - const value = item[field]; - for (let i = 0; i < mmHgRanges.length; i++) { - if (i === mmHgRanges.length - 1) { - if (value >= mmHgRanges[i]) counts[i]++; - } else if (value >= mmHgRanges[i] && value < mmHgRanges[i + 1]) { + colors = '#5bc0de'; + } + + // 初始化计数数组 + counts = Array(mmHgRanges.length).fill(0); + + // 统计每个区间的数据 + validData.forEach(item => { + const value = item[field]; + // 找到对应的区间 + for (let i = 0; i < mmHgRanges.length; i++) { + if (i === mmHgRanges.length - 1) { + // 最后一个区间包含所有大于等于该值的数据 + if (value >= mmHgRanges[i]) { + counts[i]++; + break; + } + } else { + // 其他区间:[当前值, 下一个值) + if (value >= mmHgRanges[i] && value < mmHgRanges[i + 1]) { counts[i]++; break; } } - }); - colors = '#5bc0de'; - } - const total = data.length; + } + }); + // 计算百分比 + const total = validData.length; const percentages = counts.map(count => total > 0 ? (count / total * 100).toFixed(1) : '0.0'); const option = { @@ -2193,8 +2228,9 @@ const bpChart = echarts.init(document.getElementById('scatter-bp-relation')); const bpData = chartDataTable.map(item => [item.diastolic, item.systolic]); const bpRegression = calculateLinearRegression(bpData); - const bpXArr = chartDataTable.map(item => item.diastolic).filter(v => v != null && !isNaN(v)); - const bpXAxisMax = getAxisMax(bpXArr); + // const bpXArr = chartDataTable.map(item => item.diastolic).filter(v => v != null && !isNaN(v)); + // const bpXAxisMax = getAxisMax(bpXArr); + const bpXAxisMax = 110; // 固定X轴最大值为110 // 生成拟合线数据 const bpFitLineData = []; for (let x = 40; x <= bpXAxisMax; x += 10) { @@ -2220,8 +2256,11 @@ xAxis: { type: 'value', name: '舒张压(mmHg)', + nameTextStyle: { + color: '#000' + }, // 设置为黑色 min: 40, - max: bpXAxisMax, + max: bpXAxisMax, // 固定为110 interval: 10, minorTick: { show: true, splitNumber: 5 }, minorSplitLine: { show: false }, @@ -2229,13 +2268,17 @@ nameGap: 30, axisLine: { show: true }, axisTick: { show: true }, - splitLine: { show: true, lineStyle: { color: '#e0e0e0' } } + splitLine: { show: false } // 隐藏竖线 }, yAxis: { type: 'value', name: '收缩压(mmHg)', + nameTextStyle: { + color: '#000' // 设置为黑色 + }, min: 100, - max: 200, + max: 180, // 固定为180 + interval: 10, // 步长改为10 nameLocation: 'middle', nameGap: 40, axisLine: { show: true }, @@ -2269,8 +2312,9 @@ const hrChart = echarts.init(document.getElementById('scatter-hr-relation')); const hrData = chartDataTable.map(item => [item.heartRate, item.systolic]); const hrRegression = calculateLinearRegression(hrData); - const hrXArr = chartDataTable.map(item => item.heartRate).filter(v => v != null && !isNaN(v)); - const hrXAxisMax = getAxisMax(hrXArr); + // const hrXArr = chartDataTable.map(item => item.heartRate).filter(v => v != null && !isNaN(v)); + // const hrXAxisMax = getAxisMax(hrXArr); + const hrXAxisMax = 110; // 固定X轴最大值为110 // 生成拟合线数据 const hrFitLineData = []; for (let x = 40; x <= hrXAxisMax; x += 10) { @@ -2296,8 +2340,11 @@ xAxis: { type: 'value', name: '心率(次/分)', + nameTextStyle: { + color: '#000' // 设置为黑色 + }, min: 40, - max: hrXAxisMax, + max: hrXAxisMax, // 固定为110 interval: 10, minorTick: { show: true, splitNumber: 5 }, minorSplitLine: { show: false }, @@ -2305,13 +2352,17 @@ nameGap: 30, axisLine: { show: true }, axisTick: { show: true }, - splitLine: { show: true, lineStyle: { color: '#e0e0e0' } } + splitLine: { show: false } // 隐藏竖线 }, yAxis: { type: 'value', name: '收缩压(mmHg)', + nameTextStyle: { + color: '#000' // 设置为黑色 + }, min: 100, - max: 200, + max: 180, // 固定为180 + interval: 10, // 步长改为10 nameLocation: 'middle', nameGap: 40, axisLine: { show: true }, @@ -2346,77 +2397,148 @@ // 渲染小时平均图 function renderHourlyChart() { const chart = echarts.init(document.getElementById('hourly-average-chart')); - // 按小时分组计算平均值 const hourlyData = Array.from({ length: 24 }, (_, hour) => { const hourData = chartDataTable.filter(item => new Date(item.originalTime).getHours() === hour); - if (hourData.length === 0) { return { hour, systolic: null, diastolic: null, heartRate: null }; } - const avgSystolic = Math.round(hourData.reduce((sum, item) => sum + item.systolic, 0) / hourData.length); const avgDiastolic = Math.round(hourData.reduce((sum, item) => sum + item.diastolic, 0) / hourData.length); const avgHeartRate = Math.round(hourData.reduce((sum, item) => sum + item.heartRate, 0) / hourData.length); - return { hour, systolic: avgSystolic, diastolic: avgDiastolic, heartRate: avgHeartRate }; }); - + // 计算Y轴最大值和间隔(与第一页一致) + const systolicArr = hourlyData.map(d => d.systolic).filter(v => v != null && !isNaN(v)); + const diastolicArr = hourlyData.map(d => d.diastolic).filter(v => v != null && !isNaN(v)); + const heartRateArr = hourlyData.map(d => d.heartRate).filter(v => v != null && !isNaN(v)); + const bpValues = [...systolicArr, ...diastolicArr]; + const getYAxisMax = (arr, step = 20) => { + if (!arr || arr.length === 0) return step === 20 ? 200 : 120; + const validValues = arr.filter(v => v != null && !isNaN(v)); + if (validValues.length === 0) return step === 20 ? 200 : 120; + const max = Math.max(...validValues); + return Math.ceil(max / step) * step; + }; + const bpMaxValue = getYAxisMax(bpValues, 20); + let bpInterval = 10; + if (bpMaxValue > 300) { + bpInterval = 40; + } else if (bpMaxValue > 200) { + bpInterval = 30; + } else if (bpMaxValue > 100) { + bpInterval = 20; + } else { + bpInterval = 10; + } + const hrMaxValue = getYAxisMax(heartRateArr, 10); + const hrMinValue = (() => { + if (!heartRateArr.length) return 50; + const minHR = Math.min(...heartRateArr); + return Math.max(0, Math.floor(minHR / 10) * 10 - 10); + })(); + // X轴只显示偶数小时 + const xAxisLabels = Array.from({ length: 24 }, (_, i) => (i % 2 === 0 ? String(i).padStart(2, '0') : '')); const option = { - tooltip: { trigger: 'axis' }, - legend: { data: ['收缩压', '舒张压', '心率'], bottom: 0 }, + grid: { left: 50, right: 50, bottom: 60, top: 50, containLabel: true }, + tooltip: { + trigger: 'axis', + axisPointer: { type: 'cross' }, + backgroundColor: 'rgba(50,50,50,0.7)', + borderWidth: 0, + textStyle: { color: '#fff' }, + formatter: function(params) { + if (params && params.length > 0) { + let html = ''; + params.forEach((param) => { + html += `${param.seriesName}: ${param.data || '--'}
`; + if (param.seriesIndex === 0) { + html += `小时: ${param.axisValue}
`; + } + }); + return html; + } + return ''; + } + }, + legend: { data: ['收缩压', '舒张压', '心率'], top: 10, left: 'center' }, xAxis: { type: 'category', - data: Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')) + data: Array.from({ length: 24 }, (_, i) => String(i).padStart(2, '0')), + axisLabel: { + formatter: (value) => { + const hour = parseInt(value, 10); + return hour % 2 === 0 ? value : ''; + }, + color: '#666' + }, + axisLine: { lineStyle: { color: '#e0e0e0' } }, + axisTick: { show: false }, + splitLine: { show: false } }, yAxis: [ - { type: 'value', name: 'mmHg', position: 'left', min: 0, max: 200 }, - { type: 'value', name: '次/分', position: 'right', min: 50, max: 120 } + { + type: 'value', + name: 'mmHg', + nameTextStyle: { color: '#000' }, + position: 'left', + min: 0, + max: bpMaxValue, + interval: bpInterval, + axisLabel: { color: '#666' }, + axisLine: { lineStyle: { color: '#e0e0e0' } }, + splitLine: { show: true, lineStyle: { color: '#f0f0f0', type: 'dashed' } } + }, + { + type: 'value', + name: '次/分', + nameTextStyle: { color: '#000' }, + position: 'right', + min: hrMinValue, + max: hrMaxValue, + interval: Math.max(5, Math.round(hrMaxValue / 10)), + axisLabel: { color: '#666' }, + axisLine: { lineStyle: { color: '#e0e0e0' } }, + splitLine: { show: false } + } ], series: [ { name: '收缩压', type: 'line', - smooth: true, symbol: 'circle', - symbolSize: 6, // 增加点的大小 + symbolSize: 6, + lineStyle: { width: 2 }, + connectNulls: false, + smooth: false, data: hourlyData.map(d => d.systolic), - itemStyle: { - color: '#409eff', - borderWidth: 1, - borderColor: '#fff' // 添加白色边框使点更明显 - } + itemStyle: { color: '#409eff', borderWidth: 1, borderColor: '#fff' } }, { name: '舒张压', type: 'line', - smooth: true, symbol: 'circle', - symbolSize: 6, // 增加点的大小 + symbolSize: 6, + lineStyle: { width: 2 }, + connectNulls: false, + smooth: false, data: hourlyData.map(d => d.diastolic), - itemStyle: { - color: '#67c23a', - borderWidth: 1, - borderColor: '#fff' // 添加白色边框使点更明显 - } + itemStyle: { color: '#67c23a', borderWidth: 1, borderColor: '#fff' } }, { name: '心率', type: 'line', - smooth: true, symbol: 'circle', - symbolSize: 6, // 增加点的大小 + symbolSize: 6, + lineStyle: { width: 2 }, + connectNulls: false, + smooth: false, yAxisIndex: 1, data: hourlyData.map(d => d.heartRate), - itemStyle: { - color: '#e6a23c', - borderWidth: 1, - borderColor: '#fff' // 添加白色边框使点更明显 - } + itemStyle: { color: '#e6a23c', borderWidth: 1, borderColor: '#fff' } } ] }; - chart.setOption(option); }