带事件div的自定义日历

时间:2012-05-12 09:59:17

标签: javascript jquery algorithm

我正在研究一个事件系统,它基本上是一个720px高度的容器,每个像素从上午9点到晚上9点表示一分钟,宽度为620px(左右10px填充)

日历系统的自然要求是:

  • 应该布置物体,使它们在视觉上不重叠。
  • 如果时间段中有一个事件,其宽度将为600px
  • 每个碰撞事件的宽度必须与其碰撞宽度的其他事件的宽度相同。
  • 事件应尽可能使用最大宽度,同时仍遵守第一个约束。

enter image description here

输入将是一个类似于:

的数组
[
 {id : 1, start : 30, end : 150},  // an event from 9:30am to 11:30am
 {id : 2, start : 540, end : 600}, // an event from 6pm to 7pm
 {id : 3, start : 560, end : 620}, // an event from 6:20pm to 7:20pm
 {id : 4, start : 610, end : 670} // an event from 7:10pm to 8:10pm
]

我已经创建了所需的布局,但我仍然坚持使用JavaScript部分:(这是我到目前为止所拥有的:

var Calendar = function() {

   var layOutDay = function(events) {
     var eventsLength = events.length;

     if (! eventsLength) return false;

     // sort events
     events.sort(function(a, b){return a.start - b.start;});

     for (var i = 0; i < eventsLength; i++) {
         // not sure what is next
     }         

   };

   return {
       layOutDay : layOutDay,
   }

}();

需要创建div并根据上述要求定位它们。

Please see JSBin demo.

非常感谢任何帮助。

6 个答案:

答案 0 :(得分:7)

这是一个有效的解决方案:http://jsbin.com/igujil/13/edit#preview

正如您所看到的,这不是一个容易解决的问题。让我带你了解我是如何做到的。

标记为步骤0 的第一步是确保事件按ID排序。当我们开始玩数据时,这将使我们的生活更轻松。

步骤1 是初始化时隙的二维数组。对于日历中的每一分钟,我们将创建一个数组,其中包含在该分钟内发生的事件。我们这样做......

第2步!您注意我添加了一项检查,以确保事件在结束前启动。有点防守,但我的算法会在坏数据上遇到无限循环,所以我想确保事件有意义。

在这个循环结束时,我们的时间段数组将如下所示:

0:[]
1:[]
...
30:[1]
31:[1]
...
(跳过一些有趣的数字)
540:[2]
560:[2,3]
610:[3,4]

如果您感到困惑/好奇,我建议您在第3步之前添加console.log(timeslots)。这是解决方案中非常重要的一部分,下一步要难以解释。

第3步是我们解决计划冲突的地方。每个事件都需要知道两件事:

  1. 冲突的最大次数。
  2. 其水平排序(以便冲突不会重叠)。
  3. 由于我们的数据存储方式,

    (1)很容易;每个时隙的数组宽度是事件的数量。例如,时间段30只有1个事件,因为事件#1是当时唯一的事件。然而,在Timeslot 560,我们有两个事件,所以每个事件(#2和#3)都有两个。 (如果有一行有三个事件,他们都会得到三个等等。)

    (2)更微妙一点。事件#1足够明显,因为它可以跨越日历的整个宽度。事件#2必须缩小其宽度,但它仍然可以沿着左边缘开始。事件#3不能。

    我们使用名为next_hindex的每时隙变量来解决这个问题。它从0开始,因为默认情况下我们想要沿着左边缘定位,但每次发现冲突时它都会增加。这样,下一个事件(我们冲突的下一个部分)将从下一个水平位置开始。

    第4步非常简单明了。宽度计算使用步骤3中的最大冲突计数。例如,如果我们知道在5:50有2个事件,我们知道每个事件必须是日历宽度的1/2。 (如果我们有3个事件,每个事件将是1/3,等等。)x位置的计算方法类似;我们将乘以hindex,因为我们希望偏移(冲突数)事件的宽度。

    最后,我们只创建一个小DOM,定位我们的事件div,并设置一个随机颜色,以便它们易于分辨。结果是(我认为)你在寻找什么。

    如果您有任何疑问,我很乐意回答。我知道这可能比你期望的更多代码(并且更复杂),但这是一个非常复杂的问题需要解决:)

答案 1 :(得分:2)

如果您想自己滚动,请使用以下代码:
演示:http://jsfiddle.net/CBnJY/11/

var Calendar = function() {
var layOutDay = function(events) {
    var eventsLength = events.length;

    if (!eventsLength) return false;

    // sort events
    events.sort(function(a, b) {
        return a.start - b.start;
    });

    $(".timeSlot").each(function(index, val) {
        var CurSlot = $(this);
        var SlotID = CurSlot.prop("SlotID");
        var EventHeight = CurSlot.height() - 1;
        //alert(SlotID);
        //get events and add to calendar
        var CurrEvent = [];
        for (var i = 0; i < eventsLength; i++) {
            // not sure what is next
            if ((events[i].start <= SlotID) && (SlotID < events[i].end)) {
                CurrEvent.push(events[i]);
            }
        }

        var EventTable = $('<table style="border:1px dashed purple;width:100%"><tr></tr></table');
        for (var x = 0; x < CurrEvent.length; x++) {
            var newEvt = $('<td></td>');
            newEvt.html(CurrEvent[x].start+"-"+CurrEvent[x].end);
            newEvt.addClass("timeEvent");
            newEvt.css("width", (100/CurrEvent.length)+"%");
            newEvt.css("height", EventHeight);
            newEvt.prop("id", CurrEvent[x].id);
            newEvt.appendTo(EventTable.find("tr"));
        }
        EventTable.appendTo(CurSlot);
    });

};

return {
    layOutDay: layOutDay
}
}();

var events = [
{
id: 1,
start: 30,
end: 150},
{
id: 2,
start: 180,
end: 240},
{
id: 3,
start: 180,
end: 240}];

$(document).ready(function() {
var SlotId = 0;
$(".slot").each(function(index, val) {
    var newDiv = $('<div></div>');
    newDiv.prop("SlotID", SlotId)
    //newDiv.html(SlotId);
    newDiv.height($(this).height()+2);
    newDiv.addClass("timeSlot");
    newDiv.appendTo($("#calander"));
    SlotId = SlotId + 30;
});

// call now
Calendar.layOutDay(events);
});

我强烈建议您使用http://arshaw.com/fullcalendar/
演示:http://jsfiddle.net/jGG34/2/
无论你想要实现的是什么,已经实现了这一点,只需启用日期模式并做一些css hacks ..就好了!

答案 2 :(得分:1)

这是我的解决方案:#一天 一天的日历

要求

第一部分:编写一个函数,在一天的日历上列出一系列事件。

事件将被放置在容器中。容器的顶部代表上午9点,底部代表晚上9点 容器的宽度为620px(左右10px填充),高度为720px(上午9点到晚上9点之间每分钟1个像素)。应该布置物体,使它们在视觉上不重叠。如果给定时隙只有一个事件,则其宽度应为600px。

有两个主要限制: 1.每个碰撞事件的宽度必须与碰撞宽度的其他事件的宽度相同。 2.事件应该尽可能使用最大宽度,同时仍然遵守第一个约束条件。

有关示例,请参见下图。 image 1

该函数的输入将是一个事件对象数组,包含事件的开始和结束时间。示例(JS):

[

     {id : 1, start : 60, end : 120},  // an event from 10am to 11am
     {id : 2, start : 100, end : 240}, // an event from 10:40am to 1pm
     {id : 3, start : 700, end : 720} // an event from 8:40pm to 9pm 
]

除了id,start和end time之外,该函数还应该返回一个事件对象数组,这些事件对象设置了左侧和顶部位置(相对于容器的左上角)。

第二部分:使用第一部分中的功能创建一个样式的网页,就像下面的示例图片一样 image2 以下日历活动:

  1. 上午9:30开始,上午11:30结束的活动
  2. 从下午6:00开始,到晚上7:00结束的活动
  3. 活动于下午6:20开始,于下午7:20结束
  4. 下午7:10开始,晚上8:10结束的活动
  5. 代码

    安装&amp;运行

    1. 克隆此回购
    2. 在您喜欢的浏览器中打开index.html文件。
    3. 注意:在启动时有一组默认事件(第二部分需要)。
      对于测试,在默认数组(第14行)下方,您可以找到生成随机事件数组的generateEvents函数。数组大小将由arrayLength属性确定。

      依赖关系

      无!

      解决方案

      下面你可以找到根据要求解决问题的算法。

      简介

      我将尝试以图表的方式解决这个问题,因此应该给出很少的术语。

      条款

      节点:代表一个事件 - $ n $,$ n \ in N,N $ - 所有节点组。
      Edge:表示碰撞事件 - $ e $,$ e \ in E,E $ - 所有边缘组。例如,如果节点$ u $和$ v $碰撞,那么将有一个边缘$ e_ {u,v} $连接它们。
      图表:节点和边缘的集合$ G,G \ in(N,E)$。
      群集:代表一组连接的节点(图的子组) - $ c $,$ c \ subseteq G $。例如,如果我们有以下节点:$ u,v,w $和edge $ e_ {u,v} $。然后将有2个集群,第一个将包含$ u,v $,第二个将仅包含$ w $ Clique:表示群集中的子节点组,此组中的每对节点都有一个连接边 - $ cq $,$ cq \ subseteq c $。请注意,clique表示一组碰撞事件。

      Board:包含所有活动的日期容器。

      条款示例

      对于以下输入:

      [
           {id : 1, start : 0, end : 120}, 
           {id : 2, start : 60, end : 120},
           {id : 3, start : 60, end : 180},
           {id : 4, start : 150, end : 240},
           {id : 5, start : 200, end : 240},
           {id : 6, start : 300, end : 420},
           {id : 7, start : 360, end : 420},
           {id : 8, start : 300, end : 720}
      ]
      

      图表将是:

      graph image

      黑色循环 - 节点 - 事件
      绿色椭圆 - 集团 - 一组碰撞事件
      红色椭圆 - 簇 - 连接节点组
      蓝线 - 边缘 - 碰撞事件之间的连接器
      注意:左上方的绿色椭圆是左侧星团中最大的一个 董事会将是: board

      红色矩形 - 群集
      彩色圆点 - 集团(每种颜色都是不同的集团)。

      约束释义

      1. 相同群集中的每个节点必须在棋盘上具有相同的宽度,以便满足第一个约束。
      2. 节点不得在板上相互重叠,同时淀粉到最大宽度并仍然坚持第一个约束。
      3. 群集中节点的宽度将由群集中最大的群集设置。这是正确的,因为同一集团中的节点将在板上共享至少一分钟,这意味着它们必须具有相同的宽度(因为它们是相互冲突的)。因此,群集中的其他节点将具有与最小节点相同的宽度。
      4. 集团中的每个节点将相对于其邻居获得其X轴位置。
      5. 算法

        对于给定的事件数组arrayOfEvents(来自需求示例):

        [
        
             {id : 1, start : 60, end : 120},  // an event from 10am to 11am
             {id : 2, start : 100, end : 240}, // an event from 10:40am to 1pm
             {id : 3, start : 700, end : 720} // an event from 8:40pm to 9pm 
        ]
        

        第一步:创建事件直方图 将创建一个数组Array,我们将此数组称为histogramhistogram长度为720,histogram的每个索引代表一分钟(天)。 让我们调用histogram a minute的每个索引。每个minute都是一个数组本身。 minute数组的每个索引表示此时发生的事件。

        伪代码:

        histogram = new Array(720); 
        
        forEach minute in histogram:
            minute = new Array(); 
        
        forEach event in arrayOfEvents:
            forEach minute inBetween event.start and endMinute:
                histogram[minute].push(event.id);
        
        在此步骤之后,

        histogram数组将如下所示(对于给定的示例):

        [
            1: [],
            2: [],
            .
            .
            .
            59: [],
            60: [1],
            61: [1],
            .
            .
            .
            99: [1],
            100: [1,2],
            101: [1,2],
            .
            .
            .
            120: [1,2],
            121: [2],
            122: [2],
            .
            .
            .
            240: [2],
            241: [],
            242: [],
            .
            .
            .
            699: [],
            700: [3],
            701: [3],
            .
            .
            .
            720: [3]
        ]
        

        第二步:创建图表
        在此步骤中,将创建图形,包括节点,节点邻居和集群,也将确定集群的最大集团 请注意,不会成为边缘实体,每个节点将保存与其(其邻居)发生冲突的节点(键:节点ID,值:节点)的映射。该地图将被称为邻居。此外,将为每个节点添加maxCliqueSize属性。 maxCliqueSize是节点所属的最大集团。

        伪代码:

        nodesMap := Map<nodeId, node>;
        graph := Object<clusters, nodesMap>;
        Node := Object<nodeId, start, end, neighbours, cluster, position, biggestCliqueSize>;
        Cluster := Object<mapOfNodesInCluster, width>
        
        //creating the nodes
        forEach event in arrayOfEvents {
            node = new Node(event.id, event.start, event.end, new Map<nodeId, node>, null)
            nodeMap[node.nodeId] = node;
         }
        
        //creating the clusters
        cluster = null;
        forEach minute in histogram {
            if(minute.length > 0) {
                cluster = cluster || new Cluster(new Array(), 0);
                forEach eventId in minute {
                    if(eventId not in cluster.nodes) {
                        cluster.nodes[eventId] = nodeMap[eventId];
                        nodeMap[eventId].cluster = cluster;
                    }
                } 
            } else { 
                if(cluster != null) {
                    graph.clusters.push(cluster);
                }
        
                cluster = null;
            }
        }
        
        //adding edges to nodes and finding biggest clique for each node
        forEach minute in histogram {
            forEach sourceEventId in minute {
                sourceNode = eventsMap[sourceEventId];
                sourceNode.biggestCliqueSize = Math.max(sourceNode.biggestCliqueSize, minute.length);
                forEach targetEventId in minute {
                    if(sourceEventId != targetEventId) {
                        sourceNode.neighbours[targetEventId] = eventsMap[targetEventId];
                    }
                }
            }
        }
        

        第三步:计算每个群集的宽度 如上所述,集群中所有节点的宽度将由集群中最大集团的大小决定 群集$ c $中每个节点$ n $的宽度将遵循以下等式:
        $$ n_ {width} = \ frac {Board_ {width}} {Max \ left(n_ {1} .biggestCliqueSize,n_ {2} .biggestCliqueSize,...,n_ {n} .biggestCliqueSize \ right)} $$

        每个节点宽度将在与其相关的集群中设置。因此,width属性将在集群实体上设置。

        伪代码:

        forEach cluster in graph.clusters {
            maxCliqueSize = 1;
            forEach node in cluster.nodes {
                maxCliqueSize = Max(node.biggestCliqueSize, sizeOf(node.clique);
            }
            cluster.width = BOARD_WIDTH / maxCliqueSize; 
            cluster.biggestCliqueSize = biggestCliqueSize;
        }
        

        第四步:计算其集团内的节点位置 如前所述,节点必须与其邻居共享X轴(&#34;房地产&#34;)。在该步骤中,将根据其邻居为每个节点给出X轴位置。群集中最大的群体将决定可用的位置数量。

        伪代码:

        forEach node in nodesMap {
            positionArray = new Array(node.cluster.biggestCliqueSize);
            forEach cliqueNode in node.clique {
                if(cliqueNode.position != null) {
                    //marking occupied indexes
                    positionArray[cliqueNode.position] = true;
                }
            }
        
            forEach index in positionArray {
                if(!positionArray[index]) {
                    node.position = index;
                    break;
                }
            }
        }
        

        第五步:将节点放在电路板上。 在这一步中,我们已经获得了将事件(节点)放置在其在板上的位置所需的所有信息。每个节点的位置和大小将由以下内容确定:

        1. height:node.end - node.start
        2. width:node.cluster.width
        3. top-offset:node.start
        4. left-offset:node.cluster.width * node.position + left-padding
        5. 算法复杂度

          算法的时间复杂度为$ O \ left(n ^ {2} \ right)$。
          该算法的空间复杂度为$ O \ left(n \ right)$。

          Github回购:https://github.com/vlio20/one-day

答案 3 :(得分:0)

我会按如下方式解决问题。

divider 是一天中没有越过的任何时刻。因此,如果您有一个活动从上午9点到上午11点,另一个活动从上午11点到下午1点,并且没有其他活动,那么上午11点有一个分隔线,并且在任何时间下午1点或更晚,并且在任何时间在9点我或更早。

我会把每天分成一组“多事的时间跨度”,这是一个不包含分隔符的最长时间跨度。对于每个可能的时间跨度,我将计算并发重叠事件的最大数量,并将其用作该事件跨度的“列号”。然后,我会在计算出的列数上贪婪地布置每个可能的时间跨度,以便按照事件的开始时间顺序尽可能地布置每个事件。

所以,例如以下时间表:

A  9 am - 11 am
B 10 am - 12 pm
C 10 am -  1 pm
D  1 pm  - 2 pm
E  2 pm  - 5 pm
F  3 pm  - 4 pm

将按如下方式处理。多事的时间跨度是上午9点至下午1点,下午1点至下午2点,下午2点至下午5点,因为下午1点和下午2点有分隔线(没有事件越过那些时间)。

在第一个范围内,最多有三个重叠事件,第二个只有一个,第三个是两个。

列分配如下:

 9 am - 10 am  |   |   |   |
10 am - 11 am  |   |   |   |
11 am - 12 pm  |   |   |   |
12 pm -  1 pm  |   |   |   |___ end of first e.t.s.
 1 pm -  2 pm  |           |___ end of second e.t.s.
 2 pm -  3 pm  |     |     |
 3 pm -  4 pm  |     |     |
 4 pm -  5 pm  |     |     |

之后,贪婪地按照时间顺序填写事件:

 9 am - 10 am  | A |###|###|
10 am - 11 am  |_A_| B | C |
11 am - 12 pm  |###|_B_| C |
12 pm -  1 pm  |###|###|_C_|
 1 pm -  2 pm  |_____D_____|
 2 pm -  3 pm  |  E  |#####|
 3 pm -  4 pm  |  E  |__F__|
 4 pm -  5 pm  |__E__|#####|

看起来很合理。 #表示可用空间

答案 4 :(得分:0)

如果我理解正确,输入是包含开始和结束时间的事件列表,对于每个事件,输出是该事件的列号和该事件期间的总列数。你基本上需要为interval graph着色;这是一些伪代码。

  1. 对于每个事件e,请将两个“瞬间”(开始,e)和(结束,e)指向e

  2. 按时间对这些瞬间进行排序,结束时间出现同时启动时刻。

  3. 初始化空列表component,空列表column_stack,号码num_columns = 0和号码num_active = 0component包含将分配相同列数的所有事件。 column_stack会记住哪些列是免费的。

  4. 按顺序扫描瞬间。如果它是事件e的开始时刻,那么我们需要为e分配一列。如果它是非空的,请弹出column_stack来获取此列;否则,分配一个新列(编号num_columns)并增加num_columns(基于1的索引而不是基于0的其他顺序)。将e附加到component。增加num_active。如果是结束时刻,请将e指定的列推送到column_stack。减少num_active。如果num_active现在为0,那么我们会通过弹出component中的所有事件并将其总列数设置为num_columns来开始新的连接组件,然后清除column_stack和将num_columns重置为0.

答案 5 :(得分:0)

关键点是计算约会中的所有碰撞。有一个非常简单的算法:

  • 根据开始日期和与结束日期断开的关系对约会数组appointments进行排序。
  • 维护一个包含所有活动约会的数组active,该约会在开头是空的
  • 为每个约会维护一个值collision(因为您已有对象,您可以将其存储为另一个属性)[{id : 1, start : 30, end : 150, collisions : 0},...]

使用以下步骤迭代appointments

  1. i
  2. 移动第一项(appointments
  3. i的开始日期与j中所有项active的结束日期进行比较 - 删除j.enddate&lt; i.startdate的所有项目j
  4. 更新所有剩余i的冲突(每个+1)
  5. 更新i.collision = active.lengthi
  6. 的冲突
  7. active弹出到数组appointments
  8. var unsorted = [7,9],[2,8],[1,3],[2,5],[10,12] // var appointments = sort(unsorted); var appointments = [1,3],[2,5],[2,8],[7,9],[10,12] // now for all items of appoitments: for (var x = 0; x<appointments.length;x++){ var i = appointments[x]; // step 1 for (var j=0; j<active.length;j++){ // remove j if j.enddate < j.startdate // step 2 // else j.collision += 1; // step 3 } i.collision = active.length; // step 4 active.pop(i); // step 5 } 的所有项目重复这些步骤。

    示例:

    (小心,伪代码):

    {{1}}

    如果收集从活动状态中删除的项目,则会得到一个数组,按开始日期之前的结束日期排序,您可以使用它来显示div。

    现在,如果您需要进一步的帮助,请尝试使代码能够使其正常工作并撰写评论。