我正在研究一个事件系统,它基本上是一个720px高度的容器,每个像素从上午9点到晚上9点表示一分钟,宽度为620px(左右10px填充)
日历系统的自然要求是:
输入将是一个类似于:
的数组[
{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并根据上述要求定位它们。
非常感谢任何帮助。
答案 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)很容易;每个时隙的数组宽度是事件的数量。例如,时间段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.事件应该尽可能使用最大宽度,同时仍然遵守第一个约束条件。
有关示例,请参见下图。
该函数的输入将是一个事件对象数组,包含事件的开始和结束时间。示例(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之外,该函数还应该返回一个事件对象数组,这些事件对象设置了左侧和顶部位置(相对于容器的左上角)。
第二部分:使用第一部分中的功能创建一个样式的网页,就像下面的示例图片一样
以下日历活动:
注意:在启动时有一组默认事件(第二部分需要)。
对于测试,在默认数组(第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}
]
图表将是:
黑色循环 - 节点 - 事件
绿色椭圆 - 集团 - 一组碰撞事件
红色椭圆 - 簇 - 连接节点组
蓝线 - 边缘 - 碰撞事件之间的连接器
注意:左上方的绿色椭圆是左侧星团中最大的一个
董事会将是:
红色矩形 - 群集
彩色圆点 - 集团(每种颜色都是不同的集团)。
对于给定的事件数组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
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;
}
}
}
第五步:将节点放在电路板上。 在这一步中,我们已经获得了将事件(节点)放置在其在板上的位置所需的所有信息。每个节点的位置和大小将由以下内容确定:
算法的时间复杂度为$ 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着色;这是一些伪代码。
对于每个事件e
,请将两个“瞬间”(开始,e
)和(结束,e
)指向e
。
按时间对这些瞬间进行排序,结束时间出现同时启动时刻。
初始化空列表component
,空列表column_stack
,号码num_columns = 0
和号码num_active = 0
。 component
包含将分配相同列数的所有事件。 column_stack
会记住哪些列是免费的。
按顺序扫描瞬间。如果它是事件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
:
i
appointments
)
i
的开始日期与j
中所有项active
的结束日期进行比较 - 删除j.enddate
&lt; i.startdate
的所有项目j
i
的冲突(每个+1)i.collision = active.length
(i
)active
弹出到数组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<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。
现在,如果您需要进一步的帮助,请尝试使代码能够使其正常工作并撰写评论。