let events = [
{ startAt: "00:00", endsAt: "01:00", description: "evt1", id: '00001' },
{ startAt: "01:30", endsAt: "08:00", description: "evt2", id: '00002' },
{ startAt: "01:30", endsAt: "04:00", description: "evt3", id: '00003' },
{ startAt: "00:30", endsAt: "02:30", description: "evt3", id: '00013' },
{ startAt: "00:00", endsAt: "01:00", description: "evt3", id: '00014' },
{ startAt: "03:00", endsAt: "06:00", description: "evt4", id: '00004' },
{ startAt: "01:30", endsAt: "04:30", description: "evt5", id: '00005' },
{ startAt: "01:30", endsAt: "07:00", description: "evt6", id: '00006' },
{ startAt: "06:30", endsAt: "09:00", description: "evt7", id: '00007' },
{ startAt: "04:30", endsAt: "06:00", description: "evt8", id: '00008' },
{ startAt: "05:00", endsAt: "06:00", description: "evt9", id: '00009' },
{ startAt: "09:00", endsAt: "10:00", description: "evt10", id: '00010' },
{ startAt: "09:00", endsAt: "10:30", description: "evt11", id: '00011' },
{ startAt: "07:00", endsAt: "08:00", description: "evt12", id: '00012' }
]
console.time()
// will store counts of events in each 30-min chunk
// each element represents 30 min chunk starting from midnight
// ... so indexOf * 30 minutes = start time
// it will also store references to events for each chunk
// each element format will be: { count: <int>, eventIds: <array_of_ids> }
let counter = []
// helper to convert time to counter index
time2index = (time) => {
let splitTime = time.split(":")
return parseInt(splitTime[0]) * 2 + parseInt(splitTime[1])/30
}
// loop through events and fill up counter with data
events.map(event => {
for (let i = time2index(event.startAt); i < time2index(event.endsAt); i++) {
if (counter[i] && counter[i].count) {
counter[i].count++
counter[i].eventIds.push(event.id)
} else {
counter[i] = { count: 1, eventIds: [event.id] }
}
}
})
//find chunk with most items. This will become number of slots (vertical spaces) for our calendar grid
let calSlots = Math.max(...counter.filter(c=>c).map(c=>c.count)) // filtering out undefined elements
console.log("number of calendar slots: " + calSlots)
// loop through events and add some more props to each:
// - overlaps: all overlapped events (by ref)
// - maxOverlapsInChunk: number of overlapped events in the most crowded chunk
// (1/this is maximum number of slots event can occupy)
// - pos: position of event from left (in which slot it starts)
// - expandable: if maxOverlapsInChunk = calSlot, this event is not expandable for sure
events.map(event => {
let overlappedEvents = events.filter(comp => {
return !(comp.endsAt <= event.startAt || comp.startAt >= event.endsAt || comp.id === event.id)
})
event.overlaps = overlappedEvents //stores overlapped events by reference!
event.maxOverlapsInChunk = Math.max(...counter.filter(c=>c).map(c=>c.eventIds.indexOf(event.id) > -1 ? c.count : 0))
event.expandable = event.maxOverlapsInChunk !== calSlots
event.pos = Math.max(...counter.filter(c=>c).map(c => {
let p = c.eventIds.indexOf(event.id)
return p > -1 ? p+1 : 1
}))
})
// loop to move events leftmost possible and fill gaps if any
// some expandable events will stop being expandable if they fit gap perfectly - we will recheck those later
events.map(event => {
if (event.pos > 1) {
//find positions of overlapped events on the left side
let vertSlotsTakenLeft = event.overlaps.reduce((result, cur) => {
if (result.indexOf(cur.pos) < 0 && cur.pos < event.pos) result.push(cur.pos)
return result
}, [])
// check if empty space on the left
for (i = 1; i < event.pos; i++) {
if (vertSlotsTakenLeft.indexOf(i) < 0) {
event.pos = i
console.log("moving " + event.description + " left to pos " + i)
break
}
}
}
})
// fix moved events if they became non-expandable because of moving
events.filter(event=>event.expandable).map(event => {
let leftFixed = event.overlaps.filter(comp => {
return event.pos - 1 === comp.pos && comp.maxOverlapsInChunk === calSlots
})
let rightFixed = event.overlaps.filter(comp => {
return event.pos + 1 === comp.pos && comp.maxOverlapsInChunk === calSlots
})
event.expandable = (!leftFixed.length || !rightFixed.length)
})
//settings for calendar (positioning events)
let calendar = {width: 300, chunkHeight: 30}
// one more loop through events to calculate top, left, width and height
events.map(event => {
event.top = time2index(event.startAt) * calendar.chunkHeight
event.height = time2index(event.endsAt) * calendar.chunkHeight - event.top
//event.width = 1/event.maxOverlapsInChunk * calendar.width
event.width = calendar.width/calSlots // TODO: temporary width is 1 slot
event.left = (event.pos - 1) * calendar.width/calSlots
})
console.timeEnd()
// TEST drawing divs
events.map(event => {
$("body").append(`<div style="position: absolute;
top: ${event.top}px;
left: ${event.left}px;
width: ${event.width}px;
height: ${event.height}px;
background-color: ${event.expandable ? "green" : "grey"};
border: solid black 1px;
">${event.description}</div>`)
})
//console.log(events)
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
从我的头顶,我倒是不通过事件,而是经过30分钟的增量循环,计数落在每30分钟跨度事件的数量。然后循环thruogh项目和1)找出哪个30分钟增量与这个项目重叠,2)找到最大计数从所有这些重叠的30分钟增量,这是你的宽度(计数3是33%,2为50%等等。)。我猜这不是一个问题,因为它只是项目持续时间的倍数/ 0.5h x 40 px –
听起来很有前途@MirkoVukušić。我会试试看,并让你知道后来:) –
@MirkoVukušić其实我认为宽度有问题。看图中的evt2和evt7。他们的宽度是多少? –