You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 15:51:26