基于D3.js甘特图的告警数组扁平化与重构方案问询
D3.js甘特图告警数据最优重构方案
需求说明
开发D3.js甘特图时,需对告警数据集做以下处理:
- 按
starting_time/ending_time组成的时间区间扁平化数组,同一区间的条目合并为一条数据 - 按
type字段分组,重构text字段,同时生成符合要求的tooltip文本(鼠标悬停时展示(Device: pH, Humidity)(Report: Swelling)(Clinician Assessment: Pain Level)格式的分组信息)
原始数据
let array = [ {text: "pH", starting_time: 1693347129000, ending_time: 1693350729000, type: "device"}, {text: "humidity", starting_time: 1693347129000, ending_time: 1693350729000, type: "device"}, {text: "pH", starting_time: 1693339929000, ending_time: 1693343529000, type: "device"}, {text: "pH", starting_time: 1692958329000, ending_time: 1692961929000, type: "device"}, {text: "humidity", starting_time: 1692958329000, ending_time: 1692961929000, type: "device"}, {text: "pH", starting_time: 1692738729000, ending_time: 1692742329000, type: "device"}, {text: "humidity", starting_time: 1692738729000, ending_time: 1692742329000, type: "device"}, {text: "temperature", starting_time: 1692735129000, ending_time: 1692738729000, type: "device"}, {text: "pH", starting_time: 1692735129000, ending_time: 1692738729000, type: "device"}, {text: "humidity", starting_time: 1692213729000, ending_time: 1692217329000, type: "device"}, {text: "humidity", starting_time: 1692130929000, ending_time: 1692134529000, 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"} ];
初步尝试代码
function alertBreakdown(result){ let schedule = [...result]; //flat all the dates to one array const timestamps = schedule.flatMap(_ => _.starting_time); //flatten also for ending_time? so if same starting/ending time? //console.log("timestamps", timestamps); //count the occurrences of each date in a dictionary structure const day2count = {}; timestamps.forEach(timestamp => { //console.log("--<timestamp", timestamp); const day = moment(timestamp).format("YYYY-MM-DD");//.toString().split('T')[0]; //const day = timestamp.toString().split('T')[0]; day2count[day] = (day2count[day] || 0) + 1; }); //console.log("day2count", day2count); // convert the map to an array of entries const scheduleResult = Object.entries(day2count).map(([date, value]) => ({date: moment(date), value: value, toolTipText: "test" })) .sort((a,b) => moment(a.date) - moment(b.date)) return scheduleResult; }
期望输出格式
let array = [ {text: "Device: pH, humidity", starting_time: 1693347129000, ending_time: 1693350729000, type: "device"}, {text: "Device: pH", starting_time: 1693339929000, ending_time: 1693343529000, type: "device"}, {text: "Device: pH, humidity", starting_time: 1692958329000, ending_time: 1692961929000, type: "device"}, {text: "Device: pH, humidity", starting_time: 1692738729000, ending_time: 1692742329000, type: "device"}, {text: "Device: temperature, pH", starting_time: 1692735129000, ending_time: 1692738729000, type: "device"}, {text: "Device: humidity", starting_time: 1692213729000, ending_time: 1692217329000, type: "device"}, {text: "Device: humidity", starting_time: 1692130929000, ending_time: 1692134529000, type: "device"}, {text: "Report: pain level", starting_time: 1695972618000, ending_time: 1695976218000, type: "report"}, {text: "Report: swelling, Clinician Assessment: level of pain", starting_time: 1695597028000, ending_time: 1695600628000, type: "report, clinicianAssement"}, ];
最优数据重构方案
核心思路
- 按时间区间分组:用
starting_time和ending_time的组合作为唯一键,将同一时间区间的所有条目聚合在一起 - 按type二次分组:在每个时间区间内,按
type收集对应的text内容,同时处理type的友好显示名称(如clinicianAssessment转为Clinician Assessment) - 生成格式化文本:根据分组结果,构建符合要求的
text字段和tooltip文本 - 整理输出结构:将分组后的数据转换为期望的数组格式,同时保留时间区间信息
代码实现
// 定义type到友好名称的映射 const typeDisplayNameMap = { device: "Device", report: "Report", clinicianAssessment: "Clinician Assessment" }; function refactorAlertData(originalData) { // 用时间区间作为键,聚合数据 const intervalMap = {}; originalData.forEach(item => { // 生成时间区间的唯一键:starting_time + "-" + ending_time const intervalKey = `${item.starting_time}-${item.ending_time}`; if (!intervalMap[intervalKey]) { // 初始化区间数据 intervalMap[intervalKey] = { starting_time: item.starting_time, ending_time: item.ending_time, typeGroups: {} // 存储该区间下的type->texts映射 }; } const intervalData = intervalMap[intervalKey]; // 按type收集text if (!intervalData.typeGroups[item.type]) { intervalData.typeGroups[item.type] = []; } intervalData.typeGroups[item.type].push(item.text); }); // 将intervalMap转换为期望的数组格式 return Object.values(intervalMap).map(intervalData => { // 生成text字段和tooltip文本 const textSegments = []; const tooltipSegments = []; const types = []; // 遍历每个type分组 Object.entries(intervalData.typeGroups).forEach(([type, texts]) => { const displayName = typeDisplayNameMap[type] || type; const joinedTexts = texts.join(", "); textSegments.push(`${displayName}: ${joinedTexts}`); tooltipSegments.push(`(${displayName}: ${joinedTexts})`); types.push(type); }); return { text: textSegments.join(", "), starting_time: intervalData.starting_time, ending_time: intervalData.ending_time, type: types.join(", "), tooltipText: tooltipSegments.join("") // 额外生成tooltip专用文本,方便D3调用 }; }).sort((a, b) => a.starting_time - b.starting_time); // 按开始时间排序 } // 使用示例 const refactoredArray = refactorAlertData(array); console.log(refactoredArray);
方案优势
- 时间复杂度为O(n),仅需遍历原始数据一次,再遍历分组后的结果一次,性能高效
- 逻辑清晰,通过双层分组(时间区间→type)精准聚合数据
- 扩展性强,新增type时只需在
typeDisplayNameMap中添加映射即可 - 额外生成
tooltipText字段,直接满足D3.js悬停展示需求,无需后续二次处理
内容的提问来源于stack exchange,提问作者The Old County
相关产品推荐
相关产品推荐

