如何在不相互重叠的情况下传播div

时间:2017-01-11 15:18:25

标签: javascript html css

我正在构建一个事件调度程序,我已经陷入了一个问题,我无法找到一种方法来传播事件而不会相互重叠。 (它可能同时具有事件且没有限制。只要可能,使用100%的可用width

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

一些注意事项:

  • 事件包含在position: relative的div中,所有事件都有position:absolute

  • 使用javascript,我必须弄清楚每个“div事件”的top left widthheight的价值是什么飞。

  • 事件是一组对象,如下面的代码:

    {     startAt:“12:00:30”,     endsAt:“13:00:00”,     描述:“evt1”,     id:'00001' }

  • 我正在使用Vue.js来开发这个项目。但如果你不了解Vue,这不是问题。我使用jsbin构建了一个小项目,所以你可以使用javascript函数。

直播代码:https://jsbin.com/bipesoy/

我遇到问题的地方?

我找不到基于事件数组动态计算top left widthheight的算法。

关于jsbin代码的一些注意事项:

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

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

2 个答案:

答案 0 :(得分:3)

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

Snippet在这里(如果您愿意,可以使用JSBin:http://jsbin.com/humiyi/99/edit?html,js,console,output)。

绿色事件被检测为"可扩展"。灰色不能扩展。为了澄清扩展逻辑的问题,一些例子:

  • evt 1和evt3?它可以像这样或evt3正确,它们都扩展
  • evt7和evt12?有很多方法可以扩展这个......如何定义规则?
  • 想象evt7和evt11合并成一个大事件。如何扩展evt10,evt7 / 11和evt 12? ...现在尝试编写规则以持续回答上面的3(以及更多可能的情况,不在本例中)

我的结论是,编写规则并开发它并不值得付出努力。 UI可用性不会太多。在某些情况下,它们甚至会松动,即某些事件在视觉上更大,只是因为它们有空间而不是因为它们更重要。

我建议布局与示例中的布局相似或完全相同。活动不会扩大。我不知道你期望的每日活动有多少,现实生活中的情景如何,但只有我可能做的升级就是在分开的区域内垂直分割日历 - 与时间不重叠的时间点任何事件,例如evt7和evt11之间的界限。然后单独为每个区域运行相同的脚本。这将重新计算每个区域的垂直槽,因此具有evt10 i 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)
&#13;
<!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>
&#13;
&#13;
&#13;

答案 1 :(得分:0)

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