You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@climate.apache.org by jo...@apache.org on 2014/07/01 16:49:42 UTC
[18/56] [partial] gh-pages clean up
http://git-wip-us.apache.org/repos/asf/climate/blob/a53e3af5/ocw-ui/frontend/app/js/lib/timeline/timeline.js
----------------------------------------------------------------------
diff --git a/ocw-ui/frontend/app/js/lib/timeline/timeline.js b/ocw-ui/frontend/app/js/lib/timeline/timeline.js
deleted file mode 100644
index 0e009a2..0000000
--- a/ocw-ui/frontend/app/js/lib/timeline/timeline.js
+++ /dev/null
@@ -1,6381 +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-04-18
- * @version 2.4.2
- */
-
-/*
- * 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,
- '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);
-
- // 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
- * }
- * @param {google.visualization.DataTable} dataTable
- * @type {Object} map
- */
-links.Timeline.mapColumnIds = function (dataTable) {
- var cols = {},
- colMax = dataTable.getNumberOfColumns(),
- allUndefined = true;
-
- // loop over the columns, and map the column id's to the column indexes
- for (var col = 0; col < colMax; 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') {
- allUndefined = false;
- }
- }
-
- // if no labels or ids are defined,
- // use the default mapping for start, end, content
- if (allUndefined) {
- cols.start = 0;
- cols.end = 1;
- cols.content = 2;
- }
-
- 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)
- }));
- }
- }
- 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 (min != undefined && start != undefined) {
- min = Math.min(min.valueOf(), start.valueOf());
- }
- else {
- min = start;
- }
-
- if (max != undefined && end != undefined) {
- max = Math.max(max, end);
- }
- else {
- max = 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";
- 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);
-
- frame.style.borderStyle = options.groupsOnRight ?
- "none none none solid" :
- "none solid none none";
-
- // 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";
- 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 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
- navBar.addButton.style.borderRightWidth = "1px";
- navBar.addButton.style.borderRightStyle = "solid";
- }
-
- 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 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 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 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 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 = false;
-
- if (params.zoomed) {
- this.trigger("rangechanged");
- }
-
- if (params.onTouchMove) {
- links.Timeline.removeEventListener(document, "touchmove", params.onTouchMove);
- delete params.onTouchMove;
-
- }
- if (params.onTouchEnd) {
- links.Timeline.removeEventListener(document, "touchend", params.onTouchEnd);
- delete params.onTouchEnd;
- }
-
- this.onMouseUp(event);
-
- // check for double tap event
- var delta = 500; // ms
- var doubleTapEnd = (new Date()).valueOf();
- var target = links.Timeline.getTarget(event);
- var doubleTapItem = this.getItemIndex(target);
- if (params.doubleTapStartPrev &&
- (doubleTapEnd - params.doubleTapStartPrev) < delta &&
- params.doubleTapItem == params.doubleTapItemPrev) {
- params.touchDown = true;
- me.onDblClick(event);
- params.touchDown = false;
- }
-
- links.Timeline.preventDefault(event);
-};
-
-
-/**
- * Start a moving operation inside the provided parent element
- * @param {Event} event The event that occurred (required for
- * retrieving the mouse position)
- */
-links.Timeline.prototype.onMouseDown = function(event) {
- event = event || window.event;
-
- var params = this.eventParams,
- options = this.options,
- dom = this.dom;
-
- // only react on left mouse button down
- var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1);
- if (!leftButtonDown && !params.touchDown) {
- return;
- }
-
- // get mouse position
- params.mouseX = links.Timeline.getPageX(event);
- params.mouseY = links.Ti
<TRUNCATED>