带事件div的自定义日历

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

日历系统的自然要求是:

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

在此处输入图像描述

输入将是一个类似于下列的数组:

[ {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并根据上述要求定位它们。

请参阅JSBin演示。

任何帮助将不胜感激。

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

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

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

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

第2步! 你会注意到我添加了一张支票,以确保事件在结束前开始。 有点防守,但我的算法会在坏数据上遇到无限循环,所以我想确保事件有意义。

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

0:[]
1:[]

30:[1]
31:[1]

(跳到一些有趣的数字)
540:[2]
560:[2,3]
610:[3,4]

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

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

  1. 它冲突的最大次数。
  2. 它的水平排序(以便冲突不重叠)。

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

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

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

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

最后,我们只创建一个小DOM,定位我们的事件div,并设置一个随机颜色,这样它们很容易区分开来。 结果是(我认为)你在寻找什么。

如果您有任何疑问,我很乐意回答。 我知道这可能是比你期望的更多的代码(和更多的复杂性),但它是一个令人惊讶的复杂问题要解决:)

如果你想自己滚动,那么使用以下代码:
演示: 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 = $(''); 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 = $('
'); 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 ..就是这样!!

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

要求

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

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

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

有关示例,请参见下图。 图片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之外,该函数还应返回一个事件对象数组,这些事件对象设置了左侧和顶部位置(相对于容器的左上角)。

第二部分:使用第一部分中的函数创建一个样式的网页,就像下面的示例图像一样。
图像2 以下日历活动:

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

安装和运行

  1. 克隆这个回购
  2. 在您喜欢的浏览器中打开index.html文件。

注意:启动时有一组默认事件(第二部分需要)。
对于测试,在默认数组(第14行)的下方,您可以找到生成随机事件数组的generateEvents函数。 数组大小将由arrayLength属性确定。

依赖

没有!

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

介绍

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

条款

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

董事会:举行所有活动的日间容器。

术语示例

对于以下输入:

 [ {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} ] 

该图表将是:

图形图像

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

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

约束释义

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

算法

对于给定的事件数组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,让我们以histogram调用此数组。 histogram长度为720, histogram每个索引将代表板上的一分钟(日)。 让我们调用histogram的每个索引一minute 。 每一minute ,都是一个arrays本身。 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] ] 

第二步:创建图表
在此步骤中,将创建图形,包括节点,节点邻居和集群,也将确定集群的最大集团。
请注意,不会有边缘实体,每个节点将保存与其碰撞的节点(key:node id,value:node)的映射(其邻居)。 该地图将被称为邻居。 此外, maxCliqueSize属性将添加到每个节点。 maxCliqueSize是节点maxCliqueSize的最大集团。

伪代码:

 nodesMap := Map; graph := Object; Node := Object; Cluster := Object //creating the nodes forEach event in arrayOfEvents { node = new Node(event.id, event.start, event.end, new Map, 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轴(“不动产”)。 在该步骤中,将根据其邻居为每个节点给出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

算法复杂度

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

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

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

分隔符是一天内任何事件都不会发生的任何时刻。 因此,如果您有一个活动从上午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 ets 1 pm - 2 pm | |___ end of second ets 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__|#####| 

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

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

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

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

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

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

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

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

使用以下步骤迭代appointments

  1. 将第一项( i )从appointments转移
  2. 比较i开始日期和活动中所有项目j结束日期 – 删除j.enddate < i.startdate中的所有项目
  3. 更新所有剩余j冲突(每个+1)
  4. 更新i碰撞( i.collision = active.length
  5. 弹出i进入arraysactive

对所有appointments项重复这些步骤。

例:

(小心,伪代码):

 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 

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

现在,如果您需要进一步的帮助,请尝试获取代码以使其工作并编写注释。