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

Javascript 日历事件的可视化。用最大宽度布局事件的算法,javascript,algorithm,calendar,visualization,Javascript,Algorithm,Calendar,Visualization,我需要你的算法帮助(它将在客户端用javascript开发,但并不重要,我主要对算法本身感兴趣)安排日历事件,以便每个事件框具有最大宽度。请看下图: Y轴是时间。因此,如果“测试事件”在中午开始(例如),并且没有更多的东西与它相交,它将占用整个100%的宽度。“每周回顾”与“翻滚的基督教青年会”和“安娜/阿米莉亚”相交,但后两个没有相交,所以它们都占了50%。Test3、Test4和Test5都是相交的,因此每个的最大宽度为33.3%。但是Test7是66%,因为Test3是33%固定的(见上

我需要你的算法帮助(它将在客户端用javascript开发,但并不重要,我主要对算法本身感兴趣)安排日历事件,以便每个事件框具有最大宽度。请看下图:

Y轴是时间。因此,如果“测试事件”在中午开始(例如),并且没有更多的东西与它相交,它将占用整个100%的宽度。“每周回顾”与“翻滚的基督教青年会”和“安娜/阿米莉亚”相交,但后两个没有相交,所以它们都占了50%。Test3、Test4和Test5都是相交的,因此每个的最大宽度为33.3%。但是Test7是66%,因为Test3是33%固定的(见上文),所以它占用了所有可用空间,即66%

我需要一个算法来解决这个问题

提前谢谢

  • 想象一个只有左边缘的无限网格
  • 每个事件都有一个单元格宽,高度和垂直位置根据开始和结束时间固定
  • 尽量将每个事件放在最左边的列中,不要与该列中的任何早期事件相交
  • 然后,当放置每个连接的事件组时,它们的实际宽度将是该组使用的最大列数的1/n
  • 您还可以展开最左侧和最右侧的事件,以使用所有剩余空间
  • ///选择每个事件的左右位置,这样就不会有重叠。
    ///算法的第三步。
    无效布局事件(IEnumerable事件)
    {
    var columns=新列表();
    DateTime?lastEventEnding=null;
    foreach(events.OrderBy中的var ev(ev=>ev.Start)。然后by(ev=>ev.End))
    {
    如果(ev.Start>=lastEventEnding)
    {
    填料(柱);
    columns.Clear();
    lastEventEnding=null;
    }
    bool-placed=false;
    foreach(列中的变量列)
    {
    如果(!col.Last().collizeswith(ev))
    {
    列加(ev);
    放置=真;
    打破
    }
    }
    如果(!放置)
    {
    添加(新列表{ev});
    }
    if(lastEventEnding==null | | ev.End>lastEventEnding.Value)
    {
    lastEventEnding=ev.End;
    }
    }
    如果(columns.Count>0)
    {
    填料(柱);
    }
    }
    ///设置所连接组中每个事件的左右位置。
    ///算法的第4步。
    无效包装事件(列表列)
    {
    float numColumns=columns.Count;
    int-iColumn=0;
    foreach(列中的变量列)
    {
    foreach(列中的var ev)
    {
    int colSpan=ExpandEvent(ev、iColumn、columns);
    ev.Left=i列/num列;
    ev.Right=(iColumn+colSpan)/numColumn;
    }
    iColumn++;
    }
    }
    ///检查事件可以扩展到多少列而不与之冲突
    ///其他活动。
    ///算法的第5步。
    int ExpandEvent(事件ev、int iColumn、列表列)
    {
    int colSpan=1;
    foreach(列中的变量col.Skip(iColumn+1))
    {
    foreach(列中的var ev1)
    {
    如果(ev1.与(ev)碰撞)
    {
    返回colSpan;
    }
    }
    colSpan++;
    }
    返回colSpan;
    }
    
    编辑:现在对事件进行排序,而不是假设它们已排序


    Edit2:如果有足够的空间,现在将事件向右展开。

    接受的答案描述了一个包含5个步骤的算法。在接受答案的注释中链接的示例实现仅实现步骤1到4。第5步是确保最右边的事件使用所有可用空间。参见OP提供的图片中的事件7

    我通过添加所述算法的步骤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;
    }
    
    $(文档).ready(函数(){
    var列_指数=0;
    $('#时间表事件.daysheet容器')。每个(函数(){
    var block_width=$(this).width();
    var列=[];
    var lastEventEnding=null;
    //创建所有事件的数组
    var events=$('.bubble_selector',this).map(函数(索引,o){
    o=$(o);
    var top=o.偏移量().top;
    返回{
    “obj”:o,
    "顶":顶,,
    “底部”:顶部+o.高度()
    };
    }).get();
    //按开始时间排序,然后按结束时间排序。
    events=events.sort(函数(e1、e2){
    如果(e1.tope2.top)返回1;
    if(e1.bottome2.bottom)返回1;
    返回0;
    });
    //迭代排序的数组
    $(事件)。每个(功能)(索引,e){
    //检查是否需要启动新的事件组
    if(lastEventEnding!==null&&e.top>=lastEventEnding){
    //最新事件晚于中的任何事件
    //当前组。没有重叠。输出当前组
    //事件组并启动新的事件组。
    填料通风孔(柱、块体宽度);
    columns=[];//这将启动新的事件组。
    lastEventEnding=null;
    }
    //尝试将事件放置在现有列中
    var=false;
    对于(var i=0;ilastEventEnding){
    lastEventEnding=e.bottom;
    }
    });
    if(columns.length)