如何重写levelEvents函数解决自定义时间间隔条形/丝带图重叠问题

问题核心错误
- 重叠判断逻辑完全反向:原有
doesOverlap函数的判断条件实际是校验两个区间不重叠,导致分层筛选逻辑从根上出错。 - 分层规则不符合要求:原有递归逻辑没有优先将开始时间更晚的事件分配到上层,也无法保证每层内部事件无重叠。
- 重叠计数逻辑存在索引错误:
getOverlaps函数中错误使用sortedEvents[i + j]取值,计算出的最大重叠数完全错误。
修正方案
1. 基础工具函数
// 正确的两个区间重叠判断 const isOverlap = (eventA, eventB) => { return eventA.dayStart < eventB.dayEnd && eventB.dayStart < eventA.dayEnd; };
2. 重写分层函数
不需要递归,单次遍历即可完成分层,同时满足三个要求:使用最少行数、上层放置开始时间更晚的事件、每层内部事件无重叠:
// 返回值为二维数组,索引越小代表层级越靠上,每个子数组为当前层要渲染的事件 const levelEvents = (events) => { // 先按开始时间降序排序,保证晚开始的事件优先分配上层 const sortedEvents = [...events].sort((a, b) => b.dayStart - a.dayStart); const levels = []; sortedEvents.forEach(currentEvent => { // 从最上层开始找可放入的层级 for (let i = 0; i < levels.length; i++) { const lastEvent = levels[i].at(-1); // 当前层最后一个事件的结束时间早等于当前事件的开始时间,无重叠可放入 if (lastEvent.dayEnd <= currentEvent.dayStart) { levels[i].push(currentEvent); return; } } // 现有层级都放不下,新增层级 levels.push([currentEvent]); }); // 每层内部按开始时间升序排序,方便从左到右渲染 return levels.map(level => level.sort((a, b) => a.dayStart - b.dayStart)); };
3. 可选:修正最大重叠数计算
如果不需要单独计算重叠数,可以直接取levelEvents(events).length作为需要渲染的总行数,比单独计算效率更高。如果需要单独的计数函数,使用扫描线算法实现:
const getMaxOverlap = (events) => { if (!events?.length) return 0; const timePoints = []; events.forEach(event => { timePoints.push({ time: event.dayStart, type: 1 }); timePoints.push({ time: event.dayEnd, type: -1 }); }); // 相同时间点先处理结束事件,避免相邻无重叠区间被误判为重叠 timePoints.sort((a, b) => a.time - b.time || a.type - b.type); let maxOverlap = 0; let currentOverlap = 0; timePoints.forEach(point => { currentOverlap += point.type; maxOverlap = Math.max(maxOverlap, currentOverlap); }); return maxOverlap; };
4. 渲染逻辑调整
直接用分层结果渲染,不需要额外生成空数组:
{data.map((ribbon, order) => { const events = ribbonEvents[ribbon.id]; const eventLevels = levelEvents(events); return eventLevels.map((levelEvents, level) => ( <RibbonChart key={`${ribbon.id}-level-${level}`} ribbon={ribbon} ribbonEvents={levelEvents} level={level} /> )); })}
内容的提问来源于stack exchange,提问作者Igor Shmukler
相关产品推荐
相关产品推荐

