Recharts实现各次服药浓度独立抛物线区域图的方法
Recharts 实现多服药时间点独立抛物线浓度图方案
核心数据结构设计
原有实现出现浓度合并展示的核心原因,是把所有服药计划的浓度值累加后绑定到了同一个dataKey字段上。要实现每个计划剂量独立、互不干扰的抛物线区域展示,需要给每一个服药计划单独分配一个独立的dataKey字段,所有x轴时间刻度共享统一的时间维度,每个时间点下不同字段只存储对应服药计划在该时刻的浓度值,不做任何累加。
单条x轴刻度对应的数据结构示例:
{ time: 1717207200000, // 统一x轴时间戳,示例为10:00 dose_1717200000000: 1.2, // 8:00服药在10:00的浓度值,非生效时段为0 dose_1717214400000: 0, // 12:00服药在10:00尚未生效,浓度为0 dose_1717236000000: 0 // 18:00服药同理 }
具体实现步骤
- 预处理服药计划列表:给每个计划生成唯一字段标识,直接用计划服药时间戳作为后缀即可,避免同剂量同时间的字段重名
- 生成全量x轴时间序列:按你需要的展示精度(30分钟/1小时均可),覆盖要展示的完整时间区间,作为数据的基础骨架
- 单计划浓度计算:遍历每个服药计划,单独调用已有的
generateSubstanceData、calculateParabolaPoint方法,计算该单剂量在所有x轴时间点的浓度值,超出代谢有效时长的位置直接填0 - 数据对齐合并:将所有计划的浓度值按x轴时间点对齐,填充到对应独立字段中,生成最终传入Recharts的数组
可直接复用的代码实现
import { AreaChart, Area, XAxis, YAxis, CartesianGrid, Tooltip } from 'recharts'; /** * 生成独立剂量的图表数据 * @param {Array} dosePlans 服药计划列表,每项含time(时间戳)、dosage(剂量) * @param {number} rangeStart 展示区间开始时间戳 * @param {number} rangeEnd 展示区间结束时间戳 * @param {number} stepMinute x轴刻度步长,单位分钟 */ function generateIndependentDoseData(dosePlans, rangeStart, rangeEnd, stepMinute = 30) { // 初始化x轴时间骨架 const chartData = []; const stepMs = stepMinute * 60 * 1000; for (let t = rangeStart; t <= rangeEnd; t += stepMs) { chartData.push({ time: t }); } // 逐个填充每个服药计划的独立浓度字段 dosePlans.forEach(plan => { const dataKey = `dose_${plan.time}`; // 复用已有逻辑生成单剂量的浓度数据 const singleDoseConcentration = generateSubstanceData(plan.time, plan.dosage, calculateParabolaPoint); const concentrationMap = new Map(singleDoseConcentration.map(item => [item.time, item.concentration])); chartData.forEach(point => { point[dataKey] = concentrationMap.get(point.time) ?? 0; }); }); return chartData; } // 图表组件 function SubstanceConcentrationGraph({ dosePlans }) { // 默认展示当日0点到次日0点的24小时区间 const dayStart = new Date().setHours(0, 0, 0, 0); const dayEnd = dayStart + 24 * 60 * 60 * 1000; const chartData = generateIndependentDoseData(dosePlans, dayStart, dayEnd); // 动态生成每个独立剂量对应的区域配置 const doseAreaList = dosePlans.map(plan => { const dataKey = `dose_${plan.time}`; const takeTime = new Date(plan.time); return ( <Area key={dataKey} type="monotone" dataKey={dataKey} stroke="#165DFF" fill="#165DFF" fillOpacity={0.25} strokeWidth={1.5} name={`${takeTime.getHours()}:${String(takeTime.getMinutes()).padStart(2, '0')} 服用 ${plan.dosage}单位`} /> ); }); return ( <AreaChart width={900} height={450} data={chartData} margin={{ top: 24, right: 24, left: 12, bottom: 24 }}> <CartesianGrid strokeDasharray="3 3" stroke="#eee" /> <XAxis dataKey="time" tickFormatter={ts => { const d = new Date(ts); return `${d.getHours()}:${String(d.getMinutes()).padStart(2, '0')}`; }} /> <YAxis label={{ value: '血液浓度', angle: -90, position: 'insideLeft' }} /> <Tooltip labelFormatter={ts => { const d = new Date(ts); return `时间:${d.getHours()}:${String(d.getMinutes()).padStart(2, '0')}`; }} itemSorter={item => item.value === 0 ? 1 : -1} /> {doseAreaList} </AreaChart> ); }
适配注意事项
- 非生效时段的浓度值必须填
0,不要传null,否则Recharts会出现线条断裂、区域填充异常的问题 - 如果需要实现单剂量高亮、单独显示/隐藏某条抛物线的交互,直接操作对应
dataKey的Area组件属性即可,因为字段完全独立,不需要额外做数据拆分 - 如果需要同时展示总浓度曲线,额外加一个Line组件,在数据生成阶段把所有剂量字段的浓度加总存到
totalConcentration字段绑定即可,和独立区域展示不冲突
内容的提问来源于stack exchange,提问作者LeCoda
相关产品推荐
相关产品推荐

