如何优雅展示日历中的重叠日程?三步搞定复杂布局

前言

在日历类应用中,如何高效展示重叠事件是一个常见需求。 本文将分三步解决这一问题:事件排序 → 重叠分组 → 组内布局。

一、事件排序

目标:确保时间顺序。使用第三方库对事件按照开始时间排序,若开始时间一致,以结束时间进行排序。以 dayjs为例:

ts 复制代码
import dayjs from 'dayjs'

interface Event {
  start: string
  end: string
  name: string
}

export function sort(events: Event[]): Evnet[] {
  return events.sort((a, b) => {
    if (a.start === b.start)
      return dayjs(a.end).isBefore(dayjs(b.end)) ? -1 : 1
    return dayjs(a.start).isBefore(dayjs(b.start)) ? -1 : 1
  })
}

二、事件分组

目标:识别重叠事件放置同一组内。 先两个时间重叠的事件放在一个组内,这个组可以看成是一个新的事件,继续和下一个事件进行处理。

ts 复制代码
import dayjs from 'dayjs'
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
import { sort } from './sort'

dayjs.extend(isSameOrAfter)

interface Group {
  start: string
  end: string
  events: Event[]
}

export function group(events: Event[]): Group[] {
  // 事件排序
  const sortEvents = sort(events)

  // 组的集合
  const result: Group[] = []
  // 当前组
  let currentGroup: Group | null = null

  sortEvents.forEach((event) => {
    // 组为空或事件和当前组未重叠时,新建组
    if (!currentGroup || dayjs(event.start).isSameOrAfter(currentGroup.end)) {
      currentGroup = {
        start: event.start,
        end: event.end,
        events: [event],
      }
      result.push(currentGroup)
    }
    else if (dayjs(event.start).isBefore(currentGroup.end)) {
    // 事件的结束时间晚于当前组的结束时间,更新当前组的结束时间
      if (dayjs(event.end).isAfter(currentGroup.end))
        currentGroup.end = event.end
      currentGroup.events.push(event)
    }
  })

  return result
}

三、组内布局

目标:同一个组在界面在同一行,分为多个列进行展示,所以需要确定组内事件需要几列和每个事件列的位置。同一个组在界面在同一行,分为多个列进行展示,如下图。

以上面五个事件为例:

  1. Meeting (8:30-10:30),计算在第一个位置,记录起始下标为 0,当前组有 1 列;
  2. Holiday (9:00-10:00),计算在第二个位置,记录下标为 1,当前组有 2 列;
  3. Travel (9:30-10:30),计算在第三个位置,记录下标为 2,当前组有 3 列;
  4. Party (10:20-11:20),计算在第二个位置,放置在会议 2 的下面,下标同为 1,当前组有 3 列;
  5. Birthday (10:40-11:40),计算在第一个位置,放置在会议 1 的下面,下标同为 0,当前组有 3 列。
ts 复制代码
import dayjs from 'dayjs'
import isSameOrAfter from 'dayjs/plugin/isSameOrAfter'
import { group } from './group'

dayjs.extend(isSameOrAfter)

interface Event {
  start: string
  end: string
  name: string
  left?: number // 列标
  isPlacement?: boolean // 是否布局
  isStack?: boolean // 是否堆叠
}

interface Group {
  start: string
  end: string
  events: Event[]
  columnCount?: number // 列的数量
}

export function layout(events: Event[]): Map<string, number[]> {
  const result = new Map<string, number[]>()
  const groups = group(events)

  for (const group of groups) {
    const { events } = group
    let columnCount = 0
    let left = 0
    events.forEach((event) => {
	  // 已布局事件 按照结束时间排序
      const placementEvents = events.filter(e => e.isPlacement && !e.isStack).sort((a, b) => dayjs(a.end).isBefore(dayjs(b.end)) ? -1 : 1)
      // 查找是否存在事件堆叠
      const stackEvent = placementEvents.find(e => dayjs(event.start).isSameOrAfter(dayjs(e.end)))
      if (stackEvent) {
	    // 存在堆叠事件
	    // 列标同堆叠事件
        event.left = stackEvent.left
        event.isPlacement = true
        stackEvent.isStack = true
      }
      else {
	    // 无堆叠事件
        event.left = left
        event.isPlacement = true
        // 列标加一
        left++
        // 列加一
        columnCount++
      }
    })
    group.columnCount = columnCount
  }

  for (const group of groups) {
    const { events } = group
    events.forEach((event) => {
      result.set(event.name, [event.left ?? -1, group.columnCount || 0])
    })
  }

  return result
}

注意点:

  1. 布局事件时判断是否能放置在已布局事件下面。如果有,标记已布局事件为堆叠事件,两个事件堆叠看作为一个事件;
  2. 为了使事件排列更紧凑,需要将已布局事件以结束时间排序来寻找结束时间早于事件开始时间的位置。
相关推荐
阿猿收手吧!5 分钟前
【C++】C++模板特化:精准定制泛型逻辑
开发语言·c++·算法
智驱力人工智能19 分钟前
货车走快车道检测 高速公路安全治理的工程实践与价值闭环 高速公路货车占用小客车道抓拍系统 城市快速路货车违规占道AI识别
人工智能·opencv·算法·安全·yolo·目标检测·边缘计算
喵手29 分钟前
Python爬虫实战:电商实体消歧完整实战 - 从混乱店铺名到标准化知识库的工程化实现,一文带你搞定!
爬虫·python·算法·爬虫实战·零基础python爬虫教学·同名实体消除·从混乱店铺名到标准化知识库
weixin_4521595532 分钟前
C++与Java性能对比
开发语言·c++·算法
80530单词突击赢33 分钟前
C++哈希表实现:开散列与闭散列详解
算法·哈希算法·散列表
Timmylyx051836 分钟前
类欧几里得学习笔记
笔记·学习·算法
wangluoqi38 分钟前
26.2.2练习总结
算法
2301_7657031440 分钟前
C++中的工厂模式实战
开发语言·c++·算法
晚霞的不甘40 分钟前
Flutter for OpenHarmony构建全功能视差侧滑菜单系统:从动效设计到多页面导航的完整实践
前端·学习·flutter·microsoft·前端框架·交互
黎子越41 分钟前
python相关练习
java·前端·python