2017-01-11 37 views
5

我正在构建一个事件调度程序,并且我已经进入了一个观点,即我无法找出一种方式来传播事件而不会彼此重叠。 (它可能同时发生但没有限制,只要有可能,请使用可用的100%width如何在彼此不重叠的情况下传播divs

这里是这种场景的图片。 enter image description here

一些注意事项:

  • 事件被包裹一个div里面有position: relative和所有的事件都有position:absolute

  • 使用JavaScript,我要弄清楚什么需要是topleftwidth和动态每个“格事件” height值。

  • 事件是像下面的代码对象的数组:

    { startAt: “12时零零分30秒”, endsAt: “13:00:00”, 描述: “EVT1”, id:'00001' }

  • 我正在使用Vue.js开发此项目。但是,如果你不知道Vue,这不是问题。我已经使用jsbin构建了一个小型项目,因此您可以使用javascript函数进行游戏。

实时代码:https://jsbin.com/bipesoy/

当我有问题吗?

我无法找到一个算法来计算基于事件的阵列上飞topleftwidthheight

有关jsbin代码一些注意事项:

  • 所有的代码找到上面的4个属性是功能parsedEvents
  • 里面parsedEvents您可以使用访问事件的数组内:this.events
  • parsedEvents的工作循环遍历事件数组,并向每个事件添加样式属性,然后用样式对象返回一个新的事件数组。
  • 每30分钟的高度为40px;

任何想法如何实现它或更好的解决方案?

+0

从我的头顶,我倒是不通过事件,而是经过30分钟的增量循环,计数落在每30分钟跨度事件的数量。然后循环thruogh项目和1)找出哪个30分钟增量与这个项目重叠,2)找到最大计数从所有这些重叠的30分钟增量,这是你的宽度(计数3是33%,2为50%等等。)。我猜这不是一个问题,因为它只是项目持续时间的倍数/ 0.5h x 40 px –

+0

听起来很有前途@MirkoVukušić。我会试试看,并让你知道后来:) –

+0

@MirkoVukušić其实我认为宽度有问题。看图中的evt2和evt7。他们的宽度是多少? –

回答

3

经过一段时间玩这个挑战,我认为是时候放弃这个想法。可以编排许多可能的事件安排场景,但是当你深入挖掘时,你会意识到甚至很难写出你想要完成的事情,即使你进行了管理,你的一些决策在外观上看起来也不太好屏幕。这仅适用于扩展事件的宽度。定位甚至重新定位它们以填补空白是可以解决的,并不是太困难。

片段是在这里(或JSBin如果你喜欢:http://jsbin.com/humiyi/99/edit?html,js,console,output)。

绿色事件被检测为“可扩展”。灰色的不能扩展。为了阐明扩展的逻辑问题,一些例子:

  • evt 1和evt3?它可以像这样或evt3进行正确,他们都扩大
  • evt7和evt12?许多方式来扩大这...如何定义规则?
  • 想象一下evt7和evt11 ar合并成一个大事件。如何扩大evt10,evt7/11和evt 12? ......现在尝试编写规则一致回答上述3(和许多许多可能的情况不在此例)

我的结论是,编写规则和发展,这是不值得的。 UI可用性不会太大。在某些情况下,它们甚至会松动,也就是说,有些事件在视觉上会更大,只是因为它们有空间而不是因为它们更重要。

我会建议布局类似或完全相同的例子。事件不会扩大。我不知道你期望的日常活动有多少,什么是现实生活场景,但只有升级我可能做的是在分开的区域垂直分割日历 - 与任何事件不重叠的时间点,例如evt7和例如evt11。然后独立运行每个区域的相同脚本。这将重新计算每个区域的垂直槽,因此使用evt10的区域evt11将只有2个垂直槽,每个区域填充50%的空间。这可能是值得的,如果你的日历几乎没有拥挤的小时,只有几个事件后/之前。这可以解决一天晚些时候事件过于狭窄的问题,而不会花费太多时间。但是,如果事件全天都在发生,并且重叠很多,我认为这不值得。

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>

+0

Uau @MirkoVukušić你是对的。通常,它不会有太多重叠事件。我会按照你的建议完成我的项目。非常感谢你的伟大答案! :) –

0

正在考虑更多关于此。我覆盖了宽度,高度和顶部。但是,您可能在计算left时遇到问题。因此,为每个30分钟增量创建计数器的目标仍然存在一点变化,但是不是循环增量和过滤项目,反之亦然。循环播放项目并从那里填充该计数器对象。它也会更快。在该循环中,不要只将计数器存储到计数器对象,还要将itemID推送到同一对象的另一个属性(以及相同的增量)。因此,每个增量可以表示为{span:“17:00”,counter:2,items:[135,148]}。此items阵列稍后将帮助您确定每个项目的left位置并避免水平重叠。因为最大值items数组中项目的所有增量项的位置=从左边的日历上的项目位置。将其乘以宽度(-1)并且您有left