
本文介绍一种基于 Map 的高效数据重构方法,用于将原始 D3.js 甘特图数据按 starting_time 和 ending_time 合并同时间段内不同类型的观测项,并自动生成符合语义的 tooltip 文本(如 “Device: pH, humidity”),同时保留原始时序顺序。
本文介绍一种基于 map 的高效数据重构方法,用于将原始 d3.js 甘特图数据按 `starting_time` 和 `ending_time` 合并同时间段内不同类型的观测项,并自动生成符合语义的 tooltip 文本(如 “device: ph, humidity”),同时保留原始时序顺序。
在构建交互式甘特图(如临床监测或设备告警可视化)时,原始数据常以细粒度条目形式存在——同一时间段内可能包含多个设备读数、报告项或临床评估。若直接渲染,会导致重复时间条、tooltip 冗余或视觉干扰。因此,需对数据进行按时间窗口聚合 + 按类型分组 + 语义化文本生成三步重构。
以下是一个健壮、可扩展且保持插入顺序的实现方案:
✅ 核心思路:双层 Map 聚合
- 外层 Map:以 starting_time$ending_time 为唯一键,确保相同时间区间的数据被归入同一组;
- 内层 Map(typeTextMap):以 type 为键,存储该类型下所有 text 值的数组,天然保留首次出现顺序;
- 最终遍历 Map 值,动态拼接 text 字段(如 "Device: pH, humidity")与 type 字段(如 "device" 或 "report, clinicianAssessment")。
? 完整实现代码
function alertBreakdown(arr) {
const groupedData = new Map();
for (const obj of arr) {
const key = `${obj.starting_time}$${obj.ending_time}`;
let grouped = groupedData.get(key);
if (!grouped) {
grouped = { obj }; // 保留首个对象作为模板(含 timestamp、type 等基础字段)
}
const typeTextMap = grouped.typeTextMap || new Map();
const existingTexts = typeTextMap.get(obj.type) || [];
typeTextMap.set(obj.type, [...existingTexts, obj.text]);
grouped.typeTextMap = typeTextMap;
groupedData.set(key, grouped);
}
// 辅助函数:将 camelCase / kebab-case 类型名转为标题格式(如 "clinicianAssessment" → "Clinician Assessment")
const transformTypeName = type =>
type
.replace(/([a-z])([A-Z])/g, '$1 $2') // camelCase 分割
.replace(/-/g, ' ') // kebab-case 替换
.replace(/\b\w/g, c => c.toUpperCase()); // 首字母大写
return Array.from(groupedData.values(), ({ obj, typeTextMap }) => ({
...obj,
text: Array.from(typeTextMap, ([type, texts]) =>
`${transformTypeName(type)}: ${texts.join(', ')}`).join(', '),
type: Array.from(typeTextMap.keys()).join(', ')
}));
}
// 示例调用
const rawData = [
{text: "pH", starting_time: 1693347129000, ending_time: 1693350729000, type: "device"},
{text: "humidity", starting_time: 1693347129000, ending_time: 1693350729000, type: "device"},
{text: "pain level", starting_time: 1695972618000, ending_time: 1695976218000, type: "report"},
{text: "swelling", starting_time: 1695597028000, ending_time: 1695600628000, type: "report"},
{text: "level of pain", starting_time: 1695597028000, ending_time: 1695600628000, type: "clinicianAssessment"}
];
console.log(alertBreakdown(rawData));
// 输出示例:
// [
// {text: "Device: pH, humidity", starting_time: ..., ending_time: ..., type: "device"},
// {text: "Report: pain level", starting_time: ..., ending_time: ..., type: "report"},
// {text: "Report: swelling, Clinician Assessment: level of pain", ..., type: "report, clinicianAssessment"}
// ]⚠️ 关键注意事项
- 时间精度敏感:确保 starting_time 与 ending_time 为精确毫秒级时间戳(而非字符串或 Date 对象),否则键生成会失败;
-
类型命名规范:transformTypeName() 仅处理常见驼峰/短横线命名;若业务中存在固定映射(如 "clinicianAssessment" → "Clinician Assessment"),建议改用预定义字典提升可维护性:
const TYPE_LABELS = { device: 'Device', report: 'Report', clinicianAssessment: 'Clinician Assessment' }; - Tooltip 可访问性:生成的 text 字段已适配屏幕阅读器友好格式(逗号分隔、冒号明确归属),无需额外 ARIA 处理;
- 性能考量:Map 查找与插入均为 O(1),整体复杂度为 O(n),适用于数千条以内数据;超大规模数据可考虑先按时间排序再滑动窗口合并。
✅ 总结
该方案不依赖外部库(如 Lodash 或 Moment.js),纯原生 JavaScript 实现,兼顾语义清晰性、执行效率与可读性。重构后的数据可直接传入 D3.js .data() 绑定,配合 .attr('title', d => d.text) 即可实现所见即所得的聚合 tooltip 效果,显著提升甘特图的信息密度与用户体验。

















