vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

6667 lines
205 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. /**
  2. * vis.js
  3. * https://github.com/almende/vis
  4. *
  5. * A dynamic, browser-based visualization library.
  6. *
  7. * @version 0.0.7
  8. * @date 2013-04-25
  9. *
  10. * @license
  11. * Copyright (C) 2011-2013 Almende B.V, http://almende.com
  12. *
  13. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  14. * use this file except in compliance with the License. You may obtain a copy
  15. * of the License at
  16. *
  17. * http://www.apache.org/licenses/LICENSE-2.0
  18. *
  19. * Unless required by applicable law or agreed to in writing, software
  20. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  21. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  22. * License for the specific language governing permissions and limitations under
  23. * the License.
  24. */
  25. (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;
  26. 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<n.length;s++)i(n[s]);return i})({1:[function(require,module,exports){
  27. /**
  28. * vis.js library exports
  29. */
  30. var vis = {
  31. Controller: require('./controller'),
  32. DataSet: require('./dataset'),
  33. events: require('./events'),
  34. Range: require('./range'),
  35. Stack: require('./stack'),
  36. TimeStep: require('./timestep'),
  37. util: require('./util'),
  38. component: {
  39. item: {
  40. Item: '../../Item',
  41. ItemBox: '../../ItemBox',
  42. ItemPoint: '../../ItemPoint',
  43. ItemRange: '../../ItemRange'
  44. },
  45. Component: require('./component/component'),
  46. Panel: require('./component/panel'),
  47. RootPanel: require('./component/rootpanel'),
  48. ItemSet: require('./component/itemset'),
  49. TimeAxis: require('./component/timeaxis')
  50. },
  51. Timeline: require('./visualization/timeline')
  52. };
  53. module.exports = exports = vis;
  54. },{"./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}],4:[function(require,module,exports){
  55. /**
  56. * Event listener (singleton)
  57. */
  58. var events = {
  59. 'listeners': [],
  60. /**
  61. * Find a single listener by its object
  62. * @param {Object} object
  63. * @return {Number} index -1 when not found
  64. */
  65. 'indexOf': function (object) {
  66. var listeners = this.listeners;
  67. for (var i = 0, iMax = this.listeners.length; i < iMax; i++) {
  68. var listener = listeners[i];
  69. if (listener && listener.object == object) {
  70. return i;
  71. }
  72. }
  73. return -1;
  74. },
  75. /**
  76. * Add an event listener
  77. * @param {Object} object
  78. * @param {String} event The name of an event, for example 'select'
  79. * @param {function} callback The callback method, called when the
  80. * event takes place
  81. */
  82. 'addListener': function (object, event, callback) {
  83. var index = this.indexOf(object);
  84. var listener = this.listeners[index];
  85. if (!listener) {
  86. listener = {
  87. 'object': object,
  88. 'events': {}
  89. };
  90. this.listeners.push(listener);
  91. }
  92. var callbacks = listener.events[event];
  93. if (!callbacks) {
  94. callbacks = [];
  95. listener.events[event] = callbacks;
  96. }
  97. // add the callback if it does not yet exist
  98. if (callbacks.indexOf(callback) == -1) {
  99. callbacks.push(callback);
  100. }
  101. },
  102. /**
  103. * Remove an event listener
  104. * @param {Object} object
  105. * @param {String} event The name of an event, for example 'select'
  106. * @param {function} callback The registered callback method
  107. */
  108. 'removeListener': function (object, event, callback) {
  109. var index = this.indexOf(object);
  110. var listener = this.listeners[index];
  111. if (listener) {
  112. var callbacks = listener.events[event];
  113. if (callbacks) {
  114. index = callbacks.indexOf(callback);
  115. if (index != -1) {
  116. callbacks.splice(index, 1);
  117. }
  118. // remove the array when empty
  119. if (callbacks.length == 0) {
  120. delete listener.events[event];
  121. }
  122. }
  123. // count the number of registered events. remove listener when empty
  124. var count = 0;
  125. var events = listener.events;
  126. for (var e in events) {
  127. if (events.hasOwnProperty(e)) {
  128. count++;
  129. }
  130. }
  131. if (count == 0) {
  132. delete this.listeners[index];
  133. }
  134. }
  135. },
  136. /**
  137. * Remove all registered event listeners
  138. */
  139. 'removeAllListeners': function () {
  140. this.listeners = [];
  141. },
  142. /**
  143. * Trigger an event. All registered event handlers will be called
  144. * @param {Object} object
  145. * @param {String} event
  146. * @param {Object} properties (optional)
  147. */
  148. 'trigger': function (object, event, properties) {
  149. var index = this.indexOf(object);
  150. var listener = this.listeners[index];
  151. if (listener) {
  152. var callbacks = listener.events[event];
  153. if (callbacks) {
  154. for (var i = 0, iMax = callbacks.length; i < iMax; i++) {
  155. callbacks[i](properties);
  156. }
  157. }
  158. }
  159. }
  160. };
  161. // exports
  162. module.exports = exports = events;
  163. },{}],8:[function(require,module,exports){
  164. /**
  165. * utility functions
  166. */
  167. var util = {};
  168. /**
  169. * Test whether given object is a number
  170. * @param {*} object
  171. * @return {Boolean} isNumber
  172. */
  173. util.isNumber = function isNumber(object) {
  174. return (object instanceof Number || typeof object == 'number');
  175. };
  176. /**
  177. * Test whether given object is a string
  178. * @param {*} object
  179. * @return {Boolean} isString
  180. */
  181. util.isString = function isString(object) {
  182. return (object instanceof String || typeof object == 'string');
  183. };
  184. /**
  185. * Test whether given object is a Date, or a String containing a Date
  186. * @param {Date | String} object
  187. * @return {Boolean} isDate
  188. */
  189. util.isDate = function isDate(object) {
  190. if (object instanceof Date) {
  191. return true;
  192. }
  193. else if (util.isString(object)) {
  194. // test whether this string contains a date
  195. var match = ASPDateRegex.exec(object);
  196. if (match) {
  197. return true;
  198. }
  199. else if (!isNaN(Date.parse(object))) {
  200. return true;
  201. }
  202. }
  203. return false;
  204. };
  205. /**
  206. * Test whether given object is an instance of google.visualization.DataTable
  207. * @param {*} object
  208. * @return {Boolean} isDataTable
  209. */
  210. util.isDataTable = function isDataTable(object) {
  211. return (typeof (google) !== 'undefined') &&
  212. (google.visualization) &&
  213. (google.visualization.DataTable) &&
  214. (object instanceof google.visualization.DataTable);
  215. };
  216. /**
  217. * Create a semi UUID
  218. * source: http://stackoverflow.com/a/105074/1262753
  219. * @return {String} uuid
  220. */
  221. util.randomUUID = function randomUUID () {
  222. var S4 = function () {
  223. return Math.floor(
  224. Math.random() * 0x10000 /* 65536 */
  225. ).toString(16);
  226. };
  227. return (
  228. S4() + S4() + '-' +
  229. S4() + '-' +
  230. S4() + '-' +
  231. S4() + '-' +
  232. S4() + S4() + S4()
  233. );
  234. };
  235. /**
  236. * Extend object a with the properties of object b
  237. * @param {Object} a
  238. * @param {Object} b
  239. * @return {Object} a
  240. */
  241. util.extend = function (a, b) {
  242. for (var prop in b) {
  243. if (b.hasOwnProperty(prop)) {
  244. a[prop] = b[prop];
  245. }
  246. }
  247. return a;
  248. };
  249. /**
  250. * Cast an object to another type
  251. * @param {Boolean | Number | String | Date | Null | undefined} object
  252. * @param {String | function | undefined} type Name of the type or a cast
  253. * function. Available types:
  254. * 'Boolean', 'Number', 'String',
  255. * 'Date', ISODate', 'ASPDate'.
  256. * @return {*} object
  257. * @throws Error
  258. */
  259. util.cast = function cast(object, type) {
  260. if (object === undefined) {
  261. return undefined;
  262. }
  263. if (object === null) {
  264. return null;
  265. }
  266. if (!type) {
  267. return object;
  268. }
  269. if (typeof type == 'function') {
  270. return type(object);
  271. }
  272. //noinspection FallthroughInSwitchStatementJS
  273. switch (type) {
  274. case 'boolean':
  275. case 'Boolean':
  276. return Boolean(object);
  277. case 'number':
  278. case 'Number':
  279. return Number(object);
  280. case 'string':
  281. case 'String':
  282. return String(object);
  283. case 'Date':
  284. if (util.isNumber(object)) {
  285. return new Date(object);
  286. }
  287. if (object instanceof Date) {
  288. return new Date(object.valueOf());
  289. }
  290. if (util.isString(object)) {
  291. // parse ASP.Net Date pattern,
  292. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  293. // code from http://momentjs.com/
  294. var match = ASPDateRegex.exec(object);
  295. if (match) {
  296. return new Date(Number(match[1]));
  297. }
  298. else {
  299. return moment(object).toDate(); // parse string
  300. }
  301. }
  302. else {
  303. throw new Error(
  304. 'Cannot cast object of type ' + util.getType(object) +
  305. ' to type Date');
  306. }
  307. case 'ISODate':
  308. if (object instanceof Date) {
  309. return object.toISOString();
  310. }
  311. else if (util.isNumber(object) || util.isString(object)) {
  312. return moment(object).toDate().toISOString();
  313. }
  314. else {
  315. throw new Error(
  316. 'Cannot cast object of type ' + util.getType(object) +
  317. ' to type ISODate');
  318. }
  319. case 'ASPDate':
  320. if (object instanceof Date) {
  321. return '/Date(' + object.valueOf() + ')/';
  322. }
  323. else if (util.isNumber(object) || util.isString(object)) {
  324. return '/Date(' + moment(object).valueOf() + ')/';
  325. }
  326. else {
  327. throw new Error(
  328. 'Cannot cast object of type ' + util.getType(object) +
  329. ' to type ASPDate');
  330. }
  331. default:
  332. throw new Error('Cannot cast object of type ' + util.getType(object) +
  333. ' to type "' + type + '"');
  334. }
  335. };
  336. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  337. /**
  338. * Get the type of an object, for example util.getType([]) returns 'Array'
  339. * @param {*} object
  340. * @return {String} type
  341. */
  342. util.getType = function getType(object) {
  343. var type = typeof object;
  344. if (type == 'object') {
  345. if (object == null) {
  346. return 'null';
  347. }
  348. if (object instanceof Boolean) {
  349. return 'Boolean';
  350. }
  351. if (object instanceof Number) {
  352. return 'Number';
  353. }
  354. if (object instanceof String) {
  355. return 'String';
  356. }
  357. if (object instanceof Array) {
  358. return 'Array';
  359. }
  360. if (object instanceof Date) {
  361. return 'Date';
  362. }
  363. return 'Object';
  364. }
  365. else if (type == 'number') {
  366. return 'Number';
  367. }
  368. else if (type == 'boolean') {
  369. return 'Boolean';
  370. }
  371. else if (type == 'string') {
  372. return 'String';
  373. }
  374. return type;
  375. };
  376. /**
  377. * Retrieve the absolute left value of a DOM element
  378. * @param {Element} elem A dom element, for example a div
  379. * @return {number} left The absolute left position of this element
  380. * in the browser page.
  381. */
  382. util.getAbsoluteLeft = function getAbsoluteLeft (elem) {
  383. var doc = document.documentElement;
  384. var body = document.body;
  385. var left = elem.offsetLeft;
  386. var e = elem.offsetParent;
  387. while (e != null && e != body && e != doc) {
  388. left += e.offsetLeft;
  389. left -= e.scrollLeft;
  390. e = e.offsetParent;
  391. }
  392. return left;
  393. };
  394. /**
  395. * Retrieve the absolute top value of a DOM element
  396. * @param {Element} elem A dom element, for example a div
  397. * @return {number} top The absolute top position of this element
  398. * in the browser page.
  399. */
  400. util.getAbsoluteTop = function getAbsoluteTop (elem) {
  401. var doc = document.documentElement;
  402. var body = document.body;
  403. var top = elem.offsetTop;
  404. var e = elem.offsetParent;
  405. while (e != null && e != body && e != doc) {
  406. top += e.offsetTop;
  407. top -= e.scrollTop;
  408. e = e.offsetParent;
  409. }
  410. return top;
  411. };
  412. /**
  413. * Get the absolute, vertical mouse position from an event.
  414. * @param {Event} event
  415. * @return {Number} pageY
  416. */
  417. util.getPageY = function getPageY (event) {
  418. if ('pageY' in event) {
  419. return event.pageY;
  420. }
  421. else {
  422. var clientY;
  423. if (('targetTouches' in event) && event.targetTouches.length) {
  424. clientY = event.targetTouches[0].clientY;
  425. }
  426. else {
  427. clientY = event.clientY;
  428. }
  429. var doc = document.documentElement;
  430. var body = document.body;
  431. return clientY +
  432. ( doc && doc.scrollTop || body && body.scrollTop || 0 ) -
  433. ( doc && doc.clientTop || body && body.clientTop || 0 );
  434. }
  435. };
  436. /**
  437. * Get the absolute, horizontal mouse position from an event.
  438. * @param {Event} event
  439. * @return {Number} pageX
  440. */
  441. util.getPageX = function getPageX (event) {
  442. if ('pageY' in event) {
  443. return event.pageX;
  444. }
  445. else {
  446. var clientX;
  447. if (('targetTouches' in event) && event.targetTouches.length) {
  448. clientX = event.targetTouches[0].clientX;
  449. }
  450. else {
  451. clientX = event.clientX;
  452. }
  453. var doc = document.documentElement;
  454. var body = document.body;
  455. return clientX +
  456. ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
  457. ( doc && doc.clientLeft || body && body.clientLeft || 0 );
  458. }
  459. };
  460. /**
  461. * add a className to the given elements style
  462. * @param {Element} elem
  463. * @param {String} className
  464. */
  465. util.addClassName = function addClassName(elem, className) {
  466. var classes = elem.className.split(' ');
  467. if (classes.indexOf(className) == -1) {
  468. classes.push(className); // add the class to the array
  469. elem.className = classes.join(' ');
  470. }
  471. };
  472. /**
  473. * add a className to the given elements style
  474. * @param {Element} elem
  475. * @param {String} className
  476. */
  477. util.removeClassName = function removeClassname(elem, className) {
  478. var classes = elem.className.split(' ');
  479. var index = classes.indexOf(className);
  480. if (index != -1) {
  481. classes.splice(index, 1); // remove the class from the array
  482. elem.className = classes.join(' ');
  483. }
  484. };
  485. /**
  486. * For each method for both arrays and objects.
  487. * In case of an array, the built-in Array.forEach() is applied.
  488. * In case of an Object, the method loops over all properties of the object.
  489. * @param {Object | Array} object An Object or Array
  490. * @param {function} callback Callback method, called for each item in
  491. * the object or array with three parameters:
  492. * callback(value, index, object)
  493. */
  494. util.forEach = function forEach (object, callback) {
  495. if (object instanceof Array) {
  496. // array
  497. object.forEach(callback);
  498. }
  499. else {
  500. // object
  501. for (var key in object) {
  502. if (object.hasOwnProperty(key)) {
  503. callback(object[key], key, object);
  504. }
  505. }
  506. }
  507. };
  508. /**
  509. * Update a property in an object
  510. * @param {Object} object
  511. * @param {String} key
  512. * @param {*} value
  513. * @return {Boolean} changed
  514. */
  515. util.updateProperty = function updateProp (object, key, value) {
  516. if (object[key] !== value) {
  517. object[key] = value;
  518. return true;
  519. }
  520. else {
  521. return false;
  522. }
  523. };
  524. /**
  525. * Add and event listener. Works for all browsers
  526. * @param {Element} element An html element
  527. * @param {string} action The action, for example "click",
  528. * without the prefix "on"
  529. * @param {function} listener The callback function to be executed
  530. * @param {boolean} [useCapture]
  531. */
  532. util.addEventListener = function addEventListener(element, action, listener, useCapture) {
  533. if (element.addEventListener) {
  534. if (useCapture === undefined)
  535. useCapture = false;
  536. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  537. action = "DOMMouseScroll"; // For Firefox
  538. }
  539. element.addEventListener(action, listener, useCapture);
  540. } else {
  541. element.attachEvent("on" + action, listener); // IE browsers
  542. }
  543. };
  544. /**
  545. * Remove an event listener from an element
  546. * @param {Element} element An html dom element
  547. * @param {string} action The name of the event, for example "mousedown"
  548. * @param {function} listener The listener function
  549. * @param {boolean} [useCapture]
  550. */
  551. util.removeEventListener = function removeEventListener(element, action, listener, useCapture) {
  552. if (element.removeEventListener) {
  553. // non-IE browsers
  554. if (useCapture === undefined)
  555. useCapture = false;
  556. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  557. action = "DOMMouseScroll"; // For Firefox
  558. }
  559. element.removeEventListener(action, listener, useCapture);
  560. } else {
  561. // IE browsers
  562. element.detachEvent("on" + action, listener);
  563. }
  564. };
  565. /**
  566. * Get HTML element which is the target of the event
  567. * @param {Event} event
  568. * @return {Element} target element
  569. */
  570. util.getTarget = function getTarget(event) {
  571. // code from http://www.quirksmode.org/js/events_properties.html
  572. if (!event) {
  573. event = window.event;
  574. }
  575. var target;
  576. if (event.target) {
  577. target = event.target;
  578. }
  579. else if (event.srcElement) {
  580. target = event.srcElement;
  581. }
  582. if (target.nodeType != undefined && target.nodeType == 3) {
  583. // defeat Safari bug
  584. target = target.parentNode;
  585. }
  586. return target;
  587. };
  588. /**
  589. * Stop event propagation
  590. */
  591. util.stopPropagation = function stopPropagation(event) {
  592. if (!event)
  593. event = window.event;
  594. if (event.stopPropagation) {
  595. event.stopPropagation(); // non-IE browsers
  596. }
  597. else {
  598. event.cancelBubble = true; // IE browsers
  599. }
  600. };
  601. /**
  602. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  603. */
  604. util.preventDefault = function preventDefault (event) {
  605. if (!event)
  606. event = window.event;
  607. if (event.preventDefault) {
  608. event.preventDefault(); // non-IE browsers
  609. }
  610. else {
  611. event.returnValue = false; // IE browsers
  612. }
  613. };
  614. util.option = {};
  615. /**
  616. * Cast a value as boolean
  617. * @param {Boolean | function | undefined} value
  618. * @param {Boolean} [defaultValue]
  619. * @returns {Boolean} bool
  620. */
  621. util.option.asBoolean = function (value, defaultValue) {
  622. if (typeof value == 'function') {
  623. value = value();
  624. }
  625. if (value != null) {
  626. return (value != false);
  627. }
  628. return defaultValue || null;
  629. };
  630. /**
  631. * Cast a value as number
  632. * @param {Boolean | function | undefined} value
  633. * @param {Number} [defaultValue]
  634. * @returns {Number} number
  635. */
  636. util.option.asNumber = function (value, defaultValue) {
  637. if (typeof value == 'function') {
  638. value = value();
  639. }
  640. if (value != null) {
  641. return Number(value);
  642. }
  643. return defaultValue || null;
  644. };
  645. /**
  646. * Cast a value as string
  647. * @param {String | function | undefined} value
  648. * @param {String} [defaultValue]
  649. * @returns {String} str
  650. */
  651. util.option.asString = function (value, defaultValue) {
  652. if (typeof value == 'function') {
  653. value = value();
  654. }
  655. if (value != null) {
  656. return String(value);
  657. }
  658. return defaultValue || null;
  659. };
  660. /**
  661. * Cast a size or location in pixels or a percentage
  662. * @param {String | Number | function | undefined} value
  663. * @param {String} [defaultValue]
  664. * @returns {String} size
  665. */
  666. util.option.asSize = function (value, defaultValue) {
  667. if (typeof value == 'function') {
  668. value = value();
  669. }
  670. if (util.isString(value)) {
  671. return value;
  672. }
  673. else if (util.isNumber(value)) {
  674. return value + 'px';
  675. }
  676. else {
  677. return defaultValue || null;
  678. }
  679. };
  680. /**
  681. * Cast a value as DOM element
  682. * @param {HTMLElement | function | undefined} value
  683. * @param {HTMLElement} [defaultValue]
  684. * @returns {HTMLElement | null} dom
  685. */
  686. util.option.asElement = function (value, defaultValue) {
  687. if (typeof value == 'function') {
  688. value = value();
  689. }
  690. return value || defaultValue || null;
  691. };
  692. // Internet Explorer 8 and older does not support Array.indexOf, so we define
  693. // it here in that case.
  694. // http://soledadpenades.com/2007/05/17/arrayindexof-in-internet-explorer/
  695. if(!Array.prototype.indexOf) {
  696. Array.prototype.indexOf = function(obj){
  697. for(var i = 0; i < this.length; i++){
  698. if(this[i] == obj){
  699. return i;
  700. }
  701. }
  702. return -1;
  703. };
  704. try {
  705. console.log("Warning: Ancient browser detected. Please update your browser");
  706. }
  707. catch (err) {
  708. }
  709. }
  710. // Internet Explorer 8 and older does not support Array.forEach, so we define
  711. // it here in that case.
  712. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/forEach
  713. if (!Array.prototype.forEach) {
  714. Array.prototype.forEach = function(fn, scope) {
  715. for(var i = 0, len = this.length; i < len; ++i) {
  716. fn.call(scope || this, this[i], i, this);
  717. }
  718. }
  719. }
  720. // Internet Explorer 8 and older does not support Array.map, so we define it
  721. // here in that case.
  722. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/map
  723. // Production steps of ECMA-262, Edition 5, 15.4.4.19
  724. // Reference: http://es5.github.com/#x15.4.4.19
  725. if (!Array.prototype.map) {
  726. Array.prototype.map = function(callback, thisArg) {
  727. var T, A, k;
  728. if (this == null) {
  729. throw new TypeError(" this is null or not defined");
  730. }
  731. // 1. Let O be the result of calling ToObject passing the |this| value as the argument.
  732. var O = Object(this);
  733. // 2. Let lenValue be the result of calling the Get internal method of O with the argument "length".
  734. // 3. Let len be ToUint32(lenValue).
  735. var len = O.length >>> 0;
  736. // 4. If IsCallable(callback) is false, throw a TypeError exception.
  737. // See: http://es5.github.com/#x9.11
  738. if (typeof callback !== "function") {
  739. throw new TypeError(callback + " is not a function");
  740. }
  741. // 5. If thisArg was supplied, let T be thisArg; else let T be undefined.
  742. if (thisArg) {
  743. T = thisArg;
  744. }
  745. // 6. Let A be a new array created as if by the expression new Array(len) where Array is
  746. // the standard built-in constructor with that name and len is the value of len.
  747. A = new Array(len);
  748. // 7. Let k be 0
  749. k = 0;
  750. // 8. Repeat, while k < len
  751. while(k < len) {
  752. var kValue, mappedValue;
  753. // a. Let Pk be ToString(k).
  754. // This is implicit for LHS operands of the in operator
  755. // b. Let kPresent be the result of calling the HasProperty internal method of O with argument Pk.
  756. // This step can be combined with c
  757. // c. If kPresent is true, then
  758. if (k in O) {
  759. // i. Let kValue be the result of calling the Get internal method of O with argument Pk.
  760. kValue = O[ k ];
  761. // ii. Let mappedValue be the result of calling the Call internal method of callback
  762. // with T as the this value and argument list containing kValue, k, and O.
  763. mappedValue = callback.call(T, kValue, k, O);
  764. // iii. Call the DefineOwnProperty internal method of A with arguments
  765. // Pk, Property Descriptor {Value: mappedValue, : true, Enumerable: true, Configurable: true},
  766. // and false.
  767. // In browsers that support Object.defineProperty, use the following:
  768. // Object.defineProperty(A, Pk, { value: mappedValue, writable: true, enumerable: true, configurable: true });
  769. // For best browser support, use the following:
  770. A[ k ] = mappedValue;
  771. }
  772. // d. Increase k by 1.
  773. k++;
  774. }
  775. // 9. return A
  776. return A;
  777. };
  778. }
  779. // Internet Explorer 8 and older does not support Array.filter, so we define it
  780. // here in that case.
  781. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/filter
  782. if (!Array.prototype.filter) {
  783. Array.prototype.filter = function(fun /*, thisp */) {
  784. "use strict";
  785. if (this == null) {
  786. throw new TypeError();
  787. }
  788. var t = Object(this);
  789. var len = t.length >>> 0;
  790. if (typeof fun != "function") {
  791. throw new TypeError();
  792. }
  793. var res = [];
  794. var thisp = arguments[1];
  795. for (var i = 0; i < len; i++) {
  796. if (i in t) {
  797. var val = t[i]; // in case fun mutates this
  798. if (fun.call(thisp, val, i, t))
  799. res.push(val);
  800. }
  801. }
  802. return res;
  803. };
  804. }
  805. // Internet Explorer 8 and older does not support Object.keys, so we define it
  806. // here in that case.
  807. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Object/keys
  808. if (!Object.keys) {
  809. Object.keys = (function () {
  810. var hasOwnProperty = Object.prototype.hasOwnProperty,
  811. hasDontEnumBug = !({toString: null}).propertyIsEnumerable('toString'),
  812. dontEnums = [
  813. 'toString',
  814. 'toLocaleString',
  815. 'valueOf',
  816. 'hasOwnProperty',
  817. 'isPrototypeOf',
  818. 'propertyIsEnumerable',
  819. 'constructor'
  820. ],
  821. dontEnumsLength = dontEnums.length;
  822. return function (obj) {
  823. if (typeof obj !== 'object' && typeof obj !== 'function' || obj === null) {
  824. throw new TypeError('Object.keys called on non-object');
  825. }
  826. var result = [];
  827. for (var prop in obj) {
  828. if (hasOwnProperty.call(obj, prop)) result.push(prop);
  829. }
  830. if (hasDontEnumBug) {
  831. for (var i=0; i < dontEnumsLength; i++) {
  832. if (hasOwnProperty.call(obj, dontEnums[i])) result.push(dontEnums[i]);
  833. }
  834. }
  835. return result;
  836. }
  837. })()
  838. }
  839. // Internet Explorer 8 and older does not support Array.isArray,
  840. // so we define it here in that case.
  841. // https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Array/isArray
  842. if(!Array.isArray) {
  843. Array.isArray = function (vArg) {
  844. return Object.prototype.toString.call(vArg) === "[object Array]";
  845. };
  846. }
  847. // exports
  848. module.exports = exports = util;
  849. },{}],2:[function(require,module,exports){
  850. var util = require('./util'),
  851. Component = require('./component/component');
  852. /**
  853. * @constructor Controller
  854. *
  855. * A Controller controls the reflows and repaints of all visual components
  856. */
  857. function Controller () {
  858. this.id = util.randomUUID();
  859. this.components = {};
  860. this.repaintTimer = undefined;
  861. this.reflowTimer = undefined;
  862. }
  863. /**
  864. * Add a component to the controller
  865. * @param {Component | Controller} component
  866. */
  867. Controller.prototype.add = function (component) {
  868. // validate the component
  869. if (component.id == undefined) {
  870. throw new Error('Component has no field id');
  871. }
  872. if (!(component instanceof Component) && !(component instanceof Controller)) {
  873. throw new TypeError('Component must be an instance of ' +
  874. 'prototype Component or Controller');
  875. }
  876. // add the component
  877. component.controller = this;
  878. this.components[component.id] = component;
  879. };
  880. /**
  881. * Request a reflow. The controller will schedule a reflow
  882. */
  883. Controller.prototype.requestReflow = function () {
  884. if (!this.reflowTimer) {
  885. var me = this;
  886. this.reflowTimer = setTimeout(function () {
  887. me.reflowTimer = undefined;
  888. me.reflow();
  889. }, 0);
  890. }
  891. };
  892. /**
  893. * Request a repaint. The controller will schedule a repaint
  894. */
  895. Controller.prototype.requestRepaint = function () {
  896. if (!this.repaintTimer) {
  897. var me = this;
  898. this.repaintTimer = setTimeout(function () {
  899. me.repaintTimer = undefined;
  900. me.repaint();
  901. }, 0);
  902. }
  903. };
  904. /**
  905. * Repaint all components
  906. */
  907. Controller.prototype.repaint = function () {
  908. var changed = false;
  909. // cancel any running repaint request
  910. if (this.repaintTimer) {
  911. clearTimeout(this.repaintTimer);
  912. this.repaintTimer = undefined;
  913. }
  914. var done = {};
  915. function repaint(component, id) {
  916. if (!(id in done)) {
  917. // first repaint the components on which this component is dependent
  918. if (component.depends) {
  919. component.depends.forEach(function (dep) {
  920. repaint(dep, dep.id);
  921. });
  922. }
  923. if (component.parent) {
  924. repaint(component.parent, component.parent.id);
  925. }
  926. // repaint the component itself and mark as done
  927. changed = component.repaint() || changed;
  928. done[id] = true;
  929. }
  930. }
  931. util.forEach(this.components, repaint);
  932. // immediately reflow when needed
  933. if (changed) {
  934. this.reflow();
  935. }
  936. // TODO: limit the number of nested reflows/repaints, prevent loop
  937. };
  938. /**
  939. * Reflow all components
  940. */
  941. Controller.prototype.reflow = function () {
  942. var resized = false;
  943. // cancel any running repaint request
  944. if (this.reflowTimer) {
  945. clearTimeout(this.reflowTimer);
  946. this.reflowTimer = undefined;
  947. }
  948. var done = {};
  949. function reflow(component, id) {
  950. if (!(id in done)) {
  951. // first reflow the components on which this component is dependent
  952. if (component.depends) {
  953. component.depends.forEach(function (dep) {
  954. reflow(dep, dep.id);
  955. });
  956. }
  957. if (component.parent) {
  958. reflow(component.parent, component.parent.id);
  959. }
  960. // reflow the component itself and mark as done
  961. resized = component.reflow() || resized;
  962. done[id] = true;
  963. }
  964. }
  965. util.forEach(this.components, reflow);
  966. // immediately repaint when needed
  967. if (resized) {
  968. this.repaint();
  969. }
  970. // TODO: limit the number of nested reflows/repaints, prevent loop
  971. };
  972. // exports
  973. module.exports = exports = Controller;
  974. },{"./util":8,"./component/component":9}],3:[function(require,module,exports){
  975. var util = require('./util');
  976. /**
  977. * DataSet
  978. *
  979. * Usage:
  980. * var dataSet = new DataSet({
  981. * fieldId: '_id',
  982. * fieldTypes: {
  983. * // ...
  984. * }
  985. * });
  986. *
  987. * dataSet.add(item);
  988. * dataSet.add(data);
  989. * dataSet.update(item);
  990. * dataSet.update(data);
  991. * dataSet.remove(id);
  992. * dataSet.remove(ids);
  993. * var data = dataSet.get();
  994. * var data = dataSet.get(id);
  995. * var data = dataSet.get(ids);
  996. * var data = dataSet.get(ids, options, data);
  997. * dataSet.clear();
  998. *
  999. * A data set can:
  1000. * - add/remove/update data
  1001. * - gives triggers upon changes in the data
  1002. * - can import/export data in various data formats
  1003. * @param {Object} [options] Available options:
  1004. * {String} fieldId Field name of the id in the
  1005. * items, 'id' by default.
  1006. * {Object.<String, String} fieldTypes
  1007. * A map with field names as key,
  1008. * and the field type as value.
  1009. */
  1010. function DataSet (options) {
  1011. var me = this;
  1012. this.options = options || {};
  1013. this.data = {}; // map with data indexed by id
  1014. this.fieldId = this.options.fieldId || 'id'; // name of the field containing id
  1015. this.fieldTypes = {}; // field types by field name
  1016. if (this.options.fieldTypes) {
  1017. util.forEach(this.options.fieldTypes, function (value, field) {
  1018. if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
  1019. me.fieldTypes[field] = 'Date';
  1020. }
  1021. else {
  1022. me.fieldTypes[field] = value;
  1023. }
  1024. });
  1025. }
  1026. // event subscribers
  1027. this.subscribers = {};
  1028. this.internalIds = {}; // internally generated id's
  1029. }
  1030. /**
  1031. * Subscribe to an event, add an event listener
  1032. * @param {String} event Event name. Available events: 'put', 'update',
  1033. * 'remove'
  1034. * @param {function} callback Callback method. Called with three parameters:
  1035. * {String} event
  1036. * {Object | null} params
  1037. * {String} senderId
  1038. * @param {String} [id] Optional id for the sender, used to filter
  1039. * events triggered by the sender itself.
  1040. */
  1041. DataSet.prototype.subscribe = function (event, callback, id) {
  1042. var subscribers = this.subscribers[event];
  1043. if (!subscribers) {
  1044. subscribers = [];
  1045. this.subscribers[event] = subscribers;
  1046. }
  1047. subscribers.push({
  1048. id: id ? String(id) : null,
  1049. callback: callback
  1050. });
  1051. };
  1052. /**
  1053. * Unsubscribe from an event, remove an event listener
  1054. * @param {String} event
  1055. * @param {function} callback
  1056. */
  1057. DataSet.prototype.unsubscribe = function (event, callback) {
  1058. var subscribers = this.subscribers[event];
  1059. if (subscribers) {
  1060. this.subscribers[event] = subscribers.filter(function (listener) {
  1061. return (listener.callback != callback);
  1062. });
  1063. }
  1064. };
  1065. /**
  1066. * Trigger an event
  1067. * @param {String} event
  1068. * @param {Object | null} params
  1069. * @param {String} [senderId] Optional id of the sender. The event will
  1070. * be triggered for all subscribers except the
  1071. * sender itself.
  1072. * @private
  1073. */
  1074. DataSet.prototype._trigger = function (event, params, senderId) {
  1075. if (event == '*') {
  1076. throw new Error('Cannot trigger event *');
  1077. }
  1078. var subscribers = [];
  1079. if (event in this.subscribers) {
  1080. subscribers = subscribers.concat(this.subscribers[event]);
  1081. }
  1082. if ('*' in this.subscribers) {
  1083. subscribers = subscribers.concat(this.subscribers['*']);
  1084. }
  1085. subscribers.forEach(function (listener) {
  1086. if (listener.id != senderId && listener.callback) {
  1087. listener.callback(event, params, senderId || null);
  1088. }
  1089. });
  1090. };
  1091. /**
  1092. * Add data. Existing items with the same id will be overwritten.
  1093. * @param {Object | Array | DataTable} data
  1094. * @param {String} [senderId] Optional sender id, used to trigger events for
  1095. * all but this sender's event subscribers.
  1096. */
  1097. DataSet.prototype.add = function (data, senderId) {
  1098. var items = [],
  1099. id,
  1100. me = this;
  1101. if (data instanceof Array) {
  1102. // Array
  1103. data.forEach(function (item) {
  1104. var id = me._addItem(item);
  1105. items.push(id);
  1106. });
  1107. }
  1108. else if (util.isDataTable(data)) {
  1109. // Google DataTable
  1110. var columns = this._getColumnNames(data);
  1111. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1112. var item = {};
  1113. columns.forEach(function (field, col) {
  1114. item[field] = data.getValue(row, col);
  1115. });
  1116. id = me._addItem(item);
  1117. items.push(id);
  1118. }
  1119. }
  1120. else if (data instanceof Object) {
  1121. // Single item
  1122. id = me._addItem(data);
  1123. items.push(id);
  1124. }
  1125. else {
  1126. throw new Error('Unknown dataType');
  1127. }
  1128. this._trigger('add', {items: items}, senderId);
  1129. };
  1130. /**
  1131. * Update existing items. Items with the same id will be merged
  1132. * @param {Object | Array | DataTable} data
  1133. * @param {String} [senderId] Optional sender id, used to trigger events for
  1134. * all but this sender's event subscribers.
  1135. */
  1136. DataSet.prototype.update = function (data, senderId) {
  1137. var items = [],
  1138. id,
  1139. me = this;
  1140. if (data instanceof Array) {
  1141. // Array
  1142. data.forEach(function (item) {
  1143. var id = me._updateItem(item);
  1144. items.push(id);
  1145. });
  1146. }
  1147. else if (util.isDataTable(data)) {
  1148. // Google DataTable
  1149. var columns = this._getColumnNames(data);
  1150. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1151. var item = {};
  1152. columns.forEach(function (field, col) {
  1153. item[field] = data.getValue(row, col);
  1154. });
  1155. id = me._updateItem(item);
  1156. items.push(id);
  1157. }
  1158. }
  1159. else if (data instanceof Object) {
  1160. // Single item
  1161. id = me._updateItem(data);
  1162. items.push(id);
  1163. }
  1164. else {
  1165. throw new Error('Unknown dataType');
  1166. }
  1167. this._trigger('update', {items: items}, senderId);
  1168. };
  1169. /**
  1170. * Get a data item or multiple items
  1171. * @param {String | Number | Array | Object} [ids] Id of a single item, or an
  1172. * array with multiple id's, or
  1173. * undefined or an Object with options
  1174. * to retrieve all data.
  1175. * @param {Object} [options] Available options:
  1176. * {String} [type]
  1177. * 'DataTable' or 'Array' (default)
  1178. * {Object.<String, String>} [fieldTypes]
  1179. * {String[]} [fields] filter fields
  1180. * @param {Array | DataTable} [data] If provided, items will be appended
  1181. * to this array or table. Required
  1182. * in case of Google DataTable
  1183. * @return {Array | Object | DataTable | null} data
  1184. * @throws Error
  1185. */
  1186. DataSet.prototype.get = function (ids, options, data) {
  1187. var me = this;
  1188. // shift arguments when first argument contains the options
  1189. if (util.getType(ids) == 'Object') {
  1190. data = options;
  1191. options = ids;
  1192. ids = undefined;
  1193. }
  1194. // merge field types
  1195. var fieldTypes = {};
  1196. if (this.options && this.options.fieldTypes) {
  1197. util.forEach(this.options.fieldTypes, function (value, field) {
  1198. fieldTypes[field] = value;
  1199. });
  1200. }
  1201. if (options && options.fieldTypes) {
  1202. util.forEach(options.fieldTypes, function (value, field) {
  1203. fieldTypes[field] = value;
  1204. });
  1205. }
  1206. var fields = options ? options.fields : undefined;
  1207. // determine the return type
  1208. var type;
  1209. if (options && options.type) {
  1210. type = (options.type == 'DataTable') ? 'DataTable' : 'Array';
  1211. if (data && (type != util.getType(data))) {
  1212. throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
  1213. 'does not correspond with specified options.type (' + options.type + ')');
  1214. }
  1215. if (type == 'DataTable' && !util.isDataTable(data)) {
  1216. throw new Error('Parameter "data" must be a DataTable ' +
  1217. 'when options.type is "DataTable"');
  1218. }
  1219. }
  1220. else if (data) {
  1221. type = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
  1222. }
  1223. else {
  1224. type = 'Array';
  1225. }
  1226. if (type == 'DataTable') {
  1227. // return a Google DataTable
  1228. var columns = this._getColumnNames(data);
  1229. if (ids == undefined) {
  1230. // return all data
  1231. util.forEach(this.data, function (item) {
  1232. me._appendRow(data, columns, me._castItem(item));
  1233. });
  1234. }
  1235. else if (util.isNumber(ids) || util.isString(ids)) {
  1236. var item = me._castItem(me.data[ids], fieldTypes, fields);
  1237. this._appendRow(data, columns, item);
  1238. }
  1239. else if (ids instanceof Array) {
  1240. ids.forEach(function (id) {
  1241. var item = me._castItem(me.data[id], fieldTypes, fields);
  1242. me._appendRow(data, columns, item);
  1243. });
  1244. }
  1245. else {
  1246. throw new TypeError('Parameter "ids" must be ' +
  1247. 'undefined, a String, Number, or Array');
  1248. }
  1249. }
  1250. else {
  1251. // return an array
  1252. data = data || [];
  1253. if (ids == undefined) {
  1254. // return all data
  1255. util.forEach(this.data, function (item) {
  1256. data.push(me._castItem(item, fieldTypes, fields));
  1257. });
  1258. }
  1259. else if (util.isNumber(ids) || util.isString(ids)) {
  1260. // return a single item
  1261. return this._castItem(me.data[ids], fieldTypes, fields);
  1262. }
  1263. else if (ids instanceof Array) {
  1264. ids.forEach(function (id) {
  1265. data.push(me._castItem(me.data[id], fieldTypes, fields));
  1266. });
  1267. }
  1268. else {
  1269. throw new TypeError('Parameter "ids" must be ' +
  1270. 'undefined, a String, Number, or Array');
  1271. }
  1272. }
  1273. return data;
  1274. };
  1275. /**
  1276. * Remove an object by pointer or by id
  1277. * @param {String | Number | Object | Array} id Object or id, or an array with
  1278. * objects or ids to be removed
  1279. * @param {String} [senderId] Optional sender id, used to trigger events for
  1280. * all but this sender's event subscribers.
  1281. */
  1282. DataSet.prototype.remove = function (id, senderId) {
  1283. var items = [],
  1284. me = this;
  1285. if (util.isNumber(id) || util.isString(id)) {
  1286. delete this.data[id];
  1287. delete this.internalIds[id];
  1288. items.push(id);
  1289. }
  1290. else if (id instanceof Array) {
  1291. id.forEach(function (id) {
  1292. me.remove(id);
  1293. });
  1294. items = items.concat(id);
  1295. }
  1296. else if (id instanceof Object) {
  1297. // search for the object
  1298. for (var i in this.data) {
  1299. if (this.data.hasOwnProperty(i)) {
  1300. if (this.data[i] == id) {
  1301. delete this.data[i];
  1302. delete this.internalIds[i];
  1303. items.push(i);
  1304. }
  1305. }
  1306. }
  1307. }
  1308. this._trigger('remove', {items: items}, senderId);
  1309. };
  1310. /**
  1311. * Clear the data
  1312. * @param {String} [senderId] Optional sender id, used to trigger events for
  1313. * all but this sender's event subscribers.
  1314. */
  1315. DataSet.prototype.clear = function (senderId) {
  1316. var ids = Object.keys(this.data);
  1317. this.data = {};
  1318. this.internalIds = {};
  1319. this._trigger('remove', {items: ids}, senderId);
  1320. };
  1321. /**
  1322. * Find the item with maximum value of a specified field
  1323. * @param {String} field
  1324. * @return {Object} item Item containing max value, or null if no items
  1325. */
  1326. DataSet.prototype.max = function (field) {
  1327. var data = this.data,
  1328. ids = Object.keys(data);
  1329. var max = null;
  1330. var maxField = null;
  1331. ids.forEach(function (id) {
  1332. var item = data[id];
  1333. var itemField = item[field];
  1334. if (itemField != null && (!max || itemField > maxField)) {
  1335. max = item;
  1336. maxField = itemField;
  1337. }
  1338. });
  1339. return max;
  1340. };
  1341. /**
  1342. * Find the item with minimum value of a specified field
  1343. * @param {String} field
  1344. */
  1345. DataSet.prototype.min = function (field) {
  1346. var data = this.data,
  1347. ids = Object.keys(data);
  1348. var min = null;
  1349. var minField = null;
  1350. ids.forEach(function (id) {
  1351. var item = data[id];
  1352. var itemField = item[field];
  1353. if (itemField != null && (!min || itemField < minField)) {
  1354. min = item;
  1355. minField = itemField;
  1356. }
  1357. });
  1358. return min;
  1359. };
  1360. /**
  1361. * Add a single item
  1362. * @param {Object} item
  1363. * @return {String} id
  1364. * @private
  1365. */
  1366. DataSet.prototype._addItem = function (item) {
  1367. var id = item[this.fieldId];
  1368. if (id == undefined) {
  1369. // generate an id
  1370. id = util.randomUUID();
  1371. item[this.fieldId] = id;
  1372. this.internalIds[id] = item;
  1373. }
  1374. var d = {};
  1375. for (var field in item) {
  1376. if (item.hasOwnProperty(field)) {
  1377. var type = this.fieldTypes[field]; // type may be undefined
  1378. d[field] = util.cast(item[field], type);
  1379. }
  1380. }
  1381. this.data[id] = d;
  1382. //TODO: fail when an item with this id already exists?
  1383. return id;
  1384. };
  1385. /**
  1386. * Cast and filter the fields of an item
  1387. * @param {Object | undefined} item
  1388. * @param {Object.<String, String>} [fieldTypes]
  1389. * @param {String[]} [fields]
  1390. * @return {Object | null} castedItem
  1391. * @private
  1392. */
  1393. DataSet.prototype._castItem = function (item, fieldTypes, fields) {
  1394. var clone,
  1395. fieldId = this.fieldId,
  1396. internalIds = this.internalIds;
  1397. if (item) {
  1398. clone = {};
  1399. fieldTypes = fieldTypes || {};
  1400. if (fields) {
  1401. // output filtered fields
  1402. util.forEach(item, function (value, field) {
  1403. if (fields.indexOf(field) != -1) {
  1404. clone[field] = util.cast(value, fieldTypes[field]);
  1405. }
  1406. });
  1407. }
  1408. else {
  1409. // output all fields, except internal ids
  1410. util.forEach(item, function (value, field) {
  1411. if (field != fieldId || !(value in internalIds)) {
  1412. clone[field] = util.cast(value, fieldTypes[field]);
  1413. }
  1414. });
  1415. }
  1416. }
  1417. else {
  1418. clone = null;
  1419. }
  1420. return clone;
  1421. };
  1422. /**
  1423. * Update a single item: merge with existing item
  1424. * @param {Object} item
  1425. * @return {String} id
  1426. * @private
  1427. */
  1428. DataSet.prototype._updateItem = function (item) {
  1429. var id = item[this.fieldId];
  1430. if (id == undefined) {
  1431. throw new Error('Item has no id (item: ' + JSON.stringify(item) + ')');
  1432. }
  1433. var d = this.data[id];
  1434. if (d) {
  1435. // merge with current item
  1436. for (var field in item) {
  1437. if (item.hasOwnProperty(field)) {
  1438. var type = this.fieldTypes[field]; // type may be undefined
  1439. d[field] = util.cast(item[field], type);
  1440. }
  1441. }
  1442. }
  1443. else {
  1444. // create new item
  1445. this._addItem(item);
  1446. }
  1447. return id;
  1448. };
  1449. /**
  1450. * Get an array with the column names of a Google DataTable
  1451. * @param {DataTable} dataTable
  1452. * @return {Array} columnNames
  1453. * @private
  1454. */
  1455. DataSet.prototype._getColumnNames = function (dataTable) {
  1456. var columns = [];
  1457. for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
  1458. columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
  1459. }
  1460. return columns;
  1461. };
  1462. /**
  1463. * Append an item as a row to the dataTable
  1464. * @param dataTable
  1465. * @param columns
  1466. * @param item
  1467. * @private
  1468. */
  1469. DataSet.prototype._appendRow = function (dataTable, columns, item) {
  1470. var row = dataTable.addRow();
  1471. columns.forEach(function (field, col) {
  1472. dataTable.setValue(row, col, item[field]);
  1473. });
  1474. };
  1475. // exports
  1476. module.exports = exports = DataSet;
  1477. },{"./util":8}],5:[function(require,module,exports){
  1478. var util = require('./util'),
  1479. events = require('./events');
  1480. /**
  1481. * @constructor Range
  1482. * A Range controls a numeric range with a start and end value.
  1483. * The Range adjusts the range based on mouse events or programmatic changes,
  1484. * and triggers events when the range is changing or has been changed.
  1485. * @param {Object} [options] See description at Range.setOptions
  1486. * @extends Controller
  1487. */
  1488. function Range(options) {
  1489. this.id = util.randomUUID();
  1490. this.start = 0; // Number
  1491. this.end = 0; // Number
  1492. this.options = {
  1493. min: null,
  1494. max: null,
  1495. zoomMin: null,
  1496. zoomMax: null
  1497. };
  1498. this.setOptions(options);
  1499. this.listeners = [];
  1500. }
  1501. /**
  1502. * Set options for the range controller
  1503. * @param {Object} options Available options:
  1504. * {Number} start Set start value of the range
  1505. * {Number} end Set end value of the range
  1506. * {Number} min Minimum value for start
  1507. * {Number} max Maximum value for end
  1508. * {Number} zoomMin Set a minimum value for
  1509. * (end - start).
  1510. * {Number} zoomMax Set a maximum value for
  1511. * (end - start).
  1512. */
  1513. Range.prototype.setOptions = function (options) {
  1514. util.extend(this.options, options);
  1515. if (options.start != null || options.end != null) {
  1516. this.setRange(options.start, options.end);
  1517. }
  1518. };
  1519. /**
  1520. * Add listeners for mouse and touch events to the component
  1521. * @param {Component} component
  1522. * @param {String} event Available events: 'move', 'zoom'
  1523. * @param {String} direction Available directions: 'horizontal', 'vertical'
  1524. */
  1525. Range.prototype.subscribe = function (component, event, direction) {
  1526. var me = this;
  1527. var listener;
  1528. if (direction != 'horizontal' && direction != 'vertical') {
  1529. throw new TypeError('Unknown direction "' + direction + '". ' +
  1530. 'Choose "horizontal" or "vertical".');
  1531. }
  1532. //noinspection FallthroughInSwitchStatementJS
  1533. if (event == 'move') {
  1534. listener = {
  1535. component: component,
  1536. event: event,
  1537. direction: direction,
  1538. callback: function (event) {
  1539. me._onMouseDown(event, listener);
  1540. },
  1541. params: {}
  1542. };
  1543. component.on('mousedown', listener.callback);
  1544. me.listeners.push(listener);
  1545. }
  1546. else if (event == 'zoom') {
  1547. listener = {
  1548. component: component,
  1549. event: event,
  1550. direction: direction,
  1551. callback: function (event) {
  1552. me._onMouseWheel(event, listener);
  1553. },
  1554. params: {}
  1555. };
  1556. component.on('mousewheel', listener.callback);
  1557. me.listeners.push(listener);
  1558. }
  1559. else {
  1560. throw new TypeError('Unknown event "' + event + '". ' +
  1561. 'Choose "move" or "zoom".');
  1562. }
  1563. };
  1564. /**
  1565. * Event handler
  1566. * @param {String} event name of the event, for example 'click', 'mousemove'
  1567. * @param {function} callback callback handler, invoked with the raw HTML Event
  1568. * as parameter.
  1569. */
  1570. Range.prototype.on = function (event, callback) {
  1571. events.addListener(this, event, callback);
  1572. };
  1573. /**
  1574. * Trigger an event
  1575. * @param {String} event name of the event, available events: 'rangechange',
  1576. * 'rangechanged'
  1577. * @private
  1578. */
  1579. Range.prototype._trigger = function (event) {
  1580. events.trigger(this, event, {
  1581. start: this.start,
  1582. end: this.end
  1583. });
  1584. };
  1585. /**
  1586. * Set a new start and end range
  1587. * @param {Number} start
  1588. * @param {Number} end
  1589. */
  1590. Range.prototype.setRange = function(start, end) {
  1591. var changed = this._applyRange(start, end);
  1592. if (changed) {
  1593. this._trigger('rangechange');
  1594. this._trigger('rangechanged');
  1595. }
  1596. };
  1597. /**
  1598. * Set a new start and end range. This method is the same as setRange, but
  1599. * does not trigger a range change and range changed event, and it returns
  1600. * true when the range is changed
  1601. * @param {Number} start
  1602. * @param {Number} end
  1603. * @return {Boolean} changed
  1604. * @private
  1605. */
  1606. Range.prototype._applyRange = function(start, end) {
  1607. var newStart = (start != null) ? util.cast(start, 'Number') : this.start;
  1608. var newEnd = (end != null) ? util.cast(end, 'Number') : this.end;
  1609. var diff;
  1610. // check for valid number
  1611. if (isNaN(newStart)) {
  1612. throw new Error('Invalid start "' + start + '"');
  1613. }
  1614. if (isNaN(newEnd)) {
  1615. throw new Error('Invalid end "' + end + '"');
  1616. }
  1617. // prevent start < end
  1618. if (newEnd < newStart) {
  1619. newEnd = newStart;
  1620. }
  1621. // prevent start < min
  1622. if (this.options.min != null) {
  1623. var min = this.options.min.valueOf();
  1624. if (newStart < min) {
  1625. diff = (min - newStart);
  1626. newStart += diff;
  1627. newEnd += diff;
  1628. }
  1629. }
  1630. // prevent end > max
  1631. if (this.options.max != null) {
  1632. var max = this.options.max.valueOf();
  1633. if (newEnd > max) {
  1634. diff = (newEnd - max);
  1635. newStart -= diff;
  1636. newEnd -= diff;
  1637. }
  1638. }
  1639. // prevent (end-start) > zoomMin
  1640. if (this.options.zoomMin != null) {
  1641. var zoomMin = this.options.zoomMin.valueOf();
  1642. if (zoomMin < 0) {
  1643. zoomMin = 0;
  1644. }
  1645. if ((newEnd - newStart) < zoomMin) {
  1646. if ((this.end - this.start) > zoomMin) {
  1647. // zoom to the minimum
  1648. diff = (zoomMin - (newEnd - newStart));
  1649. newStart -= diff / 2;
  1650. newEnd += diff / 2;
  1651. }
  1652. else {
  1653. // ingore this action, we are already zoomed to the minimum
  1654. newStart = this.start;
  1655. newEnd = this.end;
  1656. }
  1657. }
  1658. }
  1659. // prevent (end-start) > zoomMin
  1660. if (this.options.zoomMax != null) {
  1661. var zoomMax = this.options.zoomMax.valueOf();
  1662. if (zoomMax < 0) {
  1663. zoomMax = 0;
  1664. }
  1665. if ((newEnd - newStart) > zoomMax) {
  1666. if ((this.end - this.start) < zoomMax) {
  1667. // zoom to the maximum
  1668. diff = ((newEnd - newStart) - zoomMax);
  1669. newStart += diff / 2;
  1670. newEnd -= diff / 2;
  1671. }
  1672. else {
  1673. // ingore this action, we are already zoomed to the maximum
  1674. newStart = this.start;
  1675. newEnd = this.end;
  1676. }
  1677. }
  1678. }
  1679. var changed = (this.start != newStart || this.end != newEnd);
  1680. this.start = newStart;
  1681. this.end = newEnd;
  1682. return changed;
  1683. };
  1684. /**
  1685. * Retrieve the current range.
  1686. * @return {Object} An object with start and end properties
  1687. */
  1688. Range.prototype.getRange = function() {
  1689. return {
  1690. start: this.start,
  1691. end: this.end
  1692. };
  1693. };
  1694. /**
  1695. * Calculate the conversion offset and factor for current range, based on
  1696. * the provided width
  1697. * @param {Number} width
  1698. * @returns {{offset: number, factor: number}} conversion
  1699. */
  1700. Range.prototype.conversion = function (width) {
  1701. var start = this.start;
  1702. var end = this.end;
  1703. return Range.conversion(this.start, this.end, width);
  1704. };
  1705. /**
  1706. * Static method to calculate the conversion offset and factor for a range,
  1707. * based on the provided start, end, and width
  1708. * @param {Number} start
  1709. * @param {Number} end
  1710. * @param {Number} width
  1711. * @returns {{offset: number, factor: number}} conversion
  1712. */
  1713. Range.conversion = function (start, end, width) {
  1714. if (width != 0 && (end - start != 0)) {
  1715. return {
  1716. offset: start,
  1717. factor: width / (end - start)
  1718. }
  1719. }
  1720. else {
  1721. return {
  1722. offset: 0,
  1723. factor: 1
  1724. };
  1725. }
  1726. };
  1727. /**
  1728. * Start moving horizontally or vertically
  1729. * @param {Event} event
  1730. * @param {Object} listener Listener containing the component and params
  1731. * @private
  1732. */
  1733. Range.prototype._onMouseDown = function(event, listener) {
  1734. event = event || window.event;
  1735. var params = listener.params;
  1736. // only react on left mouse button down
  1737. var leftButtonDown = event.which ? (event.which == 1) : (event.button == 1);
  1738. if (!leftButtonDown) {
  1739. return;
  1740. }
  1741. // get mouse position
  1742. params.mouseX = util.getPageX(event);
  1743. params.mouseY = util.getPageY(event);
  1744. params.previousLeft = 0;
  1745. params.previousOffset = 0;
  1746. params.moved = false;
  1747. params.start = this.start;
  1748. params.end = this.end;
  1749. var frame = listener.component.frame;
  1750. if (frame) {
  1751. frame.style.cursor = 'move';
  1752. }
  1753. // add event listeners to handle moving the contents
  1754. // we store the function onmousemove and onmouseup in the timeaxis,
  1755. // so we can remove the eventlisteners lateron in the function onmouseup
  1756. var me = this;
  1757. if (!params.onMouseMove) {
  1758. params.onMouseMove = function (event) {
  1759. me._onMouseMove(event, listener);
  1760. };
  1761. util.addEventListener(document, "mousemove", params.onMouseMove);
  1762. }
  1763. if (!params.onMouseUp) {
  1764. params.onMouseUp = function (event) {
  1765. me._onMouseUp(event, listener);
  1766. };
  1767. util.addEventListener(document, "mouseup", params.onMouseUp);
  1768. }
  1769. util.preventDefault(event);
  1770. };
  1771. /**
  1772. * Perform moving operating.
  1773. * This function activated from within the funcion TimeAxis._onMouseDown().
  1774. * @param {Event} event
  1775. * @param {Object} listener
  1776. * @private
  1777. */
  1778. Range.prototype._onMouseMove = function (event, listener) {
  1779. event = event || window.event;
  1780. var params = listener.params;
  1781. // calculate change in mouse position
  1782. var mouseX = util.getPageX(event);
  1783. var mouseY = util.getPageY(event);
  1784. if (params.mouseX == undefined) {
  1785. params.mouseX = mouseX;
  1786. }
  1787. if (params.mouseY == undefined) {
  1788. params.mouseY = mouseY;
  1789. }
  1790. var diffX = mouseX - params.mouseX;
  1791. var diffY = mouseY - params.mouseY;
  1792. var diff = (listener.direction == 'horizontal') ? diffX : diffY;
  1793. // if mouse movement is big enough, register it as a "moved" event
  1794. if (Math.abs(diff) >= 1) {
  1795. params.moved = true;
  1796. }
  1797. var interval = (params.end - params.start);
  1798. var width = (listener.direction == 'horizontal') ?
  1799. listener.component.width : listener.component.height;
  1800. var diffRange = -diff / width * interval;
  1801. this._applyRange(params.start + diffRange, params.end + diffRange);
  1802. // fire a rangechange event
  1803. this._trigger('rangechange');
  1804. util.preventDefault(event);
  1805. };
  1806. /**
  1807. * Stop moving operating.
  1808. * This function activated from within the function Range._onMouseDown().
  1809. * @param {event} event
  1810. * @param {Object} listener
  1811. * @private
  1812. */
  1813. Range.prototype._onMouseUp = function (event, listener) {
  1814. event = event || window.event;
  1815. var params = listener.params;
  1816. if (listener.component.frame) {
  1817. listener.component.frame.style.cursor = 'auto';
  1818. }
  1819. // remove event listeners here, important for Safari
  1820. if (params.onMouseMove) {
  1821. util.removeEventListener(document, "mousemove", params.onMouseMove);
  1822. params.onMouseMove = null;
  1823. }
  1824. if (params.onMouseUp) {
  1825. util.removeEventListener(document, "mouseup", params.onMouseUp);
  1826. params.onMouseUp = null;
  1827. }
  1828. //util.preventDefault(event);
  1829. if (params.moved) {
  1830. // fire a rangechanged event
  1831. this._trigger('rangechanged');
  1832. }
  1833. };
  1834. /**
  1835. * Event handler for mouse wheel event, used to zoom
  1836. * Code from http://adomas.org/javascript-mouse-wheel/
  1837. * @param {Event} event
  1838. * @param {Object} listener
  1839. * @private
  1840. */
  1841. Range.prototype._onMouseWheel = function(event, listener) {
  1842. event = event || window.event;
  1843. // retrieve delta
  1844. var delta = 0;
  1845. if (event.wheelDelta) { /* IE/Opera. */
  1846. delta = event.wheelDelta / 120;
  1847. } else if (event.detail) { /* Mozilla case. */
  1848. // In Mozilla, sign of delta is different than in IE.
  1849. // Also, delta is multiple of 3.
  1850. delta = -event.detail / 3;
  1851. }
  1852. // If delta is nonzero, handle it.
  1853. // Basically, delta is now positive if wheel was scrolled up,
  1854. // and negative, if wheel was scrolled down.
  1855. if (delta) {
  1856. var me = this;
  1857. var zoom = function () {
  1858. // perform the zoom action. Delta is normally 1 or -1
  1859. var zoomFactor = delta / 5.0;
  1860. var zoomAround = null;
  1861. var frame = listener.component.frame;
  1862. if (frame) {
  1863. var size, conversion;
  1864. if (listener.direction == 'horizontal') {
  1865. size = listener.component.width;
  1866. conversion = me.conversion(size);
  1867. var frameLeft = util.getAbsoluteLeft(frame);
  1868. var mouseX = util.getPageX(event);
  1869. zoomAround = (mouseX - frameLeft) / conversion.factor + conversion.offset;
  1870. }
  1871. else {
  1872. size = listener.component.height;
  1873. conversion = me.conversion(size);
  1874. var frameTop = util.getAbsoluteTop(frame);
  1875. var mouseY = util.getPageY(event);
  1876. zoomAround = ((frameTop + size - mouseY) - frameTop) / conversion.factor + conversion.offset;
  1877. }
  1878. }
  1879. me.zoom(zoomFactor, zoomAround);
  1880. };
  1881. zoom();
  1882. }
  1883. // Prevent default actions caused by mouse wheel.
  1884. // That might be ugly, but we handle scrolls somehow
  1885. // anyway, so don't bother here...
  1886. util.preventDefault(event);
  1887. };
  1888. /**
  1889. * Zoom the range the given zoomfactor in or out. Start and end date will
  1890. * be adjusted, and the timeline will be redrawn. You can optionally give a
  1891. * date around which to zoom.
  1892. * For example, try zoomfactor = 0.1 or -0.1
  1893. * @param {Number} zoomFactor Zooming amount. Positive value will zoom in,
  1894. * negative value will zoom out
  1895. * @param {Number} zoomAround Value around which will be zoomed. Optional
  1896. */
  1897. Range.prototype.zoom = function(zoomFactor, zoomAround) {
  1898. // if zoomAroundDate is not provided, take it half between start Date and end Date
  1899. if (zoomAround == null) {
  1900. zoomAround = (this.start + this.end) / 2;
  1901. }
  1902. // prevent zoom factor larger than 1 or smaller than -1 (larger than 1 will
  1903. // result in a start>=end )
  1904. if (zoomFactor >= 1) {
  1905. zoomFactor = 0.9;
  1906. }
  1907. if (zoomFactor <= -1) {
  1908. zoomFactor = -0.9;
  1909. }
  1910. // adjust a negative factor such that zooming in with 0.1 equals zooming
  1911. // out with a factor -0.1
  1912. if (zoomFactor < 0) {
  1913. zoomFactor = zoomFactor / (1 + zoomFactor);
  1914. }
  1915. // zoom start and end relative to the zoomAround value
  1916. var startDiff = (this.start - zoomAround);
  1917. var endDiff = (this.end - zoomAround);
  1918. // calculate new start and end
  1919. var newStart = this.start - startDiff * zoomFactor;
  1920. var newEnd = this.end - endDiff * zoomFactor;
  1921. this.setRange(newStart, newEnd);
  1922. };
  1923. /**
  1924. * Move the range with a given factor to the left or right. Start and end
  1925. * value will be adjusted. For example, try moveFactor = 0.1 or -0.1
  1926. * @param {Number} moveFactor Moving amount. Positive value will move right,
  1927. * negative value will move left
  1928. */
  1929. Range.prototype.move = function(moveFactor) {
  1930. // zoom start Date and end Date relative to the zoomAroundDate
  1931. var diff = (this.end - this.start);
  1932. // apply new values
  1933. var newStart = this.start + diff * moveFactor;
  1934. var newEnd = this.end + diff * moveFactor;
  1935. // TODO: reckon with min and max range
  1936. this.start = newStart;
  1937. this.end = newEnd;
  1938. };
  1939. // exports
  1940. module.exports = exports = Range;
  1941. },{"./util":8,"./events":4}],6:[function(require,module,exports){
  1942. var util = require('./util');
  1943. /**
  1944. * @constructor Stack
  1945. * Stacks items on top of each other.
  1946. * @param {ItemSet} parent
  1947. * @param {Object} [options]
  1948. */
  1949. function Stack (parent, options) {
  1950. this.parent = parent;
  1951. this.options = {
  1952. order: function (a, b) {
  1953. return (b.width - a.width) || (a.left - b.left);
  1954. }
  1955. };
  1956. this.ordered = []; // ordered items
  1957. this.setOptions(options);
  1958. }
  1959. /**
  1960. * Set options for the stack
  1961. * @param {Object} options Available options:
  1962. * {ItemSet} parent
  1963. * {Number} margin
  1964. * {function} order Stacking order
  1965. */
  1966. Stack.prototype.setOptions = function (options) {
  1967. util.extend(this.options, options);
  1968. // TODO: register on data changes at the connected parent itemset, and update the changed part only and immediately
  1969. };
  1970. /**
  1971. * Stack the items such that they don't overlap. The items will have a minimal
  1972. * distance equal to options.margin.item.
  1973. */
  1974. Stack.prototype.update = function() {
  1975. this._order();
  1976. this._stack();
  1977. };
  1978. /**
  1979. * Order the items. The items are ordered by width first, and by left position
  1980. * second.
  1981. * If a custom order function has been provided via the options, then this will
  1982. * be used.
  1983. * @private
  1984. */
  1985. Stack.prototype._order = function() {
  1986. var items = this.parent.items;
  1987. if (!items) {
  1988. throw new Error('Cannot stack items: parent does not contain items');
  1989. }
  1990. // TODO: store the sorted items, to have less work later on
  1991. var ordered = [];
  1992. var index = 0;
  1993. util.forEach(items, function (item, id) {
  1994. ordered[index] = item;
  1995. index++;
  1996. });
  1997. //if a customer stack order function exists, use it.
  1998. var order = this.options.order;
  1999. if (!(typeof this.options.order === 'function')) {
  2000. throw new Error('Option order must be a function');
  2001. }
  2002. ordered.sort(order);
  2003. this.ordered = ordered;
  2004. };
  2005. /**
  2006. * Adjust vertical positions of the events such that they don't overlap each
  2007. * other.
  2008. * @private
  2009. */
  2010. Stack.prototype._stack = function() {
  2011. var i,
  2012. iMax,
  2013. ordered = this.ordered,
  2014. options = this.options,
  2015. axisOnTop = (options.orientation == 'top'),
  2016. margin = options.margin && options.margin.item || 0;
  2017. // calculate new, non-overlapping positions
  2018. for (i = 0, iMax = ordered.length; i < iMax; i++) {
  2019. var item = ordered[i];
  2020. var collidingItem = null;
  2021. do {
  2022. // TODO: optimize checking for overlap. when there is a gap without items,
  2023. // you only need to check for items from the next item on, not from zero
  2024. collidingItem = this.checkOverlap(ordered, i, 0, i - 1, margin);
  2025. if (collidingItem != null) {
  2026. // There is a collision. Reposition the event above the colliding element
  2027. if (axisOnTop) {
  2028. item.top = collidingItem.top + collidingItem.height + margin;
  2029. }
  2030. else {
  2031. item.top = collidingItem.top - item.height - margin;
  2032. }
  2033. }
  2034. } while (collidingItem);
  2035. }
  2036. };
  2037. /**
  2038. * Check if the destiny position of given item overlaps with any
  2039. * of the other items from index itemStart to itemEnd.
  2040. * @param {Array} items Array with items
  2041. * @param {int} itemIndex Number of the item to be checked for overlap
  2042. * @param {int} itemStart First item to be checked.
  2043. * @param {int} itemEnd Last item to be checked.
  2044. * @return {Object | null} colliding item, or undefined when no collisions
  2045. * @param {Number} margin A minimum required margin.
  2046. * If margin is provided, the two items will be
  2047. * marked colliding when they overlap or
  2048. * when the margin between the two is smaller than
  2049. * the requested margin.
  2050. */
  2051. Stack.prototype.checkOverlap = function(items, itemIndex, itemStart, itemEnd, margin) {
  2052. var collision = this.collision;
  2053. // we loop from end to start, as we suppose that the chance of a
  2054. // collision is larger for items at the end, so check these first.
  2055. var a = items[itemIndex];
  2056. for (var i = itemEnd; i >= itemStart; i--) {
  2057. var b = items[i];
  2058. if (collision(a, b, margin)) {
  2059. if (i != itemIndex) {
  2060. return b;
  2061. }
  2062. }
  2063. }
  2064. return null;
  2065. };
  2066. /**
  2067. * Test if the two provided items collide
  2068. * The items must have parameters left, width, top, and height.
  2069. * @param {Component} a The first item
  2070. * @param {Component} b The second item
  2071. * @param {Number} margin A minimum required margin.
  2072. * If margin is provided, the two items will be
  2073. * marked colliding when they overlap or
  2074. * when the margin between the two is smaller than
  2075. * the requested margin.
  2076. * @return {boolean} true if a and b collide, else false
  2077. */
  2078. Stack.prototype.collision = function(a, b, margin) {
  2079. return ((a.left - margin) < (b.left + b.width) &&
  2080. (a.left + a.width + margin) > b.left &&
  2081. (a.top - margin) < (b.top + b.height) &&
  2082. (a.top + a.height + margin) > b.top);
  2083. };
  2084. // exports
  2085. module.exports = exports = Stack;
  2086. },{"./util":8}],9:[function(require,module,exports){
  2087. var util = require('./../util');
  2088. /**
  2089. * Prototype for visual components
  2090. */
  2091. function Component () {
  2092. this.id = null;
  2093. this.parent = null;
  2094. this.depends = null;
  2095. this.controller = null;
  2096. this.options = null;
  2097. this.frame = null; // main DOM element
  2098. this.top = 0;
  2099. this.left = 0;
  2100. this.width = 0;
  2101. this.height = 0;
  2102. }
  2103. /**
  2104. * Set parameters for the frame. Parameters will be merged in current parameter
  2105. * set.
  2106. * @param {Object} options Available parameters:
  2107. * {String | function} [className]
  2108. * {String | Number | function} [left]
  2109. * {String | Number | function} [top]
  2110. * {String | Number | function} [width]
  2111. * {String | Number | function} [height]
  2112. */
  2113. Component.prototype.setOptions = function(options) {
  2114. if (options) {
  2115. util.extend(this.options, options);
  2116. }
  2117. if (this.controller) {
  2118. this.requestRepaint();
  2119. this.requestReflow();
  2120. }
  2121. };
  2122. /**
  2123. * Get the container element of the component, which can be used by a child to
  2124. * add its own widgets. Not all components do have a container for childs, in
  2125. * that case null is returned.
  2126. * @returns {HTMLElement | null} container
  2127. */
  2128. Component.prototype.getContainer = function () {
  2129. // should be implemented by the component
  2130. return null;
  2131. };
  2132. /**
  2133. * Get the frame element of the component, the outer HTML DOM element.
  2134. * @returns {HTMLElement | null} frame
  2135. */
  2136. Component.prototype.getFrame = function () {
  2137. return this.frame;
  2138. };
  2139. /**
  2140. * Repaint the component
  2141. * @return {Boolean} changed
  2142. */
  2143. Component.prototype.repaint = function () {
  2144. // should be implemented by the component
  2145. return false;
  2146. };
  2147. /**
  2148. * Reflow the component
  2149. * @return {Boolean} resized
  2150. */
  2151. Component.prototype.reflow = function () {
  2152. // should be implemented by the component
  2153. return false;
  2154. };
  2155. /**
  2156. * Request a repaint. The controller will schedule a repaint
  2157. */
  2158. Component.prototype.requestRepaint = function () {
  2159. if (this.controller) {
  2160. this.controller.requestRepaint();
  2161. }
  2162. else {
  2163. throw new Error('Cannot request a repaint: no controller configured');
  2164. // TODO: just do a repaint when no parent is configured?
  2165. }
  2166. };
  2167. /**
  2168. * Request a reflow. The controller will schedule a reflow
  2169. */
  2170. Component.prototype.requestReflow = function () {
  2171. if (this.controller) {
  2172. this.controller.requestReflow();
  2173. }
  2174. else {
  2175. throw new Error('Cannot request a reflow: no controller configured');
  2176. // TODO: just do a reflow when no parent is configured?
  2177. }
  2178. };
  2179. /**
  2180. * Event handler
  2181. * @param {String} event name of the event, for example 'click', 'mousemove'
  2182. * @param {function} callback callback handler, invoked with the raw HTML Event
  2183. * as parameter.
  2184. */
  2185. Component.prototype.on = function (event, callback) {
  2186. // TODO: rethink the way of event delegation
  2187. if (this.parent) {
  2188. this.parent.on(event, callback);
  2189. }
  2190. else {
  2191. throw new Error('Cannot attach event: no root panel found');
  2192. }
  2193. };
  2194. // exports
  2195. module.exports = exports = Component;
  2196. },{"./../util":8}],10:[function(require,module,exports){
  2197. var util = require('../util'),
  2198. Component = require('./component');
  2199. /**
  2200. * A panel can contain components
  2201. * @param {Component} [parent]
  2202. * @param {Component[]} [depends] Components on which this components depends
  2203. * (except for the parent)
  2204. * @param {Object} [options] Available parameters:
  2205. * {String | Number | function} [left]
  2206. * {String | Number | function} [top]
  2207. * {String | Number | function} [width]
  2208. * {String | Number | function} [height]
  2209. * {String | function} [className]
  2210. * @constructor Panel
  2211. * @extends Component
  2212. */
  2213. function Panel(parent, depends, options) {
  2214. this.id = util.randomUUID();
  2215. this.parent = parent;
  2216. this.depends = depends;
  2217. this.options = {};
  2218. this.setOptions(options);
  2219. }
  2220. Panel.prototype = new Component();
  2221. /**
  2222. * Get the container element of the panel, which can be used by a child to
  2223. * add its own widgets.
  2224. * @returns {HTMLElement} container
  2225. */
  2226. Panel.prototype.getContainer = function () {
  2227. return this.frame;
  2228. };
  2229. /**
  2230. * Repaint the component
  2231. * @return {Boolean} changed
  2232. */
  2233. Panel.prototype.repaint = function () {
  2234. var changed = 0,
  2235. update = util.updateProperty,
  2236. asSize = util.option.asSize,
  2237. options = this.options,
  2238. frame = this.frame;
  2239. if (!frame) {
  2240. frame = document.createElement('div');
  2241. frame.className = 'panel';
  2242. if (options.className) {
  2243. if (typeof options.className == 'function') {
  2244. util.addClassName(frame, String(options.className()));
  2245. }
  2246. else {
  2247. util.addClassName(frame, String(options.className));
  2248. }
  2249. }
  2250. this.frame = frame;
  2251. changed += 1;
  2252. }
  2253. if (!frame.parentNode) {
  2254. if (!this.parent) {
  2255. throw new Error('Cannot repaint panel: no parent attached');
  2256. }
  2257. var parentContainer = this.parent.getContainer();
  2258. if (!parentContainer) {
  2259. throw new Error('Cannot repaint panel: parent has no container element');
  2260. }
  2261. parentContainer.appendChild(frame);
  2262. changed += 1;
  2263. }
  2264. changed += update(frame.style, 'top', asSize(options.top, '0px'));
  2265. changed += update(frame.style, 'left', asSize(options.left, '0px'));
  2266. changed += update(frame.style, 'width', asSize(options.width, '100%'));
  2267. changed += update(frame.style, 'height', asSize(options.height, '100%'));
  2268. return (changed > 0);
  2269. };
  2270. /**
  2271. * Reflow the component
  2272. * @return {Boolean} resized
  2273. */
  2274. Panel.prototype.reflow = function () {
  2275. var changed = 0,
  2276. update = util.updateProperty,
  2277. frame = this.frame;
  2278. if (frame) {
  2279. changed += update(this, 'top', frame.offsetTop);
  2280. changed += update(this, 'left', frame.offsetLeft);
  2281. changed += update(this, 'width', frame.offsetWidth);
  2282. changed += update(this, 'height', frame.offsetHeight);
  2283. }
  2284. else {
  2285. changed += 1;
  2286. }
  2287. return (changed > 0);
  2288. };
  2289. // exports
  2290. module.exports = exports = Panel;
  2291. },{"../util":8,"./component":9}],11:[function(require,module,exports){
  2292. var util = require('../util'),
  2293. Panel = require('./panel');
  2294. /**
  2295. * A root panel can hold components. The root panel must be initialized with
  2296. * a DOM element as container.
  2297. * @param {HTMLElement} container
  2298. * @param {Object} [options] Available parameters: see RootPanel.setOptions.
  2299. * @constructor RootPanel
  2300. * @extends Panel
  2301. */
  2302. function RootPanel(container, options) {
  2303. this.id = util.randomUUID();
  2304. this.container = container;
  2305. this.options = {
  2306. autoResize: true
  2307. };
  2308. this.listeners = {}; // event listeners
  2309. this.setOptions(options);
  2310. }
  2311. RootPanel.prototype = new Panel();
  2312. /**
  2313. * Set options. Will extend the current options.
  2314. * @param {Object} [options] Available parameters:
  2315. * {String | function} [className]
  2316. * {String | Number | function} [left]
  2317. * {String | Number | function} [top]
  2318. * {String | Number | function} [width]
  2319. * {String | Number | function} [height]
  2320. * {String | Number | function} [height]
  2321. * {Boolean | function} [autoResize]
  2322. */
  2323. RootPanel.prototype.setOptions = function (options) {
  2324. util.extend(this.options, options);
  2325. if (this.options.autoResize) {
  2326. this._watch();
  2327. }
  2328. else {
  2329. this._unwatch();
  2330. }
  2331. };
  2332. /**
  2333. * Repaint the component
  2334. * @return {Boolean} changed
  2335. */
  2336. RootPanel.prototype.repaint = function () {
  2337. var changed = 0,
  2338. update = util.updateProperty,
  2339. asSize = util.option.asSize,
  2340. options = this.options,
  2341. frame = this.frame;
  2342. if (!frame) {
  2343. frame = document.createElement('div');
  2344. frame.className = 'graph panel';
  2345. if (options.className) {
  2346. util.addClassName(frame, util.option.asString(options.className));
  2347. }
  2348. this.frame = frame;
  2349. changed += 1;
  2350. }
  2351. if (!frame.parentNode) {
  2352. if (!this.container) {
  2353. throw new Error('Cannot repaint root panel: no container attached');
  2354. }
  2355. this.container.appendChild(frame);
  2356. changed += 1;
  2357. }
  2358. changed += update(frame.style, 'top', asSize(options.top, '0px'));
  2359. changed += update(frame.style, 'left', asSize(options.left, '0px'));
  2360. changed += update(frame.style, 'width', asSize(options.width, '100%'));
  2361. changed += update(frame.style, 'height', asSize(options.height, '100%'));
  2362. this._updateEventEmitters();
  2363. return (changed > 0);
  2364. };
  2365. /**
  2366. * Reflow the component
  2367. * @return {Boolean} resized
  2368. */
  2369. RootPanel.prototype.reflow = function () {
  2370. var changed = 0,
  2371. update = util.updateProperty,
  2372. frame = this.frame;
  2373. if (frame) {
  2374. changed += update(this, 'top', frame.offsetTop);
  2375. changed += update(this, 'left', frame.offsetLeft);
  2376. changed += update(this, 'width', frame.offsetWidth);
  2377. changed += update(this, 'height', frame.offsetHeight);
  2378. }
  2379. else {
  2380. changed += 1;
  2381. }
  2382. return (changed > 0);
  2383. };
  2384. /**
  2385. * Watch for changes in the size of the frame. On resize, the Panel will
  2386. * automatically redraw itself.
  2387. * @private
  2388. */
  2389. RootPanel.prototype._watch = function () {
  2390. var me = this;
  2391. this._unwatch();
  2392. var checkSize = function () {
  2393. if (!me.options.autoResize) {
  2394. // stop watching when the option autoResize is changed to false
  2395. me._unwatch();
  2396. return;
  2397. }
  2398. if (me.frame) {
  2399. // check whether the frame is resized
  2400. if ((me.frame.clientWidth != me.width) ||
  2401. (me.frame.clientHeight != me.height)) {
  2402. me.requestReflow();
  2403. }
  2404. }
  2405. };
  2406. // TODO: automatically cleanup the event listener when the frame is deleted
  2407. util.addEventListener(window, 'resize', checkSize);
  2408. this.watchTimer = setInterval(checkSize, 1000);
  2409. };
  2410. /**
  2411. * Stop watching for a resize of the frame.
  2412. * @private
  2413. */
  2414. RootPanel.prototype._unwatch = function () {
  2415. if (this.watchTimer) {
  2416. clearInterval(this.watchTimer);
  2417. this.watchTimer = undefined;
  2418. }
  2419. // TODO: remove event listener on window.resize
  2420. };
  2421. /**
  2422. * Event handler
  2423. * @param {String} event name of the event, for example 'click', 'mousemove'
  2424. * @param {function} callback callback handler, invoked with the raw HTML Event
  2425. * as parameter.
  2426. */
  2427. RootPanel.prototype.on = function (event, callback) {
  2428. // register the listener at this component
  2429. var arr = this.listeners[event];
  2430. if (!arr) {
  2431. arr = [];
  2432. this.listeners[event] = arr;
  2433. }
  2434. arr.push(callback);
  2435. this._updateEventEmitters();
  2436. };
  2437. /**
  2438. * Update the event listeners for all event emitters
  2439. * @private
  2440. */
  2441. RootPanel.prototype._updateEventEmitters = function () {
  2442. if (this.listeners) {
  2443. var me = this;
  2444. util.forEach(this.listeners, function (listeners, event) {
  2445. if (!me.emitters) {
  2446. me.emitters = {};
  2447. }
  2448. if (!(event in me.emitters)) {
  2449. // create event
  2450. var frame = me.frame;
  2451. if (frame) {
  2452. //console.log('Created a listener for event ' + event + ' on component ' + me.id); // TODO: cleanup logging
  2453. var callback = function(event) {
  2454. listeners.forEach(function (listener) {
  2455. // TODO: filter on event target!
  2456. listener(event);
  2457. });
  2458. };
  2459. me.emitters[event] = callback;
  2460. util.addEventListener(frame, event, callback);
  2461. }
  2462. }
  2463. });
  2464. // TODO: be able to delete event listeners
  2465. // TODO: be able to move event listeners to a parent when available
  2466. }
  2467. };
  2468. // exports
  2469. module.exports = exports = RootPanel;
  2470. },{"./panel":10,"../util":8}],12:[function(require,module,exports){
  2471. var util = require('../util'),
  2472. DataSet = require('../dataset'),
  2473. Panel = require('./panel'),
  2474. Stack = require('../stack'),
  2475. ItemBox = require('./item/itembox'),
  2476. ItemRange = require('./item/itemrange'),
  2477. ItemPoint = require('./item/itempoint');
  2478. /**
  2479. * An ItemSet holds a set of items and ranges which can be displayed in a
  2480. * range. The width is determined by the parent of the ItemSet, and the height
  2481. * is determined by the size of the items.
  2482. * @param {Component} parent
  2483. * @param {Component[]} [depends] Components on which this components depends
  2484. * (except for the parent)
  2485. * @param {Object} [options] See ItemSet.setOptions for the available
  2486. * options.
  2487. * @constructor ItemSet
  2488. * @extends Panel
  2489. */
  2490. function ItemSet(parent, depends, options) {
  2491. this.id = util.randomUUID();
  2492. this.parent = parent;
  2493. this.depends = depends;
  2494. // one options object is shared by this itemset and all its items
  2495. this.options = {
  2496. style: 'box',
  2497. align: 'center',
  2498. orientation: 'bottom',
  2499. margin: {
  2500. axis: 20,
  2501. item: 10
  2502. },
  2503. padding: 5
  2504. };
  2505. this.dom = {};
  2506. var me = this;
  2507. this.data = null; // DataSet
  2508. this.range = null; // Range or Object {start: number, end: number}
  2509. this.listeners = {
  2510. 'add': function (event, params) {
  2511. me._onAdd(params.items);
  2512. },
  2513. 'update': function (event, params) {
  2514. me._onUpdate(params.items);
  2515. },
  2516. 'remove': function (event, params) {
  2517. me._onRemove(params.items);
  2518. }
  2519. };
  2520. this.items = {};
  2521. this.queue = {}; // queue with items to be added/updated/removed
  2522. this.stack = new Stack(this);
  2523. this.conversion = null;
  2524. this.setOptions(options);
  2525. }
  2526. ItemSet.prototype = new Panel();
  2527. // available item types will be registered here
  2528. ItemSet.types = {
  2529. box: ItemBox,
  2530. range: ItemRange,
  2531. point: ItemPoint
  2532. };
  2533. /**
  2534. * Set options for the ItemSet. Existing options will be extended/overwritten.
  2535. * @param {Object} [options] The following options are available:
  2536. * {String | function} [className]
  2537. * class name for the itemset
  2538. * {String} [style]
  2539. * Default style for the items. Choose from 'box'
  2540. * (default), 'point', or 'range'. The default
  2541. * Style can be overwritten by individual items.
  2542. * {String} align
  2543. * Alignment for the items, only applicable for
  2544. * ItemBox. Choose 'center' (default), 'left', or
  2545. * 'right'.
  2546. * {String} orientation
  2547. * Orientation of the item set. Choose 'top' or
  2548. * 'bottom' (default).
  2549. * {Number} margin.axis
  2550. * Margin between the axis and the items in pixels.
  2551. * Default is 20.
  2552. * {Number} margin.item
  2553. * Margin between items in pixels. Default is 10.
  2554. * {Number} padding
  2555. * Padding of the contents of an item in pixels.
  2556. * Must correspond with the items css. Default is 5.
  2557. */
  2558. ItemSet.prototype.setOptions = function (options) {
  2559. util.extend(this.options, options);
  2560. // TODO: ItemSet should also attach event listeners for rangechange and rangechanged, like timeaxis
  2561. this.stack.setOptions(this.options);
  2562. };
  2563. /**
  2564. * Set range (start and end).
  2565. * @param {Range | Object} range A Range or an object containing start and end.
  2566. */
  2567. ItemSet.prototype.setRange = function (range) {
  2568. if (!(range instanceof Range) && (!range || !range.start || !range.end)) {
  2569. throw new TypeError('Range must be an instance of Range, ' +
  2570. 'or an object containing start and end.');
  2571. }
  2572. this.range = range;
  2573. };
  2574. /**
  2575. * Repaint the component
  2576. * @return {Boolean} changed
  2577. */
  2578. ItemSet.prototype.repaint = function () {
  2579. var changed = 0,
  2580. update = util.updateProperty,
  2581. asSize = util.option.asSize,
  2582. options = this.options,
  2583. frame = this.frame;
  2584. if (!frame) {
  2585. frame = document.createElement('div');
  2586. frame.className = 'itemset';
  2587. if (options.className) {
  2588. util.addClassName(frame, util.option.asString(options.className));
  2589. }
  2590. // create background panel
  2591. var background = document.createElement('div');
  2592. background.className = 'background';
  2593. frame.appendChild(background);
  2594. this.dom.background = background;
  2595. // create foreground panel
  2596. var foreground = document.createElement('div');
  2597. foreground.className = 'foreground';
  2598. frame.appendChild(foreground);
  2599. this.dom.foreground = foreground;
  2600. // create axis panel
  2601. var axis = document.createElement('div');
  2602. axis.className = 'itemset-axis';
  2603. //frame.appendChild(axis);
  2604. this.dom.axis = axis;
  2605. this.frame = frame;
  2606. changed += 1;
  2607. }
  2608. if (!this.parent) {
  2609. throw new Error('Cannot repaint itemset: no parent attached');
  2610. }
  2611. var parentContainer = this.parent.getContainer();
  2612. if (!parentContainer) {
  2613. throw new Error('Cannot repaint itemset: parent has no container element');
  2614. }
  2615. if (!frame.parentNode) {
  2616. parentContainer.appendChild(frame);
  2617. changed += 1;
  2618. }
  2619. if (!this.dom.axis.parentNode) {
  2620. parentContainer.appendChild(this.dom.axis);
  2621. changed += 1;
  2622. }
  2623. // reposition frame
  2624. changed += update(frame.style, 'height', asSize(options.height, this.height + 'px'));
  2625. changed += update(frame.style, 'top', asSize(options.top, '0px'));
  2626. changed += update(frame.style, 'left', asSize(options.left, '0px'));
  2627. changed += update(frame.style, 'width', asSize(options.width, '100%'));
  2628. // reposition axis
  2629. changed += update(this.dom.axis.style, 'top', asSize(options.top, '0px'));
  2630. this._updateConversion();
  2631. var me = this,
  2632. queue = this.queue,
  2633. data = this.data,
  2634. items = this.items,
  2635. dataOptions = {
  2636. fields: ['id', 'start', 'end', 'content', 'type']
  2637. };
  2638. // TODO: copy options from the itemset itself?
  2639. // TODO: make orientation dynamically changable for the items
  2640. // show/hide added/changed/removed items
  2641. Object.keys(queue).forEach(function (id) {
  2642. var entry = queue[id];
  2643. var item = entry.item;
  2644. //noinspection FallthroughInSwitchStatementJS
  2645. switch (entry.action) {
  2646. case 'add':
  2647. case 'update':
  2648. var itemData = data.get(id, dataOptions);
  2649. var type = itemData.type ||
  2650. (itemData.start && itemData.end && 'range') ||
  2651. 'box';
  2652. var constructor = ItemSet.types[type];
  2653. // TODO: how to handle items with invalid data? hide them and give a warning? or throw an error?
  2654. if (item) {
  2655. // update item
  2656. if (!constructor || !(item instanceof constructor)) {
  2657. // item type has changed, delete the item
  2658. item.visible = false;
  2659. changed += item.repaint();
  2660. item = null;
  2661. }
  2662. else {
  2663. item.data = itemData; // TODO: create a method item.setData ?
  2664. changed += item.repaint();
  2665. }
  2666. }
  2667. if (!item) {
  2668. // create item
  2669. if (constructor) {
  2670. item = new constructor(me, itemData, options);
  2671. changed += item.repaint();
  2672. }
  2673. else {
  2674. throw new TypeError('Unknown item type "' + type + '"');
  2675. }
  2676. }
  2677. // update lists
  2678. items[id] = item;
  2679. delete queue[id];
  2680. break;
  2681. case 'remove':
  2682. if (item) {
  2683. // TODO: remove dom of the item
  2684. item.visible = false;
  2685. changed += item.repaint();
  2686. }
  2687. // update lists
  2688. delete items[id];
  2689. delete queue[id];
  2690. break;
  2691. default:
  2692. console.log('Error: unknown action "' + entry.action + '"');
  2693. }
  2694. });
  2695. // reposition all items
  2696. util.forEach(this.items, function (item) {
  2697. item.reposition();
  2698. });
  2699. return (changed > 0);
  2700. };
  2701. /**
  2702. * Get the foreground container element
  2703. * @return {HTMLElement} foreground
  2704. */
  2705. ItemSet.prototype.getForeground = function () {
  2706. return this.dom.foreground;
  2707. };
  2708. /**
  2709. * Get the background container element
  2710. * @return {HTMLElement} background
  2711. */
  2712. ItemSet.prototype.getBackground = function () {
  2713. return this.dom.background;
  2714. };
  2715. /**
  2716. * Reflow the component
  2717. * @return {Boolean} resized
  2718. */
  2719. ItemSet.prototype.reflow = function () {
  2720. var changed = 0,
  2721. options = this.options,
  2722. update = util.updateProperty,
  2723. asNumber = util.option.asNumber,
  2724. frame = this.frame;
  2725. if (frame) {
  2726. this._updateConversion();
  2727. util.forEach(this.items, function (item) {
  2728. changed += item.reflow();
  2729. });
  2730. // TODO: stack.update should be triggered via an event, in stack itself
  2731. // TODO: only update the stack when there are changed items
  2732. this.stack.update();
  2733. var maxHeight = asNumber(options.maxHeight);
  2734. var height;
  2735. if (options.height != null) {
  2736. height = frame.offsetHeight;
  2737. if (maxHeight != null) {
  2738. height = Math.min(height, maxHeight);
  2739. }
  2740. changed += update(this, 'height', height);
  2741. }
  2742. else {
  2743. // height is not specified, determine the height from the height and positioned items
  2744. var frameHeight = this.height;
  2745. height = 0;
  2746. if (options.orientation == 'top') {
  2747. util.forEach(this.items, function (item) {
  2748. height = Math.max(height, item.top + item.height);
  2749. });
  2750. }
  2751. else {
  2752. // orientation == 'bottom'
  2753. util.forEach(this.items, function (item) {
  2754. height = Math.max(height, frameHeight - item.top);
  2755. });
  2756. }
  2757. height += options.margin.axis;
  2758. if (maxHeight != null) {
  2759. height = Math.min(height, maxHeight);
  2760. }
  2761. changed += update(this, 'height', height);
  2762. }
  2763. // calculate height from items
  2764. changed += update(this, 'top', frame.offsetTop);
  2765. changed += update(this, 'left', frame.offsetLeft);
  2766. changed += update(this, 'width', frame.offsetWidth);
  2767. }
  2768. else {
  2769. changed += 1;
  2770. }
  2771. return (changed > 0);
  2772. };
  2773. /**
  2774. * Set data
  2775. * @param {DataSet | Array | DataTable} data
  2776. */
  2777. ItemSet.prototype.setData = function(data) {
  2778. // unsubscribe from current dataset
  2779. var current = this.data;
  2780. if (current) {
  2781. util.forEach(this.listeners, function (callback, event) {
  2782. current.unsubscribe(event, callback);
  2783. });
  2784. }
  2785. if (data instanceof DataSet) {
  2786. this.data = data;
  2787. }
  2788. else {
  2789. this.data = new DataSet({
  2790. fieldTypes: {
  2791. start: 'Date',
  2792. end: 'Date'
  2793. }
  2794. });
  2795. this.data.add(data);
  2796. }
  2797. var id = this.id;
  2798. var me = this;
  2799. util.forEach(this.listeners, function (callback, event) {
  2800. me.data.subscribe(event, callback, id);
  2801. });
  2802. var dataItems = this.data.get({filter: ['id']});
  2803. var ids = [];
  2804. util.forEach(dataItems, function (dataItem, index) {
  2805. ids[index] = dataItem.id;
  2806. });
  2807. this._onAdd(ids);
  2808. };
  2809. /**
  2810. * Get the data range of the item set.
  2811. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  2812. * When no minimum is found, min==null
  2813. * When no maximum is found, max==null
  2814. */
  2815. ItemSet.prototype.getDataRange = function () {
  2816. // calculate min from start filed
  2817. var data = this.data;
  2818. var min = data.min('start');
  2819. min = min ? min.start.valueOf() : null;
  2820. // calculate max of both start and end fields
  2821. var maxStart = data.max('start');
  2822. var maxEnd = data.max('end');
  2823. maxStart = maxStart ? maxStart.start.valueOf() : null;
  2824. maxEnd = maxEnd ? maxEnd.end.valueOf() : null;
  2825. var max = Math.max(maxStart, maxEnd);
  2826. return {
  2827. min: new Date(min),
  2828. max: new Date(max)
  2829. };
  2830. };
  2831. /**
  2832. * Handle updated items
  2833. * @param {Number[]} ids
  2834. * @private
  2835. */
  2836. ItemSet.prototype._onUpdate = function(ids) {
  2837. this._toQueue(ids, 'update');
  2838. };
  2839. /**
  2840. * Handle changed items
  2841. * @param {Number[]} ids
  2842. * @private
  2843. */
  2844. ItemSet.prototype._onAdd = function(ids) {
  2845. this._toQueue(ids, 'add');
  2846. };
  2847. /**
  2848. * Handle removed items
  2849. * @param {Number[]} ids
  2850. * @private
  2851. */
  2852. ItemSet.prototype._onRemove = function(ids) {
  2853. this._toQueue(ids, 'remove');
  2854. };
  2855. /**
  2856. * Put items in the queue to be added/updated/remove
  2857. * @param {Number[]} ids
  2858. * @param {String} action can be 'add', 'update', 'remove'
  2859. */
  2860. ItemSet.prototype._toQueue = function (ids, action) {
  2861. var items = this.items;
  2862. var queue = this.queue;
  2863. ids.forEach(function (id) {
  2864. var entry = queue[id];
  2865. if (entry) {
  2866. // already queued, update the action of the entry
  2867. entry.action = action;
  2868. }
  2869. else {
  2870. // not yet queued, add an entry to the queue
  2871. queue[id] = {
  2872. item: items[id] || null,
  2873. action: action
  2874. };
  2875. }
  2876. });
  2877. if (this.controller) {
  2878. //this.requestReflow();
  2879. this.requestRepaint();
  2880. }
  2881. };
  2882. /**
  2883. * Calculate the factor and offset to convert a position on screen to the
  2884. * corresponding date and vice versa.
  2885. * After the method _updateConversion is executed once, the methods toTime
  2886. * and toScreen can be used.
  2887. * @private
  2888. */
  2889. ItemSet.prototype._updateConversion = function() {
  2890. var range = this.range;
  2891. if (!range) {
  2892. throw new Error('No range configured');
  2893. }
  2894. if (range.conversion) {
  2895. this.conversion = range.conversion(this.width);
  2896. }
  2897. else {
  2898. this.conversion = Range.conversion(range.start, range.end, this.width);
  2899. }
  2900. };
  2901. /**
  2902. * Convert a position on screen (pixels) to a datetime
  2903. * Before this method can be used, the method _updateConversion must be
  2904. * executed once.
  2905. * @param {int} x Position on the screen in pixels
  2906. * @return {Date} time The datetime the corresponds with given position x
  2907. */
  2908. ItemSet.prototype.toTime = function(x) {
  2909. var conversion = this.conversion;
  2910. return new Date(x / conversion.factor + conversion.offset);
  2911. };
  2912. /**
  2913. * Convert a datetime (Date object) into a position on the screen
  2914. * Before this method can be used, the method _updateConversion must be
  2915. * executed once.
  2916. * @param {Date} time A date
  2917. * @return {int} x The position on the screen in pixels which corresponds
  2918. * with the given date.
  2919. */
  2920. ItemSet.prototype.toScreen = function(time) {
  2921. var conversion = this.conversion;
  2922. return (time.valueOf() - conversion.offset) * conversion.factor;
  2923. };
  2924. // exports
  2925. module.exports = exports = ItemSet;
  2926. },{"../util":8,"../dataset":3,"./panel":10,"../stack":6,"./item/itembox":15,"./item/itemrange":16,"./item/itempoint":17}],13:[function(require,module,exports){
  2927. var util = require('../util'),
  2928. TimeStep = require('../timestep'),
  2929. Component = require('./component');
  2930. /**
  2931. * A horizontal time axis
  2932. * @param {Component} parent
  2933. * @param {Component[]} [depends] Components on which this components depends
  2934. * (except for the parent)
  2935. * @param {Object} [options] See TimeAxis.setOptions for the available
  2936. * options.
  2937. * @constructor TimeAxis
  2938. * @extends Component
  2939. */
  2940. function TimeAxis (parent, depends, options) {
  2941. this.id = util.randomUUID();
  2942. this.parent = parent;
  2943. this.depends = depends;
  2944. this.dom = {
  2945. majorLines: [],
  2946. majorTexts: [],
  2947. minorLines: [],
  2948. minorTexts: [],
  2949. redundant: {
  2950. majorLines: [],
  2951. majorTexts: [],
  2952. minorLines: [],
  2953. minorTexts: []
  2954. }
  2955. };
  2956. this.props = {
  2957. range: {
  2958. start: 0,
  2959. end: 0,
  2960. minimumStep: 0
  2961. },
  2962. lineTop: 0
  2963. };
  2964. this.options = {
  2965. orientation: 'bottom', // supported: 'top', 'bottom'
  2966. // TODO: implement timeaxis orientations 'left' and 'right'
  2967. showMinorLabels: true,
  2968. showMajorLabels: true
  2969. };
  2970. this.conversion = null;
  2971. this.range = null;
  2972. this.setOptions(options);
  2973. }
  2974. TimeAxis.prototype = new Component();
  2975. // TODO: comment options
  2976. TimeAxis.prototype.setOptions = function (options) {
  2977. util.extend(this.options, options);
  2978. };
  2979. /**
  2980. * Set a range (start and end)
  2981. * @param {Range | Object} range A Range or an object containing start and end.
  2982. */
  2983. TimeAxis.prototype.setRange = function (range) {
  2984. if (!(range instanceof Range) && (!range || !range.start || !range.end)) {
  2985. throw new TypeError('Range must be an instance of Range, ' +
  2986. 'or an object containing start and end.');
  2987. }
  2988. this.range = range;
  2989. };
  2990. /**
  2991. * Convert a position on screen (pixels) to a datetime
  2992. * @param {int} x Position on the screen in pixels
  2993. * @return {Date} time The datetime the corresponds with given position x
  2994. */
  2995. TimeAxis.prototype.toTime = function(x) {
  2996. var conversion = this.conversion;
  2997. return new Date(x / conversion.factor + conversion.offset);
  2998. };
  2999. /**
  3000. * Convert a datetime (Date object) into a position on the screen
  3001. * @param {Date} time A date
  3002. * @return {int} x The position on the screen in pixels which corresponds
  3003. * with the given date.
  3004. * @private
  3005. */
  3006. TimeAxis.prototype.toScreen = function(time) {
  3007. var conversion = this.conversion;
  3008. return (time.valueOf() - conversion.offset) * conversion.factor;
  3009. };
  3010. /**
  3011. * Repaint the component
  3012. * @return {Boolean} changed
  3013. */
  3014. TimeAxis.prototype.repaint = function () {
  3015. var changed = 0,
  3016. update = util.updateProperty,
  3017. asSize = util.option.asSize,
  3018. options = this.options,
  3019. props = this.props,
  3020. step = this.step;
  3021. var frame = this.frame;
  3022. if (!frame) {
  3023. frame = document.createElement('div');
  3024. this.frame = frame;
  3025. changed += 1;
  3026. }
  3027. frame.className = 'axis ' + options.orientation;
  3028. // TODO: custom className?
  3029. if (!frame.parentNode) {
  3030. if (!this.parent) {
  3031. throw new Error('Cannot repaint time axis: no parent attached');
  3032. }
  3033. var parentContainer = this.parent.getContainer();
  3034. if (!parentContainer) {
  3035. throw new Error('Cannot repaint time axis: parent has no container element');
  3036. }
  3037. parentContainer.appendChild(frame);
  3038. changed += 1;
  3039. }
  3040. var parent = frame.parentNode;
  3041. if (parent) {
  3042. var beforeChild = frame.nextSibling;
  3043. parent.removeChild(frame); // take frame offline while updating (is almost twice as fast)
  3044. var orientation = options.orientation;
  3045. var defaultTop = (orientation == 'bottom' && this.props.parentHeight && this.height) ?
  3046. (this.props.parentHeight - this.height) + 'px' :
  3047. '0px';
  3048. changed += update(frame.style, 'top', asSize(options.top, defaultTop));
  3049. changed += update(frame.style, 'left', asSize(options.left, '0px'));
  3050. changed += update(frame.style, 'width', asSize(options.width, '100%'));
  3051. changed += update(frame.style, 'height', asSize(options.height, this.height + 'px'));
  3052. // get characters width and height
  3053. this._repaintMeasureChars();
  3054. if (this.step) {
  3055. this._repaintStart();
  3056. step.first();
  3057. var xFirstMajorLabel = undefined;
  3058. var max = 0;
  3059. while (step.hasNext() && max < 1000) {
  3060. max++;
  3061. var cur = step.getCurrent(),
  3062. x = this.toScreen(cur),
  3063. isMajor = step.isMajor();
  3064. // TODO: lines must have a width, such that we can create css backgrounds
  3065. if (options.showMinorLabels) {
  3066. this._repaintMinorText(x, step.getLabelMinor());
  3067. }
  3068. if (isMajor && options.showMajorLabels) {
  3069. if (x > 0) {
  3070. if (xFirstMajorLabel == undefined) {
  3071. xFirstMajorLabel = x;
  3072. }
  3073. this._repaintMajorText(x, step.getLabelMajor());
  3074. }
  3075. this._repaintMajorLine(x);
  3076. }
  3077. else {
  3078. this._repaintMinorLine(x);
  3079. }
  3080. step.next();
  3081. }
  3082. // create a major label on the left when needed
  3083. if (options.showMajorLabels) {
  3084. var leftTime = this.toTime(0),
  3085. leftText = step.getLabelMajor(leftTime),
  3086. widthText = leftText.length * (props.majorCharWidth || 10) + 10; // upper bound estimation
  3087. if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) {
  3088. this._repaintMajorText(0, leftText);
  3089. }
  3090. }
  3091. this._repaintEnd();
  3092. }
  3093. this._repaintLine();
  3094. // put frame online again
  3095. if (beforeChild) {
  3096. parent.insertBefore(frame, beforeChild);
  3097. }
  3098. else {
  3099. parent.appendChild(frame)
  3100. }
  3101. }
  3102. return (changed > 0);
  3103. };
  3104. /**
  3105. * Start a repaint. Move all DOM elements to a redundant list, where they
  3106. * can be picked for re-use, or can be cleaned up in the end
  3107. * @private
  3108. */
  3109. TimeAxis.prototype._repaintStart = function () {
  3110. var dom = this.dom,
  3111. redundant = dom.redundant;
  3112. redundant.majorLines = dom.majorLines;
  3113. redundant.majorTexts = dom.majorTexts;
  3114. redundant.minorLines = dom.minorLines;
  3115. redundant.minorTexts = dom.minorTexts;
  3116. dom.majorLines = [];
  3117. dom.majorTexts = [];
  3118. dom.minorLines = [];
  3119. dom.minorTexts = [];
  3120. };
  3121. /**
  3122. * End a repaint. Cleanup leftover DOM elements in the redundant list
  3123. * @private
  3124. */
  3125. TimeAxis.prototype._repaintEnd = function () {
  3126. util.forEach(this.dom.redundant, function (arr) {
  3127. while (arr.length) {
  3128. var elem = arr.pop();
  3129. if (elem && elem.parentNode) {
  3130. elem.parentNode.removeChild(elem);
  3131. }
  3132. }
  3133. });
  3134. };
  3135. /**
  3136. * Create a minor label for the axis at position x
  3137. * @param {Number} x
  3138. * @param {String} text
  3139. * @private
  3140. */
  3141. TimeAxis.prototype._repaintMinorText = function (x, text) {
  3142. // reuse redundant label
  3143. var label = this.dom.redundant.minorTexts.shift();
  3144. if (!label) {
  3145. // create new label
  3146. var content = document.createTextNode('');
  3147. label = document.createElement('div');
  3148. label.appendChild(content);
  3149. label.className = 'text minor';
  3150. this.frame.appendChild(label);
  3151. }
  3152. this.dom.minorTexts.push(label);
  3153. label.childNodes[0].nodeValue = text;
  3154. label.style.left = x + 'px';
  3155. label.style.top = this.props.minorLabelTop + 'px';
  3156. //label.title = title; // TODO: this is a heavy operation
  3157. };
  3158. /**
  3159. * Create a Major label for the axis at position x
  3160. * @param {Number} x
  3161. * @param {String} text
  3162. * @private
  3163. */
  3164. TimeAxis.prototype._repaintMajorText = function (x, text) {
  3165. // reuse redundant label
  3166. var label = this.dom.redundant.majorTexts.shift();
  3167. if (!label) {
  3168. // create label
  3169. var content = document.createTextNode(text);
  3170. label = document.createElement('div');
  3171. label.className = 'text major';
  3172. label.appendChild(content);
  3173. this.frame.appendChild(label);
  3174. }
  3175. this.dom.majorTexts.push(label);
  3176. label.childNodes[0].nodeValue = text;
  3177. label.style.top = this.props.majorLabelTop + 'px';
  3178. label.style.left = x + 'px';
  3179. //label.title = title; // TODO: this is a heavy operation
  3180. };
  3181. /**
  3182. * Create a minor line for the axis at position x
  3183. * @param {Number} x
  3184. * @private
  3185. */
  3186. TimeAxis.prototype._repaintMinorLine = function (x) {
  3187. // reuse redundant line
  3188. var line = this.dom.redundant.minorLines.shift();
  3189. if (!line) {
  3190. // create vertical line
  3191. line = document.createElement('div');
  3192. line.className = 'grid vertical minor';
  3193. this.frame.appendChild(line);
  3194. }
  3195. this.dom.minorLines.push(line);
  3196. var props = this.props;
  3197. line.style.top = props.minorLineTop + 'px';
  3198. line.style.height = props.minorLineHeight + 'px';
  3199. line.style.left = (x - props.minorLineWidth / 2) + 'px';
  3200. };
  3201. /**
  3202. * Create a Major line for the axis at position x
  3203. * @param {Number} x
  3204. * @private
  3205. */
  3206. TimeAxis.prototype._repaintMajorLine = function (x) {
  3207. // reuse redundant line
  3208. var line = this.dom.redundant.majorLines.shift();
  3209. if (!line) {
  3210. // create vertical line
  3211. line = document.createElement('DIV');
  3212. line.className = 'grid vertical major';
  3213. this.frame.appendChild(line);
  3214. }
  3215. this.dom.majorLines.push(line);
  3216. var props = this.props;
  3217. line.style.top = props.majorLineTop + 'px';
  3218. line.style.left = (x - props.majorLineWidth / 2) + 'px';
  3219. line.style.height = props.majorLineHeight + 'px';
  3220. };
  3221. /**
  3222. * Repaint the horizontal line for the axis
  3223. * @private
  3224. */
  3225. TimeAxis.prototype._repaintLine = function() {
  3226. var line = this.dom.line,
  3227. frame = this.frame,
  3228. options = this.options;
  3229. // line before all axis elements
  3230. if (options.showMinorLabels || options.showMajorLabels) {
  3231. if (line) {
  3232. // put this line at the end of all childs
  3233. frame.removeChild(line);
  3234. frame.appendChild(line);
  3235. }
  3236. else {
  3237. // create the axis line
  3238. line = document.createElement('div');
  3239. line.className = 'grid horizontal major';
  3240. frame.appendChild(line);
  3241. this.dom.line = line;
  3242. }
  3243. line.style.top = this.props.lineTop + 'px';
  3244. }
  3245. else {
  3246. if (line && axis.parentElement) {
  3247. frame.removeChild(axis.line);
  3248. delete this.dom.line;
  3249. }
  3250. }
  3251. };
  3252. /**
  3253. * Create characters used to determine the size of text on the axis
  3254. * @private
  3255. */
  3256. TimeAxis.prototype._repaintMeasureChars = function () {
  3257. // calculate the width and height of a single character
  3258. // this is used to calculate the step size, and also the positioning of the
  3259. // axis
  3260. var dom = this.dom,
  3261. text;
  3262. if (!dom.characterMinor) {
  3263. text = document.createTextNode('0');
  3264. var measureCharMinor = document.createElement('DIV');
  3265. measureCharMinor.className = 'text minor measure';
  3266. measureCharMinor.appendChild(text);
  3267. this.frame.appendChild(measureCharMinor);
  3268. dom.measureCharMinor = measureCharMinor;
  3269. }
  3270. if (!dom.characterMajor) {
  3271. text = document.createTextNode('0');
  3272. var measureCharMajor = document.createElement('DIV');
  3273. measureCharMajor.className = 'text major measure';
  3274. measureCharMajor.appendChild(text);
  3275. this.frame.appendChild(measureCharMajor);
  3276. dom.measureCharMajor = measureCharMajor;
  3277. }
  3278. };
  3279. /**
  3280. * Reflow the component
  3281. * @return {Boolean} resized
  3282. */
  3283. TimeAxis.prototype.reflow = function () {
  3284. var changed = 0,
  3285. update = util.updateProperty,
  3286. frame = this.frame,
  3287. range = this.range;
  3288. if (!range) {
  3289. throw new Error('Cannot repaint time axis: no range configured');
  3290. }
  3291. if (frame) {
  3292. changed += update(this, 'top', frame.offsetTop);
  3293. changed += update(this, 'left', frame.offsetLeft);
  3294. // calculate size of a character
  3295. var props = this.props,
  3296. showMinorLabels = this.options.showMinorLabels,
  3297. showMajorLabels = this.options.showMajorLabels,
  3298. measureCharMinor = this.dom.measureCharMinor,
  3299. measureCharMajor = this.dom.measureCharMajor;
  3300. if (measureCharMinor) {
  3301. props.minorCharHeight = measureCharMinor.clientHeight;
  3302. props.minorCharWidth = measureCharMinor.clientWidth;
  3303. }
  3304. if (measureCharMajor) {
  3305. props.majorCharHeight = measureCharMajor.clientHeight;
  3306. props.majorCharWidth = measureCharMajor.clientWidth;
  3307. }
  3308. var parentHeight = frame.parentNode ? frame.parentNode.offsetHeight : 0;
  3309. if (parentHeight != props.parentHeight) {
  3310. props.parentHeight = parentHeight;
  3311. changed += 1;
  3312. }
  3313. switch (this.options.orientation) {
  3314. case 'bottom':
  3315. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  3316. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  3317. props.minorLabelTop = 0;
  3318. props.majorLabelTop = props.minorLabelTop + props.minorLabelHeight;
  3319. props.minorLineTop = -this.top;
  3320. props.minorLineHeight = Math.max(this.top + props.majorLabelHeight, 0);
  3321. props.minorLineWidth = 1; // TODO: really calculate width
  3322. props.majorLineTop = -this.top;
  3323. props.majorLineHeight = Math.max(this.top + props.minorLabelHeight + props.majorLabelHeight, 0);
  3324. props.majorLineWidth = 1; // TODO: really calculate width
  3325. props.lineTop = 0;
  3326. break;
  3327. case 'top':
  3328. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  3329. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  3330. props.majorLabelTop = 0;
  3331. props.minorLabelTop = props.majorLabelTop + props.majorLabelHeight;
  3332. props.minorLineTop = props.minorLabelTop;
  3333. props.minorLineHeight = Math.max(parentHeight - props.majorLabelHeight - this.top);
  3334. props.minorLineWidth = 1; // TODO: really calculate width
  3335. props.majorLineTop = 0;
  3336. props.majorLineHeight = Math.max(parentHeight - this.top);
  3337. props.majorLineWidth = 1; // TODO: really calculate width
  3338. props.lineTop = props.majorLabelHeight + props.minorLabelHeight;
  3339. break;
  3340. default:
  3341. throw new Error('Unkown orientation "' + this.options.orientation + '"');
  3342. }
  3343. var height = props.minorLabelHeight + props.majorLabelHeight;
  3344. changed += update(this, 'width', frame.offsetWidth);
  3345. changed += update(this, 'height', height);
  3346. // calculate range and step
  3347. this._updateConversion();
  3348. var start = util.cast(range.start, 'Date'),
  3349. end = util.cast(range.end, 'Date'),
  3350. minimumStep = this.toTime((props.minorCharWidth || 10) * 5) - this.toTime(0);
  3351. this.step = new TimeStep(start, end, minimumStep);
  3352. changed += update(props.range, 'start', start.valueOf());
  3353. changed += update(props.range, 'end', end.valueOf());
  3354. changed += update(props.range, 'minimumStep', minimumStep.valueOf());
  3355. }
  3356. return (changed > 0);
  3357. };
  3358. /**
  3359. * Calculate the factor and offset to convert a position on screen to the
  3360. * corresponding date and vice versa.
  3361. * After the method _updateConversion is executed once, the methods toTime
  3362. * and toScreen can be used.
  3363. * @private
  3364. */
  3365. TimeAxis.prototype._updateConversion = function() {
  3366. var range = this.range;
  3367. if (!range) {
  3368. throw new Error('No range configured');
  3369. }
  3370. if (range.conversion) {
  3371. this.conversion = range.conversion(this.width);
  3372. }
  3373. else {
  3374. this.conversion = Range.conversion(range.start, range.end, this.width);
  3375. }
  3376. };
  3377. // exports
  3378. module.exports = exports = TimeAxis;
  3379. },{"../util":8,"../timestep":7,"./component":9}],7:[function(require,module,exports){
  3380. var util = require('./util'),
  3381. moment = require('moment');
  3382. /**
  3383. * @constructor TimeStep
  3384. * The class TimeStep is an iterator for dates. You provide a start date and an
  3385. * end date. The class itself determines the best scale (step size) based on the
  3386. * provided start Date, end Date, and minimumStep.
  3387. *
  3388. * If minimumStep is provided, the step size is chosen as close as possible
  3389. * to the minimumStep but larger than minimumStep. If minimumStep is not
  3390. * provided, the scale is set to 1 DAY.
  3391. * The minimumStep should correspond with the onscreen size of about 6 characters
  3392. *
  3393. * Alternatively, you can set a scale by hand.
  3394. * After creation, you can initialize the class by executing first(). Then you
  3395. * can iterate from the start date to the end date via next(). You can check if
  3396. * the end date is reached with the function hasNext(). After each step, you can
  3397. * retrieve the current date via getCurrent().
  3398. * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours,
  3399. * days, to years.
  3400. *
  3401. * Version: 1.2
  3402. *
  3403. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  3404. * or new Date(2010, 9, 21, 23, 45, 00)
  3405. * @param {Date} [end] The end date
  3406. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  3407. */
  3408. TimeStep = function(start, end, minimumStep) {
  3409. // variables
  3410. this.current = new Date();
  3411. this._start = new Date();
  3412. this._end = new Date();
  3413. this.autoScale = true;
  3414. this.scale = TimeStep.SCALE.DAY;
  3415. this.step = 1;
  3416. // initialize the range
  3417. this.setRange(start, end, minimumStep);
  3418. };
  3419. /// enum scale
  3420. TimeStep.SCALE = {
  3421. MILLISECOND: 1,
  3422. SECOND: 2,
  3423. MINUTE: 3,
  3424. HOUR: 4,
  3425. DAY: 5,
  3426. WEEKDAY: 6,
  3427. MONTH: 7,
  3428. YEAR: 8
  3429. };
  3430. /**
  3431. * Set a new range
  3432. * If minimumStep is provided, the step size is chosen as close as possible
  3433. * to the minimumStep but larger than minimumStep. If minimumStep is not
  3434. * provided, the scale is set to 1 DAY.
  3435. * The minimumStep should correspond with the onscreen size of about 6 characters
  3436. * @param {Date} start The start date and time.
  3437. * @param {Date} end The end date and time.
  3438. * @param {int} [minimumStep] Optional. Minimum step size in milliseconds
  3439. */
  3440. TimeStep.prototype.setRange = function(start, end, minimumStep) {
  3441. if (!(start instanceof Date) || !(end instanceof Date)) {
  3442. //throw "No legal start or end date in method setRange";
  3443. return;
  3444. }
  3445. this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
  3446. this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
  3447. if (this.autoScale) {
  3448. this.setMinimumStep(minimumStep);
  3449. }
  3450. };
  3451. /**
  3452. * Set the range iterator to the start date.
  3453. */
  3454. TimeStep.prototype.first = function() {
  3455. this.current = new Date(this._start.valueOf());
  3456. this.roundToMinor();
  3457. };
  3458. /**
  3459. * Round the current date to the first minor date value
  3460. * This must be executed once when the current date is set to start Date
  3461. */
  3462. TimeStep.prototype.roundToMinor = function() {
  3463. // round to floor
  3464. // IMPORTANT: we have no breaks in this switch! (this is no bug)
  3465. //noinspection FallthroughInSwitchStatementJS
  3466. switch (this.scale) {
  3467. case TimeStep.SCALE.YEAR:
  3468. this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
  3469. this.current.setMonth(0);
  3470. case TimeStep.SCALE.MONTH: this.current.setDate(1);
  3471. case TimeStep.SCALE.DAY: // intentional fall through
  3472. case TimeStep.SCALE.WEEKDAY: this.current.setHours(0);
  3473. case TimeStep.SCALE.HOUR: this.current.setMinutes(0);
  3474. case TimeStep.SCALE.MINUTE: this.current.setSeconds(0);
  3475. case TimeStep.SCALE.SECOND: this.current.setMilliseconds(0);
  3476. //case TimeStep.SCALE.MILLISECOND: // nothing to do for milliseconds
  3477. }
  3478. if (this.step != 1) {
  3479. // round down to the first minor value that is a multiple of the current step size
  3480. switch (this.scale) {
  3481. case TimeStep.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
  3482. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
  3483. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
  3484. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
  3485. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  3486. case TimeStep.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
  3487. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
  3488. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
  3489. default: break;
  3490. }
  3491. }
  3492. };
  3493. /**
  3494. * Check if the there is a next step
  3495. * @return {boolean} true if the current date has not passed the end date
  3496. */
  3497. TimeStep.prototype.hasNext = function () {
  3498. return (this.current.valueOf() <= this._end.valueOf());
  3499. };
  3500. /**
  3501. * Do the next step
  3502. */
  3503. TimeStep.prototype.next = function() {
  3504. var prev = this.current.valueOf();
  3505. // Two cases, needed to prevent issues with switching daylight savings
  3506. // (end of March and end of October)
  3507. if (this.current.getMonth() < 6) {
  3508. switch (this.scale) {
  3509. case TimeStep.SCALE.MILLISECOND:
  3510. this.current = new Date(this.current.valueOf() + this.step); break;
  3511. case TimeStep.SCALE.SECOND: this.current = new Date(this.current.valueOf() + this.step * 1000); break;
  3512. case TimeStep.SCALE.MINUTE: this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
  3513. case TimeStep.SCALE.HOUR:
  3514. this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
  3515. // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
  3516. var h = this.current.getHours();
  3517. this.current.setHours(h - (h % this.step));
  3518. break;
  3519. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  3520. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  3521. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  3522. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  3523. default: break;
  3524. }
  3525. }
  3526. else {
  3527. switch (this.scale) {
  3528. case TimeStep.SCALE.MILLISECOND: this.current = new Date(this.current.valueOf() + this.step); break;
  3529. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break;
  3530. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break;
  3531. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break;
  3532. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  3533. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  3534. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  3535. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  3536. default: break;
  3537. }
  3538. }
  3539. if (this.step != 1) {
  3540. // round down to the correct major value
  3541. switch (this.scale) {
  3542. case TimeStep.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
  3543. case TimeStep.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
  3544. case TimeStep.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
  3545. case TimeStep.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break;
  3546. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  3547. case TimeStep.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
  3548. case TimeStep.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
  3549. case TimeStep.SCALE.YEAR: break; // nothing to do for year
  3550. default: break;
  3551. }
  3552. }
  3553. // safety mechanism: if current time is still unchanged, move to the end
  3554. if (this.current.valueOf() == prev) {
  3555. this.current = new Date(this._end.valueOf());
  3556. }
  3557. };
  3558. /**
  3559. * Get the current datetime
  3560. * @return {Date} current The current date
  3561. */
  3562. TimeStep.prototype.getCurrent = function() {
  3563. return this.current;
  3564. };
  3565. /**
  3566. * Set a custom scale. Autoscaling will be disabled.
  3567. * For example setScale(SCALE.MINUTES, 5) will result
  3568. * in minor steps of 5 minutes, and major steps of an hour.
  3569. *
  3570. * @param {TimeStep.SCALE} newScale
  3571. * A scale. Choose from SCALE.MILLISECOND,
  3572. * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR,
  3573. * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH,
  3574. * SCALE.YEAR.
  3575. * @param {Number} newStep A step size, by default 1. Choose for
  3576. * example 1, 2, 5, or 10.
  3577. */
  3578. TimeStep.prototype.setScale = function(newScale, newStep) {
  3579. this.scale = newScale;
  3580. if (newStep > 0) {
  3581. this.step = newStep;
  3582. }
  3583. this.autoScale = false;
  3584. };
  3585. /**
  3586. * Enable or disable autoscaling
  3587. * @param {boolean} enable If true, autoascaling is set true
  3588. */
  3589. TimeStep.prototype.setAutoScale = function (enable) {
  3590. this.autoScale = enable;
  3591. };
  3592. /**
  3593. * Automatically determine the scale that bests fits the provided minimum step
  3594. * @param {Number} minimumStep The minimum step size in milliseconds
  3595. */
  3596. TimeStep.prototype.setMinimumStep = function(minimumStep) {
  3597. if (minimumStep == undefined) {
  3598. return;
  3599. }
  3600. var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
  3601. var stepMonth = (1000 * 60 * 60 * 24 * 30);
  3602. var stepDay = (1000 * 60 * 60 * 24);
  3603. var stepHour = (1000 * 60 * 60);
  3604. var stepMinute = (1000 * 60);
  3605. var stepSecond = (1000);
  3606. var stepMillisecond= (1);
  3607. // find the smallest step that is larger than the provided minimumStep
  3608. if (stepYear*1000 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1000;}
  3609. if (stepYear*500 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 500;}
  3610. if (stepYear*100 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 100;}
  3611. if (stepYear*50 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 50;}
  3612. if (stepYear*10 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 10;}
  3613. if (stepYear*5 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 5;}
  3614. if (stepYear > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1;}
  3615. if (stepMonth*3 > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 3;}
  3616. if (stepMonth > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 1;}
  3617. if (stepDay*5 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 5;}
  3618. if (stepDay*2 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 2;}
  3619. if (stepDay > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 1;}
  3620. if (stepDay/2 > minimumStep) {this.scale = TimeStep.SCALE.WEEKDAY; this.step = 1;}
  3621. if (stepHour*4 > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 4;}
  3622. if (stepHour > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 1;}
  3623. if (stepMinute*15 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 15;}
  3624. if (stepMinute*10 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 10;}
  3625. if (stepMinute*5 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 5;}
  3626. if (stepMinute > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 1;}
  3627. if (stepSecond*15 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 15;}
  3628. if (stepSecond*10 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 10;}
  3629. if (stepSecond*5 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 5;}
  3630. if (stepSecond > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 1;}
  3631. if (stepMillisecond*200 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 200;}
  3632. if (stepMillisecond*100 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 100;}
  3633. if (stepMillisecond*50 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 50;}
  3634. if (stepMillisecond*10 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 10;}
  3635. if (stepMillisecond*5 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 5;}
  3636. if (stepMillisecond > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 1;}
  3637. };
  3638. /**
  3639. * Snap a date to a rounded value. The snap intervals are dependent on the
  3640. * current scale and step.
  3641. * @param {Date} date the date to be snapped
  3642. */
  3643. TimeStep.prototype.snap = function(date) {
  3644. if (this.scale == TimeStep.SCALE.YEAR) {
  3645. var year = date.getFullYear() + Math.round(date.getMonth() / 12);
  3646. date.setFullYear(Math.round(year / this.step) * this.step);
  3647. date.setMonth(0);
  3648. date.setDate(0);
  3649. date.setHours(0);
  3650. date.setMinutes(0);
  3651. date.setSeconds(0);
  3652. date.setMilliseconds(0);
  3653. }
  3654. else if (this.scale == TimeStep.SCALE.MONTH) {
  3655. if (date.getDate() > 15) {
  3656. date.setDate(1);
  3657. date.setMonth(date.getMonth() + 1);
  3658. // important: first set Date to 1, after that change the month.
  3659. }
  3660. else {
  3661. date.setDate(1);
  3662. }
  3663. date.setHours(0);
  3664. date.setMinutes(0);
  3665. date.setSeconds(0);
  3666. date.setMilliseconds(0);
  3667. }
  3668. else if (this.scale == TimeStep.SCALE.DAY ||
  3669. this.scale == TimeStep.SCALE.WEEKDAY) {
  3670. //noinspection FallthroughInSwitchStatementJS
  3671. switch (this.step) {
  3672. case 5:
  3673. case 2:
  3674. date.setHours(Math.round(date.getHours() / 24) * 24); break;
  3675. default:
  3676. date.setHours(Math.round(date.getHours() / 12) * 12); break;
  3677. }
  3678. date.setMinutes(0);
  3679. date.setSeconds(0);
  3680. date.setMilliseconds(0);
  3681. }
  3682. else if (this.scale == TimeStep.SCALE.HOUR) {
  3683. switch (this.step) {
  3684. case 4:
  3685. date.setMinutes(Math.round(date.getMinutes() / 60) * 60); break;
  3686. default:
  3687. date.setMinutes(Math.round(date.getMinutes() / 30) * 30); break;
  3688. }
  3689. date.setSeconds(0);
  3690. date.setMilliseconds(0);
  3691. } else if (this.scale == TimeStep.SCALE.MINUTE) {
  3692. //noinspection FallthroughInSwitchStatementJS
  3693. switch (this.step) {
  3694. case 15:
  3695. case 10:
  3696. date.setMinutes(Math.round(date.getMinutes() / 5) * 5);
  3697. date.setSeconds(0);
  3698. break;
  3699. case 5:
  3700. date.setSeconds(Math.round(date.getSeconds() / 60) * 60); break;
  3701. default:
  3702. date.setSeconds(Math.round(date.getSeconds() / 30) * 30); break;
  3703. }
  3704. date.setMilliseconds(0);
  3705. }
  3706. else if (this.scale == TimeStep.SCALE.SECOND) {
  3707. //noinspection FallthroughInSwitchStatementJS
  3708. switch (this.step) {
  3709. case 15:
  3710. case 10:
  3711. date.setSeconds(Math.round(date.getSeconds() / 5) * 5);
  3712. date.setMilliseconds(0);
  3713. break;
  3714. case 5:
  3715. date.setMilliseconds(Math.round(date.getMilliseconds() / 1000) * 1000); break;
  3716. default:
  3717. date.setMilliseconds(Math.round(date.getMilliseconds() / 500) * 500); break;
  3718. }
  3719. }
  3720. else if (this.scale == TimeStep.SCALE.MILLISECOND) {
  3721. var step = this.step > 5 ? this.step / 2 : 1;
  3722. date.setMilliseconds(Math.round(date.getMilliseconds() / step) * step);
  3723. }
  3724. };
  3725. /**
  3726. * Check if the current value is a major value (for example when the step
  3727. * is DAY, a major value is each first day of the MONTH)
  3728. * @return {boolean} true if current date is major, else false.
  3729. */
  3730. TimeStep.prototype.isMajor = function() {
  3731. switch (this.scale) {
  3732. case TimeStep.SCALE.MILLISECOND:
  3733. return (this.current.getMilliseconds() == 0);
  3734. case TimeStep.SCALE.SECOND:
  3735. return (this.current.getSeconds() == 0);
  3736. case TimeStep.SCALE.MINUTE:
  3737. return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
  3738. // Note: this is no bug. Major label is equal for both minute and hour scale
  3739. case TimeStep.SCALE.HOUR:
  3740. return (this.current.getHours() == 0);
  3741. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  3742. case TimeStep.SCALE.DAY:
  3743. return (this.current.getDate() == 1);
  3744. case TimeStep.SCALE.MONTH:
  3745. return (this.current.getMonth() == 0);
  3746. case TimeStep.SCALE.YEAR:
  3747. return false;
  3748. default:
  3749. return false;
  3750. }
  3751. };
  3752. /**
  3753. * Returns formatted text for the minor axislabel, depending on the current
  3754. * date and the scale. For example when scale is MINUTE, the current time is
  3755. * formatted as "hh:mm".
  3756. * @param {Date} [date] custom date. if not provided, current date is taken
  3757. */
  3758. TimeStep.prototype.getLabelMinor = function(date) {
  3759. if (date == undefined) {
  3760. date = this.current;
  3761. }
  3762. switch (this.scale) {
  3763. case TimeStep.SCALE.MILLISECOND: return moment(date).format('SSS');
  3764. case TimeStep.SCALE.SECOND: return moment(date).format('s');
  3765. case TimeStep.SCALE.MINUTE: return moment(date).format('HH:mm');
  3766. case TimeStep.SCALE.HOUR: return moment(date).format('HH:mm');
  3767. case TimeStep.SCALE.WEEKDAY: return moment(date).format('ddd D');
  3768. case TimeStep.SCALE.DAY: return moment(date).format('D');
  3769. case TimeStep.SCALE.MONTH: return moment(date).format('MMM');
  3770. case TimeStep.SCALE.YEAR: return moment(date).format('YYYY');
  3771. default: return '';
  3772. }
  3773. };
  3774. /**
  3775. * Returns formatted text for the major axislabel, depending on the current
  3776. * date and the scale. For example when scale is MINUTE, the major scale is
  3777. * hours, and the hour will be formatted as "hh".
  3778. * @param {Date} [date] custom date. if not provided, current date is taken
  3779. */
  3780. TimeStep.prototype.getLabelMajor = function(date) {
  3781. if (date == undefined) {
  3782. date = this.current;
  3783. }
  3784. //noinspection FallthroughInSwitchStatementJS
  3785. switch (this.scale) {
  3786. case TimeStep.SCALE.MILLISECOND:return moment(date).format('HH:mm:ss');
  3787. case TimeStep.SCALE.SECOND: return moment(date).format('D MMMM HH:mm');
  3788. case TimeStep.SCALE.MINUTE:
  3789. case TimeStep.SCALE.HOUR: return moment(date).format('ddd D MMMM');
  3790. case TimeStep.SCALE.WEEKDAY:
  3791. case TimeStep.SCALE.DAY: return moment(date).format('MMMM YYYY');
  3792. case TimeStep.SCALE.MONTH: return moment(date).format('YYYY');
  3793. case TimeStep.SCALE.YEAR: return '';
  3794. default: return '';
  3795. }
  3796. };
  3797. // exports
  3798. module.exports = exports = TimeStep;
  3799. },{"./util":8,"moment":18}],15:[function(require,module,exports){
  3800. var util = require('../../util'),
  3801. Item = require('./item');
  3802. /**
  3803. * @constructor ItemBox
  3804. * @extends Item
  3805. * @param {ItemSet} parent
  3806. * @param {Object} data Object containing parameters start
  3807. * content, className.
  3808. * @param {Object} [options] Options to set initial property values
  3809. * // TODO: describe available options
  3810. */
  3811. function ItemBox (parent, data, options) {
  3812. this.props = {
  3813. dot: {
  3814. left: 0,
  3815. top: 0,
  3816. width: 0,
  3817. height: 0
  3818. },
  3819. line: {
  3820. top: 0,
  3821. left: 0,
  3822. width: 0,
  3823. height: 0
  3824. }
  3825. };
  3826. Item.call(this, parent, data, options);
  3827. }
  3828. ItemBox.prototype = new Item (null, null);
  3829. /**
  3830. * Select the item
  3831. * @override
  3832. */
  3833. ItemBox.prototype.select = function () {
  3834. this.selected = true;
  3835. // TODO: select and unselect
  3836. };
  3837. /**
  3838. * Unselect the item
  3839. * @override
  3840. */
  3841. ItemBox.prototype.unselect = function () {
  3842. this.selected = false;
  3843. // TODO: select and unselect
  3844. };
  3845. /**
  3846. * Repaint the item
  3847. * @return {Boolean} changed
  3848. */
  3849. ItemBox.prototype.repaint = function () {
  3850. // TODO: make an efficient repaint
  3851. var changed = false;
  3852. var dom = this.dom;
  3853. if (this.visible) {
  3854. if (!dom) {
  3855. this._create();
  3856. changed = true;
  3857. }
  3858. dom = this.dom;
  3859. if (dom) {
  3860. if (!this.options && !this.parent) {
  3861. throw new Error('Cannot repaint item: no parent attached');
  3862. }
  3863. var foreground = this.parent.getForeground();
  3864. if (!foreground) {
  3865. throw new Error('Cannot repaint time axis: ' +
  3866. 'parent has no foreground container element');
  3867. }
  3868. var background = this.parent.getBackground();
  3869. if (!background) {
  3870. throw new Error('Cannot repaint time axis: ' +
  3871. 'parent has no background container element');
  3872. }
  3873. if (!dom.box.parentNode) {
  3874. foreground.appendChild(dom.box);
  3875. changed = true;
  3876. }
  3877. if (!dom.line.parentNode) {
  3878. background.appendChild(dom.line);
  3879. changed = true;
  3880. }
  3881. if (!dom.dot.parentNode) {
  3882. this.parent.dom.axis.appendChild(dom.dot);
  3883. changed = true;
  3884. }
  3885. // update contents
  3886. if (this.data.content != this.content) {
  3887. this.content = this.data.content;
  3888. if (this.content instanceof Element) {
  3889. dom.content.innerHTML = '';
  3890. dom.content.appendChild(this.content);
  3891. }
  3892. else if (this.data.content != undefined) {
  3893. dom.content.innerHTML = this.content;
  3894. }
  3895. else {
  3896. throw new Error('Property "content" missing in item ' + this.data.id);
  3897. }
  3898. changed = true;
  3899. }
  3900. // update class
  3901. var className = (this.data.className? ' ' + this.data.className : '') +
  3902. (this.selected ? ' selected' : '');
  3903. if (this.className != className) {
  3904. this.className = className;
  3905. dom.box.className = 'item box' + className;
  3906. dom.line.className = 'item line' + className;
  3907. dom.dot.className = 'item dot' + className;
  3908. changed = true;
  3909. }
  3910. }
  3911. }
  3912. else {
  3913. // hide when visible
  3914. if (dom) {
  3915. if (dom.box.parentNode) {
  3916. dom.box.parentNode.removeChild(dom.box);
  3917. changed = true;
  3918. }
  3919. if (dom.line.parentNode) {
  3920. dom.line.parentNode.removeChild(dom.line);
  3921. changed = true;
  3922. }
  3923. if (dom.dot.parentNode) {
  3924. dom.dot.parentNode.removeChild(dom.dot);
  3925. changed = true;
  3926. }
  3927. }
  3928. }
  3929. return changed;
  3930. };
  3931. /**
  3932. * Reflow the item: calculate its actual size and position from the DOM
  3933. * @return {boolean} resized returns true if the axis is resized
  3934. * @override
  3935. */
  3936. ItemBox.prototype.reflow = function () {
  3937. if (this.data.start == undefined) {
  3938. throw new Error('Property "start" missing in item ' + this.data.id);
  3939. }
  3940. var update = util.updateProperty,
  3941. dom = this.dom,
  3942. props = this.props,
  3943. options = this.options,
  3944. start = this.parent.toScreen(this.data.start),
  3945. align = options && options.align,
  3946. orientation = options.orientation,
  3947. changed = 0,
  3948. top,
  3949. left;
  3950. if (dom) {
  3951. changed += update(props.dot, 'height', dom.dot.offsetHeight);
  3952. changed += update(props.dot, 'width', dom.dot.offsetWidth);
  3953. changed += update(props.line, 'width', dom.line.offsetWidth);
  3954. changed += update(props.line, 'width', dom.line.offsetWidth);
  3955. changed += update(this, 'width', dom.box.offsetWidth);
  3956. changed += update(this, 'height', dom.box.offsetHeight);
  3957. if (align == 'right') {
  3958. left = start - this.width;
  3959. }
  3960. else if (align == 'left') {
  3961. left = start;
  3962. }
  3963. else {
  3964. // default or 'center'
  3965. left = start - this.width / 2;
  3966. }
  3967. changed += update(this, 'left', left);
  3968. changed += update(props.line, 'left', start - props.line.width / 2);
  3969. changed += update(props.dot, 'left', start - props.dot.width / 2);
  3970. if (orientation == 'top') {
  3971. top = options.margin.axis;
  3972. changed += update(this, 'top', top);
  3973. changed += update(props.line, 'top', 0);
  3974. changed += update(props.line, 'height', top);
  3975. changed += update(props.dot, 'top', -props.dot.height / 2);
  3976. }
  3977. else {
  3978. // default or 'bottom'
  3979. var parentHeight = this.parent.height;
  3980. top = parentHeight - this.height - options.margin.axis;
  3981. changed += update(this, 'top', top);
  3982. changed += update(props.line, 'top', top + this.height);
  3983. changed += update(props.line, 'height', Math.max(options.margin.axis, 0));
  3984. changed += update(props.dot, 'top', parentHeight - props.dot.height / 2);
  3985. }
  3986. }
  3987. else {
  3988. changed += 1;
  3989. }
  3990. return (changed > 0);
  3991. };
  3992. /**
  3993. * Create an items DOM
  3994. * @private
  3995. */
  3996. ItemBox.prototype._create = function () {
  3997. var dom = this.dom;
  3998. if (!dom) {
  3999. this.dom = dom = {};
  4000. // create the box
  4001. dom.box = document.createElement('DIV');
  4002. // className is updated in repaint()
  4003. // contents box (inside the background box). used for making margins
  4004. dom.content = document.createElement('DIV');
  4005. dom.content.className = 'content';
  4006. dom.box.appendChild(dom.content);
  4007. // line to axis
  4008. dom.line = document.createElement('DIV');
  4009. dom.line.className = 'line';
  4010. // dot on axis
  4011. dom.dot = document.createElement('DIV');
  4012. dom.dot.className = 'dot';
  4013. }
  4014. };
  4015. /**
  4016. * Reposition the item, recalculate its left, top, and width, using the current
  4017. * range and size of the items itemset
  4018. * @override
  4019. */
  4020. ItemBox.prototype.reposition = function () {
  4021. var dom = this.dom,
  4022. props = this.props,
  4023. orientation = this.options.orientation;
  4024. if (dom) {
  4025. var box = dom.box,
  4026. line = dom.line,
  4027. dot = dom.dot;
  4028. box.style.left = this.left + 'px';
  4029. box.style.top = this.top + 'px';
  4030. line.style.left = props.line.left + 'px';
  4031. if (orientation == 'top') {
  4032. line.style.top = 0 + 'px';
  4033. line.style.height = this.top + 'px';
  4034. }
  4035. else {
  4036. // orientation 'bottom'
  4037. line.style.top = props.line.top + 'px';
  4038. line.style.top = (this.top + this.height) + 'px';
  4039. line.style.height = Math.max(props.dot.top - this.top - this.height, 0) + 'px';
  4040. }
  4041. dot.style.left = props.dot.left + 'px';
  4042. dot.style.top = props.dot.top + 'px';
  4043. }
  4044. };
  4045. // exports
  4046. module.exports = exports = ItemBox;
  4047. },{"../../util":8,"./item":19}],16:[function(require,module,exports){
  4048. var util = require('../../util'),
  4049. Item = require('./item');
  4050. /**
  4051. * @constructor ItemRange
  4052. * @extends Item
  4053. * @param {ItemSet} parent
  4054. * @param {Object} data Object containing parameters start, end
  4055. * content, className.
  4056. * @param {Object} [options] Options to set initial property values
  4057. * // TODO: describe available options
  4058. */
  4059. function ItemRange (parent, data, options) {
  4060. this.props = {
  4061. content: {
  4062. left: 0,
  4063. width: 0
  4064. }
  4065. };
  4066. Item.call(this, parent, data, options);
  4067. }
  4068. ItemRange.prototype = new Item (null, null);
  4069. /**
  4070. * Select the item
  4071. * @override
  4072. */
  4073. ItemRange.prototype.select = function () {
  4074. this.selected = true;
  4075. // TODO: select and unselect
  4076. };
  4077. /**
  4078. * Unselect the item
  4079. * @override
  4080. */
  4081. ItemRange.prototype.unselect = function () {
  4082. this.selected = false;
  4083. // TODO: select and unselect
  4084. };
  4085. /**
  4086. * Repaint the item
  4087. * @return {Boolean} changed
  4088. */
  4089. ItemRange.prototype.repaint = function () {
  4090. // TODO: make an efficient repaint
  4091. var changed = false;
  4092. var dom = this.dom;
  4093. if (this.visible) {
  4094. if (!dom) {
  4095. this._create();
  4096. changed = true;
  4097. }
  4098. dom = this.dom;
  4099. if (dom) {
  4100. if (!this.options && !this.options.parent) {
  4101. throw new Error('Cannot repaint item: no parent attached');
  4102. }
  4103. var foreground = this.parent.getForeground();
  4104. if (!foreground) {
  4105. throw new Error('Cannot repaint time axis: ' +
  4106. 'parent has no foreground container element');
  4107. }
  4108. if (!dom.box.parentNode) {
  4109. foreground.appendChild(dom.box);
  4110. changed = true;
  4111. }
  4112. // update content
  4113. if (this.data.content != this.content) {
  4114. this.content = this.data.content;
  4115. if (this.content instanceof Element) {
  4116. dom.content.innerHTML = '';
  4117. dom.content.appendChild(this.content);
  4118. }
  4119. else if (this.data.content != undefined) {
  4120. dom.content.innerHTML = this.content;
  4121. }
  4122. else {
  4123. throw new Error('Property "content" missing in item ' + this.data.id);
  4124. }
  4125. changed = true;
  4126. }
  4127. // update class
  4128. var className = this.data.className ? ('' + this.data.className) : '';
  4129. if (this.className != className) {
  4130. this.className = className;
  4131. dom.box.className = 'item range' + className;
  4132. changed = true;
  4133. }
  4134. }
  4135. }
  4136. else {
  4137. // hide when visible
  4138. if (dom) {
  4139. if (dom.box.parentNode) {
  4140. dom.box.parentNode.removeChild(dom.box);
  4141. changed = true;
  4142. }
  4143. }
  4144. }
  4145. return changed;
  4146. };
  4147. /**
  4148. * Reflow the item: calculate its actual size from the DOM
  4149. * @return {boolean} resized returns true if the axis is resized
  4150. * @override
  4151. */
  4152. ItemRange.prototype.reflow = function () {
  4153. if (this.data.start == undefined) {
  4154. throw new Error('Property "start" missing in item ' + this.data.id);
  4155. }
  4156. if (this.data.end == undefined) {
  4157. throw new Error('Property "end" missing in item ' + this.data.id);
  4158. }
  4159. var dom = this.dom,
  4160. props = this.props,
  4161. options = this.options,
  4162. parent = this.parent,
  4163. start = parent.toScreen(this.data.start),
  4164. end = parent.toScreen(this.data.end),
  4165. changed = 0;
  4166. if (dom) {
  4167. var update = util.updateProperty,
  4168. box = dom.box,
  4169. parentWidth = parent.width,
  4170. orientation = options.orientation,
  4171. contentLeft,
  4172. top;
  4173. changed += update(props.content, 'width', dom.content.offsetWidth);
  4174. changed += update(this, 'height', box.offsetHeight);
  4175. // limit the width of the this, as browsers cannot draw very wide divs
  4176. if (start < -parentWidth) {
  4177. start = -parentWidth;
  4178. }
  4179. if (end > 2 * parentWidth) {
  4180. end = 2 * parentWidth;
  4181. }
  4182. // when range exceeds left of the window, position the contents at the left of the visible area
  4183. if (start < 0) {
  4184. contentLeft = Math.min(-start,
  4185. (end - start - props.content.width - 2 * options.padding));
  4186. // TODO: remove the need for options.padding. it's terrible.
  4187. }
  4188. else {
  4189. contentLeft = 0;
  4190. }
  4191. changed += update(props.content, 'left', contentLeft);
  4192. if (orientation == 'top') {
  4193. top = options.margin.axis;
  4194. changed += update(this, 'top', top);
  4195. }
  4196. else {
  4197. // default or 'bottom'
  4198. top = parent.height - this.height - options.margin.axis;
  4199. changed += update(this, 'top', top);
  4200. }
  4201. changed += update(this, 'left', start);
  4202. changed += update(this, 'width', Math.max(end - start, 1)); // TODO: reckon with border width;
  4203. }
  4204. else {
  4205. changed += 1;
  4206. }
  4207. return (changed > 0);
  4208. };
  4209. /**
  4210. * Create an items DOM
  4211. * @private
  4212. */
  4213. ItemRange.prototype._create = function () {
  4214. var dom = this.dom;
  4215. if (!dom) {
  4216. this.dom = dom = {};
  4217. // background box
  4218. dom.box = document.createElement('div');
  4219. // className is updated in repaint()
  4220. // contents box
  4221. dom.content = document.createElement('div');
  4222. dom.content.className = 'content';
  4223. dom.box.appendChild(dom.content);
  4224. }
  4225. };
  4226. /**
  4227. * Reposition the item, recalculate its left, top, and width, using the current
  4228. * range and size of the items itemset
  4229. * @override
  4230. */
  4231. ItemRange.prototype.reposition = function () {
  4232. var dom = this.dom,
  4233. props = this.props;
  4234. if (dom) {
  4235. dom.box.style.top = this.top + 'px';
  4236. dom.box.style.left = this.left + 'px';
  4237. dom.box.style.width = this.width + 'px';
  4238. dom.content.style.left = props.content.left + 'px';
  4239. }
  4240. };
  4241. // exports
  4242. module.exports = exports = ItemRange;
  4243. },{"../../util":8,"./item":19}],17:[function(require,module,exports){
  4244. var util = require('../../util'),
  4245. Item = require('./item');
  4246. /**
  4247. * @constructor ItemPoint
  4248. * @extends Item
  4249. * @param {ItemSet} parent
  4250. * @param {Object} data Object containing parameters start
  4251. * content, className.
  4252. * @param {Object} [options] Options to set initial property values
  4253. * // TODO: describe available options
  4254. */
  4255. function ItemPoint (parent, data, options) {
  4256. this.props = {
  4257. dot: {
  4258. top: 0,
  4259. width: 0,
  4260. height: 0
  4261. },
  4262. content: {
  4263. height: 0,
  4264. marginLeft: 0
  4265. }
  4266. };
  4267. Item.call(this, parent, data, options);
  4268. }
  4269. ItemPoint.prototype = new Item (null, null);
  4270. /**
  4271. * Select the item
  4272. * @override
  4273. */
  4274. ItemPoint.prototype.select = function () {
  4275. this.selected = true;
  4276. // TODO: select and unselect
  4277. };
  4278. /**
  4279. * Unselect the item
  4280. * @override
  4281. */
  4282. ItemPoint.prototype.unselect = function () {
  4283. this.selected = false;
  4284. // TODO: select and unselect
  4285. };
  4286. /**
  4287. * Repaint the item
  4288. * @return {Boolean} changed
  4289. */
  4290. ItemPoint.prototype.repaint = function () {
  4291. // TODO: make an efficient repaint
  4292. var changed = false;
  4293. var dom = this.dom;
  4294. if (this.visible) {
  4295. if (!dom) {
  4296. this._create();
  4297. changed = true;
  4298. }
  4299. dom = this.dom;
  4300. if (dom) {
  4301. if (!this.options && !this.options.parent) {
  4302. throw new Error('Cannot repaint item: no parent attached');
  4303. }
  4304. var foreground = this.parent.getForeground();
  4305. if (!foreground) {
  4306. throw new Error('Cannot repaint time axis: ' +
  4307. 'parent has no foreground container element');
  4308. }
  4309. if (!dom.point.parentNode) {
  4310. foreground.appendChild(dom.point);
  4311. foreground.appendChild(dom.point);
  4312. changed = true;
  4313. }
  4314. // update contents
  4315. if (this.data.content != this.content) {
  4316. this.content = this.data.content;
  4317. if (this.content instanceof Element) {
  4318. dom.content.innerHTML = '';
  4319. dom.content.appendChild(this.content);
  4320. }
  4321. else if (this.data.content != undefined) {
  4322. dom.content.innerHTML = this.content;
  4323. }
  4324. else {
  4325. throw new Error('Property "content" missing in item ' + this.data.id);
  4326. }
  4327. changed = true;
  4328. }
  4329. // update class
  4330. var className = (this.data.className? ' ' + this.data.className : '') +
  4331. (this.selected ? ' selected' : '');
  4332. if (this.className != className) {
  4333. this.className = className;
  4334. dom.point.className = 'item point' + className;
  4335. changed = true;
  4336. }
  4337. }
  4338. }
  4339. else {
  4340. // hide when visible
  4341. if (dom) {
  4342. if (dom.point.parentNode) {
  4343. dom.point.parentNode.removeChild(dom.point);
  4344. changed = true;
  4345. }
  4346. }
  4347. }
  4348. return changed;
  4349. };
  4350. /**
  4351. * Reflow the item: calculate its actual size from the DOM
  4352. * @return {boolean} resized returns true if the axis is resized
  4353. * @override
  4354. */
  4355. ItemPoint.prototype.reflow = function () {
  4356. if (this.data.start == undefined) {
  4357. throw new Error('Property "start" missing in item ' + this.data.id);
  4358. }
  4359. var update = util.updateProperty,
  4360. dom = this.dom,
  4361. props = this.props,
  4362. options = this.options,
  4363. orientation = options.orientation,
  4364. start = this.parent.toScreen(this.data.start),
  4365. changed = 0,
  4366. top;
  4367. if (dom) {
  4368. changed += update(this, 'width', dom.point.offsetWidth);
  4369. changed += update(this, 'height', dom.point.offsetHeight);
  4370. changed += update(props.dot, 'width', dom.dot.offsetWidth);
  4371. changed += update(props.dot, 'height', dom.dot.offsetHeight);
  4372. changed += update(props.content, 'height', dom.content.offsetHeight);
  4373. if (orientation == 'top') {
  4374. top = options.margin.axis;
  4375. }
  4376. else {
  4377. // default or 'bottom'
  4378. var parentHeight = this.parent.height;
  4379. top = Math.max(parentHeight - this.height - options.margin.axis, 0);
  4380. }
  4381. changed += update(this, 'top', top);
  4382. changed += update(this, 'left', start - props.dot.width / 2);
  4383. changed += update(props.content, 'marginLeft', 1.5 * props.dot.width);
  4384. //changed += update(props.content, 'marginRight', 0.5 * props.dot.width); // TODO
  4385. changed += update(props.dot, 'top', (this.height - props.dot.height) / 2);
  4386. }
  4387. else {
  4388. changed += 1;
  4389. }
  4390. return (changed > 0);
  4391. };
  4392. /**
  4393. * Create an items DOM
  4394. * @private
  4395. */
  4396. ItemPoint.prototype._create = function () {
  4397. var dom = this.dom;
  4398. if (!dom) {
  4399. this.dom = dom = {};
  4400. // background box
  4401. dom.point = document.createElement('div');
  4402. // className is updated in repaint()
  4403. // contents box, right from the dot
  4404. dom.content = document.createElement('div');
  4405. dom.content.className = 'content';
  4406. dom.point.appendChild(dom.content);
  4407. // dot at start
  4408. dom.dot = document.createElement('div');
  4409. dom.dot.className = 'dot';
  4410. dom.point.appendChild(dom.dot);
  4411. }
  4412. };
  4413. /**
  4414. * Reposition the item, recalculate its left, top, and width, using the current
  4415. * range and size of the items itemset
  4416. * @override
  4417. */
  4418. ItemPoint.prototype.reposition = function () {
  4419. var dom = this.dom,
  4420. props = this.props;
  4421. if (dom) {
  4422. dom.point.style.top = this.top + 'px';
  4423. dom.point.style.left = this.left + 'px';
  4424. dom.content.style.marginLeft = props.content.marginLeft + 'px';
  4425. //dom.content.style.marginRight = props.content.marginRight + 'px'; // TODO
  4426. dom.dot.style.top = props.dot.top + 'px';
  4427. }
  4428. };
  4429. // exports
  4430. module.exports = exports = ItemPoint;
  4431. },{"../../util":8,"./item":19}],18:[function(require,module,exports){
  4432. (function(){// moment.js
  4433. // version : 2.0.0
  4434. // author : Tim Wood
  4435. // license : MIT
  4436. // momentjs.com
  4437. (function (undefined) {
  4438. /************************************
  4439. Constants
  4440. ************************************/
  4441. var moment,
  4442. VERSION = "2.0.0",
  4443. round = Math.round, i,
  4444. // internal storage for language config files
  4445. languages = {},
  4446. // check for nodeJS
  4447. hasModule = (typeof module !== 'undefined' && module.exports),
  4448. // ASP.NET json date format regex
  4449. aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
  4450. // format tokens
  4451. formattingTokens = /(\[[^\[]*\])|(\\)?(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,
  4452. localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
  4453. // parsing tokens
  4454. parseMultipleFormatChunker = /([0-9a-zA-Z\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+)/gi,
  4455. // parsing token regexes
  4456. parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
  4457. parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
  4458. parseTokenThreeDigits = /\d{3}/, // 000 - 999
  4459. parseTokenFourDigits = /\d{1,4}/, // 0 - 9999
  4460. parseTokenSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
  4461. parseTokenWord = /[0-9]*[a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF]+\s*?[\u0600-\u06FF]+/i, // any word (or two) characters or numbers including two word month in arabic.
  4462. parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/i, // +00:00 -00:00 +0000 -0000 or Z
  4463. parseTokenT = /T/i, // T (ISO seperator)
  4464. parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
  4465. // preliminary iso regex
  4466. // 0000-00-00 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000
  4467. isoRegex = /^\s*\d{4}-\d\d-\d\d((T| )(\d\d(:\d\d(:\d\d(\.\d\d?\d?)?)?)?)?([\+\-]\d\d:?\d\d)?)?/,
  4468. isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
  4469. // iso time formats and regexes
  4470. isoTimes = [
  4471. ['HH:mm:ss.S', /(T| )\d\d:\d\d:\d\d\.\d{1,3}/],
  4472. ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
  4473. ['HH:mm', /(T| )\d\d:\d\d/],
  4474. ['HH', /(T| )\d\d/]
  4475. ],
  4476. // timezone chunker "+10:00" > ["10", "00"] or "-1530" > ["-15", "30"]
  4477. parseTimezoneChunker = /([\+\-]|\d\d)/gi,
  4478. // getter and setter names
  4479. proxyGettersAndSetters = 'Month|Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
  4480. unitMillisecondFactors = {
  4481. 'Milliseconds' : 1,
  4482. 'Seconds' : 1e3,
  4483. 'Minutes' : 6e4,
  4484. 'Hours' : 36e5,
  4485. 'Days' : 864e5,
  4486. 'Months' : 2592e6,
  4487. 'Years' : 31536e6
  4488. },
  4489. // format function strings
  4490. formatFunctions = {},
  4491. // tokens to ordinalize and pad
  4492. ordinalizeTokens = 'DDD w W M D d'.split(' '),
  4493. paddedTokens = 'M D H h m s w W'.split(' '),
  4494. formatTokenFunctions = {
  4495. M : function () {
  4496. return this.month() + 1;
  4497. },
  4498. MMM : function (format) {
  4499. return this.lang().monthsShort(this, format);
  4500. },
  4501. MMMM : function (format) {
  4502. return this.lang().months(this, format);
  4503. },
  4504. D : function () {
  4505. return this.date();
  4506. },
  4507. DDD : function () {
  4508. return this.dayOfYear();
  4509. },
  4510. d : function () {
  4511. return this.day();
  4512. },
  4513. dd : function (format) {
  4514. return this.lang().weekdaysMin(this, format);
  4515. },
  4516. ddd : function (format) {
  4517. return this.lang().weekdaysShort(this, format);
  4518. },
  4519. dddd : function (format) {
  4520. return this.lang().weekdays(this, format);
  4521. },
  4522. w : function () {
  4523. return this.week();
  4524. },
  4525. W : function () {
  4526. return this.isoWeek();
  4527. },
  4528. YY : function () {
  4529. return leftZeroFill(this.year() % 100, 2);
  4530. },
  4531. YYYY : function () {
  4532. return leftZeroFill(this.year(), 4);
  4533. },
  4534. YYYYY : function () {
  4535. return leftZeroFill(this.year(), 5);
  4536. },
  4537. a : function () {
  4538. return this.lang().meridiem(this.hours(), this.minutes(), true);
  4539. },
  4540. A : function () {
  4541. return this.lang().meridiem(this.hours(), this.minutes(), false);
  4542. },
  4543. H : function () {
  4544. return this.hours();
  4545. },
  4546. h : function () {
  4547. return this.hours() % 12 || 12;
  4548. },
  4549. m : function () {
  4550. return this.minutes();
  4551. },
  4552. s : function () {
  4553. return this.seconds();
  4554. },
  4555. S : function () {
  4556. return ~~(this.milliseconds() / 100);
  4557. },
  4558. SS : function () {
  4559. return leftZeroFill(~~(this.milliseconds() / 10), 2);
  4560. },
  4561. SSS : function () {
  4562. return leftZeroFill(this.milliseconds(), 3);
  4563. },
  4564. Z : function () {
  4565. var a = -this.zone(),
  4566. b = "+";
  4567. if (a < 0) {
  4568. a = -a;
  4569. b = "-";
  4570. }
  4571. return b + leftZeroFill(~~(a / 60), 2) + ":" + leftZeroFill(~~a % 60, 2);
  4572. },
  4573. ZZ : function () {
  4574. var a = -this.zone(),
  4575. b = "+";
  4576. if (a < 0) {
  4577. a = -a;
  4578. b = "-";
  4579. }
  4580. return b + leftZeroFill(~~(10 * a / 6), 4);
  4581. },
  4582. X : function () {
  4583. return this.unix();
  4584. }
  4585. };
  4586. function padToken(func, count) {
  4587. return function (a) {
  4588. return leftZeroFill(func.call(this, a), count);
  4589. };
  4590. }
  4591. function ordinalizeToken(func) {
  4592. return function (a) {
  4593. return this.lang().ordinal(func.call(this, a));
  4594. };
  4595. }
  4596. while (ordinalizeTokens.length) {
  4597. i = ordinalizeTokens.pop();
  4598. formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i]);
  4599. }
  4600. while (paddedTokens.length) {
  4601. i = paddedTokens.pop();
  4602. formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
  4603. }
  4604. formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
  4605. /************************************
  4606. Constructors
  4607. ************************************/
  4608. function Language() {
  4609. }
  4610. // Moment prototype object
  4611. function Moment(config) {
  4612. extend(this, config);
  4613. }
  4614. // Duration Constructor
  4615. function Duration(duration) {
  4616. var data = this._data = {},
  4617. years = duration.years || duration.year || duration.y || 0,
  4618. months = duration.months || duration.month || duration.M || 0,
  4619. weeks = duration.weeks || duration.week || duration.w || 0,
  4620. days = duration.days || duration.day || duration.d || 0,
  4621. hours = duration.hours || duration.hour || duration.h || 0,
  4622. minutes = duration.minutes || duration.minute || duration.m || 0,
  4623. seconds = duration.seconds || duration.second || duration.s || 0,
  4624. milliseconds = duration.milliseconds || duration.millisecond || duration.ms || 0;
  4625. // representation for dateAddRemove
  4626. this._milliseconds = milliseconds +
  4627. seconds * 1e3 + // 1000
  4628. minutes * 6e4 + // 1000 * 60
  4629. hours * 36e5; // 1000 * 60 * 60
  4630. // Because of dateAddRemove treats 24 hours as different from a
  4631. // day when working around DST, we need to store them separately
  4632. this._days = days +
  4633. weeks * 7;
  4634. // It is impossible translate months into days without knowing
  4635. // which months you are are talking about, so we have to store
  4636. // it separately.
  4637. this._months = months +
  4638. years * 12;
  4639. // The following code bubbles up values, see the tests for
  4640. // examples of what that means.
  4641. data.milliseconds = milliseconds % 1000;
  4642. seconds += absRound(milliseconds / 1000);
  4643. data.seconds = seconds % 60;
  4644. minutes += absRound(seconds / 60);
  4645. data.minutes = minutes % 60;
  4646. hours += absRound(minutes / 60);
  4647. data.hours = hours % 24;
  4648. days += absRound(hours / 24);
  4649. days += weeks * 7;
  4650. data.days = days % 30;
  4651. months += absRound(days / 30);
  4652. data.months = months % 12;
  4653. years += absRound(months / 12);
  4654. data.years = years;
  4655. }
  4656. /************************************
  4657. Helpers
  4658. ************************************/
  4659. function extend(a, b) {
  4660. for (var i in b) {
  4661. if (b.hasOwnProperty(i)) {
  4662. a[i] = b[i];
  4663. }
  4664. }
  4665. return a;
  4666. }
  4667. function absRound(number) {
  4668. if (number < 0) {
  4669. return Math.ceil(number);
  4670. } else {
  4671. return Math.floor(number);
  4672. }
  4673. }
  4674. // left zero fill a number
  4675. // see http://jsperf.com/left-zero-filling for performance comparison
  4676. function leftZeroFill(number, targetLength) {
  4677. var output = number + '';
  4678. while (output.length < targetLength) {
  4679. output = '0' + output;
  4680. }
  4681. return output;
  4682. }
  4683. // helper function for _.addTime and _.subtractTime
  4684. function addOrSubtractDurationFromMoment(mom, duration, isAdding) {
  4685. var ms = duration._milliseconds,
  4686. d = duration._days,
  4687. M = duration._months,
  4688. currentDate;
  4689. if (ms) {
  4690. mom._d.setTime(+mom + ms * isAdding);
  4691. }
  4692. if (d) {
  4693. mom.date(mom.date() + d * isAdding);
  4694. }
  4695. if (M) {
  4696. currentDate = mom.date();
  4697. mom.date(1)
  4698. .month(mom.month() + M * isAdding)
  4699. .date(Math.min(currentDate, mom.daysInMonth()));
  4700. }
  4701. }
  4702. // check if is an array
  4703. function isArray(input) {
  4704. return Object.prototype.toString.call(input) === '[object Array]';
  4705. }
  4706. // compare two arrays, return the number of differences
  4707. function compareArrays(array1, array2) {
  4708. var len = Math.min(array1.length, array2.length),
  4709. lengthDiff = Math.abs(array1.length - array2.length),
  4710. diffs = 0,
  4711. i;
  4712. for (i = 0; i < len; i++) {
  4713. if (~~array1[i] !== ~~array2[i]) {
  4714. diffs++;
  4715. }
  4716. }
  4717. return diffs + lengthDiff;
  4718. }
  4719. /************************************
  4720. Languages
  4721. ************************************/
  4722. Language.prototype = {
  4723. set : function (config) {
  4724. var prop, i;
  4725. for (i in config) {
  4726. prop = config[i];
  4727. if (typeof prop === 'function') {
  4728. this[i] = prop;
  4729. } else {
  4730. this['_' + i] = prop;
  4731. }
  4732. }
  4733. },
  4734. _months : "January_February_March_April_May_June_July_August_September_October_November_December".split("_"),
  4735. months : function (m) {
  4736. return this._months[m.month()];
  4737. },
  4738. _monthsShort : "Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec".split("_"),
  4739. monthsShort : function (m) {
  4740. return this._monthsShort[m.month()];
  4741. },
  4742. monthsParse : function (monthName) {
  4743. var i, mom, regex, output;
  4744. if (!this._monthsParse) {
  4745. this._monthsParse = [];
  4746. }
  4747. for (i = 0; i < 12; i++) {
  4748. // make the regex if we don't have it already
  4749. if (!this._monthsParse[i]) {
  4750. mom = moment([2000, i]);
  4751. regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
  4752. this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
  4753. }
  4754. // test the regex
  4755. if (this._monthsParse[i].test(monthName)) {
  4756. return i;
  4757. }
  4758. }
  4759. },
  4760. _weekdays : "Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday".split("_"),
  4761. weekdays : function (m) {
  4762. return this._weekdays[m.day()];
  4763. },
  4764. _weekdaysShort : "Sun_Mon_Tue_Wed_Thu_Fri_Sat".split("_"),
  4765. weekdaysShort : function (m) {
  4766. return this._weekdaysShort[m.day()];
  4767. },
  4768. _weekdaysMin : "Su_Mo_Tu_We_Th_Fr_Sa".split("_"),
  4769. weekdaysMin : function (m) {
  4770. return this._weekdaysMin[m.day()];
  4771. },
  4772. _longDateFormat : {
  4773. LT : "h:mm A",
  4774. L : "MM/DD/YYYY",
  4775. LL : "MMMM D YYYY",
  4776. LLL : "MMMM D YYYY LT",
  4777. LLLL : "dddd, MMMM D YYYY LT"
  4778. },
  4779. longDateFormat : function (key) {
  4780. var output = this._longDateFormat[key];
  4781. if (!output && this._longDateFormat[key.toUpperCase()]) {
  4782. output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
  4783. return val.slice(1);
  4784. });
  4785. this._longDateFormat[key] = output;
  4786. }
  4787. return output;
  4788. },
  4789. meridiem : function (hours, minutes, isLower) {
  4790. if (hours > 11) {
  4791. return isLower ? 'pm' : 'PM';
  4792. } else {
  4793. return isLower ? 'am' : 'AM';
  4794. }
  4795. },
  4796. _calendar : {
  4797. sameDay : '[Today at] LT',
  4798. nextDay : '[Tomorrow at] LT',
  4799. nextWeek : 'dddd [at] LT',
  4800. lastDay : '[Yesterday at] LT',
  4801. lastWeek : '[last] dddd [at] LT',
  4802. sameElse : 'L'
  4803. },
  4804. calendar : function (key, mom) {
  4805. var output = this._calendar[key];
  4806. return typeof output === 'function' ? output.apply(mom) : output;
  4807. },
  4808. _relativeTime : {
  4809. future : "in %s",
  4810. past : "%s ago",
  4811. s : "a few seconds",
  4812. m : "a minute",
  4813. mm : "%d minutes",
  4814. h : "an hour",
  4815. hh : "%d hours",
  4816. d : "a day",
  4817. dd : "%d days",
  4818. M : "a month",
  4819. MM : "%d months",
  4820. y : "a year",
  4821. yy : "%d years"
  4822. },
  4823. relativeTime : function (number, withoutSuffix, string, isFuture) {
  4824. var output = this._relativeTime[string];
  4825. return (typeof output === 'function') ?
  4826. output(number, withoutSuffix, string, isFuture) :
  4827. output.replace(/%d/i, number);
  4828. },
  4829. pastFuture : function (diff, output) {
  4830. var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
  4831. return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
  4832. },
  4833. ordinal : function (number) {
  4834. return this._ordinal.replace("%d", number);
  4835. },
  4836. _ordinal : "%d",
  4837. preparse : function (string) {
  4838. return string;
  4839. },
  4840. postformat : function (string) {
  4841. return string;
  4842. },
  4843. week : function (mom) {
  4844. return weekOfYear(mom, this._week.dow, this._week.doy);
  4845. },
  4846. _week : {
  4847. dow : 0, // Sunday is the first day of the week.
  4848. doy : 6 // The week that contains Jan 1st is the first week of the year.
  4849. }
  4850. };
  4851. // Loads a language definition into the `languages` cache. The function
  4852. // takes a key and optionally values. If not in the browser and no values
  4853. // are provided, it will load the language file module. As a convenience,
  4854. // this function also returns the language values.
  4855. function loadLang(key, values) {
  4856. values.abbr = key;
  4857. if (!languages[key]) {
  4858. languages[key] = new Language();
  4859. }
  4860. languages[key].set(values);
  4861. return languages[key];
  4862. }
  4863. // Determines which language definition to use and returns it.
  4864. //
  4865. // With no parameters, it will return the global language. If you
  4866. // pass in a language key, such as 'en', it will return the
  4867. // definition for 'en', so long as 'en' has already been loaded using
  4868. // moment.lang.
  4869. function getLangDefinition(key) {
  4870. if (!key) {
  4871. return moment.fn._lang;
  4872. }
  4873. if (!languages[key] && hasModule) {
  4874. require('./lang/' + key);
  4875. }
  4876. return languages[key];
  4877. }
  4878. /************************************
  4879. Formatting
  4880. ************************************/
  4881. function removeFormattingTokens(input) {
  4882. if (input.match(/\[.*\]/)) {
  4883. return input.replace(/^\[|\]$/g, "");
  4884. }
  4885. return input.replace(/\\/g, "");
  4886. }
  4887. function makeFormatFunction(format) {
  4888. var array = format.match(formattingTokens), i, length;
  4889. for (i = 0, length = array.length; i < length; i++) {
  4890. if (formatTokenFunctions[array[i]]) {
  4891. array[i] = formatTokenFunctions[array[i]];
  4892. } else {
  4893. array[i] = removeFormattingTokens(array[i]);
  4894. }
  4895. }
  4896. return function (mom) {
  4897. var output = "";
  4898. for (i = 0; i < length; i++) {
  4899. output += typeof array[i].call === 'function' ? array[i].call(mom, format) : array[i];
  4900. }
  4901. return output;
  4902. };
  4903. }
  4904. // format date using native date object
  4905. function formatMoment(m, format) {
  4906. var i = 5;
  4907. function replaceLongDateFormatTokens(input) {
  4908. return m.lang().longDateFormat(input) || input;
  4909. }
  4910. while (i-- && localFormattingTokens.test(format)) {
  4911. format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
  4912. }
  4913. if (!formatFunctions[format]) {
  4914. formatFunctions[format] = makeFormatFunction(format);
  4915. }
  4916. return formatFunctions[format](m);
  4917. }
  4918. /************************************
  4919. Parsing
  4920. ************************************/
  4921. // get the regex to find the next token
  4922. function getParseRegexForToken(token) {
  4923. switch (token) {
  4924. case 'DDDD':
  4925. return parseTokenThreeDigits;
  4926. case 'YYYY':
  4927. return parseTokenFourDigits;
  4928. case 'YYYYY':
  4929. return parseTokenSixDigits;
  4930. case 'S':
  4931. case 'SS':
  4932. case 'SSS':
  4933. case 'DDD':
  4934. return parseTokenOneToThreeDigits;
  4935. case 'MMM':
  4936. case 'MMMM':
  4937. case 'dd':
  4938. case 'ddd':
  4939. case 'dddd':
  4940. case 'a':
  4941. case 'A':
  4942. return parseTokenWord;
  4943. case 'X':
  4944. return parseTokenTimestampMs;
  4945. case 'Z':
  4946. case 'ZZ':
  4947. return parseTokenTimezone;
  4948. case 'T':
  4949. return parseTokenT;
  4950. case 'MM':
  4951. case 'DD':
  4952. case 'YY':
  4953. case 'HH':
  4954. case 'hh':
  4955. case 'mm':
  4956. case 'ss':
  4957. case 'M':
  4958. case 'D':
  4959. case 'd':
  4960. case 'H':
  4961. case 'h':
  4962. case 'm':
  4963. case 's':
  4964. return parseTokenOneOrTwoDigits;
  4965. default :
  4966. return new RegExp(token.replace('\\', ''));
  4967. }
  4968. }
  4969. // function to convert string input to date
  4970. function addTimeToArrayFromToken(token, input, config) {
  4971. var a, b,
  4972. datePartArray = config._a;
  4973. switch (token) {
  4974. // MONTH
  4975. case 'M' : // fall through to MM
  4976. case 'MM' :
  4977. datePartArray[1] = (input == null) ? 0 : ~~input - 1;
  4978. break;
  4979. case 'MMM' : // fall through to MMMM
  4980. case 'MMMM' :
  4981. a = getLangDefinition(config._l).monthsParse(input);
  4982. // if we didn't find a month name, mark the date as invalid.
  4983. if (a != null) {
  4984. datePartArray[1] = a;
  4985. } else {
  4986. config._isValid = false;
  4987. }
  4988. break;
  4989. // DAY OF MONTH
  4990. case 'D' : // fall through to DDDD
  4991. case 'DD' : // fall through to DDDD
  4992. case 'DDD' : // fall through to DDDD
  4993. case 'DDDD' :
  4994. if (input != null) {
  4995. datePartArray[2] = ~~input;
  4996. }
  4997. break;
  4998. // YEAR
  4999. case 'YY' :
  5000. datePartArray[0] = ~~input + (~~input > 68 ? 1900 : 2000);
  5001. break;
  5002. case 'YYYY' :
  5003. case 'YYYYY' :
  5004. datePartArray[0] = ~~input;
  5005. break;
  5006. // AM / PM
  5007. case 'a' : // fall through to A
  5008. case 'A' :
  5009. config._isPm = ((input + '').toLowerCase() === 'pm');
  5010. break;
  5011. // 24 HOUR
  5012. case 'H' : // fall through to hh
  5013. case 'HH' : // fall through to hh
  5014. case 'h' : // fall through to hh
  5015. case 'hh' :
  5016. datePartArray[3] = ~~input;
  5017. break;
  5018. // MINUTE
  5019. case 'm' : // fall through to mm
  5020. case 'mm' :
  5021. datePartArray[4] = ~~input;
  5022. break;
  5023. // SECOND
  5024. case 's' : // fall through to ss
  5025. case 'ss' :
  5026. datePartArray[5] = ~~input;
  5027. break;
  5028. // MILLISECOND
  5029. case 'S' :
  5030. case 'SS' :
  5031. case 'SSS' :
  5032. datePartArray[6] = ~~ (('0.' + input) * 1000);
  5033. break;
  5034. // UNIX TIMESTAMP WITH MS
  5035. case 'X':
  5036. config._d = new Date(parseFloat(input) * 1000);
  5037. break;
  5038. // TIMEZONE
  5039. case 'Z' : // fall through to ZZ
  5040. case 'ZZ' :
  5041. config._useUTC = true;
  5042. a = (input + '').match(parseTimezoneChunker);
  5043. if (a && a[1]) {
  5044. config._tzh = ~~a[1];
  5045. }
  5046. if (a && a[2]) {
  5047. config._tzm = ~~a[2];
  5048. }
  5049. // reverse offsets
  5050. if (a && a[0] === '+') {
  5051. config._tzh = -config._tzh;
  5052. config._tzm = -config._tzm;
  5053. }
  5054. break;
  5055. }
  5056. // if the input is null, the date is not valid
  5057. if (input == null) {
  5058. config._isValid = false;
  5059. }
  5060. }
  5061. // convert an array to a date.
  5062. // the array should mirror the parameters below
  5063. // note: all values past the year are optional and will default to the lowest possible value.
  5064. // [year, month, day , hour, minute, second, millisecond]
  5065. function dateFromArray(config) {
  5066. var i, date, input = [];
  5067. if (config._d) {
  5068. return;
  5069. }
  5070. for (i = 0; i < 7; i++) {
  5071. config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
  5072. }
  5073. // add the offsets to the time to be parsed so that we can have a clean array for checking isValid
  5074. input[3] += config._tzh || 0;
  5075. input[4] += config._tzm || 0;
  5076. date = new Date(0);
  5077. if (config._useUTC) {
  5078. date.setUTCFullYear(input[0], input[1], input[2]);
  5079. date.setUTCHours(input[3], input[4], input[5], input[6]);
  5080. } else {
  5081. date.setFullYear(input[0], input[1], input[2]);
  5082. date.setHours(input[3], input[4], input[5], input[6]);
  5083. }
  5084. config._d = date;
  5085. }
  5086. // date from string and format string
  5087. function makeDateFromStringAndFormat(config) {
  5088. // This array is used to make a Date, either with `new Date` or `Date.UTC`
  5089. var tokens = config._f.match(formattingTokens),
  5090. string = config._i,
  5091. i, parsedInput;
  5092. config._a = [];
  5093. for (i = 0; i < tokens.length; i++) {
  5094. parsedInput = (getParseRegexForToken(tokens[i]).exec(string) || [])[0];
  5095. if (parsedInput) {
  5096. string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
  5097. }
  5098. // don't parse if its not a known token
  5099. if (formatTokenFunctions[tokens[i]]) {
  5100. addTimeToArrayFromToken(tokens[i], parsedInput, config);
  5101. }
  5102. }
  5103. // handle am pm
  5104. if (config._isPm && config._a[3] < 12) {
  5105. config._a[3] += 12;
  5106. }
  5107. // if is 12 am, change hours to 0
  5108. if (config._isPm === false && config._a[3] === 12) {
  5109. config._a[3] = 0;
  5110. }
  5111. // return
  5112. dateFromArray(config);
  5113. }
  5114. // date from string and array of format strings
  5115. function makeDateFromStringAndArray(config) {
  5116. var tempConfig,
  5117. tempMoment,
  5118. bestMoment,
  5119. scoreToBeat = 99,
  5120. i,
  5121. currentDate,
  5122. currentScore;
  5123. while (config._f.length) {
  5124. tempConfig = extend({}, config);
  5125. tempConfig._f = config._f.pop();
  5126. makeDateFromStringAndFormat(tempConfig);
  5127. tempMoment = new Moment(tempConfig);
  5128. if (tempMoment.isValid()) {
  5129. bestMoment = tempMoment;
  5130. break;
  5131. }
  5132. currentScore = compareArrays(tempConfig._a, tempMoment.toArray());
  5133. if (currentScore < scoreToBeat) {
  5134. scoreToBeat = currentScore;
  5135. bestMoment = tempMoment;
  5136. }
  5137. }
  5138. extend(config, bestMoment);
  5139. }
  5140. // date from iso format
  5141. function makeDateFromString(config) {
  5142. var i,
  5143. string = config._i;
  5144. if (isoRegex.exec(string)) {
  5145. config._f = 'YYYY-MM-DDT';
  5146. for (i = 0; i < 4; i++) {
  5147. if (isoTimes[i][1].exec(string)) {
  5148. config._f += isoTimes[i][0];
  5149. break;
  5150. }
  5151. }
  5152. if (parseTokenTimezone.exec(string)) {
  5153. config._f += " Z";
  5154. }
  5155. makeDateFromStringAndFormat(config);
  5156. } else {
  5157. config._d = new Date(string);
  5158. }
  5159. }
  5160. function makeDateFromInput(config) {
  5161. var input = config._i,
  5162. matched = aspNetJsonRegex.exec(input);
  5163. if (input === undefined) {
  5164. config._d = new Date();
  5165. } else if (matched) {
  5166. config._d = new Date(+matched[1]);
  5167. } else if (typeof input === 'string') {
  5168. makeDateFromString(config);
  5169. } else if (isArray(input)) {
  5170. config._a = input.slice(0);
  5171. dateFromArray(config);
  5172. } else {
  5173. config._d = input instanceof Date ? new Date(+input) : new Date(input);
  5174. }
  5175. }
  5176. /************************************
  5177. Relative Time
  5178. ************************************/
  5179. // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
  5180. function substituteTimeAgo(string, number, withoutSuffix, isFuture, lang) {
  5181. return lang.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
  5182. }
  5183. function relativeTime(milliseconds, withoutSuffix, lang) {
  5184. var seconds = round(Math.abs(milliseconds) / 1000),
  5185. minutes = round(seconds / 60),
  5186. hours = round(minutes / 60),
  5187. days = round(hours / 24),
  5188. years = round(days / 365),
  5189. args = seconds < 45 && ['s', seconds] ||
  5190. minutes === 1 && ['m'] ||
  5191. minutes < 45 && ['mm', minutes] ||
  5192. hours === 1 && ['h'] ||
  5193. hours < 22 && ['hh', hours] ||
  5194. days === 1 && ['d'] ||
  5195. days <= 25 && ['dd', days] ||
  5196. days <= 45 && ['M'] ||
  5197. days < 345 && ['MM', round(days / 30)] ||
  5198. years === 1 && ['y'] || ['yy', years];
  5199. args[2] = withoutSuffix;
  5200. args[3] = milliseconds > 0;
  5201. args[4] = lang;
  5202. return substituteTimeAgo.apply({}, args);
  5203. }
  5204. /************************************
  5205. Week of Year
  5206. ************************************/
  5207. // firstDayOfWeek 0 = sun, 6 = sat
  5208. // the day of the week that starts the week
  5209. // (usually sunday or monday)
  5210. // firstDayOfWeekOfYear 0 = sun, 6 = sat
  5211. // the first week is the week that contains the first
  5212. // of this day of the week
  5213. // (eg. ISO weeks use thursday (4))
  5214. function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
  5215. var end = firstDayOfWeekOfYear - firstDayOfWeek,
  5216. daysToDayOfWeek = firstDayOfWeekOfYear - mom.day();
  5217. if (daysToDayOfWeek > end) {
  5218. daysToDayOfWeek -= 7;
  5219. }
  5220. if (daysToDayOfWeek < end - 7) {
  5221. daysToDayOfWeek += 7;
  5222. }
  5223. return Math.ceil(moment(mom).add('d', daysToDayOfWeek).dayOfYear() / 7);
  5224. }
  5225. /************************************
  5226. Top Level Functions
  5227. ************************************/
  5228. function makeMoment(config) {
  5229. var input = config._i,
  5230. format = config._f;
  5231. if (input === null || input === '') {
  5232. return null;
  5233. }
  5234. if (typeof input === 'string') {
  5235. config._i = input = getLangDefinition().preparse(input);
  5236. }
  5237. if (moment.isMoment(input)) {
  5238. config = extend({}, input);
  5239. config._d = new Date(+input._d);
  5240. } else if (format) {
  5241. if (isArray(format)) {
  5242. makeDateFromStringAndArray(config);
  5243. } else {
  5244. makeDateFromStringAndFormat(config);
  5245. }
  5246. } else {
  5247. makeDateFromInput(config);
  5248. }
  5249. return new Moment(config);
  5250. }
  5251. moment = function (input, format, lang) {
  5252. return makeMoment({
  5253. _i : input,
  5254. _f : format,
  5255. _l : lang,
  5256. _isUTC : false
  5257. });
  5258. };
  5259. // creating with utc
  5260. moment.utc = function (input, format, lang) {
  5261. return makeMoment({
  5262. _useUTC : true,
  5263. _isUTC : true,
  5264. _l : lang,
  5265. _i : input,
  5266. _f : format
  5267. });
  5268. };
  5269. // creating with unix timestamp (in seconds)
  5270. moment.unix = function (input) {
  5271. return moment(input * 1000);
  5272. };
  5273. // duration
  5274. moment.duration = function (input, key) {
  5275. var isDuration = moment.isDuration(input),
  5276. isNumber = (typeof input === 'number'),
  5277. duration = (isDuration ? input._data : (isNumber ? {} : input)),
  5278. ret;
  5279. if (isNumber) {
  5280. if (key) {
  5281. duration[key] = input;
  5282. } else {
  5283. duration.milliseconds = input;
  5284. }
  5285. }
  5286. ret = new Duration(duration);
  5287. if (isDuration && input.hasOwnProperty('_lang')) {
  5288. ret._lang = input._lang;
  5289. }
  5290. return ret;
  5291. };
  5292. // version number
  5293. moment.version = VERSION;
  5294. // default format
  5295. moment.defaultFormat = isoFormat;
  5296. // This function will load languages and then set the global language. If
  5297. // no arguments are passed in, it will simply return the current global
  5298. // language key.
  5299. moment.lang = function (key, values) {
  5300. var i;
  5301. if (!key) {
  5302. return moment.fn._lang._abbr;
  5303. }
  5304. if (values) {
  5305. loadLang(key, values);
  5306. } else if (!languages[key]) {
  5307. getLangDefinition(key);
  5308. }
  5309. moment.duration.fn._lang = moment.fn._lang = getLangDefinition(key);
  5310. };
  5311. // returns language data
  5312. moment.langData = function (key) {
  5313. if (key && key._lang && key._lang._abbr) {
  5314. key = key._lang._abbr;
  5315. }
  5316. return getLangDefinition(key);
  5317. };
  5318. // compare moment object
  5319. moment.isMoment = function (obj) {
  5320. return obj instanceof Moment;
  5321. };
  5322. // for typechecking Duration objects
  5323. moment.isDuration = function (obj) {
  5324. return obj instanceof Duration;
  5325. };
  5326. /************************************
  5327. Moment Prototype
  5328. ************************************/
  5329. moment.fn = Moment.prototype = {
  5330. clone : function () {
  5331. return moment(this);
  5332. },
  5333. valueOf : function () {
  5334. return +this._d;
  5335. },
  5336. unix : function () {
  5337. return Math.floor(+this._d / 1000);
  5338. },
  5339. toString : function () {
  5340. return this.format("ddd MMM DD YYYY HH:mm:ss [GMT]ZZ");
  5341. },
  5342. toDate : function () {
  5343. return this._d;
  5344. },
  5345. toJSON : function () {
  5346. return moment.utc(this).format('YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  5347. },
  5348. toArray : function () {
  5349. var m = this;
  5350. return [
  5351. m.year(),
  5352. m.month(),
  5353. m.date(),
  5354. m.hours(),
  5355. m.minutes(),
  5356. m.seconds(),
  5357. m.milliseconds()
  5358. ];
  5359. },
  5360. isValid : function () {
  5361. if (this._isValid == null) {
  5362. if (this._a) {
  5363. this._isValid = !compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray());
  5364. } else {
  5365. this._isValid = !isNaN(this._d.getTime());
  5366. }
  5367. }
  5368. return !!this._isValid;
  5369. },
  5370. utc : function () {
  5371. this._isUTC = true;
  5372. return this;
  5373. },
  5374. local : function () {
  5375. this._isUTC = false;
  5376. return this;
  5377. },
  5378. format : function (inputString) {
  5379. var output = formatMoment(this, inputString || moment.defaultFormat);
  5380. return this.lang().postformat(output);
  5381. },
  5382. add : function (input, val) {
  5383. var dur;
  5384. // switch args to support add('s', 1) and add(1, 's')
  5385. if (typeof input === 'string') {
  5386. dur = moment.duration(+val, input);
  5387. } else {
  5388. dur = moment.duration(input, val);
  5389. }
  5390. addOrSubtractDurationFromMoment(this, dur, 1);
  5391. return this;
  5392. },
  5393. subtract : function (input, val) {
  5394. var dur;
  5395. // switch args to support subtract('s', 1) and subtract(1, 's')
  5396. if (typeof input === 'string') {
  5397. dur = moment.duration(+val, input);
  5398. } else {
  5399. dur = moment.duration(input, val);
  5400. }
  5401. addOrSubtractDurationFromMoment(this, dur, -1);
  5402. return this;
  5403. },
  5404. diff : function (input, units, asFloat) {
  5405. var that = this._isUTC ? moment(input).utc() : moment(input).local(),
  5406. zoneDiff = (this.zone() - that.zone()) * 6e4,
  5407. diff, output;
  5408. if (units) {
  5409. // standardize on singular form
  5410. units = units.replace(/s$/, '');
  5411. }
  5412. if (units === 'year' || units === 'month') {
  5413. diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
  5414. output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
  5415. output += ((this - moment(this).startOf('month')) - (that - moment(that).startOf('month'))) / diff;
  5416. if (units === 'year') {
  5417. output = output / 12;
  5418. }
  5419. } else {
  5420. diff = (this - that) - zoneDiff;
  5421. output = units === 'second' ? diff / 1e3 : // 1000
  5422. units === 'minute' ? diff / 6e4 : // 1000 * 60
  5423. units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
  5424. units === 'day' ? diff / 864e5 : // 1000 * 60 * 60 * 24
  5425. units === 'week' ? diff / 6048e5 : // 1000 * 60 * 60 * 24 * 7
  5426. diff;
  5427. }
  5428. return asFloat ? output : absRound(output);
  5429. },
  5430. from : function (time, withoutSuffix) {
  5431. return moment.duration(this.diff(time)).lang(this.lang()._abbr).humanize(!withoutSuffix);
  5432. },
  5433. fromNow : function (withoutSuffix) {
  5434. return this.from(moment(), withoutSuffix);
  5435. },
  5436. calendar : function () {
  5437. var diff = this.diff(moment().startOf('day'), 'days', true),
  5438. format = diff < -6 ? 'sameElse' :
  5439. diff < -1 ? 'lastWeek' :
  5440. diff < 0 ? 'lastDay' :
  5441. diff < 1 ? 'sameDay' :
  5442. diff < 2 ? 'nextDay' :
  5443. diff < 7 ? 'nextWeek' : 'sameElse';
  5444. return this.format(this.lang().calendar(format, this));
  5445. },
  5446. isLeapYear : function () {
  5447. var year = this.year();
  5448. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  5449. },
  5450. isDST : function () {
  5451. return (this.zone() < moment([this.year()]).zone() ||
  5452. this.zone() < moment([this.year(), 5]).zone());
  5453. },
  5454. day : function (input) {
  5455. var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
  5456. return input == null ? day :
  5457. this.add({ d : input - day });
  5458. },
  5459. startOf: function (units) {
  5460. units = units.replace(/s$/, '');
  5461. // the following switch intentionally omits break keywords
  5462. // to utilize falling through the cases.
  5463. switch (units) {
  5464. case 'year':
  5465. this.month(0);
  5466. /* falls through */
  5467. case 'month':
  5468. this.date(1);
  5469. /* falls through */
  5470. case 'week':
  5471. case 'day':
  5472. this.hours(0);
  5473. /* falls through */
  5474. case 'hour':
  5475. this.minutes(0);
  5476. /* falls through */
  5477. case 'minute':
  5478. this.seconds(0);
  5479. /* falls through */
  5480. case 'second':
  5481. this.milliseconds(0);
  5482. /* falls through */
  5483. }
  5484. // weeks are a special case
  5485. if (units === 'week') {
  5486. this.day(0);
  5487. }
  5488. return this;
  5489. },
  5490. endOf: function (units) {
  5491. return this.startOf(units).add(units.replace(/s?$/, 's'), 1).subtract('ms', 1);
  5492. },
  5493. isAfter: function (input, units) {
  5494. units = typeof units !== 'undefined' ? units : 'millisecond';
  5495. return +this.clone().startOf(units) > +moment(input).startOf(units);
  5496. },
  5497. isBefore: function (input, units) {
  5498. units = typeof units !== 'undefined' ? units : 'millisecond';
  5499. return +this.clone().startOf(units) < +moment(input).startOf(units);
  5500. },
  5501. isSame: function (input, units) {
  5502. units = typeof units !== 'undefined' ? units : 'millisecond';
  5503. return +this.clone().startOf(units) === +moment(input).startOf(units);
  5504. },
  5505. zone : function () {
  5506. return this._isUTC ? 0 : this._d.getTimezoneOffset();
  5507. },
  5508. daysInMonth : function () {
  5509. return moment.utc([this.year(), this.month() + 1, 0]).date();
  5510. },
  5511. dayOfYear : function (input) {
  5512. var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
  5513. return input == null ? dayOfYear : this.add("d", (input - dayOfYear));
  5514. },
  5515. isoWeek : function (input) {
  5516. var week = weekOfYear(this, 1, 4);
  5517. return input == null ? week : this.add("d", (input - week) * 7);
  5518. },
  5519. week : function (input) {
  5520. var week = this.lang().week(this);
  5521. return input == null ? week : this.add("d", (input - week) * 7);
  5522. },
  5523. // If passed a language key, it will set the language for this
  5524. // instance. Otherwise, it will return the language configuration
  5525. // variables for this instance.
  5526. lang : function (key) {
  5527. if (key === undefined) {
  5528. return this._lang;
  5529. } else {
  5530. this._lang = getLangDefinition(key);
  5531. return this;
  5532. }
  5533. }
  5534. };
  5535. // helper for adding shortcuts
  5536. function makeGetterAndSetter(name, key) {
  5537. moment.fn[name] = moment.fn[name + 's'] = function (input) {
  5538. var utc = this._isUTC ? 'UTC' : '';
  5539. if (input != null) {
  5540. this._d['set' + utc + key](input);
  5541. return this;
  5542. } else {
  5543. return this._d['get' + utc + key]();
  5544. }
  5545. };
  5546. }
  5547. // loop through and add shortcuts (Month, Date, Hours, Minutes, Seconds, Milliseconds)
  5548. for (i = 0; i < proxyGettersAndSetters.length; i ++) {
  5549. makeGetterAndSetter(proxyGettersAndSetters[i].toLowerCase().replace(/s$/, ''), proxyGettersAndSetters[i]);
  5550. }
  5551. // add shortcut for year (uses different syntax than the getter/setter 'year' == 'FullYear')
  5552. makeGetterAndSetter('year', 'FullYear');
  5553. // add plural methods
  5554. moment.fn.days = moment.fn.day;
  5555. moment.fn.weeks = moment.fn.week;
  5556. moment.fn.isoWeeks = moment.fn.isoWeek;
  5557. /************************************
  5558. Duration Prototype
  5559. ************************************/
  5560. moment.duration.fn = Duration.prototype = {
  5561. weeks : function () {
  5562. return absRound(this.days() / 7);
  5563. },
  5564. valueOf : function () {
  5565. return this._milliseconds +
  5566. this._days * 864e5 +
  5567. this._months * 2592e6;
  5568. },
  5569. humanize : function (withSuffix) {
  5570. var difference = +this,
  5571. output = relativeTime(difference, !withSuffix, this.lang());
  5572. if (withSuffix) {
  5573. output = this.lang().pastFuture(difference, output);
  5574. }
  5575. return this.lang().postformat(output);
  5576. },
  5577. lang : moment.fn.lang
  5578. };
  5579. function makeDurationGetter(name) {
  5580. moment.duration.fn[name] = function () {
  5581. return this._data[name];
  5582. };
  5583. }
  5584. function makeDurationAsGetter(name, factor) {
  5585. moment.duration.fn['as' + name] = function () {
  5586. return +this / factor;
  5587. };
  5588. }
  5589. for (i in unitMillisecondFactors) {
  5590. if (unitMillisecondFactors.hasOwnProperty(i)) {
  5591. makeDurationAsGetter(i, unitMillisecondFactors[i]);
  5592. makeDurationGetter(i.toLowerCase());
  5593. }
  5594. }
  5595. makeDurationAsGetter('Weeks', 6048e5);
  5596. /************************************
  5597. Default Lang
  5598. ************************************/
  5599. // Set default language, other languages will inherit from English.
  5600. moment.lang('en', {
  5601. ordinal : function (number) {
  5602. var b = number % 10,
  5603. output = (~~ (number % 100 / 10) === 1) ? 'th' :
  5604. (b === 1) ? 'st' :
  5605. (b === 2) ? 'nd' :
  5606. (b === 3) ? 'rd' : 'th';
  5607. return number + output;
  5608. }
  5609. });
  5610. /************************************
  5611. Exposing Moment
  5612. ************************************/
  5613. // CommonJS module is defined
  5614. if (hasModule) {
  5615. module.exports = moment;
  5616. }
  5617. /*global ender:false */
  5618. if (typeof ender === 'undefined') {
  5619. // here, `this` means `window` in the browser, or `global` on the server
  5620. // add `moment` as a global object via a string identifier,
  5621. // for Closure Compiler "advanced" mode
  5622. this['moment'] = moment;
  5623. }
  5624. /*global define:false */
  5625. if (typeof define === "function" && define.amd) {
  5626. define("moment", [], function () {
  5627. return moment;
  5628. });
  5629. }
  5630. }).call(this);
  5631. })()
  5632. },{}],14:[function(require,module,exports){
  5633. var util = require('./../util'),
  5634. moment = require('moment'),
  5635. Range = require('../range'),
  5636. Controller = require('../controller'),
  5637. Component = require('../component/component'),
  5638. RootPanel = require('../component/rootpanel'),
  5639. TimeAxis = require('../component/timeaxis'),
  5640. ItemSet = require('../component/itemset');
  5641. /**
  5642. * Create a timeline visualization
  5643. * @param {HTMLElement} container
  5644. * @param {DataSet | Array | DataTable} [data]
  5645. * @param {Object} [options] See Timeline.setOptions for the available options.
  5646. * @constructor
  5647. */
  5648. function Timeline (container, data, options) {
  5649. var me = this;
  5650. this.options = {
  5651. orientation: 'bottom',
  5652. zoomMin: 10, // milliseconds
  5653. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000, // milliseconds
  5654. moveable: true,
  5655. zoomable: true
  5656. };
  5657. // controller
  5658. this.controller = new Controller();
  5659. // main panel
  5660. if (!container) {
  5661. throw new Error('No container element provided');
  5662. }
  5663. this.main = new RootPanel(container, {
  5664. autoResize: false,
  5665. height: function () {
  5666. return me.timeaxis.height + me.itemset.height;
  5667. }
  5668. });
  5669. this.controller.add(this.main);
  5670. // range
  5671. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  5672. this.range = new Range({
  5673. start: now.clone().add('days', -3).valueOf(),
  5674. end: now.clone().add('days', 4).valueOf()
  5675. });
  5676. // TODO: reckon with options moveable and zoomable
  5677. this.range.subscribe(this.main, 'move', 'horizontal');
  5678. this.range.subscribe(this.main, 'zoom', 'horizontal');
  5679. this.range.on('rangechange', function () {
  5680. // TODO: fix the delay in reflow/repaint, does not feel snappy
  5681. me.controller.requestReflow();
  5682. });
  5683. this.range.on('rangechanged', function () {
  5684. me.controller.requestReflow();
  5685. });
  5686. // TODO: put the listeners in setOptions, be able to dynamically change with options moveable and zoomable
  5687. // time axis
  5688. this.timeaxis = new TimeAxis(this.main, [], {
  5689. orientation: this.options.orientation,
  5690. range: this.range
  5691. });
  5692. this.timeaxis.setRange(this.range);
  5693. this.controller.add(this.timeaxis);
  5694. // items panel
  5695. this.itemset = new ItemSet(this.main, [this.timeaxis], {
  5696. orientation: this.options.orientation
  5697. });
  5698. this.itemset.setRange(this.range);
  5699. this.controller.add(this.itemset);
  5700. // set data
  5701. if (data) {
  5702. this.setData(data);
  5703. }
  5704. this.setOptions(options);
  5705. }
  5706. /**
  5707. * Set options
  5708. * @param {Object} options TODO: describe the available options
  5709. */
  5710. Timeline.prototype.setOptions = function (options) {
  5711. util.extend(this.options, options);
  5712. // update options the timeaxis
  5713. this.timeaxis.setOptions(this.options);
  5714. // update options for the range
  5715. this.range.setOptions(this.options);
  5716. // update options the itemset
  5717. var top,
  5718. me = this;
  5719. if (this.options.orientation == 'top') {
  5720. top = function () {
  5721. return me.timeaxis.height;
  5722. }
  5723. }
  5724. else {
  5725. top = function () {
  5726. return me.main.height - me.timeaxis.height - me.itemset.height;
  5727. }
  5728. }
  5729. this.itemset.setOptions({
  5730. orientation: this.options.orientation,
  5731. top: top
  5732. });
  5733. this.controller.repaint();
  5734. };
  5735. /**
  5736. * Set data
  5737. * @param {DataSet | Array | DataTable} data
  5738. */
  5739. Timeline.prototype.setData = function(data) {
  5740. var dataset = this.itemset.data;
  5741. if (!dataset) {
  5742. // first load of data
  5743. this.itemset.setData(data);
  5744. // apply the data range as range
  5745. var dataRange = this.itemset.getDataRange();
  5746. // add 5% on both sides
  5747. var min = dataRange.min;
  5748. var max = dataRange.max;
  5749. if (min != null && max != null) {
  5750. var interval = (max.valueOf() - min.valueOf());
  5751. min = new Date(min.valueOf() - interval * 0.05);
  5752. max = new Date(max.valueOf() + interval * 0.05);
  5753. }
  5754. // apply range if there is a min or max available
  5755. if (min != null || max != null) {
  5756. this.range.setRange(min, max);
  5757. }
  5758. }
  5759. else {
  5760. // updated data
  5761. this.itemset.setData(data);
  5762. }
  5763. };
  5764. // exports
  5765. module.exports = exports = Timeline;
  5766. },{"./../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){
  5767. var Component = require('../component');
  5768. /**
  5769. * @constructor Item
  5770. * @param {ItemSet} parent
  5771. * @param {Object} data Object containing (optional) parameters type,
  5772. * start, end, content, group, className.
  5773. * @param {Object} [options] Options to set initial property values
  5774. * // TODO: describe available options
  5775. */
  5776. function Item (parent, data, options) {
  5777. this.parent = parent;
  5778. this.data = data;
  5779. this.selected = false;
  5780. this.visible = true;
  5781. this.dom = null;
  5782. this.options = options;
  5783. }
  5784. Item.prototype = new Component();
  5785. /**
  5786. * Select current item
  5787. */
  5788. Item.prototype.select = function () {
  5789. this.selected = true;
  5790. };
  5791. /**
  5792. * Unselect current item
  5793. */
  5794. Item.prototype.unselect = function () {
  5795. this.selected = false;
  5796. };
  5797. // exports
  5798. module.exports = exports = Item;
  5799. },{"../component":9}]},{},[1])(1)
  5800. });
  5801. ;
  5802. /**
  5803. * AMD module exports
  5804. */
  5805. if (typeof(define) === 'function') {
  5806. define(function () {
  5807. return vis;
  5808. });
  5809. }
  5810. /**
  5811. * load css from text
  5812. * @param {String} css Text containing css
  5813. */
  5814. var loadCss = function (css) {
  5815. // get the script location, and built the css file name from the js file name
  5816. // http://stackoverflow.com/a/2161748/1262753
  5817. var scripts = document.getElementsByTagName('script');
  5818. // var jsFile = scripts[scripts.length-1].src.split('?')[0];
  5819. // var cssFile = jsFile.substring(0, jsFile.length - 2) + 'css';
  5820. // inject css
  5821. // http://stackoverflow.com/questions/524696/how-to-create-a-style-tag-with-javascript
  5822. var style = document.createElement('style');
  5823. style.type = 'text/css';
  5824. if (style.styleSheet){
  5825. style.styleSheet.cssText = css;
  5826. } else {
  5827. style.appendChild(document.createTextNode(css));
  5828. }
  5829. document.getElementsByTagName('head')[0].appendChild(style);
  5830. };
  5831. 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");