7

Several days ago I asked the following question on webapps.stackexchange.com:
https://webapps.stackexchange.com/questions/54130/is-there-a-way-to-remove-overlaying-events-in-google-calendar

I didn't get any good answers so far, so I've decided to write my own little script that will change width of the event based on the number of overlapping events.

I want to avoid overlapping of the boxes and want them to stack.

Here's the initial test:

enter image description here

Below is a basic script with description:

    $('[class^="tg-col"]').each(function(){ // each day column 
                                           //(Mon, Tue, etc. has class tg-col or tg-col-weekend.
    var ItemArray = [];
    $(this).find(".chip").each(function(){ //each event box has chip class.

    var top = $$(this).position().top;  //Get top pixels
    var bottom = $$(this).height() + top;  //get end of the event.
    var arr = ItemArray.push({
        class: $$(this).attr("class").split(" ")[0], 
        start : top, 
        end:bottom
         }); 
    });
    var result = getOverlaps(ItemArray); //get overlaps counts number of overlapping events.
    $$.each(result, function(index, value){ 
      var ec = result[index].eventCount;
      var w = 100/result[index].eventCount-1 + "%";
    var el = $$("."+result[index].class);
      el.width(w);
    //el.css("left",w);
        });
    });
function getOverlaps(events) {
    // sort events
    events.sort(function (a, b) {
        return a.start - b.start;
    });

    var results = [];
    for (var i = 0, l = events.length; i < l; i++) {
        var oEvent = events[i];
        var nOverlaps = 0;
        for (var j = 0; j < l; j++) {
            var oCompareEvent = events[j];
            if (oCompareEvent.start <= oEvent.end && oCompareEvent.end > oEvent.start || oCompareEvent.end <= oEvent.start && oCompareEvent.start > oEvent.end) {
                nOverlaps++;
            }
        }
        if (nOverlaps > 1) {
            results.push({
                class: oEvent.class,
                eventCount: nOverlaps
            });
        }

    }
    return results;
}

This solution works only on simple events (on the left):
enter image description here

For more complex overlapping we cannot simply count numbers of overlaps and diving 100% / number of overlaps. We have to take into considerations other dimensions.

Also, after each manipulation in google calendar it redraws events and script changes are lost. Is there easier way to solve this problem?
(Maybe it is possible to modify js received from google directly? (But it's minified :().

Community
  • 1
  • 1
user194076
  • 8,787
  • 23
  • 94
  • 154
  • 4
    If anyone wants to fiddle with it, here's a calendar fiddle [Here](http://jsfiddle.net/JfRU9/) – Olumide Jan 22 '14 at 06:16
  • Is it possible to change width and height of each event box? If not is it possible to change the width and/or height of each container holding those events boxes? – Sasidhar Vanga Jan 22 '14 at 07:10
  • Checked at Google Calendar, we can only change width and position of each event. right? – Sasidhar Vanga Jan 22 '14 at 07:24
  • You should post a working example so we can see all of the code in context. Though it appears that collision detection may make this calendar confusing once a certain number of things is added, so the UI may need to be re-thought to accommodate the issues that may arise. – FiLeVeR10 Jan 22 '14 at 15:21
  • @FiLeVeR10, Sorry, I'm not sure how to post a working example from google cal. What I do is I open google calendar, inject Jquery with "Always Inject Jquery" plugin and open console. It works. – user194076 Jan 25 '14 at 02:02
  • Can you share your motivation for this? – Eduardo Jan 27 '14 at 16:38

3 Answers3

3

You already have correct left positions so you only need to set correct width.

In pseudocode:

foreach chip
  chips_siblings = this.parent
                       .allchilds(chip)
                       .filter(this.top <= child.top <= this.bottom
                               && child.left > this.left)
  this.width = min(chips_siblings.left) - this.left

In JavaScript:

function placeChipsCorrectly() {
    "use strict"
    Array.prototype.forEach.call(
        document.getElementsByClassName('chip'),
        function(self) {
            var siblings = Array.prototype.filter.call(
                self.parentElement.getElementsByClassName('chip'),
                function(child) {
                  return child.offsetTop >= self.offsetTop-2 &&
                         child.offsetTop <= self.offsetTop+self.offsetHeight-3 &&
                         child.offsetLeft > self.offsetLeft;
                });
            if (siblings.length > 0) {
                var minLeft = Math.min.apply(null, Array.prototype.map.call(
                    siblings,
                    function(el) {
                        return el.offsetLeft;
                    }));
                self.style.width = (minLeft - self.offsetLeft - 2) + "px";
            }
        });
}
placeChipsCorrectly();

As for tracking modification events:

After each data change you can see "Loading..." in the top right corner. After inspecting how it works you can see that it changes style attribute of the container with id lo-c.
So you can track it appearance and disappearance as following:

(new MutationObserver(placeChipsCorrectly)).observe(document.getElementById('lo-c'),{attributes: true});

When you only modify view and not data (e.g. by choosing another day or week) then "Loading..." doesn't appear. For these events you may track global click event:

document.body.addEventListener('click', placeChipsCorrectly);

Keep in mind that both methods would take some overhead. If you would like to improve them, start by modifying the observer so that it calls placeChipsCorrectly only on disappearance and by limiting click tracking to specific controls.

user
  • 23,260
  • 9
  • 113
  • 101
  • Thank you so much. It works great. There are several things I wanted to ask your advice on. 1) it doesn't place chips correctly when there are two overlapping events: one is set from 5pm-6pm and another one is set from 5-5.30pm. 2) addEventListeber doesn't change behavior and everything gets reset. Should I use something other than DomAttrModified? – user194076 Jan 28 '14 at 02:01
  • Also, it doesn't correctly calculate width when. there are pairs of two and three items. http://tinypic.com/r/s5bclz/5 – user194076 Jan 28 '14 at 02:13
  • @user194076 I've updated my answer. DOMAttrModified replaced by cross-browser MutationObserver. Added more pixel adjustments to function so it works correctly in cases that you showed. – user Jan 28 '14 at 21:25
  • OP you should totally give this guy the bounty – Jordan Schuetz Jan 29 '14 at 03:34
  • Thanks again. This answer has a lot of cool new things I didn't know! Thank you thank you. – user194076 Jan 29 '14 at 23:06
0

You could use the bounding box of your elements and try to avoid the overlap by using (in pure javascript)

document.getElementById("elemID").getBoundingClientRect();

I think the equivalent in jquery is something like

elemID.offset()

With this, you can have all Top, Bottom, Right and Left properties. With it, you can way more easily detect if you are overlaping or not. And once you have detect, just make the ajustment from the overlaping box bounds.

Miraino Hikari
  • 266
  • 3
  • 12
0

I think you should approach the problem from a different angle. You have a list of events, with a starting time and an ending time. I believe Google Calendar displays events with a precision of 15 minutes on a grid. Furthermore, notice that we don't need to know how many events a certain event conflicts with during it's entire timespan, but instead how many events it conflict with on every interval.

So... what if you do some preperation and determine for each 15-minute timespan how many events there are. First you prepare an Array:

var timespans = Array();
//24*4 quarters
for( var i = 0; i < 96; i++ ) {
  timespans[i] = 0;
}

Then we populate this day:

for( var i = 0, l = events.length; i < l; i++) {
  var oEvent = events[i];
  //Assuming that .start and .end is 'minutes since midnight'
  var firstQuarter = Math.floor( oEvent.start / 15 );
  var lastQuarter = Math.ceil( oEvent.end / 15 );
  for( var j = firstQuarter; j < lastQuarter; j++ ) {
    timespans[j] += 1;
  }
}

Then if we loop over all events again, we just have to check the Array we prepared.

for( var i = 0, l = events.length; i < l; i++) {
  var oEvent = events[i];
  //Assuming that .start and .end is 'minutes since midnight'
  var firstQuarter = Math.floor( oEvent.start / 15 );
  var lastQuarter = Math.ceil( oEvent.end / 15 );
  var conflictingTimespans = timespans.slice( firstQuarter, lastQuarter );
  //http://stackoverflow.com/a/1669222/2209007
  var maxConflicting = Math.max.apply( null, conflictingTimespans );
  //Set width of said element to columnWidth / maxConflicting
}

Besides that it should get rid of your problem, you will only check 2n events, instead of n^2 events, which should increase performance.

As for your second question, I am unsure how to help you with that.

Sumurai8
  • 20,333
  • 11
  • 66
  • 100