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:
Daru13 2018-11-26 16:06:31 +01:00
parent c4e70840ad
commit 51251c0a8f
3 changed files with 149 additions and 1 deletions

View file

@ -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();

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

View file

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