Add a new event sorting algorithm (using interval graph coloring).
Note: this only sort the event nodes in a particular, "optimal" order; but it does not explicitly set the grid row of each node. Therefore, the result is likely to spread on many more rows than what could be expected.
This commit is contained in:
parent
c4e70840ad
commit
51251c0a8f
3 changed files with 149 additions and 1 deletions
|
@ -70,7 +70,8 @@ class Calendar {
|
||||||
this.applyLocationStylesAsCSS();
|
this.applyLocationStylesAsCSS();
|
||||||
this.updateEventLocationStyleID();
|
this.updateEventLocationStyleID();
|
||||||
|
|
||||||
this.sortEventNodesByEndTimeAndLocation();
|
//this.sortEventNodesByEndTimeAndLocation();
|
||||||
|
this.sortEventNodesByLocationAndIntervalGraphColoring();
|
||||||
this.updateCalendarNodeHeight();
|
this.updateCalendarNodeHeight();
|
||||||
|
|
||||||
this.initEventOverflowTooltips();
|
this.initEventOverflowTooltips();
|
||||||
|
@ -334,6 +335,68 @@ class Calendar {
|
||||||
|
|
||||||
// Event node sorting
|
// Event node sorting
|
||||||
|
|
||||||
|
// The following method requires the IntervalColoration class,
|
||||||
|
// which provides the algorithm (see interval_coloring.js)
|
||||||
|
sortEventNodesByLocationAndIntervalGraphColoring () {
|
||||||
|
// Group events by location
|
||||||
|
let locationsToEvents = new Map();
|
||||||
|
for (let event of this.events) {
|
||||||
|
let location = event.location;
|
||||||
|
|
||||||
|
if (! locationsToEvents.has(location)) {
|
||||||
|
locationsToEvents.set(location, [event]);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
locationsToEvents
|
||||||
|
.get(location)
|
||||||
|
.push(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign a color to all events,
|
||||||
|
// by using the interval graph coloration algorithm
|
||||||
|
// on each subset of events with the same location
|
||||||
|
let allEventColors = [];
|
||||||
|
let nbAlreadyColoredEvents = 0;
|
||||||
|
|
||||||
|
for (let eventsAtSameLocation of locationsToEvents.values()) {
|
||||||
|
// Build intervals for each event
|
||||||
|
let intervals = [];
|
||||||
|
for (let event of eventsAtSameLocation) {
|
||||||
|
intervals.push([
|
||||||
|
event.startDate.getTime(),
|
||||||
|
event.endDate.getTime()
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the graph coloring, and assign each color to each event
|
||||||
|
let intervalGraphColors = new IntervalColoration(intervals).colors;
|
||||||
|
let eventsToColors = new Map();
|
||||||
|
for (let i = 0; i < eventsAtSameLocation; i++) {
|
||||||
|
eventsToColors.set(event, intervalGraphColors[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort the events by color
|
||||||
|
eventsAtSameLocation.sort((event1, event2) => {
|
||||||
|
return eventsToColors.get(event2) - eventsToColors.get(event1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Finally sort all event nodes,
|
||||||
|
// (1) by their location and (2) by their color
|
||||||
|
|
||||||
|
// Note: the container is detached from the DOM for better performances
|
||||||
|
this.eventContainerNode.detach();
|
||||||
|
|
||||||
|
for (let eventsAtSameLocation of locationsToEvents.values()) {
|
||||||
|
for (let event of eventsAtSameLocation) {
|
||||||
|
this.eventContainerNode.prepend(event.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.eventContainerNode.appendTo(this.containerNode);
|
||||||
|
}
|
||||||
|
|
||||||
sortEventNodesByEndTimeAndLocation () {
|
sortEventNodesByEndTimeAndLocation () {
|
||||||
this.events.sort((event1, event2) => {
|
this.events.sort((event1, event2) => {
|
||||||
return event2.endDate.getTime() - event1.endDate.getTime();
|
return event2.endDate.getTime() - event1.endDate.getTime();
|
||||||
|
|
84
event/static/js/interval_coloration.js
Normal file
84
event/static/js/interval_coloration.js
Normal file
|
@ -0,0 +1,84 @@
|
||||||
|
// Interval graph coloring algorithm, by Twal
|
||||||
|
|
||||||
|
class IntervalColoration {
|
||||||
|
constructor (intervals) {
|
||||||
|
this.intervals = intervals;
|
||||||
|
this.n = this.intervals.length;
|
||||||
|
this.computeInterferenceGraph();
|
||||||
|
this.computePEO();
|
||||||
|
this.computeColoration();
|
||||||
|
}
|
||||||
|
|
||||||
|
computeInterferenceGraph() {
|
||||||
|
this.adj = new Array(this.n);
|
||||||
|
for (let i = 0; i < this.n; ++i) {
|
||||||
|
this.adj[i] = [];
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this.n; ++i) {
|
||||||
|
for (let j = 0; j < i; ++j) {
|
||||||
|
let inti = this.intervals[i];
|
||||||
|
let intj = this.intervals[j];
|
||||||
|
if (inti[0] <= intj[1] && intj[0] <= inti[1]) {
|
||||||
|
this.adj[i].push(j);
|
||||||
|
this.adj[j].push(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//Perfect elimination order using Maximum Cardinality Search
|
||||||
|
//Runs in O(n^2), could be optimized in O(n log n)
|
||||||
|
computePEO() {
|
||||||
|
let marked = new Array(this.n);
|
||||||
|
let nbMarkedNeighbor = new Array(this.n);
|
||||||
|
this.perm = new Array(this.n);
|
||||||
|
for (let i = 0; i < this.n; ++i) {
|
||||||
|
marked[i] = false;
|
||||||
|
nbMarkedNeighbor[i] = 0;
|
||||||
|
}
|
||||||
|
for (let k = this.n-1; k >= 0; --k) {
|
||||||
|
let maxi = -1;
|
||||||
|
for (let i = 0; i < this.n; ++i) {
|
||||||
|
if (!marked[i] && (maxi == -1 || nbMarkedNeighbor[i] >= nbMarkedNeighbor[maxi])) {
|
||||||
|
maxi = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this.adj[maxi].length; ++i) {
|
||||||
|
nbMarkedNeighbor[this.adj[maxi][i]] += 1;
|
||||||
|
}
|
||||||
|
this.perm[maxi] = k;
|
||||||
|
marked[maxi] = true;
|
||||||
|
}
|
||||||
|
// console.log(this.perm);
|
||||||
|
}
|
||||||
|
|
||||||
|
computeColoration() {
|
||||||
|
this.colors = new Array(this.n);
|
||||||
|
let isColorUsed = new Array(this.n);
|
||||||
|
for (let i = 0; i < this.n; ++i) {
|
||||||
|
this.colors[i] = -1;
|
||||||
|
isColorUsed[i] = false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < this.n; ++i) {
|
||||||
|
let ind = this.perm[i];
|
||||||
|
for (let j = 0; j < this.adj[ind].length; ++j) {
|
||||||
|
let neigh = this.adj[ind][j];
|
||||||
|
if (this.colors[neigh] >= 0) {
|
||||||
|
isColorUsed[this.colors[neigh]] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let j = 0; j < this.n; ++j) {
|
||||||
|
if (!isColorUsed[j]) {
|
||||||
|
this.colors[ind] = j;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let j = 0; j < this.adj[ind].length; ++j) {
|
||||||
|
let neigh = this.adj[ind][j];
|
||||||
|
if (this.colors[neigh] >= 0) {
|
||||||
|
isColorUsed[this.colors[neigh]] = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -26,6 +26,7 @@
|
||||||
{% block extra_js %}
|
{% block extra_js %}
|
||||||
{{ block.super }}
|
{{ block.super }}
|
||||||
<script type="text/javascript" src="{% static "js/tipso.min.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/tipso.min.js" %}"></script>
|
||||||
|
<script type="text/javascript" src="{% static "js/interval_coloration.js" %}"></script>
|
||||||
<script type="text/javascript" src="{% static "js/calendar.js" %}"></script>
|
<script type="text/javascript" src="{% static "js/calendar.js" %}"></script>
|
||||||
|
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
|
|
Loading…
Reference in a new issue