日历事件的可视化。 用最大宽度布局事件的algorithm

我需要你的一个algorithm的帮助(这将在客户端与JavaScript开发,但并不重要,我最感兴趣的algorithm本身)布置日历事件,以便每个事件框具有最大宽度。 请看下面的图片:

日历事件布局

Y轴是时间。 所以如果“testing事件”从中午开始(例如),没有更多的交叉,它占用了整个100%的宽度。 “每周回顾”与“摇摇欲坠基督教青年会”和“安娜/阿米莉亚”相交,但后两者不相交,所以他们都填满了50%。 Test3,Test4和Test5都是相交的,所以最大宽度为33.3%。 但Test3是66%,因为Test3是固定的33%(见上面),所以它占用了所有可用空间,这是66%。

我需要一个algorithm来阐明这一点。

提前致谢

  1. 想想只有左边缘的无限网格。
  2. 每个事件是一个单元格的宽度,高度和垂直位置是固定的基础上的开始和结束时间。
  3. 尝试将每个事件放置在尽可能靠左的列中,而不要与该列中的任何早期事件相交。
  4. 然后,当每个连接的事件组被放置时,它们的实际宽度将是该组使用的最大列数的1 / n。
  5. 您还可以扩展最左侧和最右侧的事件以使用剩余的空间。
/// Pick the left and right positions of each event, such that there are no overlap. /// Step 3 in the algorithm. void LayoutEvents(IEnumerable<Event> events) { var columns = new List<List<Event>>(); DateTime? lastEventEnding = null; foreach (var ev in events.OrderBy(ev => ev.Start).ThenBy(ev => ev.End)) { if (ev.Start >= lastEventEnding) { PackEvents(columns); columns.Clear(); lastEventEnding = null; } bool placed = false; foreach (var col in columns) { if (!col.Last().CollidesWith(ev)) { col.Add(ev); placed = true; break; } } if (!placed) { columns.Add(new List<Event> { ev }); } if (lastEventEnding == null || ev.End > lastEventEnding.Value) { lastEventEnding = ev.End; } } if (columns.Count > 0) { PackEvents(columns); } } /// Set the left and right positions for each event in the connected group. /// Step 4 in the algorithm. void PackEvents(List<List<Event>> columns) { float numColumns = columns.Count; int iColumn = 0; foreach (var col in columns) { foreach (var ev in col) { int colSpan = ExpandEvent(ev, iColumn, columns); ev.Left = iColumn / numColumns; ev.Right = (iColumn + colSpan) / numColumns; } iColumn++; } } /// Checks how many columns the event can expand into, without colliding with /// other events. /// Step 5 in the algorithm. int ExpandEvent(Event ev, int iColumn, List<List<Event>> columns) { int colSpan = 1; foreach (var col in columns.Skip(iColumn + 1)) { foreach (var ev1 in col) { if (ev1.CollidesWith(ev)) { return colSpan; } } colSpan++; } return colSpan; } 

编辑:现在sorting的事件,而不是假设他们sorting。

编辑2:如果有足够的空间,现在将事件展开到右侧。

接受的答案描述了一个有5个步骤的algorithm。 在接受的答案的评论中链接的示例实现只实现步骤1到4.步骤5是关于确保最右边的事件使用所有可用的空间。 请参阅OP提供的图像中的事件7。

我通过添加上述algorithm的第5步扩展了给定的实现:

 $( document ).ready( function( ) { var column_index = 0; $( '#timesheet-events .daysheet-container' ).each( function() { var block_width = $(this).width(); var columns = []; var lastEventEnding = null; // Create an array of all events var events = $('.bubble_selector', this).map(function(index, o) { o = $(o); var top = o.offset().top; return { 'obj': o, 'top': top, 'bottom': top + o.height() }; }).get(); // Sort it by starting time, and then by ending time. events = events.sort(function(e1,e2) { if (e1.top < e2.top) return -1; if (e1.top > e2.top) return 1; if (e1.bottom < e2.bottom) return -1; if (e1.bottom > e2.bottom) return 1; return 0; }); // Iterate over the sorted array $(events).each(function(index, e) { // Check if a new event group needs to be started if (lastEventEnding !== null && e.top >= lastEventEnding) { // The latest event is later than any of the event in the // current group. There is no overlap. Output the current // event group and start a new event group. PackEvents( columns, block_width ); columns = []; // This starts new event group. lastEventEnding = null; } // Try to place the event inside the existing columns var placed = false; for (var i = 0; i < columns.length; i++) { var col = columns[ i ]; if (!collidesWith( col[col.length-1], e ) ) { col.push(e); placed = true; break; } } // It was not possible to place the event. Add a new column // for the current event group. if (!placed) { columns.push([e]); } // Remember the latest event end time of the current group. // This is later used to determine if a new groups starts. if (lastEventEnding === null || e.bottom > lastEventEnding) { lastEventEnding = e.bottom; } }); if (columns.length > 0) { PackEvents( columns, block_width ); } }); }); // Function does the layout for a group of events. function PackEvents( columns, block_width ) { var n = columns.length; for (var i = 0; i < n; i++) { var col = columns[ i ]; for (var j = 0; j < col.length; j++) { var bubble = col[j]; var colSpan = ExpandEvent(bubble, i, columns); bubble.obj.css( 'left', (i / n)*100 + '%' ); bubble.obj.css( 'width', block_width * colSpan / n - 1 ); } } } // Check if two events collide. function collidesWith( a, b ) { return a.bottom > b.top && a.top < b.bottom; } // Expand events at the far right to use up any remaining space. // Checks how many columns the event can expand into, without // colliding with other events. Step 5 in the algorithm. function ExpandEvent(ev, iColumn, columns) { var colSpan = 1; // To see the output without event expansion, uncomment // the line below. Watch column 3 in the output. //return colSpan; for (var i = iColumn + 1; i < columns.length; i++) { var col = columns[i]; for (var j = 0; j < col.length; j++) { var ev1 = col[j]; if (collidesWith(ev, ev1)) { return colSpan; } } colSpan++; } return colSpan; } 

可以在http://jsbin.com/detefuveta/edit?html,js,outputfind一个工作演示请参阅输出结果的第3列,了解扩展最右边事件的示例。;

PS:这应该是对接受的答案的评论。 不幸的是我没有评论的特权。