Custom Calendar with Event Separators

I am working on an event system, which is basically a container with a height of 720 pixels with each pixel being one minute from 9AM to 9PM, and has a width of 620px (10px indent on the left and right).

A natural requirement for a calendar system is that:

  • Objects must be positioned so that they do not visually overlap.
  • If there is one event in the time interval, its width will be 600 pixels
  • Each counter event must be the same width as every other event that it collides with the width.
  • The event should use the maximum width while maintaining the first constraint.

enter image description here

The input will be an array similar to:

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

I created the necessary layout, but I was stuck with the JavaScript part :( This is what I have so far:

 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, } }(); 

You must create divs and place them in accordance with the above requirements.

See a demo of JSBin.

Any help would be greatly appreciated.

+7
source share
6 answers

Here is a working solution: http://jsbin.com/igujil/13/edit#preview

As you can see, this is not an easy task. Let me tell you how I did it.

The first step, indicated by Step 0 , is to check the sorting of events by id. It will make our life easier when we start playing with data.

Step 1 - initialize a two-dimensional array of time intervals. Every minute in the calendar we are going to create an array that will contain events that occur during this minute. We do it in ...

Step 2! . You will notice that I have added a check to make sure that the event starts before its completion. A little protection, but my algorithm hit an infinite loop on bad data, so I want to make sure that the events make sense.

At the end of this cycle, our array of time intervals will look like this:

0: []
one: []
...
30: [1]
31: [1]
...
(go to some interesting numbers)
540: [2]
560: [2,3]
610: [3.4]

I recommend that you add console.log(timeslots) immediately before step 3 if you are confused / curious. This is a very important part of the solution, and the next step is much more difficult to explain.

Step 3 is where we resolve planning conflicts. Each event should know two things:

  • The maximum number of conflicts.
  • Its horizontal ordering (so that conflicts do not overlap).

(1) easy due to how our data is stored; the width of each array of time slots is the number of events. For example, Timeslot 30 has only 1 event, because event No. 1 is the only one at that time. In Timeslot 560, however, we have two events, so each event (# 2 and # 3) gets a score of two. (And if there was a line with three events, they would all get a score of three, etc.)

(2) a little thinner. Event No. 1 is quite obvious, since it can cover the entire width of the calendar. Event No. 2 will have to reduce its width, but it can still begin along the left edge. Event No. 3 cannot.

We solve this with a variable for each time interval, which I called next_hindex . It starts at 0 because by default we want to position along the left edge, but it will increase every time we find a conflict. Thus, the next event (the next fragment of our conflict) will begin in the next horizontal position.

Step 4 is pretty simple. The width calculation uses our maximum conflict counter from step 3. If we know that we have 2 events at 5:50, we know that each event should be 1/2 the width of the calendar. (If we had 3 events, each of them would be equal to 1/3, etc.). X-position is calculated in the same way; we multiply by hindex because we want to compensate for the width (number of conflicts) of the events.

Finally, we simply create a small DOM, position our div events, and set a random color so that it is easy to separate them. The result (I think) is what you were looking for.

If you have any questions, I will be happy to answer. I know this is probably more code (and more complicated) than you expected, but it was an amazingly difficult problem :)

+6
source

if you want to flip your own, use the following code:
DEMO: 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); }); 

I highly recommend using http://arshaw.com/fullcalendar/
demo: http://jsfiddle.net/jGG34/2/
everything that you are trying to achieve is already implemented in this, just turn on day mode and do some css hacks .. here it is!

+2
source

Here is my solution: # one day One-day calendar

Requirements

Part I: Write a function for laying out a series of events in a calendar for one day.

Events will be placed in the container. The top of the container is 9am and the bottom is 9pm.
The width of the container will be 620px (10px padding left and right), and the height will be 720px (1 pixel for every minute between 9:00 and 21:00). Objects must be positioned so that they do not visually overlap. If there is only one event in a given time interval, its width should be 600 pixels.

There are two main limitations: 1. Each counter event must be the same width as every other event that it collides with the width. 2. The event should use the maximum possible width, still adhering to the first constraint.

The following is an example image. image 1

Function input will be an array of event objects with the start and end time of the event. Example (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 ] 

The function should return an array of event objects that have left and top positions (relative to the top left corner of the container), in addition to id, start and end.

Part II Use your function from part I to create a web page that is written in style, as shown in the example below.
image2 with the following calendar events:

  • An event that starts at 9:30 and ends at 11:30.
  • An event that starts at 18:00 and ends at 19:00.
  • An event that starts at 18:20 and ends at 19:20.
  • An event that starts at 19:10 p.m. and ends at 8:10 p.m.

the code

Installation and launch

  • Clone this repo
  • Open the index.html file in your favorite browser.

Note: at startup there is a set of events by default (required in the second part).
For testing, right below the default array (line 14), you can find the generateEvents function that generates a random array of events. The size of the array will be determined by the arrayLength attribute.

Dependencies

does not have!

Decision

Below you can find an algorithm to solve the problem in accordance with the requirements.

Introduction

I will try to solve this problem in the form of graphs, so I need to specify several terms.

Conditions

Node: represents an event - $ n $, $ n \ in N, N $ - a group of all nodes.
Edge: represents counter events - $ e $, $ e \ in E, E $ - a group of all edges. For example, if node $ u $ and $ v $ collide, then the edge $ e_ {u, v} $ will be connected.
Graph: a set of nodes and edges $ G, G \ in (N, E) $.
Cluster: represents a group of connected nodes (a subgroup of the diagram) - $ c $, $ c \ subseteq G $. For example, if we have the following nodes: $ u, v, w $ and edge $ e_ {u, v} $. Then there will be 2 clusters, the first will contain $ u, v $, and the second will contain only $ w $.
Clique: represents a subgroup of nodes in a cluster, each pair of nodes in this group has a connecting edge - $ cq $, $ cq \ subseteq c $. Note: a click is a group of colliding events.

Tip: A container of the day that contains all the events.

Condition example

For the following input:

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

The schedule will be:

graph image

Black loop - node - event
Green ellipse - clique - group of colliding events
Red ellipse - cluster - group of connected nodes
Blue line - edge - connector between oncoming events
Note: the upper left green ellipse is the largest click in the left cluster.
The fee will be: board

Red Rectangle - Cluster
Colored dots - click (each color - another click).

Paraphrase paraphrase

  • Each node in the same cluster must have the same width on the board in order to meet the first limitation.
  • The nodes should not intersect with each other on the board, but starch to the maximum width and at the same time adhere to the first restriction.
  • The width of the nodes in the cluster will be set by the largest click in the cluster. This is true because the nodes in one click will share at least one minute on the board, i.e. they must have the same width (because they collide). Thus, the other nodes in the cluster will have the same width as the smallest node.
  • Each node in a click will receive its X axis position relative to its neighbors.

Algorithm

For this array of events arrayOfEvents (from the sample requirements):

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

Step one: create a histogram of events.
An array of arrays will be created that will call this array as a histogram . The histogram length will be 720, each histogram index will represent a minute on the board (day). Lets name each histogram a minute index. Each minute is an array. Each minute array index represents an event that occurs at that minute.

pseudo code:

 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 array will look like this after this step (for this example):

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

Step two: create a schedule
At this point, a graph will be created, including nodes, node neighbors and clusters, and the largest click of the cluster will be determined.
Note that there will be no edge object, each node will contain a map of nodes (key: node id, value: node) that it encounters (neighbors). This card will be called neighbors. In addition, the maxCliqueSize attribute will be added to each node. maxCliqueSize is the largest click of which node is a part.

pseudo code:

 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]; } } } } 

Step Three: Calculate the Width of Each Cluster
As mentioned above, the width of all nodes in the cluster will be determined by the size of the largest click in the cluster.
The width of each node $ n $ in the cluster $ c $ will follow this equation:
$$ n_ {width} = \ frac {Board_ {width}} {Max \ left (n_ {1} .biggestCliqueSize, n_ {2} .biggestCliqueSize, ..., n_ {n} .biggestCliqueSize \ right)} $$

Each node width will be set in the cluster to which it is associated. Thus, the width property will be set to the cluster object.

pseudo code:

 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; } 

Fourth step: calculates the position of the node in its click.
As already mentioned, nodes will have to share the X axis ("real estate") with their neighbors. At this point, the position of the X axis will be indicated for each node according to its neighbors. The largest click in the cluster will determine the number of available places.

pseudo code:

 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; } } } 

Step Five: Placing the nodes on the board. At this stage, we already have all the information necessary to place an event (node) at its position on the board. The position and size of each node will be determined using:

  • height: node.end - node.start
  • width: node.cluster.width
  • top offset: node.start
  • left shift: node.cluster.width * node.position + left-padding
Algorithm complexity

The time complexity of the algorithm is $ O \ left (n ^ {2} \ right) $.
The complexity of the algorithm space is $ O \ left (n \ right) $.

Github repo: https://github.com/vlio20/one-day

+1
source

I would approach the problem as follows.

A separator is any moment during the day when no event crosses. Therefore, if you have one event from 9 am to 11 am, and another from 11:00 to 13:00 and no other events, at 11 am there is a separator at any time at 13:00 or later and at any time at 9 am or earlier.

Every day, I would share a lot of β€œevent-saturated time periods”, which are maximum time intervals that do not contain separators. For each event-saturated time span, I calculated the maximum number of concurrent events at the same time and used it as the "column number" for this event range. Then I would decompose each event-laden period of time into the calculated number of columns so that each event is laid out as far as possible, in order of the time the events started.

So, for example, the following schedule:

 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 

will be processed as follows. Long periods of time - from 9:00 to 13:00, from 13:00 to 14:00 and from 14:00 to 17:00, since there are separators at 13:00 and 14:00 (no events).

In the first flight there are a maximum of three overlapping events, in the second only one, and in the third - two.

Columns are highlighted as follows:

  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 | | | 

After which the events are filled in their chronological order with greed:

  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__|#####| 

which looks very reasonable. # denotes free space

0
source

If I understand you correctly, the input list is a list of events with start and end times, and for each event, the output is the column number of this event and the total number of columns during this event. You basically need to put an interval chart ; here is some pseudo code.

  • For each event e create two β€œmoments” (start, e ) and (end, e ), returning to e .

  • Sort these moments by time, with the appearance moments appearing before the simultaneous launch moments.

  • Initialize an empty component list, an empty column_stack list, num_columns = 0 and num_active = 0 . component contains all events that will be assigned the same number of columns. column_stack remembers which columns are free.

  • Scanning moments in order. If this is the moment the event e starts, we need to assign column e . Get this column by clicking column_stack if it is not empty; otherwise, assign a new column (number num_columns ) and increment num_columns (another order for indexing based on 1 instead of 0). Add e to component . The increment is num_active . If this is the end point, then press the e assigned column on column_stack . Decrease num_active . If num_active now 0, then we start a new connected component, selecting all the events from component and setting their total number of columns to num_columns , then clearing column_stack and resetting num_columns to 0.

0
source

The key point is counting all collisions from your appointments. There is a fairly simple algorithm for this:

  • sort the destination assignments array according to the start date and break the links with the end date.
  • maintain an active array with all active assignments, which is empty at the beginning
  • maintain the collision value for each meeting (since you already have objects, you can save it as another property) [{id : 1, start : 30, end : 150, collisions : 0},...]

iterate through appointments with the following steps:

  • slide the first item ( i ) with appointments
  • compare the start date with i with the final values ​​of all elements j in active - delete all elements where j.enddate < i.startdate
  • update collisions from all other j (+1 for each)
  • update collision i ( i.collision = active.length )
  • pop i to array active

Repeat these steps for all appointments .

Example:

(beware, pseudo-code):

 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 } 

If you collected the elements removed from the active, you will get an array sorted by date before the start, which you can use to display the div.

Now try if you can get the code to make it work and write a comment if you need more help.

0
source

All Articles