带事件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)因为我们的数据存储方式很容易; 每个时隙数组的宽度是事件的数量。 例如,时间段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.事件应尽可能使用最大宽度,同时仍遵守第一个约束。
有关示例,请参见下图。
函数的输入将是一个事件对象数组,包含事件的开始和结束时间。 示例(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之外,该函数还应返回一个事件对象数组,这些事件对象设置了左侧和顶部位置(相对于容器的左上角)。
第二部分:使用第一部分中的函数创建一个样式的网页,就像下面的示例图像一样。
以下日历活动:
- 活动从上午9:30开始,到上午11:30结束
- 活动从下午6:00开始,到晚上7:00结束
- 活动于下午6:20开始,于下午7:20结束
- 下午7:10开始,晚上8:10结束的活动
码
安装和运行
- 克隆这个回购
- 在您喜欢的浏览器中打开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} ]
该图表将是:
黑色循环 – 节点 – 事件
绿色椭圆 – 集团 – 一组碰撞事件
红色椭圆 – 群集 – 连接节点组
蓝线 – 边缘 – 碰撞事件之间的连接器
注意:左上角的绿色椭圆是左侧簇中最大的一个。
董事会将是:
红色矩形 – 集群
彩色圆点 – 集团(每种颜色都是不同的集团)。
约束释义
- 为了满足第一个约束, 同一簇中的每个节点必须在板上具有相同的宽度。
- 节点不得在板上相互重叠,同时淀粉到最大宽度并仍然坚持第一个约束。
- 群集中节点的宽度将由群集中的最大群集设置。 这是正确的,因为同一集团中的节点将在板上共享至少一分钟,这意味着它们必须具有相同的宽度(因为它们是相互冲突的)。 因此,群集中的其他节点将具有与最小节点相同的宽度。
- 集团中的每个节点将相对于其邻居获得其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; } } }
第五步:将节点放在电路板上。 在这一步中,我们已经拥有了将事件(节点)放置在电路板上的位置所需的所有信息。 每个节点的位置和大小将由以下因素确定:
- height:node.end – node.start
- width:node.cluster.width
- top-offset:node.start
- 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__|#####|
这看起来很合理。 #表示可用空间
如果我理解正确,则输入是具有开始和结束时间的事件列表,并且对于每个事件,输出是该事件的列号和该事件期间的总列数。 你基本上需要为区间图着色; 这是一些伪代码。
-
对于每个事件
e
,使两个“瞬间”(开始,e
)和(结束,e
)指向e
。 -
按时间对这些瞬间进行排序,结束时刻出现在同步启动瞬间之前 。
-
初始化空列表
component
,空列表num_columns = 0
,数字num_columns = 0
和数字num_active = 0
。component
包含将分配相同列数的所有事件。column_stack
记住哪些列是空闲的。 -
按顺序扫描瞬间。 如果它是事件
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
:
- 将第一项(
i
)从appointments
转移 - 比较
i
开始日期和活动中所有项目j
结束日期 – 删除j.enddate
<i.startdate
中的所有项目 - 更新所有剩余
j
冲突(每个+1) - 更新
i
碰撞(i.collision = active.length
) - 弹出
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。
现在,如果您需要进一步的帮助,请尝试获取代码以使其工作并编写注释。