From c5d7a9a9a5ce28e71f27efe2f42c24dfd3b86aad Mon Sep 17 00:00:00 2001 From: josdejong Date: Fri, 26 Apr 2013 12:02:58 +0200 Subject: [PATCH] Isolated the local classes in a single namespace (using commonjs for referring local classes does not work that handy) --- Jakefile.js | 49 +- src/component/component.js | 5 - src/component/item/item.js | 5 - src/component/item/itembox.js | 6 - src/component/item/itempoint.js | 6 - src/component/item/itemrange.js | 6 - src/component/itemset.js | 11 - src/component/panel.js | 6 - src/component/rootpanel.js | 6 - src/component/timeaxis.js | 7 - src/controller.js | 7 - src/dataset.js | 5 - src/events.js | 3 - src/exports.js | 57 + src/imports.js | 4 + src/module.js | 33 - src/range.js | 6 - src/stack.js | 5 - src/timestep.js | 8 +- src/util.js | 27 +- src/vis.js | 31 - src/visualization/timeline.js | 12 - vis.js | 5405 +++++++++++++++---------------- vis.min.js | 8 +- 24 files changed, 2785 insertions(+), 2933 deletions(-) create mode 100644 src/exports.js create mode 100644 src/imports.js delete mode 100644 src/module.js delete mode 100644 src/vis.js diff --git a/Jakefile.js b/Jakefile.js index 296c2d5f..cd21b450 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -3,12 +3,14 @@ */ var jake = require('jake'), browserify = require('browserify'), - path = require('path'); + path = require('path'), + fs = require('fs'); require('jake-utils'); // constants var VIS = './vis.js'; +var VIS_TMP = './vis.js.tmp'; var VIS_MIN = './vis.min.js'; /** @@ -36,24 +38,57 @@ task('build', {async: true}, function () { }); var cssText = JSON.stringify(result.code); + // concatenate the script files + concat({ + dest: VIS_TMP, + src: [ + './src/imports.js', + + './src/util.js', + './src/events.js', + './src/timestep.js', + './src/dataset.js', + './src/stack.js', + './src/range.js', + './src/controller.js', + + './src/component/component.js', + './src/component/panel.js', + './src/component/rootpanel.js', + './src/component/timeaxis.js', + './src/component/itemset.js', + './src/component/item/*.js', + + './src/visualization/timeline.js', + + './src/exports.js' + ], + + separator: '\n', + + // Note: we insert the css as a string in the javascript code here + // the css will be injected on load of the javascript library + footer: '// inject css\n' + + 'util.loadCss(' + cssText + ');\n' + }); + // bundle the script files // TODO: do not package moment.js with vis.js. var b = browserify(); - b.add('./src/vis.js'); + b.add(VIS_TMP); b.bundle({ standalone: 'vis' }, function (err, code) { // add header and footer - var lib = - read('./src/header.js') + - code + - read('./src/module.js') + - '\nloadCss(' + cssText + ');\n'; // inline css + var lib = read('./src/header.js') + code; // write bundled file write(VIS, lib); console.log('created ' + VIS); + // remove temporary file + fs.unlinkSync(VIS_TMP); + // update version number and stuff in the javascript files replacePlaceholders(VIS); diff --git a/src/component/component.js b/src/component/component.js index 85fd8412..a9007549 100644 --- a/src/component/component.js +++ b/src/component/component.js @@ -1,5 +1,3 @@ -var util = require('./../util'); - /** * Prototype for visual components */ @@ -116,6 +114,3 @@ Component.prototype.on = function (event, callback) { throw new Error('Cannot attach event: no root panel found'); } }; - -// exports -module.exports = exports = Component; diff --git a/src/component/item/item.js b/src/component/item/item.js index c2697e54..110a8166 100644 --- a/src/component/item/item.js +++ b/src/component/item/item.js @@ -1,5 +1,3 @@ -var Component = require('../component'); - /** * @constructor Item * @param {ItemSet} parent @@ -32,6 +30,3 @@ Item.prototype.select = function () { Item.prototype.unselect = function () { this.selected = false; }; - -// exports -module.exports = exports = Item; diff --git a/src/component/item/itembox.js b/src/component/item/itembox.js index 960d9f91..a83ef250 100644 --- a/src/component/item/itembox.js +++ b/src/component/item/itembox.js @@ -1,6 +1,3 @@ -var util = require('../../util'), - Item = require('./item'); - /** * @constructor ItemBox * @extends Item @@ -273,6 +270,3 @@ ItemBox.prototype.reposition = function () { dot.style.top = props.dot.top + 'px'; } }; - -// exports -module.exports = exports = ItemBox; diff --git a/src/component/item/itempoint.js b/src/component/item/itempoint.js index 1efccb93..45f699c6 100644 --- a/src/component/item/itempoint.js +++ b/src/component/item/itempoint.js @@ -1,6 +1,3 @@ -var util = require('../../util'), - Item = require('./item'); - /** * @constructor ItemPoint * @extends Item @@ -209,6 +206,3 @@ ItemPoint.prototype.reposition = function () { dom.dot.style.top = props.dot.top + 'px'; } }; - -// exports -module.exports = exports = ItemPoint; diff --git a/src/component/item/itemrange.js b/src/component/item/itemrange.js index 56b1b4a6..fca77f17 100644 --- a/src/component/item/itemrange.js +++ b/src/component/item/itemrange.js @@ -1,6 +1,3 @@ -var util = require('../../util'), - Item = require('./item'); - /** * @constructor ItemRange * @extends Item @@ -218,6 +215,3 @@ ItemRange.prototype.reposition = function () { dom.content.style.left = props.content.left + 'px'; } }; - -// exports -module.exports = exports = ItemRange; diff --git a/src/component/itemset.js b/src/component/itemset.js index 5646b473..1d6e56fc 100644 --- a/src/component/itemset.js +++ b/src/component/itemset.js @@ -1,11 +1,3 @@ -var util = require('../util'), - DataSet = require('../dataset'), - Panel = require('./panel'), - Stack = require('../stack'), - ItemBox = require('./item/itembox'), - ItemRange = require('./item/itemrange'), - ItemPoint = require('./item/itempoint'); - /** * An ItemSet holds a set of items and ranges which can be displayed in a * range. The width is determined by the parent of the ItemSet, and the height @@ -512,6 +504,3 @@ ItemSet.prototype.toScreen = function(time) { var conversion = this.conversion; return (time.valueOf() - conversion.offset) * conversion.factor; }; - -// exports -module.exports = exports = ItemSet; diff --git a/src/component/panel.js b/src/component/panel.js index 20ccda82..dcb66eb8 100644 --- a/src/component/panel.js +++ b/src/component/panel.js @@ -1,6 +1,3 @@ -var util = require('../util'), - Component = require('./component'); - /** * A panel can contain components * @param {Component} [parent] @@ -102,6 +99,3 @@ Panel.prototype.reflow = function () { return (changed > 0); }; - -// exports -module.exports = exports = Panel; diff --git a/src/component/rootpanel.js b/src/component/rootpanel.js index 7b107ea0..30fc0417 100644 --- a/src/component/rootpanel.js +++ b/src/component/rootpanel.js @@ -1,6 +1,3 @@ -var util = require('../util'), - Panel = require('./panel'); - /** * A root panel can hold components. The root panel must be initialized with * a DOM element as container. @@ -201,6 +198,3 @@ RootPanel.prototype._updateEventEmitters = function () { // TODO: be able to move event listeners to a parent when available } }; - -// exports -module.exports = exports = RootPanel; diff --git a/src/component/timeaxis.js b/src/component/timeaxis.js index 07c79466..fc5cee4f 100644 --- a/src/component/timeaxis.js +++ b/src/component/timeaxis.js @@ -1,7 +1,3 @@ -var util = require('../util'), - TimeStep = require('../timestep'), - Component = require('./component'); - /** * A horizontal time axis * @param {Component} parent @@ -526,6 +522,3 @@ TimeAxis.prototype._updateConversion = function() { this.conversion = Range.conversion(range.start, range.end, this.width); } }; - -// exports -module.exports = exports = TimeAxis; diff --git a/src/controller.js b/src/controller.js index 0735f36e..6c15b3c2 100644 --- a/src/controller.js +++ b/src/controller.js @@ -1,6 +1,3 @@ -var util = require('./util'), - Component = require('./component/component'); - /** * @constructor Controller * @@ -140,7 +137,3 @@ Controller.prototype.reflow = function () { } // TODO: limit the number of nested reflows/repaints, prevent loop }; - -// exports -module.exports = exports = Controller; - diff --git a/src/dataset.js b/src/dataset.js index 083872a1..a0f27fd8 100644 --- a/src/dataset.js +++ b/src/dataset.js @@ -1,5 +1,3 @@ -var util = require('./util'); - /** * DataSet * @@ -547,6 +545,3 @@ DataSet.prototype._appendRow = function (dataTable, columns, item) { dataTable.setValue(row, col, item[field]); }); }; - -// exports -module.exports = exports = DataSet; diff --git a/src/events.js b/src/events.js index b2309a6b..cb8bffe0 100644 --- a/src/events.js +++ b/src/events.js @@ -113,6 +113,3 @@ var events = { } } }; - -// exports -module.exports = exports = events; diff --git a/src/exports.js b/src/exports.js new file mode 100644 index 00000000..462c68e6 --- /dev/null +++ b/src/exports.js @@ -0,0 +1,57 @@ +/** + * vis.js library exports + */ +var vis = { + util: util, + events: events, + + Controller: Controller, + DataSet: DataSet, + Range: Range, + Stack: Stack, + TimeStep: TimeStep, + + components: { + items: { + Item: Item, + ItemBox: ItemBox, + ItemPoint: ItemPoint, + ItemRange: ItemRange + }, + + Component: Component, + Panel: Panel, + RootPanel: RootPanel, + ItemSet: ItemSet, + TimeAxis: TimeAxis + }, + + Timeline: Timeline +}; + +/** + * CommonJS module exports + */ +if (typeof exports !== 'undefined') { + exports = vis; +} +if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = vis; +} + +/** + * AMD module exports + */ +if (typeof(define) === 'function') { + define(function () { + return vis; + }); +} + +/** + * Window exports + */ +if (typeof window !== 'undefined') { + // attach the module to the window, load as a regular javascript file + window['vis'] = vis; +} diff --git a/src/imports.js b/src/imports.js new file mode 100644 index 00000000..fbc66340 --- /dev/null +++ b/src/imports.js @@ -0,0 +1,4 @@ +/** + * vis.js library imports + */ +var moment = require('moment'); diff --git a/src/module.js b/src/module.js deleted file mode 100644 index 0ef13dac..00000000 --- a/src/module.js +++ /dev/null @@ -1,33 +0,0 @@ - -/** - * AMD module exports - */ -if (typeof(define) === 'function') { - define(function () { - return vis; - }); -} - -/** - * load css from text - * @param {String} css Text containing css - */ -var loadCss = function (css) { - // get the script location, and built the css file name from the js file name - // http://stackoverflow.com/a/2161748/1262753 - var scripts = document.getElementsByTagName('script'); - // var jsFile = scripts[scripts.length-1].src.split('?')[0]; - // var cssFile = jsFile.substring(0, jsFile.length - 2) + 'css'; - - // inject css - // http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript - var style = document.createElement('style'); - style.type = 'text/css'; - if (style.styleSheet){ - style.styleSheet.cssText = css; - } else { - style.appendChild(document.createTextNode(css)); - } - - document.getElementsByTagName('head')[0].appendChild(style); -}; diff --git a/src/range.js b/src/range.js index 3dd8187d..4ce5c595 100644 --- a/src/range.js +++ b/src/range.js @@ -1,6 +1,3 @@ -var util = require('./util'), - events = require('./events'); - /** * @constructor Range * A Range controls a numeric range with a start and end value. @@ -525,6 +522,3 @@ Range.prototype.move = function(moveFactor) { this.start = newStart; this.end = newEnd; }; - -// exports -module.exports = exports = Range; diff --git a/src/stack.js b/src/stack.js index 5cfa2363..b79c419d 100644 --- a/src/stack.js +++ b/src/stack.js @@ -1,5 +1,3 @@ -var util = require('./util'); - /** * @constructor Stack * Stacks items on top of each other. @@ -157,6 +155,3 @@ Stack.prototype.collision = function(a, b, margin) { (a.top - margin) < (b.top + b.height) && (a.top + a.height + margin) > b.top); }; - -// exports -module.exports = exports = Stack; diff --git a/src/timestep.js b/src/timestep.js index 0c9e7d34..4360a9c7 100644 --- a/src/timestep.js +++ b/src/timestep.js @@ -1,7 +1,4 @@ -var util = require('./util'), - moment = require('moment'); - - /** +/** * @constructor TimeStep * The class TimeStep is an iterator for dates. You provide a start date and an * end date. The class itself determines the best scale (step size) based on the @@ -451,6 +448,3 @@ TimeStep.prototype.getLabelMajor = function(date) { default: return ''; } }; - -// exports -module.exports = exports = TimeStep; diff --git a/src/util.js b/src/util.js index e59e1f05..be0f07a3 100644 --- a/src/util.js +++ b/src/util.js @@ -592,6 +592,30 @@ util.option.asElement = function (value, defaultValue) { return value || defaultValue || null; }; +/** + * load css from text + * @param {String} css Text containing css + */ +util.loadCss = function (css) { + // get the script location, and built the css file name from the js file name + // http://stackoverflow.com/a/2161748/1262753 + var scripts = document.getElementsByTagName('script'); + // var jsFile = scripts[scripts.length-1].src.split('?')[0]; + // var cssFile = jsFile.substring(0, jsFile.length - 2) + 'css'; + + // inject css + // http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript + var style = document.createElement('style'); + style.type = 'text/css'; + if (style.styleSheet){ + style.styleSheet.cssText = css; + } else { + style.appendChild(document.createTextNode(css)); + } + + document.getElementsByTagName('head')[0].appendChild(style); +}; + // Internet Explorer 8 and older does not support Array.indexOf, so we define // it here in that case. @@ -780,6 +804,3 @@ if(!Array.isArray) { return Object.prototype.toString.call(vArg) === "[object Array]"; }; } - -// exports -module.exports = exports = util; diff --git a/src/vis.js b/src/vis.js deleted file mode 100644 index 581c1cf9..00000000 --- a/src/vis.js +++ /dev/null @@ -1,31 +0,0 @@ -/** - * vis.js library exports - */ -var vis = { - Controller: require('./controller'), - DataSet: require('./dataset'), - events: require('./events'), - Range: require('./range'), - Stack: require('./stack'), - TimeStep: require('./timestep'), - util: require('./util'), - - component: { - item: { - Item: '../../Item', - ItemBox: '../../ItemBox', - ItemPoint: '../../ItemPoint', - ItemRange: '../../ItemRange' - }, - - Component: require('./component/component'), - Panel: require('./component/panel'), - RootPanel: require('./component/rootpanel'), - ItemSet: require('./component/itemset'), - TimeAxis: require('./component/timeaxis') - }, - - Timeline: require('./visualization/timeline') -}; - -module.exports = exports = vis; diff --git a/src/visualization/timeline.js b/src/visualization/timeline.js index 8d70740e..c92ddcd9 100644 --- a/src/visualization/timeline.js +++ b/src/visualization/timeline.js @@ -1,12 +1,3 @@ -var util = require('./../util'), - moment = require('moment'), - Range = require('../range'), - Controller = require('../controller'), - Component = require('../component/component'), - RootPanel = require('../component/rootpanel'), - TimeAxis = require('../component/timeaxis'), - ItemSet = require('../component/itemset'); - /** * Create a timeline visualization * @param {HTMLElement} container @@ -147,6 +138,3 @@ Timeline.prototype.setData = function(data) { this.itemset.setData(data); } }; - -// exports -module.exports = exports = Timeline; diff --git a/vis.js b/vis.js index d4dd053a..8d2f3c72 100644 --- a/vis.js +++ b/vis.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 0.0.7 - * @date 2013-04-25 + * @date 2013-04-26 * * @license * Copyright (C) 2011-2013 Almende B.V, http://almende.com @@ -25,158 +25,10 @@ (function(e){if("function"==typeof bootstrap)bootstrap("vis",e);else if("object"==typeof exports)module.exports=e();else if("function"==typeof define&&define.amd)define(e);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeVis=e}else"undefined"!=typeof window?window.vis=e():global.vis=e()})(function(){var define,ses,bootstrap,module,exports; return (function(e,t,n){function i(n,s){if(!t[n]){if(!e[n]){var o=typeof require=="function"&&require;if(!s&&o)return o(n,!0);if(r)return r(n,!0);throw new Error("Cannot find module '"+n+"'")}var u=t[n]={exports:{}};e[n][0].call(u.exports,function(t){var r=e[n][1][t];return i(r?r:t)},u,u.exports)}return t[n].exports}var r=typeof require=="function"&&require;for(var s=0;s 0) { + this.step = newStep; } - this._trigger('update', {items: items}, senderId); + this.autoScale = false; }; /** - * Get a data item or multiple items - * @param {String | Number | Array | Object} [ids] Id of a single item, or an - * array with multiple id's, or - * undefined or an Object with options - * to retrieve all data. - * @param {Object} [options] Available options: - * {String} [type] - * 'DataTable' or 'Array' (default) - * {Object.} [fieldTypes] - * {String[]} [fields] filter fields - * @param {Array | DataTable} [data] If provided, items will be appended - * to this array or table. Required - * in case of Google DataTable - * @return {Array | Object | DataTable | null} data - * @throws Error + * Enable or disable autoscaling + * @param {boolean} enable If true, autoascaling is set true */ -DataSet.prototype.get = function (ids, options, data) { - var me = this; +TimeStep.prototype.setAutoScale = function (enable) { + this.autoScale = enable; +}; - // shift arguments when first argument contains the options - if (util.getType(ids) == 'Object') { - data = options; - options = ids; - ids = undefined; - } - // merge field types - var fieldTypes = {}; - if (this.options && this.options.fieldTypes) { - util.forEach(this.options.fieldTypes, function (value, field) { - fieldTypes[field] = value; - }); - } - if (options && options.fieldTypes) { - util.forEach(options.fieldTypes, function (value, field) { - fieldTypes[field] = value; - }); +/** + * Automatically determine the scale that bests fits the provided minimum step + * @param {Number} minimumStep The minimum step size in milliseconds + */ +TimeStep.prototype.setMinimumStep = function(minimumStep) { + if (minimumStep == undefined) { + return; } - var fields = options ? options.fields : undefined; - - // determine the return type - var type; - if (options && options.type) { - type = (options.type == 'DataTable') ? 'DataTable' : 'Array'; + var stepYear = (1000 * 60 * 60 * 24 * 30 * 12); + var stepMonth = (1000 * 60 * 60 * 24 * 30); + var stepDay = (1000 * 60 * 60 * 24); + var stepHour = (1000 * 60 * 60); + var stepMinute = (1000 * 60); + var stepSecond = (1000); + var stepMillisecond= (1); - if (data && (type != util.getType(data))) { - throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' + - 'does not correspond with specified options.type (' + options.type + ')'); - } - if (type == 'DataTable' && !util.isDataTable(data)) { - throw new Error('Parameter "data" must be a DataTable ' + - 'when options.type is "DataTable"'); - } - } - else if (data) { - type = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array'; - } - else { - type = 'Array'; - } + // find the smallest step that is larger than the provided minimumStep + if (stepYear*1000 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1000;} + if (stepYear*500 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 500;} + if (stepYear*100 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 100;} + if (stepYear*50 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 50;} + if (stepYear*10 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 10;} + if (stepYear*5 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 5;} + if (stepYear > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1;} + if (stepMonth*3 > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 3;} + if (stepMonth > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 1;} + if (stepDay*5 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 5;} + if (stepDay*2 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 2;} + if (stepDay > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 1;} + if (stepDay/2 > minimumStep) {this.scale = TimeStep.SCALE.WEEKDAY; this.step = 1;} + if (stepHour*4 > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 4;} + if (stepHour > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 1;} + if (stepMinute*15 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 15;} + if (stepMinute*10 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 10;} + if (stepMinute*5 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 5;} + if (stepMinute > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 1;} + if (stepSecond*15 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 15;} + if (stepSecond*10 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 10;} + if (stepSecond*5 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 5;} + if (stepSecond > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 1;} + if (stepMillisecond*200 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 200;} + if (stepMillisecond*100 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 100;} + if (stepMillisecond*50 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 50;} + if (stepMillisecond*10 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 10;} + if (stepMillisecond*5 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 5;} + if (stepMillisecond > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 1;} +}; - if (type == 'DataTable') { - // return a Google DataTable - var columns = this._getColumnNames(data); - if (ids == undefined) { - // return all data - util.forEach(this.data, function (item) { - me._appendRow(data, columns, me._castItem(item)); - }); - } - else if (util.isNumber(ids) || util.isString(ids)) { - var item = me._castItem(me.data[ids], fieldTypes, fields); - this._appendRow(data, columns, item); - } - else if (ids instanceof Array) { - ids.forEach(function (id) { - var item = me._castItem(me.data[id], fieldTypes, fields); - me._appendRow(data, columns, item); - }); +/** + * Snap a date to a rounded value. The snap intervals are dependent on the + * current scale and step. + * @param {Date} date the date to be snapped + */ +TimeStep.prototype.snap = function(date) { + if (this.scale == TimeStep.SCALE.YEAR) { + var year = date.getFullYear() + Math.round(date.getMonth() / 12); + date.setFullYear(Math.round(year / this.step) * this.step); + date.setMonth(0); + date.setDate(0); + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + date.setMilliseconds(0); + } + else if (this.scale == TimeStep.SCALE.MONTH) { + if (date.getDate() > 15) { + date.setDate(1); + date.setMonth(date.getMonth() + 1); + // important: first set Date to 1, after that change the month. } else { - throw new TypeError('Parameter "ids" must be ' + - 'undefined, a String, Number, or Array'); + date.setDate(1); } + + date.setHours(0); + date.setMinutes(0); + date.setSeconds(0); + date.setMilliseconds(0); } - else { - // return an array - data = data || []; - if (ids == undefined) { - // return all data - util.forEach(this.data, function (item) { - data.push(me._castItem(item, fieldTypes, fields)); - }); + else if (this.scale == TimeStep.SCALE.DAY || + this.scale == TimeStep.SCALE.WEEKDAY) { + //noinspection FallthroughInSwitchStatementJS + switch (this.step) { + case 5: + case 2: + date.setHours(Math.round(date.getHours() / 24) * 24); break; + default: + date.setHours(Math.round(date.getHours() / 12) * 12); break; } - else if (util.isNumber(ids) || util.isString(ids)) { - // return a single item - return this._castItem(me.data[ids], fieldTypes, fields); + date.setMinutes(0); + date.setSeconds(0); + date.setMilliseconds(0); + } + else if (this.scale == TimeStep.SCALE.HOUR) { + switch (this.step) { + case 4: + date.setMinutes(Math.round(date.getMinutes() / 60) * 60); break; + default: + date.setMinutes(Math.round(date.getMinutes() / 30) * 30); break; } - else if (ids instanceof Array) { - ids.forEach(function (id) { - data.push(me._castItem(me.data[id], fieldTypes, fields)); - }); + date.setSeconds(0); + date.setMilliseconds(0); + } else if (this.scale == TimeStep.SCALE.MINUTE) { + //noinspection FallthroughInSwitchStatementJS + switch (this.step) { + case 15: + case 10: + date.setMinutes(Math.round(date.getMinutes() / 5) * 5); + date.setSeconds(0); + break; + case 5: + date.setSeconds(Math.round(date.getSeconds() / 60) * 60); break; + default: + date.setSeconds(Math.round(date.getSeconds() / 30) * 30); break; } - else { - throw new TypeError('Parameter "ids" must be ' + - 'undefined, a String, Number, or Array'); + date.setMilliseconds(0); + } + else if (this.scale == TimeStep.SCALE.SECOND) { + //noinspection FallthroughInSwitchStatementJS + switch (this.step) { + case 15: + case 10: + date.setSeconds(Math.round(date.getSeconds() / 5) * 5); + date.setMilliseconds(0); + break; + case 5: + date.setMilliseconds(Math.round(date.getMilliseconds() / 1000) * 1000); break; + default: + date.setMilliseconds(Math.round(date.getMilliseconds() / 500) * 500); break; } } - - return data; + else if (this.scale == TimeStep.SCALE.MILLISECOND) { + var step = this.step > 5 ? this.step / 2 : 1; + date.setMilliseconds(Math.round(date.getMilliseconds() / step) * step); + } }; /** - * Remove an object by pointer or by id - * @param {String | Number | Object | Array} id Object or id, or an array with - * objects or ids to be removed - * @param {String} [senderId] Optional sender id, used to trigger events for - * all but this sender's event subscribers. + * Check if the current value is a major value (for example when the step + * is DAY, a major value is each first day of the MONTH) + * @return {boolean} true if current date is major, else false. */ -DataSet.prototype.remove = function (id, senderId) { - var items = [], - me = this; - - if (util.isNumber(id) || util.isString(id)) { - delete this.data[id]; - delete this.internalIds[id]; - items.push(id); - } - else if (id instanceof Array) { - id.forEach(function (id) { - me.remove(id); - }); - items = items.concat(id); - } - else if (id instanceof Object) { - // search for the object - for (var i in this.data) { - if (this.data.hasOwnProperty(i)) { - if (this.data[i] == id) { - delete this.data[i]; - delete this.internalIds[i]; - items.push(i); - } - } - } +TimeStep.prototype.isMajor = function() { + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND: + return (this.current.getMilliseconds() == 0); + case TimeStep.SCALE.SECOND: + return (this.current.getSeconds() == 0); + case TimeStep.SCALE.MINUTE: + return (this.current.getHours() == 0) && (this.current.getMinutes() == 0); + // Note: this is no bug. Major label is equal for both minute and hour scale + case TimeStep.SCALE.HOUR: + return (this.current.getHours() == 0); + case TimeStep.SCALE.WEEKDAY: // intentional fall through + case TimeStep.SCALE.DAY: + return (this.current.getDate() == 1); + case TimeStep.SCALE.MONTH: + return (this.current.getMonth() == 0); + case TimeStep.SCALE.YEAR: + return false; + default: + return false; } - - this._trigger('remove', {items: items}, senderId); }; -/** - * Clear the data - * @param {String} [senderId] Optional sender id, used to trigger events for - * all but this sender's event subscribers. - */ -DataSet.prototype.clear = function (senderId) { - var ids = Object.keys(this.data); - - this.data = {}; - this.internalIds = {}; - - this._trigger('remove', {items: ids}, senderId); -}; /** - * Find the item with maximum value of a specified field - * @param {String} field - * @return {Object} item Item containing max value, or null if no items + * Returns formatted text for the minor axislabel, depending on the current + * date and the scale. For example when scale is MINUTE, the current time is + * formatted as "hh:mm". + * @param {Date} [date] custom date. if not provided, current date is taken */ -DataSet.prototype.max = function (field) { - var data = this.data, - ids = Object.keys(data); - - var max = null; - var maxField = null; - ids.forEach(function (id) { - var item = data[id]; - var itemField = item[field]; - if (itemField != null && (!max || itemField > maxField)) { - max = item; - maxField = itemField; - } - }); +TimeStep.prototype.getLabelMinor = function(date) { + if (date == undefined) { + date = this.current; + } - return max; + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND: return moment(date).format('SSS'); + case TimeStep.SCALE.SECOND: return moment(date).format('s'); + case TimeStep.SCALE.MINUTE: return moment(date).format('HH:mm'); + case TimeStep.SCALE.HOUR: return moment(date).format('HH:mm'); + case TimeStep.SCALE.WEEKDAY: return moment(date).format('ddd D'); + case TimeStep.SCALE.DAY: return moment(date).format('D'); + case TimeStep.SCALE.MONTH: return moment(date).format('MMM'); + case TimeStep.SCALE.YEAR: return moment(date).format('YYYY'); + default: return ''; + } }; -/** - * Find the item with minimum value of a specified field - * @param {String} field - */ -DataSet.prototype.min = function (field) { - var data = this.data, - ids = Object.keys(data); - - var min = null; - var minField = null; - ids.forEach(function (id) { - var item = data[id]; - var itemField = item[field]; - if (itemField != null && (!min || itemField < minField)) { - min = item; - minField = itemField; - } - }); - - return min; -}; /** - * Add a single item - * @param {Object} item - * @return {String} id - * @private + * Returns formatted text for the major axislabel, depending on the current + * date and the scale. For example when scale is MINUTE, the major scale is + * hours, and the hour will be formatted as "hh". + * @param {Date} [date] custom date. if not provided, current date is taken */ -DataSet.prototype._addItem = function (item) { - var id = item[this.fieldId]; - if (id == undefined) { - // generate an id - id = util.randomUUID(); - item[this.fieldId] = id; - - this.internalIds[id] = item; +TimeStep.prototype.getLabelMajor = function(date) { + if (date == undefined) { + date = this.current; } - var d = {}; - for (var field in item) { - if (item.hasOwnProperty(field)) { - var type = this.fieldTypes[field]; // type may be undefined - d[field] = util.cast(item[field], type); - } + //noinspection FallthroughInSwitchStatementJS + switch (this.scale) { + case TimeStep.SCALE.MILLISECOND:return moment(date).format('HH:mm:ss'); + case TimeStep.SCALE.SECOND: return moment(date).format('D MMMM HH:mm'); + case TimeStep.SCALE.MINUTE: + case TimeStep.SCALE.HOUR: return moment(date).format('ddd D MMMM'); + case TimeStep.SCALE.WEEKDAY: + case TimeStep.SCALE.DAY: return moment(date).format('MMMM YYYY'); + case TimeStep.SCALE.MONTH: return moment(date).format('YYYY'); + case TimeStep.SCALE.YEAR: return ''; + default: return ''; } - this.data[id] = d; - //TODO: fail when an item with this id already exists? - - return id; }; /** - * Cast and filter the fields of an item - * @param {Object | undefined} item - * @param {Object.} [fieldTypes] - * @param {String[]} [fields] - * @return {Object | null} castedItem - * @private + * DataSet + * + * Usage: + * var dataSet = new DataSet({ + * fieldId: '_id', + * fieldTypes: { + * // ... + * } + * }); + * + * dataSet.add(item); + * dataSet.add(data); + * dataSet.update(item); + * dataSet.update(data); + * dataSet.remove(id); + * dataSet.remove(ids); + * var data = dataSet.get(); + * var data = dataSet.get(id); + * var data = dataSet.get(ids); + * var data = dataSet.get(ids, options, data); + * dataSet.clear(); + * + * A data set can: + * - add/remove/update data + * - gives triggers upon changes in the data + * - can import/export data in various data formats + * @param {Object} [options] Available options: + * {String} fieldId Field name of the id in the + * items, 'id' by default. + * {Object.} [fieldTypes] + * {String[]} [fields] filter fields + * @param {Array | DataTable} [data] If provided, items will be appended + * to this array or table. Required + * in case of Google DataTable + * @return {Array | Object | DataTable | null} data + * @throws Error */ -Range.prototype._trigger = function (event) { - events.trigger(this, event, { - start: this.start, - end: this.end - }); -}; +DataSet.prototype.get = function (ids, options, data) { + var me = this; -/** - * Set a new start and end range - * @param {Number} start - * @param {Number} end - */ -Range.prototype.setRange = function(start, end) { - var changed = this._applyRange(start, end); - if (changed) { - this._trigger('rangechange'); - this._trigger('rangechanged'); + // shift arguments when first argument contains the options + if (util.getType(ids) == 'Object') { + data = options; + options = ids; + ids = undefined; } -}; - -/** - * Set a new start and end range. This method is the same as setRange, but - * does not trigger a range change and range changed event, and it returns - * true when the range is changed - * @param {Number} start - * @param {Number} end - * @return {Boolean} changed - * @private - */ -Range.prototype._applyRange = function(start, end) { - var newStart = (start != null) ? util.cast(start, 'Number') : this.start; - var newEnd = (end != null) ? util.cast(end, 'Number') : this.end; - var diff; - // check for valid number - if (isNaN(newStart)) { - throw new Error('Invalid start "' + start + '"'); + // merge field types + var fieldTypes = {}; + if (this.options && this.options.fieldTypes) { + util.forEach(this.options.fieldTypes, function (value, field) { + fieldTypes[field] = value; + }); } - if (isNaN(newEnd)) { - throw new Error('Invalid end "' + end + '"'); + if (options && options.fieldTypes) { + util.forEach(options.fieldTypes, function (value, field) { + fieldTypes[field] = value; + }); } - // prevent start < end - if (newEnd < newStart) { - newEnd = newStart; - } + var fields = options ? options.fields : undefined; - // prevent start < min - if (this.options.min != null) { - var min = this.options.min.valueOf(); - if (newStart < min) { - diff = (min - newStart); - newStart += diff; - newEnd += diff; - } - } + // determine the return type + var type; + if (options && options.type) { + type = (options.type == 'DataTable') ? 'DataTable' : 'Array'; - // prevent end > max - if (this.options.max != null) { - var max = this.options.max.valueOf(); - if (newEnd > max) { - diff = (newEnd - max); - newStart -= diff; - newEnd -= diff; + if (data && (type != util.getType(data))) { + throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' + + 'does not correspond with specified options.type (' + options.type + ')'); + } + if (type == 'DataTable' && !util.isDataTable(data)) { + throw new Error('Parameter "data" must be a DataTable ' + + 'when options.type is "DataTable"'); } } + else if (data) { + type = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array'; + } + else { + type = 'Array'; + } - // prevent (end-start) > zoomMin - if (this.options.zoomMin != null) { - var zoomMin = this.options.zoomMin.valueOf(); - if (zoomMin < 0) { - zoomMin = 0; + if (type == 'DataTable') { + // return a Google DataTable + var columns = this._getColumnNames(data); + if (ids == undefined) { + // return all data + util.forEach(this.data, function (item) { + me._appendRow(data, columns, me._castItem(item)); + }); } - if ((newEnd - newStart) < zoomMin) { - if ((this.end - this.start) > zoomMin) { - // zoom to the minimum - diff = (zoomMin - (newEnd - newStart)); - newStart -= diff / 2; - newEnd += diff / 2; - } - else { - // ingore this action, we are already zoomed to the minimum - newStart = this.start; - newEnd = this.end; - } + else if (util.isNumber(ids) || util.isString(ids)) { + var item = me._castItem(me.data[ids], fieldTypes, fields); + this._appendRow(data, columns, item); } - } - - // prevent (end-start) > zoomMin - if (this.options.zoomMax != null) { - var zoomMax = this.options.zoomMax.valueOf(); - if (zoomMax < 0) { - zoomMax = 0; + else if (ids instanceof Array) { + ids.forEach(function (id) { + var item = me._castItem(me.data[id], fieldTypes, fields); + me._appendRow(data, columns, item); + }); } - if ((newEnd - newStart) > zoomMax) { - if ((this.end - this.start) < zoomMax) { - // zoom to the maximum - diff = ((newEnd - newStart) - zoomMax); - newStart += diff / 2; - newEnd -= diff / 2; - } - else { - // ingore this action, we are already zoomed to the maximum - newStart = this.start; - newEnd = this.end; - } + else { + throw new TypeError('Parameter "ids" must be ' + + 'undefined, a String, Number, or Array'); } } - - var changed = (this.start != newStart || this.end != newEnd); - - this.start = newStart; - this.end = newEnd; - - return changed; -}; - -/** - * Retrieve the current range. - * @return {Object} An object with start and end properties - */ -Range.prototype.getRange = function() { - return { - start: this.start, - end: this.end - }; -}; - -/** - * Calculate the conversion offset and factor for current range, based on - * the provided width - * @param {Number} width - * @returns {{offset: number, factor: number}} conversion - */ -Range.prototype.conversion = function (width) { - var start = this.start; - var end = this.end; - - return Range.conversion(this.start, this.end, width); -}; - -/** - * Static method to calculate the conversion offset and factor for a range, - * based on the provided start, end, and width - * @param {Number} start - * @param {Number} end - * @param {Number} width - * @returns {{offset: number, factor: number}} conversion - */ -Range.conversion = function (start, end, width) { - if (width != 0 && (end - start != 0)) { - return { - offset: start, - factor: width / (end - start) + else { + // return an array + data = data || []; + if (ids == undefined) { + // return all data + util.forEach(this.data, function (item) { + data.push(me._castItem(item, fieldTypes, fields)); + }); + } + else if (util.isNumber(ids) || util.isString(ids)) { + // return a single item + return this._castItem(me.data[ids], fieldTypes, fields); + } + else if (ids instanceof Array) { + ids.forEach(function (id) { + data.push(me._castItem(me.data[id], fieldTypes, fields)); + }); + } + else { + throw new TypeError('Parameter "ids" must be ' + + 'undefined, a String, Number, or Array'); } } - else { - return { - offset: 0, - factor: 1 - }; - } + + return data; }; /** - * Start moving horizontally or vertically - * @param {Event} event - * @param {Object} listener Listener containing the component and params - * @private + * Remove an object by pointer or by id + * @param {String | Number | Object | Array} id Object or id, or an array with + * objects or ids to be removed + * @param {String} [senderId] Optional sender id, used to trigger events for + * all but this sender's event subscribers. */ -Range.prototype._onMouseDown = function(event, listener) { - event = event || window.event; - var params = listener.params; - - // only react on left mouse button down - var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1); - if (!leftButtonDown) { - return; - } - - // get mouse position - params.mouseX = util.getPageX(event); - params.mouseY = util.getPageY(event); - params.previousLeft = 0; - params.previousOffset = 0; - - params.moved = false; - params.start = this.start; - params.end = this.end; +DataSet.prototype.remove = function (id, senderId) { + var items = [], + me = this; - var frame = listener.component.frame; - if (frame) { - frame.style.cursor = 'move'; + if (util.isNumber(id) || util.isString(id)) { + delete this.data[id]; + delete this.internalIds[id]; + items.push(id); } - - // add event listeners to handle moving the contents - // we store the function onmousemove and onmouseup in the timeaxis, - // so we can remove the eventlisteners lateron in the function onmouseup - var me = this; - if (!params.onMouseMove) { - params.onMouseMove = function (event) { - me._onMouseMove(event, listener); - }; - util.addEventListener(document, "mousemove", params.onMouseMove); + else if (id instanceof Array) { + id.forEach(function (id) { + me.remove(id); + }); + items = items.concat(id); } - if (!params.onMouseUp) { - params.onMouseUp = function (event) { - me._onMouseUp(event, listener); - }; - util.addEventListener(document, "mouseup", params.onMouseUp); + else if (id instanceof Object) { + // search for the object + for (var i in this.data) { + if (this.data.hasOwnProperty(i)) { + if (this.data[i] == id) { + delete this.data[i]; + delete this.internalIds[i]; + items.push(i); + } + } + } } - util.preventDefault(event); + this._trigger('remove', {items: items}, senderId); }; /** - * Perform moving operating. - * This function activated from within the funcion TimeAxis._onMouseDown(). - * @param {Event} event - * @param {Object} listener - * @private + * Clear the data + * @param {String} [senderId] Optional sender id, used to trigger events for + * all but this sender's event subscribers. */ -Range.prototype._onMouseMove = function (event, listener) { - event = event || window.event; +DataSet.prototype.clear = function (senderId) { + var ids = Object.keys(this.data); - var params = listener.params; + this.data = {}; + this.internalIds = {}; - // calculate change in mouse position - var mouseX = util.getPageX(event); - var mouseY = util.getPageY(event); + this._trigger('remove', {items: ids}, senderId); +}; - if (params.mouseX == undefined) { - params.mouseX = mouseX; - } - if (params.mouseY == undefined) { - params.mouseY = mouseY; - } +/** + * Find the item with maximum value of a specified field + * @param {String} field + * @return {Object} item Item containing max value, or null if no items + */ +DataSet.prototype.max = function (field) { + var data = this.data, + ids = Object.keys(data); - var diffX = mouseX - params.mouseX; - var diffY = mouseY - params.mouseY; - var diff = (listener.direction == 'horizontal') ? diffX : diffY; + var max = null; + var maxField = null; + ids.forEach(function (id) { + var item = data[id]; + var itemField = item[field]; + if (itemField != null && (!max || itemField > maxField)) { + max = item; + maxField = itemField; + } + }); - // if mouse movement is big enough, register it as a "moved" event - if (Math.abs(diff) >= 1) { - params.moved = true; - } + return max; +}; - var interval = (params.end - params.start); - var width = (listener.direction == 'horizontal') ? - listener.component.width : listener.component.height; - var diffRange = -diff / width * interval; - this._applyRange(params.start + diffRange, params.end + diffRange); +/** + * Find the item with minimum value of a specified field + * @param {String} field + */ +DataSet.prototype.min = function (field) { + var data = this.data, + ids = Object.keys(data); - // fire a rangechange event - this._trigger('rangechange'); + var min = null; + var minField = null; + ids.forEach(function (id) { + var item = data[id]; + var itemField = item[field]; + if (itemField != null && (!min || itemField < minField)) { + min = item; + minField = itemField; + } + }); - util.preventDefault(event); + return min; }; /** - * Stop moving operating. - * This function activated from within the function Range._onMouseDown(). - * @param {event} event - * @param {Object} listener + * Add a single item + * @param {Object} item + * @return {String} id * @private */ -Range.prototype._onMouseUp = function (event, listener) { - event = event || window.event; - - var params = listener.params; +DataSet.prototype._addItem = function (item) { + var id = item[this.fieldId]; + if (id == undefined) { + // generate an id + id = util.randomUUID(); + item[this.fieldId] = id; - if (listener.component.frame) { - listener.component.frame.style.cursor = 'auto'; + this.internalIds[id] = item; } - // remove event listeners here, important for Safari - if (params.onMouseMove) { - util.removeEventListener(document, "mousemove", params.onMouseMove); - params.onMouseMove = null; - } - if (params.onMouseUp) { - util.removeEventListener(document, "mouseup", params.onMouseUp); - params.onMouseUp = null; + var d = {}; + for (var field in item) { + if (item.hasOwnProperty(field)) { + var type = this.fieldTypes[field]; // type may be undefined + d[field] = util.cast(item[field], type); + } } - //util.preventDefault(event); + this.data[id] = d; + //TODO: fail when an item with this id already exists? - if (params.moved) { - // fire a rangechanged event - this._trigger('rangechanged'); - } + return id; }; /** - * Event handler for mouse wheel event, used to zoom - * Code from http://adomas.org/javascript-mouse-wheel/ - * @param {Event} event - * @param {Object} listener + * Cast and filter the fields of an item + * @param {Object | undefined} item + * @param {Object.} [fieldTypes] + * @param {String[]} [fields] + * @return {Object | null} castedItem * @private - */ -Range.prototype._onMouseWheel = function(event, listener) { - event = event || window.event; - - // retrieve delta - var delta = 0; - if (event.wheelDelta) { /* IE/Opera. */ - delta = event.wheelDelta / 120; - } else if (event.detail) { /* Mozilla case. */ - // In Mozilla, sign of delta is different than in IE. - // Also, delta is multiple of 3. - delta = -event.detail / 3; - } - - // If delta is nonzero, handle it. - // Basically, delta is now positive if wheel was scrolled up, - // and negative, if wheel was scrolled down. - if (delta) { - var me = this; - var zoom = function () { - // perform the zoom action. Delta is normally 1 or -1 - var zoomFactor = delta / 5.0; - var zoomAround = null; - var frame = listener.component.frame; - if (frame) { - var size, conversion; - if (listener.direction == 'horizontal') { - size = listener.component.width; - conversion = me.conversion(size); - var frameLeft = util.getAbsoluteLeft(frame); - var mouseX = util.getPageX(event); - zoomAround = (mouseX - frameLeft) / conversion.factor + conversion.offset; - } - else { - size = listener.component.height; - conversion = me.conversion(size); - var frameTop = util.getAbsoluteTop(frame); - var mouseY = util.getPageY(event); - zoomAround = ((frameTop + size - mouseY) - frameTop) / conversion.factor + conversion.offset; - } - } + */ +DataSet.prototype._castItem = function (item, fieldTypes, fields) { + var clone, + fieldId = this.fieldId, + internalIds = this.internalIds; - me.zoom(zoomFactor, zoomAround); - }; + if (item) { + clone = {}; + fieldTypes = fieldTypes || {}; - zoom(); + if (fields) { + // output filtered fields + util.forEach(item, function (value, field) { + if (fields.indexOf(field) != -1) { + clone[field] = util.cast(value, fieldTypes[field]); + } + }); + } + else { + // output all fields, except internal ids + util.forEach(item, function (value, field) { + if (field != fieldId || !(value in internalIds)) { + clone[field] = util.cast(value, fieldTypes[field]); + } + }); + } + } + else { + clone = null; } - // Prevent default actions caused by mouse wheel. - // That might be ugly, but we handle scrolls somehow - // anyway, so don't bother here... - util.preventDefault(event); + return clone; }; - /** - * Zoom the range the given zoomfactor in or out. Start and end date will - * be adjusted, and the timeline will be redrawn. You can optionally give a - * date around which to zoom. - * For example, try zoomfactor = 0.1 or -0.1 - * @param {Number} zoomFactor Zooming amount. Positive value will zoom in, - * negative value will zoom out - * @param {Number} zoomAround Value around which will be zoomed. Optional + * Update a single item: merge with existing item + * @param {Object} item + * @return {String} id + * @private */ -Range.prototype.zoom = function(zoomFactor, zoomAround) { - // if zoomAroundDate is not provided, take it half between start Date and end Date - if (zoomAround == null) { - zoomAround = (this.start + this.end) / 2; - } - - // prevent zoom factor larger than 1 or smaller than -1 (larger than 1 will - // result in a start>=end ) - if (zoomFactor >= 1) { - zoomFactor = 0.9; +DataSet.prototype._updateItem = function (item) { + var id = item[this.fieldId]; + if (id == undefined) { + throw new Error('Item has no id (item: ' + JSON.stringify(item) + ')'); } - if (zoomFactor <= -1) { - zoomFactor = -0.9; + var d = this.data[id]; + if (d) { + // merge with current item + for (var field in item) { + if (item.hasOwnProperty(field)) { + var type = this.fieldTypes[field]; // type may be undefined + d[field] = util.cast(item[field], type); + } + } } - - // adjust a negative factor such that zooming in with 0.1 equals zooming - // out with a factor -0.1 - if (zoomFactor < 0) { - zoomFactor = zoomFactor / (1 + zoomFactor); + else { + // create new item + this._addItem(item); } - // zoom start and end relative to the zoomAround value - var startDiff = (this.start - zoomAround); - var endDiff = (this.end - zoomAround); - - // calculate new start and end - var newStart = this.start - startDiff * zoomFactor; - var newEnd = this.end - endDiff * zoomFactor; - - this.setRange(newStart, newEnd); + return id; }; /** - * Move the range with a given factor to the left or right. Start and end - * value will be adjusted. For example, try moveFactor = 0.1 or -0.1 - * @param {Number} moveFactor Moving amount. Positive value will move right, - * negative value will move left + * Get an array with the column names of a Google DataTable + * @param {DataTable} dataTable + * @return {Array} columnNames + * @private */ -Range.prototype.move = function(moveFactor) { - // zoom start Date and end Date relative to the zoomAroundDate - var diff = (this.end - this.start); - - // apply new values - var newStart = this.start + diff * moveFactor; - var newEnd = this.end + diff * moveFactor; - - // TODO: reckon with min and max range - - this.start = newStart; - this.end = newEnd; +DataSet.prototype._getColumnNames = function (dataTable) { + var columns = []; + for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) { + columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col); + } + return columns; }; -// exports -module.exports = exports = Range; - -},{"./util":8,"./events":4}],6:[function(require,module,exports){ -var util = require('./util'); +/** + * Append an item as a row to the dataTable + * @param dataTable + * @param columns + * @param item + * @private + */ +DataSet.prototype._appendRow = function (dataTable, columns, item) { + var row = dataTable.addRow(); + columns.forEach(function (field, col) { + dataTable.setValue(row, col, item[field]); + }); +}; /** * @constructor Stack @@ -2358,789 +2109,882 @@ Stack.prototype.collision = function(a, b, margin) { (a.top + a.height + margin) > b.top); }; -// exports -module.exports = exports = Stack; - -},{"./util":8}],9:[function(require,module,exports){ -var util = require('./../util'); - /** - * Prototype for visual components + * @constructor Range + * A Range controls a numeric range with a start and end value. + * The Range adjusts the range based on mouse events or programmatic changes, + * and triggers events when the range is changing or has been changed. + * @param {Object} [options] See description at Range.setOptions + * @extends Controller */ -function Component () { - this.id = null; - this.parent = null; - this.depends = null; - this.controller = null; - this.options = null; +function Range(options) { + this.id = util.randomUUID(); + this.start = 0; // Number + this.end = 0; // Number - this.frame = null; // main DOM element - this.top = 0; - this.left = 0; - this.width = 0; - this.height = 0; + this.options = { + min: null, + max: null, + zoomMin: null, + zoomMax: null + }; + + this.setOptions(options); + + this.listeners = []; } /** - * Set parameters for the frame. Parameters will be merged in current parameter - * set. - * @param {Object} options Available parameters: - * {String | function} [className] - * {String | Number | function} [left] - * {String | Number | function} [top] - * {String | Number | function} [width] - * {String | Number | function} [height] + * Set options for the range controller + * @param {Object} options Available options: + * {Number} start Set start value of the range + * {Number} end Set end value of the range + * {Number} min Minimum value for start + * {Number} max Maximum value for end + * {Number} zoomMin Set a minimum value for + * (end - start). + * {Number} zoomMax Set a maximum value for + * (end - start). */ -Component.prototype.setOptions = function(options) { - if (options) { - util.extend(this.options, options); - } +Range.prototype.setOptions = function (options) { + util.extend(this.options, options); - if (this.controller) { - this.requestRepaint(); - this.requestReflow(); + if (options.start != null || options.end != null) { + this.setRange(options.start, options.end); } }; /** - * Get the container element of the component, which can be used by a child to - * add its own widgets. Not all components do have a container for childs, in - * that case null is returned. - * @returns {HTMLElement | null} container + * Add listeners for mouse and touch events to the component + * @param {Component} component + * @param {String} event Available events: 'move', 'zoom' + * @param {String} direction Available directions: 'horizontal', 'vertical' */ -Component.prototype.getContainer = function () { - // should be implemented by the component - return null; +Range.prototype.subscribe = function (component, event, direction) { + var me = this; + var listener; + + if (direction != 'horizontal' && direction != 'vertical') { + throw new TypeError('Unknown direction "' + direction + '". ' + + 'Choose "horizontal" or "vertical".'); + } + + //noinspection FallthroughInSwitchStatementJS + if (event == 'move') { + listener = { + component: component, + event: event, + direction: direction, + callback: function (event) { + me._onMouseDown(event, listener); + }, + params: {} + }; + + component.on('mousedown', listener.callback); + me.listeners.push(listener); + } + else if (event == 'zoom') { + listener = { + component: component, + event: event, + direction: direction, + callback: function (event) { + me._onMouseWheel(event, listener); + }, + params: {} + }; + + component.on('mousewheel', listener.callback); + me.listeners.push(listener); + } + else { + throw new TypeError('Unknown event "' + event + '". ' + + 'Choose "move" or "zoom".'); + } }; /** - * Get the frame element of the component, the outer HTML DOM element. - * @returns {HTMLElement | null} frame + * Event handler + * @param {String} event name of the event, for example 'click', 'mousemove' + * @param {function} callback callback handler, invoked with the raw HTML Event + * as parameter. */ -Component.prototype.getFrame = function () { - return this.frame; +Range.prototype.on = function (event, callback) { + events.addListener(this, event, callback); }; /** - * Repaint the component - * @return {Boolean} changed + * Trigger an event + * @param {String} event name of the event, available events: 'rangechange', + * 'rangechanged' + * @private */ -Component.prototype.repaint = function () { - // should be implemented by the component - return false; +Range.prototype._trigger = function (event) { + events.trigger(this, event, { + start: this.start, + end: this.end + }); }; /** - * Reflow the component - * @return {Boolean} resized + * Set a new start and end range + * @param {Number} start + * @param {Number} end */ -Component.prototype.reflow = function () { - // should be implemented by the component - return false; +Range.prototype.setRange = function(start, end) { + var changed = this._applyRange(start, end); + if (changed) { + this._trigger('rangechange'); + this._trigger('rangechanged'); + } }; /** - * Request a repaint. The controller will schedule a repaint + * Set a new start and end range. This method is the same as setRange, but + * does not trigger a range change and range changed event, and it returns + * true when the range is changed + * @param {Number} start + * @param {Number} end + * @return {Boolean} changed + * @private */ -Component.prototype.requestRepaint = function () { - if (this.controller) { - this.controller.requestRepaint(); +Range.prototype._applyRange = function(start, end) { + var newStart = (start != null) ? util.cast(start, 'Number') : this.start; + var newEnd = (end != null) ? util.cast(end, 'Number') : this.end; + var diff; + + // check for valid number + if (isNaN(newStart)) { + throw new Error('Invalid start "' + start + '"'); } - else { - throw new Error('Cannot request a repaint: no controller configured'); - // TODO: just do a repaint when no parent is configured? + if (isNaN(newEnd)) { + throw new Error('Invalid end "' + end + '"'); } -}; -/** - * Request a reflow. The controller will schedule a reflow - */ -Component.prototype.requestReflow = function () { - if (this.controller) { - this.controller.requestReflow(); + // prevent start < end + if (newEnd < newStart) { + newEnd = newStart; } - else { - throw new Error('Cannot request a reflow: no controller configured'); - // TODO: just do a reflow when no parent is configured? + + // prevent start < min + if (this.options.min != null) { + var min = this.options.min.valueOf(); + if (newStart < min) { + diff = (min - newStart); + newStart += diff; + newEnd += diff; + } } -}; -/** - * Event handler - * @param {String} event name of the event, for example 'click', 'mousemove' - * @param {function} callback callback handler, invoked with the raw HTML Event - * as parameter. - */ -Component.prototype.on = function (event, callback) { - // TODO: rethink the way of event delegation - if (this.parent) { - this.parent.on(event, callback); + // prevent end > max + if (this.options.max != null) { + var max = this.options.max.valueOf(); + if (newEnd > max) { + diff = (newEnd - max); + newStart -= diff; + newEnd -= diff; + } } - else { - throw new Error('Cannot attach event: no root panel found'); + + // prevent (end-start) > zoomMin + if (this.options.zoomMin != null) { + var zoomMin = this.options.zoomMin.valueOf(); + if (zoomMin < 0) { + zoomMin = 0; + } + if ((newEnd - newStart) < zoomMin) { + if ((this.end - this.start) > zoomMin) { + // zoom to the minimum + diff = (zoomMin - (newEnd - newStart)); + newStart -= diff / 2; + newEnd += diff / 2; + } + else { + // ingore this action, we are already zoomed to the minimum + newStart = this.start; + newEnd = this.end; + } + } } -}; -// exports -module.exports = exports = Component; + // prevent (end-start) > zoomMin + if (this.options.zoomMax != null) { + var zoomMax = this.options.zoomMax.valueOf(); + if (zoomMax < 0) { + zoomMax = 0; + } + if ((newEnd - newStart) > zoomMax) { + if ((this.end - this.start) < zoomMax) { + // zoom to the maximum + diff = ((newEnd - newStart) - zoomMax); + newStart += diff / 2; + newEnd -= diff / 2; + } + else { + // ingore this action, we are already zoomed to the maximum + newStart = this.start; + newEnd = this.end; + } + } + } + + var changed = (this.start != newStart || this.end != newEnd); + + this.start = newStart; + this.end = newEnd; -},{"./../util":8}],10:[function(require,module,exports){ -var util = require('../util'), - Component = require('./component'); + return changed; +}; /** - * A panel can contain components - * @param {Component} [parent] - * @param {Component[]} [depends] Components on which this components depends - * (except for the parent) - * @param {Object} [options] Available parameters: - * {String | Number | function} [left] - * {String | Number | function} [top] - * {String | Number | function} [width] - * {String | Number | function} [height] - * {String | function} [className] - * @constructor Panel - * @extends Component + * Retrieve the current range. + * @return {Object} An object with start and end properties */ -function Panel(parent, depends, options) { - this.id = util.randomUUID(); - this.parent = parent; - this.depends = depends; - this.options = {}; +Range.prototype.getRange = function() { + return { + start: this.start, + end: this.end + }; +}; - this.setOptions(options); -} +/** + * Calculate the conversion offset and factor for current range, based on + * the provided width + * @param {Number} width + * @returns {{offset: number, factor: number}} conversion + */ +Range.prototype.conversion = function (width) { + var start = this.start; + var end = this.end; -Panel.prototype = new Component(); + return Range.conversion(this.start, this.end, width); +}; /** - * Get the container element of the panel, which can be used by a child to - * add its own widgets. - * @returns {HTMLElement} container + * Static method to calculate the conversion offset and factor for a range, + * based on the provided start, end, and width + * @param {Number} start + * @param {Number} end + * @param {Number} width + * @returns {{offset: number, factor: number}} conversion */ -Panel.prototype.getContainer = function () { - return this.frame; +Range.conversion = function (start, end, width) { + if (width != 0 && (end - start != 0)) { + return { + offset: start, + factor: width / (end - start) + } + } + else { + return { + offset: 0, + factor: 1 + }; + } }; /** - * Repaint the component - * @return {Boolean} changed + * Start moving horizontally or vertically + * @param {Event} event + * @param {Object} listener Listener containing the component and params + * @private */ -Panel.prototype.repaint = function () { - var changed = 0, - update = util.updateProperty, - asSize = util.option.asSize, - options = this.options, - frame = this.frame; - if (!frame) { - frame = document.createElement('div'); - frame.className = 'panel'; - - if (options.className) { - if (typeof options.className == 'function') { - util.addClassName(frame, String(options.className())); - } - else { - util.addClassName(frame, String(options.className)); - } - } +Range.prototype._onMouseDown = function(event, listener) { + event = event || window.event; + var params = listener.params; - this.frame = frame; - changed += 1; + // only react on left mouse button down + var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1); + if (!leftButtonDown) { + return; } - if (!frame.parentNode) { - if (!this.parent) { - throw new Error('Cannot repaint panel: no parent attached'); - } - var parentContainer = this.parent.getContainer(); - if (!parentContainer) { - throw new Error('Cannot repaint panel: parent has no container element'); - } - parentContainer.appendChild(frame); - changed += 1; + + // get mouse position + params.mouseX = util.getPageX(event); + params.mouseY = util.getPageY(event); + params.previousLeft = 0; + params.previousOffset = 0; + + params.moved = false; + params.start = this.start; + params.end = this.end; + + var frame = listener.component.frame; + if (frame) { + frame.style.cursor = 'move'; } - changed += update(frame.style, 'top', asSize(options.top, '0px')); - changed += update(frame.style, 'left', asSize(options.left, '0px')); - changed += update(frame.style, 'width', asSize(options.width, '100%')); - changed += update(frame.style, 'height', asSize(options.height, '100%')); + // add event listeners to handle moving the contents + // we store the function onmousemove and onmouseup in the timeaxis, + // so we can remove the eventlisteners lateron in the function onmouseup + var me = this; + if (!params.onMouseMove) { + params.onMouseMove = function (event) { + me._onMouseMove(event, listener); + }; + util.addEventListener(document, "mousemove", params.onMouseMove); + } + if (!params.onMouseUp) { + params.onMouseUp = function (event) { + me._onMouseUp(event, listener); + }; + util.addEventListener(document, "mouseup", params.onMouseUp); + } - return (changed > 0); + util.preventDefault(event); }; /** - * Reflow the component - * @return {Boolean} resized + * Perform moving operating. + * This function activated from within the funcion TimeAxis._onMouseDown(). + * @param {Event} event + * @param {Object} listener + * @private */ -Panel.prototype.reflow = function () { - var changed = 0, - update = util.updateProperty, - frame = this.frame; +Range.prototype._onMouseMove = function (event, listener) { + event = event || window.event; - if (frame) { - changed += update(this, 'top', frame.offsetTop); - changed += update(this, 'left', frame.offsetLeft); - changed += update(this, 'width', frame.offsetWidth); - changed += update(this, 'height', frame.offsetHeight); - } - else { - changed += 1; - } + var params = listener.params; - return (changed > 0); -}; + // calculate change in mouse position + var mouseX = util.getPageX(event); + var mouseY = util.getPageY(event); -// exports -module.exports = exports = Panel; + if (params.mouseX == undefined) { + params.mouseX = mouseX; + } + if (params.mouseY == undefined) { + params.mouseY = mouseY; + } -},{"../util":8,"./component":9}],11:[function(require,module,exports){ -var util = require('../util'), - Panel = require('./panel'); + var diffX = mouseX - params.mouseX; + var diffY = mouseY - params.mouseY; + var diff = (listener.direction == 'horizontal') ? diffX : diffY; -/** - * A root panel can hold components. The root panel must be initialized with - * a DOM element as container. - * @param {HTMLElement} container - * @param {Object} [options] Available parameters: see RootPanel.setOptions. - * @constructor RootPanel - * @extends Panel - */ -function RootPanel(container, options) { - this.id = util.randomUUID(); - this.container = container; - this.options = { - autoResize: true - }; + // if mouse movement is big enough, register it as a "moved" event + if (Math.abs(diff) >= 1) { + params.moved = true; + } - this.listeners = {}; // event listeners + var interval = (params.end - params.start); + var width = (listener.direction == 'horizontal') ? + listener.component.width : listener.component.height; + var diffRange = -diff / width * interval; + this._applyRange(params.start + diffRange, params.end + diffRange); - this.setOptions(options); -} + // fire a rangechange event + this._trigger('rangechange'); -RootPanel.prototype = new Panel(); + util.preventDefault(event); +}; /** - * Set options. Will extend the current options. - * @param {Object} [options] Available parameters: - * {String | function} [className] - * {String | Number | function} [left] - * {String | Number | function} [top] - * {String | Number | function} [width] - * {String | Number | function} [height] - * {String | Number | function} [height] - * {Boolean | function} [autoResize] + * Stop moving operating. + * This function activated from within the function Range._onMouseDown(). + * @param {event} event + * @param {Object} listener + * @private */ -RootPanel.prototype.setOptions = function (options) { - util.extend(this.options, options); +Range.prototype._onMouseUp = function (event, listener) { + event = event || window.event; - if (this.options.autoResize) { - this._watch(); + var params = listener.params; + + if (listener.component.frame) { + listener.component.frame.style.cursor = 'auto'; } - else { - this._unwatch(); + + // remove event listeners here, important for Safari + if (params.onMouseMove) { + util.removeEventListener(document, "mousemove", params.onMouseMove); + params.onMouseMove = null; + } + if (params.onMouseUp) { + util.removeEventListener(document, "mouseup", params.onMouseUp); + params.onMouseUp = null; + } + //util.preventDefault(event); + + if (params.moved) { + // fire a rangechanged event + this._trigger('rangechanged'); } }; /** - * Repaint the component - * @return {Boolean} changed + * Event handler for mouse wheel event, used to zoom + * Code from http://adomas.org/javascript-mouse-wheel/ + * @param {Event} event + * @param {Object} listener + * @private */ -RootPanel.prototype.repaint = function () { - var changed = 0, - update = util.updateProperty, - asSize = util.option.asSize, - options = this.options, - frame = this.frame; - if (!frame) { - frame = document.createElement('div'); - frame.className = 'graph panel'; - - if (options.className) { - util.addClassName(frame, util.option.asString(options.className)); - } +Range.prototype._onMouseWheel = function(event, listener) { + event = event || window.event; - this.frame = frame; - changed += 1; - } - if (!frame.parentNode) { - if (!this.container) { - throw new Error('Cannot repaint root panel: no container attached'); - } - this.container.appendChild(frame); - changed += 1; + // retrieve delta + var delta = 0; + if (event.wheelDelta) { /* IE/Opera. */ + delta = event.wheelDelta / 120; + } else if (event.detail) { /* Mozilla case. */ + // In Mozilla, sign of delta is different than in IE. + // Also, delta is multiple of 3. + delta = -event.detail / 3; } - changed += update(frame.style, 'top', asSize(options.top, '0px')); - changed += update(frame.style, 'left', asSize(options.left, '0px')); - changed += update(frame.style, 'width', asSize(options.width, '100%')); - changed += update(frame.style, 'height', asSize(options.height, '100%')); + // If delta is nonzero, handle it. + // Basically, delta is now positive if wheel was scrolled up, + // and negative, if wheel was scrolled down. + if (delta) { + var me = this; + var zoom = function () { + // perform the zoom action. Delta is normally 1 or -1 + var zoomFactor = delta / 5.0; + var zoomAround = null; + var frame = listener.component.frame; + if (frame) { + var size, conversion; + if (listener.direction == 'horizontal') { + size = listener.component.width; + conversion = me.conversion(size); + var frameLeft = util.getAbsoluteLeft(frame); + var mouseX = util.getPageX(event); + zoomAround = (mouseX - frameLeft) / conversion.factor + conversion.offset; + } + else { + size = listener.component.height; + conversion = me.conversion(size); + var frameTop = util.getAbsoluteTop(frame); + var mouseY = util.getPageY(event); + zoomAround = ((frameTop + size - mouseY) - frameTop) / conversion.factor + conversion.offset; + } + } - this._updateEventEmitters(); + me.zoom(zoomFactor, zoomAround); + }; - return (changed > 0); + zoom(); + } + + // Prevent default actions caused by mouse wheel. + // That might be ugly, but we handle scrolls somehow + // anyway, so don't bother here... + util.preventDefault(event); }; + /** - * Reflow the component - * @return {Boolean} resized + * Zoom the range the given zoomfactor in or out. Start and end date will + * be adjusted, and the timeline will be redrawn. You can optionally give a + * date around which to zoom. + * For example, try zoomfactor = 0.1 or -0.1 + * @param {Number} zoomFactor Zooming amount. Positive value will zoom in, + * negative value will zoom out + * @param {Number} zoomAround Value around which will be zoomed. Optional */ -RootPanel.prototype.reflow = function () { - var changed = 0, - update = util.updateProperty, - frame = this.frame; +Range.prototype.zoom = function(zoomFactor, zoomAround) { + // if zoomAroundDate is not provided, take it half between start Date and end Date + if (zoomAround == null) { + zoomAround = (this.start + this.end) / 2; + } - if (frame) { - changed += update(this, 'top', frame.offsetTop); - changed += update(this, 'left', frame.offsetLeft); - changed += update(this, 'width', frame.offsetWidth); - changed += update(this, 'height', frame.offsetHeight); + // prevent zoom factor larger than 1 or smaller than -1 (larger than 1 will + // result in a start>=end ) + if (zoomFactor >= 1) { + zoomFactor = 0.9; } - else { - changed += 1; + if (zoomFactor <= -1) { + zoomFactor = -0.9; + } + + // adjust a negative factor such that zooming in with 0.1 equals zooming + // out with a factor -0.1 + if (zoomFactor < 0) { + zoomFactor = zoomFactor / (1 + zoomFactor); } - return (changed > 0); + // zoom start and end relative to the zoomAround value + var startDiff = (this.start - zoomAround); + var endDiff = (this.end - zoomAround); + + // calculate new start and end + var newStart = this.start - startDiff * zoomFactor; + var newEnd = this.end - endDiff * zoomFactor; + + this.setRange(newStart, newEnd); }; /** - * Watch for changes in the size of the frame. On resize, the Panel will - * automatically redraw itself. - * @private + * Move the range with a given factor to the left or right. Start and end + * value will be adjusted. For example, try moveFactor = 0.1 or -0.1 + * @param {Number} moveFactor Moving amount. Positive value will move right, + * negative value will move left */ -RootPanel.prototype._watch = function () { - var me = this; +Range.prototype.move = function(moveFactor) { + // zoom start Date and end Date relative to the zoomAroundDate + var diff = (this.end - this.start); - this._unwatch(); + // apply new values + var newStart = this.start + diff * moveFactor; + var newEnd = this.end + diff * moveFactor; - var checkSize = function () { - if (!me.options.autoResize) { - // stop watching when the option autoResize is changed to false - me._unwatch(); - return; - } + // TODO: reckon with min and max range - if (me.frame) { - // check whether the frame is resized - if ((me.frame.clientWidth != me.width) || - (me.frame.clientHeight != me.height)) { - me.requestReflow(); - } - } - }; + this.start = newStart; + this.end = newEnd; +}; - // TODO: automatically cleanup the event listener when the frame is deleted - util.addEventListener(window, 'resize', checkSize); +/** + * @constructor Controller + * + * A Controller controls the reflows and repaints of all visual components + */ +function Controller () { + this.id = util.randomUUID(); + this.components = {}; - this.watchTimer = setInterval(checkSize, 1000); -}; + this.repaintTimer = undefined; + this.reflowTimer = undefined; +} /** - * Stop watching for a resize of the frame. - * @private + * Add a component to the controller + * @param {Component | Controller} component */ -RootPanel.prototype._unwatch = function () { - if (this.watchTimer) { - clearInterval(this.watchTimer); - this.watchTimer = undefined; +Controller.prototype.add = function (component) { + // validate the component + if (component.id == undefined) { + throw new Error('Component has no field id'); + } + if (!(component instanceof Component) && !(component instanceof Controller)) { + throw new TypeError('Component must be an instance of ' + + 'prototype Component or Controller'); } - // TODO: remove event listener on window.resize + // add the component + component.controller = this; + this.components[component.id] = component; }; /** - * Event handler - * @param {String} event name of the event, for example 'click', 'mousemove' - * @param {function} callback callback handler, invoked with the raw HTML Event - * as parameter. + * Request a reflow. The controller will schedule a reflow */ -RootPanel.prototype.on = function (event, callback) { - // register the listener at this component - var arr = this.listeners[event]; - if (!arr) { - arr = []; - this.listeners[event] = arr; +Controller.prototype.requestReflow = function () { + if (!this.reflowTimer) { + var me = this; + this.reflowTimer = setTimeout(function () { + me.reflowTimer = undefined; + me.reflow(); + }, 0); } - arr.push(callback); - - this._updateEventEmitters(); }; /** - * Update the event listeners for all event emitters - * @private + * Request a repaint. The controller will schedule a repaint */ -RootPanel.prototype._updateEventEmitters = function () { - if (this.listeners) { +Controller.prototype.requestRepaint = function () { + if (!this.repaintTimer) { var me = this; - util.forEach(this.listeners, function (listeners, event) { - if (!me.emitters) { - me.emitters = {}; + this.repaintTimer = setTimeout(function () { + me.repaintTimer = undefined; + me.repaint(); + }, 0); + } +}; + +/** + * Repaint all components + */ +Controller.prototype.repaint = function () { + var changed = false; + + // cancel any running repaint request + if (this.repaintTimer) { + clearTimeout(this.repaintTimer); + this.repaintTimer = undefined; + } + + var done = {}; + + function repaint(component, id) { + if (!(id in done)) { + // first repaint the components on which this component is dependent + if (component.depends) { + component.depends.forEach(function (dep) { + repaint(dep, dep.id); + }); } - if (!(event in me.emitters)) { - // create event - var frame = me.frame; - if (frame) { - //console.log('Created a listener for event ' + event + ' on component ' + me.id); // TODO: cleanup logging - var callback = function(event) { - listeners.forEach(function (listener) { - // TODO: filter on event target! - listener(event); - }); - }; - me.emitters[event] = callback; - util.addEventListener(frame, event, callback); - } + if (component.parent) { + repaint(component.parent, component.parent.id); } - }); - // TODO: be able to delete event listeners - // TODO: be able to move event listeners to a parent when available + // repaint the component itself and mark as done + changed = component.repaint() || changed; + done[id] = true; + } } -}; -// exports -module.exports = exports = RootPanel; + util.forEach(this.components, repaint); -},{"./panel":10,"../util":8}],12:[function(require,module,exports){ -var util = require('../util'), - DataSet = require('../dataset'), - Panel = require('./panel'), - Stack = require('../stack'), - ItemBox = require('./item/itembox'), - ItemRange = require('./item/itemrange'), - ItemPoint = require('./item/itempoint'); + // immediately reflow when needed + if (changed) { + this.reflow(); + } + // TODO: limit the number of nested reflows/repaints, prevent loop +}; /** - * An ItemSet holds a set of items and ranges which can be displayed in a - * range. The width is determined by the parent of the ItemSet, and the height - * is determined by the size of the items. - * @param {Component} parent - * @param {Component[]} [depends] Components on which this components depends - * (except for the parent) - * @param {Object} [options] See ItemSet.setOptions for the available - * options. - * @constructor ItemSet - * @extends Panel + * Reflow all components */ -function ItemSet(parent, depends, options) { - this.id = util.randomUUID(); - this.parent = parent; - this.depends = depends; +Controller.prototype.reflow = function () { + var resized = false; - // one options object is shared by this itemset and all its items - this.options = { - style: 'box', - align: 'center', - orientation: 'bottom', - margin: { - axis: 20, - item: 10 - }, - padding: 5 - }; + // cancel any running repaint request + if (this.reflowTimer) { + clearTimeout(this.reflowTimer); + this.reflowTimer = undefined; + } - this.dom = {}; + var done = {}; - var me = this; - this.data = null; // DataSet - this.range = null; // Range or Object {start: number, end: number} - this.listeners = { - 'add': function (event, params) { - me._onAdd(params.items); - }, - 'update': function (event, params) { - me._onUpdate(params.items); - }, - 'remove': function (event, params) { - me._onRemove(params.items); + function reflow(component, id) { + if (!(id in done)) { + // first reflow the components on which this component is dependent + if (component.depends) { + component.depends.forEach(function (dep) { + reflow(dep, dep.id); + }); + } + if (component.parent) { + reflow(component.parent, component.parent.id); + } + + // reflow the component itself and mark as done + resized = component.reflow() || resized; + done[id] = true; } - }; + } - this.items = {}; - this.queue = {}; // queue with items to be added/updated/removed - this.stack = new Stack(this); - this.conversion = null; + util.forEach(this.components, reflow); + + // immediately repaint when needed + if (resized) { + this.repaint(); + } + // TODO: limit the number of nested reflows/repaints, prevent loop +}; + +/** + * Prototype for visual components + */ +function Component () { + this.id = null; + this.parent = null; + this.depends = null; + this.controller = null; + this.options = null; - this.setOptions(options); + this.frame = null; // main DOM element + this.top = 0; + this.left = 0; + this.width = 0; + this.height = 0; } -ItemSet.prototype = new Panel(); +/** + * Set parameters for the frame. Parameters will be merged in current parameter + * set. + * @param {Object} options Available parameters: + * {String | function} [className] + * {String | Number | function} [left] + * {String | Number | function} [top] + * {String | Number | function} [width] + * {String | Number | function} [height] + */ +Component.prototype.setOptions = function(options) { + if (options) { + util.extend(this.options, options); + } -// available item types will be registered here -ItemSet.types = { - box: ItemBox, - range: ItemRange, - point: ItemPoint + if (this.controller) { + this.requestRepaint(); + this.requestReflow(); + } }; /** - * Set options for the ItemSet. Existing options will be extended/overwritten. - * @param {Object} [options] The following options are available: - * {String | function} [className] - * class name for the itemset - * {String} [style] - * Default style for the items. Choose from 'box' - * (default), 'point', or 'range'. The default - * Style can be overwritten by individual items. - * {String} align - * Alignment for the items, only applicable for - * ItemBox. Choose 'center' (default), 'left', or - * 'right'. - * {String} orientation - * Orientation of the item set. Choose 'top' or - * 'bottom' (default). - * {Number} margin.axis - * Margin between the axis and the items in pixels. - * Default is 20. - * {Number} margin.item - * Margin between items in pixels. Default is 10. - * {Number} padding - * Padding of the contents of an item in pixels. - * Must correspond with the items css. Default is 5. + * Get the container element of the component, which can be used by a child to + * add its own widgets. Not all components do have a container for childs, in + * that case null is returned. + * @returns {HTMLElement | null} container */ -ItemSet.prototype.setOptions = function (options) { - util.extend(this.options, options); - - // TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis - - this.stack.setOptions(this.options); +Component.prototype.getContainer = function () { + // should be implemented by the component + return null; }; /** - * Set range (start and end). - * @param {Range | Object} range A Range or an object containing start and end. + * Get the frame element of the component, the outer HTML DOM element. + * @returns {HTMLElement | null} frame */ -ItemSet.prototype.setRange = function (range) { - if (!(range instanceof Range) && (!range || !range.start || !range.end)) { - throw new TypeError('Range must be an instance of Range, ' + - 'or an object containing start and end.'); - } - this.range = range; +Component.prototype.getFrame = function () { + return this.frame; }; /** * Repaint the component * @return {Boolean} changed */ -ItemSet.prototype.repaint = function () { - var changed = 0, - update = util.updateProperty, - asSize = util.option.asSize, - options = this.options, - frame = this.frame; - - if (!frame) { - frame = document.createElement('div'); - frame.className = 'itemset'; - - if (options.className) { - util.addClassName(frame, util.option.asString(options.className)); - } - - // create background panel - var background = document.createElement('div'); - background.className = 'background'; - frame.appendChild(background); - this.dom.background = background; - - // create foreground panel - var foreground = document.createElement('div'); - foreground.className = 'foreground'; - frame.appendChild(foreground); - this.dom.foreground = foreground; - - // create axis panel - var axis = document.createElement('div'); - axis.className = 'itemset-axis'; - //frame.appendChild(axis); - this.dom.axis = axis; +Component.prototype.repaint = function () { + // should be implemented by the component + return false; +}; - this.frame = frame; - changed += 1; - } +/** + * Reflow the component + * @return {Boolean} resized + */ +Component.prototype.reflow = function () { + // should be implemented by the component + return false; +}; - if (!this.parent) { - throw new Error('Cannot repaint itemset: no parent attached'); +/** + * Request a repaint. The controller will schedule a repaint + */ +Component.prototype.requestRepaint = function () { + if (this.controller) { + this.controller.requestRepaint(); } - var parentContainer = this.parent.getContainer(); - if (!parentContainer) { - throw new Error('Cannot repaint itemset: parent has no container element'); + else { + throw new Error('Cannot request a repaint: no controller configured'); + // TODO: just do a repaint when no parent is configured? } - if (!frame.parentNode) { - parentContainer.appendChild(frame); - changed += 1; +}; + +/** + * Request a reflow. The controller will schedule a reflow + */ +Component.prototype.requestReflow = function () { + if (this.controller) { + this.controller.requestReflow(); } - if (!this.dom.axis.parentNode) { - parentContainer.appendChild(this.dom.axis); - changed += 1; + else { + throw new Error('Cannot request a reflow: no controller configured'); + // TODO: just do a reflow when no parent is configured? } +}; - // reposition frame - changed += update(frame.style, 'height', asSize(options.height, this.height + 'px')); - changed += update(frame.style, 'top', asSize(options.top, '0px')); - changed += update(frame.style, 'left', asSize(options.left, '0px')); - changed += update(frame.style, 'width', asSize(options.width, '100%')); - - // reposition axis - changed += update(this.dom.axis.style, 'top', asSize(options.top, '0px')); - - this._updateConversion(); - - var me = this, - queue = this.queue, - data = this.data, - items = this.items, - dataOptions = { - fields: ['id', 'start', 'end', 'content', 'type'] - }; - // TODO: copy options from the itemset itself? - // TODO: make orientation dynamically changable for the items - - // show/hide added/changed/removed items - Object.keys(queue).forEach(function (id) { - var entry = queue[id]; - var item = entry.item; - //noinspection FallthroughInSwitchStatementJS - switch (entry.action) { - case 'add': - case 'update': - var itemData = data.get(id, dataOptions); - var type = itemData.type || - (itemData.start && itemData.end && 'range') || - 'box'; - var constructor = ItemSet.types[type]; - - // TODO: how to handle items with invalid data? hide them and give a warning? or throw an error? - if (item) { - // update item - if (!constructor || !(item instanceof constructor)) { - // item type has changed, delete the item - item.visible = false; - changed += item.repaint(); - item = null; - } - else { - item.data = itemData; // TODO: create a method item.setData ? - changed += item.repaint(); - } - } - - if (!item) { - // create item - if (constructor) { - item = new constructor(me, itemData, options); - changed += item.repaint(); - } - else { - throw new TypeError('Unknown item type "' + type + '"'); - } - } - - // update lists - items[id] = item; - delete queue[id]; - break; - - case 'remove': - if (item) { - // TODO: remove dom of the item - item.visible = false; - changed += item.repaint(); - } - - // update lists - delete items[id]; - delete queue[id]; - break; - - default: - console.log('Error: unknown action "' + entry.action + '"'); - } - }); - - // reposition all items - util.forEach(this.items, function (item) { - item.reposition(); - }); - - return (changed > 0); +/** + * Event handler + * @param {String} event name of the event, for example 'click', 'mousemove' + * @param {function} callback callback handler, invoked with the raw HTML Event + * as parameter. + */ +Component.prototype.on = function (event, callback) { + // TODO: rethink the way of event delegation + if (this.parent) { + this.parent.on(event, callback); + } + else { + throw new Error('Cannot attach event: no root panel found'); + } }; /** - * Get the foreground container element - * @return {HTMLElement} foreground + * A panel can contain components + * @param {Component} [parent] + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] Available parameters: + * {String | Number | function} [left] + * {String | Number | function} [top] + * {String | Number | function} [width] + * {String | Number | function} [height] + * {String | function} [className] + * @constructor Panel + * @extends Component */ -ItemSet.prototype.getForeground = function () { - return this.dom.foreground; -}; +function Panel(parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + this.options = {}; + + this.setOptions(options); +} + +Panel.prototype = new Component(); /** - * Get the background container element - * @return {HTMLElement} background + * Get the container element of the panel, which can be used by a child to + * add its own widgets. + * @returns {HTMLElement} container */ -ItemSet.prototype.getBackground = function () { - return this.dom.background; +Panel.prototype.getContainer = function () { + return this.frame; }; /** - * Reflow the component - * @return {Boolean} resized + * Repaint the component + * @return {Boolean} changed */ -ItemSet.prototype.reflow = function () { +Panel.prototype.repaint = function () { var changed = 0, - options = this.options, update = util.updateProperty, - asNumber = util.option.asNumber, + asSize = util.option.asSize, + options = this.options, frame = this.frame; + if (!frame) { + frame = document.createElement('div'); + frame.className = 'panel'; - if (frame) { - this._updateConversion(); - - util.forEach(this.items, function (item) { - changed += item.reflow(); - }); - - // TODO: stack.update should be triggered via an event, in stack itself - // TODO: only update the stack when there are changed items - this.stack.update(); - - var maxHeight = asNumber(options.maxHeight); - var height; - if (options.height != null) { - height = frame.offsetHeight; - if (maxHeight != null) { - height = Math.min(height, maxHeight); - } - changed += update(this, 'height', height); - } - else { - // height is not specified, determine the height from the height and positioned items - var frameHeight = this.height; - height = 0; - if (options.orientation == 'top') { - util.forEach(this.items, function (item) { - height = Math.max(height, item.top + item.height); - }); + if (options.className) { + if (typeof options.className == 'function') { + util.addClassName(frame, String(options.className())); } else { - // orientation == 'bottom' - util.forEach(this.items, function (item) { - height = Math.max(height, frameHeight - item.top); - }); - } - height += options.margin.axis; - - if (maxHeight != null) { - height = Math.min(height, maxHeight); + util.addClassName(frame, String(options.className)); } + } - changed += update(this, 'height', height); + this.frame = frame; + changed += 1; + } + if (!frame.parentNode) { + if (!this.parent) { + throw new Error('Cannot repaint panel: no parent attached'); + } + var parentContainer = this.parent.getContainer(); + if (!parentContainer) { + throw new Error('Cannot repaint panel: parent has no container element'); } + parentContainer.appendChild(frame); + changed += 1; + } - // calculate height from items + changed += update(frame.style, 'top', asSize(options.top, '0px')); + changed += update(frame.style, 'left', asSize(options.left, '0px')); + changed += update(frame.style, 'width', asSize(options.width, '100%')); + changed += update(frame.style, 'height', asSize(options.height, '100%')); + + return (changed > 0); +}; + +/** + * Reflow the component + * @return {Boolean} resized + */ +Panel.prototype.reflow = function () { + var changed = 0, + update = util.updateProperty, + frame = this.frame; + + if (frame) { changed += update(this, 'top', frame.offsetTop); changed += update(this, 'left', frame.offsetLeft); changed += update(this, 'width', frame.offsetWidth); + changed += update(this, 'height', frame.offsetHeight); } else { changed += 1; @@ -3150,180 +2994,205 @@ ItemSet.prototype.reflow = function () { }; /** - * Set data - * @param {DataSet | Array | DataTable} data + * A root panel can hold components. The root panel must be initialized with + * a DOM element as container. + * @param {HTMLElement} container + * @param {Object} [options] Available parameters: see RootPanel.setOptions. + * @constructor RootPanel + * @extends Panel */ -ItemSet.prototype.setData = function(data) { - // unsubscribe from current dataset - var current = this.data; - if (current) { - util.forEach(this.listeners, function (callback, event) { - current.unsubscribe(event, callback); - }); - } - - if (data instanceof DataSet) { - this.data = data; - } - else { - this.data = new DataSet({ - fieldTypes: { - start: 'Date', - end: 'Date' - } - }); - this.data.add(data); - } +function RootPanel(container, options) { + this.id = util.randomUUID(); + this.container = container; + this.options = { + autoResize: true + }; - var id = this.id; - var me = this; - util.forEach(this.listeners, function (callback, event) { - me.data.subscribe(event, callback, id); - }); + this.listeners = {}; // event listeners - var dataItems = this.data.get({filter: ['id']}); - var ids = []; - util.forEach(dataItems, function (dataItem, index) { - ids[index] = dataItem.id; - }); - this._onAdd(ids); -}; + this.setOptions(options); +} +RootPanel.prototype = new Panel(); /** - * Get the data range of the item set. - * @returns {{min: Date, max: Date}} range A range with a start and end Date. - * When no minimum is found, min==null - * When no maximum is found, max==null + * Set options. Will extend the current options. + * @param {Object} [options] Available parameters: + * {String | function} [className] + * {String | Number | function} [left] + * {String | Number | function} [top] + * {String | Number | function} [width] + * {String | Number | function} [height] + * {String | Number | function} [height] + * {Boolean | function} [autoResize] */ -ItemSet.prototype.getDataRange = function () { - // calculate min from start filed - var data = this.data; - var min = data.min('start'); - min = min ? min.start.valueOf() : null; - - // calculate max of both start and end fields - var maxStart = data.max('start'); - var maxEnd = data.max('end'); - maxStart = maxStart ? maxStart.start.valueOf() : null; - maxEnd = maxEnd ? maxEnd.end.valueOf() : null; - var max = Math.max(maxStart, maxEnd); +RootPanel.prototype.setOptions = function (options) { + util.extend(this.options, options); - return { - min: new Date(min), - max: new Date(max) - }; + if (this.options.autoResize) { + this._watch(); + } + else { + this._unwatch(); + } }; /** - * Handle updated items - * @param {Number[]} ids - * @private + * Repaint the component + * @return {Boolean} changed */ -ItemSet.prototype._onUpdate = function(ids) { - this._toQueue(ids, 'update'); +RootPanel.prototype.repaint = function () { + var changed = 0, + update = util.updateProperty, + asSize = util.option.asSize, + options = this.options, + frame = this.frame; + if (!frame) { + frame = document.createElement('div'); + frame.className = 'graph panel'; + + if (options.className) { + util.addClassName(frame, util.option.asString(options.className)); + } + + this.frame = frame; + changed += 1; + } + if (!frame.parentNode) { + if (!this.container) { + throw new Error('Cannot repaint root panel: no container attached'); + } + this.container.appendChild(frame); + changed += 1; + } + + changed += update(frame.style, 'top', asSize(options.top, '0px')); + changed += update(frame.style, 'left', asSize(options.left, '0px')); + changed += update(frame.style, 'width', asSize(options.width, '100%')); + changed += update(frame.style, 'height', asSize(options.height, '100%')); + + this._updateEventEmitters(); + + return (changed > 0); }; /** - * Handle changed items - * @param {Number[]} ids - * @private + * Reflow the component + * @return {Boolean} resized */ -ItemSet.prototype._onAdd = function(ids) { - this._toQueue(ids, 'add'); +RootPanel.prototype.reflow = function () { + var changed = 0, + update = util.updateProperty, + frame = this.frame; + + if (frame) { + changed += update(this, 'top', frame.offsetTop); + changed += update(this, 'left', frame.offsetLeft); + changed += update(this, 'width', frame.offsetWidth); + changed += update(this, 'height', frame.offsetHeight); + } + else { + changed += 1; + } + + return (changed > 0); }; /** - * Handle removed items - * @param {Number[]} ids + * Watch for changes in the size of the frame. On resize, the Panel will + * automatically redraw itself. * @private */ -ItemSet.prototype._onRemove = function(ids) { - this._toQueue(ids, 'remove'); -}; +RootPanel.prototype._watch = function () { + var me = this; -/** - * Put items in the queue to be added/updated/remove - * @param {Number[]} ids - * @param {String} action can be 'add', 'update', 'remove' - */ -ItemSet.prototype._toQueue = function (ids, action) { - var items = this.items; - var queue = this.queue; - ids.forEach(function (id) { - var entry = queue[id]; - if (entry) { - // already queued, update the action of the entry - entry.action = action; + this._unwatch(); + + var checkSize = function () { + if (!me.options.autoResize) { + // stop watching when the option autoResize is changed to false + me._unwatch(); + return; } - else { - // not yet queued, add an entry to the queue - queue[id] = { - item: items[id] || null, - action: action - }; + + if (me.frame) { + // check whether the frame is resized + if ((me.frame.clientWidth != me.width) || + (me.frame.clientHeight != me.height)) { + me.requestReflow(); + } } - }); + }; - if (this.controller) { - //this.requestReflow(); - this.requestRepaint(); - } + // TODO: automatically cleanup the event listener when the frame is deleted + util.addEventListener(window, 'resize', checkSize); + + this.watchTimer = setInterval(checkSize, 1000); }; /** - * Calculate the factor and offset to convert a position on screen to the - * corresponding date and vice versa. - * After the method _updateConversion is executed once, the methods toTime - * and toScreen can be used. + * Stop watching for a resize of the frame. * @private */ -ItemSet.prototype._updateConversion = function() { - var range = this.range; - if (!range) { - throw new Error('No range configured'); +RootPanel.prototype._unwatch = function () { + if (this.watchTimer) { + clearInterval(this.watchTimer); + this.watchTimer = undefined; } - if (range.conversion) { - this.conversion = range.conversion(this.width); - } - else { - this.conversion = Range.conversion(range.start, range.end, this.width); - } + // TODO: remove event listener on window.resize }; /** - * Convert a position on screen (pixels) to a datetime - * Before this method can be used, the method _updateConversion must be - * executed once. - * @param {int} x Position on the screen in pixels - * @return {Date} time The datetime the corresponds with given position x + * Event handler + * @param {String} event name of the event, for example 'click', 'mousemove' + * @param {function} callback callback handler, invoked with the raw HTML Event + * as parameter. */ -ItemSet.prototype.toTime = function(x) { - var conversion = this.conversion; - return new Date(x / conversion.factor + conversion.offset); +RootPanel.prototype.on = function (event, callback) { + // register the listener at this component + var arr = this.listeners[event]; + if (!arr) { + arr = []; + this.listeners[event] = arr; + } + arr.push(callback); + + this._updateEventEmitters(); }; /** - * Convert a datetime (Date object) into a position on the screen - * Before this method can be used, the method _updateConversion 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. + * Update the event listeners for all event emitters + * @private */ -ItemSet.prototype.toScreen = function(time) { - var conversion = this.conversion; - return (time.valueOf() - conversion.offset) * conversion.factor; -}; - -// exports -module.exports = exports = ItemSet; +RootPanel.prototype._updateEventEmitters = function () { + if (this.listeners) { + var me = this; + util.forEach(this.listeners, function (listeners, event) { + if (!me.emitters) { + me.emitters = {}; + } + if (!(event in me.emitters)) { + // create event + var frame = me.frame; + if (frame) { + //console.log('Created a listener for event ' + event + ' on component ' + me.id); // TODO: cleanup logging + var callback = function(event) { + listeners.forEach(function (listener) { + // TODO: filter on event target! + listener(event); + }); + }; + me.emitters[event] = callback; + util.addEventListener(frame, event, callback); + } + } + }); -},{"../util":8,"../dataset":3,"./panel":10,"../stack":6,"./item/itembox":15,"./item/itemrange":16,"./item/itempoint":17}],13:[function(require,module,exports){ -var util = require('../util'), - TimeStep = require('../timestep'), - Component = require('./component'); + // TODO: be able to delete event listeners + // TODO: be able to move event listeners to a parent when available + } +}; /** * A horizontal time axis @@ -3798,522 +3667,597 @@ TimeAxis.prototype.reflow = function () { props.minorLineHeight = Math.max(parentHeight - props.majorLabelHeight - this.top); props.minorLineWidth = 1; // TODO: really calculate width - props.majorLineTop = 0; - props.majorLineHeight = Math.max(parentHeight - this.top); - props.majorLineWidth = 1; // TODO: really calculate width + props.majorLineTop = 0; + props.majorLineHeight = Math.max(parentHeight - this.top); + props.majorLineWidth = 1; // TODO: really calculate width + + props.lineTop = props.majorLabelHeight + props.minorLabelHeight; + + break; + + default: + throw new Error('Unkown orientation "' + this.options.orientation + '"'); + } + + var height = props.minorLabelHeight + props.majorLabelHeight; + changed += update(this, 'width', frame.offsetWidth); + changed += update(this, 'height', height); + + // calculate range and step + this._updateConversion(); + + var start = util.cast(range.start, 'Date'), + end = util.cast(range.end, 'Date'), + minimumStep = this.toTime((props.minorCharWidth || 10) * 5) - this.toTime(0); + this.step = new TimeStep(start, end, minimumStep); + changed += update(props.range, 'start', start.valueOf()); + changed += update(props.range, 'end', end.valueOf()); + changed += update(props.range, 'minimumStep', minimumStep.valueOf()); + } + + return (changed > 0); +}; + +/** + * Calculate the factor and offset to convert a position on screen to the + * corresponding date and vice versa. + * After the method _updateConversion is executed once, the methods toTime + * and toScreen can be used. + * @private + */ +TimeAxis.prototype._updateConversion = function() { + var range = this.range; + if (!range) { + throw new Error('No range configured'); + } + + if (range.conversion) { + this.conversion = range.conversion(this.width); + } + else { + this.conversion = Range.conversion(range.start, range.end, this.width); + } +}; + +/** + * An ItemSet holds a set of items and ranges which can be displayed in a + * range. The width is determined by the parent of the ItemSet, and the height + * is determined by the size of the items. + * @param {Component} parent + * @param {Component[]} [depends] Components on which this components depends + * (except for the parent) + * @param {Object} [options] See ItemSet.setOptions for the available + * options. + * @constructor ItemSet + * @extends Panel + */ +function ItemSet(parent, depends, options) { + this.id = util.randomUUID(); + this.parent = parent; + this.depends = depends; + + // one options object is shared by this itemset and all its items + this.options = { + style: 'box', + align: 'center', + orientation: 'bottom', + margin: { + axis: 20, + item: 10 + }, + padding: 5 + }; + + this.dom = {}; + + var me = this; + this.data = null; // DataSet + this.range = null; // Range or Object {start: number, end: number} + this.listeners = { + 'add': function (event, params) { + me._onAdd(params.items); + }, + 'update': function (event, params) { + me._onUpdate(params.items); + }, + 'remove': function (event, params) { + me._onRemove(params.items); + } + }; + + this.items = {}; + this.queue = {}; // queue with items to be added/updated/removed + this.stack = new Stack(this); + this.conversion = null; - props.lineTop = props.majorLabelHeight + props.minorLabelHeight; + this.setOptions(options); +} - break; +ItemSet.prototype = new Panel(); - default: - throw new Error('Unkown orientation "' + this.options.orientation + '"'); - } +// available item types will be registered here +ItemSet.types = { + box: ItemBox, + range: ItemRange, + point: ItemPoint +}; - var height = props.minorLabelHeight + props.majorLabelHeight; - changed += update(this, 'width', frame.offsetWidth); - changed += update(this, 'height', height); +/** + * Set options for the ItemSet. Existing options will be extended/overwritten. + * @param {Object} [options] The following options are available: + * {String | function} [className] + * class name for the itemset + * {String} [style] + * Default style for the items. Choose from 'box' + * (default), 'point', or 'range'. The default + * Style can be overwritten by individual items. + * {String} align + * Alignment for the items, only applicable for + * ItemBox. Choose 'center' (default), 'left', or + * 'right'. + * {String} orientation + * Orientation of the item set. Choose 'top' or + * 'bottom' (default). + * {Number} margin.axis + * Margin between the axis and the items in pixels. + * Default is 20. + * {Number} margin.item + * Margin between items in pixels. Default is 10. + * {Number} padding + * Padding of the contents of an item in pixels. + * Must correspond with the items css. Default is 5. + */ +ItemSet.prototype.setOptions = function (options) { + util.extend(this.options, options); - // calculate range and step - this._updateConversion(); + // TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis - var start = util.cast(range.start, 'Date'), - end = util.cast(range.end, 'Date'), - minimumStep = this.toTime((props.minorCharWidth || 10) * 5) - this.toTime(0); - this.step = new TimeStep(start, end, minimumStep); - changed += update(props.range, 'start', start.valueOf()); - changed += update(props.range, 'end', end.valueOf()); - changed += update(props.range, 'minimumStep', minimumStep.valueOf()); - } + this.stack.setOptions(this.options); +}; - return (changed > 0); +/** + * Set range (start and end). + * @param {Range | Object} range A Range or an object containing start and end. + */ +ItemSet.prototype.setRange = function (range) { + if (!(range instanceof Range) && (!range || !range.start || !range.end)) { + throw new TypeError('Range must be an instance of Range, ' + + 'or an object containing start and end.'); + } + this.range = range; }; /** - * Calculate the factor and offset to convert a position on screen to the - * corresponding date and vice versa. - * After the method _updateConversion is executed once, the methods toTime - * and toScreen can be used. - * @private + * Repaint the component + * @return {Boolean} changed */ -TimeAxis.prototype._updateConversion = function() { - var range = this.range; - if (!range) { - throw new Error('No range configured'); +ItemSet.prototype.repaint = function () { + var changed = 0, + update = util.updateProperty, + asSize = util.option.asSize, + options = this.options, + frame = this.frame; + + if (!frame) { + frame = document.createElement('div'); + frame.className = 'itemset'; + + if (options.className) { + util.addClassName(frame, util.option.asString(options.className)); + } + + // create background panel + var background = document.createElement('div'); + background.className = 'background'; + frame.appendChild(background); + this.dom.background = background; + + // create foreground panel + var foreground = document.createElement('div'); + foreground.className = 'foreground'; + frame.appendChild(foreground); + this.dom.foreground = foreground; + + // create axis panel + var axis = document.createElement('div'); + axis.className = 'itemset-axis'; + //frame.appendChild(axis); + this.dom.axis = axis; + + this.frame = frame; + changed += 1; } - if (range.conversion) { - this.conversion = range.conversion(this.width); + if (!this.parent) { + throw new Error('Cannot repaint itemset: no parent attached'); } - else { - this.conversion = Range.conversion(range.start, range.end, this.width); + var parentContainer = this.parent.getContainer(); + if (!parentContainer) { + throw new Error('Cannot repaint itemset: parent has no container element'); + } + if (!frame.parentNode) { + parentContainer.appendChild(frame); + changed += 1; + } + if (!this.dom.axis.parentNode) { + parentContainer.appendChild(this.dom.axis); + changed += 1; } -}; -// exports -module.exports = exports = TimeAxis; + // reposition frame + changed += update(frame.style, 'height', asSize(options.height, this.height + 'px')); + changed += update(frame.style, 'top', asSize(options.top, '0px')); + changed += update(frame.style, 'left', asSize(options.left, '0px')); + changed += update(frame.style, 'width', asSize(options.width, '100%')); -},{"../util":8,"../timestep":7,"./component":9}],7:[function(require,module,exports){ -var util = require('./util'), - moment = require('moment'); + // reposition axis + changed += update(this.dom.axis.style, 'top', asSize(options.top, '0px')); - /** - * @constructor TimeStep - * The class TimeStep is an iterator for dates. You provide a start date and an - * end date. The class itself determines the best scale (step size) based on the - * provided start Date, end Date, and minimumStep. - * - * If minimumStep is provided, the step size is chosen as close as possible - * to the minimumStep but larger than minimumStep. If minimumStep is not - * provided, the scale is set to 1 DAY. - * The minimumStep should correspond with the onscreen size of about 6 characters - * - * Alternatively, you can set a scale by hand. - * After creation, you can initialize the class by executing first(). Then you - * can iterate from the start date to the end date via next(). You can check if - * the end date is reached with the function hasNext(). After each step, you can - * retrieve the current date via getCurrent(). - * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours, - * days, to years. - * - * Version: 1.2 - * - * @param {Date} [start] The start date, for example new Date(2010, 9, 21) - * or new Date(2010, 9, 21, 23, 45, 00) - * @param {Date} [end] The end date - * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds - */ -TimeStep = function(start, end, minimumStep) { - // variables - this.current = new Date(); - this._start = new Date(); - this._end = new Date(); + this._updateConversion(); - this.autoScale = true; - this.scale = TimeStep.SCALE.DAY; - this.step = 1; + var me = this, + queue = this.queue, + data = this.data, + items = this.items, + dataOptions = { + fields: ['id', 'start', 'end', 'content', 'type'] + }; + // TODO: copy options from the itemset itself? + // TODO: make orientation dynamically changable for the items - // initialize the range - this.setRange(start, end, minimumStep); -}; + // show/hide added/changed/removed items + Object.keys(queue).forEach(function (id) { + var entry = queue[id]; + var item = entry.item; + //noinspection FallthroughInSwitchStatementJS + switch (entry.action) { + case 'add': + case 'update': + var itemData = data.get(id, dataOptions); + var type = itemData.type || + (itemData.start && itemData.end && 'range') || + 'box'; + var constructor = ItemSet.types[type]; -/// enum scale -TimeStep.SCALE = { - MILLISECOND: 1, - SECOND: 2, - MINUTE: 3, - HOUR: 4, - DAY: 5, - WEEKDAY: 6, - MONTH: 7, - YEAR: 8 -}; + // TODO: how to handle items with invalid data? hide them and give a warning? or throw an error? + if (item) { + // update item + if (!constructor || !(item instanceof constructor)) { + // item type has changed, delete the item + item.visible = false; + changed += item.repaint(); + item = null; + } + else { + item.data = itemData; // TODO: create a method item.setData ? + changed += item.repaint(); + } + } + + if (!item) { + // create item + if (constructor) { + item = new constructor(me, itemData, options); + changed += item.repaint(); + } + else { + throw new TypeError('Unknown item type "' + type + '"'); + } + } + + // update lists + items[id] = item; + delete queue[id]; + break; + + case 'remove': + if (item) { + // TODO: remove dom of the item + item.visible = false; + changed += item.repaint(); + } + // update lists + delete items[id]; + delete queue[id]; + break; -/** - * Set a new range - * If minimumStep is provided, the step size is chosen as close as possible - * to the minimumStep but larger than minimumStep. If minimumStep is not - * provided, the scale is set to 1 DAY. - * The minimumStep should correspond with the onscreen size of about 6 characters - * @param {Date} start The start date and time. - * @param {Date} end The end date and time. - * @param {int} [minimumStep] Optional. Minimum step size in milliseconds - */ -TimeStep.prototype.setRange = function(start, end, minimumStep) { - if (!(start instanceof Date) || !(end instanceof Date)) { - //throw "No legal start or end date in method setRange"; - return; - } + default: + console.log('Error: unknown action "' + entry.action + '"'); + } + }); - this._start = (start != undefined) ? new Date(start.valueOf()) : new Date(); - this._end = (end != undefined) ? new Date(end.valueOf()) : new Date(); + // reposition all items + util.forEach(this.items, function (item) { + item.reposition(); + }); - if (this.autoScale) { - this.setMinimumStep(minimumStep); - } + return (changed > 0); }; /** - * Set the range iterator to the start date. + * Get the foreground container element + * @return {HTMLElement} foreground */ -TimeStep.prototype.first = function() { - this.current = new Date(this._start.valueOf()); - this.roundToMinor(); +ItemSet.prototype.getForeground = function () { + return this.dom.foreground; }; /** - * Round the current date to the first minor date value - * This must be executed once when the current date is set to start Date + * Get the background container element + * @return {HTMLElement} background */ -TimeStep.prototype.roundToMinor = function() { - // round to floor - // IMPORTANT: we have no breaks in this switch! (this is no bug) - //noinspection FallthroughInSwitchStatementJS - switch (this.scale) { - case TimeStep.SCALE.YEAR: - this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step)); - this.current.setMonth(0); - case TimeStep.SCALE.MONTH: this.current.setDate(1); - case TimeStep.SCALE.DAY: // intentional fall through - case TimeStep.SCALE.WEEKDAY: this.current.setHours(0); - case TimeStep.SCALE.HOUR: this.current.setMinutes(0); - case TimeStep.SCALE.MINUTE: this.current.setSeconds(0); - case TimeStep.SCALE.SECOND: this.current.setMilliseconds(0); - //case TimeStep.SCALE.MILLISECOND: // nothing to do for milliseconds - } - - if (this.step != 1) { - // round down to the first minor value that is a multiple of the current step size - switch (this.scale) { - case TimeStep.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break; - case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break; - case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break; - case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break; - case TimeStep.SCALE.WEEKDAY: // intentional fall through - case TimeStep.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break; - case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break; - case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break; - default: break; - } - } +ItemSet.prototype.getBackground = function () { + return this.dom.background; }; /** - * Check if the there is a next step - * @return {boolean} true if the current date has not passed the end date + * Reflow the component + * @return {Boolean} resized */ -TimeStep.prototype.hasNext = function () { - return (this.current.valueOf() <= this._end.valueOf()); -}; +ItemSet.prototype.reflow = function () { + var changed = 0, + options = this.options, + update = util.updateProperty, + asNumber = util.option.asNumber, + frame = this.frame; -/** - * Do the next step - */ -TimeStep.prototype.next = function() { - var prev = this.current.valueOf(); + if (frame) { + this._updateConversion(); - // Two cases, needed to prevent issues with switching daylight savings - // (end of March and end of October) - if (this.current.getMonth() < 6) { - switch (this.scale) { - case TimeStep.SCALE.MILLISECOND: + util.forEach(this.items, function (item) { + changed += item.reflow(); + }); - this.current = new Date(this.current.valueOf() + this.step); break; - case TimeStep.SCALE.SECOND: this.current = new Date(this.current.valueOf() + this.step * 1000); break; - case TimeStep.SCALE.MINUTE: this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break; - case TimeStep.SCALE.HOUR: - this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60); - // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...) - var h = this.current.getHours(); - this.current.setHours(h - (h % this.step)); - break; - case TimeStep.SCALE.WEEKDAY: // intentional fall through - case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break; - case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break; - case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break; - default: break; + // TODO: stack.update should be triggered via an event, in stack itself + // TODO: only update the stack when there are changed items + this.stack.update(); + + var maxHeight = asNumber(options.maxHeight); + var height; + if (options.height != null) { + height = frame.offsetHeight; + if (maxHeight != null) { + height = Math.min(height, maxHeight); + } + changed += update(this, 'height', height); + } + else { + // height is not specified, determine the height from the height and positioned items + var frameHeight = this.height; + height = 0; + if (options.orientation == 'top') { + util.forEach(this.items, function (item) { + height = Math.max(height, item.top + item.height); + }); + } + else { + // orientation == 'bottom' + util.forEach(this.items, function (item) { + height = Math.max(height, frameHeight - item.top); + }); + } + height += options.margin.axis; + + if (maxHeight != null) { + height = Math.min(height, maxHeight); + } + + changed += update(this, 'height', height); } + + // calculate height from items + changed += update(this, 'top', frame.offsetTop); + changed += update(this, 'left', frame.offsetLeft); + changed += update(this, 'width', frame.offsetWidth); } else { - switch (this.scale) { - case TimeStep.SCALE.MILLISECOND: this.current = new Date(this.current.valueOf() + this.step); break; - case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break; - case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break; - case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break; - case TimeStep.SCALE.WEEKDAY: // intentional fall through - case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break; - case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break; - case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break; - default: break; - } + changed += 1; } - if (this.step != 1) { - // round down to the correct major value - switch (this.scale) { - case TimeStep.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break; - case TimeStep.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break; - case TimeStep.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break; - case TimeStep.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break; - case TimeStep.SCALE.WEEKDAY: // intentional fall through - case TimeStep.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break; - case TimeStep.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break; - case TimeStep.SCALE.YEAR: break; // nothing to do for year - default: break; - } + return (changed > 0); +}; + +/** + * Set data + * @param {DataSet | Array | DataTable} data + */ +ItemSet.prototype.setData = function(data) { + // unsubscribe from current dataset + var current = this.data; + if (current) { + util.forEach(this.listeners, function (callback, event) { + current.unsubscribe(event, callback); + }); } - // safety mechanism: if current time is still unchanged, move to the end - if (this.current.valueOf() == prev) { - this.current = new Date(this._end.valueOf()); + if (data instanceof DataSet) { + this.data = data; + } + else { + this.data = new DataSet({ + fieldTypes: { + start: 'Date', + end: 'Date' + } + }); + this.data.add(data); } -}; + var id = this.id; + var me = this; + util.forEach(this.listeners, function (callback, event) { + me.data.subscribe(event, callback, id); + }); -/** - * Get the current datetime - * @return {Date} current The current date - */ -TimeStep.prototype.getCurrent = function() { - return this.current; + var dataItems = this.data.get({filter: ['id']}); + var ids = []; + util.forEach(dataItems, function (dataItem, index) { + ids[index] = dataItem.id; + }); + this._onAdd(ids); }; + /** - * 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 {TimeStep.SCALE} newScale - * A scale. Choose from SCALE.MILLISECOND, - * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR, - * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH, - * SCALE.YEAR. - * @param {Number} newStep A step size, by default 1. Choose for - * example 1, 2, 5, or 10. + * Get the data range of the item set. + * @returns {{min: Date, max: Date}} range A range with a start and end Date. + * When no minimum is found, min==null + * When no maximum is found, max==null */ -TimeStep.prototype.setScale = function(newScale, newStep) { - this.scale = newScale; - - if (newStep > 0) { - this.step = newStep; - } +ItemSet.prototype.getDataRange = function () { + // calculate min from start filed + var data = this.data; + var min = data.min('start'); + min = min ? min.start.valueOf() : null; - this.autoScale = false; + // calculate max of both start and end fields + var maxStart = data.max('start'); + var maxEnd = data.max('end'); + maxStart = maxStart ? maxStart.start.valueOf() : null; + maxEnd = maxEnd ? maxEnd.end.valueOf() : null; + var max = Math.max(maxStart, maxEnd); + + return { + min: new Date(min), + max: new Date(max) + }; }; /** - * Enable or disable autoscaling - * @param {boolean} enable If true, autoascaling is set true + * Handle updated items + * @param {Number[]} ids + * @private */ -TimeStep.prototype.setAutoScale = function (enable) { - this.autoScale = enable; +ItemSet.prototype._onUpdate = function(ids) { + this._toQueue(ids, 'update'); }; - /** - * Automatically determine the scale that bests fits the provided minimum step - * @param {Number} minimumStep The minimum step size in milliseconds + * Handle changed items + * @param {Number[]} ids + * @private */ -TimeStep.prototype.setMinimumStep = function(minimumStep) { - if (minimumStep == undefined) { - return; - } - - var stepYear = (1000 * 60 * 60 * 24 * 30 * 12); - var stepMonth = (1000 * 60 * 60 * 24 * 30); - var stepDay = (1000 * 60 * 60 * 24); - var stepHour = (1000 * 60 * 60); - var stepMinute = (1000 * 60); - var stepSecond = (1000); - var stepMillisecond= (1); +ItemSet.prototype._onAdd = function(ids) { + this._toQueue(ids, 'add'); +}; - // find the smallest step that is larger than the provided minimumStep - if (stepYear*1000 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1000;} - if (stepYear*500 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 500;} - if (stepYear*100 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 100;} - if (stepYear*50 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 50;} - if (stepYear*10 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 10;} - if (stepYear*5 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 5;} - if (stepYear > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1;} - if (stepMonth*3 > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 3;} - if (stepMonth > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 1;} - if (stepDay*5 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 5;} - if (stepDay*2 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 2;} - if (stepDay > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 1;} - if (stepDay/2 > minimumStep) {this.scale = TimeStep.SCALE.WEEKDAY; this.step = 1;} - if (stepHour*4 > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 4;} - if (stepHour > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 1;} - if (stepMinute*15 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 15;} - if (stepMinute*10 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 10;} - if (stepMinute*5 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 5;} - if (stepMinute > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 1;} - if (stepSecond*15 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 15;} - if (stepSecond*10 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 10;} - if (stepSecond*5 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 5;} - if (stepSecond > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 1;} - if (stepMillisecond*200 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 200;} - if (stepMillisecond*100 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 100;} - if (stepMillisecond*50 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 50;} - if (stepMillisecond*10 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 10;} - if (stepMillisecond*5 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 5;} - if (stepMillisecond > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 1;} +/** + * Handle removed items + * @param {Number[]} ids + * @private + */ +ItemSet.prototype._onRemove = function(ids) { + this._toQueue(ids, 'remove'); }; /** - * Snap a date to a rounded value. The snap intervals are dependent on the - * current scale and step. - * @param {Date} date the date to be snapped + * Put items in the queue to be added/updated/remove + * @param {Number[]} ids + * @param {String} action can be 'add', 'update', 'remove' */ -TimeStep.prototype.snap = function(date) { - if (this.scale == TimeStep.SCALE.YEAR) { - var year = date.getFullYear() + Math.round(date.getMonth() / 12); - date.setFullYear(Math.round(year / this.step) * this.step); - date.setMonth(0); - date.setDate(0); - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); - date.setMilliseconds(0); - } - else if (this.scale == TimeStep.SCALE.MONTH) { - if (date.getDate() > 15) { - date.setDate(1); - date.setMonth(date.getMonth() + 1); - // important: first set Date to 1, after that change the month. +ItemSet.prototype._toQueue = function (ids, action) { + var items = this.items; + var queue = this.queue; + ids.forEach(function (id) { + var entry = queue[id]; + if (entry) { + // already queued, update the action of the entry + entry.action = action; } else { - date.setDate(1); + // not yet queued, add an entry to the queue + queue[id] = { + item: items[id] || null, + action: action + }; } + }); - date.setHours(0); - date.setMinutes(0); - date.setSeconds(0); - date.setMilliseconds(0); - } - else if (this.scale == TimeStep.SCALE.DAY || - this.scale == TimeStep.SCALE.WEEKDAY) { - //noinspection FallthroughInSwitchStatementJS - switch (this.step) { - case 5: - case 2: - date.setHours(Math.round(date.getHours() / 24) * 24); break; - default: - date.setHours(Math.round(date.getHours() / 12) * 12); break; - } - date.setMinutes(0); - date.setSeconds(0); - date.setMilliseconds(0); + if (this.controller) { + //this.requestReflow(); + this.requestRepaint(); } - else if (this.scale == TimeStep.SCALE.HOUR) { - switch (this.step) { - case 4: - date.setMinutes(Math.round(date.getMinutes() / 60) * 60); break; - default: - date.setMinutes(Math.round(date.getMinutes() / 30) * 30); break; - } - date.setSeconds(0); - date.setMilliseconds(0); - } else if (this.scale == TimeStep.SCALE.MINUTE) { - //noinspection FallthroughInSwitchStatementJS - switch (this.step) { - case 15: - case 10: - date.setMinutes(Math.round(date.getMinutes() / 5) * 5); - date.setSeconds(0); - break; - case 5: - date.setSeconds(Math.round(date.getSeconds() / 60) * 60); break; - default: - date.setSeconds(Math.round(date.getSeconds() / 30) * 30); break; - } - date.setMilliseconds(0); +}; + +/** + * Calculate the factor and offset to convert a position on screen to the + * corresponding date and vice versa. + * After the method _updateConversion is executed once, the methods toTime + * and toScreen can be used. + * @private + */ +ItemSet.prototype._updateConversion = function() { + var range = this.range; + if (!range) { + throw new Error('No range configured'); } - else if (this.scale == TimeStep.SCALE.SECOND) { - //noinspection FallthroughInSwitchStatementJS - switch (this.step) { - case 15: - case 10: - date.setSeconds(Math.round(date.getSeconds() / 5) * 5); - date.setMilliseconds(0); - break; - case 5: - date.setMilliseconds(Math.round(date.getMilliseconds() / 1000) * 1000); break; - default: - date.setMilliseconds(Math.round(date.getMilliseconds() / 500) * 500); break; - } + + if (range.conversion) { + this.conversion = range.conversion(this.width); } - else if (this.scale == TimeStep.SCALE.MILLISECOND) { - var step = this.step > 5 ? this.step / 2 : 1; - date.setMilliseconds(Math.round(date.getMilliseconds() / step) * step); + else { + this.conversion = Range.conversion(range.start, range.end, this.width); } }; /** - * Check if the current value is a major value (for example when the step - * is DAY, a major value is each first day of the MONTH) - * @return {boolean} true if current date is major, else false. + * Convert a position on screen (pixels) to a datetime + * Before this method can be used, the method _updateConversion must be + * executed once. + * @param {int} x Position on the screen in pixels + * @return {Date} time The datetime the corresponds with given position x */ -TimeStep.prototype.isMajor = function() { - switch (this.scale) { - case TimeStep.SCALE.MILLISECOND: - return (this.current.getMilliseconds() == 0); - case TimeStep.SCALE.SECOND: - return (this.current.getSeconds() == 0); - case TimeStep.SCALE.MINUTE: - return (this.current.getHours() == 0) && (this.current.getMinutes() == 0); - // Note: this is no bug. Major label is equal for both minute and hour scale - case TimeStep.SCALE.HOUR: - return (this.current.getHours() == 0); - case TimeStep.SCALE.WEEKDAY: // intentional fall through - case TimeStep.SCALE.DAY: - return (this.current.getDate() == 1); - case TimeStep.SCALE.MONTH: - return (this.current.getMonth() == 0); - case TimeStep.SCALE.YEAR: - return false; - default: - return false; - } +ItemSet.prototype.toTime = function(x) { + var conversion = this.conversion; + return new Date(x / conversion.factor + conversion.offset); }; - /** - * Returns formatted text for the minor axislabel, depending on the current - * date and the scale. For example when scale is MINUTE, the current time is - * formatted as "hh:mm". - * @param {Date} [date] custom date. if not provided, current date is taken + * Convert a datetime (Date object) into a position on the screen + * Before this method can be used, the method _updateConversion 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. */ -TimeStep.prototype.getLabelMinor = function(date) { - if (date == undefined) { - date = this.current; - } - - switch (this.scale) { - case TimeStep.SCALE.MILLISECOND: return moment(date).format('SSS'); - case TimeStep.SCALE.SECOND: return moment(date).format('s'); - case TimeStep.SCALE.MINUTE: return moment(date).format('HH:mm'); - case TimeStep.SCALE.HOUR: return moment(date).format('HH:mm'); - case TimeStep.SCALE.WEEKDAY: return moment(date).format('ddd D'); - case TimeStep.SCALE.DAY: return moment(date).format('D'); - case TimeStep.SCALE.MONTH: return moment(date).format('MMM'); - case TimeStep.SCALE.YEAR: return moment(date).format('YYYY'); - default: return ''; - } +ItemSet.prototype.toScreen = function(time) { + var conversion = this.conversion; + return (time.valueOf() - conversion.offset) * conversion.factor; }; - /** - * Returns formatted text for the major axislabel, depending on the current - * date and the scale. For example when scale is MINUTE, the major scale is - * hours, and the hour will be formatted as "hh". - * @param {Date} [date] custom date. if not provided, current date is taken + * @constructor Item + * @param {ItemSet} parent + * @param {Object} data Object containing (optional) parameters type, + * start, end, content, group, className. + * @param {Object} [options] Options to set initial property values + * // TODO: describe available options */ -TimeStep.prototype.getLabelMajor = function(date) { - if (date == undefined) { - date = this.current; - } +function Item (parent, data, options) { + this.parent = parent; + this.data = data; + this.selected = false; + this.visible = true; + this.dom = null; + this.options = options; +} - //noinspection FallthroughInSwitchStatementJS - switch (this.scale) { - case TimeStep.SCALE.MILLISECOND:return moment(date).format('HH:mm:ss'); - case TimeStep.SCALE.SECOND: return moment(date).format('D MMMM HH:mm'); - case TimeStep.SCALE.MINUTE: - case TimeStep.SCALE.HOUR: return moment(date).format('ddd D MMMM'); - case TimeStep.SCALE.WEEKDAY: - case TimeStep.SCALE.DAY: return moment(date).format('MMMM YYYY'); - case TimeStep.SCALE.MONTH: return moment(date).format('YYYY'); - case TimeStep.SCALE.YEAR: return ''; - default: return ''; - } -}; +Item.prototype = new Component(); -// exports -module.exports = exports = TimeStep; +/** + * Select current item + */ +Item.prototype.select = function () { + this.selected = true; +}; -},{"./util":8,"moment":18}],15:[function(require,module,exports){ -var util = require('../../util'), - Item = require('./item'); +/** + * Unselect current item + */ +Item.prototype.unselect = function () { + this.selected = false; +}; /** * @constructor ItemBox @@ -4588,13 +4532,215 @@ ItemBox.prototype.reposition = function () { } }; -// exports -module.exports = exports = ItemBox; - -},{"../../util":8,"./item":19}],16:[function(require,module,exports){ -var util = require('../../util'), - Item = require('./item'); - +/** + * @constructor ItemPoint + * @extends Item + * @param {ItemSet} parent + * @param {Object} data Object containing parameters start + * content, className. + * @param {Object} [options] Options to set initial property values + * // TODO: describe available options + */ +function ItemPoint (parent, data, options) { + this.props = { + dot: { + top: 0, + width: 0, + height: 0 + }, + content: { + height: 0, + marginLeft: 0 + } + }; + + Item.call(this, parent, data, options); +} + +ItemPoint.prototype = new Item (null, null); + +/** + * Select the item + * @override + */ +ItemPoint.prototype.select = function () { + this.selected = true; + // TODO: select and unselect +}; + +/** + * Unselect the item + * @override + */ +ItemPoint.prototype.unselect = function () { + this.selected = false; + // TODO: select and unselect +}; + +/** + * Repaint the item + * @return {Boolean} changed + */ +ItemPoint.prototype.repaint = function () { + // TODO: make an efficient repaint + var changed = false; + var dom = this.dom; + + if (this.visible) { + if (!dom) { + this._create(); + changed = true; + } + dom = this.dom; + + if (dom) { + if (!this.options && !this.options.parent) { + throw new Error('Cannot repaint item: no parent attached'); + } + var foreground = this.parent.getForeground(); + if (!foreground) { + throw new Error('Cannot repaint time axis: ' + + 'parent has no foreground container element'); + } + + if (!dom.point.parentNode) { + foreground.appendChild(dom.point); + foreground.appendChild(dom.point); + changed = true; + } + + // update contents + if (this.data.content != this.content) { + this.content = this.data.content; + if (this.content instanceof Element) { + dom.content.innerHTML = ''; + dom.content.appendChild(this.content); + } + else if (this.data.content != undefined) { + dom.content.innerHTML = this.content; + } + else { + throw new Error('Property "content" missing in item ' + this.data.id); + } + changed = true; + } + + // update class + var className = (this.data.className? ' ' + this.data.className : '') + + (this.selected ? ' selected' : ''); + if (this.className != className) { + this.className = className; + dom.point.className = 'item point' + className; + changed = true; + } + } + } + else { + // hide when visible + if (dom) { + if (dom.point.parentNode) { + dom.point.parentNode.removeChild(dom.point); + changed = true; + } + } + } + + return changed; +}; + +/** + * Reflow the item: calculate its actual size from the DOM + * @return {boolean} resized returns true if the axis is resized + * @override + */ +ItemPoint.prototype.reflow = function () { + if (this.data.start == undefined) { + throw new Error('Property "start" missing in item ' + this.data.id); + } + + var update = util.updateProperty, + dom = this.dom, + props = this.props, + options = this.options, + orientation = options.orientation, + start = this.parent.toScreen(this.data.start), + changed = 0, + top; + + if (dom) { + changed += update(this, 'width', dom.point.offsetWidth); + changed += update(this, 'height', dom.point.offsetHeight); + changed += update(props.dot, 'width', dom.dot.offsetWidth); + changed += update(props.dot, 'height', dom.dot.offsetHeight); + changed += update(props.content, 'height', dom.content.offsetHeight); + + if (orientation == 'top') { + top = options.margin.axis; + } + else { + // default or 'bottom' + var parentHeight = this.parent.height; + top = Math.max(parentHeight - this.height - options.margin.axis, 0); + } + changed += update(this, 'top', top); + changed += update(this, 'left', start - props.dot.width / 2); + changed += update(props.content, 'marginLeft', 1.5 * props.dot.width); + //changed += update(props.content, 'marginRight', 0.5 * props.dot.width); // TODO + + changed += update(props.dot, 'top', (this.height - props.dot.height) / 2); + } + else { + changed += 1; + } + + return (changed > 0); +}; + +/** + * Create an items DOM + * @private + */ +ItemPoint.prototype._create = function () { + var dom = this.dom; + if (!dom) { + this.dom = dom = {}; + + // background box + dom.point = document.createElement('div'); + // className is updated in repaint() + + // contents box, right from the dot + dom.content = document.createElement('div'); + dom.content.className = 'content'; + dom.point.appendChild(dom.content); + + // dot at start + dom.dot = document.createElement('div'); + dom.dot.className = 'dot'; + dom.point.appendChild(dom.dot); + } +}; + +/** + * Reposition the item, recalculate its left, top, and width, using the current + * range and size of the items itemset + * @override + */ +ItemPoint.prototype.reposition = function () { + var dom = this.dom, + props = this.props; + + if (dom) { + dom.point.style.top = this.top + 'px'; + dom.point.style.left = this.left + 'px'; + + dom.content.style.marginLeft = props.content.marginLeft + 'px'; + //dom.content.style.marginRight = props.content.marginRight + 'px'; // TODO + + dom.dot.style.top = props.dot.top + 'px'; + } +}; + /** * @constructor ItemRange * @extends Item @@ -4813,226 +4959,208 @@ ItemRange.prototype.reposition = function () { } }; -// exports -module.exports = exports = ItemRange; - -},{"../../util":8,"./item":19}],17:[function(require,module,exports){ -var util = require('../../util'), - Item = require('./item'); - /** - * @constructor ItemPoint - * @extends Item - * @param {ItemSet} parent - * @param {Object} data Object containing parameters start - * content, className. - * @param {Object} [options] Options to set initial property values - * // TODO: describe available options + * Create a timeline visualization + * @param {HTMLElement} container + * @param {DataSet | Array | DataTable} [data] + * @param {Object} [options] See Timeline.setOptions for the available options. + * @constructor */ -function ItemPoint (parent, data, options) { - this.props = { - dot: { - top: 0, - width: 0, - height: 0 - }, - content: { - height: 0, - marginLeft: 0 - } +function Timeline (container, data, options) { + var me = this; + this.options = { + orientation: 'bottom', + zoomMin: 10, // milliseconds + zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds + moveable: true, + zoomable: true }; - Item.call(this, parent, data, options); -} + // controller + this.controller = new Controller(); -ItemPoint.prototype = new Item (null, null); + // main panel + if (!container) { + throw new Error('No container element provided'); + } + this.main = new RootPanel(container, { + autoResize: false, + height: function () { + return me.timeaxis.height + me.itemset.height; + } + }); + this.controller.add(this.main); -/** - * Select the item - * @override - */ -ItemPoint.prototype.select = function () { - this.selected = true; - // TODO: select and unselect -}; + // range + var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0); + this.range = new Range({ + start: now.clone().add('days', -3).valueOf(), + end: now.clone().add('days', 4).valueOf() + }); + // TODO: reckon with options moveable and zoomable + this.range.subscribe(this.main, 'move', 'horizontal'); + this.range.subscribe(this.main, 'zoom', 'horizontal'); + this.range.on('rangechange', function () { + // TODO: fix the delay in reflow/repaint, does not feel snappy + me.controller.requestReflow(); + }); + this.range.on('rangechanged', function () { + me.controller.requestReflow(); + }); -/** - * Unselect the item - * @override - */ -ItemPoint.prototype.unselect = function () { - this.selected = false; - // TODO: select and unselect -}; + // TODO: put the listeners in setOptions, be able to dynamically change with options moveable and zoomable -/** - * Repaint the item - * @return {Boolean} changed - */ -ItemPoint.prototype.repaint = function () { - // TODO: make an efficient repaint - var changed = false; - var dom = this.dom; + // time axis + this.timeaxis = new TimeAxis(this.main, [], { + orientation: this.options.orientation, + range: this.range + }); + this.timeaxis.setRange(this.range); + this.controller.add(this.timeaxis); - if (this.visible) { - if (!dom) { - this._create(); - changed = true; - } - dom = this.dom; + // items panel + this.itemset = new ItemSet(this.main, [this.timeaxis], { + orientation: this.options.orientation + }); + this.itemset.setRange(this.range); + this.controller.add(this.itemset); - if (dom) { - if (!this.options && !this.options.parent) { - throw new Error('Cannot repaint item: no parent attached'); - } - var foreground = this.parent.getForeground(); - if (!foreground) { - throw new Error('Cannot repaint time axis: ' + - 'parent has no foreground container element'); - } + // set data + if (data) { + this.setData(data); + } - if (!dom.point.parentNode) { - foreground.appendChild(dom.point); - foreground.appendChild(dom.point); - changed = true; - } + this.setOptions(options); +} - // update contents - if (this.data.content != this.content) { - this.content = this.data.content; - if (this.content instanceof Element) { - dom.content.innerHTML = ''; - dom.content.appendChild(this.content); - } - else if (this.data.content != undefined) { - dom.content.innerHTML = this.content; - } - else { - throw new Error('Property "content" missing in item ' + this.data.id); - } - changed = true; - } +/** + * Set options + * @param {Object} options TODO: describe the available options + */ +Timeline.prototype.setOptions = function (options) { + util.extend(this.options, options); - // update class - var className = (this.data.className? ' ' + this.data.className : '') + - (this.selected ? ' selected' : ''); - if (this.className != className) { - this.className = className; - dom.point.className = 'item point' + className; - changed = true; - } + // update options the timeaxis + this.timeaxis.setOptions(this.options); + + // update options for the range + this.range.setOptions(this.options); + + // update options the itemset + var top, + me = this; + if (this.options.orientation == 'top') { + top = function () { + return me.timeaxis.height; } } else { - // hide when visible - if (dom) { - if (dom.point.parentNode) { - dom.point.parentNode.removeChild(dom.point); - changed = true; - } + top = function () { + return me.main.height - me.timeaxis.height - me.itemset.height; } } + this.itemset.setOptions({ + orientation: this.options.orientation, + top: top + }); - return changed; + this.controller.repaint(); }; /** - * Reflow the item: calculate its actual size from the DOM - * @return {boolean} resized returns true if the axis is resized - * @override + * Set data + * @param {DataSet | Array | DataTable} data */ -ItemPoint.prototype.reflow = function () { - if (this.data.start == undefined) { - throw new Error('Property "start" missing in item ' + this.data.id); - } - - var update = util.updateProperty, - dom = this.dom, - props = this.props, - options = this.options, - orientation = options.orientation, - start = this.parent.toScreen(this.data.start), - changed = 0, - top; +Timeline.prototype.setData = function(data) { + var dataset = this.itemset.data; + if (!dataset) { + // first load of data + this.itemset.setData(data); - if (dom) { - changed += update(this, 'width', dom.point.offsetWidth); - changed += update(this, 'height', dom.point.offsetHeight); - changed += update(props.dot, 'width', dom.dot.offsetWidth); - changed += update(props.dot, 'height', dom.dot.offsetHeight); - changed += update(props.content, 'height', dom.content.offsetHeight); + // apply the data range as range + var dataRange = this.itemset.getDataRange(); - if (orientation == 'top') { - top = options.margin.axis; - } - else { - // default or 'bottom' - var parentHeight = this.parent.height; - top = Math.max(parentHeight - this.height - options.margin.axis, 0); + // add 5% on both sides + var min = dataRange.min; + var max = dataRange.max; + if (min != null && max != null) { + var interval = (max.valueOf() - min.valueOf()); + min = new Date(min.valueOf() - interval * 0.05); + max = new Date(max.valueOf() + interval * 0.05); } - changed += update(this, 'top', top); - changed += update(this, 'left', start - props.dot.width / 2); - changed += update(props.content, 'marginLeft', 1.5 * props.dot.width); - //changed += update(props.content, 'marginRight', 0.5 * props.dot.width); // TODO - changed += update(props.dot, 'top', (this.height - props.dot.height) / 2); + // apply range if there is a min or max available + if (min != null || max != null) { + this.range.setRange(min, max); + } } else { - changed += 1; + // updated data + this.itemset.setData(data); } - - return (changed > 0); }; -/** - * Create an items DOM - * @private - */ -ItemPoint.prototype._create = function () { - var dom = this.dom; - if (!dom) { - this.dom = dom = {}; - - // background box - dom.point = document.createElement('div'); - // className is updated in repaint() +/** + * vis.js library exports + */ +var vis = { + util: util, + events: events, + + Controller: Controller, + DataSet: DataSet, + Range: Range, + Stack: Stack, + TimeStep: TimeStep, + + components: { + items: { + Item: Item, + ItemBox: ItemBox, + ItemPoint: ItemPoint, + ItemRange: ItemRange + }, - // contents box, right from the dot - dom.content = document.createElement('div'); - dom.content.className = 'content'; - dom.point.appendChild(dom.content); + Component: Component, + Panel: Panel, + RootPanel: RootPanel, + ItemSet: ItemSet, + TimeAxis: TimeAxis + }, - // dot at start - dom.dot = document.createElement('div'); - dom.dot.className = 'dot'; - dom.point.appendChild(dom.dot); - } + Timeline: Timeline }; /** - * Reposition the item, recalculate its left, top, and width, using the current - * range and size of the items itemset - * @override + * CommonJS module exports */ -ItemPoint.prototype.reposition = function () { - var dom = this.dom, - props = this.props; - - if (dom) { - dom.point.style.top = this.top + 'px'; - dom.point.style.left = this.left + 'px'; +if (typeof exports !== 'undefined') { + exports = vis; +} +if (typeof module !== 'undefined' && typeof module.exports !== 'undefined') { + module.exports = vis; +} - dom.content.style.marginLeft = props.content.marginLeft + 'px'; - //dom.content.style.marginRight = props.content.marginRight + 'px'; // TODO +/** + * AMD module exports + */ +if (typeof(define) === 'function') { + define(function () { + return vis; + }); +} - dom.dot.style.top = props.dot.top + 'px'; - } -}; +/** + * Window exports + */ +if (typeof window !== 'undefined') { + // attach the module to the window, load as a regular javascript file + window['vis'] = vis; +} -// exports -module.exports = exports = ItemPoint; +util.loadCss("/* vis.js stylesheet */\n\n.graph {\n position: relative;\n border: 1px solid #bfbfbf;\n}\n\n.graph .panel {\n position: absolute;\n}\n\n.graph .itemset {\n position: absolute;\n padding: 0;\n margin: 0;\n overflow: hidden;\n}\n\n.graph .background {\n}\n\n.graph .foreground {\n}\n\n.graph .itemset-axis {\n position: absolute;\n}\n\n.graph .item {\n position: absolute;\n color: #1A1A1A;\n border-color: #97B0F8;\n background-color: #D5DDF6;\n display: inline-block;\n}\n\n.graph .item.selected {\n border-color: #FFC200;\n background-color: #FFF785;\n z-index: 999;\n}\n\n.graph .item.cluster {\n /* TODO: use another color or pattern? */\n background: #97B0F8 url('img/cluster_bg.png');\n color: white;\n}\n.graph .item.cluster.point {\n border-color: #D5DDF6;\n}\n\n.graph .item.box {\n text-align: center;\n border-style: solid;\n border-width: 1px;\n border-radius: 5px;\n -moz-border-radius: 5px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.point {\n background: none;\n}\n\n.graph .dot {\n border: 5px solid #97B0F8;\n position: absolute;\n border-radius: 5px;\n -moz-border-radius: 5px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.range {\n overflow: hidden;\n border-style: solid;\n border-width: 1px;\n border-radius: 2px;\n -moz-border-radius: 2px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.range .drag-left {\n cursor: w-resize;\n z-index: 1000;\n}\n\n.graph .item.range .drag-right {\n cursor: e-resize;\n z-index: 1000;\n}\n\n.graph .item.range .content {\n position: relative;\n display: inline-block;\n}\n\n.graph .item.line {\n position: absolute;\n width: 0;\n border-left-width: 1px;\n border-left-style: solid;\n}\n\n.graph .item .content {\n margin: 5px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n/* TODO: better css name, 'graph' is way to generic */\n\n.graph {\n overflow: hidden;\n}\n\n.graph .axis {\n position: relative;\n}\n\n.graph .axis .text {\n position: absolute;\n color: #4d4d4d;\n padding: 3px;\n white-space: nowrap;\n}\n\n.graph .axis .text.measure {\n position: absolute;\n padding-left: 0;\n padding-right: 0;\n margin-left: 0;\n margin-right: 0;\n visibility: hidden;\n}\n\n.graph .axis .grid.vertical {\n position: absolute;\n width: 0;\n border-right: 1px solid;\n}\n\n.graph .axis .grid.horizontal {\n position: absolute;\n left: 0;\n width: 100%;\n height: 0;\n border-bottom: 1px solid;\n}\n\n.graph .axis .grid.minor {\n border-color: #e5e5e5;\n}\n\n.graph .axis .grid.major {\n border-color: #bfbfbf;\n}\n\n"); -},{"../../util":8,"./item":19}],18:[function(require,module,exports){ +},{"moment":2}],2:[function(require,module,exports){ (function(){// moment.js // version : 2.0.0 // author : Tim Wood @@ -6435,233 +6563,6 @@ module.exports = exports = ItemPoint; }).call(this); })() -},{}],14:[function(require,module,exports){ -var util = require('./../util'), - moment = require('moment'), - Range = require('../range'), - Controller = require('../controller'), - Component = require('../component/component'), - RootPanel = require('../component/rootpanel'), - TimeAxis = require('../component/timeaxis'), - ItemSet = require('../component/itemset'); - -/** - * Create a timeline visualization - * @param {HTMLElement} container - * @param {DataSet | Array | DataTable} [data] - * @param {Object} [options] See Timeline.setOptions for the available options. - * @constructor - */ -function Timeline (container, data, options) { - var me = this; - this.options = { - orientation: 'bottom', - zoomMin: 10, // milliseconds - zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds - moveable: true, - zoomable: true - }; - - // controller - this.controller = new Controller(); - - // main panel - if (!container) { - throw new Error('No container element provided'); - } - this.main = new RootPanel(container, { - autoResize: false, - height: function () { - return me.timeaxis.height + me.itemset.height; - } - }); - this.controller.add(this.main); - - // range - var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0); - this.range = new Range({ - start: now.clone().add('days', -3).valueOf(), - end: now.clone().add('days', 4).valueOf() - }); - // TODO: reckon with options moveable and zoomable - this.range.subscribe(this.main, 'move', 'horizontal'); - this.range.subscribe(this.main, 'zoom', 'horizontal'); - this.range.on('rangechange', function () { - // TODO: fix the delay in reflow/repaint, does not feel snappy - me.controller.requestReflow(); - }); - this.range.on('rangechanged', function () { - me.controller.requestReflow(); - }); - - // TODO: put the listeners in setOptions, be able to dynamically change with options moveable and zoomable - - // time axis - this.timeaxis = new TimeAxis(this.main, [], { - orientation: this.options.orientation, - range: this.range - }); - this.timeaxis.setRange(this.range); - this.controller.add(this.timeaxis); - - // items panel - this.itemset = new ItemSet(this.main, [this.timeaxis], { - orientation: this.options.orientation - }); - this.itemset.setRange(this.range); - this.controller.add(this.itemset); - - // set data - if (data) { - this.setData(data); - } - - this.setOptions(options); -} - -/** - * Set options - * @param {Object} options TODO: describe the available options - */ -Timeline.prototype.setOptions = function (options) { - util.extend(this.options, options); - - // update options the timeaxis - this.timeaxis.setOptions(this.options); - - // update options for the range - this.range.setOptions(this.options); - - // update options the itemset - var top, - me = this; - if (this.options.orientation == 'top') { - top = function () { - return me.timeaxis.height; - } - } - else { - top = function () { - return me.main.height - me.timeaxis.height - me.itemset.height; - } - } - this.itemset.setOptions({ - orientation: this.options.orientation, - top: top - }); - - this.controller.repaint(); -}; - -/** - * Set data - * @param {DataSet | Array | DataTable} data - */ -Timeline.prototype.setData = function(data) { - var dataset = this.itemset.data; - if (!dataset) { - // first load of data - this.itemset.setData(data); - - // apply the data range as range - var dataRange = this.itemset.getDataRange(); - - // add 5% on both sides - var min = dataRange.min; - var max = dataRange.max; - if (min != null && max != null) { - var interval = (max.valueOf() - min.valueOf()); - min = new Date(min.valueOf() - interval * 0.05); - max = new Date(max.valueOf() + interval * 0.05); - } - - // apply range if there is a min or max available - if (min != null || max != null) { - this.range.setRange(min, max); - } - } - else { - // updated data - this.itemset.setData(data); - } -}; - -// exports -module.exports = exports = Timeline; - -},{"./../util":8,"../range":5,"../controller":2,"../component/component":9,"../component/rootpanel":11,"../component/timeaxis":13,"../component/itemset":12,"moment":18}],19:[function(require,module,exports){ -var Component = require('../component'); - -/** - * @constructor Item - * @param {ItemSet} parent - * @param {Object} data Object containing (optional) parameters type, - * start, end, content, group, className. - * @param {Object} [options] Options to set initial property values - * // TODO: describe available options - */ -function Item (parent, data, options) { - this.parent = parent; - this.data = data; - this.selected = false; - this.visible = true; - this.dom = null; - this.options = options; -} - -Item.prototype = new Component(); - -/** - * Select current item - */ -Item.prototype.select = function () { - this.selected = true; -}; - -/** - * Unselect current item - */ -Item.prototype.unselect = function () { - this.selected = false; -}; - -// exports -module.exports = exports = Item; - -},{"../component":9}]},{},[1])(1) +},{}]},{},[1])(1) }); -; -/** - * AMD module exports - */ -if (typeof(define) === 'function') { - define(function () { - return vis; - }); -} - -/** - * load css from text - * @param {String} css Text containing css - */ -var loadCss = function (css) { - // get the script location, and built the css file name from the js file name - // http://stackoverflow.com/a/2161748/1262753 - var scripts = document.getElementsByTagName('script'); - // var jsFile = scripts[scripts.length-1].src.split('?')[0]; - // var cssFile = jsFile.substring(0, jsFile.length - 2) + 'css'; - - // inject css - // http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript - var style = document.createElement('style'); - style.type = 'text/css'; - if (style.styleSheet){ - style.styleSheet.cssText = css; - } else { - style.appendChild(document.createTextNode(css)); - } - - document.getElementsByTagName('head')[0].appendChild(style); -}; - -loadCss("/* vis.js stylesheet */\n\n.graph {\n position: relative;\n border: 1px solid #bfbfbf;\n}\n\n.graph .panel {\n position: absolute;\n}\n\n.graph .itemset {\n position: absolute;\n padding: 0;\n margin: 0;\n overflow: hidden;\n}\n\n.graph .background {\n}\n\n.graph .foreground {\n}\n\n.graph .itemset-axis {\n position: absolute;\n}\n\n.graph .item {\n position: absolute;\n color: #1A1A1A;\n border-color: #97B0F8;\n background-color: #D5DDF6;\n display: inline-block;\n}\n\n.graph .item.selected {\n border-color: #FFC200;\n background-color: #FFF785;\n z-index: 999;\n}\n\n.graph .item.cluster {\n /* TODO: use another color or pattern? */\n background: #97B0F8 url('img/cluster_bg.png');\n color: white;\n}\n.graph .item.cluster.point {\n border-color: #D5DDF6;\n}\n\n.graph .item.box {\n text-align: center;\n border-style: solid;\n border-width: 1px;\n border-radius: 5px;\n -moz-border-radius: 5px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.point {\n background: none;\n}\n\n.graph .dot {\n border: 5px solid #97B0F8;\n position: absolute;\n border-radius: 5px;\n -moz-border-radius: 5px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.range {\n overflow: hidden;\n border-style: solid;\n border-width: 1px;\n border-radius: 2px;\n -moz-border-radius: 2px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.range .drag-left {\n cursor: w-resize;\n z-index: 1000;\n}\n\n.graph .item.range .drag-right {\n cursor: e-resize;\n z-index: 1000;\n}\n\n.graph .item.range .content {\n position: relative;\n display: inline-block;\n}\n\n.graph .item.line {\n position: absolute;\n width: 0;\n border-left-width: 1px;\n border-left-style: solid;\n}\n\n.graph .item .content {\n margin: 5px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n/* TODO: better css name, 'graph' is way to generic */\n\n.graph {\n overflow: hidden;\n}\n\n.graph .axis {\n position: relative;\n}\n\n.graph .axis .text {\n position: absolute;\n color: #4d4d4d;\n padding: 3px;\n white-space: nowrap;\n}\n\n.graph .axis .text.measure {\n position: absolute;\n padding-left: 0;\n padding-right: 0;\n margin-left: 0;\n margin-right: 0;\n visibility: hidden;\n}\n\n.graph .axis .grid.vertical {\n position: absolute;\n width: 0;\n border-right: 1px solid;\n}\n\n.graph .axis .grid.horizontal {\n position: absolute;\n left: 0;\n width: 100%;\n height: 0;\n border-bottom: 1px solid;\n}\n\n.graph .axis .grid.minor {\n border-color: #e5e5e5;\n}\n\n.graph .axis .grid.major {\n border-color: #bfbfbf;\n}\n\n"); +; \ No newline at end of file diff --git a/vis.min.js b/vis.min.js index 6826a8fe..942abc10 100644 --- a/vis.min.js +++ b/vis.min.js @@ -5,7 +5,7 @@ * A dynamic, browser-based visualization library. * * @version 0.0.7 - * @date 2013-04-25 + * @date 2013-04-26 * * @license * Copyright (C) 2011-2013 Almende B.V, http://almende.com @@ -22,6 +22,6 @@ * License for the specific language governing permissions and limitations under * the License. */ -(function(t){if("function"==typeof bootstrap)bootstrap("vis",t);else if("object"==typeof exports)module.exports=t();else if("function"==typeof define&&define.amd)define(t);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeVis=t}else"undefined"!=typeof window?window.vis=t():global.vis=t()})(function(){var t;return function(t,e,n){function i(n,r){if(!e[n]){if(!t[n]){var s="function"==typeof require&&require;if(!r&&s)return s(n,!0);if(o)return o(n,!0);throw Error("Cannot find module '"+n+"'")}var a=e[n]={exports:{}};t[n][0].call(a.exports,function(e){var o=t[n][1][e];return i(o?o:e)},a,a.exports)}return e[n].exports}for(var o="function"==typeof require&&require,r=0;n.length>r;r++)i(n[r]);return i}({1:[function(t,e,n){var i={Controller:t("./controller"),DataSet:t("./dataset"),events:t("./events"),Range:t("./range"),Stack:t("./stack"),TimeStep:t("./timestep"),util:t("./util"),component:{item:{Item:"../../Item",ItemBox:"../../ItemBox",ItemPoint:"../../ItemPoint",ItemRange:"../../ItemRange"},Component:t("./component/component"),Panel:t("./component/panel"),RootPanel:t("./component/rootpanel"),ItemSet:t("./component/itemset"),TimeAxis:t("./component/timeaxis")},Timeline:t("./visualization/timeline")};e.exports=n=i},{"./controller":2,"./dataset":3,"./events":4,"./range":5,"./stack":6,"./timestep":7,"./util":8,"./component/component":9,"./component/panel":10,"./component/rootpanel":11,"./component/itemset":12,"./component/timeaxis":13,"./visualization/timeline":14}],8:[function(t,e,n){var i={};i.isNumber=function(t){return t instanceof Number||"number"==typeof t},i.isString=function(t){return t instanceof String||"string"==typeof t},i.isDate=function(t){if(t instanceof Date)return!0;if(i.isString(t)){var e=o.exec(t);if(e)return!0;if(!isNaN(Date.parse(t)))return!0}return!1},i.isDataTable=function(t){return"undefined"!=typeof google&&google.visualization&&google.visualization.DataTable&&t instanceof google.visualization.DataTable},i.randomUUID=function(){var t=function(){return Math.floor(65536*Math.random()).toString(16)};return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()},i.extend=function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t},i.cast=function(t,e){if(void 0===t)return void 0;if(null===t)return null;if(!e)return t;if("function"==typeof e)return e(t);switch(e){case"boolean":case"Boolean":return Boolean(t);case"number":case"Number":return Number(t);case"string":case"String":return t+"";case"Date":if(i.isNumber(t))return new Date(t);if(t instanceof Date)return new Date(t.valueOf());if(i.isString(t)){var n=o.exec(t);return n?new Date(Number(n[1])):moment(t).toDate()}throw Error("Cannot cast object of type "+i.getType(t)+" to type Date");case"ISODate":if(t instanceof Date)return t.toISOString();if(i.isNumber(t)||i.isString(t))return moment(t).toDate().toISOString();throw Error("Cannot cast object of type "+i.getType(t)+" to type ISODate");case"ASPDate":if(t instanceof Date)return"/Date("+t.valueOf()+")/";if(i.isNumber(t)||i.isString(t))return"/Date("+moment(t).valueOf()+")/";throw Error("Cannot cast object of type "+i.getType(t)+" to type ASPDate");default:throw Error("Cannot cast object of type "+i.getType(t)+' to type "'+e+'"')}};var o=/^\/?Date\((\-?\d+)/i;if(i.getType=function(t){var e=typeof t;return"object"==e?null==t?"null":t instanceof Boolean?"Boolean":t instanceof Number?"Number":t instanceof String?"String":t instanceof Array?"Array":t instanceof Date?"Date":"Object":"number"==e?"Number":"boolean"==e?"Boolean":"string"==e?"String":e},i.getAbsoluteLeft=function(t){for(var e=document.documentElement,n=document.body,i=t.offsetLeft,o=t.offsetParent;null!=o&&o!=n&&o!=e;)i+=o.offsetLeft,i-=o.scrollLeft,o=o.offsetParent;return i},i.getAbsoluteTop=function(t){for(var e=document.documentElement,n=document.body,i=t.offsetTop,o=t.offsetParent;null!=o&&o!=n&&o!=e;)i+=o.offsetTop,i-=o.scrollTop,o=o.offsetParent;return i},i.getPageY=function(t){if("pageY"in t)return t.pageY;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientY:t.clientY;var n=document.documentElement,i=document.body;return e+(n&&n.scrollTop||i&&i.scrollTop||0)-(n&&n.clientTop||i&&i.clientTop||0)},i.getPageX=function(t){if("pageY"in t)return t.pageX;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientX:t.clientX;var n=document.documentElement,i=document.body;return e+(n&&n.scrollLeft||i&&i.scrollLeft||0)-(n&&n.clientLeft||i&&i.clientLeft||0)},i.addClassName=function(t,e){var n=t.className.split(" ");-1==n.indexOf(e)&&(n.push(e),t.className=n.join(" "))},i.removeClassName=function(t,e){var n=t.className.split(" "),i=n.indexOf(e);-1!=i&&(n.splice(i,1),t.className=n.join(" "))},i.forEach=function(t,e){if(t instanceof Array)t.forEach(e);else for(var n in t)t.hasOwnProperty(n)&&e(t[n],n,t)},i.updateProperty=function(t,e,n){return t[e]!==n?(t[e]=n,!0):!1},i.addEventListener=function(t,e,n,i){t.addEventListener?(void 0===i&&(i=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.addEventListener(e,n,i)):t.attachEvent("on"+e,n)},i.removeEventListener=function(t,e,n,i){t.removeEventListener?(void 0===i&&(i=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.removeEventListener(e,n,i)):t.detachEvent("on"+e,n)},i.getTarget=function(t){t||(t=window.event);var e;return t.target?e=t.target:t.srcElement&&(e=t.srcElement),void 0!=e.nodeType&&3==e.nodeType&&(e=e.parentNode),e},i.stopPropagation=function(t){t||(t=window.event),t.stopPropagation?t.stopPropagation():t.cancelBubble=!0},i.preventDefault=function(t){t||(t=window.event),t.preventDefault?t.preventDefault():t.returnValue=!1},i.option={},i.option.asBoolean=function(t,e){return"function"==typeof t&&(t=t()),null!=t?0!=t:e||null},i.option.asNumber=function(t,e){return"function"==typeof t&&(t=t()),null!=t?Number(t):e||null},i.option.asString=function(t,e){return"function"==typeof t&&(t=t()),null!=t?t+"":e||null},i.option.asSize=function(t,e){return"function"==typeof t&&(t=t()),i.isString(t)?t:i.isNumber(t)?t+"px":e||null},i.option.asElement=function(t,e){return"function"==typeof t&&(t=t()),t||e||null},!Array.prototype.indexOf){Array.prototype.indexOf=function(t){for(var e=0;this.length>e;e++)if(this[e]==t)return e;return-1};try{console.log("Warning: Ancient browser detected. Please update your browser")}catch(r){}}Array.prototype.forEach||(Array.prototype.forEach=function(t,e){for(var n=0,i=this.length;i>n;++n)t.call(e||this,this[n],n,this)}),Array.prototype.map||(Array.prototype.map=function(t,e){var n,i,o;if(null==this)throw new TypeError(" this is null or not defined");var r=Object(this),s=r.length>>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(e&&(n=e),i=Array(s),o=0;s>o;){var a,h;o in r&&(a=r[o],h=t.call(n,a,o,r),i[o]=h),o++}return i}),Array.prototype.filter||(Array.prototype.filter=function(t){"use strict";if(null==this)throw new TypeError;var e=Object(this),n=e.length>>>0;if("function"!=typeof t)throw new TypeError;for(var i=[],o=arguments[1],r=0;n>r;r++)if(r in e){var s=e[r];t.call(o,s,r,e)&&i.push(s)}return i}),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!{toString:null}.propertyIsEnumerable("toString"),n=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],i=n.length;return function(o){if("object"!=typeof o&&"function"!=typeof o||null===o)throw new TypeError("Object.keys called on non-object");var r=[];for(var s in o)t.call(o,s)&&r.push(s);if(e)for(var a=0;i>a;a++)t.call(o,n[a])&&r.push(n[a]);return r}}()),Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)}),e.exports=n=i},{}],4:[function(t,e,n){var i={listeners:[],indexOf:function(t){for(var e=this.listeners,n=0,i=this.listeners.length;i>n;n++){var o=e[n];if(o&&o.object==t)return n}return-1},addListener:function(t,e,n){var i=this.indexOf(t),o=this.listeners[i];o||(o={object:t,events:{}},this.listeners.push(o));var r=o.events[e];r||(r=[],o.events[e]=r),-1==r.indexOf(n)&&r.push(n)},removeListener:function(t,e,n){var i=this.indexOf(t),o=this.listeners[i];if(o){var r=o.events[e];r&&(i=r.indexOf(n),-1!=i&&r.splice(i,1),0==r.length&&delete o.events[e]);var s=0,a=o.events;for(var h in a)a.hasOwnProperty(h)&&s++;0==s&&delete this.listeners[i]}},removeAllListeners:function(){this.listeners=[]},trigger:function(t,e,n){var i=this.indexOf(t),o=this.listeners[i];if(o){var r=o.events[e];if(r)for(var s=0,a=r.length;a>s;s++)r[s](n)}}};e.exports=n=i},{}],2:[function(t,e,n){function i(){this.id=o.randomUUID(),this.components={},this.repaintTimer=void 0,this.reflowTimer=void 0}var o=t("./util"),r=t("./component/component");i.prototype.add=function(t){if(void 0==t.id)throw Error("Component has no field id");if(!(t instanceof r||t instanceof i))throw new TypeError("Component must be an instance of prototype Component or Controller");t.controller=this,this.components[t.id]=t},i.prototype.requestReflow=function(){if(!this.reflowTimer){var t=this;this.reflowTimer=setTimeout(function(){t.reflowTimer=void 0,t.reflow()},0)}},i.prototype.requestRepaint=function(){if(!this.repaintTimer){var t=this;this.repaintTimer=setTimeout(function(){t.repaintTimer=void 0,t.repaint()},0)}},i.prototype.repaint=function(){function t(i,o){o in n||(i.depends&&i.depends.forEach(function(e){t(e,e.id)}),i.parent&&t(i.parent,i.parent.id),e=i.repaint()||e,n[o]=!0)}var e=!1;this.repaintTimer&&(clearTimeout(this.repaintTimer),this.repaintTimer=void 0);var n={};o.forEach(this.components,t),e&&this.reflow()},i.prototype.reflow=function(){function t(i,o){o in n||(i.depends&&i.depends.forEach(function(e){t(e,e.id)}),i.parent&&t(i.parent,i.parent.id),e=i.reflow()||e,n[o]=!0)}var e=!1;this.reflowTimer&&(clearTimeout(this.reflowTimer),this.reflowTimer=void 0);var n={};o.forEach(this.components,t),e&&this.repaint()},e.exports=n=i},{"./util":8,"./component/component":9}],3:[function(t,e,n){function i(t){var e=this;this.options=t||{},this.data={},this.fieldId=this.options.fieldId||"id",this.fieldTypes={},this.options.fieldTypes&&o.forEach(this.options.fieldTypes,function(t,n){e.fieldTypes[n]="Date"==t||"ISODate"==t||"ASPDate"==t?"Date":t}),this.subscribers={},this.internalIds={}}var o=t("./util");i.prototype.subscribe=function(t,e,n){var i=this.subscribers[t];i||(i=[],this.subscribers[t]=i),i.push({id:n?n+"":null,callback:e})},i.prototype.unsubscribe=function(t,e){var n=this.subscribers[t];n&&(this.subscribers[t]=n.filter(function(t){return t.callback!=e}))},i.prototype._trigger=function(t,e,n){if("*"==t)throw Error("Cannot trigger event *");var i=[];t in this.subscribers&&(i=i.concat(this.subscribers[t])),"*"in this.subscribers&&(i=i.concat(this.subscribers["*"])),i.forEach(function(i){i.id!=n&&i.callback&&i.callback(t,e,n||null)})},i.prototype.add=function(t,e){var n,i=[],r=this;if(t instanceof Array)t.forEach(function(t){var e=r._addItem(t);i.push(e)});else if(o.isDataTable(t))for(var s=this._getColumnNames(t),a=0,h=t.getNumberOfRows();h>a;a++){var c={};s.forEach(function(e,n){c[e]=t.getValue(a,n)}),n=r._addItem(c),i.push(n)}else{if(!(t instanceof Object))throw Error("Unknown dataType");n=r._addItem(t),i.push(n)}this._trigger("add",{items:i},e)},i.prototype.update=function(t,e){var n,i=[],r=this;if(t instanceof Array)t.forEach(function(t){var e=r._updateItem(t);i.push(e)});else if(o.isDataTable(t))for(var s=this._getColumnNames(t),a=0,h=t.getNumberOfRows();h>a;a++){var c={};s.forEach(function(e,n){c[e]=t.getValue(a,n)}),n=r._updateItem(c),i.push(n)}else{if(!(t instanceof Object))throw Error("Unknown dataType");n=r._updateItem(t),i.push(n)}this._trigger("update",{items:i},e)},i.prototype.get=function(t,e,n){var i=this;"Object"==o.getType(t)&&(n=e,e=t,t=void 0);var r={};this.options&&this.options.fieldTypes&&o.forEach(this.options.fieldTypes,function(t,e){r[e]=t}),e&&e.fieldTypes&&o.forEach(e.fieldTypes,function(t,e){r[e]=t});var s,a=e?e.fields:void 0;if(e&&e.type){if(s="DataTable"==e.type?"DataTable":"Array",n&&s!=o.getType(n))throw Error('Type of parameter "data" ('+o.getType(n)+") "+"does not correspond with specified options.type ("+e.type+")");if("DataTable"==s&&!o.isDataTable(n))throw Error('Parameter "data" must be a DataTable when options.type is "DataTable"')}else s=n?"DataTable"==o.getType(n)?"DataTable":"Array":"Array";if("DataTable"==s){var h=this._getColumnNames(n);if(void 0==t)o.forEach(this.data,function(t){i._appendRow(n,h,i._castItem(t))});else if(o.isNumber(t)||o.isString(t)){var c=i._castItem(i.data[t],r,a);this._appendRow(n,h,c)}else{if(!(t instanceof Array))throw new TypeError('Parameter "ids" must be undefined, a String, Number, or Array');t.forEach(function(t){var e=i._castItem(i.data[t],r,a);i._appendRow(n,h,e)})}}else if(n=n||[],void 0==t)o.forEach(this.data,function(t){n.push(i._castItem(t,r,a))});else{if(o.isNumber(t)||o.isString(t))return this._castItem(i.data[t],r,a);if(!(t instanceof Array))throw new TypeError('Parameter "ids" must be undefined, a String, Number, or Array');t.forEach(function(t){n.push(i._castItem(i.data[t],r,a))})}return n},i.prototype.remove=function(t,e){var n=[],i=this;if(o.isNumber(t)||o.isString(t))delete this.data[t],delete this.internalIds[t],n.push(t);else if(t instanceof Array)t.forEach(function(t){i.remove(t)}),n=n.concat(t);else if(t instanceof Object)for(var r in this.data)this.data.hasOwnProperty(r)&&this.data[r]==t&&(delete this.data[r],delete this.internalIds[r],n.push(r));this._trigger("remove",{items:n},e)},i.prototype.clear=function(t){var e=Object.keys(this.data);this.data={},this.internalIds={},this._trigger("remove",{items:e},t)},i.prototype.max=function(t){var e=this.data,n=Object.keys(e),i=null,o=null;return n.forEach(function(n){var r=e[n],s=r[t];null!=s&&(!i||s>o)&&(i=r,o=s)}),i},i.prototype.min=function(t){var e=this.data,n=Object.keys(e),i=null,o=null;return n.forEach(function(n){var r=e[n],s=r[t];null!=s&&(!i||o>s)&&(i=r,o=s)}),i},i.prototype._addItem=function(t){var e=t[this.fieldId];void 0==e&&(e=o.randomUUID(),t[this.fieldId]=e,this.internalIds[e]=t);var n={};for(var i in t)if(t.hasOwnProperty(i)){var r=this.fieldTypes[i];n[i]=o.cast(t[i],r)}return this.data[e]=n,e},i.prototype._castItem=function(t,e,n){var i,r=this.fieldId,s=this.internalIds;return t?(i={},e=e||{},n?o.forEach(t,function(t,r){-1!=n.indexOf(r)&&(i[r]=o.cast(t,e[r]))}):o.forEach(t,function(t,n){n==r&&t in s||(i[n]=o.cast(t,e[n]))})):i=null,i},i.prototype._updateItem=function(t){var e=t[this.fieldId];if(void 0==e)throw Error("Item has no id (item: "+JSON.stringify(t)+")");var n=this.data[e];if(n){for(var i in t)if(t.hasOwnProperty(i)){var r=this.fieldTypes[i];n[i]=o.cast(t[i],r)}}else this._addItem(t);return e},i.prototype._getColumnNames=function(t){for(var e=[],n=0,i=t.getNumberOfColumns();i>n;n++)e[n]=t.getColumnId(n)||t.getColumnLabel(n);return e},i.prototype._appendRow=function(t,e,n){var i=t.addRow();e.forEach(function(e,o){t.setValue(i,o,n[e])})},e.exports=n=i},{"./util":8}],5:[function(t,e,n){function i(t){this.id=o.randomUUID(),this.start=0,this.end=0,this.options={min:null,max:null,zoomMin:null,zoomMax:null},this.setOptions(t),this.listeners=[]}var o=t("./util"),r=t("./events");i.prototype.setOptions=function(t){o.extend(this.options,t),(null!=t.start||null!=t.end)&&this.setRange(t.start,t.end)},i.prototype.subscribe=function(t,e,n){var i,o=this;if("horizontal"!=n&&"vertical"!=n)throw new TypeError('Unknown direction "'+n+'". '+'Choose "horizontal" or "vertical".');if("move"==e)i={component:t,event:e,direction:n,callback:function(t){o._onMouseDown(t,i)},params:{}},t.on("mousedown",i.callback),o.listeners.push(i);else{if("zoom"!=e)throw new TypeError('Unknown event "'+e+'". '+'Choose "move" or "zoom".');i={component:t,event:e,direction:n,callback:function(t){o._onMouseWheel(t,i)},params:{}},t.on("mousewheel",i.callback),o.listeners.push(i)}},i.prototype.on=function(t,e){r.addListener(this,t,e)},i.prototype._trigger=function(t){r.trigger(this,t,{start:this.start,end:this.end})},i.prototype.setRange=function(t,e){var n=this._applyRange(t,e);n&&(this._trigger("rangechange"),this._trigger("rangechanged"))},i.prototype._applyRange=function(t,e){var n,i=null!=t?o.cast(t,"Number"):this.start,r=null!=e?o.cast(e,"Number"):this.end;if(isNaN(i))throw Error('Invalid start "'+t+'"');if(isNaN(r))throw Error('Invalid end "'+e+'"');if(i>r&&(r=i),null!=this.options.min){var s=this.options.min.valueOf();s>i&&(n=s-i,i+=n,r+=n)}if(null!=this.options.max){var a=this.options.max.valueOf();r>a&&(n=r-a,i-=n,r-=n)}if(null!=this.options.zoomMin){var h=this.options.zoomMin.valueOf();0>h&&(h=0),h>r-i&&(this.end-this.start>h?(n=h-(r-i),i-=n/2,r+=n/2):(i=this.start,r=this.end))}if(null!=this.options.zoomMax){var c=this.options.zoomMax.valueOf();0>c&&(c=0),r-i>c&&(c>this.end-this.start?(n=r-i-c,i+=n/2,r-=n/2):(i=this.start,r=this.end))}var p=this.start!=i||this.end!=r;return this.start=i,this.end=r,p},i.prototype.getRange=function(){return{start:this.start,end:this.end}},i.prototype.conversion=function(t){return this.start,this.end,i.conversion(this.start,this.end,t)},i.conversion=function(t,e,n){return 0!=n&&0!=e-t?{offset:t,factor:n/(e-t)}:{offset:0,factor:1}},i.prototype._onMouseDown=function(t,e){t=t||window.event;var n=e.params,i=t.which?1==t.which:1==t.button;if(i){n.mouseX=o.getPageX(t),n.mouseY=o.getPageY(t),n.previousLeft=0,n.previousOffset=0,n.moved=!1,n.start=this.start,n.end=this.end;var r=e.component.frame;r&&(r.style.cursor="move");var s=this;n.onMouseMove||(n.onMouseMove=function(t){s._onMouseMove(t,e)},o.addEventListener(document,"mousemove",n.onMouseMove)),n.onMouseUp||(n.onMouseUp=function(t){s._onMouseUp(t,e)},o.addEventListener(document,"mouseup",n.onMouseUp)),o.preventDefault(t)}},i.prototype._onMouseMove=function(t,e){t=t||window.event;var n=e.params,i=o.getPageX(t),r=o.getPageY(t);void 0==n.mouseX&&(n.mouseX=i),void 0==n.mouseY&&(n.mouseY=r);var s=i-n.mouseX,a=r-n.mouseY,h="horizontal"==e.direction?s:a;Math.abs(h)>=1&&(n.moved=!0);var c=n.end-n.start,p="horizontal"==e.direction?e.component.width:e.component.height,u=-h/p*c;this._applyRange(n.start+u,n.end+u),this._trigger("rangechange"),o.preventDefault(t)},i.prototype._onMouseUp=function(t,e){t=t||window.event;var n=e.params;e.component.frame&&(e.component.frame.style.cursor="auto"),n.onMouseMove&&(o.removeEventListener(document,"mousemove",n.onMouseMove),n.onMouseMove=null),n.onMouseUp&&(o.removeEventListener(document,"mouseup",n.onMouseUp),n.onMouseUp=null),n.moved&&this._trigger("rangechanged")},i.prototype._onMouseWheel=function(t,e){t=t||window.event;var n=0;if(t.wheelDelta?n=t.wheelDelta/120:t.detail&&(n=-t.detail/3),n){var i=this,r=function(){var r=n/5,s=null,a=e.component.frame;if(a){var h,c;if("horizontal"==e.direction){h=e.component.width,c=i.conversion(h);var p=o.getAbsoluteLeft(a),u=o.getPageX(t);s=(u-p)/c.factor+c.offset}else{h=e.component.height,c=i.conversion(h);var l=o.getAbsoluteTop(a),d=o.getPageY(t);s=(l+h-d-l)/c.factor+c.offset}}i.zoom(r,s)};r()}o.preventDefault(t)},i.prototype.zoom=function(t,e){null==e&&(e=(this.start+this.end)/2),t>=1&&(t=.9),-1>=t&&(t=-.9),0>t&&(t/=1+t);var n=this.start-e,i=this.end-e,o=this.start-n*t,r=this.end-i*t;this.setRange(o,r)},i.prototype.move=function(t){var e=this.end-this.start,n=this.start+e*t,i=this.end+e*t;this.start=n,this.end=i},e.exports=n=i},{"./util":8,"./events":4}],6:[function(t,e,n){function i(t,e){this.parent=t,this.options={order:function(t,e){return e.width-t.width||t.left-e.left}},this.ordered=[],this.setOptions(e)}var o=t("./util");i.prototype.setOptions=function(t){o.extend(this.options,t)},i.prototype.update=function(){this._order(),this._stack()},i.prototype._order=function(){var t=this.parent.items;if(!t)throw Error("Cannot stack items: parent does not contain items");var e=[],n=0;o.forEach(t,function(t){e[n]=t,n++});var i=this.options.order;if("function"!=typeof this.options.order)throw Error("Option order must be a function");e.sort(i),this.ordered=e},i.prototype._stack=function(){var t,e,n=this.ordered,i=this.options,o="top"==i.orientation,r=i.margin&&i.margin.item||0;for(t=0,e=n.length;e>t;t++){var s=n[t],a=null;do a=this.checkOverlap(n,t,0,t-1,r),null!=a&&(s.top=o?a.top+a.height+r:a.top-s.height-r);while(a)}},i.prototype.checkOverlap=function(t,e,n,i,o){for(var r=this.collision,s=t[e],a=i;a>=n;a--){var h=t[a];if(r(s,h,o)&&a!=e)return h}return null},i.prototype.collision=function(t,e,n){return t.left-ne.left&&t.top-ne.top},e.exports=n=i},{"./util":8}],9:[function(t,e,n){function i(){this.id=null,this.parent=null,this.depends=null,this.controller=null,this.options=null,this.frame=null,this.top=0,this.left=0,this.width=0,this.height=0}var o=t("./../util");i.prototype.setOptions=function(t){t&&o.extend(this.options,t),this.controller&&(this.requestRepaint(),this.requestReflow())},i.prototype.getContainer=function(){return null},i.prototype.getFrame=function(){return this.frame},i.prototype.repaint=function(){return!1},i.prototype.reflow=function(){return!1},i.prototype.requestRepaint=function(){if(!this.controller)throw Error("Cannot request a repaint: no controller configured");this.controller.requestRepaint()},i.prototype.requestReflow=function(){if(!this.controller)throw Error("Cannot request a reflow: no controller configured");this.controller.requestReflow()},i.prototype.on=function(t,e){if(!this.parent)throw Error("Cannot attach event: no root panel found");this.parent.on(t,e)},e.exports=n=i},{"./../util":8}],10:[function(t,e,n){function i(t,e,n){this.id=o.randomUUID(),this.parent=t,this.depends=e,this.options={},this.setOptions(n)}var o=t("../util"),r=t("./component");i.prototype=new r,i.prototype.getContainer=function(){return this.frame},i.prototype.repaint=function(){var t=0,e=o.updateProperty,n=o.option.asSize,i=this.options,r=this.frame;if(r||(r=document.createElement("div"),r.className="panel",i.className&&("function"==typeof i.className?o.addClassName(r,i.className()+""):o.addClassName(r,i.className+"")),this.frame=r,t+=1),!r.parentNode){if(!this.parent)throw Error("Cannot repaint panel: no parent attached");var s=this.parent.getContainer();if(!s)throw Error("Cannot repaint panel: parent has no container element");s.appendChild(r),t+=1}return t+=e(r.style,"top",n(i.top,"0px")),t+=e(r.style,"left",n(i.left,"0px")),t+=e(r.style,"width",n(i.width,"100%")),t+=e(r.style,"height",n(i.height,"100%")),t>0},i.prototype.reflow=function(){var t=0,e=o.updateProperty,n=this.frame;return n?(t+=e(this,"top",n.offsetTop),t+=e(this,"left",n.offsetLeft),t+=e(this,"width",n.offsetWidth),t+=e(this,"height",n.offsetHeight)):t+=1,t>0},e.exports=n=i},{"../util":8,"./component":9}],12:[function(t,e,n){function i(t,e,n){this.id=o.randomUUID(),this.parent=t,this.depends=e,this.options={style:"box",align:"center",orientation:"bottom",margin:{axis:20,item:10},padding:5},this.dom={};var i=this;this.data=null,this.range=null,this.listeners={add:function(t,e){i._onAdd(e.items)},update:function(t,e){i._onUpdate(e.items)},remove:function(t,e){i._onRemove(e.items)}},this.items={},this.queue={},this.stack=new a(this),this.conversion=null,this.setOptions(n)}var o=t("../util"),r=t("../dataset"),s=t("./panel"),a=t("../stack"),h=t("./item/itembox"),c=t("./item/itemrange"),p=t("./item/itempoint");i.prototype=new s,i.types={box:h,range:c,point:p},i.prototype.setOptions=function(t){o.extend(this.options,t),this.stack.setOptions(this.options)},i.prototype.setRange=function(t){if(!(t instanceof Range||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},i.prototype.repaint=function(){var t=0,e=o.updateProperty,n=o.option.asSize,r=this.options,s=this.frame;if(!s){s=document.createElement("div"),s.className="itemset",r.className&&o.addClassName(s,o.option.asString(r.className));var a=document.createElement("div");a.className="background",s.appendChild(a),this.dom.background=a;var h=document.createElement("div");h.className="foreground",s.appendChild(h),this.dom.foreground=h;var c=document.createElement("div");c.className="itemset-axis",this.dom.axis=c,this.frame=s,t+=1}if(!this.parent)throw Error("Cannot repaint itemset: no parent attached");var p=this.parent.getContainer();if(!p)throw Error("Cannot repaint itemset: parent has no container element");s.parentNode||(p.appendChild(s),t+=1),this.dom.axis.parentNode||(p.appendChild(this.dom.axis),t+=1),t+=e(s.style,"height",n(r.height,this.height+"px")),t+=e(s.style,"top",n(r.top,"0px")),t+=e(s.style,"left",n(r.left,"0px")),t+=e(s.style,"width",n(r.width,"100%")),t+=e(this.dom.axis.style,"top",n(r.top,"0px")),this._updateConversion();var u=this,l=this.queue,d=this.data,f=this.items,m={fields:["id","start","end","content","type"]};return Object.keys(l).forEach(function(e){var n=l[e],o=n.item;switch(n.action){case"add":case"update":var s=d.get(e,m),a=s.type||s.start&&s.end&&"range"||"box",h=i.types[a];if(o&&(h&&o instanceof h?(o.data=s,t+=o.repaint()):(o.visible=!1,t+=o.repaint(),o=null)),!o){if(!h)throw new TypeError('Unknown item type "'+a+'"');o=new h(u,s,r),t+=o.repaint()}f[e]=o,delete l[e];break;case"remove":o&&(o.visible=!1,t+=o.repaint()),delete f[e],delete l[e];break;default:console.log('Error: unknown action "'+n.action+'"')}}),o.forEach(this.items,function(t){t.reposition()}),t>0},i.prototype.getForeground=function(){return this.dom.foreground},i.prototype.getBackground=function(){return this.dom.background},i.prototype.reflow=function(){var t=0,e=this.options,n=o.updateProperty,i=o.option.asNumber,r=this.frame;if(r){this._updateConversion(),o.forEach(this.items,function(e){t+=e.reflow()}),this.stack.update();var s,a=i(e.maxHeight);if(null!=e.height)s=r.offsetHeight,null!=a&&(s=Math.min(s,a)),t+=n(this,"height",s);else{var h=this.height;s=0,"top"==e.orientation?o.forEach(this.items,function(t){s=Math.max(s,t.top+t.height)}):o.forEach(this.items,function(t){s=Math.max(s,h-t.top)}),s+=e.margin.axis,null!=a&&(s=Math.min(s,a)),t+=n(this,"height",s)}t+=n(this,"top",r.offsetTop),t+=n(this,"left",r.offsetLeft),t+=n(this,"width",r.offsetWidth)}else t+=1;return t>0},i.prototype.setData=function(t){var e=this.data;e&&o.forEach(this.listeners,function(t,n){e.unsubscribe(n,t)}),t instanceof r?this.data=t:(this.data=new r({fieldTypes:{start:"Date",end:"Date"}}),this.data.add(t));var n=this.id,i=this;o.forEach(this.listeners,function(t,e){i.data.subscribe(e,t,n)});var s=this.data.get({filter:["id"]}),a=[];o.forEach(s,function(t,e){a[e]=t.id}),this._onAdd(a)},i.prototype.getDataRange=function(){var t=this.data,e=t.min("start");e=e?e.start.valueOf():null;var n=t.max("start"),i=t.max("end");n=n?n.start.valueOf():null,i=i?i.end.valueOf():null;var o=Math.max(n,i);return{min:new Date(e),max:new Date(o)}},i.prototype._onUpdate=function(t){this._toQueue(t,"update")},i.prototype._onAdd=function(t){this._toQueue(t,"add")},i.prototype._onRemove=function(t){this._toQueue(t,"remove")},i.prototype._toQueue=function(t,e){var n=this.items,i=this.queue;t.forEach(function(t){var o=i[t];o?o.action=e:i[t]={item:n[t]||null,action:e}}),this.controller&&this.requestRepaint()},i.prototype._updateConversion=function(){var t=this.range;if(!t)throw Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):Range.conversion(t.start,t.end,this.width)},i.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.factor+e.offset)},i.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.factor},e.exports=n=i},{"../util":8,"../dataset":3,"./panel":10,"../stack":6,"./item/itembox":15,"./item/itemrange":16,"./item/itempoint":17}],11:[function(t,e,n){function i(t,e){this.id=o.randomUUID(),this.container=t,this.options={autoResize:!0},this.listeners={},this.setOptions(e)}var o=t("../util"),r=t("./panel");i.prototype=new r,i.prototype.setOptions=function(t){o.extend(this.options,t),this.options.autoResize?this._watch():this._unwatch()},i.prototype.repaint=function(){var t=0,e=o.updateProperty,n=o.option.asSize,i=this.options,r=this.frame;if(r||(r=document.createElement("div"),r.className="graph panel",i.className&&o.addClassName(r,o.option.asString(i.className)),this.frame=r,t+=1),!r.parentNode){if(!this.container)throw Error("Cannot repaint root panel: no container attached");this.container.appendChild(r),t+=1}return t+=e(r.style,"top",n(i.top,"0px")),t+=e(r.style,"left",n(i.left,"0px")),t+=e(r.style,"width",n(i.width,"100%")),t+=e(r.style,"height",n(i.height,"100%")),this._updateEventEmitters(),t>0},i.prototype.reflow=function(){var t=0,e=o.updateProperty,n=this.frame;return n?(t+=e(this,"top",n.offsetTop),t+=e(this,"left",n.offsetLeft),t+=e(this,"width",n.offsetWidth),t+=e(this,"height",n.offsetHeight)):t+=1,t>0},i.prototype._watch=function(){var t=this;this._unwatch();var e=function(){return t.options.autoResize?(t.frame&&(t.frame.clientWidth!=t.width||t.frame.clientHeight!=t.height)&&t.requestReflow(),void 0):(t._unwatch(),void 0)};o.addEventListener(window,"resize",e),this.watchTimer=setInterval(e,1e3)},i.prototype._unwatch=function(){this.watchTimer&&(clearInterval(this.watchTimer),this.watchTimer=void 0)},i.prototype.on=function(t,e){var n=this.listeners[t];n||(n=[],this.listeners[t]=n),n.push(e),this._updateEventEmitters()},i.prototype._updateEventEmitters=function(){if(this.listeners){var t=this;o.forEach(this.listeners,function(e,n){if(t.emitters||(t.emitters={}),!(n in t.emitters)){var i=t.frame;if(i){var r=function(t){e.forEach(function(e){e(t)})};t.emitters[n]=r,o.addEventListener(i,n,r)}}})}},e.exports=n=i},{"../util":8,"./panel":10}],13:[function(t,e,n){function i(t,e,n){this.id=o.randomUUID(),this.parent=t,this.depends=e,this.dom={majorLines:[],majorTexts:[],minorLines:[],minorTexts:[],redundant:{majorLines:[],majorTexts:[],minorLines:[],minorTexts:[]}},this.props={range:{start:0,end:0,minimumStep:0},lineTop:0},this.options={orientation:"bottom",showMinorLabels:!0,showMajorLabels:!0},this.conversion=null,this.range=null,this.setOptions(n)}var o=t("../util"),r=t("../timestep"),s=t("./component");i.prototype=new s,i.prototype.setOptions=function(t){o.extend(this.options,t)},i.prototype.setRange=function(t){if(!(t instanceof Range||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},i.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.factor+e.offset)},i.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.factor},i.prototype.repaint=function(){var t=0,e=o.updateProperty,n=o.option.asSize,i=this.options,r=this.props,s=this.step,a=this.frame;if(a||(a=document.createElement("div"),this.frame=a,t+=1),a.className="axis "+i.orientation,!a.parentNode){if(!this.parent)throw Error("Cannot repaint time axis: no parent attached");var h=this.parent.getContainer();if(!h)throw Error("Cannot repaint time axis: parent has no container element");h.appendChild(a),t+=1}var c=a.parentNode;if(c){var p=a.nextSibling;c.removeChild(a);var u=i.orientation,l="bottom"==u&&this.props.parentHeight&&this.height?this.props.parentHeight-this.height+"px":"0px";if(t+=e(a.style,"top",n(i.top,l)),t+=e(a.style,"left",n(i.left,"0px")),t+=e(a.style,"width",n(i.width,"100%")),t+=e(a.style,"height",n(i.height,this.height+"px")),this._repaintMeasureChars(),this.step){this._repaintStart(),s.first();for(var d=void 0,f=0;s.hasNext()&&1e3>f;){f++;var m=s.getCurrent(),g=this.toScreen(m),v=s.isMajor();i.showMinorLabels&&this._repaintMinorText(g,s.getLabelMinor()),v&&i.showMajorLabels?(g>0&&(void 0==d&&(d=g),this._repaintMajorText(g,s.getLabelMajor())),this._repaintMajorLine(g)):this._repaintMinorLine(g),s.next()}if(i.showMajorLabels){var y=this.toTime(0),S=s.getLabelMajor(y),T=S.length*(r.majorCharWidth||10)+10;(void 0==d||d>T)&&this._repaintMajorText(0,S)}this._repaintEnd()}this._repaintLine(),p?c.insertBefore(a,p):c.appendChild(a)}return t>0 -},i.prototype._repaintStart=function(){var t=this.dom,e=t.redundant;e.majorLines=t.majorLines,e.majorTexts=t.majorTexts,e.minorLines=t.minorLines,e.minorTexts=t.minorTexts,t.majorLines=[],t.majorTexts=[],t.minorLines=[],t.minorTexts=[]},i.prototype._repaintEnd=function(){o.forEach(this.dom.redundant,function(t){for(;t.length;){var e=t.pop();e&&e.parentNode&&e.parentNode.removeChild(e)}})},i.prototype._repaintMinorText=function(t,e){var n=this.dom.redundant.minorTexts.shift();if(!n){var i=document.createTextNode("");n=document.createElement("div"),n.appendChild(i),n.className="text minor",this.frame.appendChild(n)}this.dom.minorTexts.push(n),n.childNodes[0].nodeValue=e,n.style.left=t+"px",n.style.top=this.props.minorLabelTop+"px"},i.prototype._repaintMajorText=function(t,e){var n=this.dom.redundant.majorTexts.shift();if(!n){var i=document.createTextNode(e);n=document.createElement("div"),n.className="text major",n.appendChild(i),this.frame.appendChild(n)}this.dom.majorTexts.push(n),n.childNodes[0].nodeValue=e,n.style.top=this.props.majorLabelTop+"px",n.style.left=t+"px"},i.prototype._repaintMinorLine=function(t){var e=this.dom.redundant.minorLines.shift();e||(e=document.createElement("div"),e.className="grid vertical minor",this.frame.appendChild(e)),this.dom.minorLines.push(e);var n=this.props;e.style.top=n.minorLineTop+"px",e.style.height=n.minorLineHeight+"px",e.style.left=t-n.minorLineWidth/2+"px"},i.prototype._repaintMajorLine=function(t){var e=this.dom.redundant.majorLines.shift();e||(e=document.createElement("DIV"),e.className="grid vertical major",this.frame.appendChild(e)),this.dom.majorLines.push(e);var n=this.props;e.style.top=n.majorLineTop+"px",e.style.left=t-n.majorLineWidth/2+"px",e.style.height=n.majorLineHeight+"px"},i.prototype._repaintLine=function(){var t=this.dom.line,e=this.frame,n=this.options;n.showMinorLabels||n.showMajorLabels?(t?(e.removeChild(t),e.appendChild(t)):(t=document.createElement("div"),t.className="grid horizontal major",e.appendChild(t),this.dom.line=t),t.style.top=this.props.lineTop+"px"):t&&axis.parentElement&&(e.removeChild(axis.line),delete this.dom.line)},i.prototype._repaintMeasureChars=function(){var t,e=this.dom;if(!e.characterMinor){t=document.createTextNode("0");var n=document.createElement("DIV");n.className="text minor measure",n.appendChild(t),this.frame.appendChild(n),e.measureCharMinor=n}if(!e.characterMajor){t=document.createTextNode("0");var i=document.createElement("DIV");i.className="text major measure",i.appendChild(t),this.frame.appendChild(i),e.measureCharMajor=i}},i.prototype.reflow=function(){var t=0,e=o.updateProperty,n=this.frame,i=this.range;if(!i)throw Error("Cannot repaint time axis: no range configured");if(n){t+=e(this,"top",n.offsetTop),t+=e(this,"left",n.offsetLeft);var s=this.props,a=this.options.showMinorLabels,h=this.options.showMajorLabels,c=this.dom.measureCharMinor,p=this.dom.measureCharMajor;c&&(s.minorCharHeight=c.clientHeight,s.minorCharWidth=c.clientWidth),p&&(s.majorCharHeight=p.clientHeight,s.majorCharWidth=p.clientWidth);var u=n.parentNode?n.parentNode.offsetHeight:0;switch(u!=s.parentHeight&&(s.parentHeight=u,t+=1),this.options.orientation){case"bottom":s.minorLabelHeight=a?s.minorCharHeight:0,s.majorLabelHeight=h?s.majorCharHeight:0,s.minorLabelTop=0,s.majorLabelTop=s.minorLabelTop+s.minorLabelHeight,s.minorLineTop=-this.top,s.minorLineHeight=Math.max(this.top+s.majorLabelHeight,0),s.minorLineWidth=1,s.majorLineTop=-this.top,s.majorLineHeight=Math.max(this.top+s.minorLabelHeight+s.majorLabelHeight,0),s.majorLineWidth=1,s.lineTop=0;break;case"top":s.minorLabelHeight=a?s.minorCharHeight:0,s.majorLabelHeight=h?s.majorCharHeight:0,s.majorLabelTop=0,s.minorLabelTop=s.majorLabelTop+s.majorLabelHeight,s.minorLineTop=s.minorLabelTop,s.minorLineHeight=Math.max(u-s.majorLabelHeight-this.top),s.minorLineWidth=1,s.majorLineTop=0,s.majorLineHeight=Math.max(u-this.top),s.majorLineWidth=1,s.lineTop=s.majorLabelHeight+s.minorLabelHeight;break;default:throw Error('Unkown orientation "'+this.options.orientation+'"')}var l=s.minorLabelHeight+s.majorLabelHeight;t+=e(this,"width",n.offsetWidth),t+=e(this,"height",l),this._updateConversion();var d=o.cast(i.start,"Date"),f=o.cast(i.end,"Date"),m=this.toTime(5*(s.minorCharWidth||10))-this.toTime(0);this.step=new r(d,f,m),t+=e(s.range,"start",d.valueOf()),t+=e(s.range,"end",f.valueOf()),t+=e(s.range,"minimumStep",m.valueOf())}return t>0},i.prototype._updateConversion=function(){var t=this.range;if(!t)throw Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):Range.conversion(t.start,t.end,this.width)},e.exports=n=i},{"../util":8,"../timestep":7,"./component":9}],7:[function(t,e,n){var i=(t("./util"),t("moment"));TimeStep=function(t,e,n){this.current=new Date,this._start=new Date,this._end=new Date,this.autoScale=!0,this.scale=TimeStep.SCALE.DAY,this.step=1,this.setRange(t,e,n)},TimeStep.SCALE={MILLISECOND:1,SECOND:2,MINUTE:3,HOUR:4,DAY:5,WEEKDAY:6,MONTH:7,YEAR:8},TimeStep.prototype.setRange=function(t,e,n){t instanceof Date&&e instanceof Date&&(this._start=void 0!=t?new Date(t.valueOf()):new Date,this._end=void 0!=e?new Date(e.valueOf()):new Date,this.autoScale&&this.setMinimumStep(n))},TimeStep.prototype.first=function(){this.current=new Date(this._start.valueOf()),this.roundToMinor()},TimeStep.prototype.roundToMinor=function(){switch(this.scale){case TimeStep.SCALE.YEAR:this.current.setFullYear(this.step*Math.floor(this.current.getFullYear()/this.step)),this.current.setMonth(0);case TimeStep.SCALE.MONTH:this.current.setDate(1);case TimeStep.SCALE.DAY:case TimeStep.SCALE.WEEKDAY:this.current.setHours(0);case TimeStep.SCALE.HOUR:this.current.setMinutes(0);case TimeStep.SCALE.MINUTE:this.current.setSeconds(0);case TimeStep.SCALE.SECOND:this.current.setMilliseconds(0)}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.setMilliseconds(this.current.getMilliseconds()-this.current.getMilliseconds()%this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()-this.current.getSeconds()%this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()-this.current.getMinutes()%this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()-this.current.getHours()%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()-1-(this.current.getDate()-1)%this.step+1);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()-this.current.getMonth()%this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()-this.current.getFullYear()%this.step);break;default:}},TimeStep.prototype.hasNext=function(){return this.current.valueOf()<=this._end.valueOf()},TimeStep.prototype.next=function(){var t=this.current.valueOf();if(6>this.current.getMonth())switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current=new Date(this.current.valueOf()+1e3*this.step);break;case TimeStep.SCALE.MINUTE:this.current=new Date(this.current.valueOf()+60*1e3*this.step);break;case TimeStep.SCALE.HOUR:this.current=new Date(this.current.valueOf()+60*60*1e3*this.step);var e=this.current.getHours();this.current.setHours(e-e%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step);break;default:}else switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()+this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()+this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()+this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step);break;default:}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.getMilliseconds()0&&(this.step=e),this.autoScale=!1},TimeStep.prototype.setAutoScale=function(t){this.autoScale=t},TimeStep.prototype.setMinimumStep=function(t){if(void 0!=t){var e=31104e6,n=2592e6,i=864e5,o=36e5,r=6e4,s=1e3,a=1;1e3*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1e3),500*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=500),100*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=100),50*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=50),10*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=10),5*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=5),e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1),3*n>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=3),n>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=1),5*i>t&&(this.scale=TimeStep.SCALE.DAY,this.step=5),2*i>t&&(this.scale=TimeStep.SCALE.DAY,this.step=2),i>t&&(this.scale=TimeStep.SCALE.DAY,this.step=1),i/2>t&&(this.scale=TimeStep.SCALE.WEEKDAY,this.step=1),4*o>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=4),o>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=1),15*r>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=15),10*r>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=10),5*r>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=5),r>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=1),15*s>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=15),10*s>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=10),5*s>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=5),s>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=1),200*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=200),100*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=100),50*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=50),10*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=10),5*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=5),a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=1)}},TimeStep.prototype.snap=function(t){if(this.scale==TimeStep.SCALE.YEAR){var e=t.getFullYear()+Math.round(t.getMonth()/12);t.setFullYear(Math.round(e/this.step)*this.step),t.setMonth(0),t.setDate(0),t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MONTH)t.getDate()>15?(t.setDate(1),t.setMonth(t.getMonth()+1)):t.setDate(1),t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0);else if(this.scale==TimeStep.SCALE.DAY||this.scale==TimeStep.SCALE.WEEKDAY){switch(this.step){case 5:case 2:t.setHours(24*Math.round(t.getHours()/24));break;default:t.setHours(12*Math.round(t.getHours()/12))}t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.HOUR){switch(this.step){case 4:t.setMinutes(60*Math.round(t.getMinutes()/60));break;default:t.setMinutes(30*Math.round(t.getMinutes()/30))}t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MINUTE){switch(this.step){case 15:case 10:t.setMinutes(5*Math.round(t.getMinutes()/5)),t.setSeconds(0);break;case 5:t.setSeconds(60*Math.round(t.getSeconds()/60));break;default:t.setSeconds(30*Math.round(t.getSeconds()/30))}t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.SECOND)switch(this.step){case 15:case 10:t.setSeconds(5*Math.round(t.getSeconds()/5)),t.setMilliseconds(0);break;case 5:t.setMilliseconds(1e3*Math.round(t.getMilliseconds()/1e3));break;default:t.setMilliseconds(500*Math.round(t.getMilliseconds()/500))}else if(this.scale==TimeStep.SCALE.MILLISECOND){var n=this.step>5?this.step/2:1;t.setMilliseconds(Math.round(t.getMilliseconds()/n)*n)}},TimeStep.prototype.isMajor=function(){switch(this.scale){case TimeStep.SCALE.MILLISECOND:return 0==this.current.getMilliseconds();case TimeStep.SCALE.SECOND:return 0==this.current.getSeconds();case TimeStep.SCALE.MINUTE:return 0==this.current.getHours()&&0==this.current.getMinutes();case TimeStep.SCALE.HOUR:return 0==this.current.getHours();case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return 1==this.current.getDate();case TimeStep.SCALE.MONTH:return 0==this.current.getMonth();case TimeStep.SCALE.YEAR:return!1;default:return!1}},TimeStep.prototype.getLabelMinor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return i(t).format("SSS");case TimeStep.SCALE.SECOND:return i(t).format("s");case TimeStep.SCALE.MINUTE:return i(t).format("HH:mm");case TimeStep.SCALE.HOUR:return i(t).format("HH:mm");case TimeStep.SCALE.WEEKDAY:return i(t).format("ddd D");case TimeStep.SCALE.DAY:return i(t).format("D");case TimeStep.SCALE.MONTH:return i(t).format("MMM");case TimeStep.SCALE.YEAR:return i(t).format("YYYY");default:return""}},TimeStep.prototype.getLabelMajor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return i(t).format("HH:mm:ss");case TimeStep.SCALE.SECOND:return i(t).format("D MMMM HH:mm");case TimeStep.SCALE.MINUTE:case TimeStep.SCALE.HOUR:return i(t).format("ddd D MMMM");case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return i(t).format("MMMM YYYY");case TimeStep.SCALE.MONTH:return i(t).format("YYYY");case TimeStep.SCALE.YEAR:return"";default:return""}},e.exports=n=TimeStep},{"./util":8,moment:18}],15:[function(t,e,n){function i(t,e,n){this.props={dot:{left:0,top:0,width:0,height:0},line:{top:0,left:0,width:0,height:0}},r.call(this,t,e,n)}var o=t("../../util"),r=t("./item");i.prototype=new r(null,null),i.prototype.select=function(){this.selected=!0},i.prototype.unselect=function(){this.selected=!1},i.prototype.repaint=function(){var t=!1,e=this.dom;if(this.visible){if(e||(this._create(),t=!0),e=this.dom){if(!this.options&&!this.parent)throw Error("Cannot repaint item: no parent attached");var n=this.parent.getForeground();if(!n)throw Error("Cannot repaint time axis: parent has no foreground container element");var i=this.parent.getBackground();if(!i)throw Error("Cannot repaint time axis: parent has no background container element");if(e.box.parentNode||(n.appendChild(e.box),t=!0),e.line.parentNode||(i.appendChild(e.line),t=!0),e.dot.parentNode||(this.parent.dom.axis.appendChild(e.dot),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var o=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=o&&(this.className=o,e.box.className="item box"+o,e.line.className="item line"+o,e.dot.className="item dot"+o,t=!0)}}else e&&(e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0),e.line.parentNode&&(e.line.parentNode.removeChild(e.line),t=!0),e.dot.parentNode&&(e.dot.parentNode.removeChild(e.dot),t=!0));return t},i.prototype.reflow=function(){if(void 0==this.data.start)throw Error('Property "start" missing in item '+this.data.id);var t,e,n=o.updateProperty,i=this.dom,r=this.props,s=this.options,a=this.parent.toScreen(this.data.start),h=s&&s.align,c=s.orientation,p=0;if(i)if(p+=n(r.dot,"height",i.dot.offsetHeight),p+=n(r.dot,"width",i.dot.offsetWidth),p+=n(r.line,"width",i.line.offsetWidth),p+=n(r.line,"width",i.line.offsetWidth),p+=n(this,"width",i.box.offsetWidth),p+=n(this,"height",i.box.offsetHeight),e="right"==h?a-this.width:"left"==h?a:a-this.width/2,p+=n(this,"left",e),p+=n(r.line,"left",a-r.line.width/2),p+=n(r.dot,"left",a-r.dot.width/2),"top"==c)t=s.margin.axis,p+=n(this,"top",t),p+=n(r.line,"top",0),p+=n(r.line,"height",t),p+=n(r.dot,"top",-r.dot.height/2);else{var u=this.parent.height;t=u-this.height-s.margin.axis,p+=n(this,"top",t),p+=n(r.line,"top",t+this.height),p+=n(r.line,"height",Math.max(s.margin.axis,0)),p+=n(r.dot,"top",u-r.dot.height/2)}else p+=1;return p>0},i.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("DIV"),t.content=document.createElement("DIV"),t.content.className="content",t.box.appendChild(t.content),t.line=document.createElement("DIV"),t.line.className="line",t.dot=document.createElement("DIV"),t.dot.className="dot")},i.prototype.reposition=function(){var t=this.dom,e=this.props,n=this.options.orientation;if(t){var i=t.box,o=t.line,r=t.dot;i.style.left=this.left+"px",i.style.top=this.top+"px",o.style.left=e.line.left+"px","top"==n?(o.style.top="0px",o.style.height=this.top+"px"):(o.style.top=e.line.top+"px",o.style.top=this.top+this.height+"px",o.style.height=Math.max(e.dot.top-this.top-this.height,0)+"px"),r.style.left=e.dot.left+"px",r.style.top=e.dot.top+"px"}},e.exports=n=i},{"../../util":8,"./item":19}],16:[function(t,e,n){function i(t,e,n){this.props={content:{left:0,width:0}},r.call(this,t,e,n)}var o=t("../../util"),r=t("./item");i.prototype=new r(null,null),i.prototype.select=function(){this.selected=!0},i.prototype.unselect=function(){this.selected=!1},i.prototype.repaint=function(){var t=!1,e=this.dom;if(this.visible){if(e||(this._create(),t=!0),e=this.dom){if(!this.options&&!this.options.parent)throw Error("Cannot repaint item: no parent attached");var n=this.parent.getForeground();if(!n)throw Error("Cannot repaint time axis: parent has no foreground container element");if(e.box.parentNode||(n.appendChild(e.box),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var i=this.data.className?""+this.data.className:"";this.className!=i&&(this.className=i,e.box.className="item range"+i,t=!0)}}else e&&e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0);return t},i.prototype.reflow=function(){if(void 0==this.data.start)throw Error('Property "start" missing in item '+this.data.id);if(void 0==this.data.end)throw Error('Property "end" missing in item '+this.data.id);var t=this.dom,e=this.props,n=this.options,i=this.parent,r=i.toScreen(this.data.start),s=i.toScreen(this.data.end),a=0;if(t){var h,c,p=o.updateProperty,u=t.box,l=i.width,d=n.orientation;a+=p(e.content,"width",t.content.offsetWidth),a+=p(this,"height",u.offsetHeight),-l>r&&(r=-l),s>2*l&&(s=2*l),h=0>r?Math.min(-r,s-r-e.content.width-2*n.padding):0,a+=p(e.content,"left",h),"top"==d?(c=n.margin.axis,a+=p(this,"top",c)):(c=i.height-this.height-n.margin.axis,a+=p(this,"top",c)),a+=p(this,"left",r),a+=p(this,"width",Math.max(s-r,1))}else a+=1;return a>0},i.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.box.appendChild(t.content))},i.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.box.style.top=this.top+"px",t.box.style.left=this.left+"px",t.box.style.width=this.width+"px",t.content.style.left=e.content.left+"px")},e.exports=n=i},{"../../util":8,"./item":19}],17:[function(t,e,n){function i(t,e,n){this.props={dot:{top:0,width:0,height:0},content:{height:0,marginLeft:0}},r.call(this,t,e,n)}var o=t("../../util"),r=t("./item");i.prototype=new r(null,null),i.prototype.select=function(){this.selected=!0},i.prototype.unselect=function(){this.selected=!1},i.prototype.repaint=function(){var t=!1,e=this.dom;if(this.visible){if(e||(this._create(),t=!0),e=this.dom){if(!this.options&&!this.options.parent)throw Error("Cannot repaint item: no parent attached");var n=this.parent.getForeground();if(!n)throw Error("Cannot repaint time axis: parent has no foreground container element");if(e.point.parentNode||(n.appendChild(e.point),n.appendChild(e.point),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var i=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=i&&(this.className=i,e.point.className="item point"+i,t=!0)}}else e&&e.point.parentNode&&(e.point.parentNode.removeChild(e.point),t=!0);return t},i.prototype.reflow=function(){if(void 0==this.data.start)throw Error('Property "start" missing in item '+this.data.id);var t,e=o.updateProperty,n=this.dom,i=this.props,r=this.options,s=r.orientation,a=this.parent.toScreen(this.data.start),h=0;if(n){if(h+=e(this,"width",n.point.offsetWidth),h+=e(this,"height",n.point.offsetHeight),h+=e(i.dot,"width",n.dot.offsetWidth),h+=e(i.dot,"height",n.dot.offsetHeight),h+=e(i.content,"height",n.content.offsetHeight),"top"==s)t=r.margin.axis;else{var c=this.parent.height;t=Math.max(c-this.height-r.margin.axis,0)}h+=e(this,"top",t),h+=e(this,"left",a-i.dot.width/2),h+=e(i.content,"marginLeft",1.5*i.dot.width),h+=e(i.dot,"top",(this.height-i.dot.height)/2)}else h+=1;return h>0},i.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.point=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.point.appendChild(t.content),t.dot=document.createElement("div"),t.dot.className="dot",t.point.appendChild(t.dot))},i.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.point.style.top=this.top+"px",t.point.style.left=this.left+"px",t.content.style.marginLeft=e.content.marginLeft+"px",t.dot.style.top=e.dot.top+"px")},e.exports=n=i},{"../../util":8,"./item":19}],18:[function(e,n){(function(){(function(i){function o(t,e){return function(n){return u(t.call(this,n),e)}}function r(t){return function(e){return this.lang().ordinal(t.call(this,e))}}function s(){}function a(t){c(this,t)}function h(t){var e=this._data={},n=t.years||t.year||t.y||0,i=t.months||t.month||t.M||0,o=t.weeks||t.week||t.w||0,r=t.days||t.day||t.d||0,s=t.hours||t.hour||t.h||0,a=t.minutes||t.minute||t.m||0,h=t.seconds||t.second||t.s||0,c=t.milliseconds||t.millisecond||t.ms||0;this._milliseconds=c+1e3*h+6e4*a+36e5*s,this._days=r+7*o,this._months=i+12*n,e.milliseconds=c%1e3,h+=p(c/1e3),e.seconds=h%60,a+=p(h/60),e.minutes=a%60,s+=p(a/60),e.hours=s%24,r+=p(s/24),r+=7*o,e.days=r%30,i+=p(r/30),e.months=i%12,n+=p(i/12),e.years=n}function c(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function p(t){return 0>t?Math.ceil(t):Math.floor(t)}function u(t,e){for(var n=t+"";e>n.length;)n="0"+n;return n}function l(t,e,n){var i,o=e._milliseconds,r=e._days,s=e._months;o&&t._d.setTime(+t+o*n),r&&t.date(t.date()+r*n),s&&(i=t.date(),t.date(1).month(t.month()+s*n).date(Math.min(i,t.daysInMonth())))}function d(t){return"[object Array]"===Object.prototype.toString.call(t)}function f(t,e){var n,i=Math.min(t.length,e.length),o=Math.abs(t.length-e.length),r=0;for(n=0;i>n;n++)~~t[n]!==~~e[n]&&r++;return r+o}function m(t,e){return e.abbr=t,R[t]||(R[t]=new s),R[t].set(e),R[t]}function g(t){return t?(!R[t]&&U&&e("./lang/"+t),R[t]):k.fn._lang}function v(t){return t.match(/\[.*\]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function y(t){var e,n,i=t.match(z);for(e=0,n=i.length;n>e;e++)i[e]=ae[i[e]]?ae[i[e]]:v(i[e]);return function(o){var r="";for(e=0;n>e;e++)r+="function"==typeof i[e].call?i[e].call(o,t):i[e];return r}}function S(t,e){function n(e){return t.lang().longDateFormat(e)||e}for(var i=5;i--&&P.test(e);)e=e.replace(P,n);return oe[e]||(oe[e]=y(e)),oe[e](t)}function T(t){switch(t){case"DDDD":return V;case"YYYY":return B;case"YYYYY":return Z;case"S":case"SS":case"SSS":case"DDD":return q;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":case"a":case"A":return X;case"X":return $;case"Z":case"ZZ":return K;case"T":return J;case"MM":case"DD":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return W;default:return RegExp(t.replace("\\",""))}}function w(t,e,n){var i,o=n._a;switch(t){case"M":case"MM":o[1]=null==e?0:~~e-1;break;case"MMM":case"MMMM":i=g(n._l).monthsParse(e),null!=i?o[1]=i:n._isValid=!1;break;case"D":case"DD":case"DDD":case"DDDD":null!=e&&(o[2]=~~e);break;case"YY":o[0]=~~e+(~~e>68?1900:2e3);break;case"YYYY":case"YYYYY":o[0]=~~e;break;case"a":case"A":n._isPm="pm"===(e+"").toLowerCase();break;case"H":case"HH":case"h":case"hh":o[3]=~~e;break;case"m":case"mm":o[4]=~~e;break;case"s":case"ss":o[5]=~~e;break;case"S":case"SS":case"SSS":o[6]=~~(1e3*("0."+e));break;case"X":n._d=new Date(1e3*parseFloat(e));break;case"Z":case"ZZ":n._useUTC=!0,i=(e+"").match(ee),i&&i[1]&&(n._tzh=~~i[1]),i&&i[2]&&(n._tzm=~~i[2]),i&&"+"===i[0]&&(n._tzh=-n._tzh,n._tzm=-n._tzm)}null==e&&(n._isValid=!1)}function E(t){var e,n,i=[];if(!t._d){for(e=0;7>e;e++)t._a[e]=i[e]=null==t._a[e]?2===e?1:0:t._a[e];i[3]+=t._tzh||0,i[4]+=t._tzm||0,n=new Date(0),t._useUTC?(n.setUTCFullYear(i[0],i[1],i[2]),n.setUTCHours(i[3],i[4],i[5],i[6])):(n.setFullYear(i[0],i[1],i[2]),n.setHours(i[3],i[4],i[5],i[6])),t._d=n}}function b(t){var e,n,i=t._f.match(z),o=t._i;for(t._a=[],e=0;i.length>e;e++)n=(T(i[e]).exec(o)||[])[0],n&&(o=o.slice(o.indexOf(n)+n.length)),ae[i[e]]&&w(i[e],n,t);t._isPm&&12>t._a[3]&&(t._a[3]+=12),t._isPm===!1&&12===t._a[3]&&(t._a[3]=0),E(t)}function M(t){for(var e,n,i,o,r=99;t._f.length;){if(e=c({},t),e._f=t._f.pop(),b(e),n=new a(e),n.isValid()){i=n;break}o=f(e._a,n.toArray()),r>o&&(r=o,i=n)}c(t,i)}function _(t){var e,n=t._i;if(Q.exec(n)){for(t._f="YYYY-MM-DDT",e=0;4>e;e++)if(te[e][1].exec(n)){t._f+=te[e][0];break}K.exec(n)&&(t._f+=" Z"),b(t)}else t._d=new Date(n)}function D(t){var e=t._i,n=F.exec(e);e===i?t._d=new Date:n?t._d=new Date(+n[1]):"string"==typeof e?_(t):d(e)?(t._a=e.slice(0),E(t)):t._d=e instanceof Date?new Date(+e):new Date(e)}function L(t,e,n,i,o){return o.relativeTime(e||1,!!n,t,i)}function C(t,e,n){var i=j(Math.abs(t)/1e3),o=j(i/60),r=j(o/60),s=j(r/24),a=j(s/365),h=45>i&&["s",i]||1===o&&["m"]||45>o&&["mm",o]||1===r&&["h"]||22>r&&["hh",r]||1===s&&["d"]||25>=s&&["dd",s]||45>=s&&["M"]||345>s&&["MM",j(s/30)]||1===a&&["y"]||["yy",a];return h[2]=e,h[3]=t>0,h[4]=n,L.apply({},h)}function x(t,e,n){var i=n-e,o=n-t.day();return o>i&&(o-=7),i-7>o&&(o+=7),Math.ceil(k(t).add("d",o).dayOfYear()/7)}function A(t){var e=t._i,n=t._f;return null===e||""===e?null:("string"==typeof e&&(t._i=e=g().preparse(e)),k.isMoment(e)?(t=c({},e),t._d=new Date(+e._d)):n?d(n)?M(t):b(t):D(t),new a(t))}function N(t,e){k.fn[t]=k.fn[t+"s"]=function(t){var n=this._isUTC?"UTC":"";return null!=t?(this._d["set"+n+e](t),this):this._d["get"+n+e]()}}function O(t){k.duration.fn[t]=function(){return this._data[t]}}function Y(t,e){k.duration.fn["as"+t]=function(){return+this/e}}for(var k,H,I="2.0.0",j=Math.round,R={},U=n!==i&&n.exports,F=/^\/?Date\((\-?\d+)/i,z=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,P=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,W=/\d\d?/,q=/\d{1,3}/,V=/\d{3}/,B=/\d{1,4}/,Z=/[+\-]?\d{1,6}/,X=/[0-9]*[a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF]+\s*?[\u0600-\u06FF]+/i,K=/Z|[\+\-]\d\d:?\d\d/i,J=/T/i,$=/[\+\-]?\d+(\.\d{1,3})?/,Q=/^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,G="YYYY-MM-DDTHH:mm:ssZ",te=[["HH:mm:ss.S",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ee=/([\+\-]|\d\d)/gi,ne="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),ie={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},oe={},re="DDD w W M D d".split(" "),se="M D H h m s w W".split(" "),ae={M:function(){return this.month()+1},MMM:function(t){return this.lang().monthsShort(this,t)},MMMM:function(t){return this.lang().months(this,t)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(t){return this.lang().weekdaysMin(this,t)},ddd:function(t){return this.lang().weekdaysShort(this,t)},dddd:function(t){return this.lang().weekdays(this,t)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return u(this.year()%100,2)},YYYY:function(){return u(this.year(),4)},YYYYY:function(){return u(this.year(),5)},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return~~(this.milliseconds()/100)},SS:function(){return u(~~(this.milliseconds()/10),2)},SSS:function(){return u(this.milliseconds(),3)},Z:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(t/60),2)+":"+u(~~t%60,2)},ZZ:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(10*t/6),4)},X:function(){return this.unix()}};re.length;)H=re.pop(),ae[H+"o"]=r(ae[H]);for(;se.length;)H=se.pop(),ae[H+H]=o(ae[H],2);for(ae.DDDD=o(ae.DDD,3),s.prototype={set:function(t){var e,n;for(n in t)e=t[n],"function"==typeof e?this[n]=e:this["_"+n]=e},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(t){return this._months[t.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(t){return this._monthsShort[t.month()]},monthsParse:function(t){var e,n,i;for(this._monthsParse||(this._monthsParse=[]),e=0;12>e;e++)if(this._monthsParse[e]||(n=k([2e3,e]),i="^"+this.months(n,"")+"|^"+this.monthsShort(n,""),this._monthsParse[e]=RegExp(i.replace(".",""),"i")),this._monthsParse[e].test(t))return e},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(t){return this._weekdays[t.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(t){return this._weekdaysShort[t.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(t){return this._weekdaysMin[t.day()]},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(t){var e=this._longDateFormat[t];return!e&&this._longDateFormat[t.toUpperCase()]&&(e=this._longDateFormat[t.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t]=e),e},meridiem:function(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[last] dddd [at] LT",sameElse:"L"},calendar:function(t,e){var n=this._calendar[t];return"function"==typeof n?n.apply(e):n},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(t,e,n,i){var o=this._relativeTime[n];return"function"==typeof o?o(t,e,n,i):o.replace(/%d/i,t)},pastFuture:function(t,e){var n=this._relativeTime[t>0?"future":"past"]; -return"function"==typeof n?n(e):n.replace(/%s/i,e)},ordinal:function(t){return this._ordinal.replace("%d",t)},_ordinal:"%d",preparse:function(t){return t},postformat:function(t){return t},week:function(t){return x(t,this._week.dow,this._week.doy)},_week:{dow:0,doy:6}},k=function(t,e,n){return A({_i:t,_f:e,_l:n,_isUTC:!1})},k.utc=function(t,e,n){return A({_useUTC:!0,_isUTC:!0,_l:n,_i:t,_f:e})},k.unix=function(t){return k(1e3*t)},k.duration=function(t,e){var n,i=k.isDuration(t),o="number"==typeof t,r=i?t._data:o?{}:t;return o&&(e?r[e]=t:r.milliseconds=t),n=new h(r),i&&t.hasOwnProperty("_lang")&&(n._lang=t._lang),n},k.version=I,k.defaultFormat=G,k.lang=function(t,e){return t?(e?m(t,e):R[t]||g(t),k.duration.fn._lang=k.fn._lang=g(t),i):k.fn._lang._abbr},k.langData=function(t){return t&&t._lang&&t._lang._abbr&&(t=t._lang._abbr),g(t)},k.isMoment=function(t){return t instanceof a},k.isDuration=function(t){return t instanceof h},k.fn=a.prototype={clone:function(){return k(this)},valueOf:function(){return+this._d},unix:function(){return Math.floor(+this._d/1e3)},toString:function(){return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._d},toJSON:function(){return k.utc(this).format("YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var t=this;return[t.year(),t.month(),t.date(),t.hours(),t.minutes(),t.seconds(),t.milliseconds()]},isValid:function(){return null==this._isValid&&(this._isValid=this._a?!f(this._a,(this._isUTC?k.utc(this._a):k(this._a)).toArray()):!isNaN(this._d.getTime())),!!this._isValid},utc:function(){return this._isUTC=!0,this},local:function(){return this._isUTC=!1,this},format:function(t){var e=S(this,t||k.defaultFormat);return this.lang().postformat(e)},add:function(t,e){var n;return n="string"==typeof t?k.duration(+e,t):k.duration(t,e),l(this,n,1),this},subtract:function(t,e){var n;return n="string"==typeof t?k.duration(+e,t):k.duration(t,e),l(this,n,-1),this},diff:function(t,e,n){var i,o,r=this._isUTC?k(t).utc():k(t).local(),s=6e4*(this.zone()-r.zone());return e&&(e=e.replace(/s$/,"")),"year"===e||"month"===e?(i=432e5*(this.daysInMonth()+r.daysInMonth()),o=12*(this.year()-r.year())+(this.month()-r.month()),o+=(this-k(this).startOf("month")-(r-k(r).startOf("month")))/i,"year"===e&&(o/=12)):(i=this-r-s,o="second"===e?i/1e3:"minute"===e?i/6e4:"hour"===e?i/36e5:"day"===e?i/864e5:"week"===e?i/6048e5:i),n?o:p(o)},from:function(t,e){return k.duration(this.diff(t)).lang(this.lang()._abbr).humanize(!e)},fromNow:function(t){return this.from(k(),t)},calendar:function(){var t=this.diff(k().startOf("day"),"days",!0),e=-6>t?"sameElse":-1>t?"lastWeek":0>t?"lastDay":1>t?"sameDay":2>t?"nextDay":7>t?"nextWeek":"sameElse";return this.format(this.lang().calendar(e,this))},isLeapYear:function(){var t=this.year();return 0===t%4&&0!==t%100||0===t%400},isDST:function(){return this.zone()+k(t).startOf(e)},isBefore:function(t,e){return e=e!==i?e:"millisecond",+this.clone().startOf(e)<+k(t).startOf(e)},isSame:function(t,e){return e=e!==i?e:"millisecond",+this.clone().startOf(e)===+k(t).startOf(e)},zone:function(){return this._isUTC?0:this._d.getTimezoneOffset()},daysInMonth:function(){return k.utc([this.year(),this.month()+1,0]).date()},dayOfYear:function(t){var e=j((k(this).startOf("day")-k(this).startOf("year"))/864e5)+1;return null==t?e:this.add("d",t-e)},isoWeek:function(t){var e=x(this,1,4);return null==t?e:this.add("d",7*(t-e))},week:function(t){var e=this.lang().week(this);return null==t?e:this.add("d",7*(t-e))},lang:function(t){return t===i?this._lang:(this._lang=g(t),this)}},H=0;ne.length>H;H++)N(ne[H].toLowerCase().replace(/s$/,""),ne[H]);N("year","FullYear"),k.fn.days=k.fn.day,k.fn.weeks=k.fn.week,k.fn.isoWeeks=k.fn.isoWeek,k.duration.fn=h.prototype={weeks:function(){return p(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+2592e6*this._months},humanize:function(t){var e=+this,n=C(e,!t,this.lang());return t&&(n=this.lang().pastFuture(e,n)),this.lang().postformat(n)},lang:k.fn.lang};for(H in ie)ie.hasOwnProperty(H)&&(Y(H,ie[H]),O(H.toLowerCase()));Y("Weeks",6048e5),k.lang("en",{ordinal:function(t){var e=t%10,n=1===~~(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),U&&(n.exports=k),"undefined"==typeof ender&&(this.moment=k),"function"==typeof t&&t.amd&&t("moment",[],function(){return k})}).call(this)})()},{}],14:[function(t,e,n){function i(t,e,n){var i=this;if(this.options={orientation:"bottom",zoomMin:10,zoomMax:31536e10,moveable:!0,zoomable:!0},this.controller=new a,!t)throw Error("No container element provided");this.main=new h(t,{autoResize:!1,height:function(){return i.timeaxis.height+i.itemset.height}}),this.controller.add(this.main);var o=r().hours(0).minutes(0).seconds(0).milliseconds(0);this.range=new s({start:o.clone().add("days",-3).valueOf(),end:o.clone().add("days",4).valueOf()}),this.range.subscribe(this.main,"move","horizontal"),this.range.subscribe(this.main,"zoom","horizontal"),this.range.on("rangechange",function(){i.controller.requestReflow()}),this.range.on("rangechanged",function(){i.controller.requestReflow()}),this.timeaxis=new c(this.main,[],{orientation:this.options.orientation,range:this.range}),this.timeaxis.setRange(this.range),this.controller.add(this.timeaxis),this.itemset=new p(this.main,[this.timeaxis],{orientation:this.options.orientation}),this.itemset.setRange(this.range),this.controller.add(this.itemset),e&&this.setData(e),this.setOptions(n)}var o=t("./../util"),r=t("moment"),s=t("../range"),a=t("../controller"),h=(t("../component/component"),t("../component/rootpanel")),c=t("../component/timeaxis"),p=t("../component/itemset");i.prototype.setOptions=function(t){o.extend(this.options,t),this.timeaxis.setOptions(this.options),this.range.setOptions(this.options);var e,n=this;e="top"==this.options.orientation?function(){return n.timeaxis.height}:function(){return n.main.height-n.timeaxis.height-n.itemset.height},this.itemset.setOptions({orientation:this.options.orientation,top:e}),this.controller.repaint()},i.prototype.setData=function(t){var e=this.itemset.data;if(e)this.itemset.setData(t);else{this.itemset.setData(t);var n=this.itemset.getDataRange(),i=n.min,o=n.max;if(null!=i&&null!=o){var r=o.valueOf()-i.valueOf();i=new Date(i.valueOf()-.05*r),o=new Date(o.valueOf()+.05*r)}(null!=i||null!=o)&&this.range.setRange(i,o)}},e.exports=n=i},{"./../util":8,"../range":5,"../controller":2,"../component/component":9,"../component/rootpanel":11,"../component/timeaxis":13,"../component/itemset":12,moment:18}],19:[function(t,e,n){function i(t,e,n){this.parent=t,this.data=e,this.selected=!1,this.visible=!0,this.dom=null,this.options=n}var o=t("../component");i.prototype=new o,i.prototype.select=function(){this.selected=!0},i.prototype.unselect=function(){this.selected=!1},e.exports=n=i},{"../component":9}]},{},[1])(1)}),"function"==typeof define&&define(function(){return vis});var loadCss=function(t){document.getElementsByTagName("script");var e=document.createElement("style");e.type="text/css",e.styleSheet?e.styleSheet.cssText=t:e.appendChild(document.createTextNode(t)),document.getElementsByTagName("head")[0].appendChild(e)};loadCss("/* vis.js stylesheet */\n\n.graph {\n position: relative;\n border: 1px solid #bfbfbf;\n}\n\n.graph .panel {\n position: absolute;\n}\n\n.graph .itemset {\n position: absolute;\n padding: 0;\n margin: 0;\n overflow: hidden;\n}\n\n.graph .background {\n}\n\n.graph .foreground {\n}\n\n.graph .itemset-axis {\n position: absolute;\n}\n\n.graph .item {\n position: absolute;\n color: #1A1A1A;\n border-color: #97B0F8;\n background-color: #D5DDF6;\n display: inline-block;\n}\n\n.graph .item.selected {\n border-color: #FFC200;\n background-color: #FFF785;\n z-index: 999;\n}\n\n.graph .item.cluster {\n /* TODO: use another color or pattern? */\n background: #97B0F8 url('img/cluster_bg.png');\n color: white;\n}\n.graph .item.cluster.point {\n border-color: #D5DDF6;\n}\n\n.graph .item.box {\n text-align: center;\n border-style: solid;\n border-width: 1px;\n border-radius: 5px;\n -moz-border-radius: 5px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.point {\n background: none;\n}\n\n.graph .dot {\n border: 5px solid #97B0F8;\n position: absolute;\n border-radius: 5px;\n -moz-border-radius: 5px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.range {\n overflow: hidden;\n border-style: solid;\n border-width: 1px;\n border-radius: 2px;\n -moz-border-radius: 2px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.range .drag-left {\n cursor: w-resize;\n z-index: 1000;\n}\n\n.graph .item.range .drag-right {\n cursor: e-resize;\n z-index: 1000;\n}\n\n.graph .item.range .content {\n position: relative;\n display: inline-block;\n}\n\n.graph .item.line {\n position: absolute;\n width: 0;\n border-left-width: 1px;\n border-left-style: solid;\n}\n\n.graph .item .content {\n margin: 5px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n/* TODO: better css name, 'graph' is way to generic */\n\n.graph {\n overflow: hidden;\n}\n\n.graph .axis {\n position: relative;\n}\n\n.graph .axis .text {\n position: absolute;\n color: #4d4d4d;\n padding: 3px;\n white-space: nowrap;\n}\n\n.graph .axis .text.measure {\n position: absolute;\n padding-left: 0;\n padding-right: 0;\n margin-left: 0;\n margin-right: 0;\n visibility: hidden;\n}\n\n.graph .axis .grid.vertical {\n position: absolute;\n width: 0;\n border-right: 1px solid;\n}\n\n.graph .axis .grid.horizontal {\n position: absolute;\n left: 0;\n width: 100%;\n height: 0;\n border-bottom: 1px solid;\n}\n\n.graph .axis .grid.minor {\n border-color: #e5e5e5;\n}\n\n.graph .axis .grid.major {\n border-color: #bfbfbf;\n}\n\n"); \ No newline at end of file +(function(t){if("function"==typeof bootstrap)bootstrap("vis",t);else if("object"==typeof exports)module.exports=t();else if("function"==typeof define&&define.amd)define(t);else if("undefined"!=typeof ses){if(!ses.ok())return;ses.makeVis=t}else"undefined"!=typeof window?window.vis=t():global.vis=t()})(function(){var t;return function(t,e,n){function i(n,o){if(!e[n]){if(!t[n]){var s="function"==typeof require&&require;if(!o&&s)return s(n,!0);if(r)return r(n,!0);throw Error("Cannot find module '"+n+"'")}var a=e[n]={exports:{}};t[n][0].call(a.exports,function(e){var r=t[n][1][e];return i(r?r:e)},a,a.exports)}return e[n].exports}for(var r="function"==typeof require&&require,o=0;n.length>o;o++)i(n[o]);return i}({1:[function(e,n,i){function r(t){var e=this;this.options=t||{},this.data={},this.fieldId=this.options.fieldId||"id",this.fieldTypes={},this.options.fieldTypes&&S.forEach(this.options.fieldTypes,function(t,n){e.fieldTypes[n]="Date"==t||"ISODate"==t||"ASPDate"==t?"Date":t}),this.subscribers={},this.internalIds={}}function o(t,e){this.parent=t,this.options={order:function(t,e){return e.width-t.width||t.left-e.left}},this.ordered=[],this.setOptions(e)}function s(t){this.id=S.randomUUID(),this.start=0,this.end=0,this.options={min:null,max:null,zoomMin:null,zoomMax:null},this.setOptions(t),this.listeners=[]}function a(){this.id=S.randomUUID(),this.components={},this.repaintTimer=void 0,this.reflowTimer=void 0}function h(){this.id=null,this.parent=null,this.depends=null,this.controller=null,this.options=null,this.frame=null,this.top=0,this.left=0,this.width=0,this.height=0}function c(t,e,n){this.id=S.randomUUID(),this.parent=t,this.depends=e,this.options={},this.setOptions(n)}function p(t,e){this.id=S.randomUUID(),this.container=t,this.options={autoResize:!0},this.listeners={},this.setOptions(e)}function u(t,e,n){this.id=S.randomUUID(),this.parent=t,this.depends=e,this.dom={majorLines:[],majorTexts:[],minorLines:[],minorTexts:[],redundant:{majorLines:[],majorTexts:[],minorLines:[],minorTexts:[]}},this.props={range:{start:0,end:0,minimumStep:0},lineTop:0},this.options={orientation:"bottom",showMinorLabels:!0,showMajorLabels:!0},this.conversion=null,this.range=null,this.setOptions(n)}function d(t,e,n){this.id=S.randomUUID(),this.parent=t,this.depends=e,this.options={style:"box",align:"center",orientation:"bottom",margin:{axis:20,item:10},padding:5},this.dom={};var i=this;this.data=null,this.range=null,this.listeners={add:function(t,e){i._onAdd(e.items)},update:function(t,e){i._onUpdate(e.items)},remove:function(t,e){i._onRemove(e.items)}},this.items={},this.queue={},this.stack=new o(this),this.conversion=null,this.setOptions(n)}function l(t,e,n){this.parent=t,this.data=e,this.selected=!1,this.visible=!0,this.dom=null,this.options=n}function f(t,e,n){this.props={dot:{left:0,top:0,width:0,height:0},line:{top:0,left:0,width:0,height:0}},l.call(this,t,e,n)}function m(t,e,n){this.props={dot:{top:0,width:0,height:0},content:{height:0,marginLeft:0}},l.call(this,t,e,n)}function g(t,e,n){this.props={content:{left:0,width:0}},l.call(this,t,e,n)}function v(t,e,n){var i=this;if(this.options={orientation:"bottom",zoomMin:10,zoomMax:31536e10,moveable:!0,zoomable:!0},this.controller=new a,!t)throw Error("No container element provided");this.main=new p(t,{autoResize:!1,height:function(){return i.timeaxis.height+i.itemset.height}}),this.controller.add(this.main);var r=y().hours(0).minutes(0).seconds(0).milliseconds(0);this.range=new s({start:r.clone().add("days",-3).valueOf(),end:r.clone().add("days",4).valueOf()}),this.range.subscribe(this.main,"move","horizontal"),this.range.subscribe(this.main,"zoom","horizontal"),this.range.on("rangechange",function(){i.controller.requestReflow()}),this.range.on("rangechanged",function(){i.controller.requestReflow()}),this.timeaxis=new u(this.main,[],{orientation:this.options.orientation,range:this.range}),this.timeaxis.setRange(this.range),this.controller.add(this.timeaxis),this.itemset=new d(this.main,[this.timeaxis],{orientation:this.options.orientation}),this.itemset.setRange(this.range),this.controller.add(this.itemset),e&&this.setData(e),this.setOptions(n)}var y=e("moment"),S={};S.isNumber=function(t){return t instanceof Number||"number"==typeof t},S.isString=function(t){return t instanceof String||"string"==typeof t},S.isDate=function(t){if(t instanceof Date)return!0;if(S.isString(t)){var e=w.exec(t);if(e)return!0;if(!isNaN(Date.parse(t)))return!0}return!1},S.isDataTable=function(t){return"undefined"!=typeof google&&google.visualization&&google.visualization.DataTable&&t instanceof google.visualization.DataTable},S.randomUUID=function(){var t=function(){return Math.floor(65536*Math.random()).toString(16)};return t()+t()+"-"+t()+"-"+t()+"-"+t()+"-"+t()+t()+t()},S.extend=function(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t},S.cast=function(t,e){if(void 0===t)return void 0;if(null===t)return null;if(!e)return t;if("function"==typeof e)return e(t);switch(e){case"boolean":case"Boolean":return Boolean(t);case"number":case"Number":return Number(t);case"string":case"String":return t+"";case"Date":if(S.isNumber(t))return new Date(t);if(t instanceof Date)return new Date(t.valueOf());if(S.isString(t)){var n=w.exec(t);return n?new Date(Number(n[1])):y(t).toDate()}throw Error("Cannot cast object of type "+S.getType(t)+" to type Date");case"ISODate":if(t instanceof Date)return t.toISOString();if(S.isNumber(t)||S.isString(t))return y(t).toDate().toISOString();throw Error("Cannot cast object of type "+S.getType(t)+" to type ISODate");case"ASPDate":if(t instanceof Date)return"/Date("+t.valueOf()+")/";if(S.isNumber(t)||S.isString(t))return"/Date("+y(t).valueOf()+")/";throw Error("Cannot cast object of type "+S.getType(t)+" to type ASPDate");default:throw Error("Cannot cast object of type "+S.getType(t)+' to type "'+e+'"')}};var w=/^\/?Date\((\-?\d+)/i;if(S.getType=function(t){var e=typeof t;return"object"==e?null==t?"null":t instanceof Boolean?"Boolean":t instanceof Number?"Number":t instanceof String?"String":t instanceof Array?"Array":t instanceof Date?"Date":"Object":"number"==e?"Number":"boolean"==e?"Boolean":"string"==e?"String":e},S.getAbsoluteLeft=function(t){for(var e=document.documentElement,n=document.body,i=t.offsetLeft,r=t.offsetParent;null!=r&&r!=n&&r!=e;)i+=r.offsetLeft,i-=r.scrollLeft,r=r.offsetParent;return i},S.getAbsoluteTop=function(t){for(var e=document.documentElement,n=document.body,i=t.offsetTop,r=t.offsetParent;null!=r&&r!=n&&r!=e;)i+=r.offsetTop,i-=r.scrollTop,r=r.offsetParent;return i},S.getPageY=function(t){if("pageY"in t)return t.pageY;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientY:t.clientY;var n=document.documentElement,i=document.body;return e+(n&&n.scrollTop||i&&i.scrollTop||0)-(n&&n.clientTop||i&&i.clientTop||0)},S.getPageX=function(t){if("pageY"in t)return t.pageX;var e;e="targetTouches"in t&&t.targetTouches.length?t.targetTouches[0].clientX:t.clientX;var n=document.documentElement,i=document.body;return e+(n&&n.scrollLeft||i&&i.scrollLeft||0)-(n&&n.clientLeft||i&&i.clientLeft||0)},S.addClassName=function(t,e){var n=t.className.split(" ");-1==n.indexOf(e)&&(n.push(e),t.className=n.join(" "))},S.removeClassName=function(t,e){var n=t.className.split(" "),i=n.indexOf(e);-1!=i&&(n.splice(i,1),t.className=n.join(" "))},S.forEach=function(t,e){if(t instanceof Array)t.forEach(e);else for(var n in t)t.hasOwnProperty(n)&&e(t[n],n,t)},S.updateProperty=function(t,e,n){return t[e]!==n?(t[e]=n,!0):!1},S.addEventListener=function(t,e,n,i){t.addEventListener?(void 0===i&&(i=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.addEventListener(e,n,i)):t.attachEvent("on"+e,n)},S.removeEventListener=function(t,e,n,i){t.removeEventListener?(void 0===i&&(i=!1),"mousewheel"===e&&navigator.userAgent.indexOf("Firefox")>=0&&(e="DOMMouseScroll"),t.removeEventListener(e,n,i)):t.detachEvent("on"+e,n)},S.getTarget=function(t){t||(t=window.event);var e;return t.target?e=t.target:t.srcElement&&(e=t.srcElement),void 0!=e.nodeType&&3==e.nodeType&&(e=e.parentNode),e},S.stopPropagation=function(t){t||(t=window.event),t.stopPropagation?t.stopPropagation():t.cancelBubble=!0},S.preventDefault=function(t){t||(t=window.event),t.preventDefault?t.preventDefault():t.returnValue=!1},S.option={},S.option.asBoolean=function(t,e){return"function"==typeof t&&(t=t()),null!=t?0!=t:e||null},S.option.asNumber=function(t,e){return"function"==typeof t&&(t=t()),null!=t?Number(t):e||null},S.option.asString=function(t,e){return"function"==typeof t&&(t=t()),null!=t?t+"":e||null},S.option.asSize=function(t,e){return"function"==typeof t&&(t=t()),S.isString(t)?t:S.isNumber(t)?t+"px":e||null},S.option.asElement=function(t,e){return"function"==typeof t&&(t=t()),t||e||null},S.loadCss=function(t){document.getElementsByTagName("script");var e=document.createElement("style");e.type="text/css",e.styleSheet?e.styleSheet.cssText=t:e.appendChild(document.createTextNode(t)),document.getElementsByTagName("head")[0].appendChild(e)},!Array.prototype.indexOf){Array.prototype.indexOf=function(t){for(var e=0;this.length>e;e++)if(this[e]==t)return e;return-1};try{console.log("Warning: Ancient browser detected. Please update your browser")}catch(T){}}Array.prototype.forEach||(Array.prototype.forEach=function(t,e){for(var n=0,i=this.length;i>n;++n)t.call(e||this,this[n],n,this)}),Array.prototype.map||(Array.prototype.map=function(t,e){var n,i,r;if(null==this)throw new TypeError(" this is null or not defined");var o=Object(this),s=o.length>>>0;if("function"!=typeof t)throw new TypeError(t+" is not a function");for(e&&(n=e),i=Array(s),r=0;s>r;){var a,h;r in o&&(a=o[r],h=t.call(n,a,r,o),i[r]=h),r++}return i}),Array.prototype.filter||(Array.prototype.filter=function(t){"use strict";if(null==this)throw new TypeError;var e=Object(this),n=e.length>>>0;if("function"!=typeof t)throw new TypeError;for(var i=[],r=arguments[1],o=0;n>o;o++)if(o in e){var s=e[o];t.call(r,s,o,e)&&i.push(s)}return i}),Object.keys||(Object.keys=function(){var t=Object.prototype.hasOwnProperty,e=!{toString:null}.propertyIsEnumerable("toString"),n=["toString","toLocaleString","valueOf","hasOwnProperty","isPrototypeOf","propertyIsEnumerable","constructor"],i=n.length;return function(r){if("object"!=typeof r&&"function"!=typeof r||null===r)throw new TypeError("Object.keys called on non-object");var o=[];for(var s in r)t.call(r,s)&&o.push(s);if(e)for(var a=0;i>a;a++)t.call(r,n[a])&&o.push(n[a]);return o}}()),Array.isArray||(Array.isArray=function(t){return"[object Array]"===Object.prototype.toString.call(t)});var E={listeners:[],indexOf:function(t){for(var e=this.listeners,n=0,i=this.listeners.length;i>n;n++){var r=e[n];if(r&&r.object==t)return n}return-1},addListener:function(t,e,n){var i=this.indexOf(t),r=this.listeners[i];r||(r={object:t,events:{}},this.listeners.push(r));var o=r.events[e];o||(o=[],r.events[e]=o),-1==o.indexOf(n)&&o.push(n)},removeListener:function(t,e,n){var i=this.indexOf(t),r=this.listeners[i];if(r){var o=r.events[e];o&&(i=o.indexOf(n),-1!=i&&o.splice(i,1),0==o.length&&delete r.events[e]);var s=0,a=r.events;for(var h in a)a.hasOwnProperty(h)&&s++;0==s&&delete this.listeners[i]}},removeAllListeners:function(){this.listeners=[]},trigger:function(t,e,n){var i=this.indexOf(t),r=this.listeners[i];if(r){var o=r.events[e];if(o)for(var s=0,a=o.length;a>s;s++)o[s](n)}}};TimeStep=function(t,e,n){this.current=new Date,this._start=new Date,this._end=new Date,this.autoScale=!0,this.scale=TimeStep.SCALE.DAY,this.step=1,this.setRange(t,e,n)},TimeStep.SCALE={MILLISECOND:1,SECOND:2,MINUTE:3,HOUR:4,DAY:5,WEEKDAY:6,MONTH:7,YEAR:8},TimeStep.prototype.setRange=function(t,e,n){t instanceof Date&&e instanceof Date&&(this._start=void 0!=t?new Date(t.valueOf()):new Date,this._end=void 0!=e?new Date(e.valueOf()):new Date,this.autoScale&&this.setMinimumStep(n))},TimeStep.prototype.first=function(){this.current=new Date(this._start.valueOf()),this.roundToMinor()},TimeStep.prototype.roundToMinor=function(){switch(this.scale){case TimeStep.SCALE.YEAR:this.current.setFullYear(this.step*Math.floor(this.current.getFullYear()/this.step)),this.current.setMonth(0);case TimeStep.SCALE.MONTH:this.current.setDate(1);case TimeStep.SCALE.DAY:case TimeStep.SCALE.WEEKDAY:this.current.setHours(0);case TimeStep.SCALE.HOUR:this.current.setMinutes(0);case TimeStep.SCALE.MINUTE:this.current.setSeconds(0);case TimeStep.SCALE.SECOND:this.current.setMilliseconds(0)}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.setMilliseconds(this.current.getMilliseconds()-this.current.getMilliseconds()%this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()-this.current.getSeconds()%this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()-this.current.getMinutes()%this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()-this.current.getHours()%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()-1-(this.current.getDate()-1)%this.step+1);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()-this.current.getMonth()%this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()-this.current.getFullYear()%this.step);break;default:}},TimeStep.prototype.hasNext=function(){return this.current.valueOf()<=this._end.valueOf()},TimeStep.prototype.next=function(){var t=this.current.valueOf();if(6>this.current.getMonth())switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current=new Date(this.current.valueOf()+1e3*this.step);break;case TimeStep.SCALE.MINUTE:this.current=new Date(this.current.valueOf()+60*1e3*this.step);break;case TimeStep.SCALE.HOUR:this.current=new Date(this.current.valueOf()+60*60*1e3*this.step);var e=this.current.getHours();this.current.setHours(e-e%this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step);break;default:}else switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current=new Date(this.current.valueOf()+this.step);break;case TimeStep.SCALE.SECOND:this.current.setSeconds(this.current.getSeconds()+this.step);break;case TimeStep.SCALE.MINUTE:this.current.setMinutes(this.current.getMinutes()+this.step);break;case TimeStep.SCALE.HOUR:this.current.setHours(this.current.getHours()+this.step);break;case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:this.current.setDate(this.current.getDate()+this.step);break;case TimeStep.SCALE.MONTH:this.current.setMonth(this.current.getMonth()+this.step);break;case TimeStep.SCALE.YEAR:this.current.setFullYear(this.current.getFullYear()+this.step);break;default:}if(1!=this.step)switch(this.scale){case TimeStep.SCALE.MILLISECOND:this.current.getMilliseconds()0&&(this.step=e),this.autoScale=!1},TimeStep.prototype.setAutoScale=function(t){this.autoScale=t},TimeStep.prototype.setMinimumStep=function(t){if(void 0!=t){var e=31104e6,n=2592e6,i=864e5,r=36e5,o=6e4,s=1e3,a=1;1e3*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1e3),500*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=500),100*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=100),50*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=50),10*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=10),5*e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=5),e>t&&(this.scale=TimeStep.SCALE.YEAR,this.step=1),3*n>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=3),n>t&&(this.scale=TimeStep.SCALE.MONTH,this.step=1),5*i>t&&(this.scale=TimeStep.SCALE.DAY,this.step=5),2*i>t&&(this.scale=TimeStep.SCALE.DAY,this.step=2),i>t&&(this.scale=TimeStep.SCALE.DAY,this.step=1),i/2>t&&(this.scale=TimeStep.SCALE.WEEKDAY,this.step=1),4*r>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=4),r>t&&(this.scale=TimeStep.SCALE.HOUR,this.step=1),15*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=15),10*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=10),5*o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=5),o>t&&(this.scale=TimeStep.SCALE.MINUTE,this.step=1),15*s>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=15),10*s>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=10),5*s>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=5),s>t&&(this.scale=TimeStep.SCALE.SECOND,this.step=1),200*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=200),100*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=100),50*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=50),10*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=10),5*a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=5),a>t&&(this.scale=TimeStep.SCALE.MILLISECOND,this.step=1)}},TimeStep.prototype.snap=function(t){if(this.scale==TimeStep.SCALE.YEAR){var e=t.getFullYear()+Math.round(t.getMonth()/12);t.setFullYear(Math.round(e/this.step)*this.step),t.setMonth(0),t.setDate(0),t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MONTH)t.getDate()>15?(t.setDate(1),t.setMonth(t.getMonth()+1)):t.setDate(1),t.setHours(0),t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0);else if(this.scale==TimeStep.SCALE.DAY||this.scale==TimeStep.SCALE.WEEKDAY){switch(this.step){case 5:case 2:t.setHours(24*Math.round(t.getHours()/24));break;default:t.setHours(12*Math.round(t.getHours()/12))}t.setMinutes(0),t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.HOUR){switch(this.step){case 4:t.setMinutes(60*Math.round(t.getMinutes()/60));break;default:t.setMinutes(30*Math.round(t.getMinutes()/30))}t.setSeconds(0),t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.MINUTE){switch(this.step){case 15:case 10:t.setMinutes(5*Math.round(t.getMinutes()/5)),t.setSeconds(0);break;case 5:t.setSeconds(60*Math.round(t.getSeconds()/60));break;default:t.setSeconds(30*Math.round(t.getSeconds()/30))}t.setMilliseconds(0)}else if(this.scale==TimeStep.SCALE.SECOND)switch(this.step){case 15:case 10:t.setSeconds(5*Math.round(t.getSeconds()/5)),t.setMilliseconds(0);break;case 5:t.setMilliseconds(1e3*Math.round(t.getMilliseconds()/1e3));break;default:t.setMilliseconds(500*Math.round(t.getMilliseconds()/500))}else if(this.scale==TimeStep.SCALE.MILLISECOND){var n=this.step>5?this.step/2:1;t.setMilliseconds(Math.round(t.getMilliseconds()/n)*n)}},TimeStep.prototype.isMajor=function(){switch(this.scale){case TimeStep.SCALE.MILLISECOND:return 0==this.current.getMilliseconds();case TimeStep.SCALE.SECOND:return 0==this.current.getSeconds();case TimeStep.SCALE.MINUTE:return 0==this.current.getHours()&&0==this.current.getMinutes();case TimeStep.SCALE.HOUR:return 0==this.current.getHours();case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return 1==this.current.getDate();case TimeStep.SCALE.MONTH:return 0==this.current.getMonth();case TimeStep.SCALE.YEAR:return!1;default:return!1}},TimeStep.prototype.getLabelMinor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return y(t).format("SSS");case TimeStep.SCALE.SECOND:return y(t).format("s");case TimeStep.SCALE.MINUTE:return y(t).format("HH:mm");case TimeStep.SCALE.HOUR:return y(t).format("HH:mm");case TimeStep.SCALE.WEEKDAY:return y(t).format("ddd D");case TimeStep.SCALE.DAY:return y(t).format("D");case TimeStep.SCALE.MONTH:return y(t).format("MMM");case TimeStep.SCALE.YEAR:return y(t).format("YYYY");default:return""}},TimeStep.prototype.getLabelMajor=function(t){switch(void 0==t&&(t=this.current),this.scale){case TimeStep.SCALE.MILLISECOND:return y(t).format("HH:mm:ss");case TimeStep.SCALE.SECOND:return y(t).format("D MMMM HH:mm");case TimeStep.SCALE.MINUTE:case TimeStep.SCALE.HOUR:return y(t).format("ddd D MMMM");case TimeStep.SCALE.WEEKDAY:case TimeStep.SCALE.DAY:return y(t).format("MMMM YYYY");case TimeStep.SCALE.MONTH:return y(t).format("YYYY");case TimeStep.SCALE.YEAR:return"";default:return""}},r.prototype.subscribe=function(t,e,n){var i=this.subscribers[t];i||(i=[],this.subscribers[t]=i),i.push({id:n?n+"":null,callback:e})},r.prototype.unsubscribe=function(t,e){var n=this.subscribers[t];n&&(this.subscribers[t]=n.filter(function(t){return t.callback!=e}))},r.prototype._trigger=function(t,e,n){if("*"==t)throw Error("Cannot trigger event *");var i=[];t in this.subscribers&&(i=i.concat(this.subscribers[t])),"*"in this.subscribers&&(i=i.concat(this.subscribers["*"])),i.forEach(function(i){i.id!=n&&i.callback&&i.callback(t,e,n||null)})},r.prototype.add=function(t,e){var n,i=[],r=this;if(t instanceof Array)t.forEach(function(t){var e=r._addItem(t);i.push(e)});else if(S.isDataTable(t))for(var o=this._getColumnNames(t),s=0,a=t.getNumberOfRows();a>s;s++){var h={};o.forEach(function(e,n){h[e]=t.getValue(s,n)}),n=r._addItem(h),i.push(n)}else{if(!(t instanceof Object))throw Error("Unknown dataType");n=r._addItem(t),i.push(n)}this._trigger("add",{items:i},e)},r.prototype.update=function(t,e){var n,i=[],r=this;if(t instanceof Array)t.forEach(function(t){var e=r._updateItem(t);i.push(e)});else if(S.isDataTable(t))for(var o=this._getColumnNames(t),s=0,a=t.getNumberOfRows();a>s;s++){var h={};o.forEach(function(e,n){h[e]=t.getValue(s,n)}),n=r._updateItem(h),i.push(n)}else{if(!(t instanceof Object))throw Error("Unknown dataType");n=r._updateItem(t),i.push(n)}this._trigger("update",{items:i},e)},r.prototype.get=function(t,e,n){var i=this;"Object"==S.getType(t)&&(n=e,e=t,t=void 0);var r={};this.options&&this.options.fieldTypes&&S.forEach(this.options.fieldTypes,function(t,e){r[e]=t}),e&&e.fieldTypes&&S.forEach(e.fieldTypes,function(t,e){r[e]=t});var o,s=e?e.fields:void 0;if(e&&e.type){if(o="DataTable"==e.type?"DataTable":"Array",n&&o!=S.getType(n))throw Error('Type of parameter "data" ('+S.getType(n)+") "+"does not correspond with specified options.type ("+e.type+")");if("DataTable"==o&&!S.isDataTable(n))throw Error('Parameter "data" must be a DataTable when options.type is "DataTable"')}else o=n?"DataTable"==S.getType(n)?"DataTable":"Array":"Array";if("DataTable"==o){var a=this._getColumnNames(n);if(void 0==t)S.forEach(this.data,function(t){i._appendRow(n,a,i._castItem(t))});else if(S.isNumber(t)||S.isString(t)){var h=i._castItem(i.data[t],r,s);this._appendRow(n,a,h)}else{if(!(t instanceof Array))throw new TypeError('Parameter "ids" must be undefined, a String, Number, or Array');t.forEach(function(t){var e=i._castItem(i.data[t],r,s);i._appendRow(n,a,e)})}}else if(n=n||[],void 0==t)S.forEach(this.data,function(t){n.push(i._castItem(t,r,s))});else{if(S.isNumber(t)||S.isString(t))return this._castItem(i.data[t],r,s);if(!(t instanceof Array))throw new TypeError('Parameter "ids" must be undefined, a String, Number, or Array');t.forEach(function(t){n.push(i._castItem(i.data[t],r,s))})}return n},r.prototype.remove=function(t,e){var n=[],i=this;if(S.isNumber(t)||S.isString(t))delete this.data[t],delete this.internalIds[t],n.push(t);else if(t instanceof Array)t.forEach(function(t){i.remove(t)}),n=n.concat(t);else if(t instanceof Object)for(var r in this.data)this.data.hasOwnProperty(r)&&this.data[r]==t&&(delete this.data[r],delete this.internalIds[r],n.push(r));this._trigger("remove",{items:n},e)},r.prototype.clear=function(t){var e=Object.keys(this.data);this.data={},this.internalIds={},this._trigger("remove",{items:e},t)},r.prototype.max=function(t){var e=this.data,n=Object.keys(e),i=null,r=null;return n.forEach(function(n){var o=e[n],s=o[t];null!=s&&(!i||s>r)&&(i=o,r=s)}),i},r.prototype.min=function(t){var e=this.data,n=Object.keys(e),i=null,r=null;return n.forEach(function(n){var o=e[n],s=o[t];null!=s&&(!i||r>s)&&(i=o,r=s)}),i},r.prototype._addItem=function(t){var e=t[this.fieldId];void 0==e&&(e=S.randomUUID(),t[this.fieldId]=e,this.internalIds[e]=t);var n={};for(var i in t)if(t.hasOwnProperty(i)){var r=this.fieldTypes[i];n[i]=S.cast(t[i],r)}return this.data[e]=n,e},r.prototype._castItem=function(t,e,n){var i,r=this.fieldId,o=this.internalIds;return t?(i={},e=e||{},n?S.forEach(t,function(t,r){-1!=n.indexOf(r)&&(i[r]=S.cast(t,e[r]))}):S.forEach(t,function(t,n){n==r&&t in o||(i[n]=S.cast(t,e[n]))})):i=null,i},r.prototype._updateItem=function(t){var e=t[this.fieldId];if(void 0==e)throw Error("Item has no id (item: "+JSON.stringify(t)+")");var n=this.data[e];if(n){for(var i in t)if(t.hasOwnProperty(i)){var r=this.fieldTypes[i];n[i]=S.cast(t[i],r)}}else this._addItem(t);return e},r.prototype._getColumnNames=function(t){for(var e=[],n=0,i=t.getNumberOfColumns();i>n;n++)e[n]=t.getColumnId(n)||t.getColumnLabel(n);return e},r.prototype._appendRow=function(t,e,n){var i=t.addRow();e.forEach(function(e,r){t.setValue(i,r,n[e])})},o.prototype.setOptions=function(t){S.extend(this.options,t)},o.prototype.update=function(){this._order(),this._stack()},o.prototype._order=function(){var t=this.parent.items;if(!t)throw Error("Cannot stack items: parent does not contain items");var e=[],n=0;S.forEach(t,function(t){e[n]=t,n++});var i=this.options.order;if("function"!=typeof this.options.order)throw Error("Option order must be a function");e.sort(i),this.ordered=e},o.prototype._stack=function(){var t,e,n=this.ordered,i=this.options,r="top"==i.orientation,o=i.margin&&i.margin.item||0;for(t=0,e=n.length;e>t;t++){var s=n[t],a=null;do a=this.checkOverlap(n,t,0,t-1,o),null!=a&&(s.top=r?a.top+a.height+o:a.top-s.height-o);while(a)}},o.prototype.checkOverlap=function(t,e,n,i,r){for(var o=this.collision,s=t[e],a=i;a>=n;a--){var h=t[a];if(o(s,h,r)&&a!=e)return h}return null},o.prototype.collision=function(t,e,n){return t.left-ne.left&&t.top-ne.top},s.prototype.setOptions=function(t){S.extend(this.options,t),(null!=t.start||null!=t.end)&&this.setRange(t.start,t.end)},s.prototype.subscribe=function(t,e,n){var i,r=this;if("horizontal"!=n&&"vertical"!=n)throw new TypeError('Unknown direction "'+n+'". '+'Choose "horizontal" or "vertical".');if("move"==e)i={component:t,event:e,direction:n,callback:function(t){r._onMouseDown(t,i)},params:{}},t.on("mousedown",i.callback),r.listeners.push(i);else{if("zoom"!=e)throw new TypeError('Unknown event "'+e+'". '+'Choose "move" or "zoom".');i={component:t,event:e,direction:n,callback:function(t){r._onMouseWheel(t,i)},params:{}},t.on("mousewheel",i.callback),r.listeners.push(i)}},s.prototype.on=function(t,e){E.addListener(this,t,e)},s.prototype._trigger=function(t){E.trigger(this,t,{start:this.start,end:this.end})},s.prototype.setRange=function(t,e){var n=this._applyRange(t,e);n&&(this._trigger("rangechange"),this._trigger("rangechanged"))},s.prototype._applyRange=function(t,e){var n,i=null!=t?S.cast(t,"Number"):this.start,r=null!=e?S.cast(e,"Number"):this.end;if(isNaN(i))throw Error('Invalid start "'+t+'"');if(isNaN(r))throw Error('Invalid end "'+e+'"');if(i>r&&(r=i),null!=this.options.min){var o=this.options.min.valueOf();o>i&&(n=o-i,i+=n,r+=n)}if(null!=this.options.max){var s=this.options.max.valueOf();r>s&&(n=r-s,i-=n,r-=n)}if(null!=this.options.zoomMin){var a=this.options.zoomMin.valueOf();0>a&&(a=0),a>r-i&&(this.end-this.start>a?(n=a-(r-i),i-=n/2,r+=n/2):(i=this.start,r=this.end))}if(null!=this.options.zoomMax){var h=this.options.zoomMax.valueOf();0>h&&(h=0),r-i>h&&(h>this.end-this.start?(n=r-i-h,i+=n/2,r-=n/2):(i=this.start,r=this.end))}var c=this.start!=i||this.end!=r;return this.start=i,this.end=r,c},s.prototype.getRange=function(){return{start:this.start,end:this.end}},s.prototype.conversion=function(t){return this.start,this.end,s.conversion(this.start,this.end,t)},s.conversion=function(t,e,n){return 0!=n&&0!=e-t?{offset:t,factor:n/(e-t)}:{offset:0,factor:1}},s.prototype._onMouseDown=function(t,e){t=t||window.event;var n=e.params,i=t.which?1==t.which:1==t.button;if(i){n.mouseX=S.getPageX(t),n.mouseY=S.getPageY(t),n.previousLeft=0,n.previousOffset=0,n.moved=!1,n.start=this.start,n.end=this.end;var r=e.component.frame;r&&(r.style.cursor="move");var o=this;n.onMouseMove||(n.onMouseMove=function(t){o._onMouseMove(t,e)},S.addEventListener(document,"mousemove",n.onMouseMove)),n.onMouseUp||(n.onMouseUp=function(t){o._onMouseUp(t,e)},S.addEventListener(document,"mouseup",n.onMouseUp)),S.preventDefault(t)}},s.prototype._onMouseMove=function(t,e){t=t||window.event;var n=e.params,i=S.getPageX(t),r=S.getPageY(t);void 0==n.mouseX&&(n.mouseX=i),void 0==n.mouseY&&(n.mouseY=r);var o=i-n.mouseX,s=r-n.mouseY,a="horizontal"==e.direction?o:s;Math.abs(a)>=1&&(n.moved=!0);var h=n.end-n.start,c="horizontal"==e.direction?e.component.width:e.component.height,p=-a/c*h;this._applyRange(n.start+p,n.end+p),this._trigger("rangechange"),S.preventDefault(t)},s.prototype._onMouseUp=function(t,e){t=t||window.event;var n=e.params;e.component.frame&&(e.component.frame.style.cursor="auto"),n.onMouseMove&&(S.removeEventListener(document,"mousemove",n.onMouseMove),n.onMouseMove=null),n.onMouseUp&&(S.removeEventListener(document,"mouseup",n.onMouseUp),n.onMouseUp=null),n.moved&&this._trigger("rangechanged")},s.prototype._onMouseWheel=function(t,e){t=t||window.event;var n=0;if(t.wheelDelta?n=t.wheelDelta/120:t.detail&&(n=-t.detail/3),n){var i=this,r=function(){var r=n/5,o=null,s=e.component.frame;if(s){var a,h;if("horizontal"==e.direction){a=e.component.width,h=i.conversion(a);var c=S.getAbsoluteLeft(s),p=S.getPageX(t);o=(p-c)/h.factor+h.offset}else{a=e.component.height,h=i.conversion(a);var u=S.getAbsoluteTop(s),d=S.getPageY(t);o=(u+a-d-u)/h.factor+h.offset}}i.zoom(r,o)};r()}S.preventDefault(t)},s.prototype.zoom=function(t,e){null==e&&(e=(this.start+this.end)/2),t>=1&&(t=.9),-1>=t&&(t=-.9),0>t&&(t/=1+t);var n=this.start-e,i=this.end-e,r=this.start-n*t,o=this.end-i*t;this.setRange(r,o)},s.prototype.move=function(t){var e=this.end-this.start,n=this.start+e*t,i=this.end+e*t;this.start=n,this.end=i},a.prototype.add=function(t){if(void 0==t.id)throw Error("Component has no field id");if(!(t instanceof h||t instanceof a))throw new TypeError("Component must be an instance of prototype Component or Controller");t.controller=this,this.components[t.id]=t},a.prototype.requestReflow=function(){if(!this.reflowTimer){var t=this;this.reflowTimer=setTimeout(function(){t.reflowTimer=void 0,t.reflow()},0)}},a.prototype.requestRepaint=function(){if(!this.repaintTimer){var t=this;this.repaintTimer=setTimeout(function(){t.repaintTimer=void 0,t.repaint()},0)}},a.prototype.repaint=function(){function t(i,r){r in n||(i.depends&&i.depends.forEach(function(e){t(e,e.id)}),i.parent&&t(i.parent,i.parent.id),e=i.repaint()||e,n[r]=!0)}var e=!1;this.repaintTimer&&(clearTimeout(this.repaintTimer),this.repaintTimer=void 0);var n={};S.forEach(this.components,t),e&&this.reflow()},a.prototype.reflow=function(){function t(i,r){r in n||(i.depends&&i.depends.forEach(function(e){t(e,e.id)}),i.parent&&t(i.parent,i.parent.id),e=i.reflow()||e,n[r]=!0)}var e=!1;this.reflowTimer&&(clearTimeout(this.reflowTimer),this.reflowTimer=void 0);var n={};S.forEach(this.components,t),e&&this.repaint()},h.prototype.setOptions=function(t){t&&S.extend(this.options,t),this.controller&&(this.requestRepaint(),this.requestReflow()) +},h.prototype.getContainer=function(){return null},h.prototype.getFrame=function(){return this.frame},h.prototype.repaint=function(){return!1},h.prototype.reflow=function(){return!1},h.prototype.requestRepaint=function(){if(!this.controller)throw Error("Cannot request a repaint: no controller configured");this.controller.requestRepaint()},h.prototype.requestReflow=function(){if(!this.controller)throw Error("Cannot request a reflow: no controller configured");this.controller.requestReflow()},h.prototype.on=function(t,e){if(!this.parent)throw Error("Cannot attach event: no root panel found");this.parent.on(t,e)},c.prototype=new h,c.prototype.getContainer=function(){return this.frame},c.prototype.repaint=function(){var t=0,e=S.updateProperty,n=S.option.asSize,i=this.options,r=this.frame;if(r||(r=document.createElement("div"),r.className="panel",i.className&&("function"==typeof i.className?S.addClassName(r,i.className()+""):S.addClassName(r,i.className+"")),this.frame=r,t+=1),!r.parentNode){if(!this.parent)throw Error("Cannot repaint panel: no parent attached");var o=this.parent.getContainer();if(!o)throw Error("Cannot repaint panel: parent has no container element");o.appendChild(r),t+=1}return t+=e(r.style,"top",n(i.top,"0px")),t+=e(r.style,"left",n(i.left,"0px")),t+=e(r.style,"width",n(i.width,"100%")),t+=e(r.style,"height",n(i.height,"100%")),t>0},c.prototype.reflow=function(){var t=0,e=S.updateProperty,n=this.frame;return n?(t+=e(this,"top",n.offsetTop),t+=e(this,"left",n.offsetLeft),t+=e(this,"width",n.offsetWidth),t+=e(this,"height",n.offsetHeight)):t+=1,t>0},p.prototype=new c,p.prototype.setOptions=function(t){S.extend(this.options,t),this.options.autoResize?this._watch():this._unwatch()},p.prototype.repaint=function(){var t=0,e=S.updateProperty,n=S.option.asSize,i=this.options,r=this.frame;if(r||(r=document.createElement("div"),r.className="graph panel",i.className&&S.addClassName(r,S.option.asString(i.className)),this.frame=r,t+=1),!r.parentNode){if(!this.container)throw Error("Cannot repaint root panel: no container attached");this.container.appendChild(r),t+=1}return t+=e(r.style,"top",n(i.top,"0px")),t+=e(r.style,"left",n(i.left,"0px")),t+=e(r.style,"width",n(i.width,"100%")),t+=e(r.style,"height",n(i.height,"100%")),this._updateEventEmitters(),t>0},p.prototype.reflow=function(){var t=0,e=S.updateProperty,n=this.frame;return n?(t+=e(this,"top",n.offsetTop),t+=e(this,"left",n.offsetLeft),t+=e(this,"width",n.offsetWidth),t+=e(this,"height",n.offsetHeight)):t+=1,t>0},p.prototype._watch=function(){var t=this;this._unwatch();var e=function(){return t.options.autoResize?(t.frame&&(t.frame.clientWidth!=t.width||t.frame.clientHeight!=t.height)&&t.requestReflow(),void 0):(t._unwatch(),void 0)};S.addEventListener(window,"resize",e),this.watchTimer=setInterval(e,1e3)},p.prototype._unwatch=function(){this.watchTimer&&(clearInterval(this.watchTimer),this.watchTimer=void 0)},p.prototype.on=function(t,e){var n=this.listeners[t];n||(n=[],this.listeners[t]=n),n.push(e),this._updateEventEmitters()},p.prototype._updateEventEmitters=function(){if(this.listeners){var t=this;S.forEach(this.listeners,function(e,n){if(t.emitters||(t.emitters={}),!(n in t.emitters)){var i=t.frame;if(i){var r=function(t){e.forEach(function(e){e(t)})};t.emitters[n]=r,S.addEventListener(i,n,r)}}})}},u.prototype=new h,u.prototype.setOptions=function(t){S.extend(this.options,t)},u.prototype.setRange=function(t){if(!(t instanceof s||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},u.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.factor+e.offset)},u.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.factor},u.prototype.repaint=function(){var t=0,e=S.updateProperty,n=S.option.asSize,i=this.options,r=this.props,o=this.step,s=this.frame;if(s||(s=document.createElement("div"),this.frame=s,t+=1),s.className="axis "+i.orientation,!s.parentNode){if(!this.parent)throw Error("Cannot repaint time axis: no parent attached");var a=this.parent.getContainer();if(!a)throw Error("Cannot repaint time axis: parent has no container element");a.appendChild(s),t+=1}var h=s.parentNode;if(h){var c=s.nextSibling;h.removeChild(s);var p=i.orientation,u="bottom"==p&&this.props.parentHeight&&this.height?this.props.parentHeight-this.height+"px":"0px";if(t+=e(s.style,"top",n(i.top,u)),t+=e(s.style,"left",n(i.left,"0px")),t+=e(s.style,"width",n(i.width,"100%")),t+=e(s.style,"height",n(i.height,this.height+"px")),this._repaintMeasureChars(),this.step){this._repaintStart(),o.first();for(var d=void 0,l=0;o.hasNext()&&1e3>l;){l++;var f=o.getCurrent(),m=this.toScreen(f),g=o.isMajor();i.showMinorLabels&&this._repaintMinorText(m,o.getLabelMinor()),g&&i.showMajorLabels?(m>0&&(void 0==d&&(d=m),this._repaintMajorText(m,o.getLabelMajor())),this._repaintMajorLine(m)):this._repaintMinorLine(m),o.next()}if(i.showMajorLabels){var v=this.toTime(0),y=o.getLabelMajor(v),w=y.length*(r.majorCharWidth||10)+10;(void 0==d||d>w)&&this._repaintMajorText(0,y)}this._repaintEnd()}this._repaintLine(),c?h.insertBefore(s,c):h.appendChild(s)}return t>0},u.prototype._repaintStart=function(){var t=this.dom,e=t.redundant;e.majorLines=t.majorLines,e.majorTexts=t.majorTexts,e.minorLines=t.minorLines,e.minorTexts=t.minorTexts,t.majorLines=[],t.majorTexts=[],t.minorLines=[],t.minorTexts=[]},u.prototype._repaintEnd=function(){S.forEach(this.dom.redundant,function(t){for(;t.length;){var e=t.pop();e&&e.parentNode&&e.parentNode.removeChild(e)}})},u.prototype._repaintMinorText=function(t,e){var n=this.dom.redundant.minorTexts.shift();if(!n){var i=document.createTextNode("");n=document.createElement("div"),n.appendChild(i),n.className="text minor",this.frame.appendChild(n)}this.dom.minorTexts.push(n),n.childNodes[0].nodeValue=e,n.style.left=t+"px",n.style.top=this.props.minorLabelTop+"px"},u.prototype._repaintMajorText=function(t,e){var n=this.dom.redundant.majorTexts.shift();if(!n){var i=document.createTextNode(e);n=document.createElement("div"),n.className="text major",n.appendChild(i),this.frame.appendChild(n)}this.dom.majorTexts.push(n),n.childNodes[0].nodeValue=e,n.style.top=this.props.majorLabelTop+"px",n.style.left=t+"px"},u.prototype._repaintMinorLine=function(t){var e=this.dom.redundant.minorLines.shift();e||(e=document.createElement("div"),e.className="grid vertical minor",this.frame.appendChild(e)),this.dom.minorLines.push(e);var n=this.props;e.style.top=n.minorLineTop+"px",e.style.height=n.minorLineHeight+"px",e.style.left=t-n.minorLineWidth/2+"px"},u.prototype._repaintMajorLine=function(t){var e=this.dom.redundant.majorLines.shift();e||(e=document.createElement("DIV"),e.className="grid vertical major",this.frame.appendChild(e)),this.dom.majorLines.push(e);var n=this.props;e.style.top=n.majorLineTop+"px",e.style.left=t-n.majorLineWidth/2+"px",e.style.height=n.majorLineHeight+"px"},u.prototype._repaintLine=function(){var t=this.dom.line,e=this.frame,n=this.options;n.showMinorLabels||n.showMajorLabels?(t?(e.removeChild(t),e.appendChild(t)):(t=document.createElement("div"),t.className="grid horizontal major",e.appendChild(t),this.dom.line=t),t.style.top=this.props.lineTop+"px"):t&&axis.parentElement&&(e.removeChild(axis.line),delete this.dom.line)},u.prototype._repaintMeasureChars=function(){var t,e=this.dom;if(!e.characterMinor){t=document.createTextNode("0");var n=document.createElement("DIV");n.className="text minor measure",n.appendChild(t),this.frame.appendChild(n),e.measureCharMinor=n}if(!e.characterMajor){t=document.createTextNode("0");var i=document.createElement("DIV");i.className="text major measure",i.appendChild(t),this.frame.appendChild(i),e.measureCharMajor=i}},u.prototype.reflow=function(){var t=0,e=S.updateProperty,n=this.frame,i=this.range;if(!i)throw Error("Cannot repaint time axis: no range configured");if(n){t+=e(this,"top",n.offsetTop),t+=e(this,"left",n.offsetLeft);var r=this.props,o=this.options.showMinorLabels,s=this.options.showMajorLabels,a=this.dom.measureCharMinor,h=this.dom.measureCharMajor;a&&(r.minorCharHeight=a.clientHeight,r.minorCharWidth=a.clientWidth),h&&(r.majorCharHeight=h.clientHeight,r.majorCharWidth=h.clientWidth);var c=n.parentNode?n.parentNode.offsetHeight:0;switch(c!=r.parentHeight&&(r.parentHeight=c,t+=1),this.options.orientation){case"bottom":r.minorLabelHeight=o?r.minorCharHeight:0,r.majorLabelHeight=s?r.majorCharHeight:0,r.minorLabelTop=0,r.majorLabelTop=r.minorLabelTop+r.minorLabelHeight,r.minorLineTop=-this.top,r.minorLineHeight=Math.max(this.top+r.majorLabelHeight,0),r.minorLineWidth=1,r.majorLineTop=-this.top,r.majorLineHeight=Math.max(this.top+r.minorLabelHeight+r.majorLabelHeight,0),r.majorLineWidth=1,r.lineTop=0;break;case"top":r.minorLabelHeight=o?r.minorCharHeight:0,r.majorLabelHeight=s?r.majorCharHeight:0,r.majorLabelTop=0,r.minorLabelTop=r.majorLabelTop+r.majorLabelHeight,r.minorLineTop=r.minorLabelTop,r.minorLineHeight=Math.max(c-r.majorLabelHeight-this.top),r.minorLineWidth=1,r.majorLineTop=0,r.majorLineHeight=Math.max(c-this.top),r.majorLineWidth=1,r.lineTop=r.majorLabelHeight+r.minorLabelHeight;break;default:throw Error('Unkown orientation "'+this.options.orientation+'"')}var p=r.minorLabelHeight+r.majorLabelHeight;t+=e(this,"width",n.offsetWidth),t+=e(this,"height",p),this._updateConversion();var u=S.cast(i.start,"Date"),d=S.cast(i.end,"Date"),l=this.toTime(5*(r.minorCharWidth||10))-this.toTime(0);this.step=new TimeStep(u,d,l),t+=e(r.range,"start",u.valueOf()),t+=e(r.range,"end",d.valueOf()),t+=e(r.range,"minimumStep",l.valueOf())}return t>0},u.prototype._updateConversion=function(){var t=this.range;if(!t)throw Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):s.conversion(t.start,t.end,this.width)},d.prototype=new c,d.types={box:f,range:g,point:m},d.prototype.setOptions=function(t){S.extend(this.options,t),this.stack.setOptions(this.options)},d.prototype.setRange=function(t){if(!(t instanceof s||t&&t.start&&t.end))throw new TypeError("Range must be an instance of Range, or an object containing start and end.");this.range=t},d.prototype.repaint=function(){var t=0,e=S.updateProperty,n=S.option.asSize,i=this.options,r=this.frame;if(!r){r=document.createElement("div"),r.className="itemset",i.className&&S.addClassName(r,S.option.asString(i.className));var o=document.createElement("div");o.className="background",r.appendChild(o),this.dom.background=o;var s=document.createElement("div");s.className="foreground",r.appendChild(s),this.dom.foreground=s;var a=document.createElement("div");a.className="itemset-axis",this.dom.axis=a,this.frame=r,t+=1}if(!this.parent)throw Error("Cannot repaint itemset: no parent attached");var h=this.parent.getContainer();if(!h)throw Error("Cannot repaint itemset: parent has no container element");r.parentNode||(h.appendChild(r),t+=1),this.dom.axis.parentNode||(h.appendChild(this.dom.axis),t+=1),t+=e(r.style,"height",n(i.height,this.height+"px")),t+=e(r.style,"top",n(i.top,"0px")),t+=e(r.style,"left",n(i.left,"0px")),t+=e(r.style,"width",n(i.width,"100%")),t+=e(this.dom.axis.style,"top",n(i.top,"0px")),this._updateConversion();var c=this,p=this.queue,u=this.data,l=this.items,f={fields:["id","start","end","content","type"]};return Object.keys(p).forEach(function(e){var n=p[e],r=n.item;switch(n.action){case"add":case"update":var o=u.get(e,f),s=o.type||o.start&&o.end&&"range"||"box",a=d.types[s];if(r&&(a&&r instanceof a?(r.data=o,t+=r.repaint()):(r.visible=!1,t+=r.repaint(),r=null)),!r){if(!a)throw new TypeError('Unknown item type "'+s+'"');r=new a(c,o,i),t+=r.repaint()}l[e]=r,delete p[e];break;case"remove":r&&(r.visible=!1,t+=r.repaint()),delete l[e],delete p[e];break;default:console.log('Error: unknown action "'+n.action+'"')}}),S.forEach(this.items,function(t){t.reposition()}),t>0},d.prototype.getForeground=function(){return this.dom.foreground},d.prototype.getBackground=function(){return this.dom.background},d.prototype.reflow=function(){var t=0,e=this.options,n=S.updateProperty,i=S.option.asNumber,r=this.frame;if(r){this._updateConversion(),S.forEach(this.items,function(e){t+=e.reflow()}),this.stack.update();var o,s=i(e.maxHeight);if(null!=e.height)o=r.offsetHeight,null!=s&&(o=Math.min(o,s)),t+=n(this,"height",o);else{var a=this.height;o=0,"top"==e.orientation?S.forEach(this.items,function(t){o=Math.max(o,t.top+t.height)}):S.forEach(this.items,function(t){o=Math.max(o,a-t.top)}),o+=e.margin.axis,null!=s&&(o=Math.min(o,s)),t+=n(this,"height",o)}t+=n(this,"top",r.offsetTop),t+=n(this,"left",r.offsetLeft),t+=n(this,"width",r.offsetWidth)}else t+=1;return t>0},d.prototype.setData=function(t){var e=this.data;e&&S.forEach(this.listeners,function(t,n){e.unsubscribe(n,t)}),t instanceof r?this.data=t:(this.data=new r({fieldTypes:{start:"Date",end:"Date"}}),this.data.add(t));var n=this.id,i=this;S.forEach(this.listeners,function(t,e){i.data.subscribe(e,t,n)});var o=this.data.get({filter:["id"]}),s=[];S.forEach(o,function(t,e){s[e]=t.id}),this._onAdd(s)},d.prototype.getDataRange=function(){var t=this.data,e=t.min("start");e=e?e.start.valueOf():null;var n=t.max("start"),i=t.max("end");n=n?n.start.valueOf():null,i=i?i.end.valueOf():null;var r=Math.max(n,i);return{min:new Date(e),max:new Date(r)}},d.prototype._onUpdate=function(t){this._toQueue(t,"update")},d.prototype._onAdd=function(t){this._toQueue(t,"add")},d.prototype._onRemove=function(t){this._toQueue(t,"remove")},d.prototype._toQueue=function(t,e){var n=this.items,i=this.queue;t.forEach(function(t){var r=i[t];r?r.action=e:i[t]={item:n[t]||null,action:e}}),this.controller&&this.requestRepaint()},d.prototype._updateConversion=function(){var t=this.range;if(!t)throw Error("No range configured");this.conversion=t.conversion?t.conversion(this.width):s.conversion(t.start,t.end,this.width)},d.prototype.toTime=function(t){var e=this.conversion;return new Date(t/e.factor+e.offset)},d.prototype.toScreen=function(t){var e=this.conversion;return(t.valueOf()-e.offset)*e.factor},l.prototype=new h,l.prototype.select=function(){this.selected=!0},l.prototype.unselect=function(){this.selected=!1},f.prototype=new l(null,null),f.prototype.select=function(){this.selected=!0},f.prototype.unselect=function(){this.selected=!1},f.prototype.repaint=function(){var t=!1,e=this.dom;if(this.visible){if(e||(this._create(),t=!0),e=this.dom){if(!this.options&&!this.parent)throw Error("Cannot repaint item: no parent attached");var n=this.parent.getForeground();if(!n)throw Error("Cannot repaint time axis: parent has no foreground container element");var i=this.parent.getBackground();if(!i)throw Error("Cannot repaint time axis: parent has no background container element");if(e.box.parentNode||(n.appendChild(e.box),t=!0),e.line.parentNode||(i.appendChild(e.line),t=!0),e.dot.parentNode||(this.parent.dom.axis.appendChild(e.dot),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var r=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=r&&(this.className=r,e.box.className="item box"+r,e.line.className="item line"+r,e.dot.className="item dot"+r,t=!0)}}else e&&(e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0),e.line.parentNode&&(e.line.parentNode.removeChild(e.line),t=!0),e.dot.parentNode&&(e.dot.parentNode.removeChild(e.dot),t=!0));return t},f.prototype.reflow=function(){if(void 0==this.data.start)throw Error('Property "start" missing in item '+this.data.id);var t,e,n=S.updateProperty,i=this.dom,r=this.props,o=this.options,s=this.parent.toScreen(this.data.start),a=o&&o.align,h=o.orientation,c=0;if(i)if(c+=n(r.dot,"height",i.dot.offsetHeight),c+=n(r.dot,"width",i.dot.offsetWidth),c+=n(r.line,"width",i.line.offsetWidth),c+=n(r.line,"width",i.line.offsetWidth),c+=n(this,"width",i.box.offsetWidth),c+=n(this,"height",i.box.offsetHeight),e="right"==a?s-this.width:"left"==a?s:s-this.width/2,c+=n(this,"left",e),c+=n(r.line,"left",s-r.line.width/2),c+=n(r.dot,"left",s-r.dot.width/2),"top"==h)t=o.margin.axis,c+=n(this,"top",t),c+=n(r.line,"top",0),c+=n(r.line,"height",t),c+=n(r.dot,"top",-r.dot.height/2);else{var p=this.parent.height;t=p-this.height-o.margin.axis,c+=n(this,"top",t),c+=n(r.line,"top",t+this.height),c+=n(r.line,"height",Math.max(o.margin.axis,0)),c+=n(r.dot,"top",p-r.dot.height/2)}else c+=1;return c>0},f.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("DIV"),t.content=document.createElement("DIV"),t.content.className="content",t.box.appendChild(t.content),t.line=document.createElement("DIV"),t.line.className="line",t.dot=document.createElement("DIV"),t.dot.className="dot")},f.prototype.reposition=function(){var t=this.dom,e=this.props,n=this.options.orientation;if(t){var i=t.box,r=t.line,o=t.dot;i.style.left=this.left+"px",i.style.top=this.top+"px",r.style.left=e.line.left+"px","top"==n?(r.style.top="0px",r.style.height=this.top+"px"):(r.style.top=e.line.top+"px",r.style.top=this.top+this.height+"px",r.style.height=Math.max(e.dot.top-this.top-this.height,0)+"px"),o.style.left=e.dot.left+"px",o.style.top=e.dot.top+"px"}},m.prototype=new l(null,null),m.prototype.select=function(){this.selected=!0},m.prototype.unselect=function(){this.selected=!1},m.prototype.repaint=function(){var t=!1,e=this.dom;if(this.visible){if(e||(this._create(),t=!0),e=this.dom){if(!this.options&&!this.options.parent)throw Error("Cannot repaint item: no parent attached");var n=this.parent.getForeground();if(!n)throw Error("Cannot repaint time axis: parent has no foreground container element");if(e.point.parentNode||(n.appendChild(e.point),n.appendChild(e.point),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var i=(this.data.className?" "+this.data.className:"")+(this.selected?" selected":"");this.className!=i&&(this.className=i,e.point.className="item point"+i,t=!0)}}else e&&e.point.parentNode&&(e.point.parentNode.removeChild(e.point),t=!0);return t},m.prototype.reflow=function(){if(void 0==this.data.start)throw Error('Property "start" missing in item '+this.data.id);var t,e=S.updateProperty,n=this.dom,i=this.props,r=this.options,o=r.orientation,s=this.parent.toScreen(this.data.start),a=0;if(n){if(a+=e(this,"width",n.point.offsetWidth),a+=e(this,"height",n.point.offsetHeight),a+=e(i.dot,"width",n.dot.offsetWidth),a+=e(i.dot,"height",n.dot.offsetHeight),a+=e(i.content,"height",n.content.offsetHeight),"top"==o)t=r.margin.axis;else{var h=this.parent.height;t=Math.max(h-this.height-r.margin.axis,0)}a+=e(this,"top",t),a+=e(this,"left",s-i.dot.width/2),a+=e(i.content,"marginLeft",1.5*i.dot.width),a+=e(i.dot,"top",(this.height-i.dot.height)/2)}else a+=1;return a>0},m.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.point=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.point.appendChild(t.content),t.dot=document.createElement("div"),t.dot.className="dot",t.point.appendChild(t.dot))},m.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.point.style.top=this.top+"px",t.point.style.left=this.left+"px",t.content.style.marginLeft=e.content.marginLeft+"px",t.dot.style.top=e.dot.top+"px")},g.prototype=new l(null,null),g.prototype.select=function(){this.selected=!0},g.prototype.unselect=function(){this.selected=!1},g.prototype.repaint=function(){var t=!1,e=this.dom;if(this.visible){if(e||(this._create(),t=!0),e=this.dom){if(!this.options&&!this.options.parent)throw Error("Cannot repaint item: no parent attached");var n=this.parent.getForeground();if(!n)throw Error("Cannot repaint time axis: parent has no foreground container element");if(e.box.parentNode||(n.appendChild(e.box),t=!0),this.data.content!=this.content){if(this.content=this.data.content,this.content instanceof Element)e.content.innerHTML="",e.content.appendChild(this.content);else{if(void 0==this.data.content)throw Error('Property "content" missing in item '+this.data.id);e.content.innerHTML=this.content}t=!0}var i=this.data.className?""+this.data.className:"";this.className!=i&&(this.className=i,e.box.className="item range"+i,t=!0)}}else e&&e.box.parentNode&&(e.box.parentNode.removeChild(e.box),t=!0);return t},g.prototype.reflow=function(){if(void 0==this.data.start)throw Error('Property "start" missing in item '+this.data.id);if(void 0==this.data.end)throw Error('Property "end" missing in item '+this.data.id);var t=this.dom,e=this.props,n=this.options,i=this.parent,r=i.toScreen(this.data.start),o=i.toScreen(this.data.end),s=0;if(t){var a,h,c=S.updateProperty,p=t.box,u=i.width,d=n.orientation;s+=c(e.content,"width",t.content.offsetWidth),s+=c(this,"height",p.offsetHeight),-u>r&&(r=-u),o>2*u&&(o=2*u),a=0>r?Math.min(-r,o-r-e.content.width-2*n.padding):0,s+=c(e.content,"left",a),"top"==d?(h=n.margin.axis,s+=c(this,"top",h)):(h=i.height-this.height-n.margin.axis,s+=c(this,"top",h)),s+=c(this,"left",r),s+=c(this,"width",Math.max(o-r,1))}else s+=1;return s>0},g.prototype._create=function(){var t=this.dom;t||(this.dom=t={},t.box=document.createElement("div"),t.content=document.createElement("div"),t.content.className="content",t.box.appendChild(t.content))},g.prototype.reposition=function(){var t=this.dom,e=this.props;t&&(t.box.style.top=this.top+"px",t.box.style.left=this.left+"px",t.box.style.width=this.width+"px",t.content.style.left=e.content.left+"px")},v.prototype.setOptions=function(t){S.extend(this.options,t),this.timeaxis.setOptions(this.options),this.range.setOptions(this.options);var e,n=this;e="top"==this.options.orientation?function(){return n.timeaxis.height}:function(){return n.main.height-n.timeaxis.height-n.itemset.height},this.itemset.setOptions({orientation:this.options.orientation,top:e}),this.controller.repaint()},v.prototype.setData=function(t){var e=this.itemset.data;if(e)this.itemset.setData(t);else{this.itemset.setData(t);var n=this.itemset.getDataRange(),i=n.min,r=n.max;if(null!=i&&null!=r){var o=r.valueOf()-i.valueOf();i=new Date(i.valueOf()-.05*o),r=new Date(r.valueOf()+.05*o)}(null!=i||null!=r)&&this.range.setRange(i,r)}};var b={util:S,events:E,Controller:a,DataSet:r,Range:s,Stack:o,TimeStep:TimeStep,components:{items:{Item:l,ItemBox:f,ItemPoint:m,ItemRange:g},Component:h,Panel:c,RootPanel:p,ItemSet:d,TimeAxis:u},Timeline:v};i!==void 0&&(i=b),n!==void 0&&n.exports!==void 0&&(n.exports=b),"function"==typeof t&&t(function(){return b}),"undefined"!=typeof window&&(window.vis=b),S.loadCss("/* vis.js stylesheet */\n\n.graph {\n position: relative;\n border: 1px solid #bfbfbf;\n}\n\n.graph .panel {\n position: absolute;\n}\n\n.graph .itemset {\n position: absolute;\n padding: 0;\n margin: 0;\n overflow: hidden;\n}\n\n.graph .background {\n}\n\n.graph .foreground {\n}\n\n.graph .itemset-axis {\n position: absolute;\n}\n\n.graph .item {\n position: absolute;\n color: #1A1A1A;\n border-color: #97B0F8;\n background-color: #D5DDF6;\n display: inline-block;\n}\n\n.graph .item.selected {\n border-color: #FFC200;\n background-color: #FFF785;\n z-index: 999;\n}\n\n.graph .item.cluster {\n /* TODO: use another color or pattern? */\n background: #97B0F8 url('img/cluster_bg.png');\n color: white;\n}\n.graph .item.cluster.point {\n border-color: #D5DDF6;\n}\n\n.graph .item.box {\n text-align: center;\n border-style: solid;\n border-width: 1px;\n border-radius: 5px;\n -moz-border-radius: 5px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.point {\n background: none;\n}\n\n.graph .dot {\n border: 5px solid #97B0F8;\n position: absolute;\n border-radius: 5px;\n -moz-border-radius: 5px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.range {\n overflow: hidden;\n border-style: solid;\n border-width: 1px;\n border-radius: 2px;\n -moz-border-radius: 2px; /* For Firefox 3.6 and older */\n}\n\n.graph .item.range .drag-left {\n cursor: w-resize;\n z-index: 1000;\n}\n\n.graph .item.range .drag-right {\n cursor: e-resize;\n z-index: 1000;\n}\n\n.graph .item.range .content {\n position: relative;\n display: inline-block;\n}\n\n.graph .item.line {\n position: absolute;\n width: 0;\n border-left-width: 1px;\n border-left-style: solid;\n}\n\n.graph .item .content {\n margin: 5px;\n white-space: nowrap;\n overflow: hidden;\n}\n\n/* TODO: better css name, 'graph' is way to generic */\n\n.graph {\n overflow: hidden;\n}\n\n.graph .axis {\n position: relative;\n}\n\n.graph .axis .text {\n position: absolute;\n color: #4d4d4d;\n padding: 3px;\n white-space: nowrap;\n}\n\n.graph .axis .text.measure {\n position: absolute;\n padding-left: 0;\n padding-right: 0;\n margin-left: 0;\n margin-right: 0;\n visibility: hidden;\n}\n\n.graph .axis .grid.vertical {\n position: absolute;\n width: 0;\n border-right: 1px solid;\n}\n\n.graph .axis .grid.horizontal {\n position: absolute;\n left: 0;\n width: 100%;\n height: 0;\n border-bottom: 1px solid;\n}\n\n.graph .axis .grid.minor {\n border-color: #e5e5e5;\n}\n\n.graph .axis .grid.major {\n border-color: #bfbfbf;\n}\n\n")},{moment:2}],2:[function(e,n){(function(){(function(i){function r(t,e){return function(n){return u(t.call(this,n),e)}}function o(t){return function(e){return this.lang().ordinal(t.call(this,e))}}function s(){}function a(t){c(this,t)}function h(t){var e=this._data={},n=t.years||t.year||t.y||0,i=t.months||t.month||t.M||0,r=t.weeks||t.week||t.w||0,o=t.days||t.day||t.d||0,s=t.hours||t.hour||t.h||0,a=t.minutes||t.minute||t.m||0,h=t.seconds||t.second||t.s||0,c=t.milliseconds||t.millisecond||t.ms||0;this._milliseconds=c+1e3*h+6e4*a+36e5*s,this._days=o+7*r,this._months=i+12*n,e.milliseconds=c%1e3,h+=p(c/1e3),e.seconds=h%60,a+=p(h/60),e.minutes=a%60,s+=p(a/60),e.hours=s%24,o+=p(s/24),o+=7*r,e.days=o%30,i+=p(o/30),e.months=i%12,n+=p(i/12),e.years=n}function c(t,e){for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function p(t){return 0>t?Math.ceil(t):Math.floor(t)}function u(t,e){for(var n=t+"";e>n.length;)n="0"+n;return n}function d(t,e,n){var i,r=e._milliseconds,o=e._days,s=e._months;r&&t._d.setTime(+t+r*n),o&&t.date(t.date()+o*n),s&&(i=t.date(),t.date(1).month(t.month()+s*n).date(Math.min(i,t.daysInMonth())))}function l(t){return"[object Array]"===Object.prototype.toString.call(t)}function f(t,e){var n,i=Math.min(t.length,e.length),r=Math.abs(t.length-e.length),o=0;for(n=0;i>n;n++)~~t[n]!==~~e[n]&&o++;return o+r}function m(t,e){return e.abbr=t,R[t]||(R[t]=new s),R[t].set(e),R[t]}function g(t){return t?(!R[t]&&U&&e("./lang/"+t),R[t]):k.fn._lang}function v(t){return t.match(/\[.*\]/)?t.replace(/^\[|\]$/g,""):t.replace(/\\/g,"")}function y(t){var e,n,i=t.match(z);for(e=0,n=i.length;n>e;e++)i[e]=ae[i[e]]?ae[i[e]]:v(i[e]);return function(r){var o="";for(e=0;n>e;e++)o+="function"==typeof i[e].call?i[e].call(r,t):i[e];return o}}function S(t,e){function n(e){return t.lang().longDateFormat(e)||e}for(var i=5;i--&&P.test(e);)e=e.replace(P,n);return re[e]||(re[e]=y(e)),re[e](t)}function w(t){switch(t){case"DDDD":return V;case"YYYY":return B;case"YYYYY":return Z;case"S":case"SS":case"SSS":case"DDD":return q;case"MMM":case"MMMM":case"dd":case"ddd":case"dddd":case"a":case"A":return X;case"X":return $;case"Z":case"ZZ":return K;case"T":return J;case"MM":case"DD":case"YY":case"HH":case"hh":case"mm":case"ss":case"M":case"D":case"d":case"H":case"h":case"m":case"s":return W;default:return RegExp(t.replace("\\",""))}}function T(t,e,n){var i,r=n._a;switch(t){case"M":case"MM":r[1]=null==e?0:~~e-1;break;case"MMM":case"MMMM":i=g(n._l).monthsParse(e),null!=i?r[1]=i:n._isValid=!1;break;case"D":case"DD":case"DDD":case"DDDD":null!=e&&(r[2]=~~e);break;case"YY":r[0]=~~e+(~~e>68?1900:2e3);break;case"YYYY":case"YYYYY":r[0]=~~e;break;case"a":case"A":n._isPm="pm"===(e+"").toLowerCase();break;case"H":case"HH":case"h":case"hh":r[3]=~~e;break;case"m":case"mm":r[4]=~~e;break;case"s":case"ss":r[5]=~~e;break;case"S":case"SS":case"SSS":r[6]=~~(1e3*("0."+e));break;case"X":n._d=new Date(1e3*parseFloat(e));break;case"Z":case"ZZ":n._useUTC=!0,i=(e+"").match(ee),i&&i[1]&&(n._tzh=~~i[1]),i&&i[2]&&(n._tzm=~~i[2]),i&&"+"===i[0]&&(n._tzh=-n._tzh,n._tzm=-n._tzm)}null==e&&(n._isValid=!1)}function E(t){var e,n,i=[];if(!t._d){for(e=0;7>e;e++)t._a[e]=i[e]=null==t._a[e]?2===e?1:0:t._a[e];i[3]+=t._tzh||0,i[4]+=t._tzm||0,n=new Date(0),t._useUTC?(n.setUTCFullYear(i[0],i[1],i[2]),n.setUTCHours(i[3],i[4],i[5],i[6])):(n.setFullYear(i[0],i[1],i[2]),n.setHours(i[3],i[4],i[5],i[6])),t._d=n}}function b(t){var e,n,i=t._f.match(z),r=t._i;for(t._a=[],e=0;i.length>e;e++)n=(w(i[e]).exec(r)||[])[0],n&&(r=r.slice(r.indexOf(n)+n.length)),ae[i[e]]&&T(i[e],n,t);t._isPm&&12>t._a[3]&&(t._a[3]+=12),t._isPm===!1&&12===t._a[3]&&(t._a[3]=0),E(t)}function M(t){for(var e,n,i,r,o=99;t._f.length;){if(e=c({},t),e._f=t._f.pop(),b(e),n=new a(e),n.isValid()){i=n;break}r=f(e._a,n.toArray()),o>r&&(o=r,i=n)}c(t,i)}function _(t){var e,n=t._i;if(Q.exec(n)){for(t._f="YYYY-MM-DDT",e=0;4>e;e++)if(te[e][1].exec(n)){t._f+=te[e][0];break}K.exec(n)&&(t._f+=" Z"),b(t)}else t._d=new Date(n)}function D(t){var e=t._i,n=F.exec(e);e===i?t._d=new Date:n?t._d=new Date(+n[1]):"string"==typeof e?_(t):l(e)?(t._a=e.slice(0),E(t)):t._d=e instanceof Date?new Date(+e):new Date(e)}function L(t,e,n,i,r){return r.relativeTime(e||1,!!n,t,i)}function C(t,e,n){var i=j(Math.abs(t)/1e3),r=j(i/60),o=j(r/60),s=j(o/24),a=j(s/365),h=45>i&&["s",i]||1===r&&["m"]||45>r&&["mm",r]||1===o&&["h"]||22>o&&["hh",o]||1===s&&["d"]||25>=s&&["dd",s]||45>=s&&["M"]||345>s&&["MM",j(s/30)]||1===a&&["y"]||["yy",a];return h[2]=e,h[3]=t>0,h[4]=n,L.apply({},h)}function x(t,e,n){var i=n-e,r=n-t.day();return r>i&&(r-=7),i-7>r&&(r+=7),Math.ceil(k(t).add("d",r).dayOfYear()/7)}function A(t){var e=t._i,n=t._f;return null===e||""===e?null:("string"==typeof e&&(t._i=e=g().preparse(e)),k.isMoment(e)?(t=c({},e),t._d=new Date(+e._d)):n?l(n)?M(t):b(t):D(t),new a(t))}function N(t,e){k.fn[t]=k.fn[t+"s"]=function(t){var n=this._isUTC?"UTC":"";return null!=t?(this._d["set"+n+e](t),this):this._d["get"+n+e]()}}function O(t){k.duration.fn[t]=function(){return this._data[t]}}function Y(t,e){k.duration.fn["as"+t]=function(){return+this/e}}for(var k,H,I="2.0.0",j=Math.round,R={},U=n!==i&&n.exports,F=/^\/?Date\((\-?\d+)/i,z=/(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|YYYYY|YYYY|YY|a|A|hh?|HH?|mm?|ss?|SS?S?|X|zz?|ZZ?|.)/g,P=/(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,W=/\d\d?/,q=/\d{1,3}/,V=/\d{3}/,B=/\d{1,4}/,Z=/[+\-]?\d{1,6}/,X=/[0-9]*[a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF]+\s*?[\u0600-\u06FF]+/i,K=/Z|[\+\-]\d\d:?\d\d/i,J=/T/i,$=/[\+\-]?\d+(\.\d{1,3})?/,Q=/^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,G="YYYY-MM-DDTHH:mm:ssZ",te=[["HH:mm:ss.S",/(T| )\d\d:\d\d:\d\d\.\d{1,3}/],["HH:mm:ss",/(T| )\d\d:\d\d:\d\d/],["HH:mm",/(T| )\d\d:\d\d/],["HH",/(T| )\d\d/]],ee=/([\+\-]|\d\d)/gi,ne="Month|Date|Hours|Minutes|Seconds|Milliseconds".split("|"),ie={Milliseconds:1,Seconds:1e3,Minutes:6e4,Hours:36e5,Days:864e5,Months:2592e6,Years:31536e6},re={},oe="DDD w W M D d".split(" "),se="M D H h m s w W".split(" "),ae={M:function(){return this.month()+1},MMM:function(t){return this.lang().monthsShort(this,t)},MMMM:function(t){return this.lang().months(this,t)},D:function(){return this.date()},DDD:function(){return this.dayOfYear()},d:function(){return this.day()},dd:function(t){return this.lang().weekdaysMin(this,t)},ddd:function(t){return this.lang().weekdaysShort(this,t) +},dddd:function(t){return this.lang().weekdays(this,t)},w:function(){return this.week()},W:function(){return this.isoWeek()},YY:function(){return u(this.year()%100,2)},YYYY:function(){return u(this.year(),4)},YYYYY:function(){return u(this.year(),5)},a:function(){return this.lang().meridiem(this.hours(),this.minutes(),!0)},A:function(){return this.lang().meridiem(this.hours(),this.minutes(),!1)},H:function(){return this.hours()},h:function(){return this.hours()%12||12},m:function(){return this.minutes()},s:function(){return this.seconds()},S:function(){return~~(this.milliseconds()/100)},SS:function(){return u(~~(this.milliseconds()/10),2)},SSS:function(){return u(this.milliseconds(),3)},Z:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(t/60),2)+":"+u(~~t%60,2)},ZZ:function(){var t=-this.zone(),e="+";return 0>t&&(t=-t,e="-"),e+u(~~(10*t/6),4)},X:function(){return this.unix()}};oe.length;)H=oe.pop(),ae[H+"o"]=o(ae[H]);for(;se.length;)H=se.pop(),ae[H+H]=r(ae[H],2);for(ae.DDDD=r(ae.DDD,3),s.prototype={set:function(t){var e,n;for(n in t)e=t[n],"function"==typeof e?this[n]=e:this["_"+n]=e},_months:"January_February_March_April_May_June_July_August_September_October_November_December".split("_"),months:function(t){return this._months[t.month()]},_monthsShort:"Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),monthsShort:function(t){return this._monthsShort[t.month()]},monthsParse:function(t){var e,n,i;for(this._monthsParse||(this._monthsParse=[]),e=0;12>e;e++)if(this._monthsParse[e]||(n=k([2e3,e]),i="^"+this.months(n,"")+"|^"+this.monthsShort(n,""),this._monthsParse[e]=RegExp(i.replace(".",""),"i")),this._monthsParse[e].test(t))return e},_weekdays:"Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),weekdays:function(t){return this._weekdays[t.day()]},_weekdaysShort:"Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),weekdaysShort:function(t){return this._weekdaysShort[t.day()]},_weekdaysMin:"Su_Mo_Tu_We_Th_Fr_Sa".split("_"),weekdaysMin:function(t){return this._weekdaysMin[t.day()]},_longDateFormat:{LT:"h:mm A",L:"MM/DD/YYYY",LL:"MMMM D YYYY",LLL:"MMMM D YYYY LT",LLLL:"dddd, MMMM D YYYY LT"},longDateFormat:function(t){var e=this._longDateFormat[t];return!e&&this._longDateFormat[t.toUpperCase()]&&(e=this._longDateFormat[t.toUpperCase()].replace(/MMMM|MM|DD|dddd/g,function(t){return t.slice(1)}),this._longDateFormat[t]=e),e},meridiem:function(t,e,n){return t>11?n?"pm":"PM":n?"am":"AM"},_calendar:{sameDay:"[Today at] LT",nextDay:"[Tomorrow at] LT",nextWeek:"dddd [at] LT",lastDay:"[Yesterday at] LT",lastWeek:"[last] dddd [at] LT",sameElse:"L"},calendar:function(t,e){var n=this._calendar[t];return"function"==typeof n?n.apply(e):n},_relativeTime:{future:"in %s",past:"%s ago",s:"a few seconds",m:"a minute",mm:"%d minutes",h:"an hour",hh:"%d hours",d:"a day",dd:"%d days",M:"a month",MM:"%d months",y:"a year",yy:"%d years"},relativeTime:function(t,e,n,i){var r=this._relativeTime[n];return"function"==typeof r?r(t,e,n,i):r.replace(/%d/i,t)},pastFuture:function(t,e){var n=this._relativeTime[t>0?"future":"past"];return"function"==typeof n?n(e):n.replace(/%s/i,e)},ordinal:function(t){return this._ordinal.replace("%d",t)},_ordinal:"%d",preparse:function(t){return t},postformat:function(t){return t},week:function(t){return x(t,this._week.dow,this._week.doy)},_week:{dow:0,doy:6}},k=function(t,e,n){return A({_i:t,_f:e,_l:n,_isUTC:!1})},k.utc=function(t,e,n){return A({_useUTC:!0,_isUTC:!0,_l:n,_i:t,_f:e})},k.unix=function(t){return k(1e3*t)},k.duration=function(t,e){var n,i=k.isDuration(t),r="number"==typeof t,o=i?t._data:r?{}:t;return r&&(e?o[e]=t:o.milliseconds=t),n=new h(o),i&&t.hasOwnProperty("_lang")&&(n._lang=t._lang),n},k.version=I,k.defaultFormat=G,k.lang=function(t,e){return t?(e?m(t,e):R[t]||g(t),k.duration.fn._lang=k.fn._lang=g(t),i):k.fn._lang._abbr},k.langData=function(t){return t&&t._lang&&t._lang._abbr&&(t=t._lang._abbr),g(t)},k.isMoment=function(t){return t instanceof a},k.isDuration=function(t){return t instanceof h},k.fn=a.prototype={clone:function(){return k(this)},valueOf:function(){return+this._d},unix:function(){return Math.floor(+this._d/1e3)},toString:function(){return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ")},toDate:function(){return this._d},toJSON:function(){return k.utc(this).format("YYYY-MM-DD[T]HH:mm:ss.SSS[Z]")},toArray:function(){var t=this;return[t.year(),t.month(),t.date(),t.hours(),t.minutes(),t.seconds(),t.milliseconds()]},isValid:function(){return null==this._isValid&&(this._isValid=this._a?!f(this._a,(this._isUTC?k.utc(this._a):k(this._a)).toArray()):!isNaN(this._d.getTime())),!!this._isValid},utc:function(){return this._isUTC=!0,this},local:function(){return this._isUTC=!1,this},format:function(t){var e=S(this,t||k.defaultFormat);return this.lang().postformat(e)},add:function(t,e){var n;return n="string"==typeof t?k.duration(+e,t):k.duration(t,e),d(this,n,1),this},subtract:function(t,e){var n;return n="string"==typeof t?k.duration(+e,t):k.duration(t,e),d(this,n,-1),this},diff:function(t,e,n){var i,r,o=this._isUTC?k(t).utc():k(t).local(),s=6e4*(this.zone()-o.zone());return e&&(e=e.replace(/s$/,"")),"year"===e||"month"===e?(i=432e5*(this.daysInMonth()+o.daysInMonth()),r=12*(this.year()-o.year())+(this.month()-o.month()),r+=(this-k(this).startOf("month")-(o-k(o).startOf("month")))/i,"year"===e&&(r/=12)):(i=this-o-s,r="second"===e?i/1e3:"minute"===e?i/6e4:"hour"===e?i/36e5:"day"===e?i/864e5:"week"===e?i/6048e5:i),n?r:p(r)},from:function(t,e){return k.duration(this.diff(t)).lang(this.lang()._abbr).humanize(!e)},fromNow:function(t){return this.from(k(),t)},calendar:function(){var t=this.diff(k().startOf("day"),"days",!0),e=-6>t?"sameElse":-1>t?"lastWeek":0>t?"lastDay":1>t?"sameDay":2>t?"nextDay":7>t?"nextWeek":"sameElse";return this.format(this.lang().calendar(e,this))},isLeapYear:function(){var t=this.year();return 0===t%4&&0!==t%100||0===t%400},isDST:function(){return this.zone()+k(t).startOf(e)},isBefore:function(t,e){return e=e!==i?e:"millisecond",+this.clone().startOf(e)<+k(t).startOf(e)},isSame:function(t,e){return e=e!==i?e:"millisecond",+this.clone().startOf(e)===+k(t).startOf(e)},zone:function(){return this._isUTC?0:this._d.getTimezoneOffset()},daysInMonth:function(){return k.utc([this.year(),this.month()+1,0]).date()},dayOfYear:function(t){var e=j((k(this).startOf("day")-k(this).startOf("year"))/864e5)+1;return null==t?e:this.add("d",t-e)},isoWeek:function(t){var e=x(this,1,4);return null==t?e:this.add("d",7*(t-e))},week:function(t){var e=this.lang().week(this);return null==t?e:this.add("d",7*(t-e))},lang:function(t){return t===i?this._lang:(this._lang=g(t),this)}},H=0;ne.length>H;H++)N(ne[H].toLowerCase().replace(/s$/,""),ne[H]);N("year","FullYear"),k.fn.days=k.fn.day,k.fn.weeks=k.fn.week,k.fn.isoWeeks=k.fn.isoWeek,k.duration.fn=h.prototype={weeks:function(){return p(this.days()/7)},valueOf:function(){return this._milliseconds+864e5*this._days+2592e6*this._months},humanize:function(t){var e=+this,n=C(e,!t,this.lang());return t&&(n=this.lang().pastFuture(e,n)),this.lang().postformat(n)},lang:k.fn.lang};for(H in ie)ie.hasOwnProperty(H)&&(Y(H,ie[H]),O(H.toLowerCase()));Y("Weeks",6048e5),k.lang("en",{ordinal:function(t){var e=t%10,n=1===~~(t%100/10)?"th":1===e?"st":2===e?"nd":3===e?"rd":"th";return t+n}}),U&&(n.exports=k),"undefined"==typeof ender&&(this.moment=k),"function"==typeof t&&t.amd&&t("moment",[],function(){return k})}).call(this)})()},{}]},{},[1])(1)}); \ No newline at end of file