    /** * This class is used to generate the rendering parameters for an event * in a {@link Ext.calendar.view.Days}. The purpose of this class is * to provide the rendering logic insulated from the DOM. *  * @private */Ext.define('Ext.calendar.view.DaysRenderer', {     /**     * @cfg {Date} end      * The end for the day.     */    end: null,     /**     * @cfg {Date} start      * The start for the day.     */    start: null,     /**     * @cfg {Ext.calendar.view.Days} view     * The view.     */    view: null,     constructor: function(config) {        var me = this,            D = Ext.Date,            view, slotTicks;         Ext.apply(me, config);         view = me.view;         slotTicks = view.slotTicks;        me.slots = (view.getEndTime() - view.getStartTime()) * (60 / slotTicks);        me.offset = view.MS_TO_MINUTES * slotTicks;         me.events = [];    },     /**     * Adds the event to this renderer if it is valid.     * @param {Ext.calendar.model.EventBase} The event.     */    addIf: function(event) {        var me = this,            start = me.start,            view = me.view,            offset = me.offset,            startSlot, endSlot;         if (!event.isSpan() && event.isContainedByRange(start, me.end)) {            startSlot = Math.max(0, (view.roundDate(event.getStartDate()) - start) / offset);            endSlot = Math.min(me.slots, (view.roundDate(event.getEndDate()) - start) / offset);             this.events.push({                event: event,                start: startSlot,                end: endSlot,                len: endSlot - startSlot,                colIdx: -1,                overlaps: [],                edgeWeight: -1,                forwardPos: -1,                backwardPos: -1            });        }    },     /**     * Indicates that all events are added and the positions can be calculated.     */    calculate: function() {        var me = this,            events = me.events,            columns, len, i, firstCol;         events.sort(me.sortEvents);         columns = me.buildColumns(events);        me.constructOverlaps(columns);         firstCol = columns[0];        if (firstCol) {            len = firstCol.length;             for (i = 0, len = firstCol.length; i < len; ++i) {                me.calculateEdgeWeights(firstCol[i]);            }             for (i = 0, len = firstCol.length; i < len; ++i) {                me.calculatePositions(firstCol[i], 0, 0);            }        }    },     /**     * Checks if this renderer has any events.     * @return {Boolean} `true` if there are events.     */    hasEvents: function() {        return this.events.length > 0;    },     privates: {        /**         * Finds any vertically overlapping events from the candidates and         * pushes them into the events overlap collection.         * @param {Object} event The event meta object.         * @param {Object[]} candidates The possible overlapping candidates.         *         * @private         */        appendOverlappingEvents: function(event, candidates) {            this.doOverlap(event, candidates, event.overlaps);        },         /**         * Construct a series of columns for events. If there is no         * vertical collision between events, they can exist in the same column.         * @param {Object[] events The events.         * @return {Object[][]} The columns. Each column will be an array of events.         *         * @private         */        buildColumns: function(events) {            var len = events.length,                columns = [],                i, j, colLen, col, event, idx;             for (i = 0; i < len; ++i) {                idx = -1;                event = events[i];                 for (j = 0, colLen = columns.length; j < colLen; ++j) {                    if (!this.hasOverlappingEvents(event, columns[j])) {                        idx = j;                        break;                    }                }                 if (idx === -1) {                    idx = columns.length;                    columns[idx] = [];                }                columns[idx].push(event);                event.colIdx = idx;            }             return columns;        },         /**         * Calculate the distance of this event to the edge in terms of items that         * are overlapping in the horizontal group. A larger weight means there are         * more items between the event and the edge,         * @param {Object} event The event.         * @return {Number} The weight.         *         * @private         */        calculateEdgeWeights: function(event) {            var overlaps = event.overlaps,                len = overlaps.length,                weight = event.edgeWeight,                i;             if (weight === -1) {                weight = 0;                for (i = 0; i < len; ++i) {                    weight = Math.max(weight, this.calculateEdgeWeights(overlaps[i]) + 1);                }                event.edgeWeight = weight;            }            return weight;        },         /**         * Calculate the backward/forward position from the start/end edges. The values         * for each offset will be a percentage value.         * @param {Object} event The event         * @param {Number} edgeOffset The number of items before this one in the horizontal series.         * @param {Number} backOffset The starting offset of an item in this horizontal series.         *         * @private         */        calculatePositions: function(event, edgeOffset, backOffset) {            var overlaps = event.overlaps,                len = overlaps.length,                nextEdgeOffset = edgeOffset + 1,                fwd, i, first, availWidth;             if (event.forwardPos === -1) {                if (len === 0) {                    event.forwardPos = 1;                } else {                    overlaps.sort(this.sortOverlaps);                     first = overlaps[0];                    // Calculate the forward pos from the backward pos of the first item                     // in the series. This will be the item with the highest edge weight in                     // the overlaps collection.                     this.calculatePositions(first, nextEdgeOffset, backOffset);                    event.forwardPos = first.backwardPos;                }                 fwd = event.forwardPos;                availWidth = fwd - backOffset;                event.backwardPos = fwd - availWidth / nextEdgeOffset;                 // Start from 1, either we have 0 overlaps or we already calculated it above.                 for (i = 1; i < len; ++i) {                    this.calculatePositions(overlaps[i], 0, fwd);                }            }        },         /**         * Finds events in subsequent columns that have a vertical overlap and tracks         * them on the event.         * @param {Object[][]} columns The columns.         *         * @private         */        constructOverlaps: function(columns) {            var len = columns.length,                col, i, j, colLen, event;             for (i = 0; i < len; ++i) {                col = columns[i];                 for (j = 0, colLen = col.length; j < colLen; ++j) {                    event = col[j];                     for (k = i + 1; k < len; ++k) {                        this.appendOverlappingEvents(event, columns[k])                    }                }            }        },         /**         * Utility method for checking events. Either returns true once         * it finds an overlap or appends items to an array.         *          * @param {Object} event The event.         * @param {Object[]} candidates The overlap candidates.         * @param {Object[]} append If specified, overlapping items will be appended here.         * @return {Boolean} `true` if there are overlapping events. Only returned if append         * is not specified.         *         * @private         */        doOverlap: function(event, candidates, append) {            var overlaps = [],                len = candidates.length,                i, other;             for (i = 0; i < len; ++i) {                other = candidates[i];                if (this.overlaps(event, other)) {                    if (append) {                        append.push(other);                    } else {                        return true;                    }                }            }             return false;        },         /**         * Checks if overlaps exist between an event and candidates.         * @param {Object} event The event.         * @param {Object[]} candidates The candidates.         * @return {Boolean} `true` if there are overlapping events.         *         * @private         */        hasOverlappingEvents: function(event, candidates) {            return this.doOverlap(event, candidates);        },         /**         * Checks whether two events vertically overlap.         * @param {Object} e1 The first event.         * @param {Object} e2 The second event.         * @return {Boolean} `true` if the events overlap.         *         * @private         */        overlaps: function(e1, e2) {            return e1.start < e2.end && e1.end > e2.start;        },         /**         * A sort comparator function for processing events.         * @param {Object} e1 The first event.         * @param {Object} e2 The second event,         * @return {Number} A standard sort comparator.         *         * @private         */        sortEvents: function(e1, e2) {            return Ext.calendar.model.EventBase.sort(e1.event, e2.event);        },         /**         * A sort comparator function for overlapping events.         * @param {Object} e1 The first event.         * @param {Object} e2 The second event,         * @return {Number} A standard sort comparator.         *         * @private         */        sortOverlaps: function(e1, e2) {            // Higher edge weights go first, since are at the start.                             return e2.edgeWeight - e1.edgeWeight ||                   // Otherwise sort by events closer to the start edge, giving                    // precedence to those where the backwardPos hasn't be calculated                    (e1.backwardPos || 0) - (e2.backwardPos || 0) ||                   Ext.calendar.model.EventBase.sort(e1.event, e2.event);                    }    }});