2017-01-11 40 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