You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@flink.apache.org by se...@apache.org on 2015/10/17 21:03:26 UTC

[06/21] flink git commit: [FLINK-2844] [web frontend] Remove old web interface

http://git-wip-us.apache.org/repos/asf/flink/blob/df448625/flink-runtime/src/main/resources/web-docs-infoserver/js/timeline.js
----------------------------------------------------------------------
diff --git a/flink-runtime/src/main/resources/web-docs-infoserver/js/timeline.js b/flink-runtime/src/main/resources/web-docs-infoserver/js/timeline.js
deleted file mode 100644
index f4c7997..0000000
--- a/flink-runtime/src/main/resources/web-docs-infoserver/js/timeline.js
+++ /dev/null
@@ -1,6444 +0,0 @@
-/**
- * @file timeline.js
- *
- * @brief
- * The Timeline is an interactive visualization chart to visualize events in
- * time, having a start and end date.
- * You can freely move and zoom in the timeline by dragging
- * and scrolling in the Timeline. Items are optionally dragable. The time
- * scale on the axis is adjusted automatically, and supports scales ranging
- * from milliseconds to years.
- *
- * Timeline is part of the CHAP Links library.
- *
- * Timeline is tested on Firefox 3.6, Safari 5.0, Chrome 6.0, Opera 10.6, and
- * Internet Explorer 6+.
- *
- * @license
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not
- * use this file except in compliance with the License. You may obtain a copy
- * of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
- * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
- * License for the specific language governing permissions and limitations under
- * the License.
- *
- * Copyright (c) 2011-2013 Almende B.V.
- *
- * @author     Jos de Jong, <jo...@almende.org>
- * @date    2013-08-20
- * @version 2.5.0
- */
-
-/*
- * i18n mods by github user iktuz (https://gist.github.com/iktuz/3749287/)
- * added to v2.4.1 with da_DK language by @bjarkebech
- */
-
-/*
- * TODO
- *
- * Add zooming with pinching on Android
- * 
- * Bug: when an item contains a javascript onclick or a link, this does not work
- *      when the item is not selected (when the item is being selected,
- *      it is redrawn, which cancels any onclick or link action)
- * Bug: when an item contains an image without size, or a css max-width, it is not sized correctly
- * Bug: neglect items when they have no valid start/end, instead of throwing an error
- * Bug: Pinching on ipad does not work very well, sometimes the page will zoom when pinching vertically
- * Bug: cannot set max width for an item, like div.timeline-event-content {white-space: normal; max-width: 100px;}
- * Bug on IE in Quirks mode. When you have groups, and delete an item, the groups become invisible
- */
-
-/**
- * Declare a unique namespace for CHAP's Common Hybrid Visualisation Library,
- * "links"
- */
-if (typeof links === 'undefined') {
-    links = {};
-    // important: do not use var, as "var links = {};" will overwrite 
-    //            the existing links variable value with undefined in IE8, IE7.  
-}
-
-
-/**
- * Ensure the variable google exists
- */
-if (typeof google === 'undefined') {
-    google = undefined;
-    // important: do not use var, as "var google = undefined;" will overwrite 
-    //            the existing google variable value with undefined in IE8, IE7.
-}
-
-
-
-// Internet Explorer 8 and older does not support Array.indexOf,
-// so we define it here in that case
-// http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
-if(!Array.prototype.indexOf) {
-    Array.prototype.indexOf = function(obj){
-        for(var i = 0; i < this.length; i++){
-            if(this[i] == obj){
-                return i;
-            }
-        }
-        return -1;
-    }
-}
-
-// Internet Explorer 8 and older does not support Array.forEach,
-// so we define it here in that case
-// https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
-if (!Array.prototype.forEach) {
-    Array.prototype.forEach = function(fn, scope) {
-        for(var i = 0, len = this.length; i < len; ++i) {
-            fn.call(scope || this, this[i], i, this);
-        }
-    }
-}
-
-
-/**
- * @constructor links.Timeline
- * The timeline is a visualization chart to visualize events in time.
- *
- * The timeline is developed in javascript as a Google Visualization Chart.
- *
- * @param {Element} container   The DOM element in which the Timeline will
- *                                  be created. Normally a div element.
- */
-links.Timeline = function(container) {
-    if (!container) {
-        // this call was probably only for inheritance, no constructor-code is required
-        return;
-    }
-
-    // create variables and set default values
-    this.dom = {};
-    this.conversion = {};
-    this.eventParams = {}; // stores parameters for mouse events
-    this.groups = [];
-    this.groupIndexes = {};
-    this.items = [];
-    this.renderQueue = {
-        show: [],   // Items made visible but not yet added to DOM
-        hide: [],   // Items currently visible but not yet removed from DOM
-        update: []  // Items with changed data but not yet adjusted DOM
-    };
-    this.renderedItems = [];  // Items currently rendered in the DOM
-    this.clusterGenerator = new links.Timeline.ClusterGenerator(this);
-    this.currentClusters = [];
-    this.selection = undefined; // stores index and item which is currently selected
-
-    this.listeners = {}; // event listener callbacks
-
-    // Initialize sizes. 
-    // Needed for IE (which gives an error when you try to set an undefined
-    // value in a style)
-    this.size = {
-        'actualHeight': 0,
-        'axis': {
-            'characterMajorHeight': 0,
-            'characterMajorWidth': 0,
-            'characterMinorHeight': 0,
-            'characterMinorWidth': 0,
-            'height': 0,
-            'labelMajorTop': 0,
-            'labelMinorTop': 0,
-            'line': 0,
-            'lineMajorWidth': 0,
-            'lineMinorHeight': 0,
-            'lineMinorTop': 0,
-            'lineMinorWidth': 0,
-            'top': 0
-        },
-        'contentHeight': 0,
-        'contentLeft': 0,
-        'contentWidth': 0,
-        'frameHeight': 0,
-        'frameWidth': 0,
-        'groupsLeft': 0,
-        'groupsWidth': 0,
-        'items': {
-            'top': 0
-        }
-    };
-
-    this.dom.container = container;
-
-    this.options = {
-        'width': "100%",
-        'height': "auto",
-        'minHeight': 0,        // minimal height in pixels
-        'autoHeight': true,
-
-        'eventMargin': 10,     // minimal margin between events
-        'eventMarginAxis': 20, // minimal margin between events and the axis
-        'dragAreaWidth': 10,   // pixels
-
-        'min': undefined,
-        'max': undefined,
-        'zoomMin': 10,     // milliseconds
-        'zoomMax': 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
-
-        'moveable': true,
-        'zoomable': true,
-        'selectable': true,
-        'unselectable': true,
-        'editable': false,
-        'snapEvents': true,
-        'groupChangeable': true,
-
-        'showCurrentTime': true, // show a red bar displaying the current time
-        'showCustomTime': false, // show a blue, draggable bar displaying a custom time    
-        'showMajorLabels': true,
-        'showMinorLabels': true,
-        'showNavigation': false,
-        'showButtonNew': false,
-        'groupsOnRight': false,
-        'axisOnTop': false,
-        'stackEvents': true,
-        'animate': true,
-        'animateZoom': true,
-        'cluster': false,
-        'style': 'box',
-        'customStackOrder': false, //a function(a,b) for determining stackorder amongst a group of items. Essentially a comparator, -ve value for "a before b" and vice versa
-        
-        // i18n: Timeline only has built-in English text per default. Include timeline-locales.js to support more localized text.
-        'locale': 'en',
-        'MONTHS': new Array("January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"),
-        'MONTHS_SHORT': new Array("Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"),
-        'DAYS': new Array("Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"),
-        'DAYS_SHORT': new Array("Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"),
-        'ZOOM_IN': "Zoom in",
-        'ZOOM_OUT': "Zoom out",
-        'MOVE_LEFT': "Move left",
-        'MOVE_RIGHT': "Move right",
-        'NEW': "New",
-        'CREATE_NEW_EVENT': "Create new event"
-    };
-
-    this.clientTimeOffset = 0;    // difference between client time and the time
-    // set via Timeline.setCurrentTime()
-    var dom = this.dom;
-
-    // remove all elements from the container element.
-    while (dom.container.hasChildNodes()) {
-        dom.container.removeChild(dom.container.firstChild);
-    }
-
-    // create a step for drawing the axis
-    this.step = new links.Timeline.StepDate();
-
-    // add standard item types
-    this.itemTypes = {
-        box:   links.Timeline.ItemBox,
-        range: links.Timeline.ItemRange,
-        dot:   links.Timeline.ItemDot
-    };
-
-    // initialize data
-    this.data = [];
-    this.firstDraw = true;
-
-    // date interval must be initialized 
-    this.setVisibleChartRange(undefined, undefined, false);
-
-    // render for the first time
-    this.render();
-
-    // fire the ready event
-    var me = this;
-    setTimeout(function () {
-        me.trigger('ready');
-    }, 0);
-};
-
-
-/**
- * Main drawing logic. This is the function that needs to be called
- * in the html page, to draw the timeline.
- *
- * A data table with the events must be provided, and an options table.
- *
- * @param {google.visualization.DataTable}      data
- *                                 The data containing the events for the timeline.
- *                                 Object DataTable is defined in
- *                                 google.visualization.DataTable
- * @param {Object} options         A name/value map containing settings for the
- *                                 timeline. Optional.
- */
-links.Timeline.prototype.draw = function(data, options) {
-    this.setOptions(options);
-    
-    if (this.options.selectable) {
-        links.Timeline.addClassName(this.dom.frame, "timeline-selectable");
-    }
-
-    // read the data
-    this.setData(data);
-
-    // set timer range. this will also redraw the timeline
-    if (options && (options.start || options.end)) {
-        this.setVisibleChartRange(options.start, options.end);
-    }
-    else if (this.firstDraw) {
-        this.setVisibleChartRangeAuto();
-    }
-
-    this.firstDraw = false;
-};
-
-
-/**
- * Set options for the timeline.
- * Timeline must be redrawn afterwards
- * @param {Object} options A name/value map containing settings for the
- *                                 timeline. Optional.
- */
-links.Timeline.prototype.setOptions = function(options) {
-    if (options) {
-        // retrieve parameter values
-        for (var i in options) {
-            if (options.hasOwnProperty(i)) {
-                this.options[i] = options[i];
-            }
-        }
-        
-        // prepare i18n dependent on set locale
-        if (typeof links.locales !== 'undefined' && this.options.locale !== 'en') {
-            var localeOpts = links.locales[this.options.locale];
-            if(localeOpts) {
-                for (var l in localeOpts) {
-                    if (localeOpts.hasOwnProperty(l)) {
-                        this.options[l] = localeOpts[l];
-                    }
-                }
-            }
-        }
-
-        // check for deprecated options
-        if (options.showButtonAdd != undefined) {
-            this.options.showButtonNew = options.showButtonAdd;
-            console.log('WARNING: Option showButtonAdd is deprecated. Use showButtonNew instead');
-        }
-        if (options.intervalMin != undefined) {
-            this.options.zoomMin = options.intervalMin;
-            console.log('WARNING: Option intervalMin is deprecated. Use zoomMin instead');
-        }
-        if (options.intervalMax != undefined) {
-            this.options.zoomMax = options.intervalMax;
-            console.log('WARNING: Option intervalMax is deprecated. Use zoomMax instead');
-        }
-
-        if (options.scale && options.step) {
-            this.step.setScale(options.scale, options.step);
-        }
-    }
-
-    // validate options
-    this.options.autoHeight = (this.options.height === "auto");
-};
-
-/**
- * Add new type of items
- * @param {String} typeName  Name of new type
- * @param {links.Timeline.Item} typeFactory Constructor of items
- */
-links.Timeline.prototype.addItemType = function (typeName, typeFactory) {
-    this.itemTypes[typeName] = typeFactory;
-};
-
-/**
- * Retrieve a map with the column indexes of the columns by column name.
- * For example, the method returns the map
- *     {
- *         start: 0,
- *         end: 1,
- *         content: 2,
- *         group: undefined,
- *         className: undefined
- *         editable: undefined
- *         type: undefined
- *     }
- * @param {google.visualization.DataTable} dataTable
- * @type {Object} map
- */
-links.Timeline.mapColumnIds = function (dataTable) {
-    var cols = {},
-        colCount = dataTable.getNumberOfColumns(),
-        allUndefined = true;
-
-    // loop over the columns, and map the column id's to the column indexes
-    for (var col = 0; col < colCount; col++) {
-        var id = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
-        cols[id] = col;
-        if (id == 'start' || id == 'end' || id == 'content' || id == 'group' ||
-            id == 'className' || id == 'editable' || id == 'type') {
-            allUndefined = false;
-        }
-    }
-
-    // if no labels or ids are defined, use the default mapping
-    // for start, end, content, group, className, editable, type
-    if (allUndefined) {
-        cols.start = 0;
-        cols.end = 1;
-        cols.content = 2;
-        if (colCount >= 3) {cols.group = 3}
-        if (colCount >= 4) {cols.className = 4}
-        if (colCount >= 5) {cols.editable = 5}
-        if (colCount >= 6) {cols.type = 6}
-    }
-
-    return cols;
-};
-
-/**
- * Set data for the timeline
- * @param {google.visualization.DataTable | Array} data
- */
-links.Timeline.prototype.setData = function(data) {
-    // unselect any previously selected item
-    this.unselectItem();
-
-    if (!data) {
-        data = [];
-    }
-
-    // clear all data
-    this.stackCancelAnimation();
-    this.clearItems();
-    this.data = data;
-    var items = this.items;
-    this.deleteGroups();
-
-    if (google && google.visualization &&
-        data instanceof google.visualization.DataTable) {
-        // map the datatable columns
-        var cols = links.Timeline.mapColumnIds(data);
-
-        // read DataTable
-        for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
-            items.push(this.createItem({
-                'start':     ((cols.start != undefined)     ? data.getValue(row, cols.start)     : undefined),
-                'end':       ((cols.end != undefined)       ? data.getValue(row, cols.end)       : undefined),
-                'content':   ((cols.content != undefined)   ? data.getValue(row, cols.content)   : undefined),
-                'group':     ((cols.group != undefined)     ? data.getValue(row, cols.group)     : undefined),
-                'className': ((cols.className != undefined) ? data.getValue(row, cols.className) : undefined),
-                'editable':  ((cols.editable != undefined)  ? data.getValue(row, cols.editable)  : undefined),
-                'type':      ((cols.editable != undefined)  ? data.getValue(row, cols.type)      : undefined)
-            }));
-        }
-    }
-    else if (links.Timeline.isArray(data)) {
-        // read JSON array
-        for (var row = 0, rows = data.length; row < rows; row++) {
-            var itemData = data[row];
-            var item = this.createItem(itemData);
-            items.push(item);
-        }
-    }
-    else {
-        throw "Unknown data type. DataTable or Array expected.";
-    }
-
-    // prepare data for clustering, by filtering and sorting by type
-    if (this.options.cluster) {
-        this.clusterGenerator.setData(this.items);
-    }
-
-    this.render({
-        animate: false
-    });
-};
-
-/**
- * Return the original data table.
- * @return {google.visualization.DataTable | Array} data
- */
-links.Timeline.prototype.getData = function  () {
-    return this.data;
-};
-
-
-/**
- * Update the original data with changed start, end or group.
- *
- * @param {Number} index
- * @param {Object} values   An object containing some of the following parameters:
- *                          {Date} start,
- *                          {Date} end,
- *                          {String} content,
- *                          {String} group
- */
-links.Timeline.prototype.updateData = function  (index, values) {
-    var data = this.data,
-        prop;
-
-    if (google && google.visualization &&
-        data instanceof google.visualization.DataTable) {
-        // update the original google DataTable
-        var missingRows = (index + 1) - data.getNumberOfRows();
-        if (missingRows > 0) {
-            data.addRows(missingRows);
-        }
-
-        // map the column id's by name
-        var cols = links.Timeline.mapColumnIds(data);
-
-        // merge all fields from the provided data into the current data
-        for (prop in values) {
-            if (values.hasOwnProperty(prop)) {
-                var col = cols[prop];
-                if (col == undefined) {
-                    // create new column
-                    var value = values[prop];
-                    var valueType = 'string';
-                    if (typeof(value) == 'number')       {valueType = 'number';}
-                    else if (typeof(value) == 'boolean') {valueType = 'boolean';}
-                    else if (value instanceof Date)      {valueType = 'datetime';}
-                    col = data.addColumn(valueType, prop);
-                }
-                data.setValue(index, col, values[prop]);
-
-                // TODO: correctly serialize the start and end Date to the desired type (Date, String, or Number)
-            }
-        }
-    }
-    else if (links.Timeline.isArray(data)) {
-        // update the original JSON table
-        var row = data[index];
-        if (row == undefined) {
-            row = {};
-            data[index] = row;
-        }
-
-        // merge all fields from the provided data into the current data
-        for (prop in values) {
-            if (values.hasOwnProperty(prop)) {
-                row[prop] = values[prop];
-
-                // TODO: correctly serialize the start and end Date to the desired type (Date, String, or Number)
-            }
-        }
-    }
-    else {
-        throw "Cannot update data, unknown type of data";
-    }
-};
-
-/**
- * Find the item index from a given HTML element
- * If no item index is found, undefined is returned
- * @param {Element} element
- * @return {Number | undefined} index
- */
-links.Timeline.prototype.getItemIndex = function(element) {
-    var e = element,
-        dom = this.dom,
-        frame = dom.items.frame,
-        items = this.items,
-        index = undefined;
-
-    // try to find the frame where the items are located in
-    while (e.parentNode && e.parentNode !== frame) {
-        e = e.parentNode;
-    }
-
-    if (e.parentNode === frame) {
-        // yes! we have found the parent element of all items
-        // retrieve its id from the array with items
-        for (var i = 0, iMax = items.length; i < iMax; i++) {
-            if (items[i].dom === e) {
-                index = i;
-                break;
-            }
-        }
-    }
-
-    return index;
-};
-
-/**
- * Set a new size for the timeline
- * @param {string} width   Width in pixels or percentage (for example "800px"
- *                         or "50%")
- * @param {string} height  Height in pixels or percentage  (for example "400px"
- *                         or "30%")
- */
-links.Timeline.prototype.setSize = function(width, height) {
-    if (width) {
-        this.options.width = width;
-        this.dom.frame.style.width = width;
-    }
-    if (height) {
-        this.options.height = height;
-        this.options.autoHeight = (this.options.height === "auto");
-        if (height !==  "auto" ) {
-            this.dom.frame.style.height = height;
-        }
-    }
-
-    this.render({
-        animate: false
-    });
-};
-
-
-/**
- * Set a new value for the visible range int the timeline.
- * Set start undefined to include everything from the earliest date to end.
- * Set end undefined to include everything from start to the last date.
- * Example usage:
- *    myTimeline.setVisibleChartRange(new Date("2010-08-22"),
- *                                    new Date("2010-09-13"));
- * @param {Date}   start     The start date for the timeline. optional
- * @param {Date}   end       The end date for the timeline. optional
- * @param {boolean} redraw   Optional. If true (default) the Timeline is
- *                           directly redrawn
- */
-links.Timeline.prototype.setVisibleChartRange = function(start, end, redraw) {
-    var range = {};
-    if (!start || !end) {
-        // retrieve the date range of the items
-        range = this.getDataRange(true);
-    }
-
-    if (!start) {
-        if (end) {
-            if (range.min && range.min.valueOf() < end.valueOf()) {
-                // start of the data
-                start = range.min;
-            }
-            else {
-                // 7 days before the end
-                start = new Date(end.valueOf());
-                start.setDate(start.getDate() - 7);
-            }
-        }
-        else {
-            // default of 3 days ago
-            start = new Date();
-            start.setDate(start.getDate() - 3);
-        }
-    }
-
-    if (!end) {
-        if (range.max) {
-            // end of the data
-            end = range.max;
-        }
-        else {
-            // 7 days after start
-            end = new Date(start.valueOf());
-            end.setDate(end.getDate() + 7);
-        }
-    }
-
-    // prevent start Date <= end Date
-    if (end <= start) {
-        end = new Date(start.valueOf());
-        end.setDate(end.getDate() + 7);
-    }
-
-    // limit to the allowed range (don't let this do by applyRange,
-    // because that method will try to maintain the interval (end-start)
-    var min = this.options.min ? this.options.min : undefined; // date
-    if (min != undefined && start.valueOf() < min.valueOf()) {
-        start = new Date(min.valueOf()); // date
-    }
-    var max = this.options.max ? this.options.max : undefined; // date
-    if (max != undefined && end.valueOf() > max.valueOf()) {
-        end = new Date(max.valueOf()); // date
-    }
-
-    this.applyRange(start, end);
-
-    if (redraw == undefined || redraw == true) {
-        this.render({
-            animate: false
-        });  // TODO: optimize, no reflow needed
-    }
-    else {
-        this.recalcConversion();
-    }
-};
-
-
-/**
- * Change the visible chart range such that all items become visible
- */
-links.Timeline.prototype.setVisibleChartRangeAuto = function() {
-    var range = this.getDataRange(true);
-    this.setVisibleChartRange(range.min, range.max);
-};
-
-/**
- * Adjust the visible range such that the current time is located in the center
- * of the timeline
- */
-links.Timeline.prototype.setVisibleChartRangeNow = function() {
-    var now = new Date();
-
-    var diff = (this.end.valueOf() - this.start.valueOf());
-
-    var startNew = new Date(now.valueOf() - diff/2);
-    var endNew = new Date(startNew.valueOf() + diff);
-    this.setVisibleChartRange(startNew, endNew);
-};
-
-
-/**
- * Retrieve the current visible range in the timeline.
- * @return {Object} An object with start and end properties
- */
-links.Timeline.prototype.getVisibleChartRange = function() {
-    return {
-        'start': new Date(this.start.valueOf()),
-        'end': new Date(this.end.valueOf())
-    };
-};
-
-/**
- * Get the date range of the items.
- * @param {boolean} [withMargin]  If true, 5% of whitespace is added to the
- *                                left and right of the range. Default is false.
- * @return {Object} range    An object with parameters min and max.
- *                           - {Date} min is the lowest start date of the items
- *                           - {Date} max is the highest start or end date of the items
- *                           If no data is available, the values of min and max
- *                           will be undefined
- */
-links.Timeline.prototype.getDataRange = function (withMargin) {
-    var items = this.items,
-        min = undefined, // number
-        max = undefined; // number
-
-    if (items) {
-        for (var i = 0, iMax = items.length; i < iMax; i++) {
-            var item = items[i],
-                start = item.start != undefined ? item.start.valueOf() : undefined,
-                end   = item.end != undefined   ? item.end.valueOf() : start;
-
-            if (start != undefined) {
-                min = (min != undefined) ? Math.min(min.valueOf(), start.valueOf()) : start;
-            }
-
-            if (end != undefined) {
-                max = (max != undefined) ? Math.max(max.valueOf(), end.valueOf()) : end;
-            }
-        }
-    }
-
-    if (min && max && withMargin) {
-        // zoom out 5% such that you have a little white space on the left and right
-        var diff = (max - min);
-        min = min - diff * 0.05;
-        max = max + diff * 0.05;
-    }
-
-    return {
-        'min': min != undefined ? new Date(min) : undefined,
-        'max': max != undefined ? new Date(max) : undefined
-    };
-};
-
-/**
- * Re-render (reflow and repaint) all components of the Timeline: frame, axis,
- * items, ...
- * @param {Object} [options]  Available options:
- *                            {boolean} renderTimesLeft   Number of times the
- *                                                        render may be repeated
- *                                                        5 times by default.
- *                            {boolean} animate           takes options.animate
- *                                                        as default value
- */
-links.Timeline.prototype.render = function(options) {
-    var frameResized = this.reflowFrame();
-    var axisResized = this.reflowAxis();
-    var groupsResized = this.reflowGroups();
-    var itemsResized = this.reflowItems();
-    var resized = (frameResized || axisResized || groupsResized || itemsResized);
-
-    // TODO: only stackEvents/filterItems when resized or changed. (gives a bootstrap issue).
-    // if (resized) {
-    var animate = this.options.animate;
-    if (options && options.animate != undefined) {
-        animate = options.animate;
-    }
-
-    this.recalcConversion();
-    this.clusterItems();
-    this.filterItems();
-    this.stackItems(animate);
-
-    this.recalcItems();
-
-    // TODO: only repaint when resized or when filterItems or stackItems gave a change?
-    var needsReflow = this.repaint();
-
-    // re-render once when needed (prevent endless re-render loop)
-    if (needsReflow) {
-        var renderTimesLeft = options ? options.renderTimesLeft : undefined;
-        if (renderTimesLeft == undefined) {
-            renderTimesLeft = 5;
-        }
-        if (renderTimesLeft > 0) {
-            this.render({
-                'animate': options ? options.animate: undefined,
-                'renderTimesLeft': (renderTimesLeft - 1)
-            });
-        }
-    }
-};
-
-/**
- * Repaint all components of the Timeline
- * @return {boolean} needsReflow   Returns true if the DOM is changed such that
- *                                 a reflow is needed.
- */
-links.Timeline.prototype.repaint = function() {
-    var frameNeedsReflow = this.repaintFrame();
-    var axisNeedsReflow  = this.repaintAxis();
-    var groupsNeedsReflow  = this.repaintGroups();
-    var itemsNeedsReflow = this.repaintItems();
-    this.repaintCurrentTime();
-    this.repaintCustomTime();
-
-    return (frameNeedsReflow || axisNeedsReflow || groupsNeedsReflow || itemsNeedsReflow);
-};
-
-/**
- * Reflow the timeline frame
- * @return {boolean} resized    Returns true if any of the frame elements
- *                              have been resized.
- */
-links.Timeline.prototype.reflowFrame = function() {
-    var dom = this.dom,
-        options = this.options,
-        size = this.size,
-        resized = false;
-
-    // Note: IE7 has issues with giving frame.clientWidth, therefore I use offsetWidth instead
-    var frameWidth  = dom.frame ? dom.frame.offsetWidth : 0,
-        frameHeight = dom.frame ? dom.frame.clientHeight : 0;
-
-    resized = resized || (size.frameWidth !== frameWidth);
-    resized = resized || (size.frameHeight !== frameHeight);
-    size.frameWidth = frameWidth;
-    size.frameHeight = frameHeight;
-
-    return resized;
-};
-
-/**
- * repaint the Timeline frame
- * @return {boolean} needsReflow   Returns true if the DOM is changed such that
- *                                 a reflow is needed.
- */
-links.Timeline.prototype.repaintFrame = function() {
-    var needsReflow = false,
-        dom = this.dom,
-        options = this.options,
-        size = this.size;
-
-    // main frame
-    if (!dom.frame) {
-        dom.frame = document.createElement("DIV");
-        dom.frame.className = "timeline-frame ui-widget ui-widget-content ui-corner-all";
-        dom.frame.style.position = "relative";
-        dom.frame.style.overflow = "hidden";
-        dom.container.appendChild(dom.frame);
-        needsReflow = true;
-    }
-
-    var height = options.autoHeight ?
-        (size.actualHeight + "px") :
-        (options.height || "100%");
-    var width  = options.width || "100%";
-    needsReflow = needsReflow || (dom.frame.style.height != height);
-    needsReflow = needsReflow || (dom.frame.style.width != width);
-    dom.frame.style.height = height;
-    dom.frame.style.width = width;
-
-    // contents
-    if (!dom.content) {
-        // create content box where the axis and items will be created
-        dom.content = document.createElement("DIV");
-        dom.content.style.position = "relative";
-        dom.content.style.overflow = "hidden";
-        dom.frame.appendChild(dom.content);
-
-        var timelines = document.createElement("DIV");
-        timelines.style.position = "absolute";
-        timelines.style.left = "0px";
-        timelines.style.top = "0px";
-        timelines.style.height = "100%";
-        timelines.style.width = "0px";
-        dom.content.appendChild(timelines);
-        dom.contentTimelines = timelines;
-
-        var params = this.eventParams,
-            me = this;
-        if (!params.onMouseDown) {
-            params.onMouseDown = function (event) {me.onMouseDown(event);};
-            links.Timeline.addEventListener(dom.content, "mousedown", params.onMouseDown);
-        }
-        if (!params.onTouchStart) {
-            params.onTouchStart = function (event) {me.onTouchStart(event);};
-            links.Timeline.addEventListener(dom.content, "touchstart", params.onTouchStart);
-        }
-        if (!params.onMouseWheel) {
-            params.onMouseWheel = function (event) {me.onMouseWheel(event);};
-            links.Timeline.addEventListener(dom.content, "mousewheel", params.onMouseWheel);
-        }
-        if (!params.onDblClick) {
-            params.onDblClick = function (event) {me.onDblClick(event);};
-            links.Timeline.addEventListener(dom.content, "dblclick", params.onDblClick);
-        }
-
-        needsReflow = true;
-    }
-    dom.content.style.left = size.contentLeft + "px";
-    dom.content.style.top = "0px";
-    dom.content.style.width = size.contentWidth + "px";
-    dom.content.style.height = size.frameHeight + "px";
-
-    this.repaintNavigation();
-
-    return needsReflow;
-};
-
-/**
- * Reflow the timeline axis. Calculate its height, width, positioning, etc...
- * @return {boolean} resized    returns true if the axis is resized
- */
-links.Timeline.prototype.reflowAxis = function() {
-    var resized = false,
-        dom = this.dom,
-        options = this.options,
-        size = this.size,
-        axisDom = dom.axis;
-
-    var characterMinorWidth  = (axisDom && axisDom.characterMinor) ? axisDom.characterMinor.clientWidth : 0,
-        characterMinorHeight = (axisDom && axisDom.characterMinor) ? axisDom.characterMinor.clientHeight : 0,
-        characterMajorWidth  = (axisDom && axisDom.characterMajor) ? axisDom.characterMajor.clientWidth : 0,
-        characterMajorHeight = (axisDom && axisDom.characterMajor) ? axisDom.characterMajor.clientHeight : 0,
-        axisHeight = (options.showMinorLabels ? characterMinorHeight : 0) +
-            (options.showMajorLabels ? characterMajorHeight : 0);
-
-    var axisTop  = options.axisOnTop ? 0 : size.frameHeight - axisHeight,
-        axisLine = options.axisOnTop ? axisHeight : axisTop;
-
-    resized = resized || (size.axis.top !== axisTop);
-    resized = resized || (size.axis.line !== axisLine);
-    resized = resized || (size.axis.height !== axisHeight);
-    size.axis.top = axisTop;
-    size.axis.line = axisLine;
-    size.axis.height = axisHeight;
-    size.axis.labelMajorTop = options.axisOnTop ? 0 : axisLine +
-        (options.showMinorLabels ? characterMinorHeight : 0);
-    size.axis.labelMinorTop = options.axisOnTop ?
-        (options.showMajorLabels ? characterMajorHeight : 0) :
-        axisLine;
-    size.axis.lineMinorTop = options.axisOnTop ? size.axis.labelMinorTop : 0;
-    size.axis.lineMinorHeight = options.showMajorLabels ?
-        size.frameHeight - characterMajorHeight:
-        size.frameHeight;
-    if (axisDom && axisDom.minorLines && axisDom.minorLines.length) {
-        size.axis.lineMinorWidth = axisDom.minorLines[0].offsetWidth;
-    }
-    else {
-        size.axis.lineMinorWidth = 1;
-    }
-    if (axisDom && axisDom.majorLines && axisDom.majorLines.length) {
-        size.axis.lineMajorWidth = axisDom.majorLines[0].offsetWidth;
-    }
-    else {
-        size.axis.lineMajorWidth = 1;
-    }
-
-    resized = resized || (size.axis.characterMinorWidth  !== characterMinorWidth);
-    resized = resized || (size.axis.characterMinorHeight !== characterMinorHeight);
-    resized = resized || (size.axis.characterMajorWidth  !== characterMajorWidth);
-    resized = resized || (size.axis.characterMajorHeight !== characterMajorHeight);
-    size.axis.characterMinorWidth  = characterMinorWidth;
-    size.axis.characterMinorHeight = characterMinorHeight;
-    size.axis.characterMajorWidth  = characterMajorWidth;
-    size.axis.characterMajorHeight = characterMajorHeight;
-
-    var contentHeight = Math.max(size.frameHeight - axisHeight, 0);
-    size.contentLeft = options.groupsOnRight ? 0 : size.groupsWidth;
-    size.contentWidth = Math.max(size.frameWidth - size.groupsWidth, 0);
-    size.contentHeight = contentHeight;
-
-    return resized;
-};
-
-/**
- * Redraw the timeline axis with minor and major labels
- * @return {boolean} needsReflow     Returns true if the DOM is changed such
- *                                   that a reflow is needed.
- */
-links.Timeline.prototype.repaintAxis = function() {
-    var needsReflow = false,
-        dom = this.dom,
-        options = this.options,
-        size = this.size,
-        step = this.step;
-
-    var axis = dom.axis;
-    if (!axis) {
-        axis = {};
-        dom.axis = axis;
-    }
-    if (!size.axis.properties) {
-        size.axis.properties = {};
-    }
-    if (!axis.minorTexts) {
-        axis.minorTexts = [];
-    }
-    if (!axis.minorLines) {
-        axis.minorLines = [];
-    }
-    if (!axis.majorTexts) {
-        axis.majorTexts = [];
-    }
-    if (!axis.majorLines) {
-        axis.majorLines = [];
-    }
-
-    if (!axis.frame) {
-        axis.frame = document.createElement("DIV");
-        axis.frame.style.position = "absolute";
-        axis.frame.style.left = "0px";
-        axis.frame.style.top = "0px";
-        dom.content.appendChild(axis.frame);
-    }
-
-    // take axis offline
-    dom.content.removeChild(axis.frame);
-
-    axis.frame.style.width = (size.contentWidth) + "px";
-    axis.frame.style.height = (size.axis.height) + "px";
-
-    // the drawn axis is more wide than the actual visual part, such that
-    // the axis can be dragged without having to redraw it each time again.
-    var start = this.screenToTime(0);
-    var end = this.screenToTime(size.contentWidth);
-
-    // calculate minimum step (in milliseconds) based on character size
-    if (size.axis.characterMinorWidth) {
-        this.minimumStep = this.screenToTime(size.axis.characterMinorWidth * 6) -
-            this.screenToTime(0);
-
-        step.setRange(start, end, this.minimumStep);
-    }
-
-    var charsNeedsReflow = this.repaintAxisCharacters();
-    needsReflow = needsReflow || charsNeedsReflow;
-
-    // The current labels on the axis will be re-used (much better performance),
-    // therefore, the repaintAxis method uses the mechanism with
-    // repaintAxisStartOverwriting, repaintAxisEndOverwriting, and
-    // this.size.axis.properties is used.
-    this.repaintAxisStartOverwriting();
-
-    step.start();
-    var xFirstMajorLabel = undefined;
-    var max = 0;
-    while (!step.end() && max < 1000) {
-        max++;
-        var cur = step.getCurrent(),
-            x = this.timeToScreen(cur),
-            isMajor = step.isMajor();
-
-        if (options.showMinorLabels) {
-            this.repaintAxisMinorText(x, step.getLabelMinor(options));
-        }
-
-        if (isMajor && options.showMajorLabels) {
-            if (x > 0) {
-                if (xFirstMajorLabel == undefined) {
-                    xFirstMajorLabel = x;
-                }
-                this.repaintAxisMajorText(x, step.getLabelMajor(options));
-            }
-            this.repaintAxisMajorLine(x);
-        }
-        else {
-            this.repaintAxisMinorLine(x);
-        }
-
-        step.next();
-    }
-
-    // create a major label on the left when needed
-    if (options.showMajorLabels) {
-        var leftTime = this.screenToTime(0),
-            leftText = this.step.getLabelMajor(options, leftTime),
-            width = leftText.length * size.axis.characterMajorWidth + 10; // upper bound estimation
-
-        if (xFirstMajorLabel == undefined || width < xFirstMajorLabel) {
-            this.repaintAxisMajorText(0, leftText, leftTime);
-        }
-    }
-
-    // cleanup left over labels
-    this.repaintAxisEndOverwriting();
-
-    this.repaintAxisHorizontal();
-
-    // put axis online
-    dom.content.insertBefore(axis.frame, dom.content.firstChild);
-
-    return needsReflow;
-};
-
-/**
- * Create characters used to determine the size of text on the axis
- * @return {boolean} needsReflow   Returns true if the DOM is changed such that
- *                                 a reflow is needed.
- */
-links.Timeline.prototype.repaintAxisCharacters = function () {
-    // calculate the width and height of a single character
-    // this is used to calculate the step size, and also the positioning of the
-    // axis
-    var needsReflow = false,
-        dom = this.dom,
-        axis = dom.axis,
-        text;
-
-    if (!axis.characterMinor) {
-        text = document.createTextNode("0");
-        var characterMinor = document.createElement("DIV");
-        characterMinor.className = "timeline-axis-text timeline-axis-text-minor";
-        characterMinor.appendChild(text);
-        characterMinor.style.position = "absolute";
-        characterMinor.style.visibility = "hidden";
-        characterMinor.style.paddingLeft = "0px";
-        characterMinor.style.paddingRight = "0px";
-        axis.frame.appendChild(characterMinor);
-
-        axis.characterMinor = characterMinor;
-        needsReflow = true;
-    }
-
-    if (!axis.characterMajor) {
-        text = document.createTextNode("0");
-        var characterMajor = document.createElement("DIV");
-        characterMajor.className = "timeline-axis-text timeline-axis-text-major";
-        characterMajor.appendChild(text);
-        characterMajor.style.position = "absolute";
-        characterMajor.style.visibility = "hidden";
-        characterMajor.style.paddingLeft = "0px";
-        characterMajor.style.paddingRight = "0px";
-        axis.frame.appendChild(characterMajor);
-
-        axis.characterMajor = characterMajor;
-        needsReflow = true;
-    }
-
-    return needsReflow;
-};
-
-/**
- * Initialize redraw of the axis. All existing labels and lines will be
- * overwritten and reused.
- */
-links.Timeline.prototype.repaintAxisStartOverwriting = function () {
-    var properties = this.size.axis.properties;
-
-    properties.minorTextNum = 0;
-    properties.minorLineNum = 0;
-    properties.majorTextNum = 0;
-    properties.majorLineNum = 0;
-};
-
-/**
- * End of overwriting HTML DOM elements of the axis.
- * remaining elements will be removed
- */
-links.Timeline.prototype.repaintAxisEndOverwriting = function () {
-    var dom = this.dom,
-        props = this.size.axis.properties,
-        frame = this.dom.axis.frame,
-        num;
-
-    // remove leftovers
-    var minorTexts = dom.axis.minorTexts;
-    num = props.minorTextNum;
-    while (minorTexts.length > num) {
-        var minorText = minorTexts[num];
-        frame.removeChild(minorText);
-        minorTexts.splice(num, 1);
-    }
-
-    var minorLines = dom.axis.minorLines;
-    num = props.minorLineNum;
-    while (minorLines.length > num) {
-        var minorLine = minorLines[num];
-        frame.removeChild(minorLine);
-        minorLines.splice(num, 1);
-    }
-
-    var majorTexts = dom.axis.majorTexts;
-    num = props.majorTextNum;
-    while (majorTexts.length > num) {
-        var majorText = majorTexts[num];
-        frame.removeChild(majorText);
-        majorTexts.splice(num, 1);
-    }
-
-    var majorLines = dom.axis.majorLines;
-    num = props.majorLineNum;
-    while (majorLines.length > num) {
-        var majorLine = majorLines[num];
-        frame.removeChild(majorLine);
-        majorLines.splice(num, 1);
-    }
-};
-
-/**
- * Repaint the horizontal line and background of the axis
- */
-links.Timeline.prototype.repaintAxisHorizontal = function() {
-    var axis = this.dom.axis,
-        size = this.size,
-        options = this.options;
-
-    // line behind all axis elements (possibly having a background color)
-    var hasAxis = (options.showMinorLabels || options.showMajorLabels);
-    if (hasAxis) {
-        if (!axis.backgroundLine) {
-            // create the axis line background (for a background color or so)
-            var backgroundLine = document.createElement("DIV");
-            backgroundLine.className = "timeline-axis";
-            backgroundLine.style.position = "absolute";
-            backgroundLine.style.left = "0px";
-            backgroundLine.style.width = "100%";
-            backgroundLine.style.border = "none";
-            axis.frame.insertBefore(backgroundLine, axis.frame.firstChild);
-
-            axis.backgroundLine = backgroundLine;
-        }
-
-        if (axis.backgroundLine) {
-            axis.backgroundLine.style.top = size.axis.top + "px";
-            axis.backgroundLine.style.height = size.axis.height + "px";
-        }
-    }
-    else {
-        if (axis.backgroundLine) {
-            axis.frame.removeChild(axis.backgroundLine);
-            delete axis.backgroundLine;
-        }
-    }
-
-    // line before all axis elements
-    if (hasAxis) {
-        if (axis.line) {
-            // put this line at the end of all childs
-            var line = axis.frame.removeChild(axis.line);
-            axis.frame.appendChild(line);
-        }
-        else {
-            // make the axis line
-            var line = document.createElement("DIV");
-            line.className = "timeline-axis";
-            line.style.position = "absolute";
-            line.style.left = "0px";
-            line.style.width = "100%";
-            line.style.height = "0px";
-            axis.frame.appendChild(line);
-
-            axis.line = line;
-        }
-
-        axis.line.style.top = size.axis.line + "px";
-    }
-    else {
-        if (axis.line && axis.line.parentElement) {
-            axis.frame.removeChild(axis.line);
-            delete axis.line;
-        }
-    }
-};
-
-/**
- * Create a minor label for the axis at position x
- * @param {Number} x
- * @param {String} text
- */
-links.Timeline.prototype.repaintAxisMinorText = function (x, text) {
-    var size = this.size,
-        dom = this.dom,
-        props = size.axis.properties,
-        frame = dom.axis.frame,
-        minorTexts = dom.axis.minorTexts,
-        index = props.minorTextNum,
-        label;
-
-    if (index < minorTexts.length) {
-        label = minorTexts[index]
-    }
-    else {
-        // create new label
-        var content = document.createTextNode("");
-        label = document.createElement("DIV");
-        label.appendChild(content);
-        label.className = "timeline-axis-text timeline-axis-text-minor";
-        label.style.position = "absolute";
-
-        frame.appendChild(label);
-
-        minorTexts.push(label);
-    }
-
-    label.childNodes[0].nodeValue = text;
-    label.style.left = x + "px";
-    label.style.top  = size.axis.labelMinorTop + "px";
-    //label.title = title;  // TODO: this is a heavy operation
-
-    props.minorTextNum++;
-};
-
-/**
- * Create a minor line for the axis at position x
- * @param {Number} x
- */
-links.Timeline.prototype.repaintAxisMinorLine = function (x) {
-    var axis = this.size.axis,
-        dom = this.dom,
-        props = axis.properties,
-        frame = dom.axis.frame,
-        minorLines = dom.axis.minorLines,
-        index = props.minorLineNum,
-        line;
-
-    if (index < minorLines.length) {
-        line = minorLines[index];
-    }
-    else {
-        // create vertical line
-        line = document.createElement("DIV");
-        line.className = "timeline-axis-grid timeline-axis-grid-minor";
-        line.style.position = "absolute";
-        line.style.width = "0px";
-
-        frame.appendChild(line);
-        minorLines.push(line);
-    }
-
-    line.style.top = axis.lineMinorTop + "px";
-    line.style.height = axis.lineMinorHeight + "px";
-    line.style.left = (x - axis.lineMinorWidth/2) + "px";
-
-    props.minorLineNum++;
-};
-
-/**
- * Create a Major label for the axis at position x
- * @param {Number} x
- * @param {String} text
- */
-links.Timeline.prototype.repaintAxisMajorText = function (x, text) {
-    var size = this.size,
-        props = size.axis.properties,
-        frame = this.dom.axis.frame,
-        majorTexts = this.dom.axis.majorTexts,
-        index = props.majorTextNum,
-        label;
-
-    if (index < majorTexts.length) {
-        label = majorTexts[index];
-    }
-    else {
-        // create label
-        var content = document.createTextNode(text);
-        label = document.createElement("DIV");
-        label.className = "timeline-axis-text timeline-axis-text-major";
-        label.appendChild(content);
-        label.style.position = "absolute";
-        label.style.top = "0px";
-
-        frame.appendChild(label);
-        majorTexts.push(label);
-    }
-
-    label.childNodes[0].nodeValue = text;
-    label.style.top = size.axis.labelMajorTop + "px";
-    label.style.left = x + "px";
-    //label.title = title; // TODO: this is a heavy operation
-
-    props.majorTextNum ++;
-};
-
-/**
- * Create a Major line for the axis at position x
- * @param {Number} x
- */
-links.Timeline.prototype.repaintAxisMajorLine = function (x) {
-    var size = this.size,
-        props = size.axis.properties,
-        axis = this.size.axis,
-        frame = this.dom.axis.frame,
-        majorLines = this.dom.axis.majorLines,
-        index = props.majorLineNum,
-        line;
-
-    if (index < majorLines.length) {
-        line = majorLines[index];
-    }
-    else {
-        // create vertical line
-        line = document.createElement("DIV");
-        line.className = "timeline-axis-grid timeline-axis-grid-major";
-        line.style.position = "absolute";
-        line.style.top = "0px";
-        line.style.width = "0px";
-
-        frame.appendChild(line);
-        majorLines.push(line);
-    }
-
-    line.style.left = (x - axis.lineMajorWidth/2) + "px";
-    line.style.height = size.frameHeight + "px";
-
-    props.majorLineNum ++;
-};
-
-/**
- * Reflow all items, retrieve their actual size
- * @return {boolean} resized    returns true if any of the items is resized
- */
-links.Timeline.prototype.reflowItems = function() {
-    var resized = false,
-        i,
-        iMax,
-        group,
-        groups = this.groups,
-        renderedItems = this.renderedItems;
-
-    if (groups) { // TODO: need to check if labels exists?
-        // loop through all groups to reset the items height
-        groups.forEach(function (group) {
-            group.itemsHeight = 0;
-        });
-    }
-
-    // loop through the width and height of all visible items
-    for (i = 0, iMax = renderedItems.length; i < iMax; i++) {
-        var item = renderedItems[i],
-            domItem = item.dom;
-        group = item.group;
-
-        if (domItem) {
-            // TODO: move updating width and height into item.reflow
-            var width = domItem ? domItem.clientWidth : 0;
-            var height = domItem ? domItem.clientHeight : 0;
-            resized = resized || (item.width != width);
-            resized = resized || (item.height != height);
-            item.width = width;
-            item.height = height;
-            //item.borderWidth = (domItem.offsetWidth - domItem.clientWidth - 2) / 2; // TODO: borderWidth
-            item.reflow();
-        }
-
-        if (group) {
-            group.itemsHeight = group.itemsHeight ?
-                Math.max(group.itemsHeight, item.height) :
-                item.height;
-        }
-    }
-
-    return resized;
-};
-
-/**
- * Recalculate item properties:
- * - the height of each group.
- * - the actualHeight, from the stacked items or the sum of the group heights
- * @return {boolean} resized    returns true if any of the items properties is
- *                              changed
- */
-links.Timeline.prototype.recalcItems = function () {
-    var resized = false,
-        i,
-        iMax,
-        item,
-        finalItem,
-        finalItems,
-        group,
-        groups = this.groups,
-        size = this.size,
-        options = this.options,
-        renderedItems = this.renderedItems;
-
-    var actualHeight = 0;
-    if (groups.length == 0) {
-        // calculate actual height of the timeline when there are no groups
-        // but stacked items
-        if (options.autoHeight || options.cluster) {
-            var min = 0,
-                max = 0;
-
-            if (this.stack && this.stack.finalItems) {
-                // adjust the offset of all finalItems when the actualHeight has been changed
-                finalItems = this.stack.finalItems;
-                finalItem = finalItems[0];
-                if (finalItem && finalItem.top) {
-                    min = finalItem.top;
-                    max = finalItem.top + finalItem.height;
-                }
-                for (i = 1, iMax = finalItems.length; i < iMax; i++) {
-                    finalItem = finalItems[i];
-                    min = Math.min(min, finalItem.top);
-                    max = Math.max(max, finalItem.top + finalItem.height);
-                }
-            }
-            else {
-                item = renderedItems[0];
-                if (item && item.top) {
-                    min = item.top;
-                    max = item.top + item.height;
-                }
-                for (i = 1, iMax = renderedItems.length; i < iMax; i++) {
-                    item = renderedItems[i];
-                    if (item.top) {
-                        min = Math.min(min, item.top);
-                        max = Math.max(max, (item.top + item.height));
-                    }
-                }
-            }
-
-            actualHeight = (max - min) + 2 * options.eventMarginAxis + size.axis.height;
-            if (actualHeight < options.minHeight) {
-                actualHeight = options.minHeight;
-            }
-
-            if (size.actualHeight != actualHeight && options.autoHeight && !options.axisOnTop) {
-                // adjust the offset of all items when the actualHeight has been changed
-                var diff = actualHeight - size.actualHeight;
-                if (this.stack && this.stack.finalItems) {
-                    finalItems = this.stack.finalItems;
-                    for (i = 0, iMax = finalItems.length; i < iMax; i++) {
-                        finalItems[i].top += diff;
-                        finalItems[i].item.top += diff;
-                    }
-                }
-                else {
-                    for (i = 0, iMax = renderedItems.length; i < iMax; i++) {
-                        renderedItems[i].top += diff;
-                    }
-                }
-            }
-        }
-    }
-    else {
-        // loop through all groups to get the height of each group, and the
-        // total height
-        actualHeight = size.axis.height + 2 * options.eventMarginAxis;
-        for (i = 0, iMax = groups.length; i < iMax; i++) {
-            group = groups[i];
-
-            var groupHeight = Math.max(group.labelHeight || 0, group.itemsHeight || 0);
-            resized = resized || (groupHeight != group.height);
-            group.height = groupHeight;
-
-            actualHeight += groups[i].height + options.eventMargin;
-        }
-
-        // calculate top positions of the group labels and lines
-        var eventMargin = options.eventMargin,
-            top = options.axisOnTop ?
-                options.eventMarginAxis + eventMargin/2 :
-                size.contentHeight - options.eventMarginAxis + eventMargin/ 2,
-            axisHeight = size.axis.height;
-
-        for (i = 0, iMax = groups.length; i < iMax; i++) {
-            group = groups[i];
-            if (options.axisOnTop) {
-                group.top = top + axisHeight;
-                group.labelTop = top + axisHeight + (group.height - group.labelHeight) / 2;
-                group.lineTop = top + axisHeight + group.height + eventMargin/2;
-                top += group.height + eventMargin;
-            }
-            else {
-                top -= group.height + eventMargin;
-                group.top = top;
-                group.labelTop = top + (group.height - group.labelHeight) / 2;
-                group.lineTop = top - eventMargin/2;
-            }
-        }
-
-        // calculate top position of the visible items
-        for (i = 0, iMax = renderedItems.length; i < iMax; i++) {
-            item = renderedItems[i];
-            group = item.group;
-
-            if (group) {
-                item.top = group.top;
-            }
-        }
-
-        resized = true;
-    }
-
-    if (actualHeight < options.minHeight) {
-        actualHeight = options.minHeight;
-    }
-    resized = resized || (actualHeight != size.actualHeight);
-    size.actualHeight = actualHeight;
-
-    return resized;
-};
-
-/**
- * This method clears the (internal) array this.items in a safe way: neatly
- * cleaning up the DOM, and accompanying arrays this.renderedItems and
- * the created clusters.
- */
-links.Timeline.prototype.clearItems = function() {
-    // add all visible items to the list to be hidden
-    var hideItems = this.renderQueue.hide;
-    this.renderedItems.forEach(function (item) {
-        hideItems.push(item);
-    });
-
-    // clear the cluster generator
-    this.clusterGenerator.clear();
-
-    // actually clear the items
-    this.items = [];
-};
-
-/**
- * Repaint all items
- * @return {boolean} needsReflow   Returns true if the DOM is changed such that
- *                                 a reflow is needed.
- */
-links.Timeline.prototype.repaintItems = function() {
-    var i, iMax, item, index;
-
-    var needsReflow = false,
-        dom = this.dom,
-        size = this.size,
-        timeline = this,
-        renderedItems = this.renderedItems;
-
-    if (!dom.items) {
-        dom.items = {};
-    }
-
-    // draw the frame containing the items
-    var frame = dom.items.frame;
-    if (!frame) {
-        frame = document.createElement("DIV");
-        frame.style.position = "relative";
-        dom.content.appendChild(frame);
-        dom.items.frame = frame;
-    }
-
-    frame.style.left = "0px";
-    frame.style.top = size.items.top + "px";
-    frame.style.height = "0px";
-
-    // Take frame offline (for faster manipulation of the DOM)
-    dom.content.removeChild(frame);
-
-    // process the render queue with changes
-    var queue = this.renderQueue;
-    var newImageUrls = [];
-    needsReflow = needsReflow ||
-        (queue.show.length > 0) ||
-        (queue.update.length > 0) ||
-        (queue.hide.length > 0);   // TODO: reflow needed on hide of items?
-
-    while (item = queue.show.shift()) {
-        item.showDOM(frame);
-        item.getImageUrls(newImageUrls);
-        renderedItems.push(item);
-    }
-    while (item = queue.update.shift()) {
-        item.updateDOM(frame);
-        item.getImageUrls(newImageUrls);
-        index = this.renderedItems.indexOf(item);
-        if (index == -1) {
-            renderedItems.push(item);
-        }
-    }
-    while (item = queue.hide.shift()) {
-        item.hideDOM(frame);
-        index = this.renderedItems.indexOf(item);
-        if (index != -1) {
-            renderedItems.splice(index, 1);
-        }
-    }
-
-    // reposition all visible items
-    renderedItems.forEach(function (item) {
-        item.updatePosition(timeline);
-    });
-
-    // redraw the delete button and dragareas of the selected item (if any)
-    this.repaintDeleteButton();
-    this.repaintDragAreas();
-
-    // put frame online again
-    dom.content.appendChild(frame);
-
-    if (newImageUrls.length) {
-        // retrieve all image sources from the items, and set a callback once
-        // all images are retrieved
-        var callback = function () {
-            timeline.render();
-        };
-        var sendCallbackWhenAlreadyLoaded = false;
-        links.imageloader.loadAll(newImageUrls, callback, sendCallbackWhenAlreadyLoaded);
-    }
-
-    return needsReflow;
-};
-
-/**
- * Reflow the size of the groups
- * @return {boolean} resized    Returns true if any of the frame elements
- *                              have been resized.
- */
-links.Timeline.prototype.reflowGroups = function() {
-    var resized = false,
-        options = this.options,
-        size = this.size,
-        dom = this.dom;
-
-    // calculate the groups width and height
-    // TODO: only update when data is changed! -> use an updateSeq
-    var groupsWidth = 0;
-
-    // loop through all groups to get the labels width and height
-    var groups = this.groups;
-    var labels = this.dom.groups ? this.dom.groups.labels : [];
-    for (var i = 0, iMax = groups.length; i < iMax; i++) {
-        var group = groups[i];
-        var label = labels[i];
-        group.labelWidth  = label ? label.clientWidth : 0;
-        group.labelHeight = label ? label.clientHeight : 0;
-        group.width = group.labelWidth;  // TODO: group.width is redundant with labelWidth
-
-        groupsWidth = Math.max(groupsWidth, group.width);
-    }
-
-    // limit groupsWidth to the groups width in the options
-    if (options.groupsWidth !== undefined) {
-        groupsWidth = dom.groups.frame ? dom.groups.frame.clientWidth : 0;
-    }
-
-    // compensate for the border width. TODO: calculate the real border width
-    groupsWidth += 1;
-
-    var groupsLeft = options.groupsOnRight ? size.frameWidth - groupsWidth : 0;
-    resized = resized || (size.groupsWidth !== groupsWidth);
-    resized = resized || (size.groupsLeft !== groupsLeft);
-    size.groupsWidth = groupsWidth;
-    size.groupsLeft = groupsLeft;
-
-    return resized;
-};
-
-/**
- * Redraw the group labels
- */
-links.Timeline.prototype.repaintGroups = function() {
-    var dom = this.dom,
-        timeline = this,
-        options = this.options,
-        size = this.size,
-        groups = this.groups;
-
-    if (dom.groups === undefined) {
-        dom.groups = {};
-    }
-
-    var labels = dom.groups.labels;
-    if (!labels) {
-        labels = [];
-        dom.groups.labels = labels;
-    }
-    var labelLines = dom.groups.labelLines;
-    if (!labelLines) {
-        labelLines = [];
-        dom.groups.labelLines = labelLines;
-    }
-    var itemLines = dom.groups.itemLines;
-    if (!itemLines) {
-        itemLines = [];
-        dom.groups.itemLines = itemLines;
-    }
-
-    // create the frame for holding the groups
-    var frame = dom.groups.frame;
-    if (!frame) {
-        frame =  document.createElement("DIV");
-        frame.className = "timeline-groups-axis";
-        frame.style.position = "absolute";
-        frame.style.overflow = "hidden";
-        frame.style.top = "0px";
-        frame.style.height = "100%";
-
-        dom.frame.appendChild(frame);
-        dom.groups.frame = frame;
-    }
-
-    frame.style.left = size.groupsLeft + "px";
-    frame.style.width = (options.groupsWidth !== undefined) ?
-        options.groupsWidth :
-        size.groupsWidth + "px";
-
-    // hide groups axis when there are no groups
-    if (groups.length == 0) {
-        frame.style.display = 'none';
-    }
-    else {
-        frame.style.display = '';
-    }
-
-    // TODO: only create/update groups when data is changed.
-
-    // create the items
-    var current = labels.length,
-        needed = groups.length;
-
-    // overwrite existing group labels
-    for (var i = 0, iMax = Math.min(current, needed); i < iMax; i++) {
-        var group = groups[i];
-        var label = labels[i];
-        label.innerHTML = this.getGroupName(group);
-        label.style.display = '';
-    }
-
-    // append new items when needed
-    for (var i = current; i < needed; i++) {
-        var group = groups[i];
-
-        // create text label
-        var label = document.createElement("DIV");
-        label.className = "timeline-groups-text";
-        label.style.position = "absolute";
-        if (options.groupsWidth === undefined) {
-            label.style.whiteSpace = "nowrap";
-        }
-        label.innerHTML = this.getGroupName(group);
-        frame.appendChild(label);
-        labels[i] = label;
-
-        // create the grid line between the group labels
-        var labelLine = document.createElement("DIV");
-        labelLine.className = "timeline-axis-grid timeline-axis-grid-minor";
-        labelLine.style.position = "absolute";
-        labelLine.style.left = "0px";
-        labelLine.style.width = "100%";
-        labelLine.style.height = "0px";
-        labelLine.style.borderTopStyle = "solid";
-        frame.appendChild(labelLine);
-        labelLines[i] = labelLine;
-
-        // create the grid line between the items
-        var itemLine = document.createElement("DIV");
-        itemLine.className = "timeline-axis-grid timeline-axis-grid-minor";
-        itemLine.style.position = "absolute";
-        itemLine.style.left = "0px";
-        itemLine.style.width = "100%";
-        itemLine.style.height = "0px";
-        itemLine.style.borderTopStyle = "solid";
-        dom.content.insertBefore(itemLine, dom.content.firstChild);
-        itemLines[i] = itemLine;
-    }
-
-    // remove redundant items from the DOM when needed
-    for (var i = needed; i < current; i++) {
-        var label = labels[i],
-            labelLine = labelLines[i],
-            itemLine = itemLines[i];
-
-        frame.removeChild(label);
-        frame.removeChild(labelLine);
-        dom.content.removeChild(itemLine);
-    }
-    labels.splice(needed, current - needed);
-    labelLines.splice(needed, current - needed);
-    itemLines.splice(needed, current - needed);
-    
-    links.Timeline.addClassName(frame, options.groupsOnRight ? 'timeline-groups-axis-onright' : 'timeline-groups-axis-onleft');
-
-    // position the groups
-    for (var i = 0, iMax = groups.length; i < iMax; i++) {
-        var group = groups[i],
-            label = labels[i],
-            labelLine = labelLines[i],
-            itemLine = itemLines[i];
-
-        label.style.top = group.labelTop + "px";
-        labelLine.style.top = group.lineTop + "px";
-        itemLine.style.top = group.lineTop + "px";
-        itemLine.style.width = size.contentWidth + "px";
-    }
-
-    if (!dom.groups.background) {
-        // create the axis grid line background
-        var background = document.createElement("DIV");
-        background.className = "timeline-axis";
-        background.style.position = "absolute";
-        background.style.left = "0px";
-        background.style.width = "100%";
-        background.style.border = "none";
-
-        frame.appendChild(background);
-        dom.groups.background = background;
-    }
-    dom.groups.background.style.top = size.axis.top + 'px';
-    dom.groups.background.style.height = size.axis.height + 'px';
-
-    if (!dom.groups.line) {
-        // create the axis grid line
-        var line = document.createElement("DIV");
-        line.className = "timeline-axis";
-        line.style.position = "absolute";
-        line.style.left = "0px";
-        line.style.width = "100%";
-        line.style.height = "0px";
-
-        frame.appendChild(line);
-        dom.groups.line = line;
-    }
-    dom.groups.line.style.top = size.axis.line + 'px';
-
-    // create a callback when there are images which are not yet loaded
-    // TODO: more efficiently load images in the groups
-    if (dom.groups.frame && groups.length) {
-        var imageUrls = [];
-        links.imageloader.filterImageUrls(dom.groups.frame, imageUrls);
-        if (imageUrls.length) {
-            // retrieve all image sources from the items, and set a callback once
-            // all images are retrieved
-            var callback = function () {
-                timeline.render();
-            };
-            var sendCallbackWhenAlreadyLoaded = false;
-            links.imageloader.loadAll(imageUrls, callback, sendCallbackWhenAlreadyLoaded);
-        }
-    }
-};
-
-
-/**
- * Redraw the current time bar
- */
-links.Timeline.prototype.repaintCurrentTime = function() {
-    var options = this.options,
-        dom = this.dom,
-        size = this.size;
-
-    if (!options.showCurrentTime) {
-        if (dom.currentTime) {
-            dom.contentTimelines.removeChild(dom.currentTime);
-            delete dom.currentTime;
-        }
-
-        return;
-    }
-
-    if (!dom.currentTime) {
-        // create the current time bar
-        var currentTime = document.createElement("DIV");
-        currentTime.className = "timeline-currenttime";
-        currentTime.style.position = "absolute";
-        currentTime.style.top = "0px";
-        currentTime.style.height = "100%";
-
-        dom.contentTimelines.appendChild(currentTime);
-        dom.currentTime = currentTime;
-    }
-
-    var now = new Date();
-    var nowOffset = new Date(now.valueOf() + this.clientTimeOffset);
-    var x = this.timeToScreen(nowOffset);
-
-    var visible = (x > -size.contentWidth && x < 2 * size.contentWidth);
-    dom.currentTime.style.display = visible ? '' : 'none';
-    dom.currentTime.style.left = x + "px";
-    dom.currentTime.title = "Current time: " + nowOffset;
-
-    // start a timer to adjust for the new time
-    if (this.currentTimeTimer != undefined) {
-        clearTimeout(this.currentTimeTimer);
-        delete this.currentTimeTimer;
-    }
-    var timeline = this;
-    var onTimeout = function() {
-        timeline.repaintCurrentTime();
-    };
-    // the time equal to the width of one pixel, divided by 2 for more smoothness
-    var interval = 1 / this.conversion.factor / 2;
-    if (interval < 30) interval = 30;
-    this.currentTimeTimer = setTimeout(onTimeout, interval);
-};
-
-/**
- * Redraw the custom time bar
- */
-links.Timeline.prototype.repaintCustomTime = function() {
-    var options = this.options,
-        dom = this.dom,
-        size = this.size;
-
-    if (!options.showCustomTime) {
-        if (dom.customTime) {
-            dom.contentTimelines.removeChild(dom.customTime);
-            delete dom.customTime;
-        }
-
-        return;
-    }
-
-    if (!dom.customTime) {
-        var customTime = document.createElement("DIV");
-        customTime.className = "timeline-customtime";
-        customTime.style.position = "absolute";
-        customTime.style.top = "0px";
-        customTime.style.height = "100%";
-
-        var drag = document.createElement("DIV");
-        drag.style.position = "relative";
-        drag.style.top = "0px";
-        drag.style.left = "-10px";
-        drag.style.height = "100%";
-        drag.style.width = "20px";
-        customTime.appendChild(drag);
-
-        dom.contentTimelines.appendChild(customTime);
-        dom.customTime = customTime;
-
-        // initialize parameter
-        this.customTime = new Date();
-    }
-
-    var x = this.timeToScreen(this.customTime),
-        visible = (x > -size.contentWidth && x < 2 * size.contentWidth);
-    dom.customTime.style.display = visible ? '' : 'none';
-    dom.customTime.style.left = x + "px";
-    dom.customTime.title = "Time: " + this.customTime;
-};
-
-
-/**
- * Redraw the delete button, on the top right of the currently selected item
- * if there is no item selected, the button is hidden.
- */
-links.Timeline.prototype.repaintDeleteButton = function () {
-    var timeline = this,
-        dom = this.dom,
-        frame = dom.items.frame;
-
-    var deleteButton = dom.items.deleteButton;
-    if (!deleteButton) {
-        // create a delete button
-        deleteButton = document.createElement("DIV");
-        deleteButton.className = "timeline-navigation-delete";
-        deleteButton.style.position = "absolute";
-
-        frame.appendChild(deleteButton);
-        dom.items.deleteButton = deleteButton;
-    }
-
-    var index = this.selection ? this.selection.index : -1,
-        item = this.selection ? this.items[index] : undefined;
-    if (item && item.rendered && this.isEditable(item)) {
-        var right = item.getRight(this),
-            top = item.top;
-
-        deleteButton.style.left = right + 'px';
-        deleteButton.style.top = top + 'px';
-        deleteButton.style.display = '';
-        frame.removeChild(deleteButton);
-        frame.appendChild(deleteButton);
-    }
-    else {
-        deleteButton.style.display = 'none';
-    }
-};
-
-
-/**
- * Redraw the drag areas. When an item (ranges only) is selected,
- * it gets a drag area on the left and right side, to change its width
- */
-links.Timeline.prototype.repaintDragAreas = function () {
-    var timeline = this,
-        options = this.options,
-        dom = this.dom,
-        frame = this.dom.items.frame;
-
-    // create left drag area
-    var dragLeft = dom.items.dragLeft;
-    if (!dragLeft) {
-        dragLeft = document.createElement("DIV");
-        dragLeft.className="timeline-event-range-drag-left";
-        dragLeft.style.position = "absolute";
-
-        frame.appendChild(dragLeft);
-        dom.items.dragLeft = dragLeft;
-    }
-
-    // create right drag area
-    var dragRight = dom.items.dragRight;
-    if (!dragRight) {
-        dragRight = document.createElement("DIV");
-        dragRight.className="timeline-event-range-drag-right";
-        dragRight.style.position = "absolute";
-
-        frame.appendChild(dragRight);
-        dom.items.dragRight = dragRight;
-    }
-
-    // reposition left and right drag area
-    var index = this.selection ? this.selection.index : -1,
-        item = this.selection ? this.items[index] : undefined;
-    if (item && item.rendered && this.isEditable(item) &&
-        (item instanceof links.Timeline.ItemRange)) {
-        var left = this.timeToScreen(item.start),
-            right = this.timeToScreen(item.end),
-            top = item.top,
-            height = item.height;
-
-        dragLeft.style.left = left + 'px';
-        dragLeft.style.top = top + 'px';
-        dragLeft.style.width = options.dragAreaWidth + "px";
-        dragLeft.style.height = height + 'px';
-        dragLeft.style.display = '';
-        frame.removeChild(dragLeft);
-        frame.appendChild(dragLeft);
-
-        dragRight.style.left = (right - options.dragAreaWidth) + 'px';
-        dragRight.style.top = top + 'px';
-        dragRight.style.width = options.dragAreaWidth + "px";
-        dragRight.style.height = height + 'px';
-        dragRight.style.display = '';
-        frame.removeChild(dragRight);
-        frame.appendChild(dragRight);
-    }
-    else {
-        dragLeft.style.display = 'none';
-        dragRight.style.display = 'none';
-    }
-};
-
-/**
- * Create the navigation buttons for zooming and moving
- */
-links.Timeline.prototype.repaintNavigation = function () {
-    var timeline = this,
-        options = this.options,
-        dom = this.dom,
-        frame = dom.frame,
-        navBar = dom.navBar;
-
-    if (!navBar) {
-        var showButtonNew = options.showButtonNew && options.editable;
-        var showNavigation = options.showNavigation && (options.zoomable || options.moveable);
-        if (showNavigation || showButtonNew) {
-            // create a navigation bar containing the navigation buttons
-            navBar = document.createElement("DIV");
-            navBar.style.position = "absolute";
-            navBar.className = "timeline-navigation ui-widget ui-state-highlight ui-corner-all";
-            if (options.groupsOnRight) {
-                navBar.style.left = '10px';
-            }
-            else {
-                navBar.style.right = '10px';
-            }
-            if (options.axisOnTop) {
-                navBar.style.bottom = '10px';
-            }
-            else {
-                navBar.style.top = '10px';
-            }
-            dom.navBar = navBar;
-            frame.appendChild(navBar);
-        }
-
-        if (showButtonNew) {
-            // create a new in button
-            navBar.addButton = document.createElement("DIV");
-            navBar.addButton.className = "timeline-navigation-new";
-            navBar.addButton.title = options.CREATE_NEW_EVENT;
-            var addIconSpan = document.createElement("SPAN");
-            addIconSpan.className = "ui-icon ui-icon-circle-plus";            
-            navBar.addButton.appendChild(addIconSpan);
-            
-            var onAdd = function(event) {
-                links.Timeline.preventDefault(event);
-                links.Timeline.stopPropagation(event);
-
-                // create a new event at the center of the frame
-                var w = timeline.size.contentWidth;
-                var x = w / 2;
-                var xstart = timeline.screenToTime(x - w / 10); // subtract 10% of timeline width
-                var xend = timeline.screenToTime(x + w / 10);   // add 10% of timeline width
-                if (options.snapEvents) {
-                    timeline.step.snap(xstart);
-                    timeline.step.snap(xend);
-                }
-
-                var content = options.NEW;
-                var group = timeline.groups.length ? timeline.groups[0].content : undefined;
-                var preventRender = true;
-                timeline.addItem({
-                    'start': xstart,
-                    'end': xend,
-                    'content': content,
-                    'group': group
-                }, preventRender);
-                var index = (timeline.items.length - 1);
-                timeline.selectItem(index);
-
-                timeline.applyAdd = true;
-
-                // fire an add event.
-                // Note that the change can be canceled from within an event listener if
-                // this listener calls the method cancelAdd().
-                timeline.trigger('add');
-
-                if (timeline.applyAdd) {
-                    // render and select the item
-                    timeline.render({animate: false});
-                    timeline.selectItem(index);
-                }
-                else {
-                    // undo an add
-                    timeline.deleteItem(index);
-                }
-            };
-            links.Timeline.addEventListener(navBar.addButton, "mousedown", onAdd);
-            navBar.appendChild(navBar.addButton);
-        }
-
-        if (showButtonNew && showNavigation) {
-            // create a separator line
-            links.Timeline.addClassName(navBar.addButton, 'timeline-navigation-new-line');
-        }
-
-        if (showNavigation) {
-            if (options.zoomable) {
-                // create a zoom in button
-                navBar.zoomInButton = document.createElement("DIV");
-                navBar.zoomInButton.className = "timeline-navigation-zoom-in";
-                navBar.zoomInButton.title = this.options.ZOOM_IN;
-                var ziIconSpan = document.createElement("SPAN");
-                ziIconSpan.className = "ui-icon ui-icon-circle-zoomin";
-                navBar.zoomInButton.appendChild(ziIconSpan);
-                
-                var onZoomIn = function(event) {
-                    links.Timeline.preventDefault(event);
-                    links.Timeline.stopPropagation(event);
-                    timeline.zoom(0.4);
-                    timeline.trigger("rangechange");
-                    timeline.trigger("rangechanged");
-                };
-                links.Timeline.addEventListener(navBar.zoomInButton, "mousedown", onZoomIn);
-                navBar.appendChild(navBar.zoomInButton);
-
-                // create a zoom out button
-                navBar.zoomOutButton = document.createElement("DIV");
-                navBar.zoomOutButton.className = "timeline-navigation-zoom-out";
-                navBar.zoomOutButton.title = this.options.ZOOM_OUT;
-                var zoIconSpan = document.createElement("SPAN");
-                zoIconSpan.className = "ui-icon ui-icon-circle-zoomout";
-                navBar.zoomOutButton.appendChild(zoIconSpan);
-                
-                var onZoomOut = function(event) {
-                    links.Timeline.preventDefault(event);
-                    links.Timeline.stopPropagation(event);
-                    timeline.zoom(-0.4);
-                    timeline.trigger("rangechange");
-                    timeline.trigger("rangechanged");
-                };
-                links.Timeline.addEventListener(navBar.zoomOutButton, "mousedown", onZoomOut);
-                navBar.appendChild(navBar.zoomOutButton);
-            }
-
-            if (options.moveable) {
-                // create a move left button
-                navBar.moveLeftButton = document.createElement("DIV");
-                navBar.moveLeftButton.className = "timeline-navigation-move-left";
-                navBar.moveLeftButton.title = this.options.MOVE_LEFT;
-                var mlIconSpan = document.createElement("SPAN");
-                mlIconSpan.className = "ui-icon ui-icon-circle-arrow-w";
-                navBar.moveLeftButton.appendChild(mlIconSpan);
-                
-                var onMoveLeft = function(event) {
-                    links.Timeline.preventDefault(event);
-                    links.Timeline.stopPropagation(event);
-                    timeline.move(-0.2);
-                    timeline.trigger("rangechange");
-                    timeline.trigger("rangechanged");
-                };
-                links.Timeline.addEventListener(navBar.moveLeftButton, "mousedown", onMoveLeft);
-                navBar.appendChild(navBar.moveLeftButton);
-
-                // create a move right button
-                navBar.moveRightButton = document.createElement("DIV");
-                navBar.moveRightButton.className = "timeline-navigation-move-right";
-                navBar.moveRightButton.title = this.options.MOVE_RIGHT;
-                var mrIconSpan = document.createElement("SPAN");
-                mrIconSpan.className = "ui-icon ui-icon-circle-arrow-e";
-                navBar.moveRightButton.appendChild(mrIconSpan);
-                
-                var onMoveRight = function(event) {
-                    links.Timeline.preventDefault(event);
-                    links.Timeline.stopPropagation(event);
-                    timeline.move(0.2);
-                    timeline.trigger("rangechange");
-                    timeline.trigger("rangechanged");
-                };
-                links.Timeline.addEventListener(navBar.moveRightButton, "mousedown", onMoveRight);
-                navBar.appendChild(navBar.moveRightButton);
-            }
-        }
-    }
-};
-
-
-/**
- * Set current time. This function can be used to set the time in the client
- * timeline equal with the time on a server.
- * @param {Date} time
- */
-links.Timeline.prototype.setCurrentTime = function(time) {
-    var now = new Date();
-    this.clientTimeOffset = (time.valueOf() - now.valueOf());
-
-    this.repaintCurrentTime();
-};
-
-/**
- * Get current time. The time can have an offset from the real time, when
- * the current time has been changed via the method setCurrentTime.
- * @return {Date} time
- */
-links.Timeline.prototype.getCurrentTime = function() {
-    var now = new Date();
-    return new Date(now.valueOf() + this.clientTimeOffset);
-};
-
-
-/**
- * Set custom time.
- * The custom time bar can be used to display events in past or future.
- * @param {Date} time
- */
-links.Timeline.prototype.setCustomTime = function(time) {
-    this.customTime = new Date(time.valueOf());
-    this.repaintCustomTime();
-};
-
-/**
- * Retrieve the current custom time.
- * @return {Date} customTime
- */
-links.Timeline.prototype.getCustomTime = function() {
-    return new Date(this.customTime.valueOf());
-};
-
-/**
- * Set a custom scale. Autoscaling will be disabled.
- * For example setScale(SCALE.MINUTES, 5) will result
- * in minor steps of 5 minutes, and major steps of an hour.
- *
- * @param {links.Timeline.StepDate.SCALE} scale
- *                               A scale. Choose from SCALE.MILLISECOND,
- *                               SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR,
- *                               SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH,
- *                               SCALE.YEAR.
- * @param {int}        step   A step size, by default 1. Choose for
- *                               example 1, 2, 5, or 10.
- */
-links.Timeline.prototype.setScale = function(scale, step) {
-    this.step.setScale(scale, step);
-    this.render(); // TODO: optimize: only reflow/repaint axis
-};
-
-/**
- * Enable or disable autoscaling
- * @param {boolean} enable  If true or not defined, autoscaling is enabled.
- *                          If false, autoscaling is disabled.
- */
-links.Timeline.prototype.setAutoScale = function(enable) {
-    this.step.setAutoScale(enable);
-    this.render(); // TODO: optimize: only reflow/repaint axis
-};
-
-/**
- * Redraw the timeline
- * Reloads the (linked) data table and redraws the timeline when resized.
- * See also the method checkResize
- */
-links.Timeline.prototype.redraw = function() {
-    this.setData(this.data);
-};
-
-
-/**
- * Check if the timeline is resized, and if so, redraw the timeline.
- * Useful when the webpage is resized.
- */
-links.Timeline.prototype.checkResize = function() {
-    // TODO: re-implement the method checkResize, or better, make it redundant as this.render will be smarter
-    this.render();
-};
-
-/**
- * Check whether a given item is editable
- * @param {links.Timeline.Item} item
- * @return {boolean} editable
- */
-links.Timeline.prototype.isEditable = function (item) {
-    if (item) {
-        if (item.editable != undefined) {
-            return item.editable;
-        }
-        else {
-            return this.options.editable;
-        }
-    }
-    return false;
-};
-
-/**
- * Calculate the factor and offset to convert a position on screen to the
- * corresponding date and vice versa.
- * After the method calcConversionFactor is executed once, the methods screenToTime and
- * timeToScreen can be used.
- */
-links.Timeline.prototype.recalcConversion = function() {
-    this.conversion.offset = this.start.valueOf();
-    this.conversion.factor = this.size.contentWidth /
-        (this.end.valueOf() - this.start.valueOf());
-};
-
-
-/**
- * Convert a position on screen (pixels) to a datetime
- * Before this method can be used, the method calcConversionFactor must be
- * executed once.
- * @param {int}     x    Position on the screen in pixels
- * @return {Date}   time The datetime the corresponds with given position x
- */
-links.Timeline.prototype.screenToTime = function(x) {
-    var conversion = this.conversion;
-    return new Date(x / conversion.factor + conversion.offset);
-};
-
-/**
- * Convert a datetime (Date object) into a position on the screen
- * Before this method can be used, the method calcConversionFactor must be
- * executed once.
- * @param {Date}   time A date
- * @return {int}   x    The position on the screen in pixels which corresponds
- *                      with the given date.
- */
-links.Timeline.prototype.timeToScreen = function(time) {
-    var conversion = this.conversion;
-    return (time.valueOf() - conversion.offset) * conversion.factor;
-};
-
-
-
-/**
- * Event handler for touchstart event on mobile devices
- */
-links.Timeline.prototype.onTouchStart = function(event) {
-    var params = this.eventParams,
-        me = this;
-
-    if (params.touchDown) {
-        // if already moving, return
-        return;
-    }
-
-    params.touchDown = true;
-    params.zoomed = false;
-
-    this.onMouseDown(event);
-
-    if (!params.onTouchMove) {
-        params.onTouchMove = function (event) {me.onTouchMove(event);};
-        links.Timeline.addEventListener(document, "touchmove", params.onTouchMove);
-    }
-    if (!params.onTouchEnd) {
-        params.onTouchEnd  = function (event) {me.onTouchEnd(event);};
-        links.Timeline.addEventListener(document, "touchend",  params.onTouchEnd);
-    }
-
-    /* TODO
-     // check for double tap event
-     var delta = 500; // ms
-     var doubleTapStart = (new Date()).valueOf();
-     var target = links.Timeline.getTarget(event);
-     var doubleTapItem = this.getItemIndex(target);
-     if (params.doubleTapStart &&
-     (doubleTapStart - params.doubleTapStart) < delta &&
-     doubleTapItem == params.doubleTapItem) {
-     delete params.doubleTapStart;
-     delete params.doubleTapItem;
-     me.onDblClick(event);
-     params.touchDown = false;
-     }
-     params.doubleTapStart = doubleTapStart;
-     params.doubleTapItem = doubleTapItem;
-     */
-    // store timing for double taps
-    var target = links.Timeline.getTarget(event);
-    var item = this.getItemIndex(target);
-    params.doubleTapStartPrev = params.doubleTapStart;
-    params.doubleTapStart = (new Date()).valueOf();
-    params.doubleTapItemPrev = params.doubleTapItem;
-    params.doubleTapItem = item;
-
-    links.Timeline.preventDefault(event);
-};
-
-/**
- * Event handler for touchmove event on mobile devices
- */
-links.Timeline.prototype.onTouchMove = function(event) {
-    var params = this.eventParams;
-
-    if (event.scale && event.scale !== 1) {
-        params.zoomed = true;
-    }
-
-    if (!params.zoomed) {
-        // move 
-        this.onMouseMove(event);
-    }
-    else {
-        if (this.options.zoomable) {
-            // pinch
-            // TODO: pinch only supported on iPhone/iPad. Create something manually for Android?
-            params.zoomed = true;
-
-            var scale = event.scale,
-                oldWidth = (params.end.valueOf() - params.start.valueOf()),
-                newWidth = oldWidth / scale,
-                diff = newWidth - oldWidth,
-                start = new Date(parseInt(params.start.valueOf() - diff/2)),
-                end = new Date(parseInt(params.end.valueOf() + diff/2));
-
-            // TODO: determine zoom-around-date from touch positions?
-
-            this.setVisibleChartRange(start, end);
-            this.trigger("rangechange");
-        }
-    }
-
-    links.Timeline.preventDefault(event);
-};
-
-/**
- * Event handler for touchend event on mobile devices
- */
-links.Timeline.prototype.onTouchEnd = function(event) {
-    var params = this.eventParams;
-    var me = this;
-    params.touchDown = fal

<TRUNCATED>