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.

33780 lines
1.0 MiB

10 years ago
  1. /**
  2. * vis.js
  3. * https://github.com/almende/vis
  4. *
  5. * A dynamic, browser-based visualization library.
  6. *
  7. * @version 3.6.3-SNAPSHOT
  8. * @date 2014-10-28
  9. *
  10. * @license
  11. * Copyright (C) 2011-2014 Almende B.V, http://almende.com
  12. *
  13. * Vis.js is dual licensed under both
  14. *
  15. * * The Apache 2.0 License
  16. * http://www.apache.org/licenses/LICENSE-2.0
  17. *
  18. * and
  19. *
  20. * * The MIT License
  21. * http://opensource.org/licenses/MIT
  22. *
  23. * Vis.js may be distributed under either license.
  24. */
  25. (function webpackUniversalModuleDefinition(root, factory) {
  26. if(typeof exports === 'object' && typeof module === 'object')
  27. module.exports = factory();
  28. else if(typeof define === 'function' && define.amd)
  29. define(factory);
  30. else if(typeof exports === 'object')
  31. exports["vis"] = factory();
  32. else
  33. root["vis"] = factory();
  34. })(this, function() {
  35. return /******/ (function(modules) { // webpackBootstrap
  36. /******/ // The module cache
  37. /******/ var installedModules = {};
  38. /******/
  39. /******/ // The require function
  40. /******/ function __webpack_require__(moduleId) {
  41. /******/
  42. /******/ // Check if module is in cache
  43. /******/ if(installedModules[moduleId])
  44. /******/ return installedModules[moduleId].exports;
  45. /******/
  46. /******/ // Create a new module (and put it into the cache)
  47. /******/ var module = installedModules[moduleId] = {
  48. /******/ exports: {},
  49. /******/ id: moduleId,
  50. /******/ loaded: false
  51. /******/ };
  52. /******/
  53. /******/ // Execute the module function
  54. /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
  55. /******/
  56. /******/ // Flag the module as loaded
  57. /******/ module.loaded = true;
  58. /******/
  59. /******/ // Return the exports of the module
  60. /******/ return module.exports;
  61. /******/ }
  62. /******/
  63. /******/
  64. /******/ // expose the modules object (__webpack_modules__)
  65. /******/ __webpack_require__.m = modules;
  66. /******/
  67. /******/ // expose the module cache
  68. /******/ __webpack_require__.c = installedModules;
  69. /******/
  70. /******/ // __webpack_public_path__
  71. /******/ __webpack_require__.p = "";
  72. /******/
  73. /******/ // Load entry module and return exports
  74. /******/ return __webpack_require__(0);
  75. /******/ })
  76. /************************************************************************/
  77. /******/ ([
  78. /* 0 */
  79. /***/ function(module, exports, __webpack_require__) {
  80. // utils
  81. exports.util = __webpack_require__(1);
  82. exports.DOMutil = __webpack_require__(2);
  83. // data
  84. exports.DataSet = __webpack_require__(3);
  85. exports.DataView = __webpack_require__(4);
  86. exports.Queue = __webpack_require__(5);
  87. // Graph3d
  88. exports.Graph3d = __webpack_require__(13);
  89. exports.graph3d = {
  90. Camera: __webpack_require__(14),
  91. Filter: __webpack_require__(15),
  92. Point2d: __webpack_require__(16),
  93. Point3d: __webpack_require__(18),
  94. Slider: __webpack_require__(17),
  95. StepNumber: __webpack_require__(19)
  96. };
  97. // Timeline
  98. exports.Timeline = __webpack_require__(6);
  99. exports.Graph2d = __webpack_require__(7);
  100. exports.timeline = {
  101. DateUtil: __webpack_require__(8),
  102. DataStep: __webpack_require__(9),
  103. Range: __webpack_require__(10),
  104. stack: __webpack_require__(11),
  105. TimeStep: __webpack_require__(12),
  106. components: {
  107. items: {
  108. Item: __webpack_require__(31),
  109. BackgroundItem: __webpack_require__(32),
  110. BoxItem: __webpack_require__(33),
  111. PointItem: __webpack_require__(34),
  112. RangeItem: __webpack_require__(35)
  113. },
  114. Component: __webpack_require__(20),
  115. CurrentTime: __webpack_require__(21),
  116. CustomTime: __webpack_require__(22),
  117. DataAxis: __webpack_require__(23),
  118. GraphGroup: __webpack_require__(25),
  119. Group: __webpack_require__(24),
  120. BackgroundGroup: __webpack_require__(26),
  121. ItemSet: __webpack_require__(27),
  122. Legend: __webpack_require__(28),
  123. LineGraph: __webpack_require__(29),
  124. TimeAxis: __webpack_require__(30)
  125. }
  126. };
  127. // Network
  128. exports.Network = __webpack_require__(36);
  129. exports.network = {
  130. Edge: __webpack_require__(37),
  131. Groups: __webpack_require__(38),
  132. Images: __webpack_require__(39),
  133. Node: __webpack_require__(40),
  134. Popup: __webpack_require__(41),
  135. dotparser: __webpack_require__(42),
  136. gephiParser: __webpack_require__(43)
  137. };
  138. // Deprecated since v3.0.0
  139. exports.Graph = function () {
  140. throw new Error('Graph is renamed to Network. Please create a graph as new vis.Network(...)');
  141. };
  142. // bundled external libraries
  143. exports.moment = __webpack_require__(44);
  144. exports.hammer = __webpack_require__(45);
  145. /***/ },
  146. /* 1 */
  147. /***/ function(module, exports, __webpack_require__) {
  148. // utility functions
  149. // first check if moment.js is already loaded in the browser window, if so,
  150. // use this instance. Else, load via commonjs.
  151. var moment = __webpack_require__(44);
  152. /**
  153. * Test whether given object is a number
  154. * @param {*} object
  155. * @return {Boolean} isNumber
  156. */
  157. exports.isNumber = function(object) {
  158. return (object instanceof Number || typeof object == 'number');
  159. };
  160. /**
  161. * Test whether given object is a string
  162. * @param {*} object
  163. * @return {Boolean} isString
  164. */
  165. exports.isString = function(object) {
  166. return (object instanceof String || typeof object == 'string');
  167. };
  168. /**
  169. * Test whether given object is a Date, or a String containing a Date
  170. * @param {Date | String} object
  171. * @return {Boolean} isDate
  172. */
  173. exports.isDate = function(object) {
  174. if (object instanceof Date) {
  175. return true;
  176. }
  177. else if (exports.isString(object)) {
  178. // test whether this string contains a date
  179. var match = ASPDateRegex.exec(object);
  180. if (match) {
  181. return true;
  182. }
  183. else if (!isNaN(Date.parse(object))) {
  184. return true;
  185. }
  186. }
  187. return false;
  188. };
  189. /**
  190. * Test whether given object is an instance of google.visualization.DataTable
  191. * @param {*} object
  192. * @return {Boolean} isDataTable
  193. */
  194. exports.isDataTable = function(object) {
  195. return (typeof (google) !== 'undefined') &&
  196. (google.visualization) &&
  197. (google.visualization.DataTable) &&
  198. (object instanceof google.visualization.DataTable);
  199. };
  200. /**
  201. * Create a semi UUID
  202. * source: http://stackoverflow.com/a/105074/1262753
  203. * @return {String} uuid
  204. */
  205. exports.randomUUID = function() {
  206. var S4 = function () {
  207. return Math.floor(
  208. Math.random() * 0x10000 /* 65536 */
  209. ).toString(16);
  210. };
  211. return (
  212. S4() + S4() + '-' +
  213. S4() + '-' +
  214. S4() + '-' +
  215. S4() + '-' +
  216. S4() + S4() + S4()
  217. );
  218. };
  219. /**
  220. * Extend object a with the properties of object b or a series of objects
  221. * Only properties with defined values are copied
  222. * @param {Object} a
  223. * @param {... Object} b
  224. * @return {Object} a
  225. */
  226. exports.extend = function (a, b) {
  227. for (var i = 1, len = arguments.length; i < len; i++) {
  228. var other = arguments[i];
  229. for (var prop in other) {
  230. if (other.hasOwnProperty(prop)) {
  231. a[prop] = other[prop];
  232. }
  233. }
  234. }
  235. return a;
  236. };
  237. /**
  238. * Extend object a with selected properties of object b or a series of objects
  239. * Only properties with defined values are copied
  240. * @param {Array.<String>} props
  241. * @param {Object} a
  242. * @param {... Object} b
  243. * @return {Object} a
  244. */
  245. exports.selectiveExtend = function (props, a, b) {
  246. if (!Array.isArray(props)) {
  247. throw new Error('Array with property names expected as first argument');
  248. }
  249. for (var i = 2; i < arguments.length; i++) {
  250. var other = arguments[i];
  251. for (var p = 0; p < props.length; p++) {
  252. var prop = props[p];
  253. if (other.hasOwnProperty(prop)) {
  254. a[prop] = other[prop];
  255. }
  256. }
  257. }
  258. return a;
  259. };
  260. /**
  261. * Extend object a with selected properties of object b or a series of objects
  262. * Only properties with defined values are copied
  263. * @param {Array.<String>} props
  264. * @param {Object} a
  265. * @param {... Object} b
  266. * @return {Object} a
  267. */
  268. exports.selectiveDeepExtend = function (props, a, b) {
  269. // TODO: add support for Arrays to deepExtend
  270. if (Array.isArray(b)) {
  271. throw new TypeError('Arrays are not supported by deepExtend');
  272. }
  273. for (var i = 2; i < arguments.length; i++) {
  274. var other = arguments[i];
  275. for (var p = 0; p < props.length; p++) {
  276. var prop = props[p];
  277. if (other.hasOwnProperty(prop)) {
  278. if (b[prop] && b[prop].constructor === Object) {
  279. if (a[prop] === undefined) {
  280. a[prop] = {};
  281. }
  282. if (a[prop].constructor === Object) {
  283. exports.deepExtend(a[prop], b[prop]);
  284. }
  285. else {
  286. a[prop] = b[prop];
  287. }
  288. } else if (Array.isArray(b[prop])) {
  289. throw new TypeError('Arrays are not supported by deepExtend');
  290. } else {
  291. a[prop] = b[prop];
  292. }
  293. }
  294. }
  295. }
  296. return a;
  297. };
  298. /**
  299. * Extend object a with selected properties of object b or a series of objects
  300. * Only properties with defined values are copied
  301. * @param {Array.<String>} props
  302. * @param {Object} a
  303. * @param {... Object} b
  304. * @return {Object} a
  305. */
  306. exports.selectiveNotDeepExtend = function (props, a, b) {
  307. // TODO: add support for Arrays to deepExtend
  308. if (Array.isArray(b)) {
  309. throw new TypeError('Arrays are not supported by deepExtend');
  310. }
  311. for (var prop in b) {
  312. if (b.hasOwnProperty(prop)) {
  313. if (props.indexOf(prop) == -1) {
  314. if (b[prop] && b[prop].constructor === Object) {
  315. if (a[prop] === undefined) {
  316. a[prop] = {};
  317. }
  318. if (a[prop].constructor === Object) {
  319. exports.deepExtend(a[prop], b[prop]);
  320. }
  321. else {
  322. a[prop] = b[prop];
  323. }
  324. } else if (Array.isArray(b[prop])) {
  325. throw new TypeError('Arrays are not supported by deepExtend');
  326. } else {
  327. a[prop] = b[prop];
  328. }
  329. }
  330. }
  331. }
  332. return a;
  333. };
  334. /**
  335. * Deep extend an object a with the properties of object b
  336. * @param {Object} a
  337. * @param {Object} b
  338. * @returns {Object}
  339. */
  340. exports.deepExtend = function(a, b) {
  341. // TODO: add support for Arrays to deepExtend
  342. if (Array.isArray(b)) {
  343. throw new TypeError('Arrays are not supported by deepExtend');
  344. }
  345. for (var prop in b) {
  346. if (b.hasOwnProperty(prop)) {
  347. if (b[prop] && b[prop].constructor === Object) {
  348. if (a[prop] === undefined) {
  349. a[prop] = {};
  350. }
  351. if (a[prop].constructor === Object) {
  352. exports.deepExtend(a[prop], b[prop]);
  353. }
  354. else {
  355. a[prop] = b[prop];
  356. }
  357. } else if (Array.isArray(b[prop])) {
  358. throw new TypeError('Arrays are not supported by deepExtend');
  359. } else {
  360. a[prop] = b[prop];
  361. }
  362. }
  363. }
  364. return a;
  365. };
  366. /**
  367. * Test whether all elements in two arrays are equal.
  368. * @param {Array} a
  369. * @param {Array} b
  370. * @return {boolean} Returns true if both arrays have the same length and same
  371. * elements.
  372. */
  373. exports.equalArray = function (a, b) {
  374. if (a.length != b.length) return false;
  375. for (var i = 0, len = a.length; i < len; i++) {
  376. if (a[i] != b[i]) return false;
  377. }
  378. return true;
  379. };
  380. /**
  381. * Convert an object to another type
  382. * @param {Boolean | Number | String | Date | Moment | Null | undefined} object
  383. * @param {String | undefined} type Name of the type. Available types:
  384. * 'Boolean', 'Number', 'String',
  385. * 'Date', 'Moment', ISODate', 'ASPDate'.
  386. * @return {*} object
  387. * @throws Error
  388. */
  389. exports.convert = function(object, type) {
  390. var match;
  391. if (object === undefined) {
  392. return undefined;
  393. }
  394. if (object === null) {
  395. return null;
  396. }
  397. if (!type) {
  398. return object;
  399. }
  400. if (!(typeof type === 'string') && !(type instanceof String)) {
  401. throw new Error('Type must be a string');
  402. }
  403. //noinspection FallthroughInSwitchStatementJS
  404. switch (type) {
  405. case 'boolean':
  406. case 'Boolean':
  407. return Boolean(object);
  408. case 'number':
  409. case 'Number':
  410. return Number(object.valueOf());
  411. case 'string':
  412. case 'String':
  413. return String(object);
  414. case 'Date':
  415. if (exports.isNumber(object)) {
  416. return new Date(object);
  417. }
  418. if (object instanceof Date) {
  419. return new Date(object.valueOf());
  420. }
  421. else if (moment.isMoment(object)) {
  422. return new Date(object.valueOf());
  423. }
  424. if (exports.isString(object)) {
  425. match = ASPDateRegex.exec(object);
  426. if (match) {
  427. // object is an ASP date
  428. return new Date(Number(match[1])); // parse number
  429. }
  430. else {
  431. return moment(object).toDate(); // parse string
  432. }
  433. }
  434. else {
  435. throw new Error(
  436. 'Cannot convert object of type ' + exports.getType(object) +
  437. ' to type Date');
  438. }
  439. case 'Moment':
  440. if (exports.isNumber(object)) {
  441. return moment(object);
  442. }
  443. if (object instanceof Date) {
  444. return moment(object.valueOf());
  445. }
  446. else if (moment.isMoment(object)) {
  447. return moment(object);
  448. }
  449. if (exports.isString(object)) {
  450. match = ASPDateRegex.exec(object);
  451. if (match) {
  452. // object is an ASP date
  453. return moment(Number(match[1])); // parse number
  454. }
  455. else {
  456. return moment(object); // parse string
  457. }
  458. }
  459. else {
  460. throw new Error(
  461. 'Cannot convert object of type ' + exports.getType(object) +
  462. ' to type Date');
  463. }
  464. case 'ISODate':
  465. if (exports.isNumber(object)) {
  466. return new Date(object);
  467. }
  468. else if (object instanceof Date) {
  469. return object.toISOString();
  470. }
  471. else if (moment.isMoment(object)) {
  472. return object.toDate().toISOString();
  473. }
  474. else if (exports.isString(object)) {
  475. match = ASPDateRegex.exec(object);
  476. if (match) {
  477. // object is an ASP date
  478. return new Date(Number(match[1])).toISOString(); // parse number
  479. }
  480. else {
  481. return new Date(object).toISOString(); // parse string
  482. }
  483. }
  484. else {
  485. throw new Error(
  486. 'Cannot convert object of type ' + exports.getType(object) +
  487. ' to type ISODate');
  488. }
  489. case 'ASPDate':
  490. if (exports.isNumber(object)) {
  491. return '/Date(' + object + ')/';
  492. }
  493. else if (object instanceof Date) {
  494. return '/Date(' + object.valueOf() + ')/';
  495. }
  496. else if (exports.isString(object)) {
  497. match = ASPDateRegex.exec(object);
  498. var value;
  499. if (match) {
  500. // object is an ASP date
  501. value = new Date(Number(match[1])).valueOf(); // parse number
  502. }
  503. else {
  504. value = new Date(object).valueOf(); // parse string
  505. }
  506. return '/Date(' + value + ')/';
  507. }
  508. else {
  509. throw new Error(
  510. 'Cannot convert object of type ' + exports.getType(object) +
  511. ' to type ASPDate');
  512. }
  513. default:
  514. throw new Error('Unknown type "' + type + '"');
  515. }
  516. };
  517. // parse ASP.Net Date pattern,
  518. // for example '/Date(1198908717056)/' or '/Date(1198908717056-0700)/'
  519. // code from http://momentjs.com/
  520. var ASPDateRegex = /^\/?Date\((\-?\d+)/i;
  521. /**
  522. * Get the type of an object, for example exports.getType([]) returns 'Array'
  523. * @param {*} object
  524. * @return {String} type
  525. */
  526. exports.getType = function(object) {
  527. var type = typeof object;
  528. if (type == 'object') {
  529. if (object == null) {
  530. return 'null';
  531. }
  532. if (object instanceof Boolean) {
  533. return 'Boolean';
  534. }
  535. if (object instanceof Number) {
  536. return 'Number';
  537. }
  538. if (object instanceof String) {
  539. return 'String';
  540. }
  541. if (Array.isArray(object)) {
  542. return 'Array';
  543. }
  544. if (object instanceof Date) {
  545. return 'Date';
  546. }
  547. return 'Object';
  548. }
  549. else if (type == 'number') {
  550. return 'Number';
  551. }
  552. else if (type == 'boolean') {
  553. return 'Boolean';
  554. }
  555. else if (type == 'string') {
  556. return 'String';
  557. }
  558. return type;
  559. };
  560. /**
  561. * Retrieve the absolute left value of a DOM element
  562. * @param {Element} elem A dom element, for example a div
  563. * @return {number} left The absolute left position of this element
  564. * in the browser page.
  565. */
  566. exports.getAbsoluteLeft = function(elem) {
  567. return elem.getBoundingClientRect().left + window.pageXOffset;
  568. };
  569. /**
  570. * Retrieve the absolute top value of a DOM element
  571. * @param {Element} elem A dom element, for example a div
  572. * @return {number} top The absolute top position of this element
  573. * in the browser page.
  574. */
  575. exports.getAbsoluteTop = function(elem) {
  576. return elem.getBoundingClientRect().top + window.pageYOffset;
  577. };
  578. /**
  579. * add a className to the given elements style
  580. * @param {Element} elem
  581. * @param {String} className
  582. */
  583. exports.addClassName = function(elem, className) {
  584. var classes = elem.className.split(' ');
  585. if (classes.indexOf(className) == -1) {
  586. classes.push(className); // add the class to the array
  587. elem.className = classes.join(' ');
  588. }
  589. };
  590. /**
  591. * add a className to the given elements style
  592. * @param {Element} elem
  593. * @param {String} className
  594. */
  595. exports.removeClassName = function(elem, className) {
  596. var classes = elem.className.split(' ');
  597. var index = classes.indexOf(className);
  598. if (index != -1) {
  599. classes.splice(index, 1); // remove the class from the array
  600. elem.className = classes.join(' ');
  601. }
  602. };
  603. /**
  604. * For each method for both arrays and objects.
  605. * In case of an array, the built-in Array.forEach() is applied.
  606. * In case of an Object, the method loops over all properties of the object.
  607. * @param {Object | Array} object An Object or Array
  608. * @param {function} callback Callback method, called for each item in
  609. * the object or array with three parameters:
  610. * callback(value, index, object)
  611. */
  612. exports.forEach = function(object, callback) {
  613. var i,
  614. len;
  615. if (Array.isArray(object)) {
  616. // array
  617. for (i = 0, len = object.length; i < len; i++) {
  618. callback(object[i], i, object);
  619. }
  620. }
  621. else {
  622. // object
  623. for (i in object) {
  624. if (object.hasOwnProperty(i)) {
  625. callback(object[i], i, object);
  626. }
  627. }
  628. }
  629. };
  630. /**
  631. * Convert an object into an array: all objects properties are put into the
  632. * array. The resulting array is unordered.
  633. * @param {Object} object
  634. * @param {Array} array
  635. */
  636. exports.toArray = function(object) {
  637. var array = [];
  638. for (var prop in object) {
  639. if (object.hasOwnProperty(prop)) array.push(object[prop]);
  640. }
  641. return array;
  642. }
  643. /**
  644. * Update a property in an object
  645. * @param {Object} object
  646. * @param {String} key
  647. * @param {*} value
  648. * @return {Boolean} changed
  649. */
  650. exports.updateProperty = function(object, key, value) {
  651. if (object[key] !== value) {
  652. object[key] = value;
  653. return true;
  654. }
  655. else {
  656. return false;
  657. }
  658. };
  659. /**
  660. * Add and event listener. Works for all browsers
  661. * @param {Element} element An html element
  662. * @param {string} action The action, for example "click",
  663. * without the prefix "on"
  664. * @param {function} listener The callback function to be executed
  665. * @param {boolean} [useCapture]
  666. */
  667. exports.addEventListener = function(element, action, listener, useCapture) {
  668. if (element.addEventListener) {
  669. if (useCapture === undefined)
  670. useCapture = false;
  671. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  672. action = "DOMMouseScroll"; // For Firefox
  673. }
  674. element.addEventListener(action, listener, useCapture);
  675. } else {
  676. element.attachEvent("on" + action, listener); // IE browsers
  677. }
  678. };
  679. /**
  680. * Remove an event listener from an element
  681. * @param {Element} element An html dom element
  682. * @param {string} action The name of the event, for example "mousedown"
  683. * @param {function} listener The listener function
  684. * @param {boolean} [useCapture]
  685. */
  686. exports.removeEventListener = function(element, action, listener, useCapture) {
  687. if (element.removeEventListener) {
  688. // non-IE browsers
  689. if (useCapture === undefined)
  690. useCapture = false;
  691. if (action === "mousewheel" && navigator.userAgent.indexOf("Firefox") >= 0) {
  692. action = "DOMMouseScroll"; // For Firefox
  693. }
  694. element.removeEventListener(action, listener, useCapture);
  695. } else {
  696. // IE browsers
  697. element.detachEvent("on" + action, listener);
  698. }
  699. };
  700. /**
  701. * Cancels the event if it is cancelable, without stopping further propagation of the event.
  702. */
  703. exports.preventDefault = function (event) {
  704. if (!event)
  705. event = window.event;
  706. if (event.preventDefault) {
  707. event.preventDefault(); // non-IE browsers
  708. }
  709. else {
  710. event.returnValue = false; // IE browsers
  711. }
  712. };
  713. /**
  714. * Get HTML element which is the target of the event
  715. * @param {Event} event
  716. * @return {Element} target element
  717. */
  718. exports.getTarget = function(event) {
  719. // code from http://www.quirksmode.org/js/events_properties.html
  720. if (!event) {
  721. event = window.event;
  722. }
  723. var target;
  724. if (event.target) {
  725. target = event.target;
  726. }
  727. else if (event.srcElement) {
  728. target = event.srcElement;
  729. }
  730. if (target.nodeType != undefined && target.nodeType == 3) {
  731. // defeat Safari bug
  732. target = target.parentNode;
  733. }
  734. return target;
  735. };
  736. exports.option = {};
  737. /**
  738. * Convert a value into a boolean
  739. * @param {Boolean | function | undefined} value
  740. * @param {Boolean} [defaultValue]
  741. * @returns {Boolean} bool
  742. */
  743. exports.option.asBoolean = function (value, defaultValue) {
  744. if (typeof value == 'function') {
  745. value = value();
  746. }
  747. if (value != null) {
  748. return (value != false);
  749. }
  750. return defaultValue || null;
  751. };
  752. /**
  753. * Convert a value into a number
  754. * @param {Boolean | function | undefined} value
  755. * @param {Number} [defaultValue]
  756. * @returns {Number} number
  757. */
  758. exports.option.asNumber = function (value, defaultValue) {
  759. if (typeof value == 'function') {
  760. value = value();
  761. }
  762. if (value != null) {
  763. return Number(value) || defaultValue || null;
  764. }
  765. return defaultValue || null;
  766. };
  767. /**
  768. * Convert a value into a string
  769. * @param {String | function | undefined} value
  770. * @param {String} [defaultValue]
  771. * @returns {String} str
  772. */
  773. exports.option.asString = function (value, defaultValue) {
  774. if (typeof value == 'function') {
  775. value = value();
  776. }
  777. if (value != null) {
  778. return String(value);
  779. }
  780. return defaultValue || null;
  781. };
  782. /**
  783. * Convert a size or location into a string with pixels or a percentage
  784. * @param {String | Number | function | undefined} value
  785. * @param {String} [defaultValue]
  786. * @returns {String} size
  787. */
  788. exports.option.asSize = function (value, defaultValue) {
  789. if (typeof value == 'function') {
  790. value = value();
  791. }
  792. if (exports.isString(value)) {
  793. return value;
  794. }
  795. else if (exports.isNumber(value)) {
  796. return value + 'px';
  797. }
  798. else {
  799. return defaultValue || null;
  800. }
  801. };
  802. /**
  803. * Convert a value into a DOM element
  804. * @param {HTMLElement | function | undefined} value
  805. * @param {HTMLElement} [defaultValue]
  806. * @returns {HTMLElement | null} dom
  807. */
  808. exports.option.asElement = function (value, defaultValue) {
  809. if (typeof value == 'function') {
  810. value = value();
  811. }
  812. return value || defaultValue || null;
  813. };
  814. exports.GiveDec = function(Hex) {
  815. var Value;
  816. if (Hex == "A")
  817. Value = 10;
  818. else if (Hex == "B")
  819. Value = 11;
  820. else if (Hex == "C")
  821. Value = 12;
  822. else if (Hex == "D")
  823. Value = 13;
  824. else if (Hex == "E")
  825. Value = 14;
  826. else if (Hex == "F")
  827. Value = 15;
  828. else
  829. Value = eval(Hex);
  830. return Value;
  831. };
  832. exports.GiveHex = function(Dec) {
  833. var Value;
  834. if(Dec == 10)
  835. Value = "A";
  836. else if (Dec == 11)
  837. Value = "B";
  838. else if (Dec == 12)
  839. Value = "C";
  840. else if (Dec == 13)
  841. Value = "D";
  842. else if (Dec == 14)
  843. Value = "E";
  844. else if (Dec == 15)
  845. Value = "F";
  846. else
  847. Value = "" + Dec;
  848. return Value;
  849. };
  850. /**
  851. * Parse a color property into an object with border, background, and
  852. * highlight colors
  853. * @param {Object | String} color
  854. * @return {Object} colorObject
  855. */
  856. exports.parseColor = function(color) {
  857. var c;
  858. if (exports.isString(color)) {
  859. if (exports.isValidRGB(color)) {
  860. var rgb = color.substr(4).substr(0,color.length-5).split(',');
  861. color = exports.RGBToHex(rgb[0],rgb[1],rgb[2]);
  862. }
  863. if (exports.isValidHex(color)) {
  864. var hsv = exports.hexToHSV(color);
  865. var lighterColorHSV = {h:hsv.h,s:hsv.s * 0.45,v:Math.min(1,hsv.v * 1.05)};
  866. var darkerColorHSV = {h:hsv.h,s:Math.min(1,hsv.v * 1.25),v:hsv.v*0.6};
  867. var darkerColorHex = exports.HSVToHex(darkerColorHSV.h ,darkerColorHSV.h ,darkerColorHSV.v);
  868. var lighterColorHex = exports.HSVToHex(lighterColorHSV.h,lighterColorHSV.s,lighterColorHSV.v);
  869. c = {
  870. background: color,
  871. border:darkerColorHex,
  872. highlight: {
  873. background:lighterColorHex,
  874. border:darkerColorHex
  875. },
  876. hover: {
  877. background:lighterColorHex,
  878. border:darkerColorHex
  879. }
  880. };
  881. }
  882. else {
  883. c = {
  884. background:color,
  885. border:color,
  886. highlight: {
  887. background:color,
  888. border:color
  889. },
  890. hover: {
  891. background:color,
  892. border:color
  893. }
  894. };
  895. }
  896. }
  897. else {
  898. c = {};
  899. c.background = color.background || 'white';
  900. c.border = color.border || c.background;
  901. if (exports.isString(color.highlight)) {
  902. c.highlight = {
  903. border: color.highlight,
  904. background: color.highlight
  905. }
  906. }
  907. else {
  908. c.highlight = {};
  909. c.highlight.background = color.highlight && color.highlight.background || c.background;
  910. c.highlight.border = color.highlight && color.highlight.border || c.border;
  911. }
  912. if (exports.isString(color.hover)) {
  913. c.hover = {
  914. border: color.hover,
  915. background: color.hover
  916. }
  917. }
  918. else {
  919. c.hover = {};
  920. c.hover.background = color.hover && color.hover.background || c.background;
  921. c.hover.border = color.hover && color.hover.border || c.border;
  922. }
  923. }
  924. return c;
  925. };
  926. /**
  927. * http://www.yellowpipe.com/yis/tools/hex-to-rgb/color-converter.php
  928. *
  929. * @param {String} hex
  930. * @returns {{r: *, g: *, b: *}}
  931. */
  932. exports.hexToRGB = function(hex) {
  933. hex = hex.replace("#","").toUpperCase();
  934. var a = exports.GiveDec(hex.substring(0, 1));
  935. var b = exports.GiveDec(hex.substring(1, 2));
  936. var c = exports.GiveDec(hex.substring(2, 3));
  937. var d = exports.GiveDec(hex.substring(3, 4));
  938. var e = exports.GiveDec(hex.substring(4, 5));
  939. var f = exports.GiveDec(hex.substring(5, 6));
  940. var r = (a * 16) + b;
  941. var g = (c * 16) + d;
  942. var b = (e * 16) + f;
  943. return {r:r,g:g,b:b};
  944. };
  945. exports.RGBToHex = function(red,green,blue) {
  946. var a = exports.GiveHex(Math.floor(red / 16));
  947. var b = exports.GiveHex(red % 16);
  948. var c = exports.GiveHex(Math.floor(green / 16));
  949. var d = exports.GiveHex(green % 16);
  950. var e = exports.GiveHex(Math.floor(blue / 16));
  951. var f = exports.GiveHex(blue % 16);
  952. var hex = a + b + c + d + e + f;
  953. return "#" + hex;
  954. };
  955. /**
  956. * http://www.javascripter.net/faq/rgb2hsv.htm
  957. *
  958. * @param red
  959. * @param green
  960. * @param blue
  961. * @returns {*}
  962. * @constructor
  963. */
  964. exports.RGBToHSV = function(red,green,blue) {
  965. red=red/255; green=green/255; blue=blue/255;
  966. var minRGB = Math.min(red,Math.min(green,blue));
  967. var maxRGB = Math.max(red,Math.max(green,blue));
  968. // Black-gray-white
  969. if (minRGB == maxRGB) {
  970. return {h:0,s:0,v:minRGB};
  971. }
  972. // Colors other than black-gray-white:
  973. var d = (red==minRGB) ? green-blue : ((blue==minRGB) ? red-green : blue-red);
  974. var h = (red==minRGB) ? 3 : ((blue==minRGB) ? 1 : 5);
  975. var hue = 60*(h - d/(maxRGB - minRGB))/360;
  976. var saturation = (maxRGB - minRGB)/maxRGB;
  977. var value = maxRGB;
  978. return {h:hue,s:saturation,v:value};
  979. };
  980. var cssUtil = {
  981. // split a string with css styles into an object with key/values
  982. split: function (cssText) {
  983. var styles = {};
  984. cssText.split(';').forEach(function (style) {
  985. if (style.trim() != '') {
  986. var parts = style.split(':');
  987. var key = parts[0].trim();
  988. var value = parts[1].trim();
  989. styles[key] = value;
  990. }
  991. });
  992. return styles;
  993. },
  994. // build a css text string from an object with key/values
  995. join: function (styles) {
  996. return Object.keys(styles)
  997. .map(function (key) {
  998. return key + ': ' + styles[key];
  999. })
  1000. .join('; ');
  1001. }
  1002. };
  1003. /**
  1004. * Append a string with css styles to an element
  1005. * @param {Element} element
  1006. * @param {String} cssText
  1007. */
  1008. exports.addCssText = function (element, cssText) {
  1009. var currentStyles = cssUtil.split(element.style.cssText);
  1010. var newStyles = cssUtil.split(cssText);
  1011. var styles = exports.extend(currentStyles, newStyles);
  1012. element.style.cssText = cssUtil.join(styles);
  1013. };
  1014. /**
  1015. * Remove a string with css styles from an element
  1016. * @param {Element} element
  1017. * @param {String} cssText
  1018. */
  1019. exports.removeCssText = function (element, cssText) {
  1020. var styles = cssUtil.split(element.style.cssText);
  1021. var removeStyles = cssUtil.split(cssText);
  1022. for (var key in removeStyles) {
  1023. if (removeStyles.hasOwnProperty(key)) {
  1024. delete styles[key];
  1025. }
  1026. }
  1027. element.style.cssText = cssUtil.join(styles);
  1028. };
  1029. /**
  1030. * https://gist.github.com/mjijackson/5311256
  1031. * @param h
  1032. * @param s
  1033. * @param v
  1034. * @returns {{r: number, g: number, b: number}}
  1035. * @constructor
  1036. */
  1037. exports.HSVToRGB = function(h, s, v) {
  1038. var r, g, b;
  1039. var i = Math.floor(h * 6);
  1040. var f = h * 6 - i;
  1041. var p = v * (1 - s);
  1042. var q = v * (1 - f * s);
  1043. var t = v * (1 - (1 - f) * s);
  1044. switch (i % 6) {
  1045. case 0: r = v, g = t, b = p; break;
  1046. case 1: r = q, g = v, b = p; break;
  1047. case 2: r = p, g = v, b = t; break;
  1048. case 3: r = p, g = q, b = v; break;
  1049. case 4: r = t, g = p, b = v; break;
  1050. case 5: r = v, g = p, b = q; break;
  1051. }
  1052. return {r:Math.floor(r * 255), g:Math.floor(g * 255), b:Math.floor(b * 255) };
  1053. };
  1054. exports.HSVToHex = function(h, s, v) {
  1055. var rgb = exports.HSVToRGB(h, s, v);
  1056. return exports.RGBToHex(rgb.r, rgb.g, rgb.b);
  1057. };
  1058. exports.hexToHSV = function(hex) {
  1059. var rgb = exports.hexToRGB(hex);
  1060. return exports.RGBToHSV(rgb.r, rgb.g, rgb.b);
  1061. };
  1062. exports.isValidHex = function(hex) {
  1063. var isOk = /(^#[0-9A-F]{6}$)|(^#[0-9A-F]{3}$)/i.test(hex);
  1064. return isOk;
  1065. };
  1066. exports.isValidRGB = function(rgb) {
  1067. rgb = rgb.replace(" ","");
  1068. var isOk = /rgb\((\d{1,3}),(\d{1,3}),(\d{1,3})\)/i.test(rgb);
  1069. return isOk;
  1070. }
  1071. /**
  1072. * This recursively redirects the prototype of JSON objects to the referenceObject
  1073. * This is used for default options.
  1074. *
  1075. * @param referenceObject
  1076. * @returns {*}
  1077. */
  1078. exports.selectiveBridgeObject = function(fields, referenceObject) {
  1079. if (typeof referenceObject == "object") {
  1080. var objectTo = Object.create(referenceObject);
  1081. for (var i = 0; i < fields.length; i++) {
  1082. if (referenceObject.hasOwnProperty(fields[i])) {
  1083. if (typeof referenceObject[fields[i]] == "object") {
  1084. objectTo[fields[i]] = exports.bridgeObject(referenceObject[fields[i]]);
  1085. }
  1086. }
  1087. }
  1088. return objectTo;
  1089. }
  1090. else {
  1091. return null;
  1092. }
  1093. };
  1094. /**
  1095. * This recursively redirects the prototype of JSON objects to the referenceObject
  1096. * This is used for default options.
  1097. *
  1098. * @param referenceObject
  1099. * @returns {*}
  1100. */
  1101. exports.bridgeObject = function(referenceObject) {
  1102. if (typeof referenceObject == "object") {
  1103. var objectTo = Object.create(referenceObject);
  1104. for (var i in referenceObject) {
  1105. if (referenceObject.hasOwnProperty(i)) {
  1106. if (typeof referenceObject[i] == "object") {
  1107. objectTo[i] = exports.bridgeObject(referenceObject[i]);
  1108. }
  1109. }
  1110. }
  1111. return objectTo;
  1112. }
  1113. else {
  1114. return null;
  1115. }
  1116. };
  1117. /**
  1118. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1119. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1120. *
  1121. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1122. * @param [object] options | options
  1123. * @param [String] option | this is the option key in the options argument
  1124. * @private
  1125. */
  1126. exports.mergeOptions = function (mergeTarget, options, option) {
  1127. if (options[option] !== undefined) {
  1128. if (typeof options[option] == 'boolean') {
  1129. mergeTarget[option].enabled = options[option];
  1130. }
  1131. else {
  1132. mergeTarget[option].enabled = true;
  1133. for (prop in options[option]) {
  1134. if (options[option].hasOwnProperty(prop)) {
  1135. mergeTarget[option][prop] = options[option][prop];
  1136. }
  1137. }
  1138. }
  1139. }
  1140. }
  1141. /**
  1142. * this is used to set the options of subobjects in the options object. A requirement of these subobjects
  1143. * is that they have an 'enabled' element which is optional for the user but mandatory for the program.
  1144. *
  1145. * @param [object] mergeTarget | this is either this.options or the options used for the groups.
  1146. * @param [object] options | options
  1147. * @param [String] option | this is the option key in the options argument
  1148. * @private
  1149. */
  1150. exports.mergeOptions = function (mergeTarget, options, option) {
  1151. if (options[option] !== undefined) {
  1152. if (typeof options[option] == 'boolean') {
  1153. mergeTarget[option].enabled = options[option];
  1154. }
  1155. else {
  1156. mergeTarget[option].enabled = true;
  1157. for (prop in options[option]) {
  1158. if (options[option].hasOwnProperty(prop)) {
  1159. mergeTarget[option][prop] = options[option][prop];
  1160. }
  1161. }
  1162. }
  1163. }
  1164. }
  1165. /**
  1166. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1167. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1168. * This is done to be able to select the correct if statement (we do not want to check if an item is visible, we want to check
  1169. * if the time we selected (start or end) is within the current range).
  1170. *
  1171. * The trick is that every interval has to either enter the screen at the initial load or by dragging. The case of the RangeItem that is
  1172. * before and after the current range is handled by simply checking if it was in view before and if it is again. For all the rest,
  1173. * either the start OR end time has to be in the range.
  1174. *
  1175. * @param {Item[]} orderedItems Items ordered by start
  1176. * @param {{start: number, end: number}} range
  1177. * @param {String} field
  1178. * @param {String} field2
  1179. * @returns {number}
  1180. * @private
  1181. */
  1182. exports.binarySearch = function(orderedItems, range, field, field2) {
  1183. var maxIterations = 10000;
  1184. var iteration = 0;
  1185. var low = 0;
  1186. var high = orderedItems.length - 1;
  1187. while (low <= high && iteration < maxIterations) {
  1188. var middle = Math.floor((low + high) / 2);
  1189. var item = orderedItems[middle];
  1190. if (item.isVisible(range)) { // jihaa, found a visible item!
  1191. return middle;
  1192. }
  1193. var value = (field2 === undefined) ? item[field] : item[field][field2];
  1194. if (value < range.start) { // it is too small --> increase low
  1195. low = middle + 1;
  1196. }
  1197. else { // it is too big --> decrease high
  1198. high = middle - 1;
  1199. }
  1200. iteration++;
  1201. }
  1202. return -1;
  1203. };
  1204. /**
  1205. * This function does a binary search for a visible item. The user can select either the this.orderedItems.byStart or .byEnd
  1206. * arrays. This is done by giving a boolean value true if you want to use the byEnd.
  1207. * This is done to be able to select the correct if statement (we do not want to check if an item is visible, we want to check
  1208. * if the time we selected (start or end) is within the current range).
  1209. *
  1210. * The trick is that every interval has to either enter the screen at the initial load or by dragging. The case of the RangeItem that is
  1211. * before and after the current range is handled by simply checking if it was in view before and if it is again. For all the rest,
  1212. * either the start OR end time has to be in the range.
  1213. *
  1214. * @param {Array} orderedItems
  1215. * @param {{start: number, end: number}} target
  1216. * @param {String} field
  1217. * @param {String} sidePreference 'before' or 'after'
  1218. * @returns {number}
  1219. * @private
  1220. */
  1221. exports.binarySearchGeneric = function(orderedItems, target, field, sidePreference) {
  1222. var maxIterations = 10000;
  1223. var iteration = 0;
  1224. var array = orderedItems;
  1225. var found = false;
  1226. var low = 0;
  1227. var high = array.length;
  1228. var newLow = low;
  1229. var newHigh = high;
  1230. var guess = Math.floor(0.5*(high+low));
  1231. var newGuess;
  1232. var prevValue, value, nextValue;
  1233. if (high == 0) {guess = -1;}
  1234. else if (high == 1) {
  1235. value = array[guess][field];
  1236. if (value == target) {
  1237. guess = 0;
  1238. }
  1239. else {
  1240. guess = -1;
  1241. }
  1242. }
  1243. else {
  1244. high -= 1;
  1245. while (found == false && iteration < maxIterations) {
  1246. prevValue = array[Math.max(0,guess - 1)][field];
  1247. value = array[guess][field];
  1248. nextValue = array[Math.min(array.length-1,guess + 1)][field];
  1249. if (value == target || prevValue < target && value > target || value < target && nextValue > target) {
  1250. found = true;
  1251. if (value != target) {
  1252. if (sidePreference == 'before') {
  1253. if (prevValue < target && value > target) {
  1254. guess = Math.max(0,guess - 1);
  1255. }
  1256. }
  1257. else {
  1258. if (value < target && nextValue > target) {
  1259. guess = Math.min(array.length-1,guess + 1);
  1260. }
  1261. }
  1262. }
  1263. }
  1264. else {
  1265. if (value < target) { // it is too small --> increase low
  1266. newLow = Math.floor(0.5*(high+low));
  1267. }
  1268. else { // it is too big --> decrease high
  1269. newHigh = Math.floor(0.5*(high+low));
  1270. }
  1271. newGuess = Math.floor(0.5*(high+low));
  1272. // not in list;
  1273. if (low == newLow && high == newHigh) {
  1274. guess = -1;
  1275. found = true;
  1276. }
  1277. else {
  1278. high = newHigh; low = newLow;
  1279. guess = Math.floor(0.5*(high+low));
  1280. }
  1281. }
  1282. iteration++;
  1283. }
  1284. if (iteration >= maxIterations) {
  1285. console.log("BinarySearch too many iterations. Aborting.");
  1286. }
  1287. }
  1288. return guess;
  1289. };
  1290. /**
  1291. * Quadratic ease-in-out
  1292. * http://gizma.com/easing/
  1293. * @param {number} t Current time
  1294. * @param {number} start Start value
  1295. * @param {number} end End value
  1296. * @param {number} duration Duration
  1297. * @returns {number} Value corresponding with current time
  1298. */
  1299. exports.easeInOutQuad = function (t, start, end, duration) {
  1300. var change = end - start;
  1301. t /= duration/2;
  1302. if (t < 1) return change/2*t*t + start;
  1303. t--;
  1304. return -change/2 * (t*(t-2) - 1) + start;
  1305. };
  1306. /*
  1307. * Easing Functions - inspired from http://gizma.com/easing/
  1308. * only considering the t value for the range [0, 1] => [0, 1]
  1309. * https://gist.github.com/gre/1650294
  1310. */
  1311. exports.easingFunctions = {
  1312. // no easing, no acceleration
  1313. linear: function (t) {
  1314. return t
  1315. },
  1316. // accelerating from zero velocity
  1317. easeInQuad: function (t) {
  1318. return t * t
  1319. },
  1320. // decelerating to zero velocity
  1321. easeOutQuad: function (t) {
  1322. return t * (2 - t)
  1323. },
  1324. // acceleration until halfway, then deceleration
  1325. easeInOutQuad: function (t) {
  1326. return t < .5 ? 2 * t * t : -1 + (4 - 2 * t) * t
  1327. },
  1328. // accelerating from zero velocity
  1329. easeInCubic: function (t) {
  1330. return t * t * t
  1331. },
  1332. // decelerating to zero velocity
  1333. easeOutCubic: function (t) {
  1334. return (--t) * t * t + 1
  1335. },
  1336. // acceleration until halfway, then deceleration
  1337. easeInOutCubic: function (t) {
  1338. return t < .5 ? 4 * t * t * t : (t - 1) * (2 * t - 2) * (2 * t - 2) + 1
  1339. },
  1340. // accelerating from zero velocity
  1341. easeInQuart: function (t) {
  1342. return t * t * t * t
  1343. },
  1344. // decelerating to zero velocity
  1345. easeOutQuart: function (t) {
  1346. return 1 - (--t) * t * t * t
  1347. },
  1348. // acceleration until halfway, then deceleration
  1349. easeInOutQuart: function (t) {
  1350. return t < .5 ? 8 * t * t * t * t : 1 - 8 * (--t) * t * t * t
  1351. },
  1352. // accelerating from zero velocity
  1353. easeInQuint: function (t) {
  1354. return t * t * t * t * t
  1355. },
  1356. // decelerating to zero velocity
  1357. easeOutQuint: function (t) {
  1358. return 1 + (--t) * t * t * t * t
  1359. },
  1360. // acceleration until halfway, then deceleration
  1361. easeInOutQuint: function (t) {
  1362. return t < .5 ? 16 * t * t * t * t * t : 1 + 16 * (--t) * t * t * t * t
  1363. }
  1364. };
  1365. /***/ },
  1366. /* 2 */
  1367. /***/ function(module, exports, __webpack_require__) {
  1368. // DOM utility methods
  1369. /**
  1370. * this prepares the JSON container for allocating SVG elements
  1371. * @param JSONcontainer
  1372. * @private
  1373. */
  1374. exports.prepareElements = function(JSONcontainer) {
  1375. // cleanup the redundant svgElements;
  1376. for (var elementType in JSONcontainer) {
  1377. if (JSONcontainer.hasOwnProperty(elementType)) {
  1378. JSONcontainer[elementType].redundant = JSONcontainer[elementType].used;
  1379. JSONcontainer[elementType].used = [];
  1380. }
  1381. }
  1382. };
  1383. /**
  1384. * this cleans up all the unused SVG elements. By asking for the parentNode, we only need to supply the JSON container from
  1385. * which to remove the redundant elements.
  1386. *
  1387. * @param JSONcontainer
  1388. * @private
  1389. */
  1390. exports.cleanupElements = function(JSONcontainer) {
  1391. // cleanup the redundant svgElements;
  1392. for (var elementType in JSONcontainer) {
  1393. if (JSONcontainer.hasOwnProperty(elementType)) {
  1394. if (JSONcontainer[elementType].redundant) {
  1395. for (var i = 0; i < JSONcontainer[elementType].redundant.length; i++) {
  1396. JSONcontainer[elementType].redundant[i].parentNode.removeChild(JSONcontainer[elementType].redundant[i]);
  1397. }
  1398. JSONcontainer[elementType].redundant = [];
  1399. }
  1400. }
  1401. }
  1402. };
  1403. /**
  1404. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1405. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1406. *
  1407. * @param elementType
  1408. * @param JSONcontainer
  1409. * @param svgContainer
  1410. * @returns {*}
  1411. * @private
  1412. */
  1413. exports.getSVGElement = function (elementType, JSONcontainer, svgContainer) {
  1414. var element;
  1415. // allocate SVG element, if it doesnt yet exist, create one.
  1416. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1417. // check if there is an redundant element
  1418. if (JSONcontainer[elementType].redundant.length > 0) {
  1419. element = JSONcontainer[elementType].redundant[0];
  1420. JSONcontainer[elementType].redundant.shift();
  1421. }
  1422. else {
  1423. // create a new element and add it to the SVG
  1424. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1425. svgContainer.appendChild(element);
  1426. }
  1427. }
  1428. else {
  1429. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1430. element = document.createElementNS('http://www.w3.org/2000/svg', elementType);
  1431. JSONcontainer[elementType] = {used: [], redundant: []};
  1432. svgContainer.appendChild(element);
  1433. }
  1434. JSONcontainer[elementType].used.push(element);
  1435. return element;
  1436. };
  1437. /**
  1438. * Allocate or generate an SVG element if needed. Store a reference to it in the JSON container and draw it in the svgContainer
  1439. * the JSON container and the SVG container have to be supplied so other svg containers (like the legend) can use this.
  1440. *
  1441. * @param elementType
  1442. * @param JSONcontainer
  1443. * @param DOMContainer
  1444. * @returns {*}
  1445. * @private
  1446. */
  1447. exports.getDOMElement = function (elementType, JSONcontainer, DOMContainer, insertBefore) {
  1448. var element;
  1449. // allocate DOM element, if it doesnt yet exist, create one.
  1450. if (JSONcontainer.hasOwnProperty(elementType)) { // this element has been created before
  1451. // check if there is an redundant element
  1452. if (JSONcontainer[elementType].redundant.length > 0) {
  1453. element = JSONcontainer[elementType].redundant[0];
  1454. JSONcontainer[elementType].redundant.shift();
  1455. }
  1456. else {
  1457. // create a new element and add it to the SVG
  1458. element = document.createElement(elementType);
  1459. if (insertBefore !== undefined) {
  1460. DOMContainer.insertBefore(element, insertBefore);
  1461. }
  1462. else {
  1463. DOMContainer.appendChild(element);
  1464. }
  1465. }
  1466. }
  1467. else {
  1468. // create a new element and add it to the SVG, also create a new object in the svgElements to keep track of it.
  1469. element = document.createElement(elementType);
  1470. JSONcontainer[elementType] = {used: [], redundant: []};
  1471. if (insertBefore !== undefined) {
  1472. DOMContainer.insertBefore(element, insertBefore);
  1473. }
  1474. else {
  1475. DOMContainer.appendChild(element);
  1476. }
  1477. }
  1478. JSONcontainer[elementType].used.push(element);
  1479. return element;
  1480. };
  1481. /**
  1482. * draw a point object. this is a seperate function because it can also be called by the legend.
  1483. * The reason the JSONcontainer and the target SVG svgContainer have to be supplied is so the legend can use these functions
  1484. * as well.
  1485. *
  1486. * @param x
  1487. * @param y
  1488. * @param group
  1489. * @param JSONcontainer
  1490. * @param svgContainer
  1491. * @returns {*}
  1492. */
  1493. exports.drawPoint = function(x, y, group, JSONcontainer, svgContainer) {
  1494. var point;
  1495. if (group.options.drawPoints.style == 'circle') {
  1496. point = exports.getSVGElement('circle',JSONcontainer,svgContainer);
  1497. point.setAttributeNS(null, "cx", x);
  1498. point.setAttributeNS(null, "cy", y);
  1499. point.setAttributeNS(null, "r", 0.5 * group.options.drawPoints.size);
  1500. point.setAttributeNS(null, "class", group.className + " point");
  1501. }
  1502. else {
  1503. point = exports.getSVGElement('rect',JSONcontainer,svgContainer);
  1504. point.setAttributeNS(null, "x", x - 0.5*group.options.drawPoints.size);
  1505. point.setAttributeNS(null, "y", y - 0.5*group.options.drawPoints.size);
  1506. point.setAttributeNS(null, "width", group.options.drawPoints.size);
  1507. point.setAttributeNS(null, "height", group.options.drawPoints.size);
  1508. point.setAttributeNS(null, "class", group.className + " point");
  1509. }
  1510. return point;
  1511. };
  1512. /**
  1513. * draw a bar SVG element centered on the X coordinate
  1514. *
  1515. * @param x
  1516. * @param y
  1517. * @param className
  1518. */
  1519. exports.drawBar = function (x, y, width, height, className, JSONcontainer, svgContainer) {
  1520. if (height != 0) {
  1521. if (height < 0) {
  1522. height *= -1;
  1523. y -= height;
  1524. }
  1525. var rect = exports.getSVGElement('rect',JSONcontainer, svgContainer);
  1526. rect.setAttributeNS(null, "x", x - 0.5 * width);
  1527. rect.setAttributeNS(null, "y", y);
  1528. rect.setAttributeNS(null, "width", width);
  1529. rect.setAttributeNS(null, "height", height);
  1530. rect.setAttributeNS(null, "class", className);
  1531. }
  1532. };
  1533. /***/ },
  1534. /* 3 */
  1535. /***/ function(module, exports, __webpack_require__) {
  1536. var util = __webpack_require__(1);
  1537. var Queue = __webpack_require__(5);
  1538. /**
  1539. * DataSet
  1540. *
  1541. * Usage:
  1542. * var dataSet = new DataSet({
  1543. * fieldId: '_id',
  1544. * type: {
  1545. * // ...
  1546. * }
  1547. * });
  1548. *
  1549. * dataSet.add(item);
  1550. * dataSet.add(data);
  1551. * dataSet.update(item);
  1552. * dataSet.update(data);
  1553. * dataSet.remove(id);
  1554. * dataSet.remove(ids);
  1555. * var data = dataSet.get();
  1556. * var data = dataSet.get(id);
  1557. * var data = dataSet.get(ids);
  1558. * var data = dataSet.get(ids, options, data);
  1559. * dataSet.clear();
  1560. *
  1561. * A data set can:
  1562. * - add/remove/update data
  1563. * - gives triggers upon changes in the data
  1564. * - can import/export data in various data formats
  1565. *
  1566. * @param {Array | DataTable} [data] Optional array with initial data
  1567. * @param {Object} [options] Available options:
  1568. * {String} fieldId Field name of the id in the
  1569. * items, 'id' by default.
  1570. * {Object.<String, String} type
  1571. * A map with field names as key,
  1572. * and the field type as value.
  1573. * {Object} queue Queue changes to the DataSet,
  1574. * flush them all at once.
  1575. * Queue options:
  1576. * - {number} delay Delay in ms, null by default
  1577. * - {number} max Maximum number of entries in the queue, Infinity by default
  1578. * @constructor DataSet
  1579. */
  1580. // TODO: add a DataSet constructor DataSet(data, options)
  1581. function DataSet (data, options) {
  1582. // correctly read optional arguments
  1583. if (data && !Array.isArray(data) && !util.isDataTable(data)) {
  1584. options = data;
  1585. data = null;
  1586. }
  1587. this._options = options || {};
  1588. this._data = {}; // map with data indexed by id
  1589. this._fieldId = this._options.fieldId || 'id'; // name of the field containing id
  1590. this._type = {}; // internal field types (NOTE: this can differ from this._options.type)
  1591. // all variants of a Date are internally stored as Date, so we can convert
  1592. // from everything to everything (also from ISODate to Number for example)
  1593. if (this._options.type) {
  1594. for (var field in this._options.type) {
  1595. if (this._options.type.hasOwnProperty(field)) {
  1596. var value = this._options.type[field];
  1597. if (value == 'Date' || value == 'ISODate' || value == 'ASPDate') {
  1598. this._type[field] = 'Date';
  1599. }
  1600. else {
  1601. this._type[field] = value;
  1602. }
  1603. }
  1604. }
  1605. }
  1606. // TODO: deprecated since version 1.1.1 (or 2.0.0?)
  1607. if (this._options.convert) {
  1608. throw new Error('Option "convert" is deprecated. Use "type" instead.');
  1609. }
  1610. this._subscribers = {}; // event subscribers
  1611. // add initial data when provided
  1612. if (data) {
  1613. this.add(data);
  1614. }
  1615. this.setOptions(options);
  1616. }
  1617. /**
  1618. * @param {Object} [options] Available options:
  1619. * {Object} queue Queue changes to the DataSet,
  1620. * flush them all at once.
  1621. * Queue options:
  1622. * - {number} delay Delay in ms, null by default
  1623. * - {number} max Maximum number of entries in the queue, Infinity by default
  1624. * @param options
  1625. */
  1626. DataSet.prototype.setOptions = function(options) {
  1627. if (options && options.queue !== undefined) {
  1628. if (options.queue === false) {
  1629. // delete queue if loaded
  1630. if (this._queue) {
  1631. this._queue.destroy();
  1632. delete this._queue;
  1633. }
  1634. }
  1635. else {
  1636. // create queue and update its options
  1637. if (!this._queue) {
  1638. this._queue = Queue.extend(this, {
  1639. replace: ['add', 'update', 'remove']
  1640. });
  1641. }
  1642. if (typeof options.queue === 'object') {
  1643. this._queue.setOptions(options.queue);
  1644. }
  1645. }
  1646. }
  1647. };
  1648. /**
  1649. * Subscribe to an event, add an event listener
  1650. * @param {String} event Event name. Available events: 'put', 'update',
  1651. * 'remove'
  1652. * @param {function} callback Callback method. Called with three parameters:
  1653. * {String} event
  1654. * {Object | null} params
  1655. * {String | Number} senderId
  1656. */
  1657. DataSet.prototype.on = function(event, callback) {
  1658. var subscribers = this._subscribers[event];
  1659. if (!subscribers) {
  1660. subscribers = [];
  1661. this._subscribers[event] = subscribers;
  1662. }
  1663. subscribers.push({
  1664. callback: callback
  1665. });
  1666. };
  1667. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1668. DataSet.prototype.subscribe = DataSet.prototype.on;
  1669. /**
  1670. * Unsubscribe from an event, remove an event listener
  1671. * @param {String} event
  1672. * @param {function} callback
  1673. */
  1674. DataSet.prototype.off = function(event, callback) {
  1675. var subscribers = this._subscribers[event];
  1676. if (subscribers) {
  1677. this._subscribers[event] = subscribers.filter(function (listener) {
  1678. return (listener.callback != callback);
  1679. });
  1680. }
  1681. };
  1682. // TODO: make this function deprecated (replaced with `on` since version 0.5)
  1683. DataSet.prototype.unsubscribe = DataSet.prototype.off;
  1684. /**
  1685. * Trigger an event
  1686. * @param {String} event
  1687. * @param {Object | null} params
  1688. * @param {String} [senderId] Optional id of the sender.
  1689. * @private
  1690. */
  1691. DataSet.prototype._trigger = function (event, params, senderId) {
  1692. if (event == '*') {
  1693. throw new Error('Cannot trigger event *');
  1694. }
  1695. var subscribers = [];
  1696. if (event in this._subscribers) {
  1697. subscribers = subscribers.concat(this._subscribers[event]);
  1698. }
  1699. if ('*' in this._subscribers) {
  1700. subscribers = subscribers.concat(this._subscribers['*']);
  1701. }
  1702. for (var i = 0; i < subscribers.length; i++) {
  1703. var subscriber = subscribers[i];
  1704. if (subscriber.callback) {
  1705. subscriber.callback(event, params, senderId || null);
  1706. }
  1707. }
  1708. };
  1709. /**
  1710. * Add data.
  1711. * Adding an item will fail when there already is an item with the same id.
  1712. * @param {Object | Array | DataTable} data
  1713. * @param {String} [senderId] Optional sender id
  1714. * @return {Array} addedIds Array with the ids of the added items
  1715. */
  1716. DataSet.prototype.add = function (data, senderId) {
  1717. var addedIds = [],
  1718. id,
  1719. me = this;
  1720. if (Array.isArray(data)) {
  1721. // Array
  1722. for (var i = 0, len = data.length; i < len; i++) {
  1723. id = me._addItem(data[i]);
  1724. addedIds.push(id);
  1725. }
  1726. }
  1727. else if (util.isDataTable(data)) {
  1728. // Google DataTable
  1729. var columns = this._getColumnNames(data);
  1730. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1731. var item = {};
  1732. for (var col = 0, cols = columns.length; col < cols; col++) {
  1733. var field = columns[col];
  1734. item[field] = data.getValue(row, col);
  1735. }
  1736. id = me._addItem(item);
  1737. addedIds.push(id);
  1738. }
  1739. }
  1740. else if (data instanceof Object) {
  1741. // Single item
  1742. id = me._addItem(data);
  1743. addedIds.push(id);
  1744. }
  1745. else {
  1746. throw new Error('Unknown dataType');
  1747. }
  1748. if (addedIds.length) {
  1749. this._trigger('add', {items: addedIds}, senderId);
  1750. }
  1751. return addedIds;
  1752. };
  1753. /**
  1754. * Update existing items. When an item does not exist, it will be created
  1755. * @param {Object | Array | DataTable} data
  1756. * @param {String} [senderId] Optional sender id
  1757. * @return {Array} updatedIds The ids of the added or updated items
  1758. */
  1759. DataSet.prototype.update = function (data, senderId) {
  1760. var addedIds = [];
  1761. var updatedIds = [];
  1762. var updatedData = [];
  1763. var me = this;
  1764. var fieldId = me._fieldId;
  1765. var addOrUpdate = function (item) {
  1766. var id = item[fieldId];
  1767. if (me._data[id]) {
  1768. // update item
  1769. id = me._updateItem(item);
  1770. updatedIds.push(id);
  1771. updatedData.push(item);
  1772. }
  1773. else {
  1774. // add new item
  1775. id = me._addItem(item);
  1776. addedIds.push(id);
  1777. }
  1778. };
  1779. if (Array.isArray(data)) {
  1780. // Array
  1781. for (var i = 0, len = data.length; i < len; i++) {
  1782. addOrUpdate(data[i]);
  1783. }
  1784. }
  1785. else if (util.isDataTable(data)) {
  1786. // Google DataTable
  1787. var columns = this._getColumnNames(data);
  1788. for (var row = 0, rows = data.getNumberOfRows(); row < rows; row++) {
  1789. var item = {};
  1790. for (var col = 0, cols = columns.length; col < cols; col++) {
  1791. var field = columns[col];
  1792. item[field] = data.getValue(row, col);
  1793. }
  1794. addOrUpdate(item);
  1795. }
  1796. }
  1797. else if (data instanceof Object) {
  1798. // Single item
  1799. addOrUpdate(data);
  1800. }
  1801. else {
  1802. throw new Error('Unknown dataType');
  1803. }
  1804. if (addedIds.length) {
  1805. this._trigger('add', {items: addedIds}, senderId);
  1806. }
  1807. if (updatedIds.length) {
  1808. this._trigger('update', {items: updatedIds, data: updatedData}, senderId);
  1809. }
  1810. return addedIds.concat(updatedIds);
  1811. };
  1812. /**
  1813. * Get a data item or multiple items.
  1814. *
  1815. * Usage:
  1816. *
  1817. * get()
  1818. * get(options: Object)
  1819. * get(options: Object, data: Array | DataTable)
  1820. *
  1821. * get(id: Number | String)
  1822. * get(id: Number | String, options: Object)
  1823. * get(id: Number | String, options: Object, data: Array | DataTable)
  1824. *
  1825. * get(ids: Number[] | String[])
  1826. * get(ids: Number[] | String[], options: Object)
  1827. * get(ids: Number[] | String[], options: Object, data: Array | DataTable)
  1828. *
  1829. * Where:
  1830. *
  1831. * {Number | String} id The id of an item
  1832. * {Number[] | String{}} ids An array with ids of items
  1833. * {Object} options An Object with options. Available options:
  1834. * {String} [returnType] Type of data to be
  1835. * returned. Can be 'DataTable' or 'Array' (default)
  1836. * {Object.<String, String>} [type]
  1837. * {String[]} [fields] field names to be returned
  1838. * {function} [filter] filter items
  1839. * {String | function} [order] Order the items by
  1840. * a field name or custom sort function.
  1841. * {Array | DataTable} [data] If provided, items will be appended to this
  1842. * array or table. Required in case of Google
  1843. * DataTable.
  1844. *
  1845. * @throws Error
  1846. */
  1847. DataSet.prototype.get = function (args) {
  1848. var me = this;
  1849. // parse the arguments
  1850. var id, ids, options, data;
  1851. var firstType = util.getType(arguments[0]);
  1852. if (firstType == 'String' || firstType == 'Number') {
  1853. // get(id [, options] [, data])
  1854. id = arguments[0];
  1855. options = arguments[1];
  1856. data = arguments[2];
  1857. }
  1858. else if (firstType == 'Array') {
  1859. // get(ids [, options] [, data])
  1860. ids = arguments[0];
  1861. options = arguments[1];
  1862. data = arguments[2];
  1863. }
  1864. else {
  1865. // get([, options] [, data])
  1866. options = arguments[0];
  1867. data = arguments[1];
  1868. }
  1869. // determine the return type
  1870. var returnType;
  1871. if (options && options.returnType) {
  1872. var allowedValues = ["DataTable", "Array", "Object"];
  1873. returnType = allowedValues.indexOf(options.returnType) == -1 ? "Array" : options.returnType;
  1874. if (data && (returnType != util.getType(data))) {
  1875. throw new Error('Type of parameter "data" (' + util.getType(data) + ') ' +
  1876. 'does not correspond with specified options.type (' + options.type + ')');
  1877. }
  1878. if (returnType == 'DataTable' && !util.isDataTable(data)) {
  1879. throw new Error('Parameter "data" must be a DataTable ' +
  1880. 'when options.type is "DataTable"');
  1881. }
  1882. }
  1883. else if (data) {
  1884. returnType = (util.getType(data) == 'DataTable') ? 'DataTable' : 'Array';
  1885. }
  1886. else {
  1887. returnType = 'Array';
  1888. }
  1889. // build options
  1890. var type = options && options.type || this._options.type;
  1891. var filter = options && options.filter;
  1892. var items = [], item, itemId, i, len;
  1893. // convert items
  1894. if (id != undefined) {
  1895. // return a single item
  1896. item = me._getItem(id, type);
  1897. if (filter && !filter(item)) {
  1898. item = null;
  1899. }
  1900. }
  1901. else if (ids != undefined) {
  1902. // return a subset of items
  1903. for (i = 0, len = ids.length; i < len; i++) {
  1904. item = me._getItem(ids[i], type);
  1905. if (!filter || filter(item)) {
  1906. items.push(item);
  1907. }
  1908. }
  1909. }
  1910. else {
  1911. // return all items
  1912. for (itemId in this._data) {
  1913. if (this._data.hasOwnProperty(itemId)) {
  1914. item = me._getItem(itemId, type);
  1915. if (!filter || filter(item)) {
  1916. items.push(item);
  1917. }
  1918. }
  1919. }
  1920. }
  1921. // order the results
  1922. if (options && options.order && id == undefined) {
  1923. this._sort(items, options.order);
  1924. }
  1925. // filter fields of the items
  1926. if (options && options.fields) {
  1927. var fields = options.fields;
  1928. if (id != undefined) {
  1929. item = this._filterFields(item, fields);
  1930. }
  1931. else {
  1932. for (i = 0, len = items.length; i < len; i++) {
  1933. items[i] = this._filterFields(items[i], fields);
  1934. }
  1935. }
  1936. }
  1937. // return the results
  1938. if (returnType == 'DataTable') {
  1939. var columns = this._getColumnNames(data);
  1940. if (id != undefined) {
  1941. // append a single item to the data table
  1942. me._appendRow(data, columns, item);
  1943. }
  1944. else {
  1945. // copy the items to the provided data table
  1946. for (i = 0; i < items.length; i++) {
  1947. me._appendRow(data, columns, items[i]);
  1948. }
  1949. }
  1950. return data;
  1951. }
  1952. else if (returnType == "Object") {
  1953. var result = {};
  1954. for (i = 0; i < items.length; i++) {
  1955. result[items[i].id] = items[i];
  1956. }
  1957. return result;
  1958. }
  1959. else {
  1960. // return an array
  1961. if (id != undefined) {
  1962. // a single item
  1963. return item;
  1964. }
  1965. else {
  1966. // multiple items
  1967. if (data) {
  1968. // copy the items to the provided array
  1969. for (i = 0, len = items.length; i < len; i++) {
  1970. data.push(items[i]);
  1971. }
  1972. return data;
  1973. }
  1974. else {
  1975. // just return our array
  1976. return items;
  1977. }
  1978. }
  1979. }
  1980. };
  1981. /**
  1982. * Get ids of all items or from a filtered set of items.
  1983. * @param {Object} [options] An Object with options. Available options:
  1984. * {function} [filter] filter items
  1985. * {String | function} [order] Order the items by
  1986. * a field name or custom sort function.
  1987. * @return {Array} ids
  1988. */
  1989. DataSet.prototype.getIds = function (options) {
  1990. var data = this._data,
  1991. filter = options && options.filter,
  1992. order = options && options.order,
  1993. type = options && options.type || this._options.type,
  1994. i,
  1995. len,
  1996. id,
  1997. item,
  1998. items,
  1999. ids = [];
  2000. if (filter) {
  2001. // get filtered items
  2002. if (order) {
  2003. // create ordered list
  2004. items = [];
  2005. for (id in data) {
  2006. if (data.hasOwnProperty(id)) {
  2007. item = this._getItem(id, type);
  2008. if (filter(item)) {
  2009. items.push(item);
  2010. }
  2011. }
  2012. }
  2013. this._sort(items, order);
  2014. for (i = 0, len = items.length; i < len; i++) {
  2015. ids[i] = items[i][this._fieldId];
  2016. }
  2017. }
  2018. else {
  2019. // create unordered list
  2020. for (id in data) {
  2021. if (data.hasOwnProperty(id)) {
  2022. item = this._getItem(id, type);
  2023. if (filter(item)) {
  2024. ids.push(item[this._fieldId]);
  2025. }
  2026. }
  2027. }
  2028. }
  2029. }
  2030. else {
  2031. // get all items
  2032. if (order) {
  2033. // create an ordered list
  2034. items = [];
  2035. for (id in data) {
  2036. if (data.hasOwnProperty(id)) {
  2037. items.push(data[id]);
  2038. }
  2039. }
  2040. this._sort(items, order);
  2041. for (i = 0, len = items.length; i < len; i++) {
  2042. ids[i] = items[i][this._fieldId];
  2043. }
  2044. }
  2045. else {
  2046. // create unordered list
  2047. for (id in data) {
  2048. if (data.hasOwnProperty(id)) {
  2049. item = data[id];
  2050. ids.push(item[this._fieldId]);
  2051. }
  2052. }
  2053. }
  2054. }
  2055. return ids;
  2056. };
  2057. /**
  2058. * Returns the DataSet itself. Is overwritten for example by the DataView,
  2059. * which returns the DataSet it is connected to instead.
  2060. */
  2061. DataSet.prototype.getDataSet = function () {
  2062. return this;
  2063. };
  2064. /**
  2065. * Execute a callback function for every item in the dataset.
  2066. * @param {function} callback
  2067. * @param {Object} [options] Available options:
  2068. * {Object.<String, String>} [type]
  2069. * {String[]} [fields] filter fields
  2070. * {function} [filter] filter items
  2071. * {String | function} [order] Order the items by
  2072. * a field name or custom sort function.
  2073. */
  2074. DataSet.prototype.forEach = function (callback, options) {
  2075. var filter = options && options.filter,
  2076. type = options && options.type || this._options.type,
  2077. data = this._data,
  2078. item,
  2079. id;
  2080. if (options && options.order) {
  2081. // execute forEach on ordered list
  2082. var items = this.get(options);
  2083. for (var i = 0, len = items.length; i < len; i++) {
  2084. item = items[i];
  2085. id = item[this._fieldId];
  2086. callback(item, id);
  2087. }
  2088. }
  2089. else {
  2090. // unordered
  2091. for (id in data) {
  2092. if (data.hasOwnProperty(id)) {
  2093. item = this._getItem(id, type);
  2094. if (!filter || filter(item)) {
  2095. callback(item, id);
  2096. }
  2097. }
  2098. }
  2099. }
  2100. };
  2101. /**
  2102. * Map every item in the dataset.
  2103. * @param {function} callback
  2104. * @param {Object} [options] Available options:
  2105. * {Object.<String, String>} [type]
  2106. * {String[]} [fields] filter fields
  2107. * {function} [filter] filter items
  2108. * {String | function} [order] Order the items by
  2109. * a field name or custom sort function.
  2110. * @return {Object[]} mappedItems
  2111. */
  2112. DataSet.prototype.map = function (callback, options) {
  2113. var filter = options && options.filter,
  2114. type = options && options.type || this._options.type,
  2115. mappedItems = [],
  2116. data = this._data,
  2117. item;
  2118. // convert and filter items
  2119. for (var id in data) {
  2120. if (data.hasOwnProperty(id)) {
  2121. item = this._getItem(id, type);
  2122. if (!filter || filter(item)) {
  2123. mappedItems.push(callback(item, id));
  2124. }
  2125. }
  2126. }
  2127. // order items
  2128. if (options && options.order) {
  2129. this._sort(mappedItems, options.order);
  2130. }
  2131. return mappedItems;
  2132. };
  2133. /**
  2134. * Filter the fields of an item
  2135. * @param {Object} item
  2136. * @param {String[]} fields Field names
  2137. * @return {Object} filteredItem
  2138. * @private
  2139. */
  2140. DataSet.prototype._filterFields = function (item, fields) {
  2141. var filteredItem = {};
  2142. for (var field in item) {
  2143. if (item.hasOwnProperty(field) && (fields.indexOf(field) != -1)) {
  2144. filteredItem[field] = item[field];
  2145. }
  2146. }
  2147. return filteredItem;
  2148. };
  2149. /**
  2150. * Sort the provided array with items
  2151. * @param {Object[]} items
  2152. * @param {String | function} order A field name or custom sort function.
  2153. * @private
  2154. */
  2155. DataSet.prototype._sort = function (items, order) {
  2156. if (util.isString(order)) {
  2157. // order by provided field name
  2158. var name = order; // field name
  2159. items.sort(function (a, b) {
  2160. var av = a[name];
  2161. var bv = b[name];
  2162. return (av > bv) ? 1 : ((av < bv) ? -1 : 0);
  2163. });
  2164. }
  2165. else if (typeof order === 'function') {
  2166. // order by sort function
  2167. items.sort(order);
  2168. }
  2169. // TODO: extend order by an Object {field:String, direction:String}
  2170. // where direction can be 'asc' or 'desc'
  2171. else {
  2172. throw new TypeError('Order must be a function or a string');
  2173. }
  2174. };
  2175. /**
  2176. * Remove an object by pointer or by id
  2177. * @param {String | Number | Object | Array} id Object or id, or an array with
  2178. * objects or ids to be removed
  2179. * @param {String} [senderId] Optional sender id
  2180. * @return {Array} removedIds
  2181. */
  2182. DataSet.prototype.remove = function (id, senderId) {
  2183. var removedIds = [],
  2184. i, len, removedId;
  2185. if (Array.isArray(id)) {
  2186. for (i = 0, len = id.length; i < len; i++) {
  2187. removedId = this._remove(id[i]);
  2188. if (removedId != null) {
  2189. removedIds.push(removedId);
  2190. }
  2191. }
  2192. }
  2193. else {
  2194. removedId = this._remove(id);
  2195. if (removedId != null) {
  2196. removedIds.push(removedId);
  2197. }
  2198. }
  2199. if (removedIds.length) {
  2200. this._trigger('remove', {items: removedIds}, senderId);
  2201. }
  2202. return removedIds;
  2203. };
  2204. /**
  2205. * Remove an item by its id
  2206. * @param {Number | String | Object} id id or item
  2207. * @returns {Number | String | null} id
  2208. * @private
  2209. */
  2210. DataSet.prototype._remove = function (id) {
  2211. if (util.isNumber(id) || util.isString(id)) {
  2212. if (this._data[id]) {
  2213. delete this._data[id];
  2214. return id;
  2215. }
  2216. }
  2217. else if (id instanceof Object) {
  2218. var itemId = id[this._fieldId];
  2219. if (itemId && this._data[itemId]) {
  2220. delete this._data[itemId];
  2221. return itemId;
  2222. }
  2223. }
  2224. return null;
  2225. };
  2226. /**
  2227. * Clear the data
  2228. * @param {String} [senderId] Optional sender id
  2229. * @return {Array} removedIds The ids of all removed items
  2230. */
  2231. DataSet.prototype.clear = function (senderId) {
  2232. var ids = Object.keys(this._data);
  2233. this._data = {};
  2234. this._trigger('remove', {items: ids}, senderId);
  2235. return ids;
  2236. };
  2237. /**
  2238. * Find the item with maximum value of a specified field
  2239. * @param {String} field
  2240. * @return {Object | null} item Item containing max value, or null if no items
  2241. */
  2242. DataSet.prototype.max = function (field) {
  2243. var data = this._data,
  2244. max = null,
  2245. maxField = null;
  2246. for (var id in data) {
  2247. if (data.hasOwnProperty(id)) {
  2248. var item = data[id];
  2249. var itemField = item[field];
  2250. if (itemField != null && (!max || itemField > maxField)) {
  2251. max = item;
  2252. maxField = itemField;
  2253. }
  2254. }
  2255. }
  2256. return max;
  2257. };
  2258. /**
  2259. * Find the item with minimum value of a specified field
  2260. * @param {String} field
  2261. * @return {Object | null} item Item containing max value, or null if no items
  2262. */
  2263. DataSet.prototype.min = function (field) {
  2264. var data = this._data,
  2265. min = null,
  2266. minField = null;
  2267. for (var id in data) {
  2268. if (data.hasOwnProperty(id)) {
  2269. var item = data[id];
  2270. var itemField = item[field];
  2271. if (itemField != null && (!min || itemField < minField)) {
  2272. min = item;
  2273. minField = itemField;
  2274. }
  2275. }
  2276. }
  2277. return min;
  2278. };
  2279. /**
  2280. * Find all distinct values of a specified field
  2281. * @param {String} field
  2282. * @return {Array} values Array containing all distinct values. If data items
  2283. * do not contain the specified field are ignored.
  2284. * The returned array is unordered.
  2285. */
  2286. DataSet.prototype.distinct = function (field) {
  2287. var data = this._data;
  2288. var values = [];
  2289. var fieldType = this._options.type && this._options.type[field] || null;
  2290. var count = 0;
  2291. var i;
  2292. for (var prop in data) {
  2293. if (data.hasOwnProperty(prop)) {
  2294. var item = data[prop];
  2295. var value = item[field];
  2296. var exists = false;
  2297. for (i = 0; i < count; i++) {
  2298. if (values[i] == value) {
  2299. exists = true;
  2300. break;
  2301. }
  2302. }
  2303. if (!exists && (value !== undefined)) {
  2304. values[count] = value;
  2305. count++;
  2306. }
  2307. }
  2308. }
  2309. if (fieldType) {
  2310. for (i = 0; i < values.length; i++) {
  2311. values[i] = util.convert(values[i], fieldType);
  2312. }
  2313. }
  2314. return values;
  2315. };
  2316. /**
  2317. * Add a single item. Will fail when an item with the same id already exists.
  2318. * @param {Object} item
  2319. * @return {String} id
  2320. * @private
  2321. */
  2322. DataSet.prototype._addItem = function (item) {
  2323. var id = item[this._fieldId];
  2324. if (id != undefined) {
  2325. // check whether this id is already taken
  2326. if (this._data[id]) {
  2327. // item already exists
  2328. throw new Error('Cannot add item: item with id ' + id + ' already exists');
  2329. }
  2330. }
  2331. else {
  2332. // generate an id
  2333. id = util.randomUUID();
  2334. item[this._fieldId] = id;
  2335. }
  2336. var d = {};
  2337. for (var field in item) {
  2338. if (item.hasOwnProperty(field)) {
  2339. var fieldType = this._type[field]; // type may be undefined
  2340. d[field] = util.convert(item[field], fieldType);
  2341. }
  2342. }
  2343. this._data[id] = d;
  2344. return id;
  2345. };
  2346. /**
  2347. * Get an item. Fields can be converted to a specific type
  2348. * @param {String} id
  2349. * @param {Object.<String, String>} [types] field types to convert
  2350. * @return {Object | null} item
  2351. * @private
  2352. */
  2353. DataSet.prototype._getItem = function (id, types) {
  2354. var field, value;
  2355. // get the item from the dataset
  2356. var raw = this._data[id];
  2357. if (!raw) {
  2358. return null;
  2359. }
  2360. // convert the items field types
  2361. var converted = {};
  2362. if (types) {
  2363. for (field in raw) {
  2364. if (raw.hasOwnProperty(field)) {
  2365. value = raw[field];
  2366. converted[field] = util.convert(value, types[field]);
  2367. }
  2368. }
  2369. }
  2370. else {
  2371. // no field types specified, no converting needed
  2372. for (field in raw) {
  2373. if (raw.hasOwnProperty(field)) {
  2374. value = raw[field];
  2375. converted[field] = value;
  2376. }
  2377. }
  2378. }
  2379. return converted;
  2380. };
  2381. /**
  2382. * Update a single item: merge with existing item.
  2383. * Will fail when the item has no id, or when there does not exist an item
  2384. * with the same id.
  2385. * @param {Object} item
  2386. * @return {String} id
  2387. * @private
  2388. */
  2389. DataSet.prototype._updateItem = function (item) {
  2390. var id = item[this._fieldId];
  2391. if (id == undefined) {
  2392. throw new Error('Cannot update item: item has no id (item: ' + JSON.stringify(item) + ')');
  2393. }
  2394. var d = this._data[id];
  2395. if (!d) {
  2396. // item doesn't exist
  2397. throw new Error('Cannot update item: no item with id ' + id + ' found');
  2398. }
  2399. // merge with current item
  2400. for (var field in item) {
  2401. if (item.hasOwnProperty(field)) {
  2402. var fieldType = this._type[field]; // type may be undefined
  2403. d[field] = util.convert(item[field], fieldType);
  2404. }
  2405. }
  2406. return id;
  2407. };
  2408. /**
  2409. * Get an array with the column names of a Google DataTable
  2410. * @param {DataTable} dataTable
  2411. * @return {String[]} columnNames
  2412. * @private
  2413. */
  2414. DataSet.prototype._getColumnNames = function (dataTable) {
  2415. var columns = [];
  2416. for (var col = 0, cols = dataTable.getNumberOfColumns(); col < cols; col++) {
  2417. columns[col] = dataTable.getColumnId(col) || dataTable.getColumnLabel(col);
  2418. }
  2419. return columns;
  2420. };
  2421. /**
  2422. * Append an item as a row to the dataTable
  2423. * @param dataTable
  2424. * @param columns
  2425. * @param item
  2426. * @private
  2427. */
  2428. DataSet.prototype._appendRow = function (dataTable, columns, item) {
  2429. var row = dataTable.addRow();
  2430. for (var col = 0, cols = columns.length; col < cols; col++) {
  2431. var field = columns[col];
  2432. dataTable.setValue(row, col, item[field]);
  2433. }
  2434. };
  2435. module.exports = DataSet;
  2436. /***/ },
  2437. /* 4 */
  2438. /***/ function(module, exports, __webpack_require__) {
  2439. var util = __webpack_require__(1);
  2440. var DataSet = __webpack_require__(3);
  2441. /**
  2442. * DataView
  2443. *
  2444. * a dataview offers a filtered view on a dataset or an other dataview.
  2445. *
  2446. * @param {DataSet | DataView} data
  2447. * @param {Object} [options] Available options: see method get
  2448. *
  2449. * @constructor DataView
  2450. */
  2451. function DataView (data, options) {
  2452. this._data = null;
  2453. this._ids = {}; // ids of the items currently in memory (just contains a boolean true)
  2454. this._options = options || {};
  2455. this._fieldId = 'id'; // name of the field containing id
  2456. this._subscribers = {}; // event subscribers
  2457. var me = this;
  2458. this.listener = function () {
  2459. me._onEvent.apply(me, arguments);
  2460. };
  2461. this.setData(data);
  2462. }
  2463. // TODO: implement a function .config() to dynamically update things like configured filter
  2464. // and trigger changes accordingly
  2465. /**
  2466. * Set a data source for the view
  2467. * @param {DataSet | DataView} data
  2468. */
  2469. DataView.prototype.setData = function (data) {
  2470. var ids, i, len;
  2471. if (this._data) {
  2472. // unsubscribe from current dataset
  2473. if (this._data.unsubscribe) {
  2474. this._data.unsubscribe('*', this.listener);
  2475. }
  2476. // trigger a remove of all items in memory
  2477. ids = [];
  2478. for (var id in this._ids) {
  2479. if (this._ids.hasOwnProperty(id)) {
  2480. ids.push(id);
  2481. }
  2482. }
  2483. this._ids = {};
  2484. this._trigger('remove', {items: ids});
  2485. }
  2486. this._data = data;
  2487. if (this._data) {
  2488. // update fieldId
  2489. this._fieldId = this._options.fieldId ||
  2490. (this._data && this._data.options && this._data.options.fieldId) ||
  2491. 'id';
  2492. // trigger an add of all added items
  2493. ids = this._data.getIds({filter: this._options && this._options.filter});
  2494. for (i = 0, len = ids.length; i < len; i++) {
  2495. id = ids[i];
  2496. this._ids[id] = true;
  2497. }
  2498. this._trigger('add', {items: ids});
  2499. // subscribe to new dataset
  2500. if (this._data.on) {
  2501. this._data.on('*', this.listener);
  2502. }
  2503. }
  2504. };
  2505. /**
  2506. * Get data from the data view
  2507. *
  2508. * Usage:
  2509. *
  2510. * get()
  2511. * get(options: Object)
  2512. * get(options: Object, data: Array | DataTable)
  2513. *
  2514. * get(id: Number)
  2515. * get(id: Number, options: Object)
  2516. * get(id: Number, options: Object, data: Array | DataTable)
  2517. *
  2518. * get(ids: Number[])
  2519. * get(ids: Number[], options: Object)
  2520. * get(ids: Number[], options: Object, data: Array | DataTable)
  2521. *
  2522. * Where:
  2523. *
  2524. * {Number | String} id The id of an item
  2525. * {Number[] | String{}} ids An array with ids of items
  2526. * {Object} options An Object with options. Available options:
  2527. * {String} [type] Type of data to be returned. Can
  2528. * be 'DataTable' or 'Array' (default)
  2529. * {Object.<String, String>} [convert]
  2530. * {String[]} [fields] field names to be returned
  2531. * {function} [filter] filter items
  2532. * {String | function} [order] Order the items by
  2533. * a field name or custom sort function.
  2534. * {Array | DataTable} [data] If provided, items will be appended to this
  2535. * array or table. Required in case of Google
  2536. * DataTable.
  2537. * @param args
  2538. */
  2539. DataView.prototype.get = function (args) {
  2540. var me = this;
  2541. // parse the arguments
  2542. var ids, options, data;
  2543. var firstType = util.getType(arguments[0]);
  2544. if (firstType == 'String' || firstType == 'Number' || firstType == 'Array') {
  2545. // get(id(s) [, options] [, data])
  2546. ids = arguments[0]; // can be a single id or an array with ids
  2547. options = arguments[1];
  2548. data = arguments[2];
  2549. }
  2550. else {
  2551. // get([, options] [, data])
  2552. options = arguments[0];
  2553. data = arguments[1];
  2554. }
  2555. // extend the options with the default options and provided options
  2556. var viewOptions = util.extend({}, this._options, options);
  2557. // create a combined filter method when needed
  2558. if (this._options.filter && options && options.filter) {
  2559. viewOptions.filter = function (item) {
  2560. return me._options.filter(item) && options.filter(item);
  2561. }
  2562. }
  2563. // build up the call to the linked data set
  2564. var getArguments = [];
  2565. if (ids != undefined) {
  2566. getArguments.push(ids);
  2567. }
  2568. getArguments.push(viewOptions);
  2569. getArguments.push(data);
  2570. return this._data && this._data.get.apply(this._data, getArguments);
  2571. };
  2572. /**
  2573. * Get ids of all items or from a filtered set of items.
  2574. * @param {Object} [options] An Object with options. Available options:
  2575. * {function} [filter] filter items
  2576. * {String | function} [order] Order the items by
  2577. * a field name or custom sort function.
  2578. * @return {Array} ids
  2579. */
  2580. DataView.prototype.getIds = function (options) {
  2581. var ids;
  2582. if (this._data) {
  2583. var defaultFilter = this._options.filter;
  2584. var filter;
  2585. if (options && options.filter) {
  2586. if (defaultFilter) {
  2587. filter = function (item) {
  2588. return defaultFilter(item) && options.filter(item);
  2589. }
  2590. }
  2591. else {
  2592. filter = options.filter;
  2593. }
  2594. }
  2595. else {
  2596. filter = defaultFilter;
  2597. }
  2598. ids = this._data.getIds({
  2599. filter: filter,
  2600. order: options && options.order
  2601. });
  2602. }
  2603. else {
  2604. ids = [];
  2605. }
  2606. return ids;
  2607. };
  2608. /**
  2609. * Get the DataSet to which this DataView is connected. In case there is a chain
  2610. * of multiple DataViews, the root DataSet of this chain is returned.
  2611. * @return {DataSet} dataSet
  2612. */
  2613. DataView.prototype.getDataSet = function () {
  2614. var dataSet = this;
  2615. while (dataSet instanceof DataView) {
  2616. dataSet = dataSet._data;
  2617. }
  2618. return dataSet || null;
  2619. };
  2620. /**
  2621. * Event listener. Will propagate all events from the connected data set to
  2622. * the subscribers of the DataView, but will filter the items and only trigger
  2623. * when there are changes in the filtered data set.
  2624. * @param {String} event
  2625. * @param {Object | null} params
  2626. * @param {String} senderId
  2627. * @private
  2628. */
  2629. DataView.prototype._onEvent = function (event, params, senderId) {
  2630. var i, len, id, item,
  2631. ids = params && params.items,
  2632. data = this._data,
  2633. added = [],
  2634. updated = [],
  2635. removed = [];
  2636. if (ids && data) {
  2637. switch (event) {
  2638. case 'add':
  2639. // filter the ids of the added items
  2640. for (i = 0, len = ids.length; i < len; i++) {
  2641. id = ids[i];
  2642. item = this.get(id);
  2643. if (item) {
  2644. this._ids[id] = true;
  2645. added.push(id);
  2646. }
  2647. }
  2648. break;
  2649. case 'update':
  2650. // determine the event from the views viewpoint: an updated
  2651. // item can be added, updated, or removed from this view.
  2652. for (i = 0, len = ids.length; i < len; i++) {
  2653. id = ids[i];
  2654. item = this.get(id);
  2655. if (item) {
  2656. if (this._ids[id]) {
  2657. updated.push(id);
  2658. }
  2659. else {
  2660. this._ids[id] = true;
  2661. added.push(id);
  2662. }
  2663. }
  2664. else {
  2665. if (this._ids[id]) {
  2666. delete this._ids[id];
  2667. removed.push(id);
  2668. }
  2669. else {
  2670. // nothing interesting for me :-(
  2671. }
  2672. }
  2673. }
  2674. break;
  2675. case 'remove':
  2676. // filter the ids of the removed items
  2677. for (i = 0, len = ids.length; i < len; i++) {
  2678. id = ids[i];
  2679. if (this._ids[id]) {
  2680. delete this._ids[id];
  2681. removed.push(id);
  2682. }
  2683. }
  2684. break;
  2685. }
  2686. if (added.length) {
  2687. this._trigger('add', {items: added}, senderId);
  2688. }
  2689. if (updated.length) {
  2690. this._trigger('update', {items: updated}, senderId);
  2691. }
  2692. if (removed.length) {
  2693. this._trigger('remove', {items: removed}, senderId);
  2694. }
  2695. }
  2696. };
  2697. // copy subscription functionality from DataSet
  2698. DataView.prototype.on = DataSet.prototype.on;
  2699. DataView.prototype.off = DataSet.prototype.off;
  2700. DataView.prototype._trigger = DataSet.prototype._trigger;
  2701. // TODO: make these functions deprecated (replaced with `on` and `off` since version 0.5)
  2702. DataView.prototype.subscribe = DataView.prototype.on;
  2703. DataView.prototype.unsubscribe = DataView.prototype.off;
  2704. module.exports = DataView;
  2705. /***/ },
  2706. /* 5 */
  2707. /***/ function(module, exports, __webpack_require__) {
  2708. /**
  2709. * A queue
  2710. * @param {Object} options
  2711. * Available options:
  2712. * - delay: number When provided, the queue will be flushed
  2713. * automatically after an inactivity of this delay
  2714. * in milliseconds.
  2715. * Default value is null.
  2716. * - max: number When the queue exceeds the given maximum number
  2717. * of entries, the queue is flushed automatically.
  2718. * Default value of max is Infinity.
  2719. * @constructor
  2720. */
  2721. function Queue(options) {
  2722. // options
  2723. this.delay = null;
  2724. this.max = Infinity;
  2725. // properties
  2726. this._queue = [];
  2727. this._timeout = null;
  2728. this._extended = null;
  2729. this.setOptions(options);
  2730. }
  2731. /**
  2732. * Update the configuration of the queue
  2733. * @param {Object} options
  2734. * Available options:
  2735. * - delay: number When provided, the queue will be flushed
  2736. * automatically after an inactivity of this delay
  2737. * in milliseconds.
  2738. * Default value is null.
  2739. * - max: number When the queue exceeds the given maximum number
  2740. * of entries, the queue is flushed automatically.
  2741. * Default value of max is Infinity.
  2742. * @param options
  2743. */
  2744. Queue.prototype.setOptions = function (options) {
  2745. if (options && typeof options.delay !== 'undefined') {
  2746. this.delay = options.delay;
  2747. }
  2748. if (options && typeof options.max !== 'undefined') {
  2749. this.max = options.max;
  2750. }
  2751. this._flushIfNeeded();
  2752. };
  2753. /**
  2754. * Extend an object with queuing functionality.
  2755. * The object will be extended with a function flush, and the methods provided
  2756. * in options.replace will be replaced with queued ones.
  2757. * @param {Object} object
  2758. * @param {Object} options
  2759. * Available options:
  2760. * - replace: Array.<string>
  2761. * A list with method names of the methods
  2762. * on the object to be replaced with queued ones.
  2763. * - delay: number When provided, the queue will be flushed
  2764. * automatically after an inactivity of this delay
  2765. * in milliseconds.
  2766. * Default value is null.
  2767. * - max: number When the queue exceeds the given maximum number
  2768. * of entries, the queue is flushed automatically.
  2769. * Default value of max is Infinity.
  2770. * @return {Queue} Returns the created queue
  2771. */
  2772. Queue.extend = function (object, options) {
  2773. var queue = new Queue(options);
  2774. if (object.flush !== undefined) {
  2775. throw new Error('Target object already has a property flush');
  2776. }
  2777. object.flush = function () {
  2778. queue.flush();
  2779. };
  2780. var methods = [{
  2781. name: 'flush',
  2782. original: undefined
  2783. }];
  2784. if (options && options.replace) {
  2785. for (var i = 0; i < options.replace.length; i++) {
  2786. var name = options.replace[i];
  2787. methods.push({
  2788. name: name,
  2789. original: object[name]
  2790. });
  2791. queue.replace(object, name);
  2792. }
  2793. }
  2794. queue._extended = {
  2795. object: object,
  2796. methods: methods
  2797. };
  2798. return queue;
  2799. };
  2800. /**
  2801. * Destroy the queue. The queue will first flush all queued actions, and in
  2802. * case it has extended an object, will restore the original object.
  2803. */
  2804. Queue.prototype.destroy = function () {
  2805. this.flush();
  2806. if (this._extended) {
  2807. var object = this._extended.object;
  2808. var methods = this._extended.methods;
  2809. for (var i = 0; i < methods.length; i++) {
  2810. var method = methods[i];
  2811. if (method.original) {
  2812. object[method.name] = method.original;
  2813. }
  2814. else {
  2815. delete object[method.name];
  2816. }
  2817. }
  2818. this._extended = null;
  2819. }
  2820. };
  2821. /**
  2822. * Replace a method on an object with a queued version
  2823. * @param {Object} object Object having the method
  2824. * @param {string} method The method name
  2825. */
  2826. Queue.prototype.replace = function(object, method) {
  2827. var me = this;
  2828. var original = object[method];
  2829. if (!original) {
  2830. throw new Error('Method ' + method + ' undefined');
  2831. }
  2832. object[method] = function () {
  2833. // create an Array with the arguments
  2834. var args = [];
  2835. for (var i = 0; i < arguments.length; i++) {
  2836. args[i] = arguments[i];
  2837. }
  2838. // add this call to the queue
  2839. me.queue({
  2840. args: args,
  2841. fn: original,
  2842. context: this
  2843. });
  2844. };
  2845. };
  2846. /**
  2847. * Queue a call
  2848. * @param {function | {fn: function, args: Array} | {fn: function, args: Array, context: Object}} entry
  2849. */
  2850. Queue.prototype.queue = function(entry) {
  2851. if (typeof entry === 'function') {
  2852. this._queue.push({fn: entry});
  2853. }
  2854. else {
  2855. this._queue.push(entry);
  2856. }
  2857. this._flushIfNeeded();
  2858. };
  2859. /**
  2860. * Check whether the queue needs to be flushed
  2861. * @private
  2862. */
  2863. Queue.prototype._flushIfNeeded = function () {
  2864. // flush when the maximum is exceeded.
  2865. if (this._queue.length > this.max) {
  2866. this.flush();
  2867. }
  2868. // flush after a period of inactivity when a delay is configured
  2869. clearTimeout(this._timeout);
  2870. if (this.queue.length > 0 && typeof this.delay === 'number') {
  2871. var me = this;
  2872. this._timeout = setTimeout(function () {
  2873. me.flush();
  2874. }, this.delay);
  2875. }
  2876. };
  2877. /**
  2878. * Flush all queued calls
  2879. */
  2880. Queue.prototype.flush = function () {
  2881. while (this._queue.length > 0) {
  2882. var entry = this._queue.shift();
  2883. entry.fn.apply(entry.context || entry.fn, entry.args || []);
  2884. }
  2885. };
  2886. module.exports = Queue;
  2887. /***/ },
  2888. /* 6 */
  2889. /***/ function(module, exports, __webpack_require__) {
  2890. var Emitter = __webpack_require__(53);
  2891. var Hammer = __webpack_require__(45);
  2892. var util = __webpack_require__(1);
  2893. var DataSet = __webpack_require__(3);
  2894. var DataView = __webpack_require__(4);
  2895. var Range = __webpack_require__(10);
  2896. var Core = __webpack_require__(46);
  2897. var TimeAxis = __webpack_require__(30);
  2898. var CurrentTime = __webpack_require__(21);
  2899. var CustomTime = __webpack_require__(22);
  2900. var ItemSet = __webpack_require__(27);
  2901. /**
  2902. * Create a timeline visualization
  2903. * @param {HTMLElement} container
  2904. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  2905. * @param {Object} [options] See Timeline.setOptions for the available options.
  2906. * @constructor
  2907. * @extends Core
  2908. */
  2909. function Timeline (container, items, groups, options) {
  2910. if (!(this instanceof Timeline)) {
  2911. throw new SyntaxError('Constructor must be called with the new operator');
  2912. }
  2913. // if the third element is options, the forth is groups (optionally);
  2914. if (!(Array.isArray(groups) || groups instanceof DataSet) && groups instanceof Object) {
  2915. var forthArgument = options;
  2916. options = groups;
  2917. groups = forthArgument;
  2918. }
  2919. var me = this;
  2920. this.defaultOptions = {
  2921. start: null,
  2922. end: null,
  2923. autoResize: true,
  2924. orientation: 'bottom',
  2925. width: null,
  2926. height: null,
  2927. maxHeight: null,
  2928. minHeight: null
  2929. };
  2930. this.options = util.deepExtend({}, this.defaultOptions);
  2931. // Create the DOM, props, and emitter
  2932. this._create(container);
  2933. // all components listed here will be repainted automatically
  2934. this.components = [];
  2935. this.body = {
  2936. dom: this.dom,
  2937. domProps: this.props,
  2938. emitter: {
  2939. on: this.on.bind(this),
  2940. off: this.off.bind(this),
  2941. emit: this.emit.bind(this)
  2942. },
  2943. hiddenDates: [],
  2944. util: {
  2945. snap: null, // will be specified after TimeAxis is created
  2946. toScreen: me._toScreen.bind(me),
  2947. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  2948. toTime: me._toTime.bind(me),
  2949. toGlobalTime : me._toGlobalTime.bind(me)
  2950. }
  2951. };
  2952. // range
  2953. this.range = new Range(this.body);
  2954. this.components.push(this.range);
  2955. this.body.range = this.range;
  2956. // time axis
  2957. this.timeAxis = new TimeAxis(this.body);
  2958. this.components.push(this.timeAxis);
  2959. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  2960. // current time bar
  2961. this.currentTime = new CurrentTime(this.body);
  2962. this.components.push(this.currentTime);
  2963. // custom time bar
  2964. // Note: time bar will be attached in this.setOptions when selected
  2965. this.customTime = new CustomTime(this.body);
  2966. this.components.push(this.customTime);
  2967. // item set
  2968. this.itemSet = new ItemSet(this.body);
  2969. this.components.push(this.itemSet);
  2970. this.itemsData = null; // DataSet
  2971. this.groupsData = null; // DataSet
  2972. // apply options
  2973. if (options) {
  2974. this.setOptions(options);
  2975. }
  2976. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  2977. if (groups) {
  2978. this.setGroups(groups);
  2979. }
  2980. // create itemset
  2981. if (items) {
  2982. this.setItems(items);
  2983. }
  2984. else {
  2985. this.redraw();
  2986. }
  2987. }
  2988. // Extend the functionality from Core
  2989. Timeline.prototype = new Core();
  2990. /**
  2991. * Set items
  2992. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  2993. */
  2994. Timeline.prototype.setItems = function(items) {
  2995. var initialLoad = (this.itemsData == null);
  2996. // convert to type DataSet when needed
  2997. var newDataSet;
  2998. if (!items) {
  2999. newDataSet = null;
  3000. }
  3001. else if (items instanceof DataSet || items instanceof DataView) {
  3002. newDataSet = items;
  3003. }
  3004. else {
  3005. // turn an array into a dataset
  3006. newDataSet = new DataSet(items, {
  3007. type: {
  3008. start: 'Date',
  3009. end: 'Date'
  3010. }
  3011. });
  3012. }
  3013. // set items
  3014. this.itemsData = newDataSet;
  3015. this.itemSet && this.itemSet.setItems(newDataSet);
  3016. if (initialLoad) {
  3017. if (this.options.start != undefined || this.options.end != undefined) {
  3018. var start = this.options.start != undefined ? this.options.start : null;
  3019. var end = this.options.end != undefined ? this.options.end : null;
  3020. this.setWindow(start, end, {animate: false});
  3021. }
  3022. else {
  3023. this.fit({animate: false});
  3024. }
  3025. }
  3026. };
  3027. /**
  3028. * Set groups
  3029. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  3030. */
  3031. Timeline.prototype.setGroups = function(groups) {
  3032. // convert to type DataSet when needed
  3033. var newDataSet;
  3034. if (!groups) {
  3035. newDataSet = null;
  3036. }
  3037. else if (groups instanceof DataSet || groups instanceof DataView) {
  3038. newDataSet = groups;
  3039. }
  3040. else {
  3041. // turn an array into a dataset
  3042. newDataSet = new DataSet(groups);
  3043. }
  3044. this.groupsData = newDataSet;
  3045. this.itemSet.setGroups(newDataSet);
  3046. };
  3047. /**
  3048. * Set selected items by their id. Replaces the current selection
  3049. * Unknown id's are silently ignored.
  3050. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  3051. * selected. If ids is an empty array, all items will be
  3052. * unselected.
  3053. * @param {Object} [options] Available options:
  3054. * `focus: boolean`
  3055. * If true, focus will be set to the selected item(s)
  3056. * `animate: boolean | number`
  3057. * If true (default), the range is animated
  3058. * smoothly to the new window.
  3059. * If a number, the number is taken as duration
  3060. * for the animation. Default duration is 500 ms.
  3061. * Only applicable when option focus is true.
  3062. */
  3063. Timeline.prototype.setSelection = function(ids, options) {
  3064. this.itemSet && this.itemSet.setSelection(ids);
  3065. if (options && options.focus) {
  3066. this.focus(ids, options);
  3067. }
  3068. };
  3069. /**
  3070. * Get the selected items by their id
  3071. * @return {Array} ids The ids of the selected items
  3072. */
  3073. Timeline.prototype.getSelection = function() {
  3074. return this.itemSet && this.itemSet.getSelection() || [];
  3075. };
  3076. /**
  3077. * Adjust the visible window such that the selected item (or multiple items)
  3078. * are centered on screen.
  3079. * @param {String | String[]} id An item id or array with item ids
  3080. * @param {Object} [options] Available options:
  3081. * `animate: boolean | number`
  3082. * If true (default), the range is animated
  3083. * smoothly to the new window.
  3084. * If a number, the number is taken as duration
  3085. * for the animation. Default duration is 500 ms.
  3086. * Only applicable when option focus is true
  3087. */
  3088. Timeline.prototype.focus = function(id, options) {
  3089. if (!this.itemsData || id == undefined) return;
  3090. var ids = Array.isArray(id) ? id : [id];
  3091. // get the specified item(s)
  3092. var itemsData = this.itemsData.getDataSet().get(ids, {
  3093. type: {
  3094. start: 'Date',
  3095. end: 'Date'
  3096. }
  3097. });
  3098. // calculate minimum start and maximum end of specified items
  3099. var start = null;
  3100. var end = null;
  3101. itemsData.forEach(function (itemData) {
  3102. var s = itemData.start.valueOf();
  3103. var e = 'end' in itemData ? itemData.end.valueOf() : itemData.start.valueOf();
  3104. if (start === null || s < start) {
  3105. start = s;
  3106. }
  3107. if (end === null || e > end) {
  3108. end = e;
  3109. }
  3110. });
  3111. if (start !== null && end !== null) {
  3112. // calculate the new middle and interval for the window
  3113. var middle = (start + end) / 2;
  3114. var interval = Math.max((this.range.end - this.range.start), (end - start) * 1.1);
  3115. var animate = (options && options.animate !== undefined) ? options.animate : true;
  3116. this.range.setRange(middle - interval / 2, middle + interval / 2, animate);
  3117. }
  3118. };
  3119. /**
  3120. * Get the data range of the item set.
  3121. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  3122. * When no minimum is found, min==null
  3123. * When no maximum is found, max==null
  3124. */
  3125. Timeline.prototype.getItemRange = function() {
  3126. // calculate min from start filed
  3127. var dataset = this.itemsData.getDataSet(),
  3128. min = null,
  3129. max = null;
  3130. if (dataset) {
  3131. // calculate the minimum value of the field 'start'
  3132. var minItem = dataset.min('start');
  3133. min = minItem ? util.convert(minItem.start, 'Date').valueOf() : null;
  3134. // Note: we convert first to Date and then to number because else
  3135. // a conversion from ISODate to Number will fail
  3136. // calculate maximum value of fields 'start' and 'end'
  3137. var maxStartItem = dataset.max('start');
  3138. if (maxStartItem) {
  3139. max = util.convert(maxStartItem.start, 'Date').valueOf();
  3140. }
  3141. var maxEndItem = dataset.max('end');
  3142. if (maxEndItem) {
  3143. if (max == null) {
  3144. max = util.convert(maxEndItem.end, 'Date').valueOf();
  3145. }
  3146. else {
  3147. max = Math.max(max, util.convert(maxEndItem.end, 'Date').valueOf());
  3148. }
  3149. }
  3150. }
  3151. return {
  3152. min: (min != null) ? new Date(min) : null,
  3153. max: (max != null) ? new Date(max) : null
  3154. };
  3155. };
  3156. module.exports = Timeline;
  3157. /***/ },
  3158. /* 7 */
  3159. /***/ function(module, exports, __webpack_require__) {
  3160. var Emitter = __webpack_require__(53);
  3161. var Hammer = __webpack_require__(45);
  3162. var util = __webpack_require__(1);
  3163. var DataSet = __webpack_require__(3);
  3164. var DataView = __webpack_require__(4);
  3165. var Range = __webpack_require__(10);
  3166. var Core = __webpack_require__(46);
  3167. var TimeAxis = __webpack_require__(30);
  3168. var CurrentTime = __webpack_require__(21);
  3169. var CustomTime = __webpack_require__(22);
  3170. var LineGraph = __webpack_require__(29);
  3171. /**
  3172. * Create a timeline visualization
  3173. * @param {HTMLElement} container
  3174. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  3175. * @param {Object} [options] See Graph2d.setOptions for the available options.
  3176. * @constructor
  3177. * @extends Core
  3178. */
  3179. function Graph2d (container, items, groups, options) {
  3180. // if the third element is options, the forth is groups (optionally);
  3181. if (!(Array.isArray(groups) || groups instanceof DataSet) && groups instanceof Object) {
  3182. var forthArgument = options;
  3183. options = groups;
  3184. groups = forthArgument;
  3185. }
  3186. var me = this;
  3187. this.defaultOptions = {
  3188. start: null,
  3189. end: null,
  3190. autoResize: true,
  3191. orientation: 'bottom',
  3192. width: null,
  3193. height: null,
  3194. maxHeight: null,
  3195. minHeight: null
  3196. };
  3197. this.options = util.deepExtend({}, this.defaultOptions);
  3198. // Create the DOM, props, and emitter
  3199. this._create(container);
  3200. // all components listed here will be repainted automatically
  3201. this.components = [];
  3202. this.body = {
  3203. dom: this.dom,
  3204. domProps: this.props,
  3205. emitter: {
  3206. on: this.on.bind(this),
  3207. off: this.off.bind(this),
  3208. emit: this.emit.bind(this)
  3209. },
  3210. hiddenDates: [],
  3211. util: {
  3212. snap: null, // will be specified after TimeAxis is created
  3213. toScreen: me._toScreen.bind(me),
  3214. toGlobalScreen: me._toGlobalScreen.bind(me), // this refers to the root.width
  3215. toTime: me._toTime.bind(me),
  3216. toGlobalTime : me._toGlobalTime.bind(me)
  3217. }
  3218. };
  3219. // range
  3220. this.range = new Range(this.body);
  3221. this.components.push(this.range);
  3222. this.body.range = this.range;
  3223. // time axis
  3224. this.timeAxis = new TimeAxis(this.body);
  3225. this.components.push(this.timeAxis);
  3226. this.body.util.snap = this.timeAxis.snap.bind(this.timeAxis);
  3227. // current time bar
  3228. this.currentTime = new CurrentTime(this.body);
  3229. this.components.push(this.currentTime);
  3230. // custom time bar
  3231. // Note: time bar will be attached in this.setOptions when selected
  3232. this.customTime = new CustomTime(this.body);
  3233. this.components.push(this.customTime);
  3234. // item set
  3235. this.linegraph = new LineGraph(this.body);
  3236. this.components.push(this.linegraph);
  3237. this.itemsData = null; // DataSet
  3238. this.groupsData = null; // DataSet
  3239. // apply options
  3240. if (options) {
  3241. this.setOptions(options);
  3242. }
  3243. // IMPORTANT: THIS HAPPENS BEFORE SET ITEMS!
  3244. if (groups) {
  3245. this.setGroups(groups);
  3246. }
  3247. // create itemset
  3248. if (items) {
  3249. this.setItems(items);
  3250. }
  3251. else {
  3252. this.redraw();
  3253. }
  3254. }
  3255. // Extend the functionality from Core
  3256. Graph2d.prototype = new Core();
  3257. /**
  3258. * Set items
  3259. * @param {vis.DataSet | Array | google.visualization.DataTable | null} items
  3260. */
  3261. Graph2d.prototype.setItems = function(items) {
  3262. var initialLoad = (this.itemsData == null);
  3263. // convert to type DataSet when needed
  3264. var newDataSet;
  3265. if (!items) {
  3266. newDataSet = null;
  3267. }
  3268. else if (items instanceof DataSet || items instanceof DataView) {
  3269. newDataSet = items;
  3270. }
  3271. else {
  3272. // turn an array into a dataset
  3273. newDataSet = new DataSet(items, {
  3274. type: {
  3275. start: 'Date',
  3276. end: 'Date'
  3277. }
  3278. });
  3279. }
  3280. // set items
  3281. this.itemsData = newDataSet;
  3282. this.linegraph && this.linegraph.setItems(newDataSet);
  3283. if (initialLoad) {
  3284. if (this.options.start != undefined || this.options.end != undefined) {
  3285. var start = this.options.start != undefined ? this.options.start : null;
  3286. var end = this.options.end != undefined ? this.options.end : null;
  3287. this.setWindow(start, end, {animate: false});
  3288. }
  3289. else {
  3290. this.fit({animate: false});
  3291. }
  3292. }
  3293. };
  3294. /**
  3295. * Set groups
  3296. * @param {vis.DataSet | Array | google.visualization.DataTable} groups
  3297. */
  3298. Graph2d.prototype.setGroups = function(groups) {
  3299. // convert to type DataSet when needed
  3300. var newDataSet;
  3301. if (!groups) {
  3302. newDataSet = null;
  3303. }
  3304. else if (groups instanceof DataSet || groups instanceof DataView) {
  3305. newDataSet = groups;
  3306. }
  3307. else {
  3308. // turn an array into a dataset
  3309. newDataSet = new DataSet(groups);
  3310. }
  3311. this.groupsData = newDataSet;
  3312. this.linegraph.setGroups(newDataSet);
  3313. };
  3314. /**
  3315. * Returns an object containing an SVG element with the icon of the group (size determined by iconWidth and iconHeight), the label of the group (content) and the yAxisOrientation of the group (left or right).
  3316. * @param groupId
  3317. * @param width
  3318. * @param height
  3319. */
  3320. Graph2d.prototype.getLegend = function(groupId, width, height) {
  3321. if (width === undefined) {width = 15;}
  3322. if (height === undefined) {height = 15;}
  3323. if (this.linegraph.groups[groupId] !== undefined) {
  3324. return this.linegraph.groups[groupId].getLegend(width,height);
  3325. }
  3326. else {
  3327. return "cannot find group:" + groupId;
  3328. }
  3329. }
  3330. /**
  3331. * This checks if the visible option of the supplied group (by ID) is true or false.
  3332. * @param groupId
  3333. * @returns {*}
  3334. */
  3335. Graph2d.prototype.isGroupVisible = function(groupId) {
  3336. if (this.linegraph.groups[groupId] !== undefined) {
  3337. return (this.linegraph.groups[groupId].visible && (this.linegraph.options.groups.visibility[groupId] === undefined || this.linegraph.options.groups.visibility[groupId] == true));
  3338. }
  3339. else {
  3340. return false;
  3341. }
  3342. }
  3343. /**
  3344. * Get the data range of the item set.
  3345. * @returns {{min: Date, max: Date}} range A range with a start and end Date.
  3346. * When no minimum is found, min==null
  3347. * When no maximum is found, max==null
  3348. */
  3349. Graph2d.prototype.getItemRange = function() {
  3350. var min = null;
  3351. var max = null;
  3352. // calculate min from start filed
  3353. for (var groupId in this.linegraph.groups) {
  3354. if (this.linegraph.groups.hasOwnProperty(groupId)) {
  3355. if (this.linegraph.groups[groupId].visible == true) {
  3356. for (var i = 0; i < this.linegraph.groups[groupId].itemsData.length; i++) {
  3357. var item = this.linegraph.groups[groupId].itemsData[i];
  3358. var value = util.convert(item.x, 'Date').valueOf();
  3359. min = min == null ? value : min > value ? value : min;
  3360. max = max == null ? value : max < value ? value : max;
  3361. }
  3362. }
  3363. }
  3364. }
  3365. return {
  3366. min: (min != null) ? new Date(min) : null,
  3367. max: (max != null) ? new Date(max) : null
  3368. };
  3369. };
  3370. module.exports = Graph2d;
  3371. /***/ },
  3372. /* 8 */
  3373. /***/ function(module, exports, __webpack_require__) {
  3374. /**
  3375. * Created by Alex on 10/3/2014.
  3376. */
  3377. var moment = __webpack_require__(44);
  3378. /**
  3379. * used in Core to convert the options into a volatile variable
  3380. *
  3381. * @param Core
  3382. */
  3383. exports.convertHiddenOptions = function(body, hiddenDates) {
  3384. body.hiddenDates = [];
  3385. if (hiddenDates) {
  3386. if (Array.isArray(hiddenDates) == true) {
  3387. for (var i = 0; i < hiddenDates.length; i++) {
  3388. if (hiddenDates[i].repeat === undefined) {
  3389. var dateItem = {};
  3390. dateItem.start = moment(hiddenDates[i].start).toDate().valueOf();
  3391. dateItem.end = moment(hiddenDates[i].end).toDate().valueOf();
  3392. body.hiddenDates.push(dateItem);
  3393. }
  3394. }
  3395. body.hiddenDates.sort(function (a, b) {
  3396. return a.start - b.start;
  3397. }); // sort by start time
  3398. }
  3399. }
  3400. };
  3401. /**
  3402. * create new entrees for the repeating hidden dates
  3403. * @param body
  3404. * @param hiddenDates
  3405. */
  3406. exports.updateHiddenDates = function (body, hiddenDates) {
  3407. if (hiddenDates && body.domProps.centerContainer.width !== undefined) {
  3408. exports.convertHiddenOptions(body, hiddenDates);
  3409. var start = moment(body.range.start);
  3410. var end = moment(body.range.end);
  3411. var totalRange = (body.range.end - body.range.start);
  3412. var pixelTime = totalRange / body.domProps.centerContainer.width;
  3413. for (var i = 0; i < hiddenDates.length; i++) {
  3414. if (hiddenDates[i].repeat !== undefined) {
  3415. var startDate = moment(hiddenDates[i].start);
  3416. var endDate = moment(hiddenDates[i].end);
  3417. if (startDate._d == "Invalid Date") {
  3418. throw new Error("Supplied start date is not valid: " + hiddenDates[i].start);
  3419. }
  3420. if (endDate._d == "Invalid Date") {
  3421. throw new Error("Supplied end date is not valid: " + hiddenDates[i].end);
  3422. }
  3423. var duration = endDate - startDate;
  3424. if (duration >= 4 * pixelTime) {
  3425. var offset = 0;
  3426. var runUntil = end.clone();
  3427. switch (hiddenDates[i].repeat) {
  3428. case "daily": // case of time
  3429. if (startDate.day() != endDate.day()) {
  3430. offset = 1;
  3431. }
  3432. startDate.dayOfYear(start.dayOfYear());
  3433. startDate.year(start.year());
  3434. startDate.subtract(7,'days');
  3435. endDate.dayOfYear(start.dayOfYear());
  3436. endDate.year(start.year());
  3437. endDate.subtract(7 - offset,'days');
  3438. runUntil.add(1, 'weeks');
  3439. break;
  3440. case "weekly":
  3441. var dayOffset = endDate.diff(startDate,'days')
  3442. var day = startDate.day();
  3443. // set the start date to the range.start
  3444. startDate.date(start.date());
  3445. startDate.month(start.month());
  3446. startDate.year(start.year());
  3447. endDate = startDate.clone();
  3448. // force
  3449. startDate.day(day);
  3450. endDate.day(day);
  3451. endDate.add(dayOffset,'days');
  3452. startDate.subtract(1,'weeks');
  3453. endDate.subtract(1,'weeks');
  3454. runUntil.add(1, 'weeks');
  3455. break
  3456. case "monthly":
  3457. if (startDate.month() != endDate.month()) {
  3458. offset = 1;
  3459. }
  3460. startDate.month(start.month());
  3461. startDate.year(start.year());
  3462. startDate.subtract(1,'months');
  3463. endDate.month(start.month());
  3464. endDate.year(start.year());
  3465. endDate.subtract(1,'months');
  3466. endDate.add(offset,'months');
  3467. runUntil.add(1, 'months');
  3468. break;
  3469. case "yearly":
  3470. if (startDate.year() != endDate.year()) {
  3471. offset = 1;
  3472. }
  3473. startDate.year(start.year());
  3474. startDate.subtract(1,'years');
  3475. endDate.year(start.year());
  3476. endDate.subtract(1,'years');
  3477. endDate.add(offset,'years');
  3478. runUntil.add(1, 'years');
  3479. break;
  3480. default:
  3481. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  3482. return;
  3483. }
  3484. while (startDate < runUntil) {
  3485. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  3486. switch (hiddenDates[i].repeat) {
  3487. case "daily":
  3488. startDate.add(1, 'days');
  3489. endDate.add(1, 'days');
  3490. break;
  3491. case "weekly":
  3492. startDate.add(1, 'weeks');
  3493. endDate.add(1, 'weeks');
  3494. break
  3495. case "monthly":
  3496. startDate.add(1, 'months');
  3497. endDate.add(1, 'months');
  3498. break;
  3499. case "yearly":
  3500. startDate.add(1, 'y');
  3501. endDate.add(1, 'y');
  3502. break;
  3503. default:
  3504. console.log("Wrong repeat format, allowed are: daily, weekly, monthly, yearly. Given:", hiddenDates[i].repeat);
  3505. return;
  3506. }
  3507. }
  3508. body.hiddenDates.push({start: startDate.valueOf(), end: endDate.valueOf()});
  3509. }
  3510. }
  3511. }
  3512. // remove duplicates, merge where possible
  3513. exports.removeDuplicates(body);
  3514. // ensure the new positions are not on hidden dates
  3515. var startHidden = exports.isHidden(body.range.start, body.hiddenDates);
  3516. var endHidden = exports.isHidden(body.range.end,body.hiddenDates);
  3517. var rangeStart = body.range.start;
  3518. var rangeEnd = body.range.end;
  3519. if (startHidden.hidden == true) {rangeStart = body.range.startToFront == true ? startHidden.startDate - 1 : startHidden.endDate + 1;}
  3520. if (endHidden.hidden == true) {rangeEnd = body.range.endToFront == true ? endHidden.startDate - 1 : endHidden.endDate + 1;}
  3521. if (startHidden.hidden == true || endHidden.hidden == true) {
  3522. body.range._applyRange(rangeStart, rangeEnd);
  3523. }
  3524. }
  3525. }
  3526. /**
  3527. * remove duplicates from the hidden dates list. Duplicates are evil. They mess everything up.
  3528. * Scales with N^2
  3529. * @param body
  3530. */
  3531. exports.removeDuplicates = function(body) {
  3532. var hiddenDates = body.hiddenDates;
  3533. var safeDates = [];
  3534. for (var i = 0; i < hiddenDates.length; i++) {
  3535. for (var j = 0; j < hiddenDates.length; j++) {
  3536. if (i != j && hiddenDates[j].remove != true && hiddenDates[i].remove != true) {
  3537. // j inside i
  3538. if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  3539. hiddenDates[j].remove = true;
  3540. }
  3541. // j start inside i
  3542. else if (hiddenDates[j].start >= hiddenDates[i].start && hiddenDates[j].start <= hiddenDates[i].end) {
  3543. hiddenDates[i].end = hiddenDates[j].end;
  3544. hiddenDates[j].remove = true;
  3545. }
  3546. // j end inside i
  3547. else if (hiddenDates[j].end >= hiddenDates[i].start && hiddenDates[j].end <= hiddenDates[i].end) {
  3548. hiddenDates[i].start = hiddenDates[j].start;
  3549. hiddenDates[j].remove = true;
  3550. }
  3551. }
  3552. }
  3553. }
  3554. for (var i = 0; i < hiddenDates.length; i++) {
  3555. if (hiddenDates[i].remove !== true) {
  3556. safeDates.push(hiddenDates[i]);
  3557. }
  3558. }
  3559. body.hiddenDates = safeDates;
  3560. body.hiddenDates.sort(function (a, b) {
  3561. return a.start - b.start;
  3562. }); // sort by start time
  3563. }
  3564. exports.printDates = function(dates) {
  3565. for (var i =0; i < dates.length; i++) {
  3566. console.log(i, new Date(dates[i].start),new Date(dates[i].end), dates[i].start, dates[i].end, dates[i].remove);
  3567. }
  3568. }
  3569. /**
  3570. * Used in TimeStep to avoid the hidden times.
  3571. * @param timeStep
  3572. * @param previousTime
  3573. */
  3574. exports.stepOverHiddenDates = function(timeStep, previousTime) {
  3575. var stepInHidden = false;
  3576. var currentValue = timeStep.current.valueOf();
  3577. for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  3578. var startDate = timeStep.hiddenDates[i].start;
  3579. var endDate = timeStep.hiddenDates[i].end;
  3580. if (currentValue >= startDate && currentValue < endDate) {
  3581. stepInHidden = true;
  3582. break;
  3583. }
  3584. }
  3585. if (stepInHidden == true && currentValue < timeStep._end.valueOf() && currentValue != previousTime) {
  3586. var prevValue = moment(previousTime);
  3587. var newValue = moment(endDate);
  3588. //check if the next step should be major
  3589. if (prevValue.year() != newValue.year()) {timeStep.switchedYear = true;}
  3590. else if (prevValue.month() != newValue.month()) {timeStep.switchedMonth = true;}
  3591. else if (prevValue.dayOfYear() != newValue.dayOfYear()) {timeStep.switchedDay = true;}
  3592. timeStep.current = newValue.toDate();
  3593. }
  3594. };
  3595. /**
  3596. * Used in TimeStep to avoid the hidden times.
  3597. * @param timeStep
  3598. * @param previousTime
  3599. */
  3600. exports.checkFirstStep = function(timeStep) {
  3601. var stepInHidden = false;
  3602. var currentValue = timeStep.current.valueOf();
  3603. for (var i = 0; i < timeStep.hiddenDates.length; i++) {
  3604. var startDate = timeStep.hiddenDates[i].start;
  3605. var endDate = timeStep.hiddenDates[i].end;
  3606. if (currentValue >= startDate && currentValue < endDate) {
  3607. stepInHidden = true;
  3608. break;
  3609. }
  3610. }
  3611. if (stepInHidden == true && currentValue <= timeStep._end.valueOf()) {
  3612. var newValue = moment(endDate);
  3613. timeStep.current = newValue.toDate();
  3614. }
  3615. };
  3616. /**
  3617. * replaces the Core toScreen methods
  3618. * @param Core
  3619. * @param time
  3620. * @param width
  3621. * @returns {number}
  3622. */
  3623. exports.toScreen = function(Core, time, width) {
  3624. var hidden = exports.isHidden(time, Core.body.hiddenDates)
  3625. if (hidden.hidden == true) {
  3626. time = hidden.startDate;
  3627. }
  3628. var duration = exports.getHiddenDurationBetween(Core.body.hiddenDates, Core.range.start, Core.range.end);
  3629. time = exports.correctTimeForHidden(Core.body.hiddenDates, Core.range, time);
  3630. var conversion = Core.range.conversion(width, duration);
  3631. return (time.valueOf() - conversion.offset) * conversion.scale;
  3632. };
  3633. /**
  3634. * Replaces the core toTime methods
  3635. * @param body
  3636. * @param range
  3637. * @param x
  3638. * @param width
  3639. * @returns {Date}
  3640. */
  3641. exports.toTime = function(body, range, x, width) {
  3642. var hiddenDuration = exports.getHiddenDurationBetween(body.hiddenDates, range.start, range.end);
  3643. var totalDuration = range.end - range.start - hiddenDuration;
  3644. var partialDuration = totalDuration * x / width;
  3645. var accumulatedHiddenDuration = exports.getAccumulatedHiddenDuration(body.hiddenDates,range, partialDuration);
  3646. var newTime = new Date(accumulatedHiddenDuration + partialDuration + range.start);
  3647. return newTime;
  3648. };
  3649. /**
  3650. * Support function
  3651. *
  3652. * @param hiddenDates
  3653. * @param range
  3654. * @returns {number}
  3655. */
  3656. exports.getHiddenDurationBetween = function(hiddenDates, start, end) {
  3657. var duration = 0;
  3658. for (var i = 0; i < hiddenDates.length; i++) {
  3659. var startDate = hiddenDates[i].start;
  3660. var endDate = hiddenDates[i].end;
  3661. // if time after the cutout, and the
  3662. if (startDate >= start && endDate < end) {
  3663. duration += endDate - startDate;
  3664. }
  3665. }
  3666. return duration;
  3667. };
  3668. /**
  3669. * Support function
  3670. * @param hiddenDates
  3671. * @param range
  3672. * @param time
  3673. * @returns {{duration: number, time: *, offset: number}}
  3674. */
  3675. exports.correctTimeForHidden = function(hiddenDates, range, time) {
  3676. time = moment(time).toDate().valueOf();
  3677. time -= exports.getHiddenDurationBefore(hiddenDates,range,time);
  3678. return time;
  3679. };
  3680. exports.getHiddenDurationBefore = function(hiddenDates, range, time) {
  3681. var timeOffset = 0;
  3682. time = moment(time).toDate().valueOf();
  3683. for (var i = 0; i < hiddenDates.length; i++) {
  3684. var startDate = hiddenDates[i].start;
  3685. var endDate = hiddenDates[i].end;
  3686. // if time after the cutout, and the
  3687. if (startDate >= range.start && endDate < range.end) {
  3688. if (time >= endDate) {
  3689. timeOffset += (endDate - startDate);
  3690. }
  3691. }
  3692. }
  3693. return timeOffset;
  3694. }
  3695. /**
  3696. * sum the duration from start to finish, including the hidden duration,
  3697. * until the required amount has been reached, return the accumulated hidden duration
  3698. * @param hiddenDates
  3699. * @param range
  3700. * @param time
  3701. * @returns {{duration: number, time: *, offset: number}}
  3702. */
  3703. exports.getAccumulatedHiddenDuration = function(hiddenDates, range, requiredDuration) {
  3704. var hiddenDuration = 0;
  3705. var duration = 0;
  3706. var previousPoint = range.start;
  3707. //exports.printDates(hiddenDates)
  3708. for (var i = 0; i < hiddenDates.length; i++) {
  3709. var startDate = hiddenDates[i].start;
  3710. var endDate = hiddenDates[i].end;
  3711. // if time after the cutout, and the
  3712. if (startDate >= range.start && endDate < range.end) {
  3713. duration += startDate - previousPoint;
  3714. previousPoint = endDate;
  3715. if (duration >= requiredDuration) {
  3716. break;
  3717. }
  3718. else {
  3719. hiddenDuration += endDate - startDate;
  3720. }
  3721. }
  3722. }
  3723. return hiddenDuration;
  3724. };
  3725. /**
  3726. * used to step over to either side of a hidden block. Correction is disabled on tablets, might be set to true
  3727. * @param hiddenDates
  3728. * @param time
  3729. * @param direction
  3730. * @param correctionEnabled
  3731. * @returns {*}
  3732. */
  3733. exports.snapAwayFromHidden = function(hiddenDates, time, direction, correctionEnabled) {
  3734. var isHidden = exports.isHidden(time, hiddenDates);
  3735. if (isHidden.hidden == true) {
  3736. if (direction < 0) {
  3737. if (correctionEnabled == true) {
  3738. return isHidden.startDate - (isHidden.endDate - time) - 1;
  3739. }
  3740. else {
  3741. return isHidden.startDate - 1;
  3742. }
  3743. }
  3744. else {
  3745. if (correctionEnabled == true) {
  3746. return isHidden.endDate + (time - isHidden.startDate) + 1;
  3747. }
  3748. else {
  3749. return isHidden.endDate + 1;
  3750. }
  3751. }
  3752. }
  3753. else {
  3754. return time;
  3755. }
  3756. }
  3757. /**
  3758. * Check if a time is hidden
  3759. *
  3760. * @param time
  3761. * @param hiddenDates
  3762. * @returns {{hidden: boolean, startDate: Window.start, endDate: *}}
  3763. */
  3764. exports.isHidden = function(time, hiddenDates) {
  3765. for (var i = 0; i < hiddenDates.length; i++) {
  3766. var startDate = hiddenDates[i].start;
  3767. var endDate = hiddenDates[i].end;
  3768. if (time >= startDate && time < endDate) { // if the start is entering a hidden zone
  3769. return {hidden: true, startDate: startDate, endDate: endDate};
  3770. break;
  3771. }
  3772. }
  3773. return {hidden: false, startDate: startDate, endDate: endDate};
  3774. }
  3775. /***/ },
  3776. /* 9 */
  3777. /***/ function(module, exports, __webpack_require__) {
  3778. /**
  3779. * @constructor DataStep
  3780. * The class DataStep is an iterator for data for the lineGraph. You provide a start data point and an
  3781. * end data point. The class itself determines the best scale (step size) based on the
  3782. * provided start Date, end Date, and minimumStep.
  3783. *
  3784. * If minimumStep is provided, the step size is chosen as close as possible
  3785. * to the minimumStep but larger than minimumStep. If minimumStep is not
  3786. * provided, the scale is set to 1 DAY.
  3787. * The minimumStep should correspond with the onscreen size of about 6 characters
  3788. *
  3789. * Alternatively, you can set a scale by hand.
  3790. * After creation, you can initialize the class by executing first(). Then you
  3791. * can iterate from the start date to the end date via next(). You can check if
  3792. * the end date is reached with the function hasNext(). After each step, you can
  3793. * retrieve the current date via getCurrent().
  3794. * The DataStep has scales ranging from milliseconds, seconds, minutes, hours,
  3795. * days, to years.
  3796. *
  3797. * Version: 1.2
  3798. *
  3799. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  3800. * or new Date(2010, 9, 21, 23, 45, 00)
  3801. * @param {Date} [end] The end date
  3802. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  3803. */
  3804. function DataStep(start, end, minimumStep, containerHeight, customRange) {
  3805. // variables
  3806. this.current = 0;
  3807. this.autoScale = true;
  3808. this.stepIndex = 0;
  3809. this.step = 1;
  3810. this.scale = 1;
  3811. this.marginStart;
  3812. this.marginEnd;
  3813. this.deadSpace = 0;
  3814. this.majorSteps = [1, 2, 5, 10];
  3815. this.minorSteps = [0.25, 0.5, 1, 2];
  3816. this.setRange(start, end, minimumStep, containerHeight, customRange);
  3817. }
  3818. /**
  3819. * Set a new range
  3820. * If minimumStep is provided, the step size is chosen as close as possible
  3821. * to the minimumStep but larger than minimumStep. If minimumStep is not
  3822. * provided, the scale is set to 1 DAY.
  3823. * The minimumStep should correspond with the onscreen size of about 6 characters
  3824. * @param {Number} [start] The start date and time.
  3825. * @param {Number} [end] The end date and time.
  3826. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  3827. */
  3828. DataStep.prototype.setRange = function(start, end, minimumStep, containerHeight, customRange) {
  3829. this._start = customRange.min === undefined ? start : customRange.min;
  3830. this._end = customRange.max === undefined ? end : customRange.max;
  3831. if (this._start == this._end) {
  3832. this._start -= 0.75;
  3833. this._end += 1;
  3834. }
  3835. if (this.autoScale) {
  3836. this.setMinimumStep(minimumStep, containerHeight);
  3837. }
  3838. this.setFirst(customRange);
  3839. };
  3840. /**
  3841. * Automatically determine the scale that bests fits the provided minimum step
  3842. * @param {Number} [minimumStep] The minimum step size in milliseconds
  3843. */
  3844. DataStep.prototype.setMinimumStep = function(minimumStep, containerHeight) {
  3845. // round to floor
  3846. var size = this._end - this._start;
  3847. var safeSize = size * 1.2;
  3848. var minimumStepValue = minimumStep * (safeSize / containerHeight);
  3849. var orderOfMagnitude = Math.round(Math.log(safeSize)/Math.LN10);
  3850. var minorStepIdx = -1;
  3851. var magnitudefactor = Math.pow(10,orderOfMagnitude);
  3852. var start = 0;
  3853. if (orderOfMagnitude < 0) {
  3854. start = orderOfMagnitude;
  3855. }
  3856. var solutionFound = false;
  3857. for (var i = start; Math.abs(i) <= Math.abs(orderOfMagnitude); i++) {
  3858. magnitudefactor = Math.pow(10,i);
  3859. for (var j = 0; j < this.minorSteps.length; j++) {
  3860. var stepSize = magnitudefactor * this.minorSteps[j];
  3861. if (stepSize >= minimumStepValue) {
  3862. solutionFound = true;
  3863. minorStepIdx = j;
  3864. break;
  3865. }
  3866. }
  3867. if (solutionFound == true) {
  3868. break;
  3869. }
  3870. }
  3871. this.stepIndex = minorStepIdx;
  3872. this.scale = magnitudefactor;
  3873. this.step = magnitudefactor * this.minorSteps[minorStepIdx];
  3874. };
  3875. /**
  3876. * Round the current date to the first minor date value
  3877. * This must be executed once when the current date is set to start Date
  3878. */
  3879. DataStep.prototype.setFirst = function(customRange) {
  3880. if (customRange === undefined) {
  3881. customRange = {};
  3882. }
  3883. var niceStart = customRange.min === undefined ? this._start - (this.scale * 2 * this.minorSteps[this.stepIndex]) : customRange.min;
  3884. var niceEnd = customRange.max === undefined ? this._end + (this.scale * this.minorSteps[this.stepIndex]) : customRange.max;
  3885. this.marginEnd = customRange.max === undefined ? this.roundToMinor(niceEnd) : customRange.max;
  3886. this.marginStart = customRange.min === undefined ? this.roundToMinor(niceStart) : customRange.min;
  3887. this.deadSpace = this.roundToMinor(niceEnd) - niceEnd + this.roundToMinor(niceStart) - niceStart;
  3888. this.marginRange = this.marginEnd - this.marginStart;
  3889. this.current = this.marginEnd;
  3890. };
  3891. DataStep.prototype.roundToMinor = function(value) {
  3892. var rounded = value - (value % (this.scale * this.minorSteps[this.stepIndex]));
  3893. if (value % (this.scale * this.minorSteps[this.stepIndex]) > 0.5 * (this.scale * this.minorSteps[this.stepIndex])) {
  3894. return rounded + (this.scale * this.minorSteps[this.stepIndex]);
  3895. }
  3896. else {
  3897. return rounded;
  3898. }
  3899. }
  3900. /**
  3901. * Check if the there is a next step
  3902. * @return {boolean} true if the current date has not passed the end date
  3903. */
  3904. DataStep.prototype.hasNext = function () {
  3905. return (this.current >= this.marginStart);
  3906. };
  3907. /**
  3908. * Do the next step
  3909. */
  3910. DataStep.prototype.next = function() {
  3911. var prev = this.current;
  3912. this.current -= this.step;
  3913. // safety mechanism: if current time is still unchanged, move to the end
  3914. if (this.current == prev) {
  3915. this.current = this._end;
  3916. }
  3917. };
  3918. /**
  3919. * Do the next step
  3920. */
  3921. DataStep.prototype.previous = function() {
  3922. this.current += this.step;
  3923. this.marginEnd += this.step;
  3924. this.marginRange = this.marginEnd - this.marginStart;
  3925. };
  3926. /**
  3927. * Get the current datetime
  3928. * @return {String} current The current date
  3929. */
  3930. DataStep.prototype.getCurrent = function() {
  3931. var toPrecision = '' + Number(this.current).toPrecision(5);
  3932. if (toPrecision.indexOf(",") != -1 || toPrecision.indexOf(".") != -1) {
  3933. for (var i = toPrecision.length-1; i > 0; i--) {
  3934. if (toPrecision[i] == "0") {
  3935. toPrecision = toPrecision.slice(0,i);
  3936. }
  3937. else if (toPrecision[i] == "." || toPrecision[i] == ",") {
  3938. toPrecision = toPrecision.slice(0,i);
  3939. break;
  3940. }
  3941. else{
  3942. break;
  3943. }
  3944. }
  3945. }
  3946. return toPrecision;
  3947. };
  3948. /**
  3949. * Snap a date to a rounded value.
  3950. * The snap intervals are dependent on the current scale and step.
  3951. * @param {Date} date the date to be snapped.
  3952. * @return {Date} snappedDate
  3953. */
  3954. DataStep.prototype.snap = function(date) {
  3955. };
  3956. /**
  3957. * Check if the current value is a major value (for example when the step
  3958. * is DAY, a major value is each first day of the MONTH)
  3959. * @return {boolean} true if current date is major, else false.
  3960. */
  3961. DataStep.prototype.isMajor = function() {
  3962. return (this.current % (this.scale * this.majorSteps[this.stepIndex]) == 0);
  3963. };
  3964. module.exports = DataStep;
  3965. /***/ },
  3966. /* 10 */
  3967. /***/ function(module, exports, __webpack_require__) {
  3968. var util = __webpack_require__(1);
  3969. var hammerUtil = __webpack_require__(47);
  3970. var moment = __webpack_require__(44);
  3971. var Component = __webpack_require__(20);
  3972. var DateUtil = __webpack_require__(8);
  3973. /**
  3974. * @constructor Range
  3975. * A Range controls a numeric range with a start and end value.
  3976. * The Range adjusts the range based on mouse events or programmatic changes,
  3977. * and triggers events when the range is changing or has been changed.
  3978. * @param {{dom: Object, domProps: Object, emitter: Emitter}} body
  3979. * @param {Object} [options] See description at Range.setOptions
  3980. */
  3981. function Range(body, options) {
  3982. var now = moment().hours(0).minutes(0).seconds(0).milliseconds(0);
  3983. this.start = now.clone().add(-3, 'days').valueOf(); // Number
  3984. this.end = now.clone().add(4, 'days').valueOf(); // Number
  3985. this.body = body;
  3986. this.deltaDifference = 0;
  3987. this.scaleOffset = 0;
  3988. this.startToFront = false;
  3989. this.endToFront = true;
  3990. // default options
  3991. this.defaultOptions = {
  3992. start: null,
  3993. end: null,
  3994. direction: 'horizontal', // 'horizontal' or 'vertical'
  3995. moveable: true,
  3996. zoomable: true,
  3997. min: null,
  3998. max: null,
  3999. zoomMin: 10, // milliseconds
  4000. zoomMax: 1000 * 60 * 60 * 24 * 365 * 10000 // milliseconds
  4001. };
  4002. this.options = util.extend({}, this.defaultOptions);
  4003. this.props = {
  4004. touch: {}
  4005. };
  4006. this.animateTimer = null;
  4007. // drag listeners for dragging
  4008. this.body.emitter.on('dragstart', this._onDragStart.bind(this));
  4009. this.body.emitter.on('drag', this._onDrag.bind(this));
  4010. this.body.emitter.on('dragend', this._onDragEnd.bind(this));
  4011. // ignore dragging when holding
  4012. this.body.emitter.on('hold', this._onHold.bind(this));
  4013. // mouse wheel for zooming
  4014. this.body.emitter.on('mousewheel', this._onMouseWheel.bind(this));
  4015. this.body.emitter.on('DOMMouseScroll', this._onMouseWheel.bind(this)); // For FF
  4016. // pinch to zoom
  4017. this.body.emitter.on('touch', this._onTouch.bind(this));
  4018. this.body.emitter.on('pinch', this._onPinch.bind(this));
  4019. this.setOptions(options);
  4020. }
  4021. Range.prototype = new Component();
  4022. /**
  4023. * Set options for the range controller
  4024. * @param {Object} options Available options:
  4025. * {Number | Date | String} start Start date for the range
  4026. * {Number | Date | String} end End date for the range
  4027. * {Number} min Minimum value for start
  4028. * {Number} max Maximum value for end
  4029. * {Number} zoomMin Set a minimum value for
  4030. * (end - start).
  4031. * {Number} zoomMax Set a maximum value for
  4032. * (end - start).
  4033. * {Boolean} moveable Enable moving of the range
  4034. * by dragging. True by default
  4035. * {Boolean} zoomable Enable zooming of the range
  4036. * by pinching/scrolling. True by default
  4037. */
  4038. Range.prototype.setOptions = function (options) {
  4039. if (options) {
  4040. // copy the options that we know
  4041. var fields = ['direction', 'min', 'max', 'zoomMin', 'zoomMax', 'moveable', 'zoomable', 'activate', 'hiddenDates'];
  4042. util.selectiveExtend(fields, this.options, options);
  4043. if ('start' in options || 'end' in options) {
  4044. // apply a new range. both start and end are optional
  4045. this.setRange(options.start, options.end);
  4046. }
  4047. }
  4048. };
  4049. /**
  4050. * Test whether direction has a valid value
  4051. * @param {String} direction 'horizontal' or 'vertical'
  4052. */
  4053. function validateDirection (direction) {
  4054. if (direction != 'horizontal' && direction != 'vertical') {
  4055. throw new TypeError('Unknown direction "' + direction + '". ' +
  4056. 'Choose "horizontal" or "vertical".');
  4057. }
  4058. }
  4059. /**
  4060. * Set a new start and end range
  4061. * @param {Date | Number | String} [start]
  4062. * @param {Date | Number | String} [end]
  4063. * @param {boolean | number} [animate=false] If true, the range is animated
  4064. * smoothly to the new window.
  4065. * If animate is a number, the
  4066. * number is taken as duration
  4067. * Default duration is 500 ms.
  4068. *
  4069. */
  4070. Range.prototype.setRange = function(start, end, animate) {
  4071. var _start = start != undefined ? util.convert(start, 'Date').valueOf() : null;
  4072. var _end = end != undefined ? util.convert(end, 'Date').valueOf() : null;
  4073. this._cancelAnimation();
  4074. if (animate) {
  4075. var me = this;
  4076. var initStart = this.start;
  4077. var initEnd = this.end;
  4078. var duration = typeof animate === 'number' ? animate : 500;
  4079. var initTime = new Date().valueOf();
  4080. var anyChanged = false;
  4081. function next() {
  4082. if (!me.props.touch.dragging) {
  4083. var now = new Date().valueOf();
  4084. var time = now - initTime;
  4085. var done = time > duration;
  4086. var s = (done || _start === null) ? _start : util.easeInOutQuad(time, initStart, _start, duration);
  4087. var e = (done || _end === null) ? _end : util.easeInOutQuad(time, initEnd, _end, duration);
  4088. changed = me._applyRange(s, e);
  4089. DateUtil.updateHiddenDates(me.body, me.options.hiddenDates);
  4090. anyChanged = anyChanged || changed;
  4091. if (changed) {
  4092. me.body.emitter.emit('rangechange', {start: new Date(me.start), end: new Date(me.end)});
  4093. }
  4094. if (done) {
  4095. if (anyChanged) {
  4096. me.body.emitter.emit('rangechanged', {start: new Date(me.start), end: new Date(me.end)});
  4097. }
  4098. }
  4099. else {
  4100. // animate with as high as possible frame rate, leave 20 ms in between
  4101. // each to prevent the browser from blocking
  4102. me.animateTimer = setTimeout(next, 20);
  4103. }
  4104. }
  4105. }
  4106. return next();
  4107. }
  4108. else {
  4109. var changed = this._applyRange(_start, _end);
  4110. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  4111. if (changed) {
  4112. var params = {start: new Date(this.start), end: new Date(this.end)};
  4113. this.body.emitter.emit('rangechange', params);
  4114. this.body.emitter.emit('rangechanged', params);
  4115. }
  4116. }
  4117. };
  4118. /**
  4119. * Stop an animation
  4120. * @private
  4121. */
  4122. Range.prototype._cancelAnimation = function () {
  4123. if (this.animateTimer) {
  4124. clearTimeout(this.animateTimer);
  4125. this.animateTimer = null;
  4126. }
  4127. };
  4128. /**
  4129. * Set a new start and end range. This method is the same as setRange, but
  4130. * does not trigger a range change and range changed event, and it returns
  4131. * true when the range is changed
  4132. * @param {Number} [start]
  4133. * @param {Number} [end]
  4134. * @return {Boolean} changed
  4135. * @private
  4136. */
  4137. Range.prototype._applyRange = function(start, end) {
  4138. var newStart = (start != null) ? util.convert(start, 'Date').valueOf() : this.start,
  4139. newEnd = (end != null) ? util.convert(end, 'Date').valueOf() : this.end,
  4140. max = (this.options.max != null) ? util.convert(this.options.max, 'Date').valueOf() : null,
  4141. min = (this.options.min != null) ? util.convert(this.options.min, 'Date').valueOf() : null,
  4142. diff;
  4143. // check for valid number
  4144. if (isNaN(newStart) || newStart === null) {
  4145. throw new Error('Invalid start "' + start + '"');
  4146. }
  4147. if (isNaN(newEnd) || newEnd === null) {
  4148. throw new Error('Invalid end "' + end + '"');
  4149. }
  4150. // prevent start < end
  4151. if (newEnd < newStart) {
  4152. newEnd = newStart;
  4153. }
  4154. // prevent start < min
  4155. if (min !== null) {
  4156. if (newStart < min) {
  4157. diff = (min - newStart);
  4158. newStart += diff;
  4159. newEnd += diff;
  4160. // prevent end > max
  4161. if (max != null) {
  4162. if (newEnd > max) {
  4163. newEnd = max;
  4164. }
  4165. }
  4166. }
  4167. }
  4168. // prevent end > max
  4169. if (max !== null) {
  4170. if (newEnd > max) {
  4171. diff = (newEnd - max);
  4172. newStart -= diff;
  4173. newEnd -= diff;
  4174. // prevent start < min
  4175. if (min != null) {
  4176. if (newStart < min) {
  4177. newStart = min;
  4178. }
  4179. }
  4180. }
  4181. }
  4182. // prevent (end-start) < zoomMin
  4183. if (this.options.zoomMin !== null) {
  4184. var zoomMin = parseFloat(this.options.zoomMin);
  4185. if (zoomMin < 0) {
  4186. zoomMin = 0;
  4187. }
  4188. if ((newEnd - newStart) < zoomMin) {
  4189. if ((this.end - this.start) === zoomMin) {
  4190. // ignore this action, we are already zoomed to the minimum
  4191. newStart = this.start;
  4192. newEnd = this.end;
  4193. }
  4194. else {
  4195. // zoom to the minimum
  4196. diff = (zoomMin - (newEnd - newStart));
  4197. newStart -= diff / 2;
  4198. newEnd += diff / 2;
  4199. }
  4200. }
  4201. }
  4202. // prevent (end-start) > zoomMax
  4203. if (this.options.zoomMax !== null) {
  4204. var zoomMax = parseFloat(this.options.zoomMax);
  4205. if (zoomMax < 0) {
  4206. zoomMax = 0;
  4207. }
  4208. if ((newEnd - newStart) > zoomMax) {
  4209. if ((this.end - this.start) === zoomMax) {
  4210. // ignore this action, we are already zoomed to the maximum
  4211. newStart = this.start;
  4212. newEnd = this.end;
  4213. }
  4214. else {
  4215. // zoom to the maximum
  4216. diff = ((newEnd - newStart) - zoomMax);
  4217. newStart += diff / 2;
  4218. newEnd -= diff / 2;
  4219. }
  4220. }
  4221. }
  4222. var changed = (this.start != newStart || this.end != newEnd);
  4223. this.start = newStart;
  4224. this.end = newEnd;
  4225. return changed;
  4226. };
  4227. /**
  4228. * Retrieve the current range.
  4229. * @return {Object} An object with start and end properties
  4230. */
  4231. Range.prototype.getRange = function() {
  4232. return {
  4233. start: this.start,
  4234. end: this.end
  4235. };
  4236. };
  4237. /**
  4238. * Calculate the conversion offset and scale for current range, based on
  4239. * the provided width
  4240. * @param {Number} width
  4241. * @returns {{offset: number, scale: number}} conversion
  4242. */
  4243. Range.prototype.conversion = function (width, totalHidden) {
  4244. return Range.conversion(this.start, this.end, width, totalHidden);
  4245. };
  4246. /**
  4247. * Static method to calculate the conversion offset and scale for a range,
  4248. * based on the provided start, end, and width
  4249. * @param {Number} start
  4250. * @param {Number} end
  4251. * @param {Number} width
  4252. * @returns {{offset: number, scale: number}} conversion
  4253. */
  4254. Range.conversion = function (start, end, width, totalHidden) {
  4255. if (totalHidden === undefined) {
  4256. totalHidden = 0;
  4257. }
  4258. if (width != 0 && (end - start != 0)) {
  4259. return {
  4260. offset: start,
  4261. scale: width / (end - start - totalHidden)
  4262. }
  4263. }
  4264. else {
  4265. return {
  4266. offset: 0,
  4267. scale: 1
  4268. };
  4269. }
  4270. };
  4271. /**
  4272. * Start dragging horizontally or vertically
  4273. * @param {Event} event
  4274. * @private
  4275. */
  4276. Range.prototype._onDragStart = function(event) {
  4277. this.deltaDifference = 0;
  4278. this.previousDelta = 0;
  4279. // only allow dragging when configured as movable
  4280. if (!this.options.moveable) return;
  4281. // refuse to drag when we where pinching to prevent the timeline make a jump
  4282. // when releasing the fingers in opposite order from the touch screen
  4283. if (!this.props.touch.allowDragging) return;
  4284. this.props.touch.start = this.start;
  4285. this.props.touch.end = this.end;
  4286. this.props.touch.dragging = true;
  4287. if (this.body.dom.root) {
  4288. this.body.dom.root.style.cursor = 'move';
  4289. }
  4290. };
  4291. /**
  4292. * Perform dragging operation
  4293. * @param {Event} event
  4294. * @private
  4295. */
  4296. Range.prototype._onDrag = function (event) {
  4297. // only allow dragging when configured as movable
  4298. if (!this.options.moveable) return;
  4299. // refuse to drag when we where pinching to prevent the timeline make a jump
  4300. // when releasing the fingers in opposite order from the touch screen
  4301. if (!this.props.touch.allowDragging) return;
  4302. var direction = this.options.direction;
  4303. validateDirection(direction);
  4304. var delta = (direction == 'horizontal') ? event.gesture.deltaX : event.gesture.deltaY;
  4305. delta -= this.deltaDifference;
  4306. var interval = (this.props.touch.end - this.props.touch.start);
  4307. // normalize dragging speed if cutout is in between.
  4308. var duration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  4309. interval -= duration;
  4310. var width = (direction == 'horizontal') ? this.body.domProps.center.width : this.body.domProps.center.height;
  4311. var diffRange = -delta / width * interval;
  4312. var newStart = this.props.touch.start + diffRange;
  4313. var newEnd = this.props.touch.end + diffRange;
  4314. // snapping times away from hidden zones
  4315. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, this.previousDelta-delta, true);
  4316. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, this.previousDelta-delta, true);
  4317. if (safeStart != newStart || safeEnd != newEnd) {
  4318. this.deltaDifference += delta;
  4319. this.props.touch.start = safeStart;
  4320. this.props.touch.end = safeEnd;
  4321. this._onDrag(event);
  4322. return;
  4323. }
  4324. this.previousDelta = delta;
  4325. this._applyRange(newStart, newEnd);
  4326. // fire a rangechange event
  4327. this.body.emitter.emit('rangechange', {
  4328. start: new Date(this.start),
  4329. end: new Date(this.end)
  4330. });
  4331. };
  4332. /**
  4333. * Stop dragging operation
  4334. * @param {event} event
  4335. * @private
  4336. */
  4337. Range.prototype._onDragEnd = function (event) {
  4338. // only allow dragging when configured as movable
  4339. if (!this.options.moveable) return;
  4340. // refuse to drag when we where pinching to prevent the timeline make a jump
  4341. // when releasing the fingers in opposite order from the touch screen
  4342. if (!this.props.touch.allowDragging) return;
  4343. this.props.touch.dragging = false;
  4344. if (this.body.dom.root) {
  4345. this.body.dom.root.style.cursor = 'auto';
  4346. }
  4347. // fire a rangechanged event
  4348. this.body.emitter.emit('rangechanged', {
  4349. start: new Date(this.start),
  4350. end: new Date(this.end)
  4351. });
  4352. };
  4353. /**
  4354. * Event handler for mouse wheel event, used to zoom
  4355. * Code from http://adomas.org/javascript-mouse-wheel/
  4356. * @param {Event} event
  4357. * @private
  4358. */
  4359. Range.prototype._onMouseWheel = function(event) {
  4360. // only allow zooming when configured as zoomable and moveable
  4361. if (!(this.options.zoomable && this.options.moveable)) return;
  4362. // retrieve delta
  4363. var delta = 0;
  4364. if (event.wheelDelta) { /* IE/Opera. */
  4365. delta = event.wheelDelta / 120;
  4366. } else if (event.detail) { /* Mozilla case. */
  4367. // In Mozilla, sign of delta is different than in IE.
  4368. // Also, delta is multiple of 3.
  4369. delta = -event.detail / 3;
  4370. }
  4371. // If delta is nonzero, handle it.
  4372. // Basically, delta is now positive if wheel was scrolled up,
  4373. // and negative, if wheel was scrolled down.
  4374. if (delta) {
  4375. // perform the zoom action. Delta is normally 1 or -1
  4376. // adjust a negative delta such that zooming in with delta 0.1
  4377. // equals zooming out with a delta -0.1
  4378. var scale;
  4379. if (delta < 0) {
  4380. scale = 1 - (delta / 5);
  4381. }
  4382. else {
  4383. scale = 1 / (1 + (delta / 5)) ;
  4384. }
  4385. // calculate center, the date to zoom around
  4386. var gesture = hammerUtil.fakeGesture(this, event),
  4387. pointer = getPointer(gesture.center, this.body.dom.center),
  4388. pointerDate = this._pointerToDate(pointer);
  4389. this.zoom(scale, pointerDate, delta);
  4390. }
  4391. // Prevent default actions caused by mouse wheel
  4392. // (else the page and timeline both zoom and scroll)
  4393. event.preventDefault();
  4394. };
  4395. /**
  4396. * Start of a touch gesture
  4397. * @private
  4398. */
  4399. Range.prototype._onTouch = function (event) {
  4400. this.props.touch.start = this.start;
  4401. this.props.touch.end = this.end;
  4402. this.props.touch.allowDragging = true;
  4403. this.props.touch.center = null;
  4404. this.scaleOffset = 0;
  4405. this.deltaDifference = 0;
  4406. };
  4407. /**
  4408. * On start of a hold gesture
  4409. * @private
  4410. */
  4411. Range.prototype._onHold = function () {
  4412. this.props.touch.allowDragging = false;
  4413. };
  4414. /**
  4415. * Handle pinch event
  4416. * @param {Event} event
  4417. * @private
  4418. */
  4419. Range.prototype._onPinch = function (event) {
  4420. // only allow zooming when configured as zoomable and moveable
  4421. if (!(this.options.zoomable && this.options.moveable)) return;
  4422. this.props.touch.allowDragging = false;
  4423. if (event.gesture.touches.length > 1) {
  4424. if (!this.props.touch.center) {
  4425. this.props.touch.center = getPointer(event.gesture.center, this.body.dom.center);
  4426. }
  4427. var scale = 1 / (event.gesture.scale + this.scaleOffset);
  4428. var center = this._pointerToDate(this.props.touch.center);
  4429. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  4430. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, center);
  4431. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  4432. // calculate new start and end
  4433. var newStart = (center-hiddenDurationBefore) + (this.start - (center-hiddenDurationBefore)) * scale;
  4434. var newEnd = (center+hiddenDurationAfter) + (this.end - (center+hiddenDurationAfter)) * scale;
  4435. // snapping times away from hidden zones
  4436. this.startToFront = 1 - scale > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  4437. this.endToFront = scale - 1 > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  4438. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, 1 - scale, true);
  4439. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, scale - 1, true);
  4440. if (safeStart != newStart || safeEnd != newEnd) {
  4441. this.props.touch.start = safeStart;
  4442. this.props.touch.end = safeEnd;
  4443. this.scaleOffset = 1 - event.gesture.scale;
  4444. newStart = safeStart;
  4445. newEnd = safeEnd;
  4446. }
  4447. this.setRange(newStart, newEnd);
  4448. this.startToFront = false; // revert to default
  4449. this.endToFront = true; // revert to default
  4450. }
  4451. };
  4452. /**
  4453. * Helper function to calculate the center date for zooming
  4454. * @param {{x: Number, y: Number}} pointer
  4455. * @return {number} date
  4456. * @private
  4457. */
  4458. Range.prototype._pointerToDate = function (pointer) {
  4459. var conversion;
  4460. var direction = this.options.direction;
  4461. validateDirection(direction);
  4462. if (direction == 'horizontal') {
  4463. return this.body.util.toTime(pointer.x).valueOf();
  4464. }
  4465. else {
  4466. var height = this.body.domProps.center.height;
  4467. conversion = this.conversion(height);
  4468. return pointer.y / conversion.scale + conversion.offset;
  4469. }
  4470. };
  4471. /**
  4472. * Get the pointer location relative to the location of the dom element
  4473. * @param {{pageX: Number, pageY: Number}} touch
  4474. * @param {Element} element HTML DOM element
  4475. * @return {{x: Number, y: Number}} pointer
  4476. * @private
  4477. */
  4478. function getPointer (touch, element) {
  4479. return {
  4480. x: touch.pageX - util.getAbsoluteLeft(element),
  4481. y: touch.pageY - util.getAbsoluteTop(element)
  4482. };
  4483. }
  4484. /**
  4485. * Zoom the range the given scale in or out. Start and end date will
  4486. * be adjusted, and the timeline will be redrawn. You can optionally give a
  4487. * date around which to zoom.
  4488. * For example, try scale = 0.9 or 1.1
  4489. * @param {Number} scale Scaling factor. Values above 1 will zoom out,
  4490. * values below 1 will zoom in.
  4491. * @param {Number} [center] Value representing a date around which will
  4492. * be zoomed.
  4493. */
  4494. Range.prototype.zoom = function(scale, center, delta) {
  4495. // if centerDate is not provided, take it half between start Date and end Date
  4496. if (center == null) {
  4497. center = (this.start + this.end) / 2;
  4498. }
  4499. var hiddenDuration = DateUtil.getHiddenDurationBetween(this.body.hiddenDates, this.start, this.end);
  4500. var hiddenDurationBefore = DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this, center);
  4501. var hiddenDurationAfter = hiddenDuration - hiddenDurationBefore;
  4502. // calculate new start and end
  4503. var newStart = (center-hiddenDurationBefore) + (this.start - (center-hiddenDurationBefore)) * scale;
  4504. var newEnd = (center+hiddenDurationAfter) + (this.end - (center+hiddenDurationAfter)) * scale;
  4505. // snapping times away from hidden zones
  4506. this.startToFront = delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  4507. this.endToFront = -delta > 0 ? false : true; // used to do the right autocorrection with periodic hidden times
  4508. var safeStart = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newStart, delta, true);
  4509. var safeEnd = DateUtil.snapAwayFromHidden(this.body.hiddenDates, newEnd, -delta, true);
  4510. if (safeStart != newStart || safeEnd != newEnd) {
  4511. newStart = safeStart;
  4512. newEnd = safeEnd;
  4513. }
  4514. this.setRange(newStart, newEnd);
  4515. this.startToFront = false; // revert to default
  4516. this.endToFront = true; // revert to default
  4517. };
  4518. /**
  4519. * Move the range with a given delta to the left or right. Start and end
  4520. * value will be adjusted. For example, try delta = 0.1 or -0.1
  4521. * @param {Number} delta Moving amount. Positive value will move right,
  4522. * negative value will move left
  4523. */
  4524. Range.prototype.move = function(delta) {
  4525. // zoom start Date and end Date relative to the centerDate
  4526. var diff = (this.end - this.start);
  4527. // apply new values
  4528. var newStart = this.start + diff * delta;
  4529. var newEnd = this.end + diff * delta;
  4530. // TODO: reckon with min and max range
  4531. this.start = newStart;
  4532. this.end = newEnd;
  4533. };
  4534. /**
  4535. * Move the range to a new center point
  4536. * @param {Number} moveTo New center point of the range
  4537. */
  4538. Range.prototype.moveTo = function(moveTo) {
  4539. var center = (this.start + this.end) / 2;
  4540. var diff = center - moveTo;
  4541. // calculate new start and end
  4542. var newStart = this.start - diff;
  4543. var newEnd = this.end - diff;
  4544. this.setRange(newStart, newEnd);
  4545. };
  4546. module.exports = Range;
  4547. /***/ },
  4548. /* 11 */
  4549. /***/ function(module, exports, __webpack_require__) {
  4550. // Utility functions for ordering and stacking of items
  4551. var EPSILON = 0.001; // used when checking collisions, to prevent round-off errors
  4552. /**
  4553. * Order items by their start data
  4554. * @param {Item[]} items
  4555. */
  4556. exports.orderByStart = function(items) {
  4557. items.sort(function (a, b) {
  4558. return a.data.start - b.data.start;
  4559. });
  4560. };
  4561. /**
  4562. * Order items by their end date. If they have no end date, their start date
  4563. * is used.
  4564. * @param {Item[]} items
  4565. */
  4566. exports.orderByEnd = function(items) {
  4567. items.sort(function (a, b) {
  4568. var aTime = ('end' in a.data) ? a.data.end : a.data.start,
  4569. bTime = ('end' in b.data) ? b.data.end : b.data.start;
  4570. return aTime - bTime;
  4571. });
  4572. };
  4573. /**
  4574. * Adjust vertical positions of the items such that they don't overlap each
  4575. * other.
  4576. * @param {Item[]} items
  4577. * All visible items
  4578. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  4579. * Margins between items and between items and the axis.
  4580. * @param {boolean} [force=false]
  4581. * If true, all items will be repositioned. If false (default), only
  4582. * items having a top===null will be re-stacked
  4583. */
  4584. exports.stack = function(items, margin, force) {
  4585. var i, iMax;
  4586. if (force) {
  4587. // reset top position of all items
  4588. for (i = 0, iMax = items.length; i < iMax; i++) {
  4589. items[i].top = null;
  4590. }
  4591. }
  4592. // calculate new, non-overlapping positions
  4593. for (i = 0, iMax = items.length; i < iMax; i++) {
  4594. var item = items[i];
  4595. if (item.stack && item.top === null) {
  4596. // initialize top position
  4597. item.top = margin.axis;
  4598. do {
  4599. // TODO: optimize checking for overlap. when there is a gap without items,
  4600. // you only need to check for items from the next item on, not from zero
  4601. var collidingItem = null;
  4602. for (var j = 0, jj = items.length; j < jj; j++) {
  4603. var other = items[j];
  4604. if (other.top !== null && other !== item && other.stack && exports.collision(item, other, margin.item)) {
  4605. collidingItem = other;
  4606. break;
  4607. }
  4608. }
  4609. if (collidingItem != null) {
  4610. // There is a collision. Reposition the items above the colliding element
  4611. item.top = collidingItem.top + collidingItem.height + margin.item.vertical;
  4612. }
  4613. } while (collidingItem);
  4614. }
  4615. }
  4616. };
  4617. /**
  4618. * Adjust vertical positions of the items without stacking them
  4619. * @param {Item[]} items
  4620. * All visible items
  4621. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  4622. * Margins between items and between items and the axis.
  4623. */
  4624. exports.nostack = function(items, margin, subgroups) {
  4625. var i, iMax, newTop;
  4626. // reset top position of all items
  4627. for (i = 0, iMax = items.length; i < iMax; i++) {
  4628. if (items[i].data.subgroup !== undefined) {
  4629. newTop = margin.axis;
  4630. for (var subgroup in subgroups) {
  4631. if (subgroups.hasOwnProperty(subgroup)) {
  4632. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroups[items[i].data.subgroup].index) {
  4633. newTop += subgroups[subgroup].height + margin.item.vertical;
  4634. }
  4635. }
  4636. }
  4637. items[i].top = newTop;
  4638. }
  4639. else {
  4640. items[i].top = margin.axis;
  4641. }
  4642. }
  4643. };
  4644. /**
  4645. * Test if the two provided items collide
  4646. * The items must have parameters left, width, top, and height.
  4647. * @param {Item} a The first item
  4648. * @param {Item} b The second item
  4649. * @param {{horizontal: number, vertical: number}} margin
  4650. * An object containing a horizontal and vertical
  4651. * minimum required margin.
  4652. * @return {boolean} true if a and b collide, else false
  4653. */
  4654. exports.collision = function(a, b, margin) {
  4655. return ((a.left - margin.horizontal + EPSILON) < (b.left + b.width) &&
  4656. (a.left + a.width + margin.horizontal - EPSILON) > b.left &&
  4657. (a.top - margin.vertical + EPSILON) < (b.top + b.height) &&
  4658. (a.top + a.height + margin.vertical - EPSILON) > b.top);
  4659. };
  4660. /***/ },
  4661. /* 12 */
  4662. /***/ function(module, exports, __webpack_require__) {
  4663. var moment = __webpack_require__(44);
  4664. var DateUtil = __webpack_require__(8);
  4665. /**
  4666. * @constructor TimeStep
  4667. * The class TimeStep is an iterator for dates. You provide a start date and an
  4668. * end date. The class itself determines the best scale (step size) based on the
  4669. * provided start Date, end Date, and minimumStep.
  4670. *
  4671. * If minimumStep is provided, the step size is chosen as close as possible
  4672. * to the minimumStep but larger than minimumStep. If minimumStep is not
  4673. * provided, the scale is set to 1 DAY.
  4674. * The minimumStep should correspond with the onscreen size of about 6 characters
  4675. *
  4676. * Alternatively, you can set a scale by hand.
  4677. * After creation, you can initialize the class by executing first(). Then you
  4678. * can iterate from the start date to the end date via next(). You can check if
  4679. * the end date is reached with the function hasNext(). After each step, you can
  4680. * retrieve the current date via getCurrent().
  4681. * The TimeStep has scales ranging from milliseconds, seconds, minutes, hours,
  4682. * days, to years.
  4683. *
  4684. * Version: 1.2
  4685. *
  4686. * @param {Date} [start] The start date, for example new Date(2010, 9, 21)
  4687. * or new Date(2010, 9, 21, 23, 45, 00)
  4688. * @param {Date} [end] The end date
  4689. * @param {Number} [minimumStep] Optional. Minimum step size in milliseconds
  4690. */
  4691. function TimeStep(start, end, minimumStep, hiddenDates) {
  4692. // variables
  4693. this.current = new Date();
  4694. this._start = new Date();
  4695. this._end = new Date();
  4696. this.autoScale = true;
  4697. this.scale = TimeStep.SCALE.DAY;
  4698. this.step = 1;
  4699. // initialize the range
  4700. this.setRange(start, end, minimumStep);
  4701. // hidden Dates options
  4702. this.switchedDay = false;
  4703. this.switchedMonth = false;
  4704. this.switchedYear = false;
  4705. this.hiddenDates = hiddenDates;
  4706. if (hiddenDates === undefined) {
  4707. this.hiddenDates = [];
  4708. }
  4709. }
  4710. /// enum scale
  4711. TimeStep.SCALE = {
  4712. MILLISECOND: 1,
  4713. SECOND: 2,
  4714. MINUTE: 3,
  4715. HOUR: 4,
  4716. DAY: 5,
  4717. WEEKDAY: 6,
  4718. MONTH: 7,
  4719. YEAR: 8
  4720. };
  4721. /**
  4722. * Set a new range
  4723. * If minimumStep is provided, the step size is chosen as close as possible
  4724. * to the minimumStep but larger than minimumStep. If minimumStep is not
  4725. * provided, the scale is set to 1 DAY.
  4726. * The minimumStep should correspond with the onscreen size of about 6 characters
  4727. * @param {Date} [start] The start date and time.
  4728. * @param {Date} [end] The end date and time.
  4729. * @param {int} [minimumStep] Optional. Minimum step size in milliseconds
  4730. */
  4731. TimeStep.prototype.setRange = function(start, end, minimumStep) {
  4732. if (!(start instanceof Date) || !(end instanceof Date)) {
  4733. throw "No legal start or end date in method setRange";
  4734. }
  4735. this._start = (start != undefined) ? new Date(start.valueOf()) : new Date();
  4736. this._end = (end != undefined) ? new Date(end.valueOf()) : new Date();
  4737. if (this.autoScale) {
  4738. this.setMinimumStep(minimumStep);
  4739. }
  4740. };
  4741. /**
  4742. * Set the range iterator to the start date.
  4743. */
  4744. TimeStep.prototype.first = function() {
  4745. this.current = new Date(this._start.valueOf());
  4746. this.roundToMinor();
  4747. };
  4748. /**
  4749. * Round the current date to the first minor date value
  4750. * This must be executed once when the current date is set to start Date
  4751. */
  4752. TimeStep.prototype.roundToMinor = function() {
  4753. // round to floor
  4754. // IMPORTANT: we have no breaks in this switch! (this is no bug)
  4755. //noinspection FallthroughInSwitchStatementJS
  4756. switch (this.scale) {
  4757. case TimeStep.SCALE.YEAR:
  4758. this.current.setFullYear(this.step * Math.floor(this.current.getFullYear() / this.step));
  4759. this.current.setMonth(0);
  4760. case TimeStep.SCALE.MONTH: this.current.setDate(1);
  4761. case TimeStep.SCALE.DAY: // intentional fall through
  4762. case TimeStep.SCALE.WEEKDAY: this.current.setHours(0);
  4763. case TimeStep.SCALE.HOUR: this.current.setMinutes(0);
  4764. case TimeStep.SCALE.MINUTE: this.current.setSeconds(0);
  4765. case TimeStep.SCALE.SECOND: this.current.setMilliseconds(0);
  4766. //case TimeStep.SCALE.MILLISECOND: // nothing to do for milliseconds
  4767. }
  4768. if (this.step != 1) {
  4769. // round down to the first minor value that is a multiple of the current step size
  4770. switch (this.scale) {
  4771. case TimeStep.SCALE.MILLISECOND: this.current.setMilliseconds(this.current.getMilliseconds() - this.current.getMilliseconds() % this.step); break;
  4772. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() - this.current.getSeconds() % this.step); break;
  4773. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() - this.current.getMinutes() % this.step); break;
  4774. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() - this.current.getHours() % this.step); break;
  4775. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  4776. case TimeStep.SCALE.DAY: this.current.setDate((this.current.getDate()-1) - (this.current.getDate()-1) % this.step + 1); break;
  4777. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() - this.current.getMonth() % this.step); break;
  4778. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() - this.current.getFullYear() % this.step); break;
  4779. default: break;
  4780. }
  4781. }
  4782. };
  4783. /**
  4784. * Check if the there is a next step
  4785. * @return {boolean} true if the current date has not passed the end date
  4786. */
  4787. TimeStep.prototype.hasNext = function () {
  4788. return (this.current.valueOf() <= this._end.valueOf());
  4789. };
  4790. /**
  4791. * Do the next step
  4792. */
  4793. TimeStep.prototype.next = function() {
  4794. var prev = this.current.valueOf();
  4795. // Two cases, needed to prevent issues with switching daylight savings
  4796. // (end of March and end of October)
  4797. if (this.current.getMonth() < 6) {
  4798. switch (this.scale) {
  4799. case TimeStep.SCALE.MILLISECOND:
  4800. this.current = new Date(this.current.valueOf() + this.step); break;
  4801. case TimeStep.SCALE.SECOND: this.current = new Date(this.current.valueOf() + this.step * 1000); break;
  4802. case TimeStep.SCALE.MINUTE: this.current = new Date(this.current.valueOf() + this.step * 1000 * 60); break;
  4803. case TimeStep.SCALE.HOUR:
  4804. this.current = new Date(this.current.valueOf() + this.step * 1000 * 60 * 60);
  4805. // in case of skipping an hour for daylight savings, adjust the hour again (else you get: 0h 5h 9h ... instead of 0h 4h 8h ...)
  4806. var h = this.current.getHours();
  4807. this.current.setHours(h - (h % this.step));
  4808. break;
  4809. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  4810. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  4811. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  4812. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  4813. default: break;
  4814. }
  4815. }
  4816. else {
  4817. switch (this.scale) {
  4818. case TimeStep.SCALE.MILLISECOND: this.current = new Date(this.current.valueOf() + this.step); break;
  4819. case TimeStep.SCALE.SECOND: this.current.setSeconds(this.current.getSeconds() + this.step); break;
  4820. case TimeStep.SCALE.MINUTE: this.current.setMinutes(this.current.getMinutes() + this.step); break;
  4821. case TimeStep.SCALE.HOUR: this.current.setHours(this.current.getHours() + this.step); break;
  4822. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  4823. case TimeStep.SCALE.DAY: this.current.setDate(this.current.getDate() + this.step); break;
  4824. case TimeStep.SCALE.MONTH: this.current.setMonth(this.current.getMonth() + this.step); break;
  4825. case TimeStep.SCALE.YEAR: this.current.setFullYear(this.current.getFullYear() + this.step); break;
  4826. default: break;
  4827. }
  4828. }
  4829. if (this.step != 1) {
  4830. // round down to the correct major value
  4831. switch (this.scale) {
  4832. case TimeStep.SCALE.MILLISECOND: if(this.current.getMilliseconds() < this.step) this.current.setMilliseconds(0); break;
  4833. case TimeStep.SCALE.SECOND: if(this.current.getSeconds() < this.step) this.current.setSeconds(0); break;
  4834. case TimeStep.SCALE.MINUTE: if(this.current.getMinutes() < this.step) this.current.setMinutes(0); break;
  4835. case TimeStep.SCALE.HOUR: if(this.current.getHours() < this.step) this.current.setHours(0); break;
  4836. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  4837. case TimeStep.SCALE.DAY: if(this.current.getDate() < this.step+1) this.current.setDate(1); break;
  4838. case TimeStep.SCALE.MONTH: if(this.current.getMonth() < this.step) this.current.setMonth(0); break;
  4839. case TimeStep.SCALE.YEAR: break; // nothing to do for year
  4840. default: break;
  4841. }
  4842. }
  4843. // safety mechanism: if current time is still unchanged, move to the end
  4844. if (this.current.valueOf() == prev) {
  4845. this.current = new Date(this._end.valueOf());
  4846. }
  4847. DateUtil.stepOverHiddenDates(this, prev);
  4848. };
  4849. /**
  4850. * Get the current datetime
  4851. * @return {Date} current The current date
  4852. */
  4853. TimeStep.prototype.getCurrent = function() {
  4854. return this.current;
  4855. };
  4856. /**
  4857. * Set a custom scale. Autoscaling will be disabled.
  4858. * For example setScale(SCALE.MINUTES, 5) will result
  4859. * in minor steps of 5 minutes, and major steps of an hour.
  4860. *
  4861. * @param {TimeStep.SCALE} newScale
  4862. * A scale. Choose from SCALE.MILLISECOND,
  4863. * SCALE.SECOND, SCALE.MINUTE, SCALE.HOUR,
  4864. * SCALE.WEEKDAY, SCALE.DAY, SCALE.MONTH,
  4865. * SCALE.YEAR.
  4866. * @param {Number} newStep A step size, by default 1. Choose for
  4867. * example 1, 2, 5, or 10.
  4868. */
  4869. TimeStep.prototype.setScale = function(newScale, newStep) {
  4870. this.scale = newScale;
  4871. if (newStep > 0) {
  4872. this.step = newStep;
  4873. }
  4874. this.autoScale = false;
  4875. };
  4876. /**
  4877. * Enable or disable autoscaling
  4878. * @param {boolean} enable If true, autoascaling is set true
  4879. */
  4880. TimeStep.prototype.setAutoScale = function (enable) {
  4881. this.autoScale = enable;
  4882. };
  4883. /**
  4884. * Automatically determine the scale that bests fits the provided minimum step
  4885. * @param {Number} [minimumStep] The minimum step size in milliseconds
  4886. */
  4887. TimeStep.prototype.setMinimumStep = function(minimumStep) {
  4888. if (minimumStep == undefined) {
  4889. return;
  4890. }
  4891. //var b = asc + ds;
  4892. var stepYear = (1000 * 60 * 60 * 24 * 30 * 12);
  4893. var stepMonth = (1000 * 60 * 60 * 24 * 30);
  4894. var stepDay = (1000 * 60 * 60 * 24);
  4895. var stepHour = (1000 * 60 * 60);
  4896. var stepMinute = (1000 * 60);
  4897. var stepSecond = (1000);
  4898. var stepMillisecond= (1);
  4899. // find the smallest step that is larger than the provided minimumStep
  4900. if (stepYear*1000 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1000;}
  4901. if (stepYear*500 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 500;}
  4902. if (stepYear*100 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 100;}
  4903. if (stepYear*50 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 50;}
  4904. if (stepYear*10 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 10;}
  4905. if (stepYear*5 > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 5;}
  4906. if (stepYear > minimumStep) {this.scale = TimeStep.SCALE.YEAR; this.step = 1;}
  4907. if (stepMonth*3 > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 3;}
  4908. if (stepMonth > minimumStep) {this.scale = TimeStep.SCALE.MONTH; this.step = 1;}
  4909. if (stepDay*5 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 5;}
  4910. if (stepDay*2 > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 2;}
  4911. if (stepDay > minimumStep) {this.scale = TimeStep.SCALE.DAY; this.step = 1;}
  4912. if (stepDay/2 > minimumStep) {this.scale = TimeStep.SCALE.WEEKDAY; this.step = 1;}
  4913. if (stepHour*4 > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 4;}
  4914. if (stepHour > minimumStep) {this.scale = TimeStep.SCALE.HOUR; this.step = 1;}
  4915. if (stepMinute*15 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 15;}
  4916. if (stepMinute*10 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 10;}
  4917. if (stepMinute*5 > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 5;}
  4918. if (stepMinute > minimumStep) {this.scale = TimeStep.SCALE.MINUTE; this.step = 1;}
  4919. if (stepSecond*15 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 15;}
  4920. if (stepSecond*10 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 10;}
  4921. if (stepSecond*5 > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 5;}
  4922. if (stepSecond > minimumStep) {this.scale = TimeStep.SCALE.SECOND; this.step = 1;}
  4923. if (stepMillisecond*200 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 200;}
  4924. if (stepMillisecond*100 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 100;}
  4925. if (stepMillisecond*50 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 50;}
  4926. if (stepMillisecond*10 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 10;}
  4927. if (stepMillisecond*5 > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 5;}
  4928. if (stepMillisecond > minimumStep) {this.scale = TimeStep.SCALE.MILLISECOND; this.step = 1;}
  4929. };
  4930. /**
  4931. * Snap a date to a rounded value.
  4932. * The snap intervals are dependent on the current scale and step.
  4933. * @param {Date} date the date to be snapped.
  4934. * @return {Date} snappedDate
  4935. */
  4936. TimeStep.prototype.snap = function(date) {
  4937. var clone = new Date(date.valueOf());
  4938. if (this.scale == TimeStep.SCALE.YEAR) {
  4939. var year = clone.getFullYear() + Math.round(clone.getMonth() / 12);
  4940. clone.setFullYear(Math.round(year / this.step) * this.step);
  4941. clone.setMonth(0);
  4942. clone.setDate(0);
  4943. clone.setHours(0);
  4944. clone.setMinutes(0);
  4945. clone.setSeconds(0);
  4946. clone.setMilliseconds(0);
  4947. }
  4948. else if (this.scale == TimeStep.SCALE.MONTH) {
  4949. if (clone.getDate() > 15) {
  4950. clone.setDate(1);
  4951. clone.setMonth(clone.getMonth() + 1);
  4952. // important: first set Date to 1, after that change the month.
  4953. }
  4954. else {
  4955. clone.setDate(1);
  4956. }
  4957. clone.setHours(0);
  4958. clone.setMinutes(0);
  4959. clone.setSeconds(0);
  4960. clone.setMilliseconds(0);
  4961. }
  4962. else if (this.scale == TimeStep.SCALE.DAY) {
  4963. //noinspection FallthroughInSwitchStatementJS
  4964. switch (this.step) {
  4965. case 5:
  4966. case 2:
  4967. clone.setHours(Math.round(clone.getHours() / 24) * 24); break;
  4968. default:
  4969. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  4970. }
  4971. clone.setMinutes(0);
  4972. clone.setSeconds(0);
  4973. clone.setMilliseconds(0);
  4974. }
  4975. else if (this.scale == TimeStep.SCALE.WEEKDAY) {
  4976. //noinspection FallthroughInSwitchStatementJS
  4977. switch (this.step) {
  4978. case 5:
  4979. case 2:
  4980. clone.setHours(Math.round(clone.getHours() / 12) * 12); break;
  4981. default:
  4982. clone.setHours(Math.round(clone.getHours() / 6) * 6); break;
  4983. }
  4984. clone.setMinutes(0);
  4985. clone.setSeconds(0);
  4986. clone.setMilliseconds(0);
  4987. }
  4988. else if (this.scale == TimeStep.SCALE.HOUR) {
  4989. switch (this.step) {
  4990. case 4:
  4991. clone.setMinutes(Math.round(clone.getMinutes() / 60) * 60); break;
  4992. default:
  4993. clone.setMinutes(Math.round(clone.getMinutes() / 30) * 30); break;
  4994. }
  4995. clone.setSeconds(0);
  4996. clone.setMilliseconds(0);
  4997. } else if (this.scale == TimeStep.SCALE.MINUTE) {
  4998. //noinspection FallthroughInSwitchStatementJS
  4999. switch (this.step) {
  5000. case 15:
  5001. case 10:
  5002. clone.setMinutes(Math.round(clone.getMinutes() / 5) * 5);
  5003. clone.setSeconds(0);
  5004. break;
  5005. case 5:
  5006. clone.setSeconds(Math.round(clone.getSeconds() / 60) * 60); break;
  5007. default:
  5008. clone.setSeconds(Math.round(clone.getSeconds() / 30) * 30); break;
  5009. }
  5010. clone.setMilliseconds(0);
  5011. }
  5012. else if (this.scale == TimeStep.SCALE.SECOND) {
  5013. //noinspection FallthroughInSwitchStatementJS
  5014. switch (this.step) {
  5015. case 15:
  5016. case 10:
  5017. clone.setSeconds(Math.round(clone.getSeconds() / 5) * 5);
  5018. clone.setMilliseconds(0);
  5019. break;
  5020. case 5:
  5021. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 1000) * 1000); break;
  5022. default:
  5023. clone.setMilliseconds(Math.round(clone.getMilliseconds() / 500) * 500); break;
  5024. }
  5025. }
  5026. else if (this.scale == TimeStep.SCALE.MILLISECOND) {
  5027. var step = this.step > 5 ? this.step / 2 : 1;
  5028. clone.setMilliseconds(Math.round(clone.getMilliseconds() / step) * step);
  5029. }
  5030. return clone;
  5031. };
  5032. /**
  5033. * Check if the current value is a major value (for example when the step
  5034. * is DAY, a major value is each first day of the MONTH)
  5035. * @return {boolean} true if current date is major, else false.
  5036. */
  5037. TimeStep.prototype.isMajor = function() {
  5038. if (this.switchedYear == true) {
  5039. this.switchedYear = false;
  5040. switch (this.scale) {
  5041. case TimeStep.SCALE.YEAR:
  5042. case TimeStep.SCALE.MONTH:
  5043. case TimeStep.SCALE.WEEKDAY:
  5044. case TimeStep.SCALE.DAY:
  5045. case TimeStep.SCALE.HOUR:
  5046. case TimeStep.SCALE.MINUTE:
  5047. case TimeStep.SCALE.SECOND:
  5048. case TimeStep.SCALE.MILLISECOND:
  5049. return true;
  5050. default:
  5051. return false;
  5052. }
  5053. }
  5054. else if (this.switchedMonth == true) {
  5055. this.switchedMonth = false;
  5056. switch (this.scale) {
  5057. case TimeStep.SCALE.WEEKDAY:
  5058. case TimeStep.SCALE.DAY:
  5059. case TimeStep.SCALE.HOUR:
  5060. case TimeStep.SCALE.MINUTE:
  5061. case TimeStep.SCALE.SECOND:
  5062. case TimeStep.SCALE.MILLISECOND:
  5063. return true;
  5064. default:
  5065. return false;
  5066. }
  5067. }
  5068. else if (this.switchedDay == true) {
  5069. this.switchedDay = false;
  5070. switch (this.scale) {
  5071. case TimeStep.SCALE.MILLISECOND:
  5072. case TimeStep.SCALE.SECOND:
  5073. case TimeStep.SCALE.MINUTE:
  5074. case TimeStep.SCALE.HOUR:
  5075. return true;
  5076. default:
  5077. return false;
  5078. }
  5079. }
  5080. switch (this.scale) {
  5081. case TimeStep.SCALE.MILLISECOND:
  5082. return (this.current.getMilliseconds() == 0);
  5083. case TimeStep.SCALE.SECOND:
  5084. return (this.current.getSeconds() == 0);
  5085. case TimeStep.SCALE.MINUTE:
  5086. return (this.current.getHours() == 0) && (this.current.getMinutes() == 0);
  5087. case TimeStep.SCALE.HOUR:
  5088. return (this.current.getHours() == 0);
  5089. case TimeStep.SCALE.WEEKDAY: // intentional fall through
  5090. case TimeStep.SCALE.DAY:
  5091. return (this.current.getDate() == 1);
  5092. case TimeStep.SCALE.MONTH:
  5093. return (this.current.getMonth() == 0);
  5094. case TimeStep.SCALE.YEAR:
  5095. return false;
  5096. default:
  5097. return false;
  5098. }
  5099. };
  5100. /**
  5101. * Returns formatted text for the minor axislabel, depending on the current
  5102. * date and the scale. For example when scale is MINUTE, the current time is
  5103. * formatted as "hh:mm".
  5104. * @param {Date} [date] custom date. if not provided, current date is taken
  5105. */
  5106. TimeStep.prototype.getLabelMinor = function(date) {
  5107. if (date == undefined) {
  5108. date = this.current;
  5109. }
  5110. switch (this.scale) {
  5111. case TimeStep.SCALE.MILLISECOND: return moment(date).format('SSS');
  5112. case TimeStep.SCALE.SECOND: return moment(date).format('s');
  5113. case TimeStep.SCALE.MINUTE: return moment(date).format('HH:mm');
  5114. case TimeStep.SCALE.HOUR: return moment(date).format('HH:mm');
  5115. case TimeStep.SCALE.WEEKDAY: return moment(date).format('ddd D');
  5116. case TimeStep.SCALE.DAY: return moment(date).format('D');
  5117. case TimeStep.SCALE.MONTH: return moment(date).format('MMM');
  5118. case TimeStep.SCALE.YEAR: return moment(date).format('YYYY');
  5119. default: return '';
  5120. }
  5121. };
  5122. /**
  5123. * Returns formatted text for the major axis label, depending on the current
  5124. * date and the scale. For example when scale is MINUTE, the major scale is
  5125. * hours, and the hour will be formatted as "hh".
  5126. * @param {Date} [date] custom date. if not provided, current date is taken
  5127. */
  5128. TimeStep.prototype.getLabelMajor = function(date) {
  5129. if (date == undefined) {
  5130. date = this.current;
  5131. }
  5132. //noinspection FallthroughInSwitchStatementJS
  5133. switch (this.scale) {
  5134. case TimeStep.SCALE.MILLISECOND:return moment(date).format('HH:mm:ss');
  5135. case TimeStep.SCALE.SECOND: return moment(date).format('D MMMM HH:mm');
  5136. case TimeStep.SCALE.MINUTE:
  5137. case TimeStep.SCALE.HOUR: return moment(date).format('ddd D MMMM');
  5138. case TimeStep.SCALE.WEEKDAY:
  5139. case TimeStep.SCALE.DAY: return moment(date).format('MMMM YYYY');
  5140. case TimeStep.SCALE.MONTH: return moment(date).format('YYYY');
  5141. case TimeStep.SCALE.YEAR: return '';
  5142. default: return '';
  5143. }
  5144. };
  5145. module.exports = TimeStep;
  5146. /***/ },
  5147. /* 13 */
  5148. /***/ function(module, exports, __webpack_require__) {
  5149. var Emitter = __webpack_require__(53);
  5150. var DataSet = __webpack_require__(3);
  5151. var DataView = __webpack_require__(4);
  5152. var util = __webpack_require__(1);
  5153. var Point3d = __webpack_require__(18);
  5154. var Point2d = __webpack_require__(16);
  5155. var Camera = __webpack_require__(14);
  5156. var Filter = __webpack_require__(15);
  5157. var Slider = __webpack_require__(17);
  5158. var StepNumber = __webpack_require__(19);
  5159. /**
  5160. * @constructor Graph3d
  5161. * Graph3d displays data in 3d.
  5162. *
  5163. * Graph3d is developed in javascript as a Google Visualization Chart.
  5164. *
  5165. * @param {Element} container The DOM element in which the Graph3d will
  5166. * be created. Normally a div element.
  5167. * @param {DataSet | DataView | Array} [data]
  5168. * @param {Object} [options]
  5169. */
  5170. function Graph3d(container, data, options) {
  5171. if (!(this instanceof Graph3d)) {
  5172. throw new SyntaxError('Constructor must be called with the new operator');
  5173. }
  5174. // create variables and set default values
  5175. this.containerElement = container;
  5176. this.width = '400px';
  5177. this.height = '400px';
  5178. this.margin = 10; // px
  5179. this.defaultXCenter = '55%';
  5180. this.defaultYCenter = '50%';
  5181. this.xLabel = 'x';
  5182. this.yLabel = 'y';
  5183. this.zLabel = 'z';
  5184. var passValueFn = function(v) { return v; };
  5185. this.xValueLabel = passValueFn;
  5186. this.yValueLabel = passValueFn;
  5187. this.zValueLabel = passValueFn;
  5188. this.filterLabel = 'time';
  5189. this.legendLabel = 'value';
  5190. this.style = Graph3d.STYLE.DOT;
  5191. this.showPerspective = true;
  5192. this.showGrid = true;
  5193. this.keepAspectRatio = true;
  5194. this.showShadow = false;
  5195. this.showGrayBottom = false; // TODO: this does not work correctly
  5196. this.showTooltip = false;
  5197. this.verticalRatio = 0.5; // 0.1 to 1.0, where 1.0 results in a 'cube'
  5198. this.animationInterval = 1000; // milliseconds
  5199. this.animationPreload = false;
  5200. this.camera = new Camera();
  5201. this.eye = new Point3d(0, 0, -1); // TODO: set eye.z about 3/4 of the width of the window?
  5202. this.dataTable = null; // The original data table
  5203. this.dataPoints = null; // The table with point objects
  5204. // the column indexes
  5205. this.colX = undefined;
  5206. this.colY = undefined;
  5207. this.colZ = undefined;
  5208. this.colValue = undefined;
  5209. this.colFilter = undefined;
  5210. this.xMin = 0;
  5211. this.xStep = undefined; // auto by default
  5212. this.xMax = 1;
  5213. this.yMin = 0;
  5214. this.yStep = undefined; // auto by default
  5215. this.yMax = 1;
  5216. this.zMin = 0;
  5217. this.zStep = undefined; // auto by default
  5218. this.zMax = 1;
  5219. this.valueMin = 0;
  5220. this.valueMax = 1;
  5221. this.xBarWidth = 1;
  5222. this.yBarWidth = 1;
  5223. // TODO: customize axis range
  5224. // constants
  5225. this.colorAxis = '#4D4D4D';
  5226. this.colorGrid = '#D3D3D3';
  5227. this.colorDot = '#7DC1FF';
  5228. this.colorDotBorder = '#3267D2';
  5229. // create a frame and canvas
  5230. this.create();
  5231. // apply options (also when undefined)
  5232. this.setOptions(options);
  5233. // apply data
  5234. if (data) {
  5235. this.setData(data);
  5236. }
  5237. }
  5238. // Extend Graph3d with an Emitter mixin
  5239. Emitter(Graph3d.prototype);
  5240. /**
  5241. * Calculate the scaling values, dependent on the range in x, y, and z direction
  5242. */
  5243. Graph3d.prototype._setScale = function() {
  5244. this.scale = new Point3d(1 / (this.xMax - this.xMin),
  5245. 1 / (this.yMax - this.yMin),
  5246. 1 / (this.zMax - this.zMin));
  5247. // keep aspect ration between x and y scale if desired
  5248. if (this.keepAspectRatio) {
  5249. if (this.scale.x < this.scale.y) {
  5250. //noinspection JSSuspiciousNameCombination
  5251. this.scale.y = this.scale.x;
  5252. }
  5253. else {
  5254. //noinspection JSSuspiciousNameCombination
  5255. this.scale.x = this.scale.y;
  5256. }
  5257. }
  5258. // scale the vertical axis
  5259. this.scale.z *= this.verticalRatio;
  5260. // TODO: can this be automated? verticalRatio?
  5261. // determine scale for (optional) value
  5262. this.scale.value = 1 / (this.valueMax - this.valueMin);
  5263. // position the camera arm
  5264. var xCenter = (this.xMax + this.xMin) / 2 * this.scale.x;
  5265. var yCenter = (this.yMax + this.yMin) / 2 * this.scale.y;
  5266. var zCenter = (this.zMax + this.zMin) / 2 * this.scale.z;
  5267. this.camera.setArmLocation(xCenter, yCenter, zCenter);
  5268. };
  5269. /**
  5270. * Convert a 3D location to a 2D location on screen
  5271. * http://en.wikipedia.org/wiki/3D_projection
  5272. * @param {Point3d} point3d A 3D point with parameters x, y, z
  5273. * @return {Point2d} point2d A 2D point with parameters x, y
  5274. */
  5275. Graph3d.prototype._convert3Dto2D = function(point3d) {
  5276. var translation = this._convertPointToTranslation(point3d);
  5277. return this._convertTranslationToScreen(translation);
  5278. };
  5279. /**
  5280. * Convert a 3D location its translation seen from the camera
  5281. * http://en.wikipedia.org/wiki/3D_projection
  5282. * @param {Point3d} point3d A 3D point with parameters x, y, z
  5283. * @return {Point3d} translation A 3D point with parameters x, y, z This is
  5284. * the translation of the point, seen from the
  5285. * camera
  5286. */
  5287. Graph3d.prototype._convertPointToTranslation = function(point3d) {
  5288. var ax = point3d.x * this.scale.x,
  5289. ay = point3d.y * this.scale.y,
  5290. az = point3d.z * this.scale.z,
  5291. cx = this.camera.getCameraLocation().x,
  5292. cy = this.camera.getCameraLocation().y,
  5293. cz = this.camera.getCameraLocation().z,
  5294. // calculate angles
  5295. sinTx = Math.sin(this.camera.getCameraRotation().x),
  5296. cosTx = Math.cos(this.camera.getCameraRotation().x),
  5297. sinTy = Math.sin(this.camera.getCameraRotation().y),
  5298. cosTy = Math.cos(this.camera.getCameraRotation().y),
  5299. sinTz = Math.sin(this.camera.getCameraRotation().z),
  5300. cosTz = Math.cos(this.camera.getCameraRotation().z),
  5301. // calculate translation
  5302. dx = cosTy * (sinTz * (ay - cy) + cosTz * (ax - cx)) - sinTy * (az - cz),
  5303. dy = sinTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) + cosTx * (cosTz * (ay - cy) - sinTz * (ax-cx)),
  5304. dz = cosTx * (cosTy * (az - cz) + sinTy * (sinTz * (ay - cy) + cosTz * (ax - cx))) - sinTx * (cosTz * (ay - cy) - sinTz * (ax-cx));
  5305. return new Point3d(dx, dy, dz);
  5306. };
  5307. /**
  5308. * Convert a translation point to a point on the screen
  5309. * @param {Point3d} translation A 3D point with parameters x, y, z This is
  5310. * the translation of the point, seen from the
  5311. * camera
  5312. * @return {Point2d} point2d A 2D point with parameters x, y
  5313. */
  5314. Graph3d.prototype._convertTranslationToScreen = function(translation) {
  5315. var ex = this.eye.x,
  5316. ey = this.eye.y,
  5317. ez = this.eye.z,
  5318. dx = translation.x,
  5319. dy = translation.y,
  5320. dz = translation.z;
  5321. // calculate position on screen from translation
  5322. var bx;
  5323. var by;
  5324. if (this.showPerspective) {
  5325. bx = (dx - ex) * (ez / dz);
  5326. by = (dy - ey) * (ez / dz);
  5327. }
  5328. else {
  5329. bx = dx * -(ez / this.camera.getArmLength());
  5330. by = dy * -(ez / this.camera.getArmLength());
  5331. }
  5332. // shift and scale the point to the center of the screen
  5333. // use the width of the graph to scale both horizontally and vertically.
  5334. return new Point2d(
  5335. this.xcenter + bx * this.frame.canvas.clientWidth,
  5336. this.ycenter - by * this.frame.canvas.clientWidth);
  5337. };
  5338. /**
  5339. * Set the background styling for the graph
  5340. * @param {string | {fill: string, stroke: string, strokeWidth: string}} backgroundColor
  5341. */
  5342. Graph3d.prototype._setBackgroundColor = function(backgroundColor) {
  5343. var fill = 'white';
  5344. var stroke = 'gray';
  5345. var strokeWidth = 1;
  5346. if (typeof(backgroundColor) === 'string') {
  5347. fill = backgroundColor;
  5348. stroke = 'none';
  5349. strokeWidth = 0;
  5350. }
  5351. else if (typeof(backgroundColor) === 'object') {
  5352. if (backgroundColor.fill !== undefined) fill = backgroundColor.fill;
  5353. if (backgroundColor.stroke !== undefined) stroke = backgroundColor.stroke;
  5354. if (backgroundColor.strokeWidth !== undefined) strokeWidth = backgroundColor.strokeWidth;
  5355. }
  5356. else if (backgroundColor === undefined) {
  5357. // use use defaults
  5358. }
  5359. else {
  5360. throw 'Unsupported type of backgroundColor';
  5361. }
  5362. this.frame.style.backgroundColor = fill;
  5363. this.frame.style.borderColor = stroke;
  5364. this.frame.style.borderWidth = strokeWidth + 'px';
  5365. this.frame.style.borderStyle = 'solid';
  5366. };
  5367. /// enumerate the available styles
  5368. Graph3d.STYLE = {
  5369. BAR: 0,
  5370. BARCOLOR: 1,
  5371. BARSIZE: 2,
  5372. DOT : 3,
  5373. DOTLINE : 4,
  5374. DOTCOLOR: 5,
  5375. DOTSIZE: 6,
  5376. GRID : 7,
  5377. LINE: 8,
  5378. SURFACE : 9
  5379. };
  5380. /**
  5381. * Retrieve the style index from given styleName
  5382. * @param {string} styleName Style name such as 'dot', 'grid', 'dot-line'
  5383. * @return {Number} styleNumber Enumeration value representing the style, or -1
  5384. * when not found
  5385. */
  5386. Graph3d.prototype._getStyleNumber = function(styleName) {
  5387. switch (styleName) {
  5388. case 'dot': return Graph3d.STYLE.DOT;
  5389. case 'dot-line': return Graph3d.STYLE.DOTLINE;
  5390. case 'dot-color': return Graph3d.STYLE.DOTCOLOR;
  5391. case 'dot-size': return Graph3d.STYLE.DOTSIZE;
  5392. case 'line': return Graph3d.STYLE.LINE;
  5393. case 'grid': return Graph3d.STYLE.GRID;
  5394. case 'surface': return Graph3d.STYLE.SURFACE;
  5395. case 'bar': return Graph3d.STYLE.BAR;
  5396. case 'bar-color': return Graph3d.STYLE.BARCOLOR;
  5397. case 'bar-size': return Graph3d.STYLE.BARSIZE;
  5398. }
  5399. return -1;
  5400. };
  5401. /**
  5402. * Determine the indexes of the data columns, based on the given style and data
  5403. * @param {DataSet} data
  5404. * @param {Number} style
  5405. */
  5406. Graph3d.prototype._determineColumnIndexes = function(data, style) {
  5407. if (this.style === Graph3d.STYLE.DOT ||
  5408. this.style === Graph3d.STYLE.DOTLINE ||
  5409. this.style === Graph3d.STYLE.LINE ||
  5410. this.style === Graph3d.STYLE.GRID ||
  5411. this.style === Graph3d.STYLE.SURFACE ||
  5412. this.style === Graph3d.STYLE.BAR) {
  5413. // 3 columns expected, and optionally a 4th with filter values
  5414. this.colX = 0;
  5415. this.colY = 1;
  5416. this.colZ = 2;
  5417. this.colValue = undefined;
  5418. if (data.getNumberOfColumns() > 3) {
  5419. this.colFilter = 3;
  5420. }
  5421. }
  5422. else if (this.style === Graph3d.STYLE.DOTCOLOR ||
  5423. this.style === Graph3d.STYLE.DOTSIZE ||
  5424. this.style === Graph3d.STYLE.BARCOLOR ||
  5425. this.style === Graph3d.STYLE.BARSIZE) {
  5426. // 4 columns expected, and optionally a 5th with filter values
  5427. this.colX = 0;
  5428. this.colY = 1;
  5429. this.colZ = 2;
  5430. this.colValue = 3;
  5431. if (data.getNumberOfColumns() > 4) {
  5432. this.colFilter = 4;
  5433. }
  5434. }
  5435. else {
  5436. throw 'Unknown style "' + this.style + '"';
  5437. }
  5438. };
  5439. Graph3d.prototype.getNumberOfRows = function(data) {
  5440. return data.length;
  5441. }
  5442. Graph3d.prototype.getNumberOfColumns = function(data) {
  5443. var counter = 0;
  5444. for (var column in data[0]) {
  5445. if (data[0].hasOwnProperty(column)) {
  5446. counter++;
  5447. }
  5448. }
  5449. return counter;
  5450. }
  5451. Graph3d.prototype.getDistinctValues = function(data, column) {
  5452. var distinctValues = [];
  5453. for (var i = 0; i < data.length; i++) {
  5454. if (distinctValues.indexOf(data[i][column]) == -1) {
  5455. distinctValues.push(data[i][column]);
  5456. }
  5457. }
  5458. return distinctValues;
  5459. }
  5460. Graph3d.prototype.getColumnRange = function(data,column) {
  5461. var minMax = {min:data[0][column],max:data[0][column]};
  5462. for (var i = 0; i < data.length; i++) {
  5463. if (minMax.min > data[i][column]) { minMax.min = data[i][column]; }
  5464. if (minMax.max < data[i][column]) { minMax.max = data[i][column]; }
  5465. }
  5466. return minMax;
  5467. };
  5468. /**
  5469. * Initialize the data from the data table. Calculate minimum and maximum values
  5470. * and column index values
  5471. * @param {Array | DataSet | DataView} rawData The data containing the items for the Graph.
  5472. * @param {Number} style Style Number
  5473. */
  5474. Graph3d.prototype._dataInitialize = function (rawData, style) {
  5475. var me = this;
  5476. // unsubscribe from the dataTable
  5477. if (this.dataSet) {
  5478. this.dataSet.off('*', this._onChange);
  5479. }
  5480. if (rawData === undefined)
  5481. return;
  5482. if (Array.isArray(rawData)) {
  5483. rawData = new DataSet(rawData);
  5484. }
  5485. var data;
  5486. if (rawData instanceof DataSet || rawData instanceof DataView) {
  5487. data = rawData.get();
  5488. }
  5489. else {
  5490. throw new Error('Array, DataSet, or DataView expected');
  5491. }
  5492. if (data.length == 0)
  5493. return;
  5494. this.dataSet = rawData;
  5495. this.dataTable = data;
  5496. // subscribe to changes in the dataset
  5497. this._onChange = function () {
  5498. me.setData(me.dataSet);
  5499. };
  5500. this.dataSet.on('*', this._onChange);
  5501. // _determineColumnIndexes
  5502. // getNumberOfRows (points)
  5503. // getNumberOfColumns (x,y,z,v,t,t1,t2...)
  5504. // getDistinctValues (unique values?)
  5505. // getColumnRange
  5506. // determine the location of x,y,z,value,filter columns
  5507. this.colX = 'x';
  5508. this.colY = 'y';
  5509. this.colZ = 'z';
  5510. this.colValue = 'style';
  5511. this.colFilter = 'filter';
  5512. // check if a filter column is provided
  5513. if (data[0].hasOwnProperty('filter')) {
  5514. if (this.dataFilter === undefined) {
  5515. this.dataFilter = new Filter(rawData, this.colFilter, this);
  5516. this.dataFilter.setOnLoadCallback(function() {me.redraw();});
  5517. }
  5518. }
  5519. var withBars = this.style == Graph3d.STYLE.BAR ||
  5520. this.style == Graph3d.STYLE.BARCOLOR ||
  5521. this.style == Graph3d.STYLE.BARSIZE;
  5522. // determine barWidth from data
  5523. if (withBars) {
  5524. if (this.defaultXBarWidth !== undefined) {
  5525. this.xBarWidth = this.defaultXBarWidth;
  5526. }
  5527. else {
  5528. var dataX = this.getDistinctValues(data,this.colX);
  5529. this.xBarWidth = (dataX[1] - dataX[0]) || 1;
  5530. }
  5531. if (this.defaultYBarWidth !== undefined) {
  5532. this.yBarWidth = this.defaultYBarWidth;
  5533. }
  5534. else {
  5535. var dataY = this.getDistinctValues(data,this.colY);
  5536. this.yBarWidth = (dataY[1] - dataY[0]) || 1;
  5537. }
  5538. }
  5539. // calculate minimums and maximums
  5540. var xRange = this.getColumnRange(data,this.colX);
  5541. if (withBars) {
  5542. xRange.min -= this.xBarWidth / 2;
  5543. xRange.max += this.xBarWidth / 2;
  5544. }
  5545. this.xMin = (this.defaultXMin !== undefined) ? this.defaultXMin : xRange.min;
  5546. this.xMax = (this.defaultXMax !== undefined) ? this.defaultXMax : xRange.max;
  5547. if (this.xMax <= this.xMin) this.xMax = this.xMin + 1;
  5548. this.xStep = (this.defaultXStep !== undefined) ? this.defaultXStep : (this.xMax-this.xMin)/5;
  5549. var yRange = this.getColumnRange(data,this.colY);
  5550. if (withBars) {
  5551. yRange.min -= this.yBarWidth / 2;
  5552. yRange.max += this.yBarWidth / 2;
  5553. }
  5554. this.yMin = (this.defaultYMin !== undefined) ? this.defaultYMin : yRange.min;
  5555. this.yMax = (this.defaultYMax !== undefined) ? this.defaultYMax : yRange.max;
  5556. if (this.yMax <= this.yMin) this.yMax = this.yMin + 1;
  5557. this.yStep = (this.defaultYStep !== undefined) ? this.defaultYStep : (this.yMax-this.yMin)/5;
  5558. var zRange = this.getColumnRange(data,this.colZ);
  5559. this.zMin = (this.defaultZMin !== undefined) ? this.defaultZMin : zRange.min;
  5560. this.zMax = (this.defaultZMax !== undefined) ? this.defaultZMax : zRange.max;
  5561. if (this.zMax <= this.zMin) this.zMax = this.zMin + 1;
  5562. this.zStep = (this.defaultZStep !== undefined) ? this.defaultZStep : (this.zMax-this.zMin)/5;
  5563. if (this.colValue !== undefined) {
  5564. var valueRange = this.getColumnRange(data,this.colValue);
  5565. this.valueMin = (this.defaultValueMin !== undefined) ? this.defaultValueMin : valueRange.min;
  5566. this.valueMax = (this.defaultValueMax !== undefined) ? this.defaultValueMax : valueRange.max;
  5567. if (this.valueMax <= this.valueMin) this.valueMax = this.valueMin + 1;
  5568. }
  5569. // set the scale dependent on the ranges.
  5570. this._setScale();
  5571. };
  5572. /**
  5573. * Filter the data based on the current filter
  5574. * @param {Array} data
  5575. * @return {Array} dataPoints Array with point objects which can be drawn on screen
  5576. */
  5577. Graph3d.prototype._getDataPoints = function (data) {
  5578. // TODO: store the created matrix dataPoints in the filters instead of reloading each time
  5579. var x, y, i, z, obj, point;
  5580. var dataPoints = [];
  5581. if (this.style === Graph3d.STYLE.GRID ||
  5582. this.style === Graph3d.STYLE.SURFACE) {
  5583. // copy all values from the google data table to a matrix
  5584. // the provided values are supposed to form a grid of (x,y) positions
  5585. // create two lists with all present x and y values
  5586. var dataX = [];
  5587. var dataY = [];
  5588. for (i = 0; i < this.getNumberOfRows(data); i++) {
  5589. x = data[i][this.colX] || 0;
  5590. y = data[i][this.colY] || 0;
  5591. if (dataX.indexOf(x) === -1) {
  5592. dataX.push(x);
  5593. }
  5594. if (dataY.indexOf(y) === -1) {
  5595. dataY.push(y);
  5596. }
  5597. }
  5598. function sortNumber(a, b) {
  5599. return a - b;
  5600. }
  5601. dataX.sort(sortNumber);
  5602. dataY.sort(sortNumber);
  5603. // create a grid, a 2d matrix, with all values.
  5604. var dataMatrix = []; // temporary data matrix
  5605. for (i = 0; i < data.length; i++) {
  5606. x = data[i][this.colX] || 0;
  5607. y = data[i][this.colY] || 0;
  5608. z = data[i][this.colZ] || 0;
  5609. var xIndex = dataX.indexOf(x); // TODO: implement Array().indexOf() for Internet Explorer
  5610. var yIndex = dataY.indexOf(y);
  5611. if (dataMatrix[xIndex] === undefined) {
  5612. dataMatrix[xIndex] = [];
  5613. }
  5614. var point3d = new Point3d();
  5615. point3d.x = x;
  5616. point3d.y = y;
  5617. point3d.z = z;
  5618. obj = {};
  5619. obj.point = point3d;
  5620. obj.trans = undefined;
  5621. obj.screen = undefined;
  5622. obj.bottom = new Point3d(x, y, this.zMin);
  5623. dataMatrix[xIndex][yIndex] = obj;
  5624. dataPoints.push(obj);
  5625. }
  5626. // fill in the pointers to the neighbors.
  5627. for (x = 0; x < dataMatrix.length; x++) {
  5628. for (y = 0; y < dataMatrix[x].length; y++) {
  5629. if (dataMatrix[x][y]) {
  5630. dataMatrix[x][y].pointRight = (x < dataMatrix.length-1) ? dataMatrix[x+1][y] : undefined;
  5631. dataMatrix[x][y].pointTop = (y < dataMatrix[x].length-1) ? dataMatrix[x][y+1] : undefined;
  5632. dataMatrix[x][y].pointCross =
  5633. (x < dataMatrix.length-1 && y < dataMatrix[x].length-1) ?
  5634. dataMatrix[x+1][y+1] :
  5635. undefined;
  5636. }
  5637. }
  5638. }
  5639. }
  5640. else { // 'dot', 'dot-line', etc.
  5641. // copy all values from the google data table to a list with Point3d objects
  5642. for (i = 0; i < data.length; i++) {
  5643. point = new Point3d();
  5644. point.x = data[i][this.colX] || 0;
  5645. point.y = data[i][this.colY] || 0;
  5646. point.z = data[i][this.colZ] || 0;
  5647. if (this.colValue !== undefined) {
  5648. point.value = data[i][this.colValue] || 0;
  5649. }
  5650. obj = {};
  5651. obj.point = point;
  5652. obj.bottom = new Point3d(point.x, point.y, this.zMin);
  5653. obj.trans = undefined;
  5654. obj.screen = undefined;
  5655. dataPoints.push(obj);
  5656. }
  5657. }
  5658. return dataPoints;
  5659. };
  5660. /**
  5661. * Create the main frame for the Graph3d.
  5662. * This function is executed once when a Graph3d object is created. The frame
  5663. * contains a canvas, and this canvas contains all objects like the axis and
  5664. * nodes.
  5665. */
  5666. Graph3d.prototype.create = function () {
  5667. // remove all elements from the container element.
  5668. while (this.containerElement.hasChildNodes()) {
  5669. this.containerElement.removeChild(this.containerElement.firstChild);
  5670. }
  5671. this.frame = document.createElement('div');
  5672. this.frame.style.position = 'relative';
  5673. this.frame.style.overflow = 'hidden';
  5674. // create the graph canvas (HTML canvas element)
  5675. this.frame.canvas = document.createElement( 'canvas' );
  5676. this.frame.canvas.style.position = 'relative';
  5677. this.frame.appendChild(this.frame.canvas);
  5678. //if (!this.frame.canvas.getContext) {
  5679. {
  5680. var noCanvas = document.createElement( 'DIV' );
  5681. noCanvas.style.color = 'red';
  5682. noCanvas.style.fontWeight = 'bold' ;
  5683. noCanvas.style.padding = '10px';
  5684. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  5685. this.frame.canvas.appendChild(noCanvas);
  5686. }
  5687. this.frame.filter = document.createElement( 'div' );
  5688. this.frame.filter.style.position = 'absolute';
  5689. this.frame.filter.style.bottom = '0px';
  5690. this.frame.filter.style.left = '0px';
  5691. this.frame.filter.style.width = '100%';
  5692. this.frame.appendChild(this.frame.filter);
  5693. // add event listeners to handle moving and zooming the contents
  5694. var me = this;
  5695. var onmousedown = function (event) {me._onMouseDown(event);};
  5696. var ontouchstart = function (event) {me._onTouchStart(event);};
  5697. var onmousewheel = function (event) {me._onWheel(event);};
  5698. var ontooltip = function (event) {me._onTooltip(event);};
  5699. // TODO: these events are never cleaned up... can give a 'memory leakage'
  5700. util.addEventListener(this.frame.canvas, 'keydown', onkeydown);
  5701. util.addEventListener(this.frame.canvas, 'mousedown', onmousedown);
  5702. util.addEventListener(this.frame.canvas, 'touchstart', ontouchstart);
  5703. util.addEventListener(this.frame.canvas, 'mousewheel', onmousewheel);
  5704. util.addEventListener(this.frame.canvas, 'mousemove', ontooltip);
  5705. // add the new graph to the container element
  5706. this.containerElement.appendChild(this.frame);
  5707. };
  5708. /**
  5709. * Set a new size for the graph
  5710. * @param {string} width Width in pixels or percentage (for example '800px'
  5711. * or '50%')
  5712. * @param {string} height Height in pixels or percentage (for example '400px'
  5713. * or '30%')
  5714. */
  5715. Graph3d.prototype.setSize = function(width, height) {
  5716. this.frame.style.width = width;
  5717. this.frame.style.height = height;
  5718. this._resizeCanvas();
  5719. };
  5720. /**
  5721. * Resize the canvas to the current size of the frame
  5722. */
  5723. Graph3d.prototype._resizeCanvas = function() {
  5724. this.frame.canvas.style.width = '100%';
  5725. this.frame.canvas.style.height = '100%';
  5726. this.frame.canvas.width = this.frame.canvas.clientWidth;
  5727. this.frame.canvas.height = this.frame.canvas.clientHeight;
  5728. // adjust with for margin
  5729. this.frame.filter.style.width = (this.frame.canvas.clientWidth - 2 * 10) + 'px';
  5730. };
  5731. /**
  5732. * Start animation
  5733. */
  5734. Graph3d.prototype.animationStart = function() {
  5735. if (!this.frame.filter || !this.frame.filter.slider)
  5736. throw 'No animation available';
  5737. this.frame.filter.slider.play();
  5738. };
  5739. /**
  5740. * Stop animation
  5741. */
  5742. Graph3d.prototype.animationStop = function() {
  5743. if (!this.frame.filter || !this.frame.filter.slider) return;
  5744. this.frame.filter.slider.stop();
  5745. };
  5746. /**
  5747. * Resize the center position based on the current values in this.defaultXCenter
  5748. * and this.defaultYCenter (which are strings with a percentage or a value
  5749. * in pixels). The center positions are the variables this.xCenter
  5750. * and this.yCenter
  5751. */
  5752. Graph3d.prototype._resizeCenter = function() {
  5753. // calculate the horizontal center position
  5754. if (this.defaultXCenter.charAt(this.defaultXCenter.length-1) === '%') {
  5755. this.xcenter =
  5756. parseFloat(this.defaultXCenter) / 100 *
  5757. this.frame.canvas.clientWidth;
  5758. }
  5759. else {
  5760. this.xcenter = parseFloat(this.defaultXCenter); // supposed to be in px
  5761. }
  5762. // calculate the vertical center position
  5763. if (this.defaultYCenter.charAt(this.defaultYCenter.length-1) === '%') {
  5764. this.ycenter =
  5765. parseFloat(this.defaultYCenter) / 100 *
  5766. (this.frame.canvas.clientHeight - this.frame.filter.clientHeight);
  5767. }
  5768. else {
  5769. this.ycenter = parseFloat(this.defaultYCenter); // supposed to be in px
  5770. }
  5771. };
  5772. /**
  5773. * Set the rotation and distance of the camera
  5774. * @param {Object} pos An object with the camera position. The object
  5775. * contains three parameters:
  5776. * - horizontal {Number}
  5777. * The horizontal rotation, between 0 and 2*PI.
  5778. * Optional, can be left undefined.
  5779. * - vertical {Number}
  5780. * The vertical rotation, between 0 and 0.5*PI
  5781. * if vertical=0.5*PI, the graph is shown from the
  5782. * top. Optional, can be left undefined.
  5783. * - distance {Number}
  5784. * The (normalized) distance of the camera to the
  5785. * center of the graph, a value between 0.71 and 5.0.
  5786. * Optional, can be left undefined.
  5787. */
  5788. Graph3d.prototype.setCameraPosition = function(pos) {
  5789. if (pos === undefined) {
  5790. return;
  5791. }
  5792. if (pos.horizontal !== undefined && pos.vertical !== undefined) {
  5793. this.camera.setArmRotation(pos.horizontal, pos.vertical);
  5794. }
  5795. if (pos.distance !== undefined) {
  5796. this.camera.setArmLength(pos.distance);
  5797. }
  5798. this.redraw();
  5799. };
  5800. /**
  5801. * Retrieve the current camera rotation
  5802. * @return {object} An object with parameters horizontal, vertical, and
  5803. * distance
  5804. */
  5805. Graph3d.prototype.getCameraPosition = function() {
  5806. var pos = this.camera.getArmRotation();
  5807. pos.distance = this.camera.getArmLength();
  5808. return pos;
  5809. };
  5810. /**
  5811. * Load data into the 3D Graph
  5812. */
  5813. Graph3d.prototype._readData = function(data) {
  5814. // read the data
  5815. this._dataInitialize(data, this.style);
  5816. if (this.dataFilter) {
  5817. // apply filtering
  5818. this.dataPoints = this.dataFilter._getDataPoints();
  5819. }
  5820. else {
  5821. // no filtering. load all data
  5822. this.dataPoints = this._getDataPoints(this.dataTable);
  5823. }
  5824. // draw the filter
  5825. this._redrawFilter();
  5826. };
  5827. /**
  5828. * Replace the dataset of the Graph3d
  5829. * @param {Array | DataSet | DataView} data
  5830. */
  5831. Graph3d.prototype.setData = function (data) {
  5832. this._readData(data);
  5833. this.redraw();
  5834. // start animation when option is true
  5835. if (this.animationAutoStart && this.dataFilter) {
  5836. this.animationStart();
  5837. }
  5838. };
  5839. /**
  5840. * Update the options. Options will be merged with current options
  5841. * @param {Object} options
  5842. */
  5843. Graph3d.prototype.setOptions = function (options) {
  5844. var cameraPosition = undefined;
  5845. this.animationStop();
  5846. if (options !== undefined) {
  5847. // retrieve parameter values
  5848. if (options.width !== undefined) this.width = options.width;
  5849. if (options.height !== undefined) this.height = options.height;
  5850. if (options.xCenter !== undefined) this.defaultXCenter = options.xCenter;
  5851. if (options.yCenter !== undefined) this.defaultYCenter = options.yCenter;
  5852. if (options.filterLabel !== undefined) this.filterLabel = options.filterLabel;
  5853. if (options.legendLabel !== undefined) this.legendLabel = options.legendLabel;
  5854. if (options.xLabel !== undefined) this.xLabel = options.xLabel;
  5855. if (options.yLabel !== undefined) this.yLabel = options.yLabel;
  5856. if (options.zLabel !== undefined) this.zLabel = options.zLabel;
  5857. if (options.xValueLabel !== undefined) this.xValueLabel = options.xValueLabel;
  5858. if (options.yValueLabel !== undefined) this.yValueLabel = options.yValueLabel;
  5859. if (options.zValueLabel !== undefined) this.zValueLabel = options.zValueLabel;
  5860. if (options.style !== undefined) {
  5861. var styleNumber = this._getStyleNumber(options.style);
  5862. if (styleNumber !== -1) {
  5863. this.style = styleNumber;
  5864. }
  5865. }
  5866. if (options.showGrid !== undefined) this.showGrid = options.showGrid;
  5867. if (options.showPerspective !== undefined) this.showPerspective = options.showPerspective;
  5868. if (options.showShadow !== undefined) this.showShadow = options.showShadow;
  5869. if (options.tooltip !== undefined) this.showTooltip = options.tooltip;
  5870. if (options.showAnimationControls !== undefined) this.showAnimationControls = options.showAnimationControls;
  5871. if (options.keepAspectRatio !== undefined) this.keepAspectRatio = options.keepAspectRatio;
  5872. if (options.verticalRatio !== undefined) this.verticalRatio = options.verticalRatio;
  5873. if (options.animationInterval !== undefined) this.animationInterval = options.animationInterval;
  5874. if (options.animationPreload !== undefined) this.animationPreload = options.animationPreload;
  5875. if (options.animationAutoStart !== undefined)this.animationAutoStart = options.animationAutoStart;
  5876. if (options.xBarWidth !== undefined) this.defaultXBarWidth = options.xBarWidth;
  5877. if (options.yBarWidth !== undefined) this.defaultYBarWidth = options.yBarWidth;
  5878. if (options.xMin !== undefined) this.defaultXMin = options.xMin;
  5879. if (options.xStep !== undefined) this.defaultXStep = options.xStep;
  5880. if (options.xMax !== undefined) this.defaultXMax = options.xMax;
  5881. if (options.yMin !== undefined) this.defaultYMin = options.yMin;
  5882. if (options.yStep !== undefined) this.defaultYStep = options.yStep;
  5883. if (options.yMax !== undefined) this.defaultYMax = options.yMax;
  5884. if (options.zMin !== undefined) this.defaultZMin = options.zMin;
  5885. if (options.zStep !== undefined) this.defaultZStep = options.zStep;
  5886. if (options.zMax !== undefined) this.defaultZMax = options.zMax;
  5887. if (options.valueMin !== undefined) this.defaultValueMin = options.valueMin;
  5888. if (options.valueMax !== undefined) this.defaultValueMax = options.valueMax;
  5889. if (options.cameraPosition !== undefined) cameraPosition = options.cameraPosition;
  5890. if (cameraPosition !== undefined) {
  5891. this.camera.setArmRotation(cameraPosition.horizontal, cameraPosition.vertical);
  5892. this.camera.setArmLength(cameraPosition.distance);
  5893. }
  5894. else {
  5895. this.camera.setArmRotation(1.0, 0.5);
  5896. this.camera.setArmLength(1.7);
  5897. }
  5898. }
  5899. this._setBackgroundColor(options && options.backgroundColor);
  5900. this.setSize(this.width, this.height);
  5901. // re-load the data
  5902. if (this.dataTable) {
  5903. this.setData(this.dataTable);
  5904. }
  5905. // start animation when option is true
  5906. if (this.animationAutoStart && this.dataFilter) {
  5907. this.animationStart();
  5908. }
  5909. };
  5910. /**
  5911. * Redraw the Graph.
  5912. */
  5913. Graph3d.prototype.redraw = function() {
  5914. if (this.dataPoints === undefined) {
  5915. throw 'Error: graph data not initialized';
  5916. }
  5917. this._resizeCanvas();
  5918. this._resizeCenter();
  5919. this._redrawSlider();
  5920. this._redrawClear();
  5921. this._redrawAxis();
  5922. if (this.style === Graph3d.STYLE.GRID ||
  5923. this.style === Graph3d.STYLE.SURFACE) {
  5924. this._redrawDataGrid();
  5925. }
  5926. else if (this.style === Graph3d.STYLE.LINE) {
  5927. this._redrawDataLine();
  5928. }
  5929. else if (this.style === Graph3d.STYLE.BAR ||
  5930. this.style === Graph3d.STYLE.BARCOLOR ||
  5931. this.style === Graph3d.STYLE.BARSIZE) {
  5932. this._redrawDataBar();
  5933. }
  5934. else {
  5935. // style is DOT, DOTLINE, DOTCOLOR, DOTSIZE
  5936. this._redrawDataDot();
  5937. }
  5938. this._redrawInfo();
  5939. this._redrawLegend();
  5940. };
  5941. /**
  5942. * Clear the canvas before redrawing
  5943. */
  5944. Graph3d.prototype._redrawClear = function() {
  5945. var canvas = this.frame.canvas;
  5946. var ctx = canvas.getContext('2d');
  5947. ctx.clearRect(0, 0, canvas.width, canvas.height);
  5948. };
  5949. /**
  5950. * Redraw the legend showing the colors
  5951. */
  5952. Graph3d.prototype._redrawLegend = function() {
  5953. var y;
  5954. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  5955. this.style === Graph3d.STYLE.DOTSIZE) {
  5956. var dotSize = this.frame.clientWidth * 0.02;
  5957. var widthMin, widthMax;
  5958. if (this.style === Graph3d.STYLE.DOTSIZE) {
  5959. widthMin = dotSize / 2; // px
  5960. widthMax = dotSize / 2 + dotSize * 2; // Todo: put this in one function
  5961. }
  5962. else {
  5963. widthMin = 20; // px
  5964. widthMax = 20; // px
  5965. }
  5966. var height = Math.max(this.frame.clientHeight * 0.25, 100);
  5967. var top = this.margin;
  5968. var right = this.frame.clientWidth - this.margin;
  5969. var left = right - widthMax;
  5970. var bottom = top + height;
  5971. }
  5972. var canvas = this.frame.canvas;
  5973. var ctx = canvas.getContext('2d');
  5974. ctx.lineWidth = 1;
  5975. ctx.font = '14px arial'; // TODO: put in options
  5976. if (this.style === Graph3d.STYLE.DOTCOLOR) {
  5977. // draw the color bar
  5978. var ymin = 0;
  5979. var ymax = height; // Todo: make height customizable
  5980. for (y = ymin; y < ymax; y++) {
  5981. var f = (y - ymin) / (ymax - ymin);
  5982. //var width = (dotSize / 2 + (1-f) * dotSize * 2); // Todo: put this in one function
  5983. var hue = f * 240;
  5984. var color = this._hsv2rgb(hue, 1, 1);
  5985. ctx.strokeStyle = color;
  5986. ctx.beginPath();
  5987. ctx.moveTo(left, top + y);
  5988. ctx.lineTo(right, top + y);
  5989. ctx.stroke();
  5990. }
  5991. ctx.strokeStyle = this.colorAxis;
  5992. ctx.strokeRect(left, top, widthMax, height);
  5993. }
  5994. if (this.style === Graph3d.STYLE.DOTSIZE) {
  5995. // draw border around color bar
  5996. ctx.strokeStyle = this.colorAxis;
  5997. ctx.fillStyle = this.colorDot;
  5998. ctx.beginPath();
  5999. ctx.moveTo(left, top);
  6000. ctx.lineTo(right, top);
  6001. ctx.lineTo(right - widthMax + widthMin, bottom);
  6002. ctx.lineTo(left, bottom);
  6003. ctx.closePath();
  6004. ctx.fill();
  6005. ctx.stroke();
  6006. }
  6007. if (this.style === Graph3d.STYLE.DOTCOLOR ||
  6008. this.style === Graph3d.STYLE.DOTSIZE) {
  6009. // print values along the color bar
  6010. var gridLineLen = 5; // px
  6011. var step = new StepNumber(this.valueMin, this.valueMax, (this.valueMax-this.valueMin)/5, true);
  6012. step.start();
  6013. if (step.getCurrent() < this.valueMin) {
  6014. step.next();
  6015. }
  6016. while (!step.end()) {
  6017. y = bottom - (step.getCurrent() - this.valueMin) / (this.valueMax - this.valueMin) * height;
  6018. ctx.beginPath();
  6019. ctx.moveTo(left - gridLineLen, y);
  6020. ctx.lineTo(left, y);
  6021. ctx.stroke();
  6022. ctx.textAlign = 'right';
  6023. ctx.textBaseline = 'middle';
  6024. ctx.fillStyle = this.colorAxis;
  6025. ctx.fillText(step.getCurrent(), left - 2 * gridLineLen, y);
  6026. step.next();
  6027. }
  6028. ctx.textAlign = 'right';
  6029. ctx.textBaseline = 'top';
  6030. var label = this.legendLabel;
  6031. ctx.fillText(label, right, bottom + this.margin);
  6032. }
  6033. };
  6034. /**
  6035. * Redraw the filter
  6036. */
  6037. Graph3d.prototype._redrawFilter = function() {
  6038. this.frame.filter.innerHTML = '';
  6039. if (this.dataFilter) {
  6040. var options = {
  6041. 'visible': this.showAnimationControls
  6042. };
  6043. var slider = new Slider(this.frame.filter, options);
  6044. this.frame.filter.slider = slider;
  6045. // TODO: css here is not nice here...
  6046. this.frame.filter.style.padding = '10px';
  6047. //this.frame.filter.style.backgroundColor = '#EFEFEF';
  6048. slider.setValues(this.dataFilter.values);
  6049. slider.setPlayInterval(this.animationInterval);
  6050. // create an event handler
  6051. var me = this;
  6052. var onchange = function () {
  6053. var index = slider.getIndex();
  6054. me.dataFilter.selectValue(index);
  6055. me.dataPoints = me.dataFilter._getDataPoints();
  6056. me.redraw();
  6057. };
  6058. slider.setOnChangeCallback(onchange);
  6059. }
  6060. else {
  6061. this.frame.filter.slider = undefined;
  6062. }
  6063. };
  6064. /**
  6065. * Redraw the slider
  6066. */
  6067. Graph3d.prototype._redrawSlider = function() {
  6068. if ( this.frame.filter.slider !== undefined) {
  6069. this.frame.filter.slider.redraw();
  6070. }
  6071. };
  6072. /**
  6073. * Redraw common information
  6074. */
  6075. Graph3d.prototype._redrawInfo = function() {
  6076. if (this.dataFilter) {
  6077. var canvas = this.frame.canvas;
  6078. var ctx = canvas.getContext('2d');
  6079. ctx.font = '14px arial'; // TODO: put in options
  6080. ctx.lineStyle = 'gray';
  6081. ctx.fillStyle = 'gray';
  6082. ctx.textAlign = 'left';
  6083. ctx.textBaseline = 'top';
  6084. var x = this.margin;
  6085. var y = this.margin;
  6086. ctx.fillText(this.dataFilter.getLabel() + ': ' + this.dataFilter.getSelectedValue(), x, y);
  6087. }
  6088. };
  6089. /**
  6090. * Redraw the axis
  6091. */
  6092. Graph3d.prototype._redrawAxis = function() {
  6093. var canvas = this.frame.canvas,
  6094. ctx = canvas.getContext('2d'),
  6095. from, to, step, prettyStep,
  6096. text, xText, yText, zText,
  6097. offset, xOffset, yOffset,
  6098. xMin2d, xMax2d;
  6099. // TODO: get the actual rendered style of the containerElement
  6100. //ctx.font = this.containerElement.style.font;
  6101. ctx.font = 24 / this.camera.getArmLength() + 'px arial';
  6102. // calculate the length for the short grid lines
  6103. var gridLenX = 0.025 / this.scale.x;
  6104. var gridLenY = 0.025 / this.scale.y;
  6105. var textMargin = 5 / this.camera.getArmLength(); // px
  6106. var armAngle = this.camera.getArmRotation().horizontal;
  6107. // draw x-grid lines
  6108. ctx.lineWidth = 1;
  6109. prettyStep = (this.defaultXStep === undefined);
  6110. step = new StepNumber(this.xMin, this.xMax, this.xStep, prettyStep);
  6111. step.start();
  6112. if (step.getCurrent() < this.xMin) {
  6113. step.next();
  6114. }
  6115. while (!step.end()) {
  6116. var x = step.getCurrent();
  6117. if (this.showGrid) {
  6118. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  6119. to = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  6120. ctx.strokeStyle = this.colorGrid;
  6121. ctx.beginPath();
  6122. ctx.moveTo(from.x, from.y);
  6123. ctx.lineTo(to.x, to.y);
  6124. ctx.stroke();
  6125. }
  6126. else {
  6127. from = this._convert3Dto2D(new Point3d(x, this.yMin, this.zMin));
  6128. to = this._convert3Dto2D(new Point3d(x, this.yMin+gridLenX, this.zMin));
  6129. ctx.strokeStyle = this.colorAxis;
  6130. ctx.beginPath();
  6131. ctx.moveTo(from.x, from.y);
  6132. ctx.lineTo(to.x, to.y);
  6133. ctx.stroke();
  6134. from = this._convert3Dto2D(new Point3d(x, this.yMax, this.zMin));
  6135. to = this._convert3Dto2D(new Point3d(x, this.yMax-gridLenX, this.zMin));
  6136. ctx.strokeStyle = this.colorAxis;
  6137. ctx.beginPath();
  6138. ctx.moveTo(from.x, from.y);
  6139. ctx.lineTo(to.x, to.y);
  6140. ctx.stroke();
  6141. }
  6142. yText = (Math.cos(armAngle) > 0) ? this.yMin : this.yMax;
  6143. text = this._convert3Dto2D(new Point3d(x, yText, this.zMin));
  6144. if (Math.cos(armAngle * 2) > 0) {
  6145. ctx.textAlign = 'center';
  6146. ctx.textBaseline = 'top';
  6147. text.y += textMargin;
  6148. }
  6149. else if (Math.sin(armAngle * 2) < 0){
  6150. ctx.textAlign = 'right';
  6151. ctx.textBaseline = 'middle';
  6152. }
  6153. else {
  6154. ctx.textAlign = 'left';
  6155. ctx.textBaseline = 'middle';
  6156. }
  6157. ctx.fillStyle = this.colorAxis;
  6158. ctx.fillText(' ' + this.xValueLabel(step.getCurrent()) + ' ', text.x, text.y);
  6159. step.next();
  6160. }
  6161. // draw y-grid lines
  6162. ctx.lineWidth = 1;
  6163. prettyStep = (this.defaultYStep === undefined);
  6164. step = new StepNumber(this.yMin, this.yMax, this.yStep, prettyStep);
  6165. step.start();
  6166. if (step.getCurrent() < this.yMin) {
  6167. step.next();
  6168. }
  6169. while (!step.end()) {
  6170. if (this.showGrid) {
  6171. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  6172. to = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  6173. ctx.strokeStyle = this.colorGrid;
  6174. ctx.beginPath();
  6175. ctx.moveTo(from.x, from.y);
  6176. ctx.lineTo(to.x, to.y);
  6177. ctx.stroke();
  6178. }
  6179. else {
  6180. from = this._convert3Dto2D(new Point3d(this.xMin, step.getCurrent(), this.zMin));
  6181. to = this._convert3Dto2D(new Point3d(this.xMin+gridLenY, step.getCurrent(), this.zMin));
  6182. ctx.strokeStyle = this.colorAxis;
  6183. ctx.beginPath();
  6184. ctx.moveTo(from.x, from.y);
  6185. ctx.lineTo(to.x, to.y);
  6186. ctx.stroke();
  6187. from = this._convert3Dto2D(new Point3d(this.xMax, step.getCurrent(), this.zMin));
  6188. to = this._convert3Dto2D(new Point3d(this.xMax-gridLenY, step.getCurrent(), this.zMin));
  6189. ctx.strokeStyle = this.colorAxis;
  6190. ctx.beginPath();
  6191. ctx.moveTo(from.x, from.y);
  6192. ctx.lineTo(to.x, to.y);
  6193. ctx.stroke();
  6194. }
  6195. xText = (Math.sin(armAngle ) > 0) ? this.xMin : this.xMax;
  6196. text = this._convert3Dto2D(new Point3d(xText, step.getCurrent(), this.zMin));
  6197. if (Math.cos(armAngle * 2) < 0) {
  6198. ctx.textAlign = 'center';
  6199. ctx.textBaseline = 'top';
  6200. text.y += textMargin;
  6201. }
  6202. else if (Math.sin(armAngle * 2) > 0){
  6203. ctx.textAlign = 'right';
  6204. ctx.textBaseline = 'middle';
  6205. }
  6206. else {
  6207. ctx.textAlign = 'left';
  6208. ctx.textBaseline = 'middle';
  6209. }
  6210. ctx.fillStyle = this.colorAxis;
  6211. ctx.fillText(' ' + this.yValueLabel(step.getCurrent()) + ' ', text.x, text.y);
  6212. step.next();
  6213. }
  6214. // draw z-grid lines and axis
  6215. ctx.lineWidth = 1;
  6216. prettyStep = (this.defaultZStep === undefined);
  6217. step = new StepNumber(this.zMin, this.zMax, this.zStep, prettyStep);
  6218. step.start();
  6219. if (step.getCurrent() < this.zMin) {
  6220. step.next();
  6221. }
  6222. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  6223. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  6224. while (!step.end()) {
  6225. // TODO: make z-grid lines really 3d?
  6226. from = this._convert3Dto2D(new Point3d(xText, yText, step.getCurrent()));
  6227. ctx.strokeStyle = this.colorAxis;
  6228. ctx.beginPath();
  6229. ctx.moveTo(from.x, from.y);
  6230. ctx.lineTo(from.x - textMargin, from.y);
  6231. ctx.stroke();
  6232. ctx.textAlign = 'right';
  6233. ctx.textBaseline = 'middle';
  6234. ctx.fillStyle = this.colorAxis;
  6235. ctx.fillText(this.zValueLabel(step.getCurrent()) + ' ', from.x - 5, from.y);
  6236. step.next();
  6237. }
  6238. ctx.lineWidth = 1;
  6239. from = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  6240. to = this._convert3Dto2D(new Point3d(xText, yText, this.zMax));
  6241. ctx.strokeStyle = this.colorAxis;
  6242. ctx.beginPath();
  6243. ctx.moveTo(from.x, from.y);
  6244. ctx.lineTo(to.x, to.y);
  6245. ctx.stroke();
  6246. // draw x-axis
  6247. ctx.lineWidth = 1;
  6248. // line at yMin
  6249. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  6250. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  6251. ctx.strokeStyle = this.colorAxis;
  6252. ctx.beginPath();
  6253. ctx.moveTo(xMin2d.x, xMin2d.y);
  6254. ctx.lineTo(xMax2d.x, xMax2d.y);
  6255. ctx.stroke();
  6256. // line at ymax
  6257. xMin2d = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  6258. xMax2d = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  6259. ctx.strokeStyle = this.colorAxis;
  6260. ctx.beginPath();
  6261. ctx.moveTo(xMin2d.x, xMin2d.y);
  6262. ctx.lineTo(xMax2d.x, xMax2d.y);
  6263. ctx.stroke();
  6264. // draw y-axis
  6265. ctx.lineWidth = 1;
  6266. // line at xMin
  6267. from = this._convert3Dto2D(new Point3d(this.xMin, this.yMin, this.zMin));
  6268. to = this._convert3Dto2D(new Point3d(this.xMin, this.yMax, this.zMin));
  6269. ctx.strokeStyle = this.colorAxis;
  6270. ctx.beginPath();
  6271. ctx.moveTo(from.x, from.y);
  6272. ctx.lineTo(to.x, to.y);
  6273. ctx.stroke();
  6274. // line at xMax
  6275. from = this._convert3Dto2D(new Point3d(this.xMax, this.yMin, this.zMin));
  6276. to = this._convert3Dto2D(new Point3d(this.xMax, this.yMax, this.zMin));
  6277. ctx.strokeStyle = this.colorAxis;
  6278. ctx.beginPath();
  6279. ctx.moveTo(from.x, from.y);
  6280. ctx.lineTo(to.x, to.y);
  6281. ctx.stroke();
  6282. // draw x-label
  6283. var xLabel = this.xLabel;
  6284. if (xLabel.length > 0) {
  6285. yOffset = 0.1 / this.scale.y;
  6286. xText = (this.xMin + this.xMax) / 2;
  6287. yText = (Math.cos(armAngle) > 0) ? this.yMin - yOffset: this.yMax + yOffset;
  6288. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  6289. if (Math.cos(armAngle * 2) > 0) {
  6290. ctx.textAlign = 'center';
  6291. ctx.textBaseline = 'top';
  6292. }
  6293. else if (Math.sin(armAngle * 2) < 0){
  6294. ctx.textAlign = 'right';
  6295. ctx.textBaseline = 'middle';
  6296. }
  6297. else {
  6298. ctx.textAlign = 'left';
  6299. ctx.textBaseline = 'middle';
  6300. }
  6301. ctx.fillStyle = this.colorAxis;
  6302. ctx.fillText(xLabel, text.x, text.y);
  6303. }
  6304. // draw y-label
  6305. var yLabel = this.yLabel;
  6306. if (yLabel.length > 0) {
  6307. xOffset = 0.1 / this.scale.x;
  6308. xText = (Math.sin(armAngle ) > 0) ? this.xMin - xOffset : this.xMax + xOffset;
  6309. yText = (this.yMin + this.yMax) / 2;
  6310. text = this._convert3Dto2D(new Point3d(xText, yText, this.zMin));
  6311. if (Math.cos(armAngle * 2) < 0) {
  6312. ctx.textAlign = 'center';
  6313. ctx.textBaseline = 'top';
  6314. }
  6315. else if (Math.sin(armAngle * 2) > 0){
  6316. ctx.textAlign = 'right';
  6317. ctx.textBaseline = 'middle';
  6318. }
  6319. else {
  6320. ctx.textAlign = 'left';
  6321. ctx.textBaseline = 'middle';
  6322. }
  6323. ctx.fillStyle = this.colorAxis;
  6324. ctx.fillText(yLabel, text.x, text.y);
  6325. }
  6326. // draw z-label
  6327. var zLabel = this.zLabel;
  6328. if (zLabel.length > 0) {
  6329. offset = 30; // pixels. // TODO: relate to the max width of the values on the z axis?
  6330. xText = (Math.cos(armAngle ) > 0) ? this.xMin : this.xMax;
  6331. yText = (Math.sin(armAngle ) < 0) ? this.yMin : this.yMax;
  6332. zText = (this.zMin + this.zMax) / 2;
  6333. text = this._convert3Dto2D(new Point3d(xText, yText, zText));
  6334. ctx.textAlign = 'right';
  6335. ctx.textBaseline = 'middle';
  6336. ctx.fillStyle = this.colorAxis;
  6337. ctx.fillText(zLabel, text.x - offset, text.y);
  6338. }
  6339. };
  6340. /**
  6341. * Calculate the color based on the given value.
  6342. * @param {Number} H Hue, a value be between 0 and 360
  6343. * @param {Number} S Saturation, a value between 0 and 1
  6344. * @param {Number} V Value, a value between 0 and 1
  6345. */
  6346. Graph3d.prototype._hsv2rgb = function(H, S, V) {
  6347. var R, G, B, C, Hi, X;
  6348. C = V * S;
  6349. Hi = Math.floor(H/60); // hi = 0,1,2,3,4,5
  6350. X = C * (1 - Math.abs(((H/60) % 2) - 1));
  6351. switch (Hi) {
  6352. case 0: R = C; G = X; B = 0; break;
  6353. case 1: R = X; G = C; B = 0; break;
  6354. case 2: R = 0; G = C; B = X; break;
  6355. case 3: R = 0; G = X; B = C; break;
  6356. case 4: R = X; G = 0; B = C; break;
  6357. case 5: R = C; G = 0; B = X; break;
  6358. default: R = 0; G = 0; B = 0; break;
  6359. }
  6360. return 'RGB(' + parseInt(R*255) + ',' + parseInt(G*255) + ',' + parseInt(B*255) + ')';
  6361. };
  6362. /**
  6363. * Draw all datapoints as a grid
  6364. * This function can be used when the style is 'grid'
  6365. */
  6366. Graph3d.prototype._redrawDataGrid = function() {
  6367. var canvas = this.frame.canvas,
  6368. ctx = canvas.getContext('2d'),
  6369. point, right, top, cross,
  6370. i,
  6371. topSideVisible, fillStyle, strokeStyle, lineWidth,
  6372. h, s, v, zAvg;
  6373. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  6374. return; // TODO: throw exception?
  6375. // calculate the translations and screen position of all points
  6376. for (i = 0; i < this.dataPoints.length; i++) {
  6377. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  6378. var screen = this._convertTranslationToScreen(trans);
  6379. this.dataPoints[i].trans = trans;
  6380. this.dataPoints[i].screen = screen;
  6381. // calculate the translation of the point at the bottom (needed for sorting)
  6382. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  6383. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  6384. }
  6385. // sort the points on depth of their (x,y) position (not on z)
  6386. var sortDepth = function (a, b) {
  6387. return b.dist - a.dist;
  6388. };
  6389. this.dataPoints.sort(sortDepth);
  6390. if (this.style === Graph3d.STYLE.SURFACE) {
  6391. for (i = 0; i < this.dataPoints.length; i++) {
  6392. point = this.dataPoints[i];
  6393. right = this.dataPoints[i].pointRight;
  6394. top = this.dataPoints[i].pointTop;
  6395. cross = this.dataPoints[i].pointCross;
  6396. if (point !== undefined && right !== undefined && top !== undefined && cross !== undefined) {
  6397. if (this.showGrayBottom || this.showShadow) {
  6398. // calculate the cross product of the two vectors from center
  6399. // to left and right, in order to know whether we are looking at the
  6400. // bottom or at the top side. We can also use the cross product
  6401. // for calculating light intensity
  6402. var aDiff = Point3d.subtract(cross.trans, point.trans);
  6403. var bDiff = Point3d.subtract(top.trans, right.trans);
  6404. var crossproduct = Point3d.crossProduct(aDiff, bDiff);
  6405. var len = crossproduct.length();
  6406. // FIXME: there is a bug with determining the surface side (shadow or colored)
  6407. topSideVisible = (crossproduct.z > 0);
  6408. }
  6409. else {
  6410. topSideVisible = true;
  6411. }
  6412. if (topSideVisible) {
  6413. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6414. zAvg = (point.point.z + right.point.z + top.point.z + cross.point.z) / 4;
  6415. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6416. s = 1; // saturation
  6417. if (this.showShadow) {
  6418. v = Math.min(1 + (crossproduct.x / len) / 2, 1); // value. TODO: scale
  6419. fillStyle = this._hsv2rgb(h, s, v);
  6420. strokeStyle = fillStyle;
  6421. }
  6422. else {
  6423. v = 1;
  6424. fillStyle = this._hsv2rgb(h, s, v);
  6425. strokeStyle = this.colorAxis;
  6426. }
  6427. }
  6428. else {
  6429. fillStyle = 'gray';
  6430. strokeStyle = this.colorAxis;
  6431. }
  6432. lineWidth = 0.5;
  6433. ctx.lineWidth = lineWidth;
  6434. ctx.fillStyle = fillStyle;
  6435. ctx.strokeStyle = strokeStyle;
  6436. ctx.beginPath();
  6437. ctx.moveTo(point.screen.x, point.screen.y);
  6438. ctx.lineTo(right.screen.x, right.screen.y);
  6439. ctx.lineTo(cross.screen.x, cross.screen.y);
  6440. ctx.lineTo(top.screen.x, top.screen.y);
  6441. ctx.closePath();
  6442. ctx.fill();
  6443. ctx.stroke();
  6444. }
  6445. }
  6446. }
  6447. else { // grid style
  6448. for (i = 0; i < this.dataPoints.length; i++) {
  6449. point = this.dataPoints[i];
  6450. right = this.dataPoints[i].pointRight;
  6451. top = this.dataPoints[i].pointTop;
  6452. if (point !== undefined) {
  6453. if (this.showPerspective) {
  6454. lineWidth = 2 / -point.trans.z;
  6455. }
  6456. else {
  6457. lineWidth = 2 * -(this.eye.z / this.camera.getArmLength());
  6458. }
  6459. }
  6460. if (point !== undefined && right !== undefined) {
  6461. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6462. zAvg = (point.point.z + right.point.z) / 2;
  6463. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6464. ctx.lineWidth = lineWidth;
  6465. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  6466. ctx.beginPath();
  6467. ctx.moveTo(point.screen.x, point.screen.y);
  6468. ctx.lineTo(right.screen.x, right.screen.y);
  6469. ctx.stroke();
  6470. }
  6471. if (point !== undefined && top !== undefined) {
  6472. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6473. zAvg = (point.point.z + top.point.z) / 2;
  6474. h = (1 - (zAvg - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6475. ctx.lineWidth = lineWidth;
  6476. ctx.strokeStyle = this._hsv2rgb(h, 1, 1);
  6477. ctx.beginPath();
  6478. ctx.moveTo(point.screen.x, point.screen.y);
  6479. ctx.lineTo(top.screen.x, top.screen.y);
  6480. ctx.stroke();
  6481. }
  6482. }
  6483. }
  6484. };
  6485. /**
  6486. * Draw all datapoints as dots.
  6487. * This function can be used when the style is 'dot' or 'dot-line'
  6488. */
  6489. Graph3d.prototype._redrawDataDot = function() {
  6490. var canvas = this.frame.canvas;
  6491. var ctx = canvas.getContext('2d');
  6492. var i;
  6493. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  6494. return; // TODO: throw exception?
  6495. // calculate the translations of all points
  6496. for (i = 0; i < this.dataPoints.length; i++) {
  6497. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  6498. var screen = this._convertTranslationToScreen(trans);
  6499. this.dataPoints[i].trans = trans;
  6500. this.dataPoints[i].screen = screen;
  6501. // calculate the distance from the point at the bottom to the camera
  6502. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  6503. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  6504. }
  6505. // order the translated points by depth
  6506. var sortDepth = function (a, b) {
  6507. return b.dist - a.dist;
  6508. };
  6509. this.dataPoints.sort(sortDepth);
  6510. // draw the datapoints as colored circles
  6511. var dotSize = this.frame.clientWidth * 0.02; // px
  6512. for (i = 0; i < this.dataPoints.length; i++) {
  6513. var point = this.dataPoints[i];
  6514. if (this.style === Graph3d.STYLE.DOTLINE) {
  6515. // draw a vertical line from the bottom to the graph value
  6516. //var from = this._convert3Dto2D(new Point3d(point.point.x, point.point.y, this.zMin));
  6517. var from = this._convert3Dto2D(point.bottom);
  6518. ctx.lineWidth = 1;
  6519. ctx.strokeStyle = this.colorGrid;
  6520. ctx.beginPath();
  6521. ctx.moveTo(from.x, from.y);
  6522. ctx.lineTo(point.screen.x, point.screen.y);
  6523. ctx.stroke();
  6524. }
  6525. // calculate radius for the circle
  6526. var size;
  6527. if (this.style === Graph3d.STYLE.DOTSIZE) {
  6528. size = dotSize/2 + 2*dotSize * (point.point.value - this.valueMin) / (this.valueMax - this.valueMin);
  6529. }
  6530. else {
  6531. size = dotSize;
  6532. }
  6533. var radius;
  6534. if (this.showPerspective) {
  6535. radius = size / -point.trans.z;
  6536. }
  6537. else {
  6538. radius = size * -(this.eye.z / this.camera.getArmLength());
  6539. }
  6540. if (radius < 0) {
  6541. radius = 0;
  6542. }
  6543. var hue, color, borderColor;
  6544. if (this.style === Graph3d.STYLE.DOTCOLOR ) {
  6545. // calculate the color based on the value
  6546. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  6547. color = this._hsv2rgb(hue, 1, 1);
  6548. borderColor = this._hsv2rgb(hue, 1, 0.8);
  6549. }
  6550. else if (this.style === Graph3d.STYLE.DOTSIZE) {
  6551. color = this.colorDot;
  6552. borderColor = this.colorDotBorder;
  6553. }
  6554. else {
  6555. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6556. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6557. color = this._hsv2rgb(hue, 1, 1);
  6558. borderColor = this._hsv2rgb(hue, 1, 0.8);
  6559. }
  6560. // draw the circle
  6561. ctx.lineWidth = 1.0;
  6562. ctx.strokeStyle = borderColor;
  6563. ctx.fillStyle = color;
  6564. ctx.beginPath();
  6565. ctx.arc(point.screen.x, point.screen.y, radius, 0, Math.PI*2, true);
  6566. ctx.fill();
  6567. ctx.stroke();
  6568. }
  6569. };
  6570. /**
  6571. * Draw all datapoints as bars.
  6572. * This function can be used when the style is 'bar', 'bar-color', or 'bar-size'
  6573. */
  6574. Graph3d.prototype._redrawDataBar = function() {
  6575. var canvas = this.frame.canvas;
  6576. var ctx = canvas.getContext('2d');
  6577. var i, j, surface, corners;
  6578. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  6579. return; // TODO: throw exception?
  6580. // calculate the translations of all points
  6581. for (i = 0; i < this.dataPoints.length; i++) {
  6582. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  6583. var screen = this._convertTranslationToScreen(trans);
  6584. this.dataPoints[i].trans = trans;
  6585. this.dataPoints[i].screen = screen;
  6586. // calculate the distance from the point at the bottom to the camera
  6587. var transBottom = this._convertPointToTranslation(this.dataPoints[i].bottom);
  6588. this.dataPoints[i].dist = this.showPerspective ? transBottom.length() : -transBottom.z;
  6589. }
  6590. // order the translated points by depth
  6591. var sortDepth = function (a, b) {
  6592. return b.dist - a.dist;
  6593. };
  6594. this.dataPoints.sort(sortDepth);
  6595. // draw the datapoints as bars
  6596. var xWidth = this.xBarWidth / 2;
  6597. var yWidth = this.yBarWidth / 2;
  6598. for (i = 0; i < this.dataPoints.length; i++) {
  6599. var point = this.dataPoints[i];
  6600. // determine color
  6601. var hue, color, borderColor;
  6602. if (this.style === Graph3d.STYLE.BARCOLOR ) {
  6603. // calculate the color based on the value
  6604. hue = (1 - (point.point.value - this.valueMin) * this.scale.value) * 240;
  6605. color = this._hsv2rgb(hue, 1, 1);
  6606. borderColor = this._hsv2rgb(hue, 1, 0.8);
  6607. }
  6608. else if (this.style === Graph3d.STYLE.BARSIZE) {
  6609. color = this.colorDot;
  6610. borderColor = this.colorDotBorder;
  6611. }
  6612. else {
  6613. // calculate Hue from the current value. At zMin the hue is 240, at zMax the hue is 0
  6614. hue = (1 - (point.point.z - this.zMin) * this.scale.z / this.verticalRatio) * 240;
  6615. color = this._hsv2rgb(hue, 1, 1);
  6616. borderColor = this._hsv2rgb(hue, 1, 0.8);
  6617. }
  6618. // calculate size for the bar
  6619. if (this.style === Graph3d.STYLE.BARSIZE) {
  6620. xWidth = (this.xBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  6621. yWidth = (this.yBarWidth / 2) * ((point.point.value - this.valueMin) / (this.valueMax - this.valueMin) * 0.8 + 0.2);
  6622. }
  6623. // calculate all corner points
  6624. var me = this;
  6625. var point3d = point.point;
  6626. var top = [
  6627. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, point3d.z)},
  6628. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, point3d.z)},
  6629. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, point3d.z)},
  6630. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, point3d.z)}
  6631. ];
  6632. var bottom = [
  6633. {point: new Point3d(point3d.x - xWidth, point3d.y - yWidth, this.zMin)},
  6634. {point: new Point3d(point3d.x + xWidth, point3d.y - yWidth, this.zMin)},
  6635. {point: new Point3d(point3d.x + xWidth, point3d.y + yWidth, this.zMin)},
  6636. {point: new Point3d(point3d.x - xWidth, point3d.y + yWidth, this.zMin)}
  6637. ];
  6638. // calculate screen location of the points
  6639. top.forEach(function (obj) {
  6640. obj.screen = me._convert3Dto2D(obj.point);
  6641. });
  6642. bottom.forEach(function (obj) {
  6643. obj.screen = me._convert3Dto2D(obj.point);
  6644. });
  6645. // create five sides, calculate both corner points and center points
  6646. var surfaces = [
  6647. {corners: top, center: Point3d.avg(bottom[0].point, bottom[2].point)},
  6648. {corners: [top[0], top[1], bottom[1], bottom[0]], center: Point3d.avg(bottom[1].point, bottom[0].point)},
  6649. {corners: [top[1], top[2], bottom[2], bottom[1]], center: Point3d.avg(bottom[2].point, bottom[1].point)},
  6650. {corners: [top[2], top[3], bottom[3], bottom[2]], center: Point3d.avg(bottom[3].point, bottom[2].point)},
  6651. {corners: [top[3], top[0], bottom[0], bottom[3]], center: Point3d.avg(bottom[0].point, bottom[3].point)}
  6652. ];
  6653. point.surfaces = surfaces;
  6654. // calculate the distance of each of the surface centers to the camera
  6655. for (j = 0; j < surfaces.length; j++) {
  6656. surface = surfaces[j];
  6657. var transCenter = this._convertPointToTranslation(surface.center);
  6658. surface.dist = this.showPerspective ? transCenter.length() : -transCenter.z;
  6659. // TODO: this dept calculation doesn't work 100% of the cases due to perspective,
  6660. // but the current solution is fast/simple and works in 99.9% of all cases
  6661. // the issue is visible in example 14, with graph.setCameraPosition({horizontal: 2.97, vertical: 0.5, distance: 0.9})
  6662. }
  6663. // order the surfaces by their (translated) depth
  6664. surfaces.sort(function (a, b) {
  6665. var diff = b.dist - a.dist;
  6666. if (diff) return diff;
  6667. // if equal depth, sort the top surface last
  6668. if (a.corners === top) return 1;
  6669. if (b.corners === top) return -1;
  6670. // both are equal
  6671. return 0;
  6672. });
  6673. // draw the ordered surfaces
  6674. ctx.lineWidth = 1;
  6675. ctx.strokeStyle = borderColor;
  6676. ctx.fillStyle = color;
  6677. // NOTE: we start at j=2 instead of j=0 as we don't need to draw the two surfaces at the backside
  6678. for (j = 2; j < surfaces.length; j++) {
  6679. surface = surfaces[j];
  6680. corners = surface.corners;
  6681. ctx.beginPath();
  6682. ctx.moveTo(corners[3].screen.x, corners[3].screen.y);
  6683. ctx.lineTo(corners[0].screen.x, corners[0].screen.y);
  6684. ctx.lineTo(corners[1].screen.x, corners[1].screen.y);
  6685. ctx.lineTo(corners[2].screen.x, corners[2].screen.y);
  6686. ctx.lineTo(corners[3].screen.x, corners[3].screen.y);
  6687. ctx.fill();
  6688. ctx.stroke();
  6689. }
  6690. }
  6691. };
  6692. /**
  6693. * Draw a line through all datapoints.
  6694. * This function can be used when the style is 'line'
  6695. */
  6696. Graph3d.prototype._redrawDataLine = function() {
  6697. var canvas = this.frame.canvas,
  6698. ctx = canvas.getContext('2d'),
  6699. point, i;
  6700. if (this.dataPoints === undefined || this.dataPoints.length <= 0)
  6701. return; // TODO: throw exception?
  6702. // calculate the translations of all points
  6703. for (i = 0; i < this.dataPoints.length; i++) {
  6704. var trans = this._convertPointToTranslation(this.dataPoints[i].point);
  6705. var screen = this._convertTranslationToScreen(trans);
  6706. this.dataPoints[i].trans = trans;
  6707. this.dataPoints[i].screen = screen;
  6708. }
  6709. // start the line
  6710. if (this.dataPoints.length > 0) {
  6711. point = this.dataPoints[0];
  6712. ctx.lineWidth = 1; // TODO: make customizable
  6713. ctx.strokeStyle = 'blue'; // TODO: make customizable
  6714. ctx.beginPath();
  6715. ctx.moveTo(point.screen.x, point.screen.y);
  6716. }
  6717. // draw the datapoints as colored circles
  6718. for (i = 1; i < this.dataPoints.length; i++) {
  6719. point = this.dataPoints[i];
  6720. ctx.lineTo(point.screen.x, point.screen.y);
  6721. }
  6722. // finish the line
  6723. if (this.dataPoints.length > 0) {
  6724. ctx.stroke();
  6725. }
  6726. };
  6727. /**
  6728. * Start a moving operation inside the provided parent element
  6729. * @param {Event} event The event that occurred (required for
  6730. * retrieving the mouse position)
  6731. */
  6732. Graph3d.prototype._onMouseDown = function(event) {
  6733. event = event || window.event;
  6734. // check if mouse is still down (may be up when focus is lost for example
  6735. // in an iframe)
  6736. if (this.leftButtonDown) {
  6737. this._onMouseUp(event);
  6738. }
  6739. // only react on left mouse button down
  6740. this.leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  6741. if (!this.leftButtonDown && !this.touchDown) return;
  6742. // get mouse position (different code for IE and all other browsers)
  6743. this.startMouseX = getMouseX(event);
  6744. this.startMouseY = getMouseY(event);
  6745. this.startStart = new Date(this.start);
  6746. this.startEnd = new Date(this.end);
  6747. this.startArmRotation = this.camera.getArmRotation();
  6748. this.frame.style.cursor = 'move';
  6749. // add event listeners to handle moving the contents
  6750. // we store the function onmousemove and onmouseup in the graph, so we can
  6751. // remove the eventlisteners lateron in the function mouseUp()
  6752. var me = this;
  6753. this.onmousemove = function (event) {me._onMouseMove(event);};
  6754. this.onmouseup = function (event) {me._onMouseUp(event);};
  6755. util.addEventListener(document, 'mousemove', me.onmousemove);
  6756. util.addEventListener(document, 'mouseup', me.onmouseup);
  6757. util.preventDefault(event);
  6758. };
  6759. /**
  6760. * Perform moving operating.
  6761. * This function activated from within the funcion Graph.mouseDown().
  6762. * @param {Event} event Well, eehh, the event
  6763. */
  6764. Graph3d.prototype._onMouseMove = function (event) {
  6765. event = event || window.event;
  6766. // calculate change in mouse position
  6767. var diffX = parseFloat(getMouseX(event)) - this.startMouseX;
  6768. var diffY = parseFloat(getMouseY(event)) - this.startMouseY;
  6769. var horizontalNew = this.startArmRotation.horizontal + diffX / 200;
  6770. var verticalNew = this.startArmRotation.vertical + diffY / 200;
  6771. var snapAngle = 4; // degrees
  6772. var snapValue = Math.sin(snapAngle / 360 * 2 * Math.PI);
  6773. // snap horizontally to nice angles at 0pi, 0.5pi, 1pi, 1.5pi, etc...
  6774. // the -0.001 is to take care that the vertical axis is always drawn at the left front corner
  6775. if (Math.abs(Math.sin(horizontalNew)) < snapValue) {
  6776. horizontalNew = Math.round((horizontalNew / Math.PI)) * Math.PI - 0.001;
  6777. }
  6778. if (Math.abs(Math.cos(horizontalNew)) < snapValue) {
  6779. horizontalNew = (Math.round((horizontalNew/ Math.PI - 0.5)) + 0.5) * Math.PI - 0.001;
  6780. }
  6781. // snap vertically to nice angles
  6782. if (Math.abs(Math.sin(verticalNew)) < snapValue) {
  6783. verticalNew = Math.round((verticalNew / Math.PI)) * Math.PI;
  6784. }
  6785. if (Math.abs(Math.cos(verticalNew)) < snapValue) {
  6786. verticalNew = (Math.round((verticalNew/ Math.PI - 0.5)) + 0.5) * Math.PI;
  6787. }
  6788. this.camera.setArmRotation(horizontalNew, verticalNew);
  6789. this.redraw();
  6790. // fire a cameraPositionChange event
  6791. var parameters = this.getCameraPosition();
  6792. this.emit('cameraPositionChange', parameters);
  6793. util.preventDefault(event);
  6794. };
  6795. /**
  6796. * Stop moving operating.
  6797. * This function activated from within the funcion Graph.mouseDown().
  6798. * @param {event} event The event
  6799. */
  6800. Graph3d.prototype._onMouseUp = function (event) {
  6801. this.frame.style.cursor = 'auto';
  6802. this.leftButtonDown = false;
  6803. // remove event listeners here
  6804. util.removeEventListener(document, 'mousemove', this.onmousemove);
  6805. util.removeEventListener(document, 'mouseup', this.onmouseup);
  6806. util.preventDefault(event);
  6807. };
  6808. /**
  6809. * After having moved the mouse, a tooltip should pop up when the mouse is resting on a data point
  6810. * @param {Event} event A mouse move event
  6811. */
  6812. Graph3d.prototype._onTooltip = function (event) {
  6813. var delay = 300; // ms
  6814. var mouseX = getMouseX(event) - util.getAbsoluteLeft(this.frame);
  6815. var mouseY = getMouseY(event) - util.getAbsoluteTop(this.frame);
  6816. if (!this.showTooltip) {
  6817. return;
  6818. }
  6819. if (this.tooltipTimeout) {
  6820. clearTimeout(this.tooltipTimeout);
  6821. }
  6822. // (delayed) display of a tooltip only if no mouse button is down
  6823. if (this.leftButtonDown) {
  6824. this._hideTooltip();
  6825. return;
  6826. }
  6827. if (this.tooltip && this.tooltip.dataPoint) {
  6828. // tooltip is currently visible
  6829. var dataPoint = this._dataPointFromXY(mouseX, mouseY);
  6830. if (dataPoint !== this.tooltip.dataPoint) {
  6831. // datapoint changed
  6832. if (dataPoint) {
  6833. this._showTooltip(dataPoint);
  6834. }
  6835. else {
  6836. this._hideTooltip();
  6837. }
  6838. }
  6839. }
  6840. else {
  6841. // tooltip is currently not visible
  6842. var me = this;
  6843. this.tooltipTimeout = setTimeout(function () {
  6844. me.tooltipTimeout = null;
  6845. // show a tooltip if we have a data point
  6846. var dataPoint = me._dataPointFromXY(mouseX, mouseY);
  6847. if (dataPoint) {
  6848. me._showTooltip(dataPoint);
  6849. }
  6850. }, delay);
  6851. }
  6852. };
  6853. /**
  6854. * Event handler for touchstart event on mobile devices
  6855. */
  6856. Graph3d.prototype._onTouchStart = function(event) {
  6857. this.touchDown = true;
  6858. var me = this;
  6859. this.ontouchmove = function (event) {me._onTouchMove(event);};
  6860. this.ontouchend = function (event) {me._onTouchEnd(event);};
  6861. util.addEventListener(document, 'touchmove', me.ontouchmove);
  6862. util.addEventListener(document, 'touchend', me.ontouchend);
  6863. this._onMouseDown(event);
  6864. };
  6865. /**
  6866. * Event handler for touchmove event on mobile devices
  6867. */
  6868. Graph3d.prototype._onTouchMove = function(event) {
  6869. this._onMouseMove(event);
  6870. };
  6871. /**
  6872. * Event handler for touchend event on mobile devices
  6873. */
  6874. Graph3d.prototype._onTouchEnd = function(event) {
  6875. this.touchDown = false;
  6876. util.removeEventListener(document, 'touchmove', this.ontouchmove);
  6877. util.removeEventListener(document, 'touchend', this.ontouchend);
  6878. this._onMouseUp(event);
  6879. };
  6880. /**
  6881. * Event handler for mouse wheel event, used to zoom the graph
  6882. * Code from http://adomas.org/javascript-mouse-wheel/
  6883. * @param {event} event The event
  6884. */
  6885. Graph3d.prototype._onWheel = function(event) {
  6886. if (!event) /* For IE. */
  6887. event = window.event;
  6888. // retrieve delta
  6889. var delta = 0;
  6890. if (event.wheelDelta) { /* IE/Opera. */
  6891. delta = event.wheelDelta/120;
  6892. } else if (event.detail) { /* Mozilla case. */
  6893. // In Mozilla, sign of delta is different than in IE.
  6894. // Also, delta is multiple of 3.
  6895. delta = -event.detail/3;
  6896. }
  6897. // If delta is nonzero, handle it.
  6898. // Basically, delta is now positive if wheel was scrolled up,
  6899. // and negative, if wheel was scrolled down.
  6900. if (delta) {
  6901. var oldLength = this.camera.getArmLength();
  6902. var newLength = oldLength * (1 - delta / 10);
  6903. this.camera.setArmLength(newLength);
  6904. this.redraw();
  6905. this._hideTooltip();
  6906. }
  6907. // fire a cameraPositionChange event
  6908. var parameters = this.getCameraPosition();
  6909. this.emit('cameraPositionChange', parameters);
  6910. // Prevent default actions caused by mouse wheel.
  6911. // That might be ugly, but we handle scrolls somehow
  6912. // anyway, so don't bother here..
  6913. util.preventDefault(event);
  6914. };
  6915. /**
  6916. * Test whether a point lies inside given 2D triangle
  6917. * @param {Point2d} point
  6918. * @param {Point2d[]} triangle
  6919. * @return {boolean} Returns true if given point lies inside or on the edge of the triangle
  6920. * @private
  6921. */
  6922. Graph3d.prototype._insideTriangle = function (point, triangle) {
  6923. var a = triangle[0],
  6924. b = triangle[1],
  6925. c = triangle[2];
  6926. function sign (x) {
  6927. return x > 0 ? 1 : x < 0 ? -1 : 0;
  6928. }
  6929. var as = sign((b.x - a.x) * (point.y - a.y) - (b.y - a.y) * (point.x - a.x));
  6930. var bs = sign((c.x - b.x) * (point.y - b.y) - (c.y - b.y) * (point.x - b.x));
  6931. var cs = sign((a.x - c.x) * (point.y - c.y) - (a.y - c.y) * (point.x - c.x));
  6932. // each of the three signs must be either equal to each other or zero
  6933. return (as == 0 || bs == 0 || as == bs) &&
  6934. (bs == 0 || cs == 0 || bs == cs) &&
  6935. (as == 0 || cs == 0 || as == cs);
  6936. };
  6937. /**
  6938. * Find a data point close to given screen position (x, y)
  6939. * @param {Number} x
  6940. * @param {Number} y
  6941. * @return {Object | null} The closest data point or null if not close to any data point
  6942. * @private
  6943. */
  6944. Graph3d.prototype._dataPointFromXY = function (x, y) {
  6945. var i,
  6946. distMax = 100, // px
  6947. dataPoint = null,
  6948. closestDataPoint = null,
  6949. closestDist = null,
  6950. center = new Point2d(x, y);
  6951. if (this.style === Graph3d.STYLE.BAR ||
  6952. this.style === Graph3d.STYLE.BARCOLOR ||
  6953. this.style === Graph3d.STYLE.BARSIZE) {
  6954. // the data points are ordered from far away to closest
  6955. for (i = this.dataPoints.length - 1; i >= 0; i--) {
  6956. dataPoint = this.dataPoints[i];
  6957. var surfaces = dataPoint.surfaces;
  6958. if (surfaces) {
  6959. for (var s = surfaces.length - 1; s >= 0; s--) {
  6960. // split each surface in two triangles, and see if the center point is inside one of these
  6961. var surface = surfaces[s];
  6962. var corners = surface.corners;
  6963. var triangle1 = [corners[0].screen, corners[1].screen, corners[2].screen];
  6964. var triangle2 = [corners[2].screen, corners[3].screen, corners[0].screen];
  6965. if (this._insideTriangle(center, triangle1) ||
  6966. this._insideTriangle(center, triangle2)) {
  6967. // return immediately at the first hit
  6968. return dataPoint;
  6969. }
  6970. }
  6971. }
  6972. }
  6973. }
  6974. else {
  6975. // find the closest data point, using distance to the center of the point on 2d screen
  6976. for (i = 0; i < this.dataPoints.length; i++) {
  6977. dataPoint = this.dataPoints[i];
  6978. var point = dataPoint.screen;
  6979. if (point) {
  6980. var distX = Math.abs(x - point.x);
  6981. var distY = Math.abs(y - point.y);
  6982. var dist = Math.sqrt(distX * distX + distY * distY);
  6983. if ((closestDist === null || dist < closestDist) && dist < distMax) {
  6984. closestDist = dist;
  6985. closestDataPoint = dataPoint;
  6986. }
  6987. }
  6988. }
  6989. }
  6990. return closestDataPoint;
  6991. };
  6992. /**
  6993. * Display a tooltip for given data point
  6994. * @param {Object} dataPoint
  6995. * @private
  6996. */
  6997. Graph3d.prototype._showTooltip = function (dataPoint) {
  6998. var content, line, dot;
  6999. if (!this.tooltip) {
  7000. content = document.createElement('div');
  7001. content.style.position = 'absolute';
  7002. content.style.padding = '10px';
  7003. content.style.border = '1px solid #4d4d4d';
  7004. content.style.color = '#1a1a1a';
  7005. content.style.background = 'rgba(255,255,255,0.7)';
  7006. content.style.borderRadius = '2px';
  7007. content.style.boxShadow = '5px 5px 10px rgba(128,128,128,0.5)';
  7008. line = document.createElement('div');
  7009. line.style.position = 'absolute';
  7010. line.style.height = '40px';
  7011. line.style.width = '0';
  7012. line.style.borderLeft = '1px solid #4d4d4d';
  7013. dot = document.createElement('div');
  7014. dot.style.position = 'absolute';
  7015. dot.style.height = '0';
  7016. dot.style.width = '0';
  7017. dot.style.border = '5px solid #4d4d4d';
  7018. dot.style.borderRadius = '5px';
  7019. this.tooltip = {
  7020. dataPoint: null,
  7021. dom: {
  7022. content: content,
  7023. line: line,
  7024. dot: dot
  7025. }
  7026. };
  7027. }
  7028. else {
  7029. content = this.tooltip.dom.content;
  7030. line = this.tooltip.dom.line;
  7031. dot = this.tooltip.dom.dot;
  7032. }
  7033. this._hideTooltip();
  7034. this.tooltip.dataPoint = dataPoint;
  7035. if (typeof this.showTooltip === 'function') {
  7036. content.innerHTML = this.showTooltip(dataPoint.point);
  7037. }
  7038. else {
  7039. content.innerHTML = '<table>' +
  7040. '<tr><td>x:</td><td>' + dataPoint.point.x + '</td></tr>' +
  7041. '<tr><td>y:</td><td>' + dataPoint.point.y + '</td></tr>' +
  7042. '<tr><td>z:</td><td>' + dataPoint.point.z + '</td></tr>' +
  7043. '</table>';
  7044. }
  7045. content.style.left = '0';
  7046. content.style.top = '0';
  7047. this.frame.appendChild(content);
  7048. this.frame.appendChild(line);
  7049. this.frame.appendChild(dot);
  7050. // calculate sizes
  7051. var contentWidth = content.offsetWidth;
  7052. var contentHeight = content.offsetHeight;
  7053. var lineHeight = line.offsetHeight;
  7054. var dotWidth = dot.offsetWidth;
  7055. var dotHeight = dot.offsetHeight;
  7056. var left = dataPoint.screen.x - contentWidth / 2;
  7057. left = Math.min(Math.max(left, 10), this.frame.clientWidth - 10 - contentWidth);
  7058. line.style.left = dataPoint.screen.x + 'px';
  7059. line.style.top = (dataPoint.screen.y - lineHeight) + 'px';
  7060. content.style.left = left + 'px';
  7061. content.style.top = (dataPoint.screen.y - lineHeight - contentHeight) + 'px';
  7062. dot.style.left = (dataPoint.screen.x - dotWidth / 2) + 'px';
  7063. dot.style.top = (dataPoint.screen.y - dotHeight / 2) + 'px';
  7064. };
  7065. /**
  7066. * Hide the tooltip when displayed
  7067. * @private
  7068. */
  7069. Graph3d.prototype._hideTooltip = function () {
  7070. if (this.tooltip) {
  7071. this.tooltip.dataPoint = null;
  7072. for (var prop in this.tooltip.dom) {
  7073. if (this.tooltip.dom.hasOwnProperty(prop)) {
  7074. var elem = this.tooltip.dom[prop];
  7075. if (elem && elem.parentNode) {
  7076. elem.parentNode.removeChild(elem);
  7077. }
  7078. }
  7079. }
  7080. }
  7081. };
  7082. /**--------------------------------------------------------------------------**/
  7083. /**
  7084. * Get the horizontal mouse position from a mouse event
  7085. * @param {Event} event
  7086. * @return {Number} mouse x
  7087. */
  7088. getMouseX = function(event) {
  7089. if ('clientX' in event) return event.clientX;
  7090. return event.targetTouches[0] && event.targetTouches[0].clientX || 0;
  7091. };
  7092. /**
  7093. * Get the vertical mouse position from a mouse event
  7094. * @param {Event} event
  7095. * @return {Number} mouse y
  7096. */
  7097. getMouseY = function(event) {
  7098. if ('clientY' in event) return event.clientY;
  7099. return event.targetTouches[0] && event.targetTouches[0].clientY || 0;
  7100. };
  7101. module.exports = Graph3d;
  7102. /***/ },
  7103. /* 14 */
  7104. /***/ function(module, exports, __webpack_require__) {
  7105. var Point3d = __webpack_require__(18);
  7106. /**
  7107. * @class Camera
  7108. * The camera is mounted on a (virtual) camera arm. The camera arm can rotate
  7109. * The camera is always looking in the direction of the origin of the arm.
  7110. * This way, the camera always rotates around one fixed point, the location
  7111. * of the camera arm.
  7112. *
  7113. * Documentation:
  7114. * http://en.wikipedia.org/wiki/3D_projection
  7115. */
  7116. Camera = function () {
  7117. this.armLocation = new Point3d();
  7118. this.armRotation = {};
  7119. this.armRotation.horizontal = 0;
  7120. this.armRotation.vertical = 0;
  7121. this.armLength = 1.7;
  7122. this.cameraLocation = new Point3d();
  7123. this.cameraRotation = new Point3d(0.5*Math.PI, 0, 0);
  7124. this.calculateCameraOrientation();
  7125. };
  7126. /**
  7127. * Set the location (origin) of the arm
  7128. * @param {Number} x Normalized value of x
  7129. * @param {Number} y Normalized value of y
  7130. * @param {Number} z Normalized value of z
  7131. */
  7132. Camera.prototype.setArmLocation = function(x, y, z) {
  7133. this.armLocation.x = x;
  7134. this.armLocation.y = y;
  7135. this.armLocation.z = z;
  7136. this.calculateCameraOrientation();
  7137. };
  7138. /**
  7139. * Set the rotation of the camera arm
  7140. * @param {Number} horizontal The horizontal rotation, between 0 and 2*PI.
  7141. * Optional, can be left undefined.
  7142. * @param {Number} vertical The vertical rotation, between 0 and 0.5*PI
  7143. * if vertical=0.5*PI, the graph is shown from the
  7144. * top. Optional, can be left undefined.
  7145. */
  7146. Camera.prototype.setArmRotation = function(horizontal, vertical) {
  7147. if (horizontal !== undefined) {
  7148. this.armRotation.horizontal = horizontal;
  7149. }
  7150. if (vertical !== undefined) {
  7151. this.armRotation.vertical = vertical;
  7152. if (this.armRotation.vertical < 0) this.armRotation.vertical = 0;
  7153. if (this.armRotation.vertical > 0.5*Math.PI) this.armRotation.vertical = 0.5*Math.PI;
  7154. }
  7155. if (horizontal !== undefined || vertical !== undefined) {
  7156. this.calculateCameraOrientation();
  7157. }
  7158. };
  7159. /**
  7160. * Retrieve the current arm rotation
  7161. * @return {object} An object with parameters horizontal and vertical
  7162. */
  7163. Camera.prototype.getArmRotation = function() {
  7164. var rot = {};
  7165. rot.horizontal = this.armRotation.horizontal;
  7166. rot.vertical = this.armRotation.vertical;
  7167. return rot;
  7168. };
  7169. /**
  7170. * Set the (normalized) length of the camera arm.
  7171. * @param {Number} length A length between 0.71 and 5.0
  7172. */
  7173. Camera.prototype.setArmLength = function(length) {
  7174. if (length === undefined)
  7175. return;
  7176. this.armLength = length;
  7177. // Radius must be larger than the corner of the graph,
  7178. // which has a distance of sqrt(0.5^2+0.5^2) = 0.71 from the center of the
  7179. // graph
  7180. if (this.armLength < 0.71) this.armLength = 0.71;
  7181. if (this.armLength > 5.0) this.armLength = 5.0;
  7182. this.calculateCameraOrientation();
  7183. };
  7184. /**
  7185. * Retrieve the arm length
  7186. * @return {Number} length
  7187. */
  7188. Camera.prototype.getArmLength = function() {
  7189. return this.armLength;
  7190. };
  7191. /**
  7192. * Retrieve the camera location
  7193. * @return {Point3d} cameraLocation
  7194. */
  7195. Camera.prototype.getCameraLocation = function() {
  7196. return this.cameraLocation;
  7197. };
  7198. /**
  7199. * Retrieve the camera rotation
  7200. * @return {Point3d} cameraRotation
  7201. */
  7202. Camera.prototype.getCameraRotation = function() {
  7203. return this.cameraRotation;
  7204. };
  7205. /**
  7206. * Calculate the location and rotation of the camera based on the
  7207. * position and orientation of the camera arm
  7208. */
  7209. Camera.prototype.calculateCameraOrientation = function() {
  7210. // calculate location of the camera
  7211. this.cameraLocation.x = this.armLocation.x - this.armLength * Math.sin(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  7212. this.cameraLocation.y = this.armLocation.y - this.armLength * Math.cos(this.armRotation.horizontal) * Math.cos(this.armRotation.vertical);
  7213. this.cameraLocation.z = this.armLocation.z + this.armLength * Math.sin(this.armRotation.vertical);
  7214. // calculate rotation of the camera
  7215. this.cameraRotation.x = Math.PI/2 - this.armRotation.vertical;
  7216. this.cameraRotation.y = 0;
  7217. this.cameraRotation.z = -this.armRotation.horizontal;
  7218. };
  7219. module.exports = Camera;
  7220. /***/ },
  7221. /* 15 */
  7222. /***/ function(module, exports, __webpack_require__) {
  7223. var DataView = __webpack_require__(4);
  7224. /**
  7225. * @class Filter
  7226. *
  7227. * @param {DataSet} data The google data table
  7228. * @param {Number} column The index of the column to be filtered
  7229. * @param {Graph} graph The graph
  7230. */
  7231. function Filter (data, column, graph) {
  7232. this.data = data;
  7233. this.column = column;
  7234. this.graph = graph; // the parent graph
  7235. this.index = undefined;
  7236. this.value = undefined;
  7237. // read all distinct values and select the first one
  7238. this.values = graph.getDistinctValues(data.get(), this.column);
  7239. // sort both numeric and string values correctly
  7240. this.values.sort(function (a, b) {
  7241. return a > b ? 1 : a < b ? -1 : 0;
  7242. });
  7243. if (this.values.length > 0) {
  7244. this.selectValue(0);
  7245. }
  7246. // create an array with the filtered datapoints. this will be loaded afterwards
  7247. this.dataPoints = [];
  7248. this.loaded = false;
  7249. this.onLoadCallback = undefined;
  7250. if (graph.animationPreload) {
  7251. this.loaded = false;
  7252. this.loadInBackground();
  7253. }
  7254. else {
  7255. this.loaded = true;
  7256. }
  7257. };
  7258. /**
  7259. * Return the label
  7260. * @return {string} label
  7261. */
  7262. Filter.prototype.isLoaded = function() {
  7263. return this.loaded;
  7264. };
  7265. /**
  7266. * Return the loaded progress
  7267. * @return {Number} percentage between 0 and 100
  7268. */
  7269. Filter.prototype.getLoadedProgress = function() {
  7270. var len = this.values.length;
  7271. var i = 0;
  7272. while (this.dataPoints[i]) {
  7273. i++;
  7274. }
  7275. return Math.round(i / len * 100);
  7276. };
  7277. /**
  7278. * Return the label
  7279. * @return {string} label
  7280. */
  7281. Filter.prototype.getLabel = function() {
  7282. return this.graph.filterLabel;
  7283. };
  7284. /**
  7285. * Return the columnIndex of the filter
  7286. * @return {Number} columnIndex
  7287. */
  7288. Filter.prototype.getColumn = function() {
  7289. return this.column;
  7290. };
  7291. /**
  7292. * Return the currently selected value. Returns undefined if there is no selection
  7293. * @return {*} value
  7294. */
  7295. Filter.prototype.getSelectedValue = function() {
  7296. if (this.index === undefined)
  7297. return undefined;
  7298. return this.values[this.index];
  7299. };
  7300. /**
  7301. * Retrieve all values of the filter
  7302. * @return {Array} values
  7303. */
  7304. Filter.prototype.getValues = function() {
  7305. return this.values;
  7306. };
  7307. /**
  7308. * Retrieve one value of the filter
  7309. * @param {Number} index
  7310. * @return {*} value
  7311. */
  7312. Filter.prototype.getValue = function(index) {
  7313. if (index >= this.values.length)
  7314. throw 'Error: index out of range';
  7315. return this.values[index];
  7316. };
  7317. /**
  7318. * Retrieve the (filtered) dataPoints for the currently selected filter index
  7319. * @param {Number} [index] (optional)
  7320. * @return {Array} dataPoints
  7321. */
  7322. Filter.prototype._getDataPoints = function(index) {
  7323. if (index === undefined)
  7324. index = this.index;
  7325. if (index === undefined)
  7326. return [];
  7327. var dataPoints;
  7328. if (this.dataPoints[index]) {
  7329. dataPoints = this.dataPoints[index];
  7330. }
  7331. else {
  7332. var f = {};
  7333. f.column = this.column;
  7334. f.value = this.values[index];
  7335. var dataView = new DataView(this.data,{filter: function (item) {return (item[f.column] == f.value);}}).get();
  7336. dataPoints = this.graph._getDataPoints(dataView);
  7337. this.dataPoints[index] = dataPoints;
  7338. }
  7339. return dataPoints;
  7340. };
  7341. /**
  7342. * Set a callback function when the filter is fully loaded.
  7343. */
  7344. Filter.prototype.setOnLoadCallback = function(callback) {
  7345. this.onLoadCallback = callback;
  7346. };
  7347. /**
  7348. * Add a value to the list with available values for this filter
  7349. * No double entries will be created.
  7350. * @param {Number} index
  7351. */
  7352. Filter.prototype.selectValue = function(index) {
  7353. if (index >= this.values.length)
  7354. throw 'Error: index out of range';
  7355. this.index = index;
  7356. this.value = this.values[index];
  7357. };
  7358. /**
  7359. * Load all filtered rows in the background one by one
  7360. * Start this method without providing an index!
  7361. */
  7362. Filter.prototype.loadInBackground = function(index) {
  7363. if (index === undefined)
  7364. index = 0;
  7365. var frame = this.graph.frame;
  7366. if (index < this.values.length) {
  7367. var dataPointsTemp = this._getDataPoints(index);
  7368. //this.graph.redrawInfo(); // TODO: not neat
  7369. // create a progress box
  7370. if (frame.progress === undefined) {
  7371. frame.progress = document.createElement('DIV');
  7372. frame.progress.style.position = 'absolute';
  7373. frame.progress.style.color = 'gray';
  7374. frame.appendChild(frame.progress);
  7375. }
  7376. var progress = this.getLoadedProgress();
  7377. frame.progress.innerHTML = 'Loading animation... ' + progress + '%';
  7378. // TODO: this is no nice solution...
  7379. frame.progress.style.bottom = 60 + 'px'; // TODO: use height of slider
  7380. frame.progress.style.left = 10 + 'px';
  7381. var me = this;
  7382. setTimeout(function() {me.loadInBackground(index+1);}, 10);
  7383. this.loaded = false;
  7384. }
  7385. else {
  7386. this.loaded = true;
  7387. // remove the progress box
  7388. if (frame.progress !== undefined) {
  7389. frame.removeChild(frame.progress);
  7390. frame.progress = undefined;
  7391. }
  7392. if (this.onLoadCallback)
  7393. this.onLoadCallback();
  7394. }
  7395. };
  7396. module.exports = Filter;
  7397. /***/ },
  7398. /* 16 */
  7399. /***/ function(module, exports, __webpack_require__) {
  7400. /**
  7401. * @prototype Point2d
  7402. * @param {Number} [x]
  7403. * @param {Number} [y]
  7404. */
  7405. Point2d = function (x, y) {
  7406. this.x = x !== undefined ? x : 0;
  7407. this.y = y !== undefined ? y : 0;
  7408. };
  7409. module.exports = Point2d;
  7410. /***/ },
  7411. /* 17 */
  7412. /***/ function(module, exports, __webpack_require__) {
  7413. var util = __webpack_require__(1);
  7414. /**
  7415. * @constructor Slider
  7416. *
  7417. * An html slider control with start/stop/prev/next buttons
  7418. * @param {Element} container The element where the slider will be created
  7419. * @param {Object} options Available options:
  7420. * {boolean} visible If true (default) the
  7421. * slider is visible.
  7422. */
  7423. function Slider(container, options) {
  7424. if (container === undefined) {
  7425. throw 'Error: No container element defined';
  7426. }
  7427. this.container = container;
  7428. this.visible = (options && options.visible != undefined) ? options.visible : true;
  7429. if (this.visible) {
  7430. this.frame = document.createElement('DIV');
  7431. //this.frame.style.backgroundColor = '#E5E5E5';
  7432. this.frame.style.width = '100%';
  7433. this.frame.style.position = 'relative';
  7434. this.container.appendChild(this.frame);
  7435. this.frame.prev = document.createElement('INPUT');
  7436. this.frame.prev.type = 'BUTTON';
  7437. this.frame.prev.value = 'Prev';
  7438. this.frame.appendChild(this.frame.prev);
  7439. this.frame.play = document.createElement('INPUT');
  7440. this.frame.play.type = 'BUTTON';
  7441. this.frame.play.value = 'Play';
  7442. this.frame.appendChild(this.frame.play);
  7443. this.frame.next = document.createElement('INPUT');
  7444. this.frame.next.type = 'BUTTON';
  7445. this.frame.next.value = 'Next';
  7446. this.frame.appendChild(this.frame.next);
  7447. this.frame.bar = document.createElement('INPUT');
  7448. this.frame.bar.type = 'BUTTON';
  7449. this.frame.bar.style.position = 'absolute';
  7450. this.frame.bar.style.border = '1px solid red';
  7451. this.frame.bar.style.width = '100px';
  7452. this.frame.bar.style.height = '6px';
  7453. this.frame.bar.style.borderRadius = '2px';
  7454. this.frame.bar.style.MozBorderRadius = '2px';
  7455. this.frame.bar.style.border = '1px solid #7F7F7F';
  7456. this.frame.bar.style.backgroundColor = '#E5E5E5';
  7457. this.frame.appendChild(this.frame.bar);
  7458. this.frame.slide = document.createElement('INPUT');
  7459. this.frame.slide.type = 'BUTTON';
  7460. this.frame.slide.style.margin = '0px';
  7461. this.frame.slide.value = ' ';
  7462. this.frame.slide.style.position = 'relative';
  7463. this.frame.slide.style.left = '-100px';
  7464. this.frame.appendChild(this.frame.slide);
  7465. // create events
  7466. var me = this;
  7467. this.frame.slide.onmousedown = function (event) {me._onMouseDown(event);};
  7468. this.frame.prev.onclick = function (event) {me.prev(event);};
  7469. this.frame.play.onclick = function (event) {me.togglePlay(event);};
  7470. this.frame.next.onclick = function (event) {me.next(event);};
  7471. }
  7472. this.onChangeCallback = undefined;
  7473. this.values = [];
  7474. this.index = undefined;
  7475. this.playTimeout = undefined;
  7476. this.playInterval = 1000; // milliseconds
  7477. this.playLoop = true;
  7478. }
  7479. /**
  7480. * Select the previous index
  7481. */
  7482. Slider.prototype.prev = function() {
  7483. var index = this.getIndex();
  7484. if (index > 0) {
  7485. index--;
  7486. this.setIndex(index);
  7487. }
  7488. };
  7489. /**
  7490. * Select the next index
  7491. */
  7492. Slider.prototype.next = function() {
  7493. var index = this.getIndex();
  7494. if (index < this.values.length - 1) {
  7495. index++;
  7496. this.setIndex(index);
  7497. }
  7498. };
  7499. /**
  7500. * Select the next index
  7501. */
  7502. Slider.prototype.playNext = function() {
  7503. var start = new Date();
  7504. var index = this.getIndex();
  7505. if (index < this.values.length - 1) {
  7506. index++;
  7507. this.setIndex(index);
  7508. }
  7509. else if (this.playLoop) {
  7510. // jump to the start
  7511. index = 0;
  7512. this.setIndex(index);
  7513. }
  7514. var end = new Date();
  7515. var diff = (end - start);
  7516. // calculate how much time it to to set the index and to execute the callback
  7517. // function.
  7518. var interval = Math.max(this.playInterval - diff, 0);
  7519. // document.title = diff // TODO: cleanup
  7520. var me = this;
  7521. this.playTimeout = setTimeout(function() {me.playNext();}, interval);
  7522. };
  7523. /**
  7524. * Toggle start or stop playing
  7525. */
  7526. Slider.prototype.togglePlay = function() {
  7527. if (this.playTimeout === undefined) {
  7528. this.play();
  7529. } else {
  7530. this.stop();
  7531. }
  7532. };
  7533. /**
  7534. * Start playing
  7535. */
  7536. Slider.prototype.play = function() {
  7537. // Test whether already playing
  7538. if (this.playTimeout) return;
  7539. this.playNext();
  7540. if (this.frame) {
  7541. this.frame.play.value = 'Stop';
  7542. }
  7543. };
  7544. /**
  7545. * Stop playing
  7546. */
  7547. Slider.prototype.stop = function() {
  7548. clearInterval(this.playTimeout);
  7549. this.playTimeout = undefined;
  7550. if (this.frame) {
  7551. this.frame.play.value = 'Play';
  7552. }
  7553. };
  7554. /**
  7555. * Set a callback function which will be triggered when the value of the
  7556. * slider bar has changed.
  7557. */
  7558. Slider.prototype.setOnChangeCallback = function(callback) {
  7559. this.onChangeCallback = callback;
  7560. };
  7561. /**
  7562. * Set the interval for playing the list
  7563. * @param {Number} interval The interval in milliseconds
  7564. */
  7565. Slider.prototype.setPlayInterval = function(interval) {
  7566. this.playInterval = interval;
  7567. };
  7568. /**
  7569. * Retrieve the current play interval
  7570. * @return {Number} interval The interval in milliseconds
  7571. */
  7572. Slider.prototype.getPlayInterval = function(interval) {
  7573. return this.playInterval;
  7574. };
  7575. /**
  7576. * Set looping on or off
  7577. * @pararm {boolean} doLoop If true, the slider will jump to the start when
  7578. * the end is passed, and will jump to the end
  7579. * when the start is passed.
  7580. */
  7581. Slider.prototype.setPlayLoop = function(doLoop) {
  7582. this.playLoop = doLoop;
  7583. };
  7584. /**
  7585. * Execute the onchange callback function
  7586. */
  7587. Slider.prototype.onChange = function() {
  7588. if (this.onChangeCallback !== undefined) {
  7589. this.onChangeCallback();
  7590. }
  7591. };
  7592. /**
  7593. * redraw the slider on the correct place
  7594. */
  7595. Slider.prototype.redraw = function() {
  7596. if (this.frame) {
  7597. // resize the bar
  7598. this.frame.bar.style.top = (this.frame.clientHeight/2 -
  7599. this.frame.bar.offsetHeight/2) + 'px';
  7600. this.frame.bar.style.width = (this.frame.clientWidth -
  7601. this.frame.prev.clientWidth -
  7602. this.frame.play.clientWidth -
  7603. this.frame.next.clientWidth - 30) + 'px';
  7604. // position the slider button
  7605. var left = this.indexToLeft(this.index);
  7606. this.frame.slide.style.left = (left) + 'px';
  7607. }
  7608. };
  7609. /**
  7610. * Set the list with values for the slider
  7611. * @param {Array} values A javascript array with values (any type)
  7612. */
  7613. Slider.prototype.setValues = function(values) {
  7614. this.values = values;
  7615. if (this.values.length > 0)
  7616. this.setIndex(0);
  7617. else
  7618. this.index = undefined;
  7619. };
  7620. /**
  7621. * Select a value by its index
  7622. * @param {Number} index
  7623. */
  7624. Slider.prototype.setIndex = function(index) {
  7625. if (index < this.values.length) {
  7626. this.index = index;
  7627. this.redraw();
  7628. this.onChange();
  7629. }
  7630. else {
  7631. throw 'Error: index out of range';
  7632. }
  7633. };
  7634. /**
  7635. * retrieve the index of the currently selected vaue
  7636. * @return {Number} index
  7637. */
  7638. Slider.prototype.getIndex = function() {
  7639. return this.index;
  7640. };
  7641. /**
  7642. * retrieve the currently selected value
  7643. * @return {*} value
  7644. */
  7645. Slider.prototype.get = function() {
  7646. return this.values[this.index];
  7647. };
  7648. Slider.prototype._onMouseDown = function(event) {
  7649. // only react on left mouse button down
  7650. var leftButtonDown = event.which ? (event.which === 1) : (event.button === 1);
  7651. if (!leftButtonDown) return;
  7652. this.startClientX = event.clientX;
  7653. this.startSlideX = parseFloat(this.frame.slide.style.left);
  7654. this.frame.style.cursor = 'move';
  7655. // add event listeners to handle moving the contents
  7656. // we store the function onmousemove and onmouseup in the graph, so we can
  7657. // remove the eventlisteners lateron in the function mouseUp()
  7658. var me = this;
  7659. this.onmousemove = function (event) {me._onMouseMove(event);};
  7660. this.onmouseup = function (event) {me._onMouseUp(event);};
  7661. util.addEventListener(document, 'mousemove', this.onmousemove);
  7662. util.addEventListener(document, 'mouseup', this.onmouseup);
  7663. util.preventDefault(event);
  7664. };
  7665. Slider.prototype.leftToIndex = function (left) {
  7666. var width = parseFloat(this.frame.bar.style.width) -
  7667. this.frame.slide.clientWidth - 10;
  7668. var x = left - 3;
  7669. var index = Math.round(x / width * (this.values.length-1));
  7670. if (index < 0) index = 0;
  7671. if (index > this.values.length-1) index = this.values.length-1;
  7672. return index;
  7673. };
  7674. Slider.prototype.indexToLeft = function (index) {
  7675. var width = parseFloat(this.frame.bar.style.width) -
  7676. this.frame.slide.clientWidth - 10;
  7677. var x = index / (this.values.length-1) * width;
  7678. var left = x + 3;
  7679. return left;
  7680. };
  7681. Slider.prototype._onMouseMove = function (event) {
  7682. var diff = event.clientX - this.startClientX;
  7683. var x = this.startSlideX + diff;
  7684. var index = this.leftToIndex(x);
  7685. this.setIndex(index);
  7686. util.preventDefault();
  7687. };
  7688. Slider.prototype._onMouseUp = function (event) {
  7689. this.frame.style.cursor = 'auto';
  7690. // remove event listeners
  7691. util.removeEventListener(document, 'mousemove', this.onmousemove);
  7692. util.removeEventListener(document, 'mouseup', this.onmouseup);
  7693. util.preventDefault();
  7694. };
  7695. module.exports = Slider;
  7696. /***/ },
  7697. /* 18 */
  7698. /***/ function(module, exports, __webpack_require__) {
  7699. /**
  7700. * @prototype Point3d
  7701. * @param {Number} [x]
  7702. * @param {Number} [y]
  7703. * @param {Number} [z]
  7704. */
  7705. function Point3d(x, y, z) {
  7706. this.x = x !== undefined ? x : 0;
  7707. this.y = y !== undefined ? y : 0;
  7708. this.z = z !== undefined ? z : 0;
  7709. };
  7710. /**
  7711. * Subtract the two provided points, returns a-b
  7712. * @param {Point3d} a
  7713. * @param {Point3d} b
  7714. * @return {Point3d} a-b
  7715. */
  7716. Point3d.subtract = function(a, b) {
  7717. var sub = new Point3d();
  7718. sub.x = a.x - b.x;
  7719. sub.y = a.y - b.y;
  7720. sub.z = a.z - b.z;
  7721. return sub;
  7722. };
  7723. /**
  7724. * Add the two provided points, returns a+b
  7725. * @param {Point3d} a
  7726. * @param {Point3d} b
  7727. * @return {Point3d} a+b
  7728. */
  7729. Point3d.add = function(a, b) {
  7730. var sum = new Point3d();
  7731. sum.x = a.x + b.x;
  7732. sum.y = a.y + b.y;
  7733. sum.z = a.z + b.z;
  7734. return sum;
  7735. };
  7736. /**
  7737. * Calculate the average of two 3d points
  7738. * @param {Point3d} a
  7739. * @param {Point3d} b
  7740. * @return {Point3d} The average, (a+b)/2
  7741. */
  7742. Point3d.avg = function(a, b) {
  7743. return new Point3d(
  7744. (a.x + b.x) / 2,
  7745. (a.y + b.y) / 2,
  7746. (a.z + b.z) / 2
  7747. );
  7748. };
  7749. /**
  7750. * Calculate the cross product of the two provided points, returns axb
  7751. * Documentation: http://en.wikipedia.org/wiki/Cross_product
  7752. * @param {Point3d} a
  7753. * @param {Point3d} b
  7754. * @return {Point3d} cross product axb
  7755. */
  7756. Point3d.crossProduct = function(a, b) {
  7757. var crossproduct = new Point3d();
  7758. crossproduct.x = a.y * b.z - a.z * b.y;
  7759. crossproduct.y = a.z * b.x - a.x * b.z;
  7760. crossproduct.z = a.x * b.y - a.y * b.x;
  7761. return crossproduct;
  7762. };
  7763. /**
  7764. * Rtrieve the length of the vector (or the distance from this point to the origin
  7765. * @return {Number} length
  7766. */
  7767. Point3d.prototype.length = function() {
  7768. return Math.sqrt(
  7769. this.x * this.x +
  7770. this.y * this.y +
  7771. this.z * this.z
  7772. );
  7773. };
  7774. module.exports = Point3d;
  7775. /***/ },
  7776. /* 19 */
  7777. /***/ function(module, exports, __webpack_require__) {
  7778. /**
  7779. * @prototype StepNumber
  7780. * The class StepNumber is an iterator for Numbers. You provide a start and end
  7781. * value, and a best step size. StepNumber itself rounds to fixed values and
  7782. * a finds the step that best fits the provided step.
  7783. *
  7784. * If prettyStep is true, the step size is chosen as close as possible to the
  7785. * provided step, but being a round value like 1, 2, 5, 10, 20, 50, ....
  7786. *
  7787. * Example usage:
  7788. * var step = new StepNumber(0, 10, 2.5, true);
  7789. * step.start();
  7790. * while (!step.end()) {
  7791. * alert(step.getCurrent());
  7792. * step.next();
  7793. * }
  7794. *
  7795. * Version: 1.0
  7796. *
  7797. * @param {Number} start The start value
  7798. * @param {Number} end The end value
  7799. * @param {Number} step Optional. Step size. Must be a positive value.
  7800. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  7801. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  7802. */
  7803. function StepNumber(start, end, step, prettyStep) {
  7804. // set default values
  7805. this._start = 0;
  7806. this._end = 0;
  7807. this._step = 1;
  7808. this.prettyStep = true;
  7809. this.precision = 5;
  7810. this._current = 0;
  7811. this.setRange(start, end, step, prettyStep);
  7812. };
  7813. /**
  7814. * Set a new range: start, end and step.
  7815. *
  7816. * @param {Number} start The start value
  7817. * @param {Number} end The end value
  7818. * @param {Number} step Optional. Step size. Must be a positive value.
  7819. * @param {boolean} prettyStep Optional. If true, the step size is rounded
  7820. * To a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  7821. */
  7822. StepNumber.prototype.setRange = function(start, end, step, prettyStep) {
  7823. this._start = start ? start : 0;
  7824. this._end = end ? end : 0;
  7825. this.setStep(step, prettyStep);
  7826. };
  7827. /**
  7828. * Set a new step size
  7829. * @param {Number} step New step size. Must be a positive value
  7830. * @param {boolean} prettyStep Optional. If true, the provided step is rounded
  7831. * to a pretty step size (like 1, 2, 5, 10, 20, 50, ...)
  7832. */
  7833. StepNumber.prototype.setStep = function(step, prettyStep) {
  7834. if (step === undefined || step <= 0)
  7835. return;
  7836. if (prettyStep !== undefined)
  7837. this.prettyStep = prettyStep;
  7838. if (this.prettyStep === true)
  7839. this._step = StepNumber.calculatePrettyStep(step);
  7840. else
  7841. this._step = step;
  7842. };
  7843. /**
  7844. * Calculate a nice step size, closest to the desired step size.
  7845. * Returns a value in one of the ranges 1*10^n, 2*10^n, or 5*10^n, where n is an
  7846. * integer Number. For example 1, 2, 5, 10, 20, 50, etc...
  7847. * @param {Number} step Desired step size
  7848. * @return {Number} Nice step size
  7849. */
  7850. StepNumber.calculatePrettyStep = function (step) {
  7851. var log10 = function (x) {return Math.log(x) / Math.LN10;};
  7852. // try three steps (multiple of 1, 2, or 5
  7853. var step1 = Math.pow(10, Math.round(log10(step))),
  7854. step2 = 2 * Math.pow(10, Math.round(log10(step / 2))),
  7855. step5 = 5 * Math.pow(10, Math.round(log10(step / 5)));
  7856. // choose the best step (closest to minimum step)
  7857. var prettyStep = step1;
  7858. if (Math.abs(step2 - step) <= Math.abs(prettyStep - step)) prettyStep = step2;
  7859. if (Math.abs(step5 - step) <= Math.abs(prettyStep - step)) prettyStep = step5;
  7860. // for safety
  7861. if (prettyStep <= 0) {
  7862. prettyStep = 1;
  7863. }
  7864. return prettyStep;
  7865. };
  7866. /**
  7867. * returns the current value of the step
  7868. * @return {Number} current value
  7869. */
  7870. StepNumber.prototype.getCurrent = function () {
  7871. return parseFloat(this._current.toPrecision(this.precision));
  7872. };
  7873. /**
  7874. * returns the current step size
  7875. * @return {Number} current step size
  7876. */
  7877. StepNumber.prototype.getStep = function () {
  7878. return this._step;
  7879. };
  7880. /**
  7881. * Set the current value to the largest value smaller than start, which
  7882. * is a multiple of the step size
  7883. */
  7884. StepNumber.prototype.start = function() {
  7885. this._current = this._start - this._start % this._step;
  7886. };
  7887. /**
  7888. * Do a step, add the step size to the current value
  7889. */
  7890. StepNumber.prototype.next = function () {
  7891. this._current += this._step;
  7892. };
  7893. /**
  7894. * Returns true whether the end is reached
  7895. * @return {boolean} True if the current value has passed the end value.
  7896. */
  7897. StepNumber.prototype.end = function () {
  7898. return (this._current > this._end);
  7899. };
  7900. module.exports = StepNumber;
  7901. /***/ },
  7902. /* 20 */
  7903. /***/ function(module, exports, __webpack_require__) {
  7904. /**
  7905. * Prototype for visual components
  7906. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} [body]
  7907. * @param {Object} [options]
  7908. */
  7909. function Component (body, options) {
  7910. this.options = null;
  7911. this.props = null;
  7912. }
  7913. /**
  7914. * Set options for the component. The new options will be merged into the
  7915. * current options.
  7916. * @param {Object} options
  7917. */
  7918. Component.prototype.setOptions = function(options) {
  7919. if (options) {
  7920. util.extend(this.options, options);
  7921. }
  7922. };
  7923. /**
  7924. * Repaint the component
  7925. * @return {boolean} Returns true if the component is resized
  7926. */
  7927. Component.prototype.redraw = function() {
  7928. // should be implemented by the component
  7929. return false;
  7930. };
  7931. /**
  7932. * Destroy the component. Cleanup DOM and event listeners
  7933. */
  7934. Component.prototype.destroy = function() {
  7935. // should be implemented by the component
  7936. };
  7937. /**
  7938. * Test whether the component is resized since the last time _isResized() was
  7939. * called.
  7940. * @return {Boolean} Returns true if the component is resized
  7941. * @protected
  7942. */
  7943. Component.prototype._isResized = function() {
  7944. var resized = (this.props._previousWidth !== this.props.width ||
  7945. this.props._previousHeight !== this.props.height);
  7946. this.props._previousWidth = this.props.width;
  7947. this.props._previousHeight = this.props.height;
  7948. return resized;
  7949. };
  7950. module.exports = Component;
  7951. /***/ },
  7952. /* 21 */
  7953. /***/ function(module, exports, __webpack_require__) {
  7954. var util = __webpack_require__(1);
  7955. var Component = __webpack_require__(20);
  7956. var moment = __webpack_require__(44);
  7957. var locales = __webpack_require__(48);
  7958. /**
  7959. * A current time bar
  7960. * @param {{range: Range, dom: Object, domProps: Object}} body
  7961. * @param {Object} [options] Available parameters:
  7962. * {Boolean} [showCurrentTime]
  7963. * @constructor CurrentTime
  7964. * @extends Component
  7965. */
  7966. function CurrentTime (body, options) {
  7967. this.body = body;
  7968. // default options
  7969. this.defaultOptions = {
  7970. showCurrentTime: true,
  7971. locales: locales,
  7972. locale: 'en'
  7973. };
  7974. this.options = util.extend({}, this.defaultOptions);
  7975. this.offset = 0;
  7976. this._create();
  7977. this.setOptions(options);
  7978. }
  7979. CurrentTime.prototype = new Component();
  7980. /**
  7981. * Create the HTML DOM for the current time bar
  7982. * @private
  7983. */
  7984. CurrentTime.prototype._create = function() {
  7985. var bar = document.createElement('div');
  7986. bar.className = 'currenttime';
  7987. bar.style.position = 'absolute';
  7988. bar.style.top = '0px';
  7989. bar.style.height = '100%';
  7990. this.bar = bar;
  7991. };
  7992. /**
  7993. * Destroy the CurrentTime bar
  7994. */
  7995. CurrentTime.prototype.destroy = function () {
  7996. this.options.showCurrentTime = false;
  7997. this.redraw(); // will remove the bar from the DOM and stop refreshing
  7998. this.body = null;
  7999. };
  8000. /**
  8001. * Set options for the component. Options will be merged in current options.
  8002. * @param {Object} options Available parameters:
  8003. * {boolean} [showCurrentTime]
  8004. */
  8005. CurrentTime.prototype.setOptions = function(options) {
  8006. if (options) {
  8007. // copy all options that we know
  8008. util.selectiveExtend(['showCurrentTime', 'locale', 'locales'], this.options, options);
  8009. }
  8010. };
  8011. /**
  8012. * Repaint the component
  8013. * @return {boolean} Returns true if the component is resized
  8014. */
  8015. CurrentTime.prototype.redraw = function() {
  8016. if (this.options.showCurrentTime) {
  8017. var parent = this.body.dom.backgroundVertical;
  8018. if (this.bar.parentNode != parent) {
  8019. // attach to the dom
  8020. if (this.bar.parentNode) {
  8021. this.bar.parentNode.removeChild(this.bar);
  8022. }
  8023. parent.appendChild(this.bar);
  8024. this.start();
  8025. }
  8026. var now = new Date(new Date().valueOf() + this.offset);
  8027. var x = this.body.util.toScreen(now);
  8028. var locale = this.options.locales[this.options.locale];
  8029. var title = locale.current + ' ' + locale.time + ': ' + moment(now).format('dddd, MMMM Do YYYY, H:mm:ss');
  8030. title = title.charAt(0).toUpperCase() + title.substring(1);
  8031. this.bar.style.left = x + 'px';
  8032. this.bar.title = title;
  8033. }
  8034. else {
  8035. // remove the line from the DOM
  8036. if (this.bar.parentNode) {
  8037. this.bar.parentNode.removeChild(this.bar);
  8038. }
  8039. this.stop();
  8040. }
  8041. return false;
  8042. };
  8043. /**
  8044. * Start auto refreshing the current time bar
  8045. */
  8046. CurrentTime.prototype.start = function() {
  8047. var me = this;
  8048. function update () {
  8049. me.stop();
  8050. // determine interval to refresh
  8051. var scale = me.body.range.conversion(me.body.domProps.center.width).scale;
  8052. var interval = 1 / scale / 10;
  8053. if (interval < 30) interval = 30;
  8054. if (interval > 1000) interval = 1000;
  8055. me.redraw();
  8056. // start a timer to adjust for the new time
  8057. me.currentTimeTimer = setTimeout(update, interval);
  8058. }
  8059. update();
  8060. };
  8061. /**
  8062. * Stop auto refreshing the current time bar
  8063. */
  8064. CurrentTime.prototype.stop = function() {
  8065. if (this.currentTimeTimer !== undefined) {
  8066. clearTimeout(this.currentTimeTimer);
  8067. delete this.currentTimeTimer;
  8068. }
  8069. };
  8070. /**
  8071. * Set a current time. This can be used for example to ensure that a client's
  8072. * time is synchronized with a shared server time.
  8073. * @param {Date | String | Number} time A Date, unix timestamp, or
  8074. * ISO date string.
  8075. */
  8076. CurrentTime.prototype.setCurrentTime = function(time) {
  8077. var t = util.convert(time, 'Date').valueOf();
  8078. var now = new Date().valueOf();
  8079. this.offset = t - now;
  8080. this.redraw();
  8081. };
  8082. /**
  8083. * Get the current time.
  8084. * @return {Date} Returns the current time.
  8085. */
  8086. CurrentTime.prototype.getCurrentTime = function() {
  8087. return new Date(new Date().valueOf() + this.offset);
  8088. };
  8089. module.exports = CurrentTime;
  8090. /***/ },
  8091. /* 22 */
  8092. /***/ function(module, exports, __webpack_require__) {
  8093. var Hammer = __webpack_require__(45);
  8094. var util = __webpack_require__(1);
  8095. var Component = __webpack_require__(20);
  8096. var moment = __webpack_require__(44);
  8097. var locales = __webpack_require__(48);
  8098. /**
  8099. * A custom time bar
  8100. * @param {{range: Range, dom: Object}} body
  8101. * @param {Object} [options] Available parameters:
  8102. * {Boolean} [showCustomTime]
  8103. * @constructor CustomTime
  8104. * @extends Component
  8105. */
  8106. function CustomTime (body, options) {
  8107. this.body = body;
  8108. // default options
  8109. this.defaultOptions = {
  8110. showCustomTime: false,
  8111. locales: locales,
  8112. locale: 'en'
  8113. };
  8114. this.options = util.extend({}, this.defaultOptions);
  8115. this.customTime = new Date();
  8116. this.eventParams = {}; // stores state parameters while dragging the bar
  8117. // create the DOM
  8118. this._create();
  8119. this.setOptions(options);
  8120. }
  8121. CustomTime.prototype = new Component();
  8122. /**
  8123. * Set options for the component. Options will be merged in current options.
  8124. * @param {Object} options Available parameters:
  8125. * {boolean} [showCustomTime]
  8126. */
  8127. CustomTime.prototype.setOptions = function(options) {
  8128. if (options) {
  8129. // copy all options that we know
  8130. util.selectiveExtend(['showCustomTime', 'locale', 'locales'], this.options, options);
  8131. }
  8132. };
  8133. /**
  8134. * Create the DOM for the custom time
  8135. * @private
  8136. */
  8137. CustomTime.prototype._create = function() {
  8138. var bar = document.createElement('div');
  8139. bar.className = 'customtime';
  8140. bar.style.position = 'absolute';
  8141. bar.style.top = '0px';
  8142. bar.style.height = '100%';
  8143. this.bar = bar;
  8144. var drag = document.createElement('div');
  8145. drag.style.position = 'relative';
  8146. drag.style.top = '0px';
  8147. drag.style.left = '-10px';
  8148. drag.style.height = '100%';
  8149. drag.style.width = '20px';
  8150. bar.appendChild(drag);
  8151. // attach event listeners
  8152. this.hammer = Hammer(bar, {
  8153. prevent_default: true
  8154. });
  8155. this.hammer.on('dragstart', this._onDragStart.bind(this));
  8156. this.hammer.on('drag', this._onDrag.bind(this));
  8157. this.hammer.on('dragend', this._onDragEnd.bind(this));
  8158. };
  8159. /**
  8160. * Destroy the CustomTime bar
  8161. */
  8162. CustomTime.prototype.destroy = function () {
  8163. this.options.showCustomTime = false;
  8164. this.redraw(); // will remove the bar from the DOM
  8165. this.hammer.enable(false);
  8166. this.hammer = null;
  8167. this.body = null;
  8168. };
  8169. /**
  8170. * Repaint the component
  8171. * @return {boolean} Returns true if the component is resized
  8172. */
  8173. CustomTime.prototype.redraw = function () {
  8174. if (this.options.showCustomTime) {
  8175. var parent = this.body.dom.backgroundVertical;
  8176. if (this.bar.parentNode != parent) {
  8177. // attach to the dom
  8178. if (this.bar.parentNode) {
  8179. this.bar.parentNode.removeChild(this.bar);
  8180. }
  8181. parent.appendChild(this.bar);
  8182. }
  8183. var x = this.body.util.toScreen(this.customTime);
  8184. var locale = this.options.locales[this.options.locale];
  8185. var title = locale.time + ': ' + moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
  8186. title = title.charAt(0).toUpperCase() + title.substring(1);
  8187. this.bar.style.left = x + 'px';
  8188. this.bar.title = title;
  8189. }
  8190. else {
  8191. // remove the line from the DOM
  8192. if (this.bar.parentNode) {
  8193. this.bar.parentNode.removeChild(this.bar);
  8194. }
  8195. }
  8196. return false;
  8197. };
  8198. /**
  8199. * Set custom time.
  8200. * @param {Date | number | string} time
  8201. */
  8202. CustomTime.prototype.setCustomTime = function(time) {
  8203. this.customTime = util.convert(time, 'Date');
  8204. this.redraw();
  8205. };
  8206. /**
  8207. * Retrieve the current custom time.
  8208. * @return {Date} customTime
  8209. */
  8210. CustomTime.prototype.getCustomTime = function() {
  8211. return new Date(this.customTime.valueOf());
  8212. };
  8213. /**
  8214. * Start moving horizontally
  8215. * @param {Event} event
  8216. * @private
  8217. */
  8218. CustomTime.prototype._onDragStart = function(event) {
  8219. this.eventParams.dragging = true;
  8220. this.eventParams.customTime = this.customTime;
  8221. event.stopPropagation();
  8222. event.preventDefault();
  8223. };
  8224. /**
  8225. * Perform moving operating.
  8226. * @param {Event} event
  8227. * @private
  8228. */
  8229. CustomTime.prototype._onDrag = function (event) {
  8230. if (!this.eventParams.dragging) return;
  8231. var deltaX = event.gesture.deltaX,
  8232. x = this.body.util.toScreen(this.eventParams.customTime) + deltaX,
  8233. time = this.body.util.toTime(x);
  8234. this.setCustomTime(time);
  8235. // fire a timechange event
  8236. this.body.emitter.emit('timechange', {
  8237. time: new Date(this.customTime.valueOf())
  8238. });
  8239. event.stopPropagation();
  8240. event.preventDefault();
  8241. };
  8242. /**
  8243. * Stop moving operating.
  8244. * @param {event} event
  8245. * @private
  8246. */
  8247. CustomTime.prototype._onDragEnd = function (event) {
  8248. if (!this.eventParams.dragging) return;
  8249. // fire a timechanged event
  8250. this.body.emitter.emit('timechanged', {
  8251. time: new Date(this.customTime.valueOf())
  8252. });
  8253. event.stopPropagation();
  8254. event.preventDefault();
  8255. };
  8256. module.exports = CustomTime;
  8257. /***/ },
  8258. /* 23 */
  8259. /***/ function(module, exports, __webpack_require__) {
  8260. var util = __webpack_require__(1);
  8261. var DOMutil = __webpack_require__(2);
  8262. var Component = __webpack_require__(20);
  8263. var DataStep = __webpack_require__(9);
  8264. /**
  8265. * A horizontal time axis
  8266. * @param {Object} [options] See DataAxis.setOptions for the available
  8267. * options.
  8268. * @constructor DataAxis
  8269. * @extends Component
  8270. * @param body
  8271. */
  8272. function DataAxis (body, options, svg, linegraphOptions) {
  8273. this.id = util.randomUUID();
  8274. this.body = body;
  8275. this.defaultOptions = {
  8276. orientation: 'left', // supported: 'left', 'right'
  8277. showMinorLabels: true,
  8278. showMajorLabels: true,
  8279. icons: true,
  8280. majorLinesOffset: 7,
  8281. minorLinesOffset: 4,
  8282. labelOffsetX: 10,
  8283. labelOffsetY: 2,
  8284. iconWidth: 20,
  8285. width: '40px',
  8286. visible: true,
  8287. customRange: {
  8288. left: {min:undefined, max:undefined},
  8289. right: {min:undefined, max:undefined}
  8290. }
  8291. };
  8292. this.linegraphOptions = linegraphOptions;
  8293. this.linegraphSVG = svg;
  8294. this.props = {};
  8295. this.DOMelements = { // dynamic elements
  8296. lines: {},
  8297. labels: {}
  8298. };
  8299. this.dom = {};
  8300. this.range = {start:0, end:0};
  8301. this.options = util.extend({}, this.defaultOptions);
  8302. this.conversionFactor = 1;
  8303. this.setOptions(options);
  8304. this.width = Number(('' + this.options.width).replace("px",""));
  8305. this.minWidth = this.width;
  8306. this.height = this.linegraphSVG.offsetHeight;
  8307. this.stepPixels = 25;
  8308. this.stepPixelsForced = 25;
  8309. this.lineOffset = 0;
  8310. this.master = true;
  8311. this.svgElements = {};
  8312. this.groups = {};
  8313. this.amountOfGroups = 0;
  8314. // create the HTML DOM
  8315. this._create();
  8316. }
  8317. DataAxis.prototype = new Component();
  8318. DataAxis.prototype.addGroup = function(label, graphOptions) {
  8319. if (!this.groups.hasOwnProperty(label)) {
  8320. this.groups[label] = graphOptions;
  8321. }
  8322. this.amountOfGroups += 1;
  8323. };
  8324. DataAxis.prototype.updateGroup = function(label, graphOptions) {
  8325. this.groups[label] = graphOptions;
  8326. };
  8327. DataAxis.prototype.removeGroup = function(label) {
  8328. if (this.groups.hasOwnProperty(label)) {
  8329. delete this.groups[label];
  8330. this.amountOfGroups -= 1;
  8331. }
  8332. };
  8333. DataAxis.prototype.setOptions = function (options) {
  8334. if (options) {
  8335. var redraw = false;
  8336. if (this.options.orientation != options.orientation && options.orientation !== undefined) {
  8337. redraw = true;
  8338. }
  8339. var fields = [
  8340. 'orientation',
  8341. 'showMinorLabels',
  8342. 'showMajorLabels',
  8343. 'icons',
  8344. 'majorLinesOffset',
  8345. 'minorLinesOffset',
  8346. 'labelOffsetX',
  8347. 'labelOffsetY',
  8348. 'iconWidth',
  8349. 'width',
  8350. 'visible',
  8351. 'customRange'
  8352. ];
  8353. util.selectiveExtend(fields, this.options, options);
  8354. this.minWidth = Number(('' + this.options.width).replace("px",""));
  8355. if (redraw == true && this.dom.frame) {
  8356. this.hide();
  8357. this.show();
  8358. }
  8359. }
  8360. };
  8361. /**
  8362. * Create the HTML DOM for the DataAxis
  8363. */
  8364. DataAxis.prototype._create = function() {
  8365. this.dom.frame = document.createElement('div');
  8366. this.dom.frame.style.width = this.options.width;
  8367. this.dom.frame.style.height = this.height;
  8368. this.dom.lineContainer = document.createElement('div');
  8369. this.dom.lineContainer.style.width = '100%';
  8370. this.dom.lineContainer.style.height = this.height;
  8371. // create svg element for graph drawing.
  8372. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  8373. this.svg.style.position = "absolute";
  8374. this.svg.style.top = '0px';
  8375. this.svg.style.height = '100%';
  8376. this.svg.style.width = '100%';
  8377. this.svg.style.display = "block";
  8378. this.dom.frame.appendChild(this.svg);
  8379. };
  8380. DataAxis.prototype._redrawGroupIcons = function () {
  8381. DOMutil.prepareElements(this.svgElements);
  8382. var x;
  8383. var iconWidth = this.options.iconWidth;
  8384. var iconHeight = 15;
  8385. var iconOffset = 4;
  8386. var y = iconOffset + 0.5 * iconHeight;
  8387. if (this.options.orientation == 'left') {
  8388. x = iconOffset;
  8389. }
  8390. else {
  8391. x = this.width - iconWidth - iconOffset;
  8392. }
  8393. for (var groupId in this.groups) {
  8394. if (this.groups.hasOwnProperty(groupId)) {
  8395. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  8396. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  8397. y += iconHeight + iconOffset;
  8398. }
  8399. }
  8400. }
  8401. DOMutil.cleanupElements(this.svgElements);
  8402. };
  8403. /**
  8404. * Create the HTML DOM for the DataAxis
  8405. */
  8406. DataAxis.prototype.show = function() {
  8407. if (!this.dom.frame.parentNode) {
  8408. if (this.options.orientation == 'left') {
  8409. this.body.dom.left.appendChild(this.dom.frame);
  8410. }
  8411. else {
  8412. this.body.dom.right.appendChild(this.dom.frame);
  8413. }
  8414. }
  8415. if (!this.dom.lineContainer.parentNode) {
  8416. this.body.dom.backgroundHorizontal.appendChild(this.dom.lineContainer);
  8417. }
  8418. };
  8419. /**
  8420. * Create the HTML DOM for the DataAxis
  8421. */
  8422. DataAxis.prototype.hide = function() {
  8423. if (this.dom.frame.parentNode) {
  8424. this.dom.frame.parentNode.removeChild(this.dom.frame);
  8425. }
  8426. if (this.dom.lineContainer.parentNode) {
  8427. this.dom.lineContainer.parentNode.removeChild(this.dom.lineContainer);
  8428. }
  8429. };
  8430. /**
  8431. * Set a range (start and end)
  8432. * @param end
  8433. * @param start
  8434. * @param end
  8435. */
  8436. DataAxis.prototype.setRange = function (start, end) {
  8437. this.range.start = start;
  8438. this.range.end = end;
  8439. };
  8440. /**
  8441. * Repaint the component
  8442. * @return {boolean} Returns true if the component is resized
  8443. */
  8444. DataAxis.prototype.redraw = function () {
  8445. var changeCalled = false;
  8446. var activeGroups = 0;
  8447. for (var groupId in this.groups) {
  8448. if (this.groups.hasOwnProperty(groupId)) {
  8449. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  8450. activeGroups++;
  8451. }
  8452. }
  8453. }
  8454. if (this.amountOfGroups == 0 || activeGroups == 0) {
  8455. this.hide();
  8456. }
  8457. else {
  8458. this.show();
  8459. this.height = Number(this.linegraphSVG.style.height.replace("px",""));
  8460. // svg offsetheight did not work in firefox and explorer...
  8461. this.dom.lineContainer.style.height = this.height + 'px';
  8462. this.width = this.options.visible == true ? Number(('' + this.options.width).replace("px","")) : 0;
  8463. var props = this.props;
  8464. var frame = this.dom.frame;
  8465. // update classname
  8466. frame.className = 'dataaxis';
  8467. // calculate character width and height
  8468. this._calculateCharSize();
  8469. var orientation = this.options.orientation;
  8470. var showMinorLabels = this.options.showMinorLabels;
  8471. var showMajorLabels = this.options.showMajorLabels;
  8472. // determine the width and height of the elemens for the axis
  8473. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  8474. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  8475. props.minorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.minorLinesOffset;
  8476. props.minorLineHeight = 1;
  8477. props.majorLineWidth = this.body.dom.backgroundHorizontal.offsetWidth - this.lineOffset - this.width + 2 * this.options.majorLinesOffset;
  8478. props.majorLineHeight = 1;
  8479. // take frame offline while updating (is almost twice as fast)
  8480. if (orientation == 'left') {
  8481. frame.style.top = '0';
  8482. frame.style.left = '0';
  8483. frame.style.bottom = '';
  8484. frame.style.width = this.width + 'px';
  8485. frame.style.height = this.height + "px";
  8486. }
  8487. else { // right
  8488. frame.style.top = '';
  8489. frame.style.bottom = '0';
  8490. frame.style.left = '0';
  8491. frame.style.width = this.width + 'px';
  8492. frame.style.height = this.height + "px";
  8493. }
  8494. changeCalled = this._redrawLabels();
  8495. if (this.options.icons == true) {
  8496. this._redrawGroupIcons();
  8497. }
  8498. }
  8499. return changeCalled;
  8500. };
  8501. /**
  8502. * Repaint major and minor text labels and vertical grid lines
  8503. * @private
  8504. */
  8505. DataAxis.prototype._redrawLabels = function () {
  8506. DOMutil.prepareElements(this.DOMelements.lines);
  8507. DOMutil.prepareElements(this.DOMelements.labels);
  8508. var orientation = this.options['orientation'];
  8509. // calculate range and step (step such that we have space for 7 characters per label)
  8510. var minimumStep = this.master ? this.props.majorCharHeight || 10 : this.stepPixelsForced;
  8511. var step = new DataStep(this.range.start, this.range.end, minimumStep, this.dom.frame.offsetHeight, this.options.customRange[this.options.orientation]);
  8512. this.step = step;
  8513. // get the distance in pixels for a step
  8514. // dead space is space that is "left over" after a step
  8515. var stepPixels = (this.dom.frame.offsetHeight - (step.deadSpace * (this.dom.frame.offsetHeight / step.marginRange))) / (((step.marginRange - step.deadSpace) / step.step));
  8516. this.stepPixels = stepPixels;
  8517. var amountOfSteps = this.height / stepPixels;
  8518. var stepDifference = 0;
  8519. if (this.master == false) {
  8520. stepPixels = this.stepPixelsForced;
  8521. stepDifference = Math.round((this.dom.frame.offsetHeight / stepPixels) - amountOfSteps);
  8522. for (var i = 0; i < 0.5 * stepDifference; i++) {
  8523. step.previous();
  8524. }
  8525. amountOfSteps = this.height / stepPixels;
  8526. }
  8527. else {
  8528. amountOfSteps += 0.25;
  8529. }
  8530. this.valueAtZero = step.marginEnd;
  8531. var marginStartPos = 0;
  8532. // do not draw the first label
  8533. var max = 1;
  8534. this.maxLabelSize = 0;
  8535. var y = 0;
  8536. while (max < Math.round(amountOfSteps)) {
  8537. step.next();
  8538. y = Math.round(max * stepPixels);
  8539. marginStartPos = max * stepPixels;
  8540. var isMajor = step.isMajor();
  8541. if (this.options['showMinorLabels'] && isMajor == false || this.master == false && this.options['showMinorLabels'] == true) {
  8542. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis minor', this.props.minorCharHeight);
  8543. }
  8544. if (isMajor && this.options['showMajorLabels'] && this.master == true ||
  8545. this.options['showMinorLabels'] == false && this.master == false && isMajor == true) {
  8546. if (y >= 0) {
  8547. this._redrawLabel(y - 2, step.getCurrent(), orientation, 'yAxis major', this.props.majorCharHeight);
  8548. }
  8549. this._redrawLine(y, orientation, 'grid horizontal major', this.options.majorLinesOffset, this.props.majorLineWidth);
  8550. }
  8551. else {
  8552. this._redrawLine(y, orientation, 'grid horizontal minor', this.options.minorLinesOffset, this.props.minorLineWidth);
  8553. }
  8554. max++;
  8555. }
  8556. if (this.master == false) {
  8557. this.conversionFactor = y / (this.valueAtZero - step.current);
  8558. }
  8559. else {
  8560. this.conversionFactor = this.dom.frame.offsetHeight / step.marginRange;
  8561. }
  8562. var offset = this.options.icons == true ? this.options.iconWidth + this.options.labelOffsetX + 15 : this.options.labelOffsetX + 15;
  8563. // this will resize the yAxis to accomodate the labels.
  8564. if (this.maxLabelSize > (this.width - offset) && this.options.visible == true) {
  8565. this.width = this.maxLabelSize + offset;
  8566. this.options.width = this.width + "px";
  8567. DOMutil.cleanupElements(this.DOMelements.lines);
  8568. DOMutil.cleanupElements(this.DOMelements.labels);
  8569. this.redraw();
  8570. return true;
  8571. }
  8572. // this will resize the yAxis if it is too big for the labels.
  8573. else if (this.maxLabelSize < (this.width - offset) && this.options.visible == true && this.width > this.minWidth) {
  8574. this.width = Math.max(this.minWidth,this.maxLabelSize + offset);
  8575. this.options.width = this.width + "px";
  8576. DOMutil.cleanupElements(this.DOMelements.lines);
  8577. DOMutil.cleanupElements(this.DOMelements.labels);
  8578. this.redraw();
  8579. return true;
  8580. }
  8581. else {
  8582. DOMutil.cleanupElements(this.DOMelements.lines);
  8583. DOMutil.cleanupElements(this.DOMelements.labels);
  8584. return false;
  8585. }
  8586. };
  8587. DataAxis.prototype.convertValue = function (value) {
  8588. var invertedValue = this.valueAtZero - value;
  8589. var convertedValue = invertedValue * this.conversionFactor;
  8590. return convertedValue;
  8591. };
  8592. /**
  8593. * Create a label for the axis at position x
  8594. * @private
  8595. * @param y
  8596. * @param text
  8597. * @param orientation
  8598. * @param className
  8599. * @param characterHeight
  8600. */
  8601. DataAxis.prototype._redrawLabel = function (y, text, orientation, className, characterHeight) {
  8602. // reuse redundant label
  8603. var label = DOMutil.getDOMElement('div',this.DOMelements.labels, this.dom.frame); //this.dom.redundant.labels.shift();
  8604. label.className = className;
  8605. label.innerHTML = text;
  8606. if (orientation == 'left') {
  8607. label.style.left = '-' + this.options.labelOffsetX + 'px';
  8608. label.style.textAlign = "right";
  8609. }
  8610. else {
  8611. label.style.right = '-' + this.options.labelOffsetX + 'px';
  8612. label.style.textAlign = "left";
  8613. }
  8614. label.style.top = y - 0.5 * characterHeight + this.options.labelOffsetY + 'px';
  8615. text += '';
  8616. var largestWidth = Math.max(this.props.majorCharWidth,this.props.minorCharWidth);
  8617. if (this.maxLabelSize < text.length * largestWidth) {
  8618. this.maxLabelSize = text.length * largestWidth;
  8619. }
  8620. };
  8621. /**
  8622. * Create a minor line for the axis at position y
  8623. * @param y
  8624. * @param orientation
  8625. * @param className
  8626. * @param offset
  8627. * @param width
  8628. */
  8629. DataAxis.prototype._redrawLine = function (y, orientation, className, offset, width) {
  8630. if (this.master == true) {
  8631. var line = DOMutil.getDOMElement('div',this.DOMelements.lines, this.dom.lineContainer);//this.dom.redundant.lines.shift();
  8632. line.className = className;
  8633. line.innerHTML = '';
  8634. if (orientation == 'left') {
  8635. line.style.left = (this.width - offset) + 'px';
  8636. }
  8637. else {
  8638. line.style.right = (this.width - offset) + 'px';
  8639. }
  8640. line.style.width = width + 'px';
  8641. line.style.top = y + 'px';
  8642. }
  8643. };
  8644. /**
  8645. * Determine the size of text on the axis (both major and minor axis).
  8646. * The size is calculated only once and then cached in this.props.
  8647. * @private
  8648. */
  8649. DataAxis.prototype._calculateCharSize = function () {
  8650. // determine the char width and height on the minor axis
  8651. if (!('minorCharHeight' in this.props)) {
  8652. var textMinor = document.createTextNode('0');
  8653. var measureCharMinor = document.createElement('DIV');
  8654. measureCharMinor.className = 'yAxis minor measure';
  8655. measureCharMinor.appendChild(textMinor);
  8656. this.dom.frame.appendChild(measureCharMinor);
  8657. this.props.minorCharHeight = measureCharMinor.clientHeight;
  8658. this.props.minorCharWidth = measureCharMinor.clientWidth;
  8659. this.dom.frame.removeChild(measureCharMinor);
  8660. }
  8661. if (!('majorCharHeight' in this.props)) {
  8662. var textMajor = document.createTextNode('0');
  8663. var measureCharMajor = document.createElement('DIV');
  8664. measureCharMajor.className = 'yAxis major measure';
  8665. measureCharMajor.appendChild(textMajor);
  8666. this.dom.frame.appendChild(measureCharMajor);
  8667. this.props.majorCharHeight = measureCharMajor.clientHeight;
  8668. this.props.majorCharWidth = measureCharMajor.clientWidth;
  8669. this.dom.frame.removeChild(measureCharMajor);
  8670. }
  8671. };
  8672. /**
  8673. * Snap a date to a rounded value.
  8674. * The snap intervals are dependent on the current scale and step.
  8675. * @param {Date} date the date to be snapped.
  8676. * @return {Date} snappedDate
  8677. */
  8678. DataAxis.prototype.snap = function(date) {
  8679. return this.step.snap(date);
  8680. };
  8681. module.exports = DataAxis;
  8682. /***/ },
  8683. /* 24 */
  8684. /***/ function(module, exports, __webpack_require__) {
  8685. var util = __webpack_require__(1);
  8686. var stack = __webpack_require__(11);
  8687. var RangeItem = __webpack_require__(35);
  8688. /**
  8689. * @constructor Group
  8690. * @param {Number | String} groupId
  8691. * @param {Object} data
  8692. * @param {ItemSet} itemSet
  8693. */
  8694. function Group (groupId, data, itemSet) {
  8695. this.groupId = groupId;
  8696. this.subgroups = {};
  8697. this.subgroupIndex = 0;
  8698. this.subgroupOrderer = data && data.subgroupOrder;
  8699. this.itemSet = itemSet;
  8700. this.dom = {};
  8701. this.props = {
  8702. label: {
  8703. width: 0,
  8704. height: 0
  8705. }
  8706. };
  8707. this.className = null;
  8708. this.items = {}; // items filtered by groupId of this group
  8709. this.visibleItems = []; // items currently visible in window
  8710. this.orderedItems = { // items sorted by start and by end
  8711. byStart: [],
  8712. byEnd: []
  8713. };
  8714. this._create();
  8715. this.setData(data);
  8716. }
  8717. /**
  8718. * Create DOM elements for the group
  8719. * @private
  8720. */
  8721. Group.prototype._create = function() {
  8722. var label = document.createElement('div');
  8723. label.className = 'vlabel';
  8724. this.dom.label = label;
  8725. var inner = document.createElement('div');
  8726. inner.className = 'inner';
  8727. label.appendChild(inner);
  8728. this.dom.inner = inner;
  8729. var foreground = document.createElement('div');
  8730. foreground.className = 'group';
  8731. foreground['timeline-group'] = this;
  8732. this.dom.foreground = foreground;
  8733. this.dom.background = document.createElement('div');
  8734. this.dom.background.className = 'group';
  8735. this.dom.axis = document.createElement('div');
  8736. this.dom.axis.className = 'group';
  8737. // create a hidden marker to detect when the Timelines container is attached
  8738. // to the DOM, or the style of a parent of the Timeline is changed from
  8739. // display:none is changed to visible.
  8740. this.dom.marker = document.createElement('div');
  8741. this.dom.marker.style.visibility = 'hidden'; // TODO: ask jos why this is not none?
  8742. this.dom.marker.innerHTML = '?';
  8743. this.dom.background.appendChild(this.dom.marker);
  8744. };
  8745. /**
  8746. * Set the group data for this group
  8747. * @param {Object} data Group data, can contain properties content and className
  8748. */
  8749. Group.prototype.setData = function(data) {
  8750. // update contents
  8751. var content = data && data.content;
  8752. if (content instanceof Element) {
  8753. this.dom.inner.appendChild(content);
  8754. }
  8755. else if (content !== undefined && content !== null) {
  8756. this.dom.inner.innerHTML = content;
  8757. }
  8758. else {
  8759. this.dom.inner.innerHTML = this.groupId || ''; // groupId can be null
  8760. }
  8761. // update title
  8762. this.dom.label.title = data && data.title || '';
  8763. if (!this.dom.inner.firstChild) {
  8764. util.addClassName(this.dom.inner, 'hidden');
  8765. }
  8766. else {
  8767. util.removeClassName(this.dom.inner, 'hidden');
  8768. }
  8769. // update className
  8770. var className = data && data.className || null;
  8771. if (className != this.className) {
  8772. if (this.className) {
  8773. util.removeClassName(this.dom.label, this.className);
  8774. util.removeClassName(this.dom.foreground, this.className);
  8775. util.removeClassName(this.dom.background, this.className);
  8776. util.removeClassName(this.dom.axis, this.className);
  8777. }
  8778. util.addClassName(this.dom.label, className);
  8779. util.addClassName(this.dom.foreground, className);
  8780. util.addClassName(this.dom.background, className);
  8781. util.addClassName(this.dom.axis, className);
  8782. this.className = className;
  8783. }
  8784. // update style
  8785. if (this.style) {
  8786. util.removeCssText(this.dom.label, this.style);
  8787. this.style = null;
  8788. }
  8789. if (data && data.style) {
  8790. util.addCssText(this.dom.label, data.style);
  8791. this.style = data.style;
  8792. }
  8793. };
  8794. /**
  8795. * Get the width of the group label
  8796. * @return {number} width
  8797. */
  8798. Group.prototype.getLabelWidth = function() {
  8799. return this.props.label.width;
  8800. };
  8801. /**
  8802. * Repaint this group
  8803. * @param {{start: number, end: number}} range
  8804. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  8805. * @param {boolean} [restack=false] Force restacking of all items
  8806. * @return {boolean} Returns true if the group is resized
  8807. */
  8808. Group.prototype.redraw = function(range, margin, restack) {
  8809. var resized = false;
  8810. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  8811. // force recalculation of the height of the items when the marker height changed
  8812. // (due to the Timeline being attached to the DOM or changed from display:none to visible)
  8813. var markerHeight = this.dom.marker.clientHeight;
  8814. if (markerHeight != this.lastMarkerHeight) {
  8815. this.lastMarkerHeight = markerHeight;
  8816. util.forEach(this.items, function (item) {
  8817. item.dirty = true;
  8818. if (item.displayed) item.redraw();
  8819. });
  8820. restack = true;
  8821. }
  8822. // reposition visible items vertically
  8823. if (this.itemSet.options.stack) { // TODO: ugly way to access options...
  8824. stack.stack(this.visibleItems, margin, restack);
  8825. }
  8826. else { // no stacking
  8827. stack.nostack(this.visibleItems, margin, this.subgroups);
  8828. }
  8829. // recalculate the height of the group
  8830. var height = this._calculateHeight(margin);
  8831. // calculate actual size and position
  8832. var foreground = this.dom.foreground;
  8833. this.top = foreground.offsetTop;
  8834. this.left = foreground.offsetLeft;
  8835. this.width = foreground.offsetWidth;
  8836. resized = util.updateProperty(this, 'height', height) || resized;
  8837. // recalculate size of label
  8838. resized = util.updateProperty(this.props.label, 'width', this.dom.inner.clientWidth) || resized;
  8839. resized = util.updateProperty(this.props.label, 'height', this.dom.inner.clientHeight) || resized;
  8840. // apply new height
  8841. this.dom.background.style.height = height + 'px';
  8842. this.dom.foreground.style.height = height + 'px';
  8843. this.dom.label.style.height = height + 'px';
  8844. // update vertical position of items after they are re-stacked and the height of the group is calculated
  8845. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  8846. var item = this.visibleItems[i];
  8847. item.repositionY(margin);
  8848. }
  8849. return resized;
  8850. };
  8851. /**
  8852. * recalculate the height of the group
  8853. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  8854. * @returns {number} Returns the height
  8855. * @private
  8856. */
  8857. Group.prototype._calculateHeight = function (margin) {
  8858. // recalculate the height of the group
  8859. var height;
  8860. var visibleItems = this.visibleItems;
  8861. //var visibleSubgroups = [];
  8862. //this.visibleSubgroups = 0;
  8863. this.resetSubgroups();
  8864. var me = this;
  8865. if (visibleItems.length) {
  8866. var min = visibleItems[0].top;
  8867. var max = visibleItems[0].top + visibleItems[0].height;
  8868. util.forEach(visibleItems, function (item) {
  8869. min = Math.min(min, item.top);
  8870. max = Math.max(max, (item.top + item.height));
  8871. if (item.data.subgroup !== undefined) {
  8872. me.subgroups[item.data.subgroup].height = Math.max(me.subgroups[item.data.subgroup].height,item.height);
  8873. me.subgroups[item.data.subgroup].visible = true;
  8874. //if (visibleSubgroups.indexOf(item.data.subgroup) == -1){
  8875. // visibleSubgroups.push(item.data.subgroup);
  8876. // me.visibleSubgroups += 1;
  8877. //}
  8878. }
  8879. });
  8880. if (min > margin.axis) {
  8881. // there is an empty gap between the lowest item and the axis
  8882. var offset = min - margin.axis;
  8883. max -= offset;
  8884. util.forEach(visibleItems, function (item) {
  8885. item.top -= offset;
  8886. });
  8887. }
  8888. height = max + margin.item.vertical / 2;
  8889. }
  8890. else {
  8891. height = margin.axis + margin.item.vertical;
  8892. }
  8893. height = Math.max(height, this.props.label.height);
  8894. return height;
  8895. };
  8896. /**
  8897. * Show this group: attach to the DOM
  8898. */
  8899. Group.prototype.show = function() {
  8900. if (!this.dom.label.parentNode) {
  8901. this.itemSet.dom.labelSet.appendChild(this.dom.label);
  8902. }
  8903. if (!this.dom.foreground.parentNode) {
  8904. this.itemSet.dom.foreground.appendChild(this.dom.foreground);
  8905. }
  8906. if (!this.dom.background.parentNode) {
  8907. this.itemSet.dom.background.appendChild(this.dom.background);
  8908. }
  8909. if (!this.dom.axis.parentNode) {
  8910. this.itemSet.dom.axis.appendChild(this.dom.axis);
  8911. }
  8912. };
  8913. /**
  8914. * Hide this group: remove from the DOM
  8915. */
  8916. Group.prototype.hide = function() {
  8917. var label = this.dom.label;
  8918. if (label.parentNode) {
  8919. label.parentNode.removeChild(label);
  8920. }
  8921. var foreground = this.dom.foreground;
  8922. if (foreground.parentNode) {
  8923. foreground.parentNode.removeChild(foreground);
  8924. }
  8925. var background = this.dom.background;
  8926. if (background.parentNode) {
  8927. background.parentNode.removeChild(background);
  8928. }
  8929. var axis = this.dom.axis;
  8930. if (axis.parentNode) {
  8931. axis.parentNode.removeChild(axis);
  8932. }
  8933. };
  8934. /**
  8935. * Add an item to the group
  8936. * @param {Item} item
  8937. */
  8938. Group.prototype.add = function(item) {
  8939. this.items[item.id] = item;
  8940. item.setParent(this);
  8941. // add to
  8942. if (item.data.subgroup !== undefined) {
  8943. if (this.subgroups[item.data.subgroup] === undefined) {
  8944. this.subgroups[item.data.subgroup] = {height:0, visible: false, index:this.subgroupIndex, items: []};
  8945. this.subgroupIndex++;
  8946. }
  8947. this.subgroups[item.data.subgroup].items.push(item);
  8948. }
  8949. this.orderSubgroups();
  8950. if (this.visibleItems.indexOf(item) == -1) {
  8951. var range = this.itemSet.body.range; // TODO: not nice accessing the range like this
  8952. this._checkIfVisible(item, this.visibleItems, range);
  8953. }
  8954. };
  8955. Group.prototype.orderSubgroups = function() {
  8956. if (this.subgroupOrderer !== undefined) {
  8957. var sortArray = [];
  8958. if (typeof this.subgroupOrderer == 'string') {
  8959. for (var subgroup in this.subgroups) {
  8960. sortArray.push({subgroup: subgroup, sortField: this.subgroups[subgroup].items[0].data[this.subgroupOrderer]})
  8961. }
  8962. sortArray.sort(function (a, b) {
  8963. return a.sortField - b.sortField;
  8964. })
  8965. }
  8966. else if (typeof this.subgroupOrderer == 'function') {
  8967. for (var subgroup in this.subgroups) {
  8968. sortArray.push(this.subgroups[subgroup].items[0].data);
  8969. }
  8970. sortArray.sort(this.subgroupOrderer);
  8971. }
  8972. if (sortArray.length > 0) {
  8973. for (var i = 0; i < sortArray.length; i++) {
  8974. this.subgroups[sortArray[i].subgroup].index = i;
  8975. }
  8976. }
  8977. }
  8978. };
  8979. Group.prototype.resetSubgroups = function() {
  8980. for (var subgroup in this.subgroups) {
  8981. if (this.subgroups.hasOwnProperty(subgroup)) {
  8982. this.subgroups[subgroup].visible = false;
  8983. }
  8984. }
  8985. };
  8986. /**
  8987. * Remove an item from the group
  8988. * @param {Item} item
  8989. */
  8990. Group.prototype.remove = function(item) {
  8991. delete this.items[item.id];
  8992. item.setParent(null);
  8993. // remove from visible items
  8994. var index = this.visibleItems.indexOf(item);
  8995. if (index != -1) this.visibleItems.splice(index, 1);
  8996. // TODO: also remove from ordered items?
  8997. };
  8998. /**
  8999. * Remove an item from the corresponding DataSet
  9000. * @param {Item} item
  9001. */
  9002. Group.prototype.removeFromDataSet = function(item) {
  9003. this.itemSet.removeItem(item.id);
  9004. };
  9005. /**
  9006. * Reorder the items
  9007. */
  9008. Group.prototype.order = function() {
  9009. var array = util.toArray(this.items);
  9010. this.orderedItems.byStart = array;
  9011. this.orderedItems.byEnd = this._constructByEndArray(array);
  9012. stack.orderByStart(this.orderedItems.byStart);
  9013. stack.orderByEnd(this.orderedItems.byEnd);
  9014. };
  9015. /**
  9016. * Create an array containing all items being a range (having an end date)
  9017. * @param {Item[]} array
  9018. * @returns {RangeItem[]}
  9019. * @private
  9020. */
  9021. Group.prototype._constructByEndArray = function(array) {
  9022. var endArray = [];
  9023. for (var i = 0; i < array.length; i++) {
  9024. if (array[i] instanceof RangeItem) {
  9025. endArray.push(array[i]);
  9026. }
  9027. }
  9028. return endArray;
  9029. };
  9030. /**
  9031. * Update the visible items
  9032. * @param {{byStart: Item[], byEnd: Item[]}} orderedItems All items ordered by start date and by end date
  9033. * @param {Item[]} visibleItems The previously visible items.
  9034. * @param {{start: number, end: number}} range Visible range
  9035. * @return {Item[]} visibleItems The new visible items.
  9036. * @private
  9037. */
  9038. Group.prototype._updateVisibleItems = function(orderedItems, visibleItems, range) {
  9039. var initialPosByStart,
  9040. newVisibleItems = [],
  9041. i;
  9042. // first check if the items that were in view previously are still in view.
  9043. // this handles the case for the RangeItem that is both before and after the current one.
  9044. if (visibleItems.length > 0) {
  9045. for (i = 0; i < visibleItems.length; i++) {
  9046. this._checkIfVisible(visibleItems[i], newVisibleItems, range);
  9047. }
  9048. }
  9049. // If there were no visible items previously, use binarySearch to find a visible PointItem or RangeItem (based on startTime)
  9050. if (newVisibleItems.length == 0) {
  9051. initialPosByStart = util.binarySearch(orderedItems.byStart, range, 'data','start');
  9052. }
  9053. else {
  9054. initialPosByStart = orderedItems.byStart.indexOf(newVisibleItems[0]);
  9055. }
  9056. // use visible search to find a visible RangeItem (only based on endTime)
  9057. var initialPosByEnd = util.binarySearch(orderedItems.byEnd, range, 'data','end');
  9058. // if we found a initial ID to use, trace it up and down until we meet an invisible item.
  9059. if (initialPosByStart != -1) {
  9060. for (i = initialPosByStart; i >= 0; i--) {
  9061. if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems, range)) {break;}
  9062. }
  9063. for (i = initialPosByStart + 1; i < orderedItems.byStart.length; i++) {
  9064. if (this._checkIfInvisible(orderedItems.byStart[i], newVisibleItems, range)) {break;}
  9065. }
  9066. }
  9067. // if we found a initial ID to use, trace it up and down until we meet an invisible item.
  9068. if (initialPosByEnd != -1) {
  9069. for (i = initialPosByEnd; i >= 0; i--) {
  9070. if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems, range)) {break;}
  9071. }
  9072. for (i = initialPosByEnd + 1; i < orderedItems.byEnd.length; i++) {
  9073. if (this._checkIfInvisible(orderedItems.byEnd[i], newVisibleItems, range)) {break;}
  9074. }
  9075. }
  9076. return newVisibleItems;
  9077. };
  9078. /**
  9079. * this function checks if an item is invisible. If it is NOT we make it visible
  9080. * and add it to the global visible items. If it is, return true.
  9081. *
  9082. * @param {Item} item
  9083. * @param {Item[]} visibleItems
  9084. * @param {{start:number, end:number}} range
  9085. * @returns {boolean}
  9086. * @private
  9087. */
  9088. Group.prototype._checkIfInvisible = function(item, visibleItems, range) {
  9089. if (item.isVisible(range)) {
  9090. if (!item.displayed) item.show();
  9091. item.repositionX();
  9092. if (visibleItems.indexOf(item) == -1) {
  9093. visibleItems.push(item);
  9094. }
  9095. return false;
  9096. }
  9097. else {
  9098. if (item.displayed) item.hide();
  9099. return true;
  9100. }
  9101. };
  9102. /**
  9103. * this function is very similar to the _checkIfInvisible() but it does not
  9104. * return booleans, hides the item if it should not be seen and always adds to
  9105. * the visibleItems.
  9106. * this one is for brute forcing and hiding.
  9107. *
  9108. * @param {Item} item
  9109. * @param {Array} visibleItems
  9110. * @param {{start:number, end:number}} range
  9111. * @private
  9112. */
  9113. Group.prototype._checkIfVisible = function(item, visibleItems, range) {
  9114. if (item.isVisible(range)) {
  9115. if (!item.displayed) item.show();
  9116. // reposition item horizontally
  9117. item.repositionX();
  9118. visibleItems.push(item);
  9119. }
  9120. else {
  9121. if (item.displayed) item.hide();
  9122. }
  9123. };
  9124. module.exports = Group;
  9125. /***/ },
  9126. /* 25 */
  9127. /***/ function(module, exports, __webpack_require__) {
  9128. var util = __webpack_require__(1);
  9129. var DOMutil = __webpack_require__(2);
  9130. /**
  9131. * @constructor Group
  9132. * @param {Number | String} groupId
  9133. * @param {Object} data
  9134. * @param {ItemSet} itemSet
  9135. */
  9136. function GraphGroup (group, groupId, options, groupsUsingDefaultStyles) {
  9137. this.id = groupId;
  9138. var fields = ['sampling','style','sort','yAxisOrientation','barChart','drawPoints','shaded','catmullRom']
  9139. this.options = util.selectiveBridgeObject(fields,options);
  9140. this.usingDefaultStyle = group.className === undefined;
  9141. this.groupsUsingDefaultStyles = groupsUsingDefaultStyles;
  9142. this.zeroPosition = 0;
  9143. this.update(group);
  9144. if (this.usingDefaultStyle == true) {
  9145. this.groupsUsingDefaultStyles[0] += 1;
  9146. }
  9147. this.itemsData = [];
  9148. this.visible = group.visible === undefined ? true : group.visible;
  9149. }
  9150. GraphGroup.prototype.setItems = function(items) {
  9151. if (items != null) {
  9152. this.itemsData = items;
  9153. if (this.options.sort == true) {
  9154. this.itemsData.sort(function (a,b) {return a.x - b.x;})
  9155. }
  9156. }
  9157. else {
  9158. this.itemsData = [];
  9159. }
  9160. };
  9161. GraphGroup.prototype.setZeroPosition = function(pos) {
  9162. this.zeroPosition = pos;
  9163. };
  9164. GraphGroup.prototype.setOptions = function(options) {
  9165. if (options !== undefined) {
  9166. var fields = ['sampling','style','sort','yAxisOrientation','barChart'];
  9167. util.selectiveDeepExtend(fields, this.options, options);
  9168. util.mergeOptions(this.options, options,'catmullRom');
  9169. util.mergeOptions(this.options, options,'drawPoints');
  9170. util.mergeOptions(this.options, options,'shaded');
  9171. if (options.catmullRom) {
  9172. if (typeof options.catmullRom == 'object') {
  9173. if (options.catmullRom.parametrization) {
  9174. if (options.catmullRom.parametrization == 'uniform') {
  9175. this.options.catmullRom.alpha = 0;
  9176. }
  9177. else if (options.catmullRom.parametrization == 'chordal') {
  9178. this.options.catmullRom.alpha = 1.0;
  9179. }
  9180. else {
  9181. this.options.catmullRom.parametrization = 'centripetal';
  9182. this.options.catmullRom.alpha = 0.5;
  9183. }
  9184. }
  9185. }
  9186. }
  9187. }
  9188. };
  9189. GraphGroup.prototype.update = function(group) {
  9190. this.group = group;
  9191. this.content = group.content || 'graph';
  9192. this.className = group.className || this.className || "graphGroup" + this.groupsUsingDefaultStyles[0] % 10;
  9193. this.visible = group.visible === undefined ? true : group.visible;
  9194. this.setOptions(group.options);
  9195. };
  9196. GraphGroup.prototype.drawIcon = function(x, y, JSONcontainer, SVGcontainer, iconWidth, iconHeight) {
  9197. var fillHeight = iconHeight * 0.5;
  9198. var path, fillPath;
  9199. var outline = DOMutil.getSVGElement("rect", JSONcontainer, SVGcontainer);
  9200. outline.setAttributeNS(null, "x", x);
  9201. outline.setAttributeNS(null, "y", y - fillHeight);
  9202. outline.setAttributeNS(null, "width", iconWidth);
  9203. outline.setAttributeNS(null, "height", 2*fillHeight);
  9204. outline.setAttributeNS(null, "class", "outline");
  9205. if (this.options.style == 'line') {
  9206. path = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  9207. path.setAttributeNS(null, "class", this.className);
  9208. path.setAttributeNS(null, "d", "M" + x + ","+y+" L" + (x + iconWidth) + ","+y+"");
  9209. if (this.options.shaded.enabled == true) {
  9210. fillPath = DOMutil.getSVGElement("path", JSONcontainer, SVGcontainer);
  9211. if (this.options.shaded.orientation == 'top') {
  9212. fillPath.setAttributeNS(null, "d", "M"+x+", " + (y - fillHeight) +
  9213. "L"+x+","+y+" L"+ (x + iconWidth) + ","+y+" L"+ (x + iconWidth) + "," + (y - fillHeight));
  9214. }
  9215. else {
  9216. fillPath.setAttributeNS(null, "d", "M"+x+","+y+" " +
  9217. "L"+x+"," + (y + fillHeight) + " " +
  9218. "L"+ (x + iconWidth) + "," + (y + fillHeight) +
  9219. "L"+ (x + iconWidth) + ","+y);
  9220. }
  9221. fillPath.setAttributeNS(null, "class", this.className + " iconFill");
  9222. }
  9223. if (this.options.drawPoints.enabled == true) {
  9224. DOMutil.drawPoint(x + 0.5 * iconWidth,y, this, JSONcontainer, SVGcontainer);
  9225. }
  9226. }
  9227. else {
  9228. var barWidth = Math.round(0.3 * iconWidth);
  9229. var bar1Height = Math.round(0.4 * iconHeight);
  9230. var bar2Height = Math.round(0.75 * iconHeight);
  9231. var offset = Math.round((iconWidth - (2 * barWidth))/3);
  9232. DOMutil.drawBar(x + 0.5*barWidth + offset , y + fillHeight - bar1Height - 1, barWidth, bar1Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  9233. DOMutil.drawBar(x + 1.5*barWidth + offset + 2, y + fillHeight - bar2Height - 1, barWidth, bar2Height, this.className + ' bar', JSONcontainer, SVGcontainer);
  9234. }
  9235. };
  9236. /**
  9237. *
  9238. * @param iconWidth
  9239. * @param iconHeight
  9240. * @returns {{icon: HTMLElement, label: (group.content|*|string), orientation: (.options.yAxisOrientation|*)}}
  9241. */
  9242. GraphGroup.prototype.getLegend = function(iconWidth, iconHeight) {
  9243. var svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  9244. this.drawIcon(0,0.5*iconHeight,[],svg,iconWidth,iconHeight);
  9245. return {icon: svg, label: this.content, orientation:this.options.yAxisOrientation};
  9246. }
  9247. module.exports = GraphGroup;
  9248. /***/ },
  9249. /* 26 */
  9250. /***/ function(module, exports, __webpack_require__) {
  9251. var util = __webpack_require__(1);
  9252. var Group = __webpack_require__(24);
  9253. /**
  9254. * @constructor BackgroundGroup
  9255. * @param {Number | String} groupId
  9256. * @param {Object} data
  9257. * @param {ItemSet} itemSet
  9258. */
  9259. function BackgroundGroup (groupId, data, itemSet) {
  9260. Group.call(this, groupId, data, itemSet);
  9261. this.width = 0;
  9262. this.height = 0;
  9263. this.top = 0;
  9264. this.left = 0;
  9265. }
  9266. BackgroundGroup.prototype = Object.create(Group.prototype);
  9267. /**
  9268. * Repaint this group
  9269. * @param {{start: number, end: number}} range
  9270. * @param {{item: {horizontal: number, vertical: number}, axis: number}} margin
  9271. * @param {boolean} [restack=false] Force restacking of all items
  9272. * @return {boolean} Returns true if the group is resized
  9273. */
  9274. BackgroundGroup.prototype.redraw = function(range, margin, restack) {
  9275. var resized = false;
  9276. this.visibleItems = this._updateVisibleItems(this.orderedItems, this.visibleItems, range);
  9277. // calculate actual size
  9278. this.width = this.dom.background.offsetWidth;
  9279. // apply new height (just always zero for BackgroundGroup
  9280. this.dom.background.style.height = '0';
  9281. // update vertical position of items after they are re-stacked and the height of the group is calculated
  9282. for (var i = 0, ii = this.visibleItems.length; i < ii; i++) {
  9283. var item = this.visibleItems[i];
  9284. item.repositionY(margin);
  9285. }
  9286. return resized;
  9287. };
  9288. /**
  9289. * Show this group: attach to the DOM
  9290. */
  9291. BackgroundGroup.prototype.show = function() {
  9292. if (!this.dom.background.parentNode) {
  9293. this.itemSet.dom.background.appendChild(this.dom.background);
  9294. }
  9295. };
  9296. module.exports = BackgroundGroup;
  9297. /***/ },
  9298. /* 27 */
  9299. /***/ function(module, exports, __webpack_require__) {
  9300. var Hammer = __webpack_require__(45);
  9301. var util = __webpack_require__(1);
  9302. var DataSet = __webpack_require__(3);
  9303. var DataView = __webpack_require__(4);
  9304. var Component = __webpack_require__(20);
  9305. var Group = __webpack_require__(24);
  9306. var BackgroundGroup = __webpack_require__(26);
  9307. var BoxItem = __webpack_require__(33);
  9308. var PointItem = __webpack_require__(34);
  9309. var RangeItem = __webpack_require__(35);
  9310. var BackgroundItem = __webpack_require__(32);
  9311. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  9312. var BACKGROUND = '__background__'; // reserved group id for background items without group
  9313. /**
  9314. * An ItemSet holds a set of items and ranges which can be displayed in a
  9315. * range. The width is determined by the parent of the ItemSet, and the height
  9316. * is determined by the size of the items.
  9317. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  9318. * @param {Object} [options] See ItemSet.setOptions for the available options.
  9319. * @constructor ItemSet
  9320. * @extends Component
  9321. */
  9322. function ItemSet(body, options) {
  9323. this.body = body;
  9324. this.defaultOptions = {
  9325. type: null, // 'box', 'point', 'range', 'background'
  9326. orientation: 'bottom', // 'top' or 'bottom'
  9327. align: 'auto', // alignment of box items
  9328. stack: true,
  9329. groupOrder: null,
  9330. selectable: true,
  9331. editable: {
  9332. updateTime: false,
  9333. updateGroup: false,
  9334. add: false,
  9335. remove: false
  9336. },
  9337. onAdd: function (item, callback) {
  9338. callback(item);
  9339. },
  9340. onUpdate: function (item, callback) {
  9341. callback(item);
  9342. },
  9343. onMove: function (item, callback) {
  9344. callback(item);
  9345. },
  9346. onRemove: function (item, callback) {
  9347. callback(item);
  9348. },
  9349. onMoving: function (item, callback) {
  9350. callback(item);
  9351. },
  9352. margin: {
  9353. item: {
  9354. horizontal: 10,
  9355. vertical: 10
  9356. },
  9357. axis: 20
  9358. },
  9359. padding: 5
  9360. };
  9361. // options is shared by this ItemSet and all its items
  9362. this.options = util.extend({}, this.defaultOptions);
  9363. // options for getting items from the DataSet with the correct type
  9364. this.itemOptions = {
  9365. type: {start: 'Date', end: 'Date'}
  9366. };
  9367. this.conversion = {
  9368. toScreen: body.util.toScreen,
  9369. toTime: body.util.toTime
  9370. };
  9371. this.dom = {};
  9372. this.props = {};
  9373. this.hammer = null;
  9374. var me = this;
  9375. this.itemsData = null; // DataSet
  9376. this.groupsData = null; // DataSet
  9377. // listeners for the DataSet of the items
  9378. this.itemListeners = {
  9379. 'add': function (event, params, senderId) {
  9380. me._onAdd(params.items);
  9381. },
  9382. 'update': function (event, params, senderId) {
  9383. me._onUpdate(params.items);
  9384. },
  9385. 'remove': function (event, params, senderId) {
  9386. me._onRemove(params.items);
  9387. }
  9388. };
  9389. // listeners for the DataSet of the groups
  9390. this.groupListeners = {
  9391. 'add': function (event, params, senderId) {
  9392. me._onAddGroups(params.items);
  9393. },
  9394. 'update': function (event, params, senderId) {
  9395. me._onUpdateGroups(params.items);
  9396. },
  9397. 'remove': function (event, params, senderId) {
  9398. me._onRemoveGroups(params.items);
  9399. }
  9400. };
  9401. this.items = {}; // object with an Item for every data item
  9402. this.groups = {}; // Group object for every group
  9403. this.groupIds = [];
  9404. this.selection = []; // list with the ids of all selected nodes
  9405. this.stackDirty = true; // if true, all items will be restacked on next redraw
  9406. this.touchParams = {}; // stores properties while dragging
  9407. // create the HTML DOM
  9408. this._create();
  9409. this.setOptions(options);
  9410. }
  9411. ItemSet.prototype = new Component();
  9412. // available item types will be registered here
  9413. ItemSet.types = {
  9414. background: BackgroundItem,
  9415. box: BoxItem,
  9416. range: RangeItem,
  9417. point: PointItem
  9418. };
  9419. /**
  9420. * Create the HTML DOM for the ItemSet
  9421. */
  9422. ItemSet.prototype._create = function(){
  9423. var frame = document.createElement('div');
  9424. frame.className = 'itemset';
  9425. frame['timeline-itemset'] = this;
  9426. this.dom.frame = frame;
  9427. // create background panel
  9428. var background = document.createElement('div');
  9429. background.className = 'background';
  9430. frame.appendChild(background);
  9431. this.dom.background = background;
  9432. // create foreground panel
  9433. var foreground = document.createElement('div');
  9434. foreground.className = 'foreground';
  9435. frame.appendChild(foreground);
  9436. this.dom.foreground = foreground;
  9437. // create axis panel
  9438. var axis = document.createElement('div');
  9439. axis.className = 'axis';
  9440. this.dom.axis = axis;
  9441. // create labelset
  9442. var labelSet = document.createElement('div');
  9443. labelSet.className = 'labelset';
  9444. this.dom.labelSet = labelSet;
  9445. // create ungrouped Group
  9446. this._updateUngrouped();
  9447. // create background Group
  9448. var backgroundGroup = new BackgroundGroup(BACKGROUND, null, this);
  9449. backgroundGroup.show();
  9450. this.groups[BACKGROUND] = backgroundGroup;
  9451. // attach event listeners
  9452. // Note: we bind to the centerContainer for the case where the height
  9453. // of the center container is larger than of the ItemSet, so we
  9454. // can click in the empty area to create a new item or deselect an item.
  9455. this.hammer = Hammer(this.body.dom.centerContainer, {
  9456. prevent_default: true
  9457. });
  9458. // drag items when selected
  9459. this.hammer.on('touch', this._onTouch.bind(this));
  9460. this.hammer.on('dragstart', this._onDragStart.bind(this));
  9461. this.hammer.on('drag', this._onDrag.bind(this));
  9462. this.hammer.on('dragend', this._onDragEnd.bind(this));
  9463. // single select (or unselect) when tapping an item
  9464. this.hammer.on('tap', this._onSelectItem.bind(this));
  9465. // multi select when holding mouse/touch, or on ctrl+click
  9466. this.hammer.on('hold', this._onMultiSelectItem.bind(this));
  9467. // add item on doubletap
  9468. this.hammer.on('doubletap', this._onAddItem.bind(this));
  9469. // attach to the DOM
  9470. this.show();
  9471. };
  9472. /**
  9473. * Set options for the ItemSet. Existing options will be extended/overwritten.
  9474. * @param {Object} [options] The following options are available:
  9475. * {String} type
  9476. * Default type for the items. Choose from 'box'
  9477. * (default), 'point', 'range', or 'background'.
  9478. * The default style can be overwritten by
  9479. * individual items.
  9480. * {String} align
  9481. * Alignment for the items, only applicable for
  9482. * BoxItem. Choose 'center' (default), 'left', or
  9483. * 'right'.
  9484. * {String} orientation
  9485. * Orientation of the item set. Choose 'top' or
  9486. * 'bottom' (default).
  9487. * {Function} groupOrder
  9488. * A sorting function for ordering groups
  9489. * {Boolean} stack
  9490. * If true (deafult), items will be stacked on
  9491. * top of each other.
  9492. * {Number} margin.axis
  9493. * Margin between the axis and the items in pixels.
  9494. * Default is 20.
  9495. * {Number} margin.item.horizontal
  9496. * Horizontal margin between items in pixels.
  9497. * Default is 10.
  9498. * {Number} margin.item.vertical
  9499. * Vertical Margin between items in pixels.
  9500. * Default is 10.
  9501. * {Number} margin.item
  9502. * Margin between items in pixels in both horizontal
  9503. * and vertical direction. Default is 10.
  9504. * {Number} margin
  9505. * Set margin for both axis and items in pixels.
  9506. * {Number} padding
  9507. * Padding of the contents of an item in pixels.
  9508. * Must correspond with the items css. Default is 5.
  9509. * {Boolean} selectable
  9510. * If true (default), items can be selected.
  9511. * {Boolean} editable
  9512. * Set all editable options to true or false
  9513. * {Boolean} editable.updateTime
  9514. * Allow dragging an item to an other moment in time
  9515. * {Boolean} editable.updateGroup
  9516. * Allow dragging an item to an other group
  9517. * {Boolean} editable.add
  9518. * Allow creating new items on double tap
  9519. * {Boolean} editable.remove
  9520. * Allow removing items by clicking the delete button
  9521. * top right of a selected item.
  9522. * {Function(item: Item, callback: Function)} onAdd
  9523. * Callback function triggered when an item is about to be added:
  9524. * when the user double taps an empty space in the Timeline.
  9525. * {Function(item: Item, callback: Function)} onUpdate
  9526. * Callback function fired when an item is about to be updated.
  9527. * This function typically has to show a dialog where the user
  9528. * change the item. If not implemented, nothing happens.
  9529. * {Function(item: Item, callback: Function)} onMove
  9530. * Fired when an item has been moved. If not implemented,
  9531. * the move action will be accepted.
  9532. * {Function(item: Item, callback: Function)} onRemove
  9533. * Fired when an item is about to be deleted.
  9534. * If not implemented, the item will be always removed.
  9535. */
  9536. ItemSet.prototype.setOptions = function(options) {
  9537. if (options) {
  9538. // copy all options that we know
  9539. var fields = ['type', 'align', 'orientation', 'padding', 'stack', 'selectable', 'groupOrder', 'dataAttributes', 'template','hide'];
  9540. util.selectiveExtend(fields, this.options, options);
  9541. if ('margin' in options) {
  9542. if (typeof options.margin === 'number') {
  9543. this.options.margin.axis = options.margin;
  9544. this.options.margin.item.horizontal = options.margin;
  9545. this.options.margin.item.vertical = options.margin;
  9546. }
  9547. else if (typeof options.margin === 'object') {
  9548. util.selectiveExtend(['axis'], this.options.margin, options.margin);
  9549. if ('item' in options.margin) {
  9550. if (typeof options.margin.item === 'number') {
  9551. this.options.margin.item.horizontal = options.margin.item;
  9552. this.options.margin.item.vertical = options.margin.item;
  9553. }
  9554. else if (typeof options.margin.item === 'object') {
  9555. util.selectiveExtend(['horizontal', 'vertical'], this.options.margin.item, options.margin.item);
  9556. }
  9557. }
  9558. }
  9559. }
  9560. if ('editable' in options) {
  9561. if (typeof options.editable === 'boolean') {
  9562. this.options.editable.updateTime = options.editable;
  9563. this.options.editable.updateGroup = options.editable;
  9564. this.options.editable.add = options.editable;
  9565. this.options.editable.remove = options.editable;
  9566. }
  9567. else if (typeof options.editable === 'object') {
  9568. util.selectiveExtend(['updateTime', 'updateGroup', 'add', 'remove'], this.options.editable, options.editable);
  9569. }
  9570. }
  9571. // callback functions
  9572. var addCallback = (function (name) {
  9573. var fn = options[name];
  9574. if (fn) {
  9575. if (!(fn instanceof Function)) {
  9576. throw new Error('option ' + name + ' must be a function ' + name + '(item, callback)');
  9577. }
  9578. this.options[name] = fn;
  9579. }
  9580. }).bind(this);
  9581. ['onAdd', 'onUpdate', 'onRemove', 'onMove', 'onMoving'].forEach(addCallback);
  9582. // force the itemSet to refresh: options like orientation and margins may be changed
  9583. this.markDirty();
  9584. }
  9585. };
  9586. /**
  9587. * Mark the ItemSet dirty so it will refresh everything with next redraw
  9588. */
  9589. ItemSet.prototype.markDirty = function() {
  9590. this.groupIds = [];
  9591. this.stackDirty = true;
  9592. };
  9593. /**
  9594. * Destroy the ItemSet
  9595. */
  9596. ItemSet.prototype.destroy = function() {
  9597. this.hide();
  9598. this.setItems(null);
  9599. this.setGroups(null);
  9600. this.hammer = null;
  9601. this.body = null;
  9602. this.conversion = null;
  9603. };
  9604. /**
  9605. * Hide the component from the DOM
  9606. */
  9607. ItemSet.prototype.hide = function() {
  9608. // remove the frame containing the items
  9609. if (this.dom.frame.parentNode) {
  9610. this.dom.frame.parentNode.removeChild(this.dom.frame);
  9611. }
  9612. // remove the axis with dots
  9613. if (this.dom.axis.parentNode) {
  9614. this.dom.axis.parentNode.removeChild(this.dom.axis);
  9615. }
  9616. // remove the labelset containing all group labels
  9617. if (this.dom.labelSet.parentNode) {
  9618. this.dom.labelSet.parentNode.removeChild(this.dom.labelSet);
  9619. }
  9620. };
  9621. /**
  9622. * Show the component in the DOM (when not already visible).
  9623. * @return {Boolean} changed
  9624. */
  9625. ItemSet.prototype.show = function() {
  9626. // show frame containing the items
  9627. if (!this.dom.frame.parentNode) {
  9628. this.body.dom.center.appendChild(this.dom.frame);
  9629. }
  9630. // show axis with dots
  9631. if (!this.dom.axis.parentNode) {
  9632. this.body.dom.backgroundVertical.appendChild(this.dom.axis);
  9633. }
  9634. // show labelset containing labels
  9635. if (!this.dom.labelSet.parentNode) {
  9636. this.body.dom.left.appendChild(this.dom.labelSet);
  9637. }
  9638. };
  9639. /**
  9640. * Set selected items by their id. Replaces the current selection
  9641. * Unknown id's are silently ignored.
  9642. * @param {string[] | string} [ids] An array with zero or more id's of the items to be
  9643. * selected, or a single item id. If ids is undefined
  9644. * or an empty array, all items will be unselected.
  9645. */
  9646. ItemSet.prototype.setSelection = function(ids) {
  9647. var i, ii, id, item;
  9648. if (ids == undefined) ids = [];
  9649. if (!Array.isArray(ids)) ids = [ids];
  9650. // unselect currently selected items
  9651. for (i = 0, ii = this.selection.length; i < ii; i++) {
  9652. id = this.selection[i];
  9653. item = this.items[id];
  9654. if (item) item.unselect();
  9655. }
  9656. // select items
  9657. this.selection = [];
  9658. for (i = 0, ii = ids.length; i < ii; i++) {
  9659. id = ids[i];
  9660. item = this.items[id];
  9661. if (item) {
  9662. this.selection.push(id);
  9663. item.select();
  9664. }
  9665. }
  9666. };
  9667. /**
  9668. * Get the selected items by their id
  9669. * @return {Array} ids The ids of the selected items
  9670. */
  9671. ItemSet.prototype.getSelection = function() {
  9672. return this.selection.concat([]);
  9673. };
  9674. /**
  9675. * Get the id's of the currently visible items.
  9676. * @returns {Array} The ids of the visible items
  9677. */
  9678. ItemSet.prototype.getVisibleItems = function() {
  9679. var range = this.body.range.getRange();
  9680. var left = this.body.util.toScreen(range.start);
  9681. var right = this.body.util.toScreen(range.end);
  9682. var ids = [];
  9683. for (var groupId in this.groups) {
  9684. if (this.groups.hasOwnProperty(groupId)) {
  9685. var group = this.groups[groupId];
  9686. var rawVisibleItems = group.visibleItems;
  9687. // filter the "raw" set with visibleItems into a set which is really
  9688. // visible by pixels
  9689. for (var i = 0; i < rawVisibleItems.length; i++) {
  9690. var item = rawVisibleItems[i];
  9691. // TODO: also check whether visible vertically
  9692. if ((item.left < right) && (item.left + item.width > left)) {
  9693. ids.push(item.id);
  9694. }
  9695. }
  9696. }
  9697. }
  9698. return ids;
  9699. };
  9700. /**
  9701. * Deselect a selected item
  9702. * @param {String | Number} id
  9703. * @private
  9704. */
  9705. ItemSet.prototype._deselect = function(id) {
  9706. var selection = this.selection;
  9707. for (var i = 0, ii = selection.length; i < ii; i++) {
  9708. if (selection[i] == id) { // non-strict comparison!
  9709. selection.splice(i, 1);
  9710. break;
  9711. }
  9712. }
  9713. };
  9714. /**
  9715. * Repaint the component
  9716. * @return {boolean} Returns true if the component is resized
  9717. */
  9718. ItemSet.prototype.redraw = function() {
  9719. var margin = this.options.margin,
  9720. range = this.body.range,
  9721. asSize = util.option.asSize,
  9722. options = this.options,
  9723. orientation = options.orientation,
  9724. resized = false,
  9725. frame = this.dom.frame,
  9726. editable = options.editable.updateTime || options.editable.updateGroup;
  9727. // recalculate absolute position (before redrawing groups)
  9728. this.props.top = this.body.domProps.top.height + this.body.domProps.border.top;
  9729. this.props.left = this.body.domProps.left.width + this.body.domProps.border.left;
  9730. // update class name
  9731. frame.className = 'itemset' + (editable ? ' editable' : '');
  9732. // reorder the groups (if needed)
  9733. resized = this._orderGroups() || resized;
  9734. // check whether zoomed (in that case we need to re-stack everything)
  9735. // TODO: would be nicer to get this as a trigger from Range
  9736. var visibleInterval = range.end - range.start;
  9737. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.props.width != this.props.lastWidth);
  9738. if (zoomed) this.stackDirty = true;
  9739. this.lastVisibleInterval = visibleInterval;
  9740. this.props.lastWidth = this.props.width;
  9741. var restack = this.stackDirty;
  9742. var firstGroup = this._firstGroup();
  9743. var firstMargin = {
  9744. item: margin.item,
  9745. axis: margin.axis
  9746. };
  9747. var nonFirstMargin = {
  9748. item: margin.item,
  9749. axis: margin.item.vertical / 2
  9750. };
  9751. var height = 0;
  9752. var minHeight = margin.axis + margin.item.vertical;
  9753. // redraw the background group
  9754. this.groups[BACKGROUND].redraw(range, nonFirstMargin, restack);
  9755. // redraw all regular groups
  9756. util.forEach(this.groups, function (group) {
  9757. var groupMargin = (group == firstGroup) ? firstMargin : nonFirstMargin;
  9758. var groupResized = group.redraw(range, groupMargin, restack);
  9759. resized = groupResized || resized;
  9760. height += group.height;
  9761. });
  9762. height = Math.max(height, minHeight);
  9763. this.stackDirty = false;
  9764. // update frame height
  9765. frame.style.height = asSize(height);
  9766. // calculate actual size
  9767. this.props.width = frame.offsetWidth;
  9768. this.props.height = height;
  9769. // reposition axis
  9770. this.dom.axis.style.top = asSize((orientation == 'top') ?
  9771. (this.body.domProps.top.height + this.body.domProps.border.top) :
  9772. (this.body.domProps.top.height + this.body.domProps.centerContainer.height));
  9773. this.dom.axis.style.left = '0';
  9774. // check if this component is resized
  9775. resized = this._isResized() || resized;
  9776. return resized;
  9777. };
  9778. /**
  9779. * Get the first group, aligned with the axis
  9780. * @return {Group | null} firstGroup
  9781. * @private
  9782. */
  9783. ItemSet.prototype._firstGroup = function() {
  9784. var firstGroupIndex = (this.options.orientation == 'top') ? 0 : (this.groupIds.length - 1);
  9785. var firstGroupId = this.groupIds[firstGroupIndex];
  9786. var firstGroup = this.groups[firstGroupId] || this.groups[UNGROUPED];
  9787. return firstGroup || null;
  9788. };
  9789. /**
  9790. * Create or delete the group holding all ungrouped items. This group is used when
  9791. * there are no groups specified.
  9792. * @protected
  9793. */
  9794. ItemSet.prototype._updateUngrouped = function() {
  9795. var ungrouped = this.groups[UNGROUPED];
  9796. var background = this.groups[BACKGROUND];
  9797. var item, itemId;
  9798. if (this.groupsData) {
  9799. // remove the group holding all ungrouped items
  9800. if (ungrouped) {
  9801. ungrouped.hide();
  9802. delete this.groups[UNGROUPED];
  9803. for (itemId in this.items) {
  9804. if (this.items.hasOwnProperty(itemId)) {
  9805. item = this.items[itemId];
  9806. item.parent && item.parent.remove(item);
  9807. var groupId = this._getGroupId(item.data);
  9808. var group = this.groups[groupId];
  9809. group && group.add(item) || item.hide();
  9810. }
  9811. }
  9812. }
  9813. }
  9814. else {
  9815. // create a group holding all (unfiltered) items
  9816. if (!ungrouped) {
  9817. var id = null;
  9818. var data = null;
  9819. ungrouped = new Group(id, data, this);
  9820. this.groups[UNGROUPED] = ungrouped;
  9821. for (itemId in this.items) {
  9822. if (this.items.hasOwnProperty(itemId)) {
  9823. item = this.items[itemId];
  9824. ungrouped.add(item);
  9825. }
  9826. }
  9827. ungrouped.show();
  9828. }
  9829. }
  9830. };
  9831. /**
  9832. * Get the element for the labelset
  9833. * @return {HTMLElement} labelSet
  9834. */
  9835. ItemSet.prototype.getLabelSet = function() {
  9836. return this.dom.labelSet;
  9837. };
  9838. /**
  9839. * Set items
  9840. * @param {vis.DataSet | null} items
  9841. */
  9842. ItemSet.prototype.setItems = function(items) {
  9843. var me = this,
  9844. ids,
  9845. oldItemsData = this.itemsData;
  9846. // replace the dataset
  9847. if (!items) {
  9848. this.itemsData = null;
  9849. }
  9850. else if (items instanceof DataSet || items instanceof DataView) {
  9851. this.itemsData = items;
  9852. }
  9853. else {
  9854. throw new TypeError('Data must be an instance of DataSet or DataView');
  9855. }
  9856. if (oldItemsData) {
  9857. // unsubscribe from old dataset
  9858. util.forEach(this.itemListeners, function (callback, event) {
  9859. oldItemsData.off(event, callback);
  9860. });
  9861. // remove all drawn items
  9862. ids = oldItemsData.getIds();
  9863. this._onRemove(ids);
  9864. }
  9865. if (this.itemsData) {
  9866. // subscribe to new dataset
  9867. var id = this.id;
  9868. util.forEach(this.itemListeners, function (callback, event) {
  9869. me.itemsData.on(event, callback, id);
  9870. });
  9871. // add all new items
  9872. ids = this.itemsData.getIds();
  9873. this._onAdd(ids);
  9874. // update the group holding all ungrouped items
  9875. this._updateUngrouped();
  9876. }
  9877. };
  9878. /**
  9879. * Get the current items
  9880. * @returns {vis.DataSet | null}
  9881. */
  9882. ItemSet.prototype.getItems = function() {
  9883. return this.itemsData;
  9884. };
  9885. /**
  9886. * Set groups
  9887. * @param {vis.DataSet} groups
  9888. */
  9889. ItemSet.prototype.setGroups = function(groups) {
  9890. var me = this,
  9891. ids;
  9892. // unsubscribe from current dataset
  9893. if (this.groupsData) {
  9894. util.forEach(this.groupListeners, function (callback, event) {
  9895. me.groupsData.unsubscribe(event, callback);
  9896. });
  9897. // remove all drawn groups
  9898. ids = this.groupsData.getIds();
  9899. this.groupsData = null;
  9900. this._onRemoveGroups(ids); // note: this will cause a redraw
  9901. }
  9902. // replace the dataset
  9903. if (!groups) {
  9904. this.groupsData = null;
  9905. }
  9906. else if (groups instanceof DataSet || groups instanceof DataView) {
  9907. this.groupsData = groups;
  9908. }
  9909. else {
  9910. throw new TypeError('Data must be an instance of DataSet or DataView');
  9911. }
  9912. if (this.groupsData) {
  9913. // subscribe to new dataset
  9914. var id = this.id;
  9915. util.forEach(this.groupListeners, function (callback, event) {
  9916. me.groupsData.on(event, callback, id);
  9917. });
  9918. // draw all ms
  9919. ids = this.groupsData.getIds();
  9920. this._onAddGroups(ids);
  9921. }
  9922. // update the group holding all ungrouped items
  9923. this._updateUngrouped();
  9924. // update the order of all items in each group
  9925. this._order();
  9926. this.body.emitter.emit('change', {queue: true});
  9927. };
  9928. /**
  9929. * Get the current groups
  9930. * @returns {vis.DataSet | null} groups
  9931. */
  9932. ItemSet.prototype.getGroups = function() {
  9933. return this.groupsData;
  9934. };
  9935. /**
  9936. * Remove an item by its id
  9937. * @param {String | Number} id
  9938. */
  9939. ItemSet.prototype.removeItem = function(id) {
  9940. var item = this.itemsData.get(id),
  9941. dataset = this.itemsData.getDataSet();
  9942. if (item) {
  9943. // confirm deletion
  9944. this.options.onRemove(item, function (item) {
  9945. if (item) {
  9946. // remove by id here, it is possible that an item has no id defined
  9947. // itself, so better not delete by the item itself
  9948. dataset.remove(id);
  9949. }
  9950. });
  9951. }
  9952. };
  9953. /**
  9954. * Get the time of an item based on it's data and options.type
  9955. * @param {Object} itemData
  9956. * @returns {string} Returns the type
  9957. * @private
  9958. */
  9959. ItemSet.prototype._getType = function (itemData) {
  9960. return itemData.type || this.options.type || (itemData.end ? 'range' : 'box');
  9961. };
  9962. /**
  9963. * Get the group id for an item
  9964. * @param {Object} itemData
  9965. * @returns {string} Returns the groupId
  9966. * @private
  9967. */
  9968. ItemSet.prototype._getGroupId = function (itemData) {
  9969. var type = this._getType(itemData);
  9970. if (type == 'background' && itemData.group == undefined) {
  9971. return BACKGROUND;
  9972. }
  9973. else {
  9974. return this.groupsData ? itemData.group : UNGROUPED;
  9975. }
  9976. };
  9977. /**
  9978. * Handle updated items
  9979. * @param {Number[]} ids
  9980. * @protected
  9981. */
  9982. ItemSet.prototype._onUpdate = function(ids) {
  9983. var me = this;
  9984. ids.forEach(function (id) {
  9985. var itemData = me.itemsData.get(id, me.itemOptions);
  9986. var item = me.items[id];
  9987. var type = me._getType(itemData);
  9988. var constructor = ItemSet.types[type];
  9989. if (item) {
  9990. // update item
  9991. if (!constructor || !(item instanceof constructor)) {
  9992. // item type has changed, delete the item and recreate it
  9993. me._removeItem(item);
  9994. item = null;
  9995. }
  9996. else {
  9997. me._updateItem(item, itemData);
  9998. }
  9999. }
  10000. if (!item) {
  10001. // create item
  10002. if (constructor) {
  10003. item = new constructor(itemData, me.conversion, me.options);
  10004. item.id = id; // TODO: not so nice setting id afterwards
  10005. me._addItem(item);
  10006. }
  10007. else if (type == 'rangeoverflow') {
  10008. // TODO: deprecated since version 2.1.0 (or 3.0.0?). cleanup some day
  10009. throw new TypeError('Item type "rangeoverflow" is deprecated. Use css styling instead: ' +
  10010. '.vis.timeline .item.range .content {overflow: visible;}');
  10011. }
  10012. else {
  10013. throw new TypeError('Unknown item type "' + type + '"');
  10014. }
  10015. }
  10016. });
  10017. this._order();
  10018. this.stackDirty = true; // force re-stacking of all items next redraw
  10019. this.body.emitter.emit('change', {queue: true});
  10020. };
  10021. /**
  10022. * Handle added items
  10023. * @param {Number[]} ids
  10024. * @protected
  10025. */
  10026. ItemSet.prototype._onAdd = ItemSet.prototype._onUpdate;
  10027. /**
  10028. * Handle removed items
  10029. * @param {Number[]} ids
  10030. * @protected
  10031. */
  10032. ItemSet.prototype._onRemove = function(ids) {
  10033. var count = 0;
  10034. var me = this;
  10035. ids.forEach(function (id) {
  10036. var item = me.items[id];
  10037. if (item) {
  10038. count++;
  10039. me._removeItem(item);
  10040. }
  10041. });
  10042. if (count) {
  10043. // update order
  10044. this._order();
  10045. this.stackDirty = true; // force re-stacking of all items next redraw
  10046. this.body.emitter.emit('change', {queue: true});
  10047. }
  10048. };
  10049. /**
  10050. * Update the order of item in all groups
  10051. * @private
  10052. */
  10053. ItemSet.prototype._order = function() {
  10054. // reorder the items in all groups
  10055. // TODO: optimization: only reorder groups affected by the changed items
  10056. util.forEach(this.groups, function (group) {
  10057. group.order();
  10058. });
  10059. };
  10060. /**
  10061. * Handle updated groups
  10062. * @param {Number[]} ids
  10063. * @private
  10064. */
  10065. ItemSet.prototype._onUpdateGroups = function(ids) {
  10066. this._onAddGroups(ids);
  10067. };
  10068. /**
  10069. * Handle changed groups (added or updated)
  10070. * @param {Number[]} ids
  10071. * @private
  10072. */
  10073. ItemSet.prototype._onAddGroups = function(ids) {
  10074. var me = this;
  10075. ids.forEach(function (id) {
  10076. var groupData = me.groupsData.get(id);
  10077. var group = me.groups[id];
  10078. if (!group) {
  10079. // check for reserved ids
  10080. if (id == UNGROUPED || id == BACKGROUND) {
  10081. throw new Error('Illegal group id. ' + id + ' is a reserved id.');
  10082. }
  10083. var groupOptions = Object.create(me.options);
  10084. util.extend(groupOptions, {
  10085. height: null
  10086. });
  10087. group = new Group(id, groupData, me);
  10088. me.groups[id] = group;
  10089. // add items with this groupId to the new group
  10090. for (var itemId in me.items) {
  10091. if (me.items.hasOwnProperty(itemId)) {
  10092. var item = me.items[itemId];
  10093. if (item.data.group == id) {
  10094. group.add(item);
  10095. }
  10096. }
  10097. }
  10098. group.order();
  10099. group.show();
  10100. }
  10101. else {
  10102. // update group
  10103. group.setData(groupData);
  10104. }
  10105. });
  10106. this.body.emitter.emit('change', {queue: true});
  10107. };
  10108. /**
  10109. * Handle removed groups
  10110. * @param {Number[]} ids
  10111. * @private
  10112. */
  10113. ItemSet.prototype._onRemoveGroups = function(ids) {
  10114. var groups = this.groups;
  10115. ids.forEach(function (id) {
  10116. var group = groups[id];
  10117. if (group) {
  10118. group.hide();
  10119. delete groups[id];
  10120. }
  10121. });
  10122. this.markDirty();
  10123. this.body.emitter.emit('change', {queue: true});
  10124. };
  10125. /**
  10126. * Reorder the groups if needed
  10127. * @return {boolean} changed
  10128. * @private
  10129. */
  10130. ItemSet.prototype._orderGroups = function () {
  10131. if (this.groupsData) {
  10132. // reorder the groups
  10133. var groupIds = this.groupsData.getIds({
  10134. order: this.options.groupOrder
  10135. });
  10136. var changed = !util.equalArray(groupIds, this.groupIds);
  10137. if (changed) {
  10138. // hide all groups, removes them from the DOM
  10139. var groups = this.groups;
  10140. groupIds.forEach(function (groupId) {
  10141. groups[groupId].hide();
  10142. });
  10143. // show the groups again, attach them to the DOM in correct order
  10144. groupIds.forEach(function (groupId) {
  10145. groups[groupId].show();
  10146. });
  10147. this.groupIds = groupIds;
  10148. }
  10149. return changed;
  10150. }
  10151. else {
  10152. return false;
  10153. }
  10154. };
  10155. /**
  10156. * Add a new item
  10157. * @param {Item} item
  10158. * @private
  10159. */
  10160. ItemSet.prototype._addItem = function(item) {
  10161. this.items[item.id] = item;
  10162. // add to group
  10163. var groupId = this._getGroupId(item.data);
  10164. var group = this.groups[groupId];
  10165. if (group) group.add(item);
  10166. };
  10167. /**
  10168. * Update an existing item
  10169. * @param {Item} item
  10170. * @param {Object} itemData
  10171. * @private
  10172. */
  10173. ItemSet.prototype._updateItem = function(item, itemData) {
  10174. var oldGroupId = item.data.group;
  10175. // update the items data (will redraw the item when displayed)
  10176. item.setData(itemData);
  10177. // update group
  10178. if (oldGroupId != item.data.group) {
  10179. var oldGroup = this.groups[oldGroupId];
  10180. if (oldGroup) oldGroup.remove(item);
  10181. var groupId = this._getGroupId(item.data);
  10182. var group = this.groups[groupId];
  10183. if (group) group.add(item);
  10184. }
  10185. };
  10186. /**
  10187. * Delete an item from the ItemSet: remove it from the DOM, from the map
  10188. * with items, and from the map with visible items, and from the selection
  10189. * @param {Item} item
  10190. * @private
  10191. */
  10192. ItemSet.prototype._removeItem = function(item) {
  10193. // remove from DOM
  10194. item.hide();
  10195. // remove from items
  10196. delete this.items[item.id];
  10197. // remove from selection
  10198. var index = this.selection.indexOf(item.id);
  10199. if (index != -1) this.selection.splice(index, 1);
  10200. // remove from group
  10201. item.parent && item.parent.remove(item);
  10202. };
  10203. /**
  10204. * Create an array containing all items being a range (having an end date)
  10205. * @param array
  10206. * @returns {Array}
  10207. * @private
  10208. */
  10209. ItemSet.prototype._constructByEndArray = function(array) {
  10210. var endArray = [];
  10211. for (var i = 0; i < array.length; i++) {
  10212. if (array[i] instanceof RangeItem) {
  10213. endArray.push(array[i]);
  10214. }
  10215. }
  10216. return endArray;
  10217. };
  10218. /**
  10219. * Register the clicked item on touch, before dragStart is initiated.
  10220. *
  10221. * dragStart is initiated from a mousemove event, which can have left the item
  10222. * already resulting in an item == null
  10223. *
  10224. * @param {Event} event
  10225. * @private
  10226. */
  10227. ItemSet.prototype._onTouch = function (event) {
  10228. // store the touched item, used in _onDragStart
  10229. this.touchParams.item = ItemSet.itemFromTarget(event);
  10230. };
  10231. /**
  10232. * Start dragging the selected events
  10233. * @param {Event} event
  10234. * @private
  10235. */
  10236. ItemSet.prototype._onDragStart = function (event) {
  10237. if (!this.options.editable.updateTime && !this.options.editable.updateGroup) {
  10238. return;
  10239. }
  10240. var item = this.touchParams.item || null;
  10241. var me = this;
  10242. var props = {};
  10243. props.initialX = event.gesture.center.clientX;
  10244. if (item && item.selected) {
  10245. var dragLeftItem = event.target.dragLeftItem;
  10246. var dragRightItem = event.target.dragRightItem;
  10247. if (dragLeftItem) {
  10248. props.item = dragLeftItem;
  10249. if (me.options.editable.updateTime) {
  10250. props.start = item.data.start.valueOf();
  10251. }
  10252. if (me.options.editable.updateGroup) {
  10253. if ('group' in item.data) props.group = item.data.group;
  10254. }
  10255. this.touchParams.itemProps = [props];
  10256. }
  10257. else if (dragRightItem) {
  10258. props.item = dragRightItem;
  10259. if (me.options.editable.updateTime) {
  10260. props.end = item.data.end.valueOf();
  10261. }
  10262. if (me.options.editable.updateGroup) {
  10263. if ('group' in item.data) props.group = item.data.group;
  10264. }
  10265. this.touchParams.itemProps = [props];
  10266. }
  10267. else {
  10268. this.touchParams.itemProps = this.getSelection().map(function (id) {
  10269. var item = me.items[id];
  10270. props.item = item;
  10271. if (me.options.editable.updateTime) {
  10272. if ('start' in item.data) props.start = item.data.start.valueOf();
  10273. if ('end' in item.data) props.end = item.data.end.valueOf();
  10274. }
  10275. if (me.options.editable.updateGroup) {
  10276. if ('group' in item.data) props.group = item.data.group;
  10277. }
  10278. return props;
  10279. });
  10280. }
  10281. event.stopPropagation();
  10282. }
  10283. };
  10284. /**
  10285. * Drag selected items
  10286. * @param {Event} event
  10287. * @private
  10288. */
  10289. ItemSet.prototype._onDrag = function (event) {
  10290. if (this.touchParams.itemProps) {
  10291. var me = this;
  10292. var snap = this.body.util.snap || null;
  10293. var xOffset = this.body.dom.root.offsetLeft + this.body.domProps.left.width;
  10294. // move
  10295. this.touchParams.itemProps.forEach(function (props) {
  10296. var newProps = {};
  10297. if ('start' in props && !('end' in props)) { // only start in props
  10298. var start = me.body.util.toTime(event.gesture.center.clientX - xOffset);
  10299. newProps.start = snap ? snap(start) : start;
  10300. }
  10301. else if ('start' in props) { // start and end in props
  10302. var current = me.body.util.toTime(event.gesture.center.clientX - xOffset);
  10303. var initial = me.body.util.toTime(props.initialX - xOffset);
  10304. var offset = current - initial;
  10305. var start = new Date(props.start + offset);
  10306. var end = new Date(props.end + offset);
  10307. newProps.start = snap ? snap(start) : start;
  10308. newProps.end = snap ? snap(end) : end;
  10309. }
  10310. else if ('end' in props) { // only end in props
  10311. var end = me.body.util.toTime(event.gesture.center.clientX - xOffset);
  10312. newProps.end = snap ? snap(end) : end;
  10313. }
  10314. if ('group' in props) {
  10315. // drag from one group to another
  10316. var group = ItemSet.groupFromTarget(event);
  10317. newProps.group = group && group.groupId;
  10318. }
  10319. // confirm moving the item
  10320. var itemData = util.extend({}, props.item.data, newProps);
  10321. me.options.onMoving(itemData, function (itemData) {
  10322. if (itemData) {
  10323. me._updateItemProps(props.item, itemData);
  10324. }
  10325. });
  10326. });
  10327. this.stackDirty = true; // force re-stacking of all items next redraw
  10328. this.body.emitter.emit('change');
  10329. event.stopPropagation();
  10330. }
  10331. };
  10332. /**
  10333. * Update an items properties
  10334. * @param {Item} item
  10335. * @param {Object} props Can contain properties start, end, and group.
  10336. * @private
  10337. */
  10338. ItemSet.prototype._updateItemProps = function(item, props) {
  10339. // TODO: copy all properties from props to item? (also new ones)
  10340. if ('start' in props) item.data.start = props.start;
  10341. if ('end' in props) item.data.end = props.end;
  10342. if ('group' in props && item.data.group != props.group) {
  10343. this._moveToGroup(item, props.group)
  10344. }
  10345. };
  10346. /**
  10347. * Move an item to another group
  10348. * @param {Item} item
  10349. * @param {String | Number} groupId
  10350. * @private
  10351. */
  10352. ItemSet.prototype._moveToGroup = function(item, groupId) {
  10353. var group = this.groups[groupId];
  10354. if (group && group.groupId != item.data.group) {
  10355. var oldGroup = item.parent;
  10356. oldGroup.remove(item);
  10357. oldGroup.order();
  10358. group.add(item);
  10359. group.order();
  10360. item.data.group = group.groupId;
  10361. }
  10362. };
  10363. /**
  10364. * End of dragging selected items
  10365. * @param {Event} event
  10366. * @private
  10367. */
  10368. ItemSet.prototype._onDragEnd = function (event) {
  10369. if (this.touchParams.itemProps) {
  10370. // prepare a change set for the changed items
  10371. var changes = [],
  10372. me = this,
  10373. dataset = this.itemsData.getDataSet();
  10374. var itemProps = this.touchParams.itemProps ;
  10375. this.touchParams.itemProps = null;
  10376. itemProps.forEach(function (props) {
  10377. var id = props.item.id,
  10378. itemData = me.itemsData.get(id, me.itemOptions);
  10379. var changed = false;
  10380. if ('start' in props.item.data) {
  10381. changed = (props.start != props.item.data.start.valueOf());
  10382. itemData.start = util.convert(props.item.data.start,
  10383. dataset._options.type && dataset._options.type.start || 'Date');
  10384. }
  10385. if ('end' in props.item.data) {
  10386. changed = changed || (props.end != props.item.data.end.valueOf());
  10387. itemData.end = util.convert(props.item.data.end,
  10388. dataset._options.type && dataset._options.type.end || 'Date');
  10389. }
  10390. if ('group' in props.item.data) {
  10391. changed = changed || (props.group != props.item.data.group);
  10392. itemData.group = props.item.data.group;
  10393. }
  10394. // only apply changes when start or end is actually changed
  10395. if (changed) {
  10396. me.options.onMove(itemData, function (itemData) {
  10397. if (itemData) {
  10398. // apply changes
  10399. itemData[dataset._fieldId] = id; // ensure the item contains its id (can be undefined)
  10400. changes.push(itemData);
  10401. }
  10402. else {
  10403. // restore original values
  10404. me._updateItemProps(props.item, props);
  10405. me.stackDirty = true; // force re-stacking of all items next redraw
  10406. me.body.emitter.emit('change');
  10407. }
  10408. });
  10409. }
  10410. });
  10411. // apply the changes to the data (if there are changes)
  10412. if (changes.length) {
  10413. dataset.update(changes);
  10414. }
  10415. event.stopPropagation();
  10416. }
  10417. };
  10418. /**
  10419. * Handle selecting/deselecting an item when tapping it
  10420. * @param {Event} event
  10421. * @private
  10422. */
  10423. ItemSet.prototype._onSelectItem = function (event) {
  10424. if (!this.options.selectable) return;
  10425. var ctrlKey = event.gesture.srcEvent && event.gesture.srcEvent.ctrlKey;
  10426. var shiftKey = event.gesture.srcEvent && event.gesture.srcEvent.shiftKey;
  10427. if (ctrlKey || shiftKey) {
  10428. this._onMultiSelectItem(event);
  10429. return;
  10430. }
  10431. var oldSelection = this.getSelection();
  10432. var item = ItemSet.itemFromTarget(event);
  10433. var selection = item ? [item.id] : [];
  10434. this.setSelection(selection);
  10435. var newSelection = this.getSelection();
  10436. // emit a select event,
  10437. // except when old selection is empty and new selection is still empty
  10438. if (newSelection.length > 0 || oldSelection.length > 0) {
  10439. this.body.emitter.emit('select', {
  10440. items: this.getSelection()
  10441. });
  10442. }
  10443. };
  10444. /**
  10445. * Handle creation and updates of an item on double tap
  10446. * @param event
  10447. * @private
  10448. */
  10449. ItemSet.prototype._onAddItem = function (event) {
  10450. if (!this.options.selectable) return;
  10451. if (!this.options.editable.add) return;
  10452. var me = this,
  10453. snap = this.body.util.snap || null,
  10454. item = ItemSet.itemFromTarget(event);
  10455. if (item) {
  10456. // update item
  10457. // execute async handler to update the item (or cancel it)
  10458. var itemData = me.itemsData.get(item.id); // get a clone of the data from the dataset
  10459. this.options.onUpdate(itemData, function (itemData) {
  10460. if (itemData) {
  10461. me.itemsData.update(itemData);
  10462. }
  10463. });
  10464. }
  10465. else {
  10466. // add item
  10467. var xAbs = util.getAbsoluteLeft(this.dom.frame);
  10468. var x = event.gesture.center.pageX - xAbs;
  10469. var start = this.body.util.toTime(x);
  10470. var newItem = {
  10471. start: snap ? snap(start) : start,
  10472. content: 'new item'
  10473. };
  10474. // when default type is a range, add a default end date to the new item
  10475. if (this.options.type === 'range') {
  10476. var end = this.body.util.toTime(x + this.props.width / 5);
  10477. newItem.end = snap ? snap(end) : end;
  10478. }
  10479. newItem[this.itemsData._fieldId] = util.randomUUID();
  10480. var group = ItemSet.groupFromTarget(event);
  10481. if (group) {
  10482. newItem.group = group.groupId;
  10483. }
  10484. // execute async handler to customize (or cancel) adding an item
  10485. this.options.onAdd(newItem, function (item) {
  10486. if (item) {
  10487. me.itemsData.add(item);
  10488. // TODO: need to trigger a redraw?
  10489. }
  10490. });
  10491. }
  10492. };
  10493. /**
  10494. * Handle selecting/deselecting multiple items when holding an item
  10495. * @param {Event} event
  10496. * @private
  10497. */
  10498. ItemSet.prototype._onMultiSelectItem = function (event) {
  10499. if (!this.options.selectable) return;
  10500. var selection,
  10501. item = ItemSet.itemFromTarget(event);
  10502. if (item) {
  10503. // multi select items
  10504. selection = this.getSelection(); // current selection
  10505. var index = selection.indexOf(item.id);
  10506. if (index == -1) {
  10507. // item is not yet selected -> select it
  10508. selection.push(item.id);
  10509. }
  10510. else {
  10511. // item is already selected -> deselect it
  10512. selection.splice(index, 1);
  10513. }
  10514. this.setSelection(selection);
  10515. this.body.emitter.emit('select', {
  10516. items: this.getSelection()
  10517. });
  10518. }
  10519. };
  10520. /**
  10521. * Find an item from an event target:
  10522. * searches for the attribute 'timeline-item' in the event target's element tree
  10523. * @param {Event} event
  10524. * @return {Item | null} item
  10525. */
  10526. ItemSet.itemFromTarget = function(event) {
  10527. var target = event.target;
  10528. while (target) {
  10529. if (target.hasOwnProperty('timeline-item')) {
  10530. return target['timeline-item'];
  10531. }
  10532. target = target.parentNode;
  10533. }
  10534. return null;
  10535. };
  10536. /**
  10537. * Find the Group from an event target:
  10538. * searches for the attribute 'timeline-group' in the event target's element tree
  10539. * @param {Event} event
  10540. * @return {Group | null} group
  10541. */
  10542. ItemSet.groupFromTarget = function(event) {
  10543. var target = event.target;
  10544. while (target) {
  10545. if (target.hasOwnProperty('timeline-group')) {
  10546. return target['timeline-group'];
  10547. }
  10548. target = target.parentNode;
  10549. }
  10550. return null;
  10551. };
  10552. /**
  10553. * Find the ItemSet from an event target:
  10554. * searches for the attribute 'timeline-itemset' in the event target's element tree
  10555. * @param {Event} event
  10556. * @return {ItemSet | null} item
  10557. */
  10558. ItemSet.itemSetFromTarget = function(event) {
  10559. var target = event.target;
  10560. while (target) {
  10561. if (target.hasOwnProperty('timeline-itemset')) {
  10562. return target['timeline-itemset'];
  10563. }
  10564. target = target.parentNode;
  10565. }
  10566. return null;
  10567. };
  10568. module.exports = ItemSet;
  10569. /***/ },
  10570. /* 28 */
  10571. /***/ function(module, exports, __webpack_require__) {
  10572. var util = __webpack_require__(1);
  10573. var DOMutil = __webpack_require__(2);
  10574. var Component = __webpack_require__(20);
  10575. /**
  10576. * Legend for Graph2d
  10577. */
  10578. function Legend(body, options, side, linegraphOptions) {
  10579. this.body = body;
  10580. this.defaultOptions = {
  10581. enabled: true,
  10582. icons: true,
  10583. iconSize: 20,
  10584. iconSpacing: 6,
  10585. left: {
  10586. visible: true,
  10587. position: 'top-left' // top/bottom - left,center,right
  10588. },
  10589. right: {
  10590. visible: true,
  10591. position: 'top-left' // top/bottom - left,center,right
  10592. }
  10593. }
  10594. this.side = side;
  10595. this.options = util.extend({},this.defaultOptions);
  10596. this.linegraphOptions = linegraphOptions;
  10597. this.svgElements = {};
  10598. this.dom = {};
  10599. this.groups = {};
  10600. this.amountOfGroups = 0;
  10601. this._create();
  10602. this.setOptions(options);
  10603. }
  10604. Legend.prototype = new Component();
  10605. Legend.prototype.addGroup = function(label, graphOptions) {
  10606. if (!this.groups.hasOwnProperty(label)) {
  10607. this.groups[label] = graphOptions;
  10608. }
  10609. this.amountOfGroups += 1;
  10610. };
  10611. Legend.prototype.updateGroup = function(label, graphOptions) {
  10612. this.groups[label] = graphOptions;
  10613. };
  10614. Legend.prototype.removeGroup = function(label) {
  10615. if (this.groups.hasOwnProperty(label)) {
  10616. delete this.groups[label];
  10617. this.amountOfGroups -= 1;
  10618. }
  10619. };
  10620. Legend.prototype._create = function() {
  10621. this.dom.frame = document.createElement('div');
  10622. this.dom.frame.className = 'legend';
  10623. this.dom.frame.style.position = "absolute";
  10624. this.dom.frame.style.top = "10px";
  10625. this.dom.frame.style.display = "block";
  10626. this.dom.textArea = document.createElement('div');
  10627. this.dom.textArea.className = 'legendText';
  10628. this.dom.textArea.style.position = "relative";
  10629. this.dom.textArea.style.top = "0px";
  10630. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  10631. this.svg.style.position = 'absolute';
  10632. this.svg.style.top = 0 +'px';
  10633. this.svg.style.width = this.options.iconSize + 5 + 'px';
  10634. this.svg.style.height = '100%';
  10635. this.dom.frame.appendChild(this.svg);
  10636. this.dom.frame.appendChild(this.dom.textArea);
  10637. };
  10638. /**
  10639. * Hide the component from the DOM
  10640. */
  10641. Legend.prototype.hide = function() {
  10642. // remove the frame containing the items
  10643. if (this.dom.frame.parentNode) {
  10644. this.dom.frame.parentNode.removeChild(this.dom.frame);
  10645. }
  10646. };
  10647. /**
  10648. * Show the component in the DOM (when not already visible).
  10649. * @return {Boolean} changed
  10650. */
  10651. Legend.prototype.show = function() {
  10652. // show frame containing the items
  10653. if (!this.dom.frame.parentNode) {
  10654. this.body.dom.center.appendChild(this.dom.frame);
  10655. }
  10656. };
  10657. Legend.prototype.setOptions = function(options) {
  10658. var fields = ['enabled','orientation','icons','left','right'];
  10659. util.selectiveDeepExtend(fields, this.options, options);
  10660. };
  10661. Legend.prototype.redraw = function() {
  10662. var activeGroups = 0;
  10663. for (var groupId in this.groups) {
  10664. if (this.groups.hasOwnProperty(groupId)) {
  10665. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  10666. activeGroups++;
  10667. }
  10668. }
  10669. }
  10670. if (this.options[this.side].visible == false || this.amountOfGroups == 0 || this.options.enabled == false || activeGroups == 0) {
  10671. this.hide();
  10672. }
  10673. else {
  10674. this.show();
  10675. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'bottom-left') {
  10676. this.dom.frame.style.left = '4px';
  10677. this.dom.frame.style.textAlign = "left";
  10678. this.dom.textArea.style.textAlign = "left";
  10679. this.dom.textArea.style.left = (this.options.iconSize + 15) + 'px';
  10680. this.dom.textArea.style.right = '';
  10681. this.svg.style.left = 0 +'px';
  10682. this.svg.style.right = '';
  10683. }
  10684. else {
  10685. this.dom.frame.style.right = '4px';
  10686. this.dom.frame.style.textAlign = "right";
  10687. this.dom.textArea.style.textAlign = "right";
  10688. this.dom.textArea.style.right = (this.options.iconSize + 15) + 'px';
  10689. this.dom.textArea.style.left = '';
  10690. this.svg.style.right = 0 +'px';
  10691. this.svg.style.left = '';
  10692. }
  10693. if (this.options[this.side].position == 'top-left' || this.options[this.side].position == 'top-right') {
  10694. this.dom.frame.style.top = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  10695. this.dom.frame.style.bottom = '';
  10696. }
  10697. else {
  10698. this.dom.frame.style.bottom = 4 - Number(this.body.dom.center.style.top.replace("px","")) + 'px';
  10699. this.dom.frame.style.top = '';
  10700. }
  10701. if (this.options.icons == false) {
  10702. this.dom.frame.style.width = this.dom.textArea.offsetWidth + 10 + 'px';
  10703. this.dom.textArea.style.right = '';
  10704. this.dom.textArea.style.left = '';
  10705. this.svg.style.width = '0px';
  10706. }
  10707. else {
  10708. this.dom.frame.style.width = this.options.iconSize + 15 + this.dom.textArea.offsetWidth + 10 + 'px'
  10709. this.drawLegendIcons();
  10710. }
  10711. var content = '';
  10712. for (var groupId in this.groups) {
  10713. if (this.groups.hasOwnProperty(groupId)) {
  10714. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  10715. content += this.groups[groupId].content + '<br />';
  10716. }
  10717. }
  10718. }
  10719. this.dom.textArea.innerHTML = content;
  10720. this.dom.textArea.style.lineHeight = ((0.75 * this.options.iconSize) + this.options.iconSpacing) + 'px';
  10721. }
  10722. };
  10723. Legend.prototype.drawLegendIcons = function() {
  10724. if (this.dom.frame.parentNode) {
  10725. DOMutil.prepareElements(this.svgElements);
  10726. var padding = window.getComputedStyle(this.dom.frame).paddingTop;
  10727. var iconOffset = Number(padding.replace('px',''));
  10728. var x = iconOffset;
  10729. var iconWidth = this.options.iconSize;
  10730. var iconHeight = 0.75 * this.options.iconSize;
  10731. var y = iconOffset + 0.5 * iconHeight + 3;
  10732. this.svg.style.width = iconWidth + 5 + iconOffset + 'px';
  10733. for (var groupId in this.groups) {
  10734. if (this.groups.hasOwnProperty(groupId)) {
  10735. if (this.groups[groupId].visible == true && (this.linegraphOptions.visibility[groupId] === undefined || this.linegraphOptions.visibility[groupId] == true)) {
  10736. this.groups[groupId].drawIcon(x, y, this.svgElements, this.svg, iconWidth, iconHeight);
  10737. y += iconHeight + this.options.iconSpacing;
  10738. }
  10739. }
  10740. }
  10741. DOMutil.cleanupElements(this.svgElements);
  10742. }
  10743. };
  10744. module.exports = Legend;
  10745. /***/ },
  10746. /* 29 */
  10747. /***/ function(module, exports, __webpack_require__) {
  10748. var util = __webpack_require__(1);
  10749. var DOMutil = __webpack_require__(2);
  10750. var DataSet = __webpack_require__(3);
  10751. var DataView = __webpack_require__(4);
  10752. var Component = __webpack_require__(20);
  10753. var DataAxis = __webpack_require__(23);
  10754. var GraphGroup = __webpack_require__(25);
  10755. var Legend = __webpack_require__(28);
  10756. var UNGROUPED = '__ungrouped__'; // reserved group id for ungrouped items
  10757. /**
  10758. * This is the constructor of the LineGraph. It requires a Timeline body and options.
  10759. *
  10760. * @param body
  10761. * @param options
  10762. * @constructor
  10763. */
  10764. function LineGraph(body, options) {
  10765. this.id = util.randomUUID();
  10766. this.body = body;
  10767. this.defaultOptions = {
  10768. yAxisOrientation: 'left',
  10769. defaultGroup: 'default',
  10770. sort: true,
  10771. sampling: true,
  10772. graphHeight: '400px',
  10773. shaded: {
  10774. enabled: false,
  10775. orientation: 'bottom' // top, bottom
  10776. },
  10777. style: 'line', // line, bar
  10778. barChart: {
  10779. width: 50,
  10780. handleOverlap: 'overlap',
  10781. align: 'center' // left, center, right
  10782. },
  10783. catmullRom: {
  10784. enabled: true,
  10785. parametrization: 'centripetal', // uniform (alpha = 0.0), chordal (alpha = 1.0), centripetal (alpha = 0.5)
  10786. alpha: 0.5
  10787. },
  10788. drawPoints: {
  10789. enabled: true,
  10790. size: 6,
  10791. style: 'square' // square, circle
  10792. },
  10793. dataAxis: {
  10794. showMinorLabels: true,
  10795. showMajorLabels: true,
  10796. icons: false,
  10797. width: '40px',
  10798. visible: true,
  10799. customRange: {
  10800. left: {min:undefined, max:undefined},
  10801. right: {min:undefined, max:undefined}
  10802. }
  10803. },
  10804. legend: {
  10805. enabled: false,
  10806. icons: true,
  10807. left: {
  10808. visible: true,
  10809. position: 'top-left' // top/bottom - left,right
  10810. },
  10811. right: {
  10812. visible: true,
  10813. position: 'top-right' // top/bottom - left,right
  10814. }
  10815. },
  10816. groups: {
  10817. visibility: {}
  10818. }
  10819. };
  10820. // options is shared by this ItemSet and all its items
  10821. this.options = util.extend({}, this.defaultOptions);
  10822. this.dom = {};
  10823. this.props = {};
  10824. this.hammer = null;
  10825. this.groups = {};
  10826. this.abortedGraphUpdate = false;
  10827. var me = this;
  10828. this.itemsData = null; // DataSet
  10829. this.groupsData = null; // DataSet
  10830. // listeners for the DataSet of the items
  10831. this.itemListeners = {
  10832. 'add': function (event, params, senderId) {
  10833. me._onAdd(params.items);
  10834. },
  10835. 'update': function (event, params, senderId) {
  10836. me._onUpdate(params.items);
  10837. },
  10838. 'remove': function (event, params, senderId) {
  10839. me._onRemove(params.items);
  10840. }
  10841. };
  10842. // listeners for the DataSet of the groups
  10843. this.groupListeners = {
  10844. 'add': function (event, params, senderId) {
  10845. me._onAddGroups(params.items);
  10846. },
  10847. 'update': function (event, params, senderId) {
  10848. me._onUpdateGroups(params.items);
  10849. },
  10850. 'remove': function (event, params, senderId) {
  10851. me._onRemoveGroups(params.items);
  10852. }
  10853. };
  10854. this.items = {}; // object with an Item for every data item
  10855. this.selection = []; // list with the ids of all selected nodes
  10856. this.lastStart = this.body.range.start;
  10857. this.touchParams = {}; // stores properties while dragging
  10858. this.svgElements = {};
  10859. this.setOptions(options);
  10860. this.groupsUsingDefaultStyles = [0];
  10861. this.body.emitter.on("rangechanged", function() {
  10862. me.lastStart = me.body.range.start;
  10863. me.svg.style.left = util.option.asSize(-me.width);
  10864. me._updateGraph.apply(me);
  10865. });
  10866. // create the HTML DOM
  10867. this._create();
  10868. this.body.emitter.emit("change");
  10869. }
  10870. LineGraph.prototype = new Component();
  10871. /**
  10872. * Create the HTML DOM for the ItemSet
  10873. */
  10874. LineGraph.prototype._create = function(){
  10875. var frame = document.createElement('div');
  10876. frame.className = 'LineGraph';
  10877. this.dom.frame = frame;
  10878. // create svg element for graph drawing.
  10879. this.svg = document.createElementNS('http://www.w3.org/2000/svg',"svg");
  10880. this.svg.style.position = "relative";
  10881. this.svg.style.height = ('' + this.options.graphHeight).replace("px",'') + 'px';
  10882. this.svg.style.display = "block";
  10883. frame.appendChild(this.svg);
  10884. // data axis
  10885. this.options.dataAxis.orientation = 'left';
  10886. this.yAxisLeft = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  10887. this.options.dataAxis.orientation = 'right';
  10888. this.yAxisRight = new DataAxis(this.body, this.options.dataAxis, this.svg, this.options.groups);
  10889. delete this.options.dataAxis.orientation;
  10890. // legends
  10891. this.legendLeft = new Legend(this.body, this.options.legend, 'left', this.options.groups);
  10892. this.legendRight = new Legend(this.body, this.options.legend, 'right', this.options.groups);
  10893. this.show();
  10894. };
  10895. /**
  10896. * set the options of the LineGraph. the mergeOptions is used for subObjects that have an enabled element.
  10897. * @param options
  10898. */
  10899. LineGraph.prototype.setOptions = function(options) {
  10900. if (options) {
  10901. var fields = ['sampling','defaultGroup','graphHeight','yAxisOrientation','style','barChart','dataAxis','sort','groups'];
  10902. util.selectiveDeepExtend(fields, this.options, options);
  10903. util.mergeOptions(this.options, options,'catmullRom');
  10904. util.mergeOptions(this.options, options,'drawPoints');
  10905. util.mergeOptions(this.options, options,'shaded');
  10906. util.mergeOptions(this.options, options,'legend');
  10907. if (options.catmullRom) {
  10908. if (typeof options.catmullRom == 'object') {
  10909. if (options.catmullRom.parametrization) {
  10910. if (options.catmullRom.parametrization == 'uniform') {
  10911. this.options.catmullRom.alpha = 0;
  10912. }
  10913. else if (options.catmullRom.parametrization == 'chordal') {
  10914. this.options.catmullRom.alpha = 1.0;
  10915. }
  10916. else {
  10917. this.options.catmullRom.parametrization = 'centripetal';
  10918. this.options.catmullRom.alpha = 0.5;
  10919. }
  10920. }
  10921. }
  10922. }
  10923. if (this.yAxisLeft) {
  10924. if (options.dataAxis !== undefined) {
  10925. this.yAxisLeft.setOptions(this.options.dataAxis);
  10926. this.yAxisRight.setOptions(this.options.dataAxis);
  10927. }
  10928. }
  10929. if (this.legendLeft) {
  10930. if (options.legend !== undefined) {
  10931. this.legendLeft.setOptions(this.options.legend);
  10932. this.legendRight.setOptions(this.options.legend);
  10933. }
  10934. }
  10935. if (this.groups.hasOwnProperty(UNGROUPED)) {
  10936. this.groups[UNGROUPED].setOptions(options);
  10937. }
  10938. }
  10939. if (this.dom.frame) {
  10940. this._updateGraph();
  10941. }
  10942. };
  10943. /**
  10944. * Hide the component from the DOM
  10945. */
  10946. LineGraph.prototype.hide = function() {
  10947. // remove the frame containing the items
  10948. if (this.dom.frame.parentNode) {
  10949. this.dom.frame.parentNode.removeChild(this.dom.frame);
  10950. }
  10951. };
  10952. /**
  10953. * Show the component in the DOM (when not already visible).
  10954. * @return {Boolean} changed
  10955. */
  10956. LineGraph.prototype.show = function() {
  10957. // show frame containing the items
  10958. if (!this.dom.frame.parentNode) {
  10959. this.body.dom.center.appendChild(this.dom.frame);
  10960. }
  10961. };
  10962. /**
  10963. * Set items
  10964. * @param {vis.DataSet | null} items
  10965. */
  10966. LineGraph.prototype.setItems = function(items) {
  10967. var me = this,
  10968. ids,
  10969. oldItemsData = this.itemsData;
  10970. // replace the dataset
  10971. if (!items) {
  10972. this.itemsData = null;
  10973. }
  10974. else if (items instanceof DataSet || items instanceof DataView) {
  10975. this.itemsData = items;
  10976. }
  10977. else {
  10978. throw new TypeError('Data must be an instance of DataSet or DataView');
  10979. }
  10980. if (oldItemsData) {
  10981. // unsubscribe from old dataset
  10982. util.forEach(this.itemListeners, function (callback, event) {
  10983. oldItemsData.off(event, callback);
  10984. });
  10985. // remove all drawn items
  10986. ids = oldItemsData.getIds();
  10987. this._onRemove(ids);
  10988. }
  10989. if (this.itemsData) {
  10990. // subscribe to new dataset
  10991. var id = this.id;
  10992. util.forEach(this.itemListeners, function (callback, event) {
  10993. me.itemsData.on(event, callback, id);
  10994. });
  10995. // add all new items
  10996. ids = this.itemsData.getIds();
  10997. this._onAdd(ids);
  10998. }
  10999. this._updateUngrouped();
  11000. this._updateGraph();
  11001. this.redraw();
  11002. };
  11003. /**
  11004. * Set groups
  11005. * @param {vis.DataSet} groups
  11006. */
  11007. LineGraph.prototype.setGroups = function(groups) {
  11008. var me = this,
  11009. ids;
  11010. // unsubscribe from current dataset
  11011. if (this.groupsData) {
  11012. util.forEach(this.groupListeners, function (callback, event) {
  11013. me.groupsData.unsubscribe(event, callback);
  11014. });
  11015. // remove all drawn groups
  11016. ids = this.groupsData.getIds();
  11017. this.groupsData = null;
  11018. this._onRemoveGroups(ids); // note: this will cause a redraw
  11019. }
  11020. // replace the dataset
  11021. if (!groups) {
  11022. this.groupsData = null;
  11023. }
  11024. else if (groups instanceof DataSet || groups instanceof DataView) {
  11025. this.groupsData = groups;
  11026. }
  11027. else {
  11028. throw new TypeError('Data must be an instance of DataSet or DataView');
  11029. }
  11030. if (this.groupsData) {
  11031. // subscribe to new dataset
  11032. var id = this.id;
  11033. util.forEach(this.groupListeners, function (callback, event) {
  11034. me.groupsData.on(event, callback, id);
  11035. });
  11036. // draw all ms
  11037. ids = this.groupsData.getIds();
  11038. this._onAddGroups(ids);
  11039. }
  11040. this._onUpdate();
  11041. };
  11042. /**
  11043. * Update the datapoints
  11044. * @param [ids]
  11045. * @private
  11046. */
  11047. LineGraph.prototype._onUpdate = function(ids) {
  11048. this._updateUngrouped();
  11049. this._updateAllGroupData();
  11050. this._updateGraph();
  11051. this.redraw();
  11052. };
  11053. LineGraph.prototype._onAdd = function (ids) {this._onUpdate(ids);};
  11054. LineGraph.prototype._onRemove = function (ids) {this._onUpdate(ids);};
  11055. LineGraph.prototype._onUpdateGroups = function (groupIds) {
  11056. for (var i = 0; i < groupIds.length; i++) {
  11057. var group = this.groupsData.get(groupIds[i]);
  11058. this._updateGroup(group, groupIds[i]);
  11059. }
  11060. this._updateGraph();
  11061. this.redraw();
  11062. };
  11063. LineGraph.prototype._onAddGroups = function (groupIds) {this._onUpdateGroups(groupIds);};
  11064. LineGraph.prototype._onRemoveGroups = function (groupIds) {
  11065. for (var i = 0; i < groupIds.length; i++) {
  11066. if (!this.groups.hasOwnProperty(groupIds[i])) {
  11067. if (this.groups[groupIds[i]].options.yAxisOrientation == 'right') {
  11068. this.yAxisRight.removeGroup(groupIds[i]);
  11069. this.legendRight.removeGroup(groupIds[i]);
  11070. this.legendRight.redraw();
  11071. }
  11072. else {
  11073. this.yAxisLeft.removeGroup(groupIds[i]);
  11074. this.legendLeft.removeGroup(groupIds[i]);
  11075. this.legendLeft.redraw();
  11076. }
  11077. delete this.groups[groupIds[i]];
  11078. }
  11079. }
  11080. this._updateUngrouped();
  11081. this._updateGraph();
  11082. this.redraw();
  11083. };
  11084. /**
  11085. * update a group object
  11086. *
  11087. * @param group
  11088. * @param groupId
  11089. * @private
  11090. */
  11091. LineGraph.prototype._updateGroup = function (group, groupId) {
  11092. if (!this.groups.hasOwnProperty(groupId)) {
  11093. this.groups[groupId] = new GraphGroup(group, groupId, this.options, this.groupsUsingDefaultStyles);
  11094. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  11095. this.yAxisRight.addGroup(groupId, this.groups[groupId]);
  11096. this.legendRight.addGroup(groupId, this.groups[groupId]);
  11097. }
  11098. else {
  11099. this.yAxisLeft.addGroup(groupId, this.groups[groupId]);
  11100. this.legendLeft.addGroup(groupId, this.groups[groupId]);
  11101. }
  11102. }
  11103. else {
  11104. this.groups[groupId].update(group);
  11105. if (this.groups[groupId].options.yAxisOrientation == 'right') {
  11106. this.yAxisRight.updateGroup(groupId, this.groups[groupId]);
  11107. this.legendRight.updateGroup(groupId, this.groups[groupId]);
  11108. }
  11109. else {
  11110. this.yAxisLeft.updateGroup(groupId, this.groups[groupId]);
  11111. this.legendLeft.updateGroup(groupId, this.groups[groupId]);
  11112. }
  11113. }
  11114. this.legendLeft.redraw();
  11115. this.legendRight.redraw();
  11116. };
  11117. LineGraph.prototype._updateAllGroupData = function () {
  11118. if (this.itemsData != null) {
  11119. var groupsContent = {};
  11120. var groupId;
  11121. for (groupId in this.groups) {
  11122. if (this.groups.hasOwnProperty(groupId)) {
  11123. groupsContent[groupId] = [];
  11124. }
  11125. }
  11126. for (var itemId in this.itemsData._data) {
  11127. if (this.itemsData._data.hasOwnProperty(itemId)) {
  11128. var item = this.itemsData._data[itemId];
  11129. item.x = util.convert(item.x,"Date");
  11130. groupsContent[item.group].push(item);
  11131. }
  11132. }
  11133. for (groupId in this.groups) {
  11134. if (this.groups.hasOwnProperty(groupId)) {
  11135. this.groups[groupId].setItems(groupsContent[groupId]);
  11136. }
  11137. }
  11138. }
  11139. };
  11140. /**
  11141. * Create or delete the group holding all ungrouped items. This group is used when
  11142. * there are no groups specified. This anonymous group is called 'graph'.
  11143. * @protected
  11144. */
  11145. LineGraph.prototype._updateUngrouped = function() {
  11146. if (this.itemsData && this.itemsData != null) {
  11147. var ungroupedCounter = 0;
  11148. for (var itemId in this.itemsData._data) {
  11149. if (this.itemsData._data.hasOwnProperty(itemId)) {
  11150. var item = this.itemsData._data[itemId];
  11151. if (item != undefined) {
  11152. if (item.hasOwnProperty('group')) {
  11153. if (item.group === undefined) {
  11154. item.group = UNGROUPED;
  11155. }
  11156. }
  11157. else {
  11158. item.group = UNGROUPED;
  11159. }
  11160. ungroupedCounter = item.group == UNGROUPED ? ungroupedCounter + 1 : ungroupedCounter;
  11161. }
  11162. }
  11163. }
  11164. if (ungroupedCounter == 0) {
  11165. delete this.groups[UNGROUPED];
  11166. this.legendLeft.removeGroup(UNGROUPED);
  11167. this.legendRight.removeGroup(UNGROUPED);
  11168. this.yAxisLeft.removeGroup(UNGROUPED);
  11169. this.yAxisRight.removeGroup(UNGROUPED);
  11170. }
  11171. else {
  11172. var group = {id: UNGROUPED, content: this.options.defaultGroup};
  11173. this._updateGroup(group, UNGROUPED);
  11174. }
  11175. }
  11176. else {
  11177. delete this.groups[UNGROUPED];
  11178. this.legendLeft.removeGroup(UNGROUPED);
  11179. this.legendRight.removeGroup(UNGROUPED);
  11180. this.yAxisLeft.removeGroup(UNGROUPED);
  11181. this.yAxisRight.removeGroup(UNGROUPED);
  11182. }
  11183. this.legendLeft.redraw();
  11184. this.legendRight.redraw();
  11185. };
  11186. /**
  11187. * Redraw the component, mandatory function
  11188. * @return {boolean} Returns true if the component is resized
  11189. */
  11190. LineGraph.prototype.redraw = function() {
  11191. var resized = false;
  11192. this.svg.style.height = ('' + this.options.graphHeight).replace('px','') + 'px';
  11193. if (this.lastWidth === undefined && this.width || this.lastWidth != this.width) {
  11194. resized = true;
  11195. }
  11196. // check if this component is resized
  11197. resized = this._isResized() || resized;
  11198. // check whether zoomed (in that case we need to re-stack everything)
  11199. var visibleInterval = this.body.range.end - this.body.range.start;
  11200. var zoomed = (visibleInterval != this.lastVisibleInterval) || (this.width != this.lastWidth);
  11201. this.lastVisibleInterval = visibleInterval;
  11202. this.lastWidth = this.width;
  11203. // calculate actual size and position
  11204. this.width = this.dom.frame.offsetWidth;
  11205. // the svg element is three times as big as the width, this allows for fully dragging left and right
  11206. // without reloading the graph. the controls for this are bound to events in the constructor
  11207. if (resized == true) {
  11208. this.svg.style.width = util.option.asSize(3*this.width);
  11209. this.svg.style.left = util.option.asSize(-this.width);
  11210. }
  11211. if (zoomed == true || this.abortedGraphUpdate == true) {
  11212. this._updateGraph();
  11213. }
  11214. else {
  11215. // move the whole svg while dragging
  11216. if (this.lastStart != 0) {
  11217. var offset = this.body.range.start - this.lastStart;
  11218. var range = this.body.range.end - this.body.range.start;
  11219. if (this.width != 0) {
  11220. var rangePerPixelInv = this.width/range;
  11221. var xOffset = offset * rangePerPixelInv;
  11222. this.svg.style.left = (-this.width - xOffset) + "px";
  11223. }
  11224. }
  11225. }
  11226. this.legendLeft.redraw();
  11227. this.legendRight.redraw();
  11228. return resized;
  11229. };
  11230. /**
  11231. * Update and redraw the graph.
  11232. *
  11233. */
  11234. LineGraph.prototype._updateGraph = function () {
  11235. // reset the svg elements
  11236. DOMutil.prepareElements(this.svgElements);
  11237. if (this.width != 0 && this.itemsData != null) {
  11238. var group, i;
  11239. var preprocessedGroupData = {};
  11240. var processedGroupData = {};
  11241. var groupRanges = {};
  11242. var changeCalled = false;
  11243. // getting group Ids
  11244. var groupIds = [];
  11245. for (var groupId in this.groups) {
  11246. if (this.groups.hasOwnProperty(groupId)) {
  11247. group = this.groups[groupId];
  11248. if (group.visible == true && (this.options.groups.visibility[groupId] === undefined || this.options.groups.visibility[groupId] == true)) {
  11249. groupIds.push(groupId);
  11250. }
  11251. }
  11252. }
  11253. if (groupIds.length > 0) {
  11254. // this is the range of the SVG canvas
  11255. var minDate = this.body.util.toGlobalTime(- this.body.domProps.root.width);
  11256. var maxDate = this.body.util.toGlobalTime(2 * this.body.domProps.root.width);
  11257. var groupsData = {};
  11258. // fill groups data
  11259. this._getRelevantData(groupIds, groupsData, minDate, maxDate);
  11260. // we transform the X coordinates to detect collisions
  11261. for (i = 0; i < groupIds.length; i++) {
  11262. preprocessedGroupData[groupIds[i]] = this._convertXcoordinates(groupsData[groupIds[i]]);
  11263. }
  11264. // now all needed data has been collected we start the processing.
  11265. this._getYRanges(groupIds, preprocessedGroupData, groupRanges);
  11266. // update the Y axis first, we use this data to draw at the correct Y points
  11267. // changeCalled is required to clean the SVG on a change emit.
  11268. changeCalled = this._updateYAxis(groupIds, groupRanges);
  11269. if (changeCalled == true) {
  11270. DOMutil.cleanupElements(this.svgElements);
  11271. this.abortedGraphUpdate = true;
  11272. this.body.emitter.emit("change");
  11273. return;
  11274. }
  11275. this.abortedGraphUpdate = false;
  11276. // With the yAxis scaled correctly, use this to get the Y values of the points.
  11277. for (i = 0; i < groupIds.length; i++) {
  11278. group = this.groups[groupIds[i]];
  11279. processedGroupData[groupIds[i]] = this._convertYcoordinates(groupsData[groupIds[i]], group);
  11280. }
  11281. // draw the groups
  11282. for (i = 0; i < groupIds.length; i++) {
  11283. group = this.groups[groupIds[i]];
  11284. if (group.options.style == 'line') {
  11285. this._drawLineGraph(processedGroupData[groupIds[i]], group);
  11286. }
  11287. }
  11288. this._drawBarGraphs(groupIds, processedGroupData);
  11289. }
  11290. }
  11291. // cleanup unused svg elements
  11292. DOMutil.cleanupElements(this.svgElements);
  11293. };
  11294. LineGraph.prototype._getRelevantData = function (groupIds, groupsData, minDate, maxDate) {
  11295. // first select and preprocess the data from the datasets.
  11296. // the groups have their preselection of data, we now loop over this data to see
  11297. // what data we need to draw. Sorted data is much faster.
  11298. // more optimization is possible by doing the sampling before and using the binary search
  11299. // to find the end date to determine the increment.
  11300. var group, i, j, item;
  11301. if (groupIds.length > 0) {
  11302. for (i = 0; i < groupIds.length; i++) {
  11303. group = this.groups[groupIds[i]];
  11304. groupsData[groupIds[i]] = [];
  11305. var dataContainer = groupsData[groupIds[i]];
  11306. // optimization for sorted data
  11307. if (group.options.sort == true) {
  11308. var guess = Math.max(0, util.binarySearchGeneric(group.itemsData, minDate, 'x', 'before'));
  11309. for (j = guess; j < group.itemsData.length; j++) {
  11310. item = group.itemsData[j];
  11311. if (item !== undefined) {
  11312. if (item.x > maxDate) {
  11313. dataContainer.push(item);
  11314. break;
  11315. }
  11316. else {
  11317. dataContainer.push(item);
  11318. }
  11319. }
  11320. }
  11321. }
  11322. else {
  11323. for (j = 0; j < group.itemsData.length; j++) {
  11324. item = group.itemsData[j];
  11325. if (item !== undefined) {
  11326. if (item.x > minDate && item.x < maxDate) {
  11327. dataContainer.push(item);
  11328. }
  11329. }
  11330. }
  11331. }
  11332. }
  11333. }
  11334. this._applySampling(groupIds, groupsData);
  11335. };
  11336. LineGraph.prototype._applySampling = function (groupIds, groupsData) {
  11337. var group;
  11338. if (groupIds.length > 0) {
  11339. for (var i = 0; i < groupIds.length; i++) {
  11340. group = this.groups[groupIds[i]];
  11341. if (group.options.sampling == true) {
  11342. var dataContainer = groupsData[groupIds[i]];
  11343. if (dataContainer.length > 0) {
  11344. var increment = 1;
  11345. var amountOfPoints = dataContainer.length;
  11346. // the global screen is used because changing the width of the yAxis may affect the increment, resulting in an endless loop
  11347. // of width changing of the yAxis.
  11348. var xDistance = this.body.util.toGlobalScreen(dataContainer[dataContainer.length - 1].x) - this.body.util.toGlobalScreen(dataContainer[0].x);
  11349. var pointsPerPixel = amountOfPoints / xDistance;
  11350. increment = Math.min(Math.ceil(0.2 * amountOfPoints), Math.max(1, Math.round(pointsPerPixel)));
  11351. var sampledData = [];
  11352. for (var j = 0; j < amountOfPoints; j += increment) {
  11353. sampledData.push(dataContainer[j]);
  11354. }
  11355. groupsData[groupIds[i]] = sampledData;
  11356. }
  11357. }
  11358. }
  11359. }
  11360. };
  11361. LineGraph.prototype._getYRanges = function (groupIds, groupsData, groupRanges) {
  11362. var groupData, group, i,j;
  11363. var barCombinedDataLeft = [];
  11364. var barCombinedDataRight = [];
  11365. var barCombinedData;
  11366. if (groupIds.length > 0) {
  11367. for (i = 0; i < groupIds.length; i++) {
  11368. groupData = groupsData[groupIds[i]];
  11369. if (groupData.length > 0) {
  11370. group = this.groups[groupIds[i]];
  11371. if (group.options.style == 'line' || group.options.barChart.handleOverlap != "stack") {
  11372. var yMin = groupData[0].y;
  11373. var yMax = groupData[0].y;
  11374. for (j = 0; j < groupData.length; j++) {
  11375. yMin = yMin > groupData[j].y ? groupData[j].y : yMin;
  11376. yMax = yMax < groupData[j].y ? groupData[j].y : yMax;
  11377. }
  11378. groupRanges[groupIds[i]] = {min: yMin, max: yMax, yAxisOrientation: group.options.yAxisOrientation};
  11379. }
  11380. else if (group.options.style == 'bar') {
  11381. if (group.options.yAxisOrientation == 'left') {
  11382. barCombinedData = barCombinedDataLeft;
  11383. }
  11384. else {
  11385. barCombinedData = barCombinedDataRight;
  11386. }
  11387. groupRanges[groupIds[i]] = {min: 0, max: 0, yAxisOrientation: group.options.yAxisOrientation, ignore: true};
  11388. // combine data
  11389. for (j = 0; j < groupData.length; j++) {
  11390. barCombinedData.push({
  11391. x: groupData[j].x,
  11392. y: groupData[j].y,
  11393. groupId: groupIds[i]
  11394. });
  11395. }
  11396. }
  11397. }
  11398. }
  11399. var intersections;
  11400. if (barCombinedDataLeft.length > 0) {
  11401. // sort by time and by group
  11402. barCombinedDataLeft.sort(function (a, b) {
  11403. if (a.x == b.x) {
  11404. return a.groupId - b.groupId;
  11405. } else {
  11406. return a.x - b.x;
  11407. }
  11408. });
  11409. intersections = {};
  11410. this._getDataIntersections(intersections, barCombinedDataLeft);
  11411. groupRanges["__barchartLeft"] = this._getStackedBarYRange(intersections, barCombinedDataLeft);
  11412. groupRanges["__barchartLeft"].yAxisOrientation = "left";
  11413. groupIds.push("__barchartLeft");
  11414. }
  11415. if (barCombinedDataRight.length > 0) {
  11416. // sort by time and by group
  11417. barCombinedDataRight.sort(function (a, b) {
  11418. if (a.x == b.x) {
  11419. return a.groupId - b.groupId;
  11420. } else {
  11421. return a.x - b.x;
  11422. }
  11423. });
  11424. intersections = {};
  11425. this._getDataIntersections(intersections, barCombinedDataRight);
  11426. groupRanges["__barchartRight"] = this._getStackedBarYRange(intersections, barCombinedDataRight);
  11427. groupRanges["__barchartRight"].yAxisOrientation = "right";
  11428. groupIds.push("__barchartRight");
  11429. }
  11430. }
  11431. };
  11432. LineGraph.prototype._getStackedBarYRange = function (intersections, combinedData) {
  11433. var key;
  11434. var yMin = combinedData[0].y;
  11435. var yMax = combinedData[0].y;
  11436. for (var i = 0; i < combinedData.length; i++) {
  11437. key = combinedData[i].x;
  11438. if (intersections[key] === undefined) {
  11439. yMin = yMin > combinedData[i].y ? combinedData[i].y : yMin;
  11440. yMax = yMax < combinedData[i].y ? combinedData[i].y : yMax;
  11441. }
  11442. else {
  11443. intersections[key].accumulated += combinedData[i].y;
  11444. }
  11445. }
  11446. for (var xpos in intersections) {
  11447. if (intersections.hasOwnProperty(xpos)) {
  11448. yMin = yMin > intersections[xpos].accumulated ? intersections[xpos].accumulated : yMin;
  11449. yMax = yMax < intersections[xpos].accumulated ? intersections[xpos].accumulated : yMax;
  11450. }
  11451. }
  11452. return {min: yMin, max: yMax};
  11453. };
  11454. /**
  11455. * this sets the Y ranges for the Y axis. It also determines which of the axis should be shown or hidden.
  11456. * @param {Array} groupIds
  11457. * @param {Object} groupRanges
  11458. * @private
  11459. */
  11460. LineGraph.prototype._updateYAxis = function (groupIds, groupRanges) {
  11461. var changeCalled = false;
  11462. var yAxisLeftUsed = false;
  11463. var yAxisRightUsed = false;
  11464. var minLeft = 1e9, minRight = 1e9, maxLeft = -1e9, maxRight = -1e9, minVal, maxVal;
  11465. // if groups are present
  11466. if (groupIds.length > 0) {
  11467. for (var i = 0; i < groupIds.length; i++) {
  11468. if (groupRanges.hasOwnProperty(groupIds[i])) {
  11469. if (groupRanges[groupIds[i]].ignore !== true) {
  11470. minVal = groupRanges[groupIds[i]].min;
  11471. maxVal = groupRanges[groupIds[i]].max;
  11472. if (groupRanges[groupIds[i]].yAxisOrientation == 'left') {
  11473. yAxisLeftUsed = true;
  11474. minLeft = minLeft > minVal ? minVal : minLeft;
  11475. maxLeft = maxLeft < maxVal ? maxVal : maxLeft;
  11476. }
  11477. else {
  11478. yAxisRightUsed = true;
  11479. minRight = minRight > minVal ? minVal : minRight;
  11480. maxRight = maxRight < maxVal ? maxVal : maxRight;
  11481. }
  11482. }
  11483. }
  11484. }
  11485. if (yAxisLeftUsed == true) {
  11486. this.yAxisLeft.setRange(minLeft, maxLeft);
  11487. }
  11488. if (yAxisRightUsed == true) {
  11489. this.yAxisRight.setRange(minRight, maxRight);
  11490. }
  11491. }
  11492. changeCalled = this._toggleAxisVisiblity(yAxisLeftUsed , this.yAxisLeft) || changeCalled;
  11493. changeCalled = this._toggleAxisVisiblity(yAxisRightUsed, this.yAxisRight) || changeCalled;
  11494. if (yAxisRightUsed == true && yAxisLeftUsed == true) {
  11495. this.yAxisLeft.drawIcons = true;
  11496. this.yAxisRight.drawIcons = true;
  11497. }
  11498. else {
  11499. this.yAxisLeft.drawIcons = false;
  11500. this.yAxisRight.drawIcons = false;
  11501. }
  11502. this.yAxisRight.master = !yAxisLeftUsed;
  11503. if (this.yAxisRight.master == false) {
  11504. if (yAxisRightUsed == true) {this.yAxisLeft.lineOffset = this.yAxisRight.width;}
  11505. else {this.yAxisLeft.lineOffset = 0;}
  11506. changeCalled = this.yAxisLeft.redraw() || changeCalled;
  11507. this.yAxisRight.stepPixelsForced = this.yAxisLeft.stepPixels;
  11508. changeCalled = this.yAxisRight.redraw() || changeCalled;
  11509. }
  11510. else {
  11511. changeCalled = this.yAxisRight.redraw() || changeCalled;
  11512. }
  11513. // clean the accumulated lists
  11514. if (groupIds.indexOf("__barchartLeft") != -1) {
  11515. groupIds.splice(groupIds.indexOf("__barchartLeft"),1);
  11516. }
  11517. if (groupIds.indexOf("__barchartRight") != -1) {
  11518. groupIds.splice(groupIds.indexOf("__barchartRight"),1);
  11519. }
  11520. return changeCalled;
  11521. };
  11522. /**
  11523. * This shows or hides the Y axis if needed. If there is a change, the changed event is emitted by the updateYAxis function
  11524. *
  11525. * @param {boolean} axisUsed
  11526. * @returns {boolean}
  11527. * @private
  11528. * @param axis
  11529. */
  11530. LineGraph.prototype._toggleAxisVisiblity = function (axisUsed, axis) {
  11531. var changed = false;
  11532. if (axisUsed == false) {
  11533. if (axis.dom.frame.parentNode) {
  11534. axis.hide();
  11535. changed = true;
  11536. }
  11537. }
  11538. else {
  11539. if (!axis.dom.frame.parentNode) {
  11540. axis.show();
  11541. changed = true;
  11542. }
  11543. }
  11544. return changed;
  11545. };
  11546. /**
  11547. * draw a bar graph
  11548. *
  11549. * @param groupIds
  11550. * @param processedGroupData
  11551. */
  11552. LineGraph.prototype._drawBarGraphs = function (groupIds, processedGroupData) {
  11553. var combinedData = [];
  11554. var intersections = {};
  11555. var coreDistance;
  11556. var key, drawData;
  11557. var group;
  11558. var i,j;
  11559. var barPoints = 0;
  11560. // combine all barchart data
  11561. for (i = 0; i < groupIds.length; i++) {
  11562. group = this.groups[groupIds[i]];
  11563. if (group.options.style == 'bar') {
  11564. if (group.visible == true && (this.options.groups.visibility[groupIds[i]] === undefined || this.options.groups.visibility[groupIds[i]] == true)) {
  11565. for (j = 0; j < processedGroupData[groupIds[i]].length; j++) {
  11566. combinedData.push({
  11567. x: processedGroupData[groupIds[i]][j].x,
  11568. y: processedGroupData[groupIds[i]][j].y,
  11569. groupId: groupIds[i]
  11570. });
  11571. barPoints += 1;
  11572. }
  11573. }
  11574. }
  11575. }
  11576. if (barPoints == 0) {return;}
  11577. // sort by time and by group
  11578. combinedData.sort(function (a, b) {
  11579. if (a.x == b.x) {
  11580. return a.groupId - b.groupId;
  11581. } else {
  11582. return a.x - b.x;
  11583. }
  11584. });
  11585. // get intersections
  11586. this._getDataIntersections(intersections, combinedData);
  11587. // plot barchart
  11588. for (i = 0; i < combinedData.length; i++) {
  11589. group = this.groups[combinedData[i].groupId];
  11590. var minWidth = 0.1 * group.options.barChart.width;
  11591. key = combinedData[i].x;
  11592. var heightOffset = 0;
  11593. if (intersections[key] === undefined) {
  11594. if (i+1 < combinedData.length) {coreDistance = Math.abs(combinedData[i+1].x - key);}
  11595. if (i > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[i-1].x - key));}
  11596. drawData = this._getSafeDrawData(coreDistance, group, minWidth);
  11597. }
  11598. else {
  11599. var nextKey = i + (intersections[key].amount - intersections[key].resolved);
  11600. var prevKey = i - (intersections[key].resolved + 1);
  11601. if (nextKey < combinedData.length) {coreDistance = Math.abs(combinedData[nextKey].x - key);}
  11602. if (prevKey > 0) {coreDistance = Math.min(coreDistance,Math.abs(combinedData[prevKey].x - key));}
  11603. drawData = this._getSafeDrawData(coreDistance, group, minWidth);
  11604. intersections[key].resolved += 1;
  11605. if (group.options.barChart.handleOverlap == 'stack') {
  11606. heightOffset = intersections[key].accumulated;
  11607. intersections[key].accumulated += group.zeroPosition - combinedData[i].y;
  11608. }
  11609. else if (group.options.barChart.handleOverlap == 'sideBySide') {
  11610. drawData.width = drawData.width / intersections[key].amount;
  11611. drawData.offset += (intersections[key].resolved) * drawData.width - (0.5*drawData.width * (intersections[key].amount+1));
  11612. if (group.options.barChart.align == 'left') {drawData.offset -= 0.5*drawData.width;}
  11613. else if (group.options.barChart.align == 'right') {drawData.offset += 0.5*drawData.width;}
  11614. }
  11615. }
  11616. DOMutil.drawBar(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, drawData.width, group.zeroPosition - combinedData[i].y, group.className + ' bar', this.svgElements, this.svg);
  11617. // draw points
  11618. if (group.options.drawPoints.enabled == true) {
  11619. DOMutil.drawPoint(combinedData[i].x + drawData.offset, combinedData[i].y - heightOffset, group, this.svgElements, this.svg);
  11620. }
  11621. }
  11622. };
  11623. /**
  11624. * Fill the intersections object with counters of how many datapoints share the same x coordinates
  11625. * @param intersections
  11626. * @param combinedData
  11627. * @private
  11628. */
  11629. LineGraph.prototype._getDataIntersections = function (intersections, combinedData) {
  11630. // get intersections
  11631. var coreDistance;
  11632. for (var i = 0; i < combinedData.length; i++) {
  11633. if (i + 1 < combinedData.length) {
  11634. coreDistance = Math.abs(combinedData[i + 1].x - combinedData[i].x);
  11635. }
  11636. if (i > 0) {
  11637. coreDistance = Math.min(coreDistance, Math.abs(combinedData[i - 1].x - combinedData[i].x));
  11638. }
  11639. if (coreDistance == 0) {
  11640. if (intersections[combinedData[i].x] === undefined) {
  11641. intersections[combinedData[i].x] = {amount: 0, resolved: 0, accumulated: 0};
  11642. }
  11643. intersections[combinedData[i].x].amount += 1;
  11644. }
  11645. }
  11646. };
  11647. /**
  11648. * Get the width and offset for bargraphs based on the coredistance between datapoints
  11649. *
  11650. * @param coreDistance
  11651. * @param group
  11652. * @param minWidth
  11653. * @returns {{width: Number, offset: Number}}
  11654. * @private
  11655. */
  11656. LineGraph.prototype._getSafeDrawData = function (coreDistance, group, minWidth) {
  11657. var width, offset;
  11658. if (coreDistance < group.options.barChart.width && coreDistance > 0) {
  11659. width = coreDistance < minWidth ? minWidth : coreDistance;
  11660. offset = 0; // recalculate offset with the new width;
  11661. if (group.options.barChart.align == 'left') {
  11662. offset -= 0.5 * coreDistance;
  11663. }
  11664. else if (group.options.barChart.align == 'right') {
  11665. offset += 0.5 * coreDistance;
  11666. }
  11667. }
  11668. else {
  11669. // default settings
  11670. width = group.options.barChart.width;
  11671. offset = 0;
  11672. if (group.options.barChart.align == 'left') {
  11673. offset -= 0.5 * group.options.barChart.width;
  11674. }
  11675. else if (group.options.barChart.align == 'right') {
  11676. offset += 0.5 * group.options.barChart.width;
  11677. }
  11678. }
  11679. return {width: width, offset: offset};
  11680. };
  11681. /**
  11682. * draw a line graph
  11683. *
  11684. * @param dataset
  11685. * @param group
  11686. */
  11687. LineGraph.prototype._drawLineGraph = function (dataset, group) {
  11688. if (dataset != null) {
  11689. if (dataset.length > 0) {
  11690. var path, d;
  11691. var svgHeight = Number(this.svg.style.height.replace("px",""));
  11692. path = DOMutil.getSVGElement('path', this.svgElements, this.svg);
  11693. path.setAttributeNS(null, "class", group.className);
  11694. // construct path from dataset
  11695. if (group.options.catmullRom.enabled == true) {
  11696. d = this._catmullRom(dataset, group);
  11697. }
  11698. else {
  11699. d = this._linear(dataset);
  11700. }
  11701. // append with points for fill and finalize the path
  11702. if (group.options.shaded.enabled == true) {
  11703. var fillPath = DOMutil.getSVGElement('path',this.svgElements, this.svg);
  11704. var dFill;
  11705. if (group.options.shaded.orientation == 'top') {
  11706. dFill = "M" + dataset[0].x + "," + 0 + " " + d + "L" + dataset[dataset.length - 1].x + "," + 0;
  11707. }
  11708. else {
  11709. dFill = "M" + dataset[0].x + "," + svgHeight + " " + d + "L" + dataset[dataset.length - 1].x + "," + svgHeight;
  11710. }
  11711. fillPath.setAttributeNS(null, "class", group.className + " fill");
  11712. fillPath.setAttributeNS(null, "d", dFill);
  11713. }
  11714. // copy properties to path for drawing.
  11715. path.setAttributeNS(null, "d", "M" + d);
  11716. // draw points
  11717. if (group.options.drawPoints.enabled == true) {
  11718. this._drawPoints(dataset, group, this.svgElements, this.svg);
  11719. }
  11720. }
  11721. }
  11722. };
  11723. /**
  11724. * draw the data points
  11725. *
  11726. * @param {Array} dataset
  11727. * @param {Object} JSONcontainer
  11728. * @param {Object} svg | SVG DOM element
  11729. * @param {GraphGroup} group
  11730. * @param {Number} [offset]
  11731. */
  11732. LineGraph.prototype._drawPoints = function (dataset, group, JSONcontainer, svg, offset) {
  11733. if (offset === undefined) {offset = 0;}
  11734. for (var i = 0; i < dataset.length; i++) {
  11735. DOMutil.drawPoint(dataset[i].x + offset, dataset[i].y, group, JSONcontainer, svg);
  11736. }
  11737. };
  11738. /**
  11739. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  11740. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  11741. * the yAxis.
  11742. *
  11743. * @param datapoints
  11744. * @returns {Array}
  11745. * @private
  11746. */
  11747. LineGraph.prototype._convertXcoordinates = function (datapoints) {
  11748. var extractedData = [];
  11749. var xValue, yValue;
  11750. var toScreen = this.body.util.toScreen;
  11751. for (var i = 0; i < datapoints.length; i++) {
  11752. xValue = toScreen(datapoints[i].x) + this.width;
  11753. yValue = datapoints[i].y;
  11754. extractedData.push({x: xValue, y: yValue});
  11755. }
  11756. return extractedData;
  11757. };
  11758. /**
  11759. * This uses the DataAxis object to generate the correct X coordinate on the SVG window. It uses the
  11760. * util function toScreen to get the x coordinate from the timestamp. It also pre-filters the data and get the minMax ranges for
  11761. * the yAxis.
  11762. *
  11763. * @param datapoints
  11764. * @returns {Array}
  11765. * @private
  11766. */
  11767. LineGraph.prototype._convertYcoordinates = function (datapoints, group) {
  11768. var extractedData = [];
  11769. var xValue, yValue;
  11770. var toScreen = this.body.util.toScreen;
  11771. var axis = this.yAxisLeft;
  11772. var svgHeight = Number(this.svg.style.height.replace("px",""));
  11773. if (group.options.yAxisOrientation == 'right') {
  11774. axis = this.yAxisRight;
  11775. }
  11776. for (var i = 0; i < datapoints.length; i++) {
  11777. xValue = toScreen(datapoints[i].x) + this.width;
  11778. yValue = Math.round(axis.convertValue(datapoints[i].y));
  11779. extractedData.push({x: xValue, y: yValue});
  11780. }
  11781. group.setZeroPosition(Math.min(svgHeight, axis.convertValue(0)));
  11782. return extractedData;
  11783. };
  11784. /**
  11785. * This uses an uniform parametrization of the CatmullRom algorithm:
  11786. * "On the Parameterization of Catmull-Rom Curves" by Cem Yuksel et al.
  11787. * @param data
  11788. * @returns {string}
  11789. * @private
  11790. */
  11791. LineGraph.prototype._catmullRomUniform = function(data) {
  11792. // catmull rom
  11793. var p0, p1, p2, p3, bp1, bp2;
  11794. var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
  11795. var normalization = 1/6;
  11796. var length = data.length;
  11797. for (var i = 0; i < length - 1; i++) {
  11798. p0 = (i == 0) ? data[0] : data[i-1];
  11799. p1 = data[i];
  11800. p2 = data[i+1];
  11801. p3 = (i + 2 < length) ? data[i+2] : p2;
  11802. // Catmull-Rom to Cubic Bezier conversion matrix
  11803. // 0 1 0 0
  11804. // -1/6 1 1/6 0
  11805. // 0 1/6 1 -1/6
  11806. // 0 0 1 0
  11807. // bp0 = { x: p1.x, y: p1.y };
  11808. bp1 = { x: ((-p0.x + 6*p1.x + p2.x) *normalization), y: ((-p0.y + 6*p1.y + p2.y) *normalization)};
  11809. bp2 = { x: (( p1.x + 6*p2.x - p3.x) *normalization), y: (( p1.y + 6*p2.y - p3.y) *normalization)};
  11810. // bp0 = { x: p2.x, y: p2.y };
  11811. d += "C" +
  11812. bp1.x + "," +
  11813. bp1.y + " " +
  11814. bp2.x + "," +
  11815. bp2.y + " " +
  11816. p2.x + "," +
  11817. p2.y + " ";
  11818. }
  11819. return d;
  11820. };
  11821. /**
  11822. * This uses either the chordal or centripetal parameterization of the catmull-rom algorithm.
  11823. * By default, the centripetal parameterization is used because this gives the nicest results.
  11824. * These parameterizations are relatively heavy because the distance between 4 points have to be calculated.
  11825. *
  11826. * One optimization can be used to reuse distances since this is a sliding window approach.
  11827. * @param data
  11828. * @returns {string}
  11829. * @private
  11830. */
  11831. LineGraph.prototype._catmullRom = function(data, group) {
  11832. var alpha = group.options.catmullRom.alpha;
  11833. if (alpha == 0 || alpha === undefined) {
  11834. return this._catmullRomUniform(data);
  11835. }
  11836. else {
  11837. var p0, p1, p2, p3, bp1, bp2, d1,d2,d3, A, B, N, M;
  11838. var d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA;
  11839. var d = Math.round(data[0].x) + "," + Math.round(data[0].y) + " ";
  11840. var length = data.length;
  11841. for (var i = 0; i < length - 1; i++) {
  11842. p0 = (i == 0) ? data[0] : data[i-1];
  11843. p1 = data[i];
  11844. p2 = data[i+1];
  11845. p3 = (i + 2 < length) ? data[i+2] : p2;
  11846. d1 = Math.sqrt(Math.pow(p0.x - p1.x,2) + Math.pow(p0.y - p1.y,2));
  11847. d2 = Math.sqrt(Math.pow(p1.x - p2.x,2) + Math.pow(p1.y - p2.y,2));
  11848. d3 = Math.sqrt(Math.pow(p2.x - p3.x,2) + Math.pow(p2.y - p3.y,2));
  11849. // Catmull-Rom to Cubic Bezier conversion matrix
  11850. //
  11851. // A = 2d1^2a + 3d1^a * d2^a + d3^2a
  11852. // B = 2d3^2a + 3d3^a * d2^a + d2^2a
  11853. //
  11854. // [ 0 1 0 0 ]
  11855. // [ -d2^2a/N A/N d1^2a/N 0 ]
  11856. // [ 0 d3^2a/M B/M -d2^2a/M ]
  11857. // [ 0 0 1 0 ]
  11858. // [ 0 1 0 0 ]
  11859. // [ -d2pow2a/N A/N d1pow2a/N 0 ]
  11860. // [ 0 d3pow2a/M B/M -d2pow2a/M ]
  11861. // [ 0 0 1 0 ]
  11862. d3powA = Math.pow(d3, alpha);
  11863. d3pow2A = Math.pow(d3,2*alpha);
  11864. d2powA = Math.pow(d2, alpha);
  11865. d2pow2A = Math.pow(d2,2*alpha);
  11866. d1powA = Math.pow(d1, alpha);
  11867. d1pow2A = Math.pow(d1,2*alpha);
  11868. A = 2*d1pow2A + 3*d1powA * d2powA + d2pow2A;
  11869. B = 2*d3pow2A + 3*d3powA * d2powA + d2pow2A;
  11870. N = 3*d1powA * (d1powA + d2powA);
  11871. if (N > 0) {N = 1 / N;}
  11872. M = 3*d3powA * (d3powA + d2powA);
  11873. if (M > 0) {M = 1 / M;}
  11874. bp1 = { x: ((-d2pow2A * p0.x + A*p1.x + d1pow2A * p2.x) * N),
  11875. y: ((-d2pow2A * p0.y + A*p1.y + d1pow2A * p2.y) * N)};
  11876. bp2 = { x: (( d3pow2A * p1.x + B*p2.x - d2pow2A * p3.x) * M),
  11877. y: (( d3pow2A * p1.y + B*p2.y - d2pow2A * p3.y) * M)};
  11878. if (bp1.x == 0 && bp1.y == 0) {bp1 = p1;}
  11879. if (bp2.x == 0 && bp2.y == 0) {bp2 = p2;}
  11880. d += "C" +
  11881. bp1.x + "," +
  11882. bp1.y + " " +
  11883. bp2.x + "," +
  11884. bp2.y + " " +
  11885. p2.x + "," +
  11886. p2.y + " ";
  11887. }
  11888. return d;
  11889. }
  11890. };
  11891. /**
  11892. * this generates the SVG path for a linear drawing between datapoints.
  11893. * @param data
  11894. * @returns {string}
  11895. * @private
  11896. */
  11897. LineGraph.prototype._linear = function(data) {
  11898. // linear
  11899. var d = "";
  11900. for (var i = 0; i < data.length; i++) {
  11901. if (i == 0) {
  11902. d += data[i].x + "," + data[i].y;
  11903. }
  11904. else {
  11905. d += " " + data[i].x + "," + data[i].y;
  11906. }
  11907. }
  11908. return d;
  11909. };
  11910. module.exports = LineGraph;
  11911. /***/ },
  11912. /* 30 */
  11913. /***/ function(module, exports, __webpack_require__) {
  11914. var util = __webpack_require__(1);
  11915. var Component = __webpack_require__(20);
  11916. var TimeStep = __webpack_require__(12);
  11917. var DateUtil = __webpack_require__(8);
  11918. var moment = __webpack_require__(44);
  11919. /**
  11920. * A horizontal time axis
  11921. * @param {{dom: Object, domProps: Object, emitter: Emitter, range: Range}} body
  11922. * @param {Object} [options] See TimeAxis.setOptions for the available
  11923. * options.
  11924. * @constructor TimeAxis
  11925. * @extends Component
  11926. */
  11927. function TimeAxis (body, options) {
  11928. this.dom = {
  11929. foreground: null,
  11930. majorLines: [],
  11931. majorTexts: [],
  11932. minorLines: [],
  11933. minorTexts: [],
  11934. redundant: {
  11935. majorLines: [],
  11936. majorTexts: [],
  11937. minorLines: [],
  11938. minorTexts: []
  11939. }
  11940. };
  11941. this.props = {
  11942. range: {
  11943. start: 0,
  11944. end: 0,
  11945. minimumStep: 0
  11946. },
  11947. lineTop: 0
  11948. };
  11949. this.defaultOptions = {
  11950. orientation: 'bottom', // supported: 'top', 'bottom'
  11951. // TODO: implement timeaxis orientations 'left' and 'right'
  11952. showMinorLabels: true,
  11953. showMajorLabels: true
  11954. };
  11955. this.options = util.extend({}, this.defaultOptions);
  11956. this.body = body;
  11957. // create the HTML DOM
  11958. this._create();
  11959. this.setOptions(options);
  11960. }
  11961. TimeAxis.prototype = new Component();
  11962. /**
  11963. * Set options for the TimeAxis.
  11964. * Parameters will be merged in current options.
  11965. * @param {Object} options Available options:
  11966. * {string} [orientation]
  11967. * {boolean} [showMinorLabels]
  11968. * {boolean} [showMajorLabels]
  11969. */
  11970. TimeAxis.prototype.setOptions = function(options) {
  11971. if (options) {
  11972. // copy all options that we know
  11973. util.selectiveExtend(['orientation', 'showMinorLabels', 'showMajorLabels','hiddenDates'], this.options, options);
  11974. // apply locale to moment.js
  11975. // TODO: not so nice, this is applied globally to moment.js
  11976. if ('locale' in options) {
  11977. if (typeof moment.locale === 'function') {
  11978. // moment.js 2.8.1+
  11979. moment.locale(options.locale);
  11980. }
  11981. else {
  11982. moment.lang(options.locale);
  11983. }
  11984. }
  11985. }
  11986. };
  11987. /**
  11988. * Create the HTML DOM for the TimeAxis
  11989. */
  11990. TimeAxis.prototype._create = function() {
  11991. this.dom.foreground = document.createElement('div');
  11992. this.dom.background = document.createElement('div');
  11993. this.dom.foreground.className = 'timeaxis foreground';
  11994. this.dom.background.className = 'timeaxis background';
  11995. };
  11996. /**
  11997. * Destroy the TimeAxis
  11998. */
  11999. TimeAxis.prototype.destroy = function() {
  12000. // remove from DOM
  12001. if (this.dom.foreground.parentNode) {
  12002. this.dom.foreground.parentNode.removeChild(this.dom.foreground);
  12003. }
  12004. if (this.dom.background.parentNode) {
  12005. this.dom.background.parentNode.removeChild(this.dom.background);
  12006. }
  12007. this.body = null;
  12008. };
  12009. /**
  12010. * Repaint the component
  12011. * @return {boolean} Returns true if the component is resized
  12012. */
  12013. TimeAxis.prototype.redraw = function () {
  12014. var options = this.options;
  12015. var props = this.props;
  12016. var foreground = this.dom.foreground;
  12017. var background = this.dom.background;
  12018. // determine the correct parent DOM element (depending on option orientation)
  12019. var parent = (options.orientation == 'top') ? this.body.dom.top : this.body.dom.bottom;
  12020. var parentChanged = (foreground.parentNode !== parent);
  12021. // calculate character width and height
  12022. this._calculateCharSize();
  12023. // TODO: recalculate sizes only needed when parent is resized or options is changed
  12024. var orientation = this.options.orientation,
  12025. showMinorLabels = this.options.showMinorLabels,
  12026. showMajorLabels = this.options.showMajorLabels;
  12027. // determine the width and height of the elemens for the axis
  12028. props.minorLabelHeight = showMinorLabels ? props.minorCharHeight : 0;
  12029. props.majorLabelHeight = showMajorLabels ? props.majorCharHeight : 0;
  12030. props.height = props.minorLabelHeight + props.majorLabelHeight;
  12031. props.width = foreground.offsetWidth;
  12032. props.minorLineHeight = this.body.domProps.root.height - props.majorLabelHeight -
  12033. (options.orientation == 'top' ? this.body.domProps.bottom.height : this.body.domProps.top.height);
  12034. props.minorLineWidth = 1; // TODO: really calculate width
  12035. props.majorLineHeight = props.minorLineHeight + props.majorLabelHeight;
  12036. props.majorLineWidth = 1; // TODO: really calculate width
  12037. // take foreground and background offline while updating (is almost twice as fast)
  12038. var foregroundNextSibling = foreground.nextSibling;
  12039. var backgroundNextSibling = background.nextSibling;
  12040. foreground.parentNode && foreground.parentNode.removeChild(foreground);
  12041. background.parentNode && background.parentNode.removeChild(background);
  12042. foreground.style.height = this.props.height + 'px';
  12043. this._repaintLabels();
  12044. // put DOM online again (at the same place)
  12045. if (foregroundNextSibling) {
  12046. parent.insertBefore(foreground, foregroundNextSibling);
  12047. }
  12048. else {
  12049. parent.appendChild(foreground)
  12050. }
  12051. if (backgroundNextSibling) {
  12052. this.body.dom.backgroundVertical.insertBefore(background, backgroundNextSibling);
  12053. }
  12054. else {
  12055. this.body.dom.backgroundVertical.appendChild(background)
  12056. }
  12057. return this._isResized() || parentChanged;
  12058. };
  12059. /**
  12060. * Repaint major and minor text labels and vertical grid lines
  12061. * @private
  12062. */
  12063. TimeAxis.prototype._repaintLabels = function () {
  12064. var orientation = this.options.orientation;
  12065. // calculate range and step (step such that we have space for 7 characters per label)
  12066. var start = util.convert(this.body.range.start, 'Number');
  12067. var end = util.convert(this.body.range.end, 'Number');
  12068. var timeLabelsize = this.body.util.toTime((this.props.minorCharWidth || 10) * 7).valueOf();
  12069. var minimumStep = timeLabelsize - DateUtil.getHiddenDurationBefore(this.body.hiddenDates, this.body.range, timeLabelsize);
  12070. minimumStep -= this.body.util.toTime(0).valueOf();
  12071. var step = new TimeStep(new Date(start), new Date(end), minimumStep, this.body.hiddenDates);
  12072. this.step = step;
  12073. // Move all DOM elements to a "redundant" list, where they
  12074. // can be picked for re-use, and clear the lists with lines and texts.
  12075. // At the end of the function _repaintLabels, left over elements will be cleaned up
  12076. var dom = this.dom;
  12077. dom.redundant.majorLines = dom.majorLines;
  12078. dom.redundant.majorTexts = dom.majorTexts;
  12079. dom.redundant.minorLines = dom.minorLines;
  12080. dom.redundant.minorTexts = dom.minorTexts;
  12081. dom.majorLines = [];
  12082. dom.majorTexts = [];
  12083. dom.minorLines = [];
  12084. dom.minorTexts = [];
  12085. step.first();
  12086. var xFirstMajorLabel = undefined;
  12087. var max = 0;
  12088. while (step.hasNext() && max < 1000) {
  12089. max++;
  12090. var cur = step.getCurrent();
  12091. var x = this.body.util.toScreen(cur);
  12092. var isMajor = step.isMajor();
  12093. // TODO: lines must have a width, such that we can create css backgrounds
  12094. if (this.options.showMinorLabels) {
  12095. this._repaintMinorText(x, step.getLabelMinor(), orientation);
  12096. }
  12097. if (isMajor && this.options.showMajorLabels) {
  12098. if (x > 0) {
  12099. if (xFirstMajorLabel == undefined) {
  12100. xFirstMajorLabel = x;
  12101. }
  12102. this._repaintMajorText(x, step.getLabelMajor(), orientation);
  12103. }
  12104. this._repaintMajorLine(x, orientation);
  12105. }
  12106. else {
  12107. this._repaintMinorLine(x, orientation);
  12108. }
  12109. step.next();
  12110. }
  12111. // create a major label on the left when needed
  12112. if (this.options.showMajorLabels) {
  12113. var leftTime = this.body.util.toTime(0),
  12114. leftText = step.getLabelMajor(leftTime),
  12115. widthText = leftText.length * (this.props.majorCharWidth || 10) + 10; // upper bound estimation
  12116. if (xFirstMajorLabel == undefined || widthText < xFirstMajorLabel) {
  12117. this._repaintMajorText(0, leftText, orientation);
  12118. }
  12119. }
  12120. // Cleanup leftover DOM elements from the redundant list
  12121. util.forEach(this.dom.redundant, function (arr) {
  12122. while (arr.length) {
  12123. var elem = arr.pop();
  12124. if (elem && elem.parentNode) {
  12125. elem.parentNode.removeChild(elem);
  12126. }
  12127. }
  12128. });
  12129. };
  12130. /**
  12131. * Create a minor label for the axis at position x
  12132. * @param {Number} x
  12133. * @param {String} text
  12134. * @param {String} orientation "top" or "bottom" (default)
  12135. * @private
  12136. */
  12137. TimeAxis.prototype._repaintMinorText = function (x, text, orientation) {
  12138. // reuse redundant label
  12139. var label = this.dom.redundant.minorTexts.shift();
  12140. if (!label) {
  12141. // create new label
  12142. var content = document.createTextNode('');
  12143. label = document.createElement('div');
  12144. label.appendChild(content);
  12145. label.className = 'text minor';
  12146. this.dom.foreground.appendChild(label);
  12147. }
  12148. this.dom.minorTexts.push(label);
  12149. label.childNodes[0].nodeValue = text;
  12150. label.style.top = (orientation == 'top') ? (this.props.majorLabelHeight + 'px') : '0';
  12151. label.style.left = x + 'px';
  12152. //label.title = title; // TODO: this is a heavy operation
  12153. };
  12154. /**
  12155. * Create a Major label for the axis at position x
  12156. * @param {Number} x
  12157. * @param {String} text
  12158. * @param {String} orientation "top" or "bottom" (default)
  12159. * @private
  12160. */
  12161. TimeAxis.prototype._repaintMajorText = function (x, text, orientation) {
  12162. // reuse redundant label
  12163. var label = this.dom.redundant.majorTexts.shift();
  12164. if (!label) {
  12165. // create label
  12166. var content = document.createTextNode(text);
  12167. label = document.createElement('div');
  12168. label.className = 'text major';
  12169. label.appendChild(content);
  12170. this.dom.foreground.appendChild(label);
  12171. }
  12172. this.dom.majorTexts.push(label);
  12173. label.childNodes[0].nodeValue = text;
  12174. //label.title = title; // TODO: this is a heavy operation
  12175. label.style.top = (orientation == 'top') ? '0' : (this.props.minorLabelHeight + 'px');
  12176. label.style.left = x + 'px';
  12177. };
  12178. /**
  12179. * Create a minor line for the axis at position x
  12180. * @param {Number} x
  12181. * @param {String} orientation "top" or "bottom" (default)
  12182. * @private
  12183. */
  12184. TimeAxis.prototype._repaintMinorLine = function (x, orientation) {
  12185. // reuse redundant line
  12186. var line = this.dom.redundant.minorLines.shift();
  12187. if (!line) {
  12188. // create vertical line
  12189. line = document.createElement('div');
  12190. line.className = 'grid vertical minor';
  12191. this.dom.background.appendChild(line);
  12192. }
  12193. this.dom.minorLines.push(line);
  12194. var props = this.props;
  12195. if (orientation == 'top') {
  12196. line.style.top = props.majorLabelHeight + 'px';
  12197. }
  12198. else {
  12199. line.style.top = this.body.domProps.top.height + 'px';
  12200. }
  12201. line.style.height = props.minorLineHeight + 'px';
  12202. line.style.left = (x - props.minorLineWidth / 2) + 'px';
  12203. };
  12204. /**
  12205. * Create a Major line for the axis at position x
  12206. * @param {Number} x
  12207. * @param {String} orientation "top" or "bottom" (default)
  12208. * @private
  12209. */
  12210. TimeAxis.prototype._repaintMajorLine = function (x, orientation) {
  12211. // reuse redundant line
  12212. var line = this.dom.redundant.majorLines.shift();
  12213. if (!line) {
  12214. // create vertical line
  12215. line = document.createElement('DIV');
  12216. line.className = 'grid vertical major';
  12217. this.dom.background.appendChild(line);
  12218. }
  12219. this.dom.majorLines.push(line);
  12220. var props = this.props;
  12221. if (orientation == 'top') {
  12222. line.style.top = '0';
  12223. }
  12224. else {
  12225. line.style.top = this.body.domProps.top.height + 'px';
  12226. }
  12227. line.style.left = (x - props.majorLineWidth / 2) + 'px';
  12228. line.style.height = props.majorLineHeight + 'px';
  12229. };
  12230. /**
  12231. * Determine the size of text on the axis (both major and minor axis).
  12232. * The size is calculated only once and then cached in this.props.
  12233. * @private
  12234. */
  12235. TimeAxis.prototype._calculateCharSize = function () {
  12236. // Note: We calculate char size with every redraw. Size may change, for
  12237. // example when any of the timelines parents had display:none for example.
  12238. // determine the char width and height on the minor axis
  12239. if (!this.dom.measureCharMinor) {
  12240. this.dom.measureCharMinor = document.createElement('DIV');
  12241. this.dom.measureCharMinor.className = 'text minor measure';
  12242. this.dom.measureCharMinor.style.position = 'absolute';
  12243. this.dom.measureCharMinor.appendChild(document.createTextNode('0'));
  12244. this.dom.foreground.appendChild(this.dom.measureCharMinor);
  12245. }
  12246. this.props.minorCharHeight = this.dom.measureCharMinor.clientHeight;
  12247. this.props.minorCharWidth = this.dom.measureCharMinor.clientWidth;
  12248. // determine the char width and height on the major axis
  12249. if (!this.dom.measureCharMajor) {
  12250. this.dom.measureCharMajor = document.createElement('DIV');
  12251. this.dom.measureCharMajor.className = 'text major measure';
  12252. this.dom.measureCharMajor.style.position = 'absolute';
  12253. this.dom.measureCharMajor.appendChild(document.createTextNode('0'));
  12254. this.dom.foreground.appendChild(this.dom.measureCharMajor);
  12255. }
  12256. this.props.majorCharHeight = this.dom.measureCharMajor.clientHeight;
  12257. this.props.majorCharWidth = this.dom.measureCharMajor.clientWidth;
  12258. };
  12259. /**
  12260. * Snap a date to a rounded value.
  12261. * The snap intervals are dependent on the current scale and step.
  12262. * @param {Date} date the date to be snapped.
  12263. * @return {Date} snappedDate
  12264. */
  12265. TimeAxis.prototype.snap = function(date) {
  12266. return this.step.snap(date);
  12267. };
  12268. module.exports = TimeAxis;
  12269. /***/ },
  12270. /* 31 */
  12271. /***/ function(module, exports, __webpack_require__) {
  12272. var Hammer = __webpack_require__(45);
  12273. var util = __webpack_require__(1);
  12274. /**
  12275. * @constructor Item
  12276. * @param {Object} data Object containing (optional) parameters type,
  12277. * start, end, content, group, className.
  12278. * @param {{toScreen: function, toTime: function}} conversion
  12279. * Conversion functions from time to screen and vice versa
  12280. * @param {Object} options Configuration options
  12281. * // TODO: describe available options
  12282. */
  12283. function Item (data, conversion, options) {
  12284. this.id = null;
  12285. this.parent = null;
  12286. this.data = data;
  12287. this.dom = null;
  12288. this.conversion = conversion || {};
  12289. this.options = options || {};
  12290. this.selected = false;
  12291. this.displayed = false;
  12292. this.dirty = true;
  12293. this.top = null;
  12294. this.left = null;
  12295. this.width = null;
  12296. this.height = null;
  12297. }
  12298. Item.prototype.stack = true;
  12299. /**
  12300. * Select current item
  12301. */
  12302. Item.prototype.select = function() {
  12303. this.selected = true;
  12304. this.dirty = true;
  12305. if (this.displayed) this.redraw();
  12306. };
  12307. /**
  12308. * Unselect current item
  12309. */
  12310. Item.prototype.unselect = function() {
  12311. this.selected = false;
  12312. this.dirty = true;
  12313. if (this.displayed) this.redraw();
  12314. };
  12315. /**
  12316. * Set data for the item. Existing data will be updated. The id should not
  12317. * be changed. When the item is displayed, it will be redrawn immediately.
  12318. * @param {Object} data
  12319. */
  12320. Item.prototype.setData = function(data) {
  12321. this.data = data;
  12322. this.dirty = true;
  12323. if (this.displayed) this.redraw();
  12324. };
  12325. /**
  12326. * Set a parent for the item
  12327. * @param {ItemSet | Group} parent
  12328. */
  12329. Item.prototype.setParent = function(parent) {
  12330. if (this.displayed) {
  12331. this.hide();
  12332. this.parent = parent;
  12333. if (this.parent) {
  12334. this.show();
  12335. }
  12336. }
  12337. else {
  12338. this.parent = parent;
  12339. }
  12340. };
  12341. /**
  12342. * Check whether this item is visible inside given range
  12343. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  12344. * @returns {boolean} True if visible
  12345. */
  12346. Item.prototype.isVisible = function(range) {
  12347. // Should be implemented by Item implementations
  12348. return false;
  12349. };
  12350. /**
  12351. * Show the Item in the DOM (when not already visible)
  12352. * @return {Boolean} changed
  12353. */
  12354. Item.prototype.show = function() {
  12355. return false;
  12356. };
  12357. /**
  12358. * Hide the Item from the DOM (when visible)
  12359. * @return {Boolean} changed
  12360. */
  12361. Item.prototype.hide = function() {
  12362. return false;
  12363. };
  12364. /**
  12365. * Repaint the item
  12366. */
  12367. Item.prototype.redraw = function() {
  12368. // should be implemented by the item
  12369. };
  12370. /**
  12371. * Reposition the Item horizontally
  12372. */
  12373. Item.prototype.repositionX = function() {
  12374. // should be implemented by the item
  12375. };
  12376. /**
  12377. * Reposition the Item vertically
  12378. */
  12379. Item.prototype.repositionY = function() {
  12380. // should be implemented by the item
  12381. };
  12382. /**
  12383. * Repaint a delete button on the top right of the item when the item is selected
  12384. * @param {HTMLElement} anchor
  12385. * @protected
  12386. */
  12387. Item.prototype._repaintDeleteButton = function (anchor) {
  12388. if (this.selected && this.options.editable.remove && !this.dom.deleteButton) {
  12389. // create and show button
  12390. var me = this;
  12391. var deleteButton = document.createElement('div');
  12392. deleteButton.className = 'delete';
  12393. deleteButton.title = 'Delete this item';
  12394. Hammer(deleteButton, {
  12395. preventDefault: true
  12396. }).on('tap', function (event) {
  12397. me.parent.removeFromDataSet(me);
  12398. event.stopPropagation();
  12399. });
  12400. anchor.appendChild(deleteButton);
  12401. this.dom.deleteButton = deleteButton;
  12402. }
  12403. else if (!this.selected && this.dom.deleteButton) {
  12404. // remove button
  12405. if (this.dom.deleteButton.parentNode) {
  12406. this.dom.deleteButton.parentNode.removeChild(this.dom.deleteButton);
  12407. }
  12408. this.dom.deleteButton = null;
  12409. }
  12410. };
  12411. /**
  12412. * Set HTML contents for the item
  12413. * @param {Element} element HTML element to fill with the contents
  12414. * @private
  12415. */
  12416. Item.prototype._updateContents = function (element) {
  12417. var content;
  12418. if (this.options.template) {
  12419. var itemData = this.parent.itemSet.itemsData.get(this.id); // get a clone of the data from the dataset
  12420. content = this.options.template(itemData);
  12421. }
  12422. else {
  12423. content = this.data.content;
  12424. }
  12425. if(content !== this.content) {
  12426. // only replace the content when changed
  12427. if (content instanceof Element) {
  12428. element.innerHTML = '';
  12429. element.appendChild(content);
  12430. }
  12431. else if (content != undefined) {
  12432. element.innerHTML = content;
  12433. }
  12434. else {
  12435. if (!(this.data.type == 'background' && this.data.content === undefined)) {
  12436. throw new Error('Property "content" missing in item ' + this.id);
  12437. }
  12438. }
  12439. this.content = content;
  12440. }
  12441. };
  12442. /**
  12443. * Set HTML contents for the item
  12444. * @param {Element} element HTML element to fill with the contents
  12445. * @private
  12446. */
  12447. Item.prototype._updateTitle = function (element) {
  12448. if (this.data.title != null) {
  12449. element.title = this.data.title || '';
  12450. }
  12451. else {
  12452. element.removeAttribute('title');
  12453. }
  12454. };
  12455. /**
  12456. * Process dataAttributes timeline option and set as data- attributes on dom.content
  12457. * @param {Element} element HTML element to which the attributes will be attached
  12458. * @private
  12459. */
  12460. Item.prototype._updateDataAttributes = function(element) {
  12461. if (this.options.dataAttributes && this.options.dataAttributes.length > 0) {
  12462. var attributes = [];
  12463. if (Array.isArray(this.options.dataAttributes)) {
  12464. attributes = this.options.dataAttributes;
  12465. }
  12466. else if (this.options.dataAttributes == 'all') {
  12467. attributes = Object.keys(this.data);
  12468. }
  12469. else {
  12470. return;
  12471. }
  12472. for (var i = 0; i < attributes.length; i++) {
  12473. var name = attributes[i];
  12474. var value = this.data[name];
  12475. if (value != null) {
  12476. element.setAttribute('data-' + name, value);
  12477. }
  12478. else {
  12479. element.removeAttribute('data-' + name);
  12480. }
  12481. }
  12482. }
  12483. };
  12484. /**
  12485. * Update custom styles of the element
  12486. * @param element
  12487. * @private
  12488. */
  12489. Item.prototype._updateStyle = function(element) {
  12490. // remove old styles
  12491. if (this.style) {
  12492. util.removeCssText(element, this.style);
  12493. this.style = null;
  12494. }
  12495. // append new styles
  12496. if (this.data.style) {
  12497. util.addCssText(element, this.data.style);
  12498. this.style = this.data.style;
  12499. }
  12500. };
  12501. module.exports = Item;
  12502. /***/ },
  12503. /* 32 */
  12504. /***/ function(module, exports, __webpack_require__) {
  12505. var Hammer = __webpack_require__(45);
  12506. var Item = __webpack_require__(31);
  12507. var BackgroundGroup = __webpack_require__(26);
  12508. var RangeItem = __webpack_require__(35);
  12509. /**
  12510. * @constructor BackgroundItem
  12511. * @extends Item
  12512. * @param {Object} data Object containing parameters start, end
  12513. * content, className.
  12514. * @param {{toScreen: function, toTime: function}} conversion
  12515. * Conversion functions from time to screen and vice versa
  12516. * @param {Object} [options] Configuration options
  12517. * // TODO: describe options
  12518. */
  12519. // TODO: implement support for the BackgroundItem just having a start, then being displayed as a sort of an annotation
  12520. function BackgroundItem (data, conversion, options) {
  12521. this.props = {
  12522. content: {
  12523. width: 0
  12524. }
  12525. };
  12526. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  12527. // validate data
  12528. if (data) {
  12529. if (data.start == undefined) {
  12530. throw new Error('Property "start" missing in item ' + data.id);
  12531. }
  12532. if (data.end == undefined) {
  12533. throw new Error('Property "end" missing in item ' + data.id);
  12534. }
  12535. }
  12536. Item.call(this, data, conversion, options);
  12537. this.emptyContent = false;
  12538. }
  12539. BackgroundItem.prototype = new Item (null, null, null);
  12540. BackgroundItem.prototype.baseClassName = 'item background';
  12541. BackgroundItem.prototype.stack = false;
  12542. /**
  12543. * Check whether this item is visible inside given range
  12544. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  12545. * @returns {boolean} True if visible
  12546. */
  12547. BackgroundItem.prototype.isVisible = function(range) {
  12548. // determine visibility
  12549. return (this.data.start < range.end) && (this.data.end > range.start);
  12550. };
  12551. /**
  12552. * Repaint the item
  12553. */
  12554. BackgroundItem.prototype.redraw = function() {
  12555. var dom = this.dom;
  12556. if (!dom) {
  12557. // create DOM
  12558. this.dom = {};
  12559. dom = this.dom;
  12560. // background box
  12561. dom.box = document.createElement('div');
  12562. // className is updated in redraw()
  12563. // contents box
  12564. dom.content = document.createElement('div');
  12565. dom.content.className = 'content';
  12566. dom.box.appendChild(dom.content);
  12567. // attach this item as attribute
  12568. dom.box['timeline-item'] = this;
  12569. this.dirty = true;
  12570. }
  12571. // append DOM to parent DOM
  12572. if (!this.parent) {
  12573. throw new Error('Cannot redraw item: no parent attached');
  12574. }
  12575. if (!dom.box.parentNode) {
  12576. var background = this.parent.dom.background;
  12577. if (!background) {
  12578. throw new Error('Cannot redraw item: parent has no background container element');
  12579. }
  12580. background.appendChild(dom.box);
  12581. }
  12582. this.displayed = true;
  12583. // Update DOM when item is marked dirty. An item is marked dirty when:
  12584. // - the item is not yet rendered
  12585. // - the item's data is changed
  12586. // - the item is selected/deselected
  12587. if (this.dirty) {
  12588. this._updateContents(this.dom.content);
  12589. this._updateTitle(this.dom.content);
  12590. this._updateDataAttributes(this.dom.content);
  12591. this._updateStyle(this.dom.box);
  12592. // update class
  12593. var className = (this.data.className ? (' ' + this.data.className) : '') +
  12594. (this.selected ? ' selected' : '');
  12595. dom.box.className = this.baseClassName + className;
  12596. // determine from css whether this box has overflow
  12597. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  12598. // recalculate size
  12599. this.props.content.width = this.dom.content.offsetWidth;
  12600. this.height = 0; // set height zero, so this item will be ignored when stacking items
  12601. this.dirty = false;
  12602. }
  12603. };
  12604. /**
  12605. * Show the item in the DOM (when not already visible). The items DOM will
  12606. * be created when needed.
  12607. */
  12608. BackgroundItem.prototype.show = RangeItem.prototype.show;
  12609. /**
  12610. * Hide the item from the DOM (when visible)
  12611. * @return {Boolean} changed
  12612. */
  12613. BackgroundItem.prototype.hide = RangeItem.prototype.hide;
  12614. /**
  12615. * Reposition the item horizontally
  12616. * @Override
  12617. */
  12618. BackgroundItem.prototype.repositionX = RangeItem.prototype.repositionX;
  12619. /**
  12620. * Reposition the item vertically
  12621. * @Override
  12622. */
  12623. BackgroundItem.prototype.repositionY = function(margin) {
  12624. var onTop = this.options.orientation === 'top';
  12625. this.dom.content.style.top = onTop ? '' : '0';
  12626. this.dom.content.style.bottom = onTop ? '0' : '';
  12627. var height;
  12628. // special positioning for subgroups
  12629. if (this.data.subgroup !== undefined) {
  12630. var itemSubgroup = this.data.subgroup;
  12631. var subgroups = this.parent.subgroups;
  12632. var subgroupIndex = subgroups[itemSubgroup].index;
  12633. // if the orientation is top, we need to take the difference in height into account.
  12634. if (onTop == true) {
  12635. // the first subgroup will have to account for the distance from the top to the first item.
  12636. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  12637. height += subgroupIndex == 0 ? margin.axis - 0.5*margin.item.vertical : 0;
  12638. var newTop = this.parent.top;
  12639. for (var subgroup in subgroups) {
  12640. if (subgroups.hasOwnProperty(subgroup)) {
  12641. if (subgroups[subgroup].visible == true && subgroups[subgroup].index < subgroupIndex) {
  12642. newTop += subgroups[subgroup].height + margin.item.vertical;
  12643. }
  12644. }
  12645. }
  12646. // the others will have to be offset downwards with this same distance.
  12647. newTop += subgroupIndex != 0 ? margin.axis - 0.5 * margin.item.vertical : 0;
  12648. this.dom.box.style.top = newTop + 'px';
  12649. this.dom.box.style.bottom = '';
  12650. }
  12651. // and when the orientation is bottom:
  12652. else {
  12653. var newTop = this.parent.top;
  12654. for (var subgroup in subgroups) {
  12655. if (subgroups.hasOwnProperty(subgroup)) {
  12656. if (subgroups[subgroup].visible == true && subgroups[subgroup].index > subgroupIndex) {
  12657. newTop += subgroups[subgroup].height + margin.item.vertical;
  12658. }
  12659. }
  12660. }
  12661. height = this.parent.subgroups[itemSubgroup].height + margin.item.vertical;
  12662. this.dom.box.style.top = newTop + 'px';
  12663. this.dom.box.style.bottom = '';
  12664. }
  12665. }
  12666. // and in the case of no subgroups:
  12667. else {
  12668. // we want backgrounds with groups to only show in groups.
  12669. if (this.parent instanceof BackgroundGroup) {
  12670. // if the item is not in a group:
  12671. height = Math.max(this.parent.height, this.parent.itemSet.body.domProps.centerContainer.height);
  12672. this.dom.box.style.top = onTop ? '0' : '';
  12673. this.dom.box.style.bottom = onTop ? '' : '0';
  12674. }
  12675. else {
  12676. height = this.parent.height;
  12677. // same alignment for items when orientation is top or bottom
  12678. this.dom.box.style.top = this.parent.top + 'px';
  12679. this.dom.box.style.bottom = '';
  12680. }
  12681. }
  12682. this.dom.box.style.height = height + 'px';
  12683. };
  12684. module.exports = BackgroundItem;
  12685. /***/ },
  12686. /* 33 */
  12687. /***/ function(module, exports, __webpack_require__) {
  12688. var Item = __webpack_require__(31);
  12689. var util = __webpack_require__(1);
  12690. /**
  12691. * @constructor BoxItem
  12692. * @extends Item
  12693. * @param {Object} data Object containing parameters start
  12694. * content, className.
  12695. * @param {{toScreen: function, toTime: function}} conversion
  12696. * Conversion functions from time to screen and vice versa
  12697. * @param {Object} [options] Configuration options
  12698. * // TODO: describe available options
  12699. */
  12700. function BoxItem (data, conversion, options) {
  12701. this.props = {
  12702. dot: {
  12703. width: 0,
  12704. height: 0
  12705. },
  12706. line: {
  12707. width: 0,
  12708. height: 0
  12709. }
  12710. };
  12711. // validate data
  12712. if (data) {
  12713. if (data.start == undefined) {
  12714. throw new Error('Property "start" missing in item ' + data);
  12715. }
  12716. }
  12717. Item.call(this, data, conversion, options);
  12718. }
  12719. BoxItem.prototype = new Item (null, null, null);
  12720. /**
  12721. * Check whether this item is visible inside given range
  12722. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  12723. * @returns {boolean} True if visible
  12724. */
  12725. BoxItem.prototype.isVisible = function(range) {
  12726. // determine visibility
  12727. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  12728. var interval = (range.end - range.start) / 4;
  12729. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  12730. };
  12731. /**
  12732. * Repaint the item
  12733. */
  12734. BoxItem.prototype.redraw = function() {
  12735. var dom = this.dom;
  12736. if (!dom) {
  12737. // create DOM
  12738. this.dom = {};
  12739. dom = this.dom;
  12740. // create main box
  12741. dom.box = document.createElement('DIV');
  12742. // contents box (inside the background box). used for making margins
  12743. dom.content = document.createElement('DIV');
  12744. dom.content.className = 'content';
  12745. dom.box.appendChild(dom.content);
  12746. // line to axis
  12747. dom.line = document.createElement('DIV');
  12748. dom.line.className = 'line';
  12749. // dot on axis
  12750. dom.dot = document.createElement('DIV');
  12751. dom.dot.className = 'dot';
  12752. // attach this item as attribute
  12753. dom.box['timeline-item'] = this;
  12754. this.dirty = true;
  12755. }
  12756. // append DOM to parent DOM
  12757. if (!this.parent) {
  12758. throw new Error('Cannot redraw item: no parent attached');
  12759. }
  12760. if (!dom.box.parentNode) {
  12761. var foreground = this.parent.dom.foreground;
  12762. if (!foreground) throw new Error('Cannot redraw item: parent has no foreground container element');
  12763. foreground.appendChild(dom.box);
  12764. }
  12765. if (!dom.line.parentNode) {
  12766. var background = this.parent.dom.background;
  12767. if (!background) throw new Error('Cannot redraw item: parent has no background container element');
  12768. background.appendChild(dom.line);
  12769. }
  12770. if (!dom.dot.parentNode) {
  12771. var axis = this.parent.dom.axis;
  12772. if (!background) throw new Error('Cannot redraw item: parent has no axis container element');
  12773. axis.appendChild(dom.dot);
  12774. }
  12775. this.displayed = true;
  12776. // Update DOM when item is marked dirty. An item is marked dirty when:
  12777. // - the item is not yet rendered
  12778. // - the item's data is changed
  12779. // - the item is selected/deselected
  12780. if (this.dirty) {
  12781. this._updateContents(this.dom.content);
  12782. this._updateTitle(this.dom.box);
  12783. this._updateDataAttributes(this.dom.box);
  12784. this._updateStyle(this.dom.box);
  12785. // update class
  12786. var className = (this.data.className? ' ' + this.data.className : '') +
  12787. (this.selected ? ' selected' : '');
  12788. dom.box.className = 'item box' + className;
  12789. dom.line.className = 'item line' + className;
  12790. dom.dot.className = 'item dot' + className;
  12791. // recalculate size
  12792. this.props.dot.height = dom.dot.offsetHeight;
  12793. this.props.dot.width = dom.dot.offsetWidth;
  12794. this.props.line.width = dom.line.offsetWidth;
  12795. this.width = dom.box.offsetWidth;
  12796. this.height = dom.box.offsetHeight;
  12797. this.dirty = false;
  12798. }
  12799. this._repaintDeleteButton(dom.box);
  12800. };
  12801. /**
  12802. * Show the item in the DOM (when not already displayed). The items DOM will
  12803. * be created when needed.
  12804. */
  12805. BoxItem.prototype.show = function() {
  12806. if (!this.displayed) {
  12807. this.redraw();
  12808. }
  12809. };
  12810. /**
  12811. * Hide the item from the DOM (when visible)
  12812. */
  12813. BoxItem.prototype.hide = function() {
  12814. if (this.displayed) {
  12815. var dom = this.dom;
  12816. if (dom.box.parentNode) dom.box.parentNode.removeChild(dom.box);
  12817. if (dom.line.parentNode) dom.line.parentNode.removeChild(dom.line);
  12818. if (dom.dot.parentNode) dom.dot.parentNode.removeChild(dom.dot);
  12819. this.top = null;
  12820. this.left = null;
  12821. this.displayed = false;
  12822. }
  12823. };
  12824. /**
  12825. * Reposition the item horizontally
  12826. * @Override
  12827. */
  12828. BoxItem.prototype.repositionX = function() {
  12829. var start = this.conversion.toScreen(this.data.start);
  12830. var align = this.options.align;
  12831. var left;
  12832. var box = this.dom.box;
  12833. var line = this.dom.line;
  12834. var dot = this.dom.dot;
  12835. // calculate left position of the box
  12836. if (align == 'right') {
  12837. this.left = start - this.width;
  12838. }
  12839. else if (align == 'left') {
  12840. this.left = start;
  12841. }
  12842. else {
  12843. // default or 'center'
  12844. this.left = start - this.width / 2;
  12845. }
  12846. // reposition box
  12847. box.style.left = this.left + 'px';
  12848. // reposition line
  12849. line.style.left = (start - this.props.line.width / 2) + 'px';
  12850. // reposition dot
  12851. dot.style.left = (start - this.props.dot.width / 2) + 'px';
  12852. };
  12853. /**
  12854. * Reposition the item vertically
  12855. * @Override
  12856. */
  12857. BoxItem.prototype.repositionY = function() {
  12858. var orientation = this.options.orientation;
  12859. var box = this.dom.box;
  12860. var line = this.dom.line;
  12861. var dot = this.dom.dot;
  12862. if (orientation == 'top') {
  12863. box.style.top = (this.top || 0) + 'px';
  12864. line.style.top = '0';
  12865. line.style.height = (this.parent.top + this.top + 1) + 'px';
  12866. line.style.bottom = '';
  12867. }
  12868. else { // orientation 'bottom'
  12869. var itemSetHeight = this.parent.itemSet.props.height; // TODO: this is nasty
  12870. var lineHeight = itemSetHeight - this.parent.top - this.parent.height + this.top;
  12871. box.style.top = (this.parent.height - this.top - this.height || 0) + 'px';
  12872. line.style.top = (itemSetHeight - lineHeight) + 'px';
  12873. line.style.bottom = '0';
  12874. }
  12875. dot.style.top = (-this.props.dot.height / 2) + 'px';
  12876. };
  12877. module.exports = BoxItem;
  12878. /***/ },
  12879. /* 34 */
  12880. /***/ function(module, exports, __webpack_require__) {
  12881. var Item = __webpack_require__(31);
  12882. /**
  12883. * @constructor PointItem
  12884. * @extends Item
  12885. * @param {Object} data Object containing parameters start
  12886. * content, className.
  12887. * @param {{toScreen: function, toTime: function}} conversion
  12888. * Conversion functions from time to screen and vice versa
  12889. * @param {Object} [options] Configuration options
  12890. * // TODO: describe available options
  12891. */
  12892. function PointItem (data, conversion, options) {
  12893. this.props = {
  12894. dot: {
  12895. top: 0,
  12896. width: 0,
  12897. height: 0
  12898. },
  12899. content: {
  12900. height: 0,
  12901. marginLeft: 0
  12902. }
  12903. };
  12904. // validate data
  12905. if (data) {
  12906. if (data.start == undefined) {
  12907. throw new Error('Property "start" missing in item ' + data);
  12908. }
  12909. }
  12910. Item.call(this, data, conversion, options);
  12911. }
  12912. PointItem.prototype = new Item (null, null, null);
  12913. /**
  12914. * Check whether this item is visible inside given range
  12915. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  12916. * @returns {boolean} True if visible
  12917. */
  12918. PointItem.prototype.isVisible = function(range) {
  12919. // determine visibility
  12920. // TODO: account for the real width of the item. Right now we just add 1/4 to the window
  12921. var interval = (range.end - range.start) / 4;
  12922. return (this.data.start > range.start - interval) && (this.data.start < range.end + interval);
  12923. };
  12924. /**
  12925. * Repaint the item
  12926. */
  12927. PointItem.prototype.redraw = function() {
  12928. var dom = this.dom;
  12929. if (!dom) {
  12930. // create DOM
  12931. this.dom = {};
  12932. dom = this.dom;
  12933. // background box
  12934. dom.point = document.createElement('div');
  12935. // className is updated in redraw()
  12936. // contents box, right from the dot
  12937. dom.content = document.createElement('div');
  12938. dom.content.className = 'content';
  12939. dom.point.appendChild(dom.content);
  12940. // dot at start
  12941. dom.dot = document.createElement('div');
  12942. dom.point.appendChild(dom.dot);
  12943. // attach this item as attribute
  12944. dom.point['timeline-item'] = this;
  12945. this.dirty = true;
  12946. }
  12947. // append DOM to parent DOM
  12948. if (!this.parent) {
  12949. throw new Error('Cannot redraw item: no parent attached');
  12950. }
  12951. if (!dom.point.parentNode) {
  12952. var foreground = this.parent.dom.foreground;
  12953. if (!foreground) {
  12954. throw new Error('Cannot redraw item: parent has no foreground container element');
  12955. }
  12956. foreground.appendChild(dom.point);
  12957. }
  12958. this.displayed = true;
  12959. // Update DOM when item is marked dirty. An item is marked dirty when:
  12960. // - the item is not yet rendered
  12961. // - the item's data is changed
  12962. // - the item is selected/deselected
  12963. if (this.dirty) {
  12964. this._updateContents(this.dom.content);
  12965. this._updateTitle(this.dom.point);
  12966. this._updateDataAttributes(this.dom.point);
  12967. this._updateStyle(this.dom.point);
  12968. // update class
  12969. var className = (this.data.className? ' ' + this.data.className : '') +
  12970. (this.selected ? ' selected' : '');
  12971. dom.point.className = 'item point' + className;
  12972. dom.dot.className = 'item dot' + className;
  12973. // recalculate size
  12974. this.width = dom.point.offsetWidth;
  12975. this.height = dom.point.offsetHeight;
  12976. this.props.dot.width = dom.dot.offsetWidth;
  12977. this.props.dot.height = dom.dot.offsetHeight;
  12978. this.props.content.height = dom.content.offsetHeight;
  12979. // resize contents
  12980. dom.content.style.marginLeft = 2 * this.props.dot.width + 'px';
  12981. //dom.content.style.marginRight = ... + 'px'; // TODO: margin right
  12982. dom.dot.style.top = ((this.height - this.props.dot.height) / 2) + 'px';
  12983. dom.dot.style.left = (this.props.dot.width / 2) + 'px';
  12984. this.dirty = false;
  12985. }
  12986. this._repaintDeleteButton(dom.point);
  12987. };
  12988. /**
  12989. * Show the item in the DOM (when not already visible). The items DOM will
  12990. * be created when needed.
  12991. */
  12992. PointItem.prototype.show = function() {
  12993. if (!this.displayed) {
  12994. this.redraw();
  12995. }
  12996. };
  12997. /**
  12998. * Hide the item from the DOM (when visible)
  12999. */
  13000. PointItem.prototype.hide = function() {
  13001. if (this.displayed) {
  13002. if (this.dom.point.parentNode) {
  13003. this.dom.point.parentNode.removeChild(this.dom.point);
  13004. }
  13005. this.top = null;
  13006. this.left = null;
  13007. this.displayed = false;
  13008. }
  13009. };
  13010. /**
  13011. * Reposition the item horizontally
  13012. * @Override
  13013. */
  13014. PointItem.prototype.repositionX = function() {
  13015. var start = this.conversion.toScreen(this.data.start);
  13016. this.left = start - this.props.dot.width;
  13017. // reposition point
  13018. this.dom.point.style.left = this.left + 'px';
  13019. };
  13020. /**
  13021. * Reposition the item vertically
  13022. * @Override
  13023. */
  13024. PointItem.prototype.repositionY = function() {
  13025. var orientation = this.options.orientation,
  13026. point = this.dom.point;
  13027. if (orientation == 'top') {
  13028. point.style.top = this.top + 'px';
  13029. }
  13030. else {
  13031. point.style.top = (this.parent.height - this.top - this.height) + 'px';
  13032. }
  13033. };
  13034. module.exports = PointItem;
  13035. /***/ },
  13036. /* 35 */
  13037. /***/ function(module, exports, __webpack_require__) {
  13038. var Hammer = __webpack_require__(45);
  13039. var Item = __webpack_require__(31);
  13040. /**
  13041. * @constructor RangeItem
  13042. * @extends Item
  13043. * @param {Object} data Object containing parameters start, end
  13044. * content, className.
  13045. * @param {{toScreen: function, toTime: function}} conversion
  13046. * Conversion functions from time to screen and vice versa
  13047. * @param {Object} [options] Configuration options
  13048. * // TODO: describe options
  13049. */
  13050. function RangeItem (data, conversion, options) {
  13051. this.props = {
  13052. content: {
  13053. width: 0
  13054. }
  13055. };
  13056. this.overflow = false; // if contents can overflow (css styling), this flag is set to true
  13057. // validate data
  13058. if (data) {
  13059. if (data.start == undefined) {
  13060. throw new Error('Property "start" missing in item ' + data.id);
  13061. }
  13062. if (data.end == undefined) {
  13063. throw new Error('Property "end" missing in item ' + data.id);
  13064. }
  13065. }
  13066. Item.call(this, data, conversion, options);
  13067. }
  13068. RangeItem.prototype = new Item (null, null, null);
  13069. RangeItem.prototype.baseClassName = 'item range';
  13070. /**
  13071. * Check whether this item is visible inside given range
  13072. * @returns {{start: Number, end: Number}} range with a timestamp for start and end
  13073. * @returns {boolean} True if visible
  13074. */
  13075. RangeItem.prototype.isVisible = function(range) {
  13076. // determine visibility
  13077. return (this.data.start < range.end) && (this.data.end > range.start);
  13078. };
  13079. /**
  13080. * Repaint the item
  13081. */
  13082. RangeItem.prototype.redraw = function() {
  13083. var dom = this.dom;
  13084. if (!dom) {
  13085. // create DOM
  13086. this.dom = {};
  13087. dom = this.dom;
  13088. // background box
  13089. dom.box = document.createElement('div');
  13090. // className is updated in redraw()
  13091. // contents box
  13092. dom.content = document.createElement('div');
  13093. dom.content.className = 'content';
  13094. dom.box.appendChild(dom.content);
  13095. // attach this item as attribute
  13096. dom.box['timeline-item'] = this;
  13097. this.dirty = true;
  13098. }
  13099. // append DOM to parent DOM
  13100. if (!this.parent) {
  13101. throw new Error('Cannot redraw item: no parent attached');
  13102. }
  13103. if (!dom.box.parentNode) {
  13104. var foreground = this.parent.dom.foreground;
  13105. if (!foreground) {
  13106. throw new Error('Cannot redraw item: parent has no foreground container element');
  13107. }
  13108. foreground.appendChild(dom.box);
  13109. }
  13110. this.displayed = true;
  13111. // Update DOM when item is marked dirty. An item is marked dirty when:
  13112. // - the item is not yet rendered
  13113. // - the item's data is changed
  13114. // - the item is selected/deselected
  13115. if (this.dirty) {
  13116. this._updateContents(this.dom.content);
  13117. this._updateTitle(this.dom.box);
  13118. this._updateDataAttributes(this.dom.box);
  13119. this._updateStyle(this.dom.box);
  13120. // update class
  13121. var className = (this.data.className ? (' ' + this.data.className) : '') +
  13122. (this.selected ? ' selected' : '');
  13123. dom.box.className = this.baseClassName + className;
  13124. // determine from css whether this box has overflow
  13125. this.overflow = window.getComputedStyle(dom.content).overflow !== 'hidden';
  13126. // recalculate size
  13127. this.props.content.width = this.dom.content.offsetWidth;
  13128. this.height = this.dom.box.offsetHeight;
  13129. this.dirty = false;
  13130. }
  13131. this._repaintDeleteButton(dom.box);
  13132. this._repaintDragLeft();
  13133. this._repaintDragRight();
  13134. };
  13135. /**
  13136. * Show the item in the DOM (when not already visible). The items DOM will
  13137. * be created when needed.
  13138. */
  13139. RangeItem.prototype.show = function() {
  13140. if (!this.displayed) {
  13141. this.redraw();
  13142. }
  13143. };
  13144. /**
  13145. * Hide the item from the DOM (when visible)
  13146. * @return {Boolean} changed
  13147. */
  13148. RangeItem.prototype.hide = function() {
  13149. if (this.displayed) {
  13150. var box = this.dom.box;
  13151. if (box.parentNode) {
  13152. box.parentNode.removeChild(box);
  13153. }
  13154. this.top = null;
  13155. this.left = null;
  13156. this.displayed = false;
  13157. }
  13158. };
  13159. /**
  13160. * Reposition the item horizontally
  13161. * @Override
  13162. */
  13163. RangeItem.prototype.repositionX = function() {
  13164. var parentWidth = this.parent.width;
  13165. var start = this.conversion.toScreen(this.data.start);
  13166. var end = this.conversion.toScreen(this.data.end);
  13167. var contentLeft;
  13168. var contentWidth;
  13169. // limit the width of the this, as browsers cannot draw very wide divs
  13170. if (start < -parentWidth) {
  13171. start = -parentWidth;
  13172. }
  13173. if (end > 2 * parentWidth) {
  13174. end = 2 * parentWidth;
  13175. }
  13176. var boxWidth = Math.max(end - start, 1);
  13177. if (this.overflow) {
  13178. this.left = start;
  13179. this.width = boxWidth + this.props.content.width;
  13180. contentWidth = this.props.content.width;
  13181. // Note: The calculation of width is an optimistic calculation, giving
  13182. // a width which will not change when moving the Timeline
  13183. // So no re-stacking needed, which is nicer for the eye;
  13184. }
  13185. else {
  13186. this.left = start;
  13187. this.width = boxWidth;
  13188. contentWidth = Math.min(end - start, this.props.content.width);
  13189. }
  13190. this.dom.box.style.left = this.left + 'px';
  13191. this.dom.box.style.width = boxWidth + 'px';
  13192. switch (this.options.align) {
  13193. case 'left':
  13194. this.dom.content.style.left = '0';
  13195. break;
  13196. case 'right':
  13197. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding), 0) + 'px';
  13198. break;
  13199. case 'center':
  13200. this.dom.content.style.left = Math.max((boxWidth - contentWidth - 2 * this.options.padding) / 2, 0) + 'px';
  13201. break;
  13202. default: // 'auto'
  13203. if (this.overflow) {
  13204. // when range exceeds left of the window, position the contents at the left of the visible area
  13205. contentLeft = Math.max(-start, 0);
  13206. }
  13207. else {
  13208. // when range exceeds left of the window, position the contents at the left of the visible area
  13209. if (start < 0) {
  13210. contentLeft = Math.min(-start,
  13211. (end - start - this.props.content.width - 2 * this.options.padding));
  13212. // TODO: remove the need for options.padding. it's terrible.
  13213. }
  13214. else {
  13215. contentLeft = 0;
  13216. }
  13217. }
  13218. this.dom.content.style.left = contentLeft + 'px';
  13219. }
  13220. };
  13221. /**
  13222. * Reposition the item vertically
  13223. * @Override
  13224. */
  13225. RangeItem.prototype.repositionY = function() {
  13226. var orientation = this.options.orientation,
  13227. box = this.dom.box;
  13228. if (orientation == 'top') {
  13229. box.style.top = this.top + 'px';
  13230. }
  13231. else {
  13232. box.style.top = (this.parent.height - this.top - this.height) + 'px';
  13233. }
  13234. };
  13235. /**
  13236. * Repaint a drag area on the left side of the range when the range is selected
  13237. * @protected
  13238. */
  13239. RangeItem.prototype._repaintDragLeft = function () {
  13240. if (this.selected && this.options.editable.updateTime && !this.dom.dragLeft) {
  13241. // create and show drag area
  13242. var dragLeft = document.createElement('div');
  13243. dragLeft.className = 'drag-left';
  13244. dragLeft.dragLeftItem = this;
  13245. // TODO: this should be redundant?
  13246. Hammer(dragLeft, {
  13247. preventDefault: true
  13248. }).on('drag', function () {
  13249. //console.log('drag left')
  13250. });
  13251. this.dom.box.appendChild(dragLeft);
  13252. this.dom.dragLeft = dragLeft;
  13253. }
  13254. else if (!this.selected && this.dom.dragLeft) {
  13255. // delete drag area
  13256. if (this.dom.dragLeft.parentNode) {
  13257. this.dom.dragLeft.parentNode.removeChild(this.dom.dragLeft);
  13258. }
  13259. this.dom.dragLeft = null;
  13260. }
  13261. };
  13262. /**
  13263. * Repaint a drag area on the right side of the range when the range is selected
  13264. * @protected
  13265. */
  13266. RangeItem.prototype._repaintDragRight = function () {
  13267. if (this.selected && this.options.editable.updateTime && !this.dom.dragRight) {
  13268. // create and show drag area
  13269. var dragRight = document.createElement('div');
  13270. dragRight.className = 'drag-right';
  13271. dragRight.dragRightItem = this;
  13272. // TODO: this should be redundant?
  13273. Hammer(dragRight, {
  13274. preventDefault: true
  13275. }).on('drag', function () {
  13276. //console.log('drag right')
  13277. });
  13278. this.dom.box.appendChild(dragRight);
  13279. this.dom.dragRight = dragRight;
  13280. }
  13281. else if (!this.selected && this.dom.dragRight) {
  13282. // delete drag area
  13283. if (this.dom.dragRight.parentNode) {
  13284. this.dom.dragRight.parentNode.removeChild(this.dom.dragRight);
  13285. }
  13286. this.dom.dragRight = null;
  13287. }
  13288. };
  13289. module.exports = RangeItem;
  13290. /***/ },
  13291. /* 36 */
  13292. /***/ function(module, exports, __webpack_require__) {
  13293. var Emitter = __webpack_require__(53);
  13294. var Hammer = __webpack_require__(45);
  13295. var mousetrap = __webpack_require__(56);
  13296. var util = __webpack_require__(1);
  13297. var hammerUtil = __webpack_require__(47);
  13298. var DataSet = __webpack_require__(3);
  13299. var DataView = __webpack_require__(4);
  13300. var dotparser = __webpack_require__(42);
  13301. var gephiParser = __webpack_require__(43);
  13302. var Groups = __webpack_require__(38);
  13303. var Images = __webpack_require__(39);
  13304. var Node = __webpack_require__(40);
  13305. var Edge = __webpack_require__(37);
  13306. var Popup = __webpack_require__(41);
  13307. var MixinLoader = __webpack_require__(51);
  13308. var Activator = __webpack_require__(52);
  13309. var locales = __webpack_require__(49);
  13310. // Load custom shapes into CanvasRenderingContext2D
  13311. __webpack_require__(50);
  13312. /**
  13313. * @constructor Network
  13314. * Create a network visualization, displaying nodes and edges.
  13315. *
  13316. * @param {Element} container The DOM element in which the Network will
  13317. * be created. Normally a div element.
  13318. * @param {Object} data An object containing parameters
  13319. * {Array} nodes
  13320. * {Array} edges
  13321. * @param {Object} options Options
  13322. */
  13323. function Network (container, data, options) {
  13324. if (!(this instanceof Network)) {
  13325. throw new SyntaxError('Constructor must be called with the new operator');
  13326. }
  13327. this._initializeMixinLoaders();
  13328. // create variables and set default values
  13329. this.containerElement = container;
  13330. // render and calculation settings
  13331. this.renderRefreshRate = 60; // hz (fps)
  13332. this.renderTimestep = 1000 / this.renderRefreshRate; // ms -- saves calculation later on
  13333. this.renderTime = 0.5 * this.renderTimestep; // measured time it takes to render a frame
  13334. this.maxPhysicsTicksPerRender = 3; // max amount of physics ticks per render step.
  13335. this.physicsDiscreteStepsize = 0.50; // discrete stepsize of the simulation
  13336. this.initializing = true;
  13337. this.triggerFunctions = {add:null,edit:null,editEdge:null,connect:null,del:null};
  13338. // set constant values
  13339. this.defaultOptions = {
  13340. nodes: {
  13341. mass: 1,
  13342. radiusMin: 10,
  13343. radiusMax: 30,
  13344. radius: 10,
  13345. shape: 'ellipse',
  13346. image: undefined,
  13347. widthMin: 16, // px
  13348. widthMax: 64, // px
  13349. fontColor: 'black',
  13350. fontSize: 14, // px
  13351. fontFace: 'verdana',
  13352. fontFill: undefined,
  13353. level: -1,
  13354. color: {
  13355. border: '#2B7CE9',
  13356. background: '#97C2FC',
  13357. highlight: {
  13358. border: '#2B7CE9',
  13359. background: '#D2E5FF'
  13360. },
  13361. hover: {
  13362. border: '#2B7CE9',
  13363. background: '#D2E5FF'
  13364. }
  13365. },
  13366. borderColor: '#2B7CE9',
  13367. backgroundColor: '#97C2FC',
  13368. highlightColor: '#D2E5FF',
  13369. group: undefined,
  13370. borderWidth: 1,
  13371. borderWidthSelected: undefined
  13372. },
  13373. edges: {
  13374. widthMin: 1, //
  13375. widthMax: 15,//
  13376. width: 1,
  13377. widthSelectionMultiplier: 2,
  13378. hoverWidth: 1.5,
  13379. style: 'line',
  13380. color: {
  13381. color:'#848484',
  13382. highlight:'#848484',
  13383. hover: '#848484'
  13384. },
  13385. fontColor: '#343434',
  13386. fontSize: 14, // px
  13387. fontFace: 'arial',
  13388. fontFill: 'white',
  13389. arrowScaleFactor: 1,
  13390. dash: {
  13391. length: 10,
  13392. gap: 5,
  13393. altLength: undefined
  13394. },
  13395. inheritColor: "from" // to, from, false, true (== from)
  13396. },
  13397. configurePhysics:false,
  13398. physics: {
  13399. barnesHut: {
  13400. enabled: true,
  13401. theta: 1 / 0.6, // inverted to save time during calculation
  13402. gravitationalConstant: -2000,
  13403. centralGravity: 0.3,
  13404. springLength: 95,
  13405. springConstant: 0.04,
  13406. damping: 0.09
  13407. },
  13408. repulsion: {
  13409. centralGravity: 0.0,
  13410. springLength: 200,
  13411. springConstant: 0.05,
  13412. nodeDistance: 100,
  13413. damping: 0.09
  13414. },
  13415. hierarchicalRepulsion: {
  13416. enabled: false,
  13417. centralGravity: 0.0,
  13418. springLength: 100,
  13419. springConstant: 0.01,
  13420. nodeDistance: 150,
  13421. damping: 0.09
  13422. },
  13423. damping: null,
  13424. centralGravity: null,
  13425. springLength: null,
  13426. springConstant: null
  13427. },
  13428. clustering: { // Per Node in Cluster = PNiC
  13429. enabled: false, // (Boolean) | global on/off switch for clustering.
  13430. initialMaxNodes: 100, // (# nodes) | if the initial amount of nodes is larger than this, we cluster until the total number is less than this threshold.
  13431. clusterThreshold:500, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than this. If it is, cluster until reduced to reduceToNodes
  13432. reduceToNodes:300, // (# nodes) | during calculate forces, we check if the total number of nodes is larger than clusterThreshold. If it is, cluster until reduced to this
  13433. chainThreshold: 0.4, // (% of all drawn nodes)| maximum percentage of allowed chainnodes (long strings of connected nodes) within all nodes. (lower means less chains).
  13434. clusterEdgeThreshold: 20, // (px) | edge length threshold. if smaller, this node is clustered.
  13435. sectorThreshold: 100, // (# nodes in cluster) | cluster size threshold. If larger, expanding in own sector.
  13436. screenSizeThreshold: 0.2, // (% of canvas) | relative size threshold. If the width or height of a clusternode takes up this much of the screen, decluster node.
  13437. fontSizeMultiplier: 4.0, // (px PNiC) | how much the cluster font size grows per node in cluster (in px).
  13438. maxFontSize: 1000,
  13439. forceAmplification: 0.1, // (multiplier PNiC) | factor of increase fo the repulsion force of a cluster (per node in cluster).
  13440. distanceAmplification: 0.1, // (multiplier PNiC) | factor how much the repulsion distance of a cluster increases (per node in cluster).
  13441. edgeGrowth: 20, // (px PNiC) | amount of clusterSize connected to the edge is multiplied with this and added to edgeLength.
  13442. nodeScaling: {width: 1, // (px PNiC) | growth of the width per node in cluster.
  13443. height: 1, // (px PNiC) | growth of the height per node in cluster.
  13444. radius: 1}, // (px PNiC) | growth of the radius per node in cluster.
  13445. maxNodeSizeIncrements: 600, // (# increments) | max growth of the width per node in cluster.
  13446. activeAreaBoxSize: 80, // (px) | box area around the curser where clusters are popped open.
  13447. clusterLevelDifference: 2
  13448. },
  13449. navigation: {
  13450. enabled: false
  13451. },
  13452. keyboard: {
  13453. enabled: false,
  13454. speed: {x: 10, y: 10, zoom: 0.02}
  13455. },
  13456. dataManipulation: {
  13457. enabled: false,
  13458. initiallyVisible: false
  13459. },
  13460. hierarchicalLayout: {
  13461. enabled:false,
  13462. levelSeparation: 150,
  13463. nodeSpacing: 100,
  13464. direction: "UD", // UD, DU, LR, RL
  13465. layout: "hubsize" // hubsize, directed
  13466. },
  13467. freezeForStabilization: false,
  13468. smoothCurves: {
  13469. enabled: true,
  13470. dynamic: true,
  13471. type: "continuous",
  13472. roundness: 0.5
  13473. },
  13474. dynamicSmoothCurves: true,
  13475. maxVelocity: 30,
  13476. minVelocity: 0.1, // px/s
  13477. stabilize: true, // stabilize before displaying the network
  13478. stabilizationIterations: 1000, // maximum number of iteration to stabilize
  13479. locale: 'en',
  13480. locales: locales,
  13481. tooltip: {
  13482. delay: 300,
  13483. fontColor: 'black',
  13484. fontSize: 14, // px
  13485. fontFace: 'verdana',
  13486. color: {
  13487. border: '#666',
  13488. background: '#FFFFC6'
  13489. }
  13490. },
  13491. dragNetwork: true,
  13492. dragNodes: true,
  13493. zoomable: true,
  13494. hover: false,
  13495. hideEdgesOnDrag: false,
  13496. hideNodesOnDrag: false,
  13497. width : '100%',
  13498. height : '100%',
  13499. selectable: true
  13500. };
  13501. this.constants = util.extend({}, this.defaultOptions);
  13502. this.hoverObj = {nodes:{},edges:{}};
  13503. this.controlNodesActive = false;
  13504. this.navigationHammers = {existing:[], new: []};
  13505. // animation properties
  13506. this.animationSpeed = 1/this.renderRefreshRate;
  13507. this.animationEasingFunction = "easeInOutQuint";
  13508. this.easingTime = 0;
  13509. this.sourceScale = 0;
  13510. this.targetScale = 0;
  13511. this.sourceTranslation = 0;
  13512. this.targetTranslation = 0;
  13513. this.lockedOnNodeId = null;
  13514. this.lockedOnNodeOffset = null;
  13515. // Node variables
  13516. var network = this;
  13517. this.groups = new Groups(); // object with groups
  13518. this.images = new Images(); // object with images
  13519. this.images.setOnloadCallback(function () {
  13520. network._redraw();
  13521. });
  13522. // keyboard navigation variables
  13523. this.xIncrement = 0;
  13524. this.yIncrement = 0;
  13525. this.zoomIncrement = 0;
  13526. // loading all the mixins:
  13527. // load the force calculation functions, grouped under the physics system.
  13528. this._loadPhysicsSystem();
  13529. // create a frame and canvas
  13530. this._create();
  13531. // load the sector system. (mandatory, fully integrated with Network)
  13532. this._loadSectorSystem();
  13533. // load the cluster system. (mandatory, even when not using the cluster system, there are function calls to it)
  13534. this._loadClusterSystem();
  13535. // load the selection system. (mandatory, required by Network)
  13536. this._loadSelectionSystem();
  13537. // load the selection system. (mandatory, required by Network)
  13538. this._loadHierarchySystem();
  13539. // apply options
  13540. this._setTranslation(this.frame.clientWidth / 2, this.frame.clientHeight / 2);
  13541. this._setScale(1);
  13542. this.setOptions(options);
  13543. // other vars
  13544. this.freezeSimulation = false;// freeze the simulation
  13545. this.cachedFunctions = {};
  13546. this.startedStabilization = false;
  13547. this.stabilized = false;
  13548. this.stabilizationIterations = null;
  13549. // containers for nodes and edges
  13550. this.calculationNodes = {};
  13551. this.calculationNodeIndices = [];
  13552. this.nodeIndices = []; // array with all the indices of the nodes. Used to speed up forces calculation
  13553. this.nodes = {}; // object with Node objects
  13554. this.edges = {}; // object with Edge objects
  13555. // position and scale variables and objects
  13556. this.canvasTopLeft = {"x": 0,"y": 0}; // coordinates of the top left of the canvas. they will be set during _redraw.
  13557. this.canvasBottomRight = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  13558. this.pointerPosition = {"x": 0,"y": 0}; // coordinates of the bottom right of the canvas. they will be set during _redraw
  13559. this.areaCenter = {}; // object with x and y elements used for determining the center of the zoom action
  13560. this.scale = 1; // defining the global scale variable in the constructor
  13561. this.previousScale = this.scale; // this is used to check if the zoom operation is zooming in or out
  13562. // datasets or dataviews
  13563. this.nodesData = null; // A DataSet or DataView
  13564. this.edgesData = null; // A DataSet or DataView
  13565. // create event listeners used to subscribe on the DataSets of the nodes and edges
  13566. this.nodesListeners = {
  13567. 'add': function (event, params) {
  13568. network._addNodes(params.items);
  13569. network.start();
  13570. },
  13571. 'update': function (event, params) {
  13572. network._updateNodes(params.items, params.data);
  13573. network.start();
  13574. },
  13575. 'remove': function (event, params) {
  13576. network._removeNodes(params.items);
  13577. network.start();
  13578. }
  13579. };
  13580. this.edgesListeners = {
  13581. 'add': function (event, params) {
  13582. network._addEdges(params.items);
  13583. network.start();
  13584. },
  13585. 'update': function (event, params) {
  13586. network._updateEdges(params.items);
  13587. network.start();
  13588. },
  13589. 'remove': function (event, params) {
  13590. network._removeEdges(params.items);
  13591. network.start();
  13592. }
  13593. };
  13594. // properties for the animation
  13595. this.moving = true;
  13596. this.timer = undefined; // Scheduling function. Is definded in this.start();
  13597. // load data (the disable start variable will be the same as the enabled clustering)
  13598. this.setData(data,this.constants.clustering.enabled || this.constants.hierarchicalLayout.enabled);
  13599. // hierarchical layout
  13600. this.initializing = false;
  13601. if (this.constants.hierarchicalLayout.enabled == true) {
  13602. this._setupHierarchicalLayout();
  13603. }
  13604. else {
  13605. // zoom so all data will fit on the screen, if clustering is enabled, we do not want start to be called here.
  13606. if (this.constants.stabilize == false) {
  13607. this.zoomExtent(undefined, true,this.constants.clustering.enabled);
  13608. }
  13609. }
  13610. // if clustering is disabled, the simulation will have started in the setData function
  13611. if (this.constants.clustering.enabled) {
  13612. this.startWithClustering();
  13613. }
  13614. }
  13615. // Extend Network with an Emitter mixin
  13616. Emitter(Network.prototype);
  13617. /**
  13618. * Get the script path where the vis.js library is located
  13619. *
  13620. * @returns {string | null} path Path or null when not found. Path does not
  13621. * end with a slash.
  13622. * @private
  13623. */
  13624. Network.prototype._getScriptPath = function() {
  13625. var scripts = document.getElementsByTagName( 'script' );
  13626. // find script named vis.js or vis.min.js
  13627. for (var i = 0; i < scripts.length; i++) {
  13628. var src = scripts[i].src;
  13629. var match = src && /\/?vis(.min)?\.js$/.exec(src);
  13630. if (match) {
  13631. // return path without the script name
  13632. return src.substring(0, src.length - match[0].length);
  13633. }
  13634. }
  13635. return null;
  13636. };
  13637. /**
  13638. * Find the center position of the network
  13639. * @private
  13640. */
  13641. Network.prototype._getRange = function() {
  13642. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  13643. for (var nodeId in this.nodes) {
  13644. if (this.nodes.hasOwnProperty(nodeId)) {
  13645. node = this.nodes[nodeId];
  13646. if (minX > (node.x)) {minX = node.x;}
  13647. if (maxX < (node.x)) {maxX = node.x;}
  13648. if (minY > (node.y)) {minY = node.y;}
  13649. if (maxY < (node.y)) {maxY = node.y;}
  13650. }
  13651. }
  13652. if (minX == 1e9 && maxX == -1e9 && minY == 1e9 && maxY == -1e9) {
  13653. minY = 0, maxY = 0, minX = 0, maxX = 0;
  13654. }
  13655. return {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  13656. };
  13657. /**
  13658. * @param {object} range = {minX: minX, maxX: maxX, minY: minY, maxY: maxY};
  13659. * @returns {{x: number, y: number}}
  13660. * @private
  13661. */
  13662. Network.prototype._findCenter = function(range) {
  13663. return {x: (0.5 * (range.maxX + range.minX)),
  13664. y: (0.5 * (range.maxY + range.minY))};
  13665. };
  13666. /**
  13667. * This function zooms out to fit all data on screen based on amount of nodes
  13668. *
  13669. * @param {Boolean} [initialZoom] | zoom based on fitted formula or range, true = fitted, default = false;
  13670. * @param {Boolean} [disableStart] | If true, start is not called.
  13671. */
  13672. Network.prototype.zoomExtent = function(animationOptions, initialZoom, disableStart) {
  13673. if (initialZoom === undefined) {
  13674. initialZoom = false;
  13675. }
  13676. if (disableStart === undefined) {
  13677. disableStart = false;
  13678. }
  13679. if (animationOptions === undefined) {
  13680. animationOptions = false;
  13681. }
  13682. var range = this._getRange();
  13683. var zoomLevel;
  13684. if (initialZoom == true) {
  13685. var numberOfNodes = this.nodeIndices.length;
  13686. if (this.constants.smoothCurves == true) {
  13687. if (this.constants.clustering.enabled == true &&
  13688. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  13689. zoomLevel = 49.07548 / (numberOfNodes + 142.05338) + 9.1444e-04; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  13690. }
  13691. else {
  13692. zoomLevel = 12.662 / (numberOfNodes + 7.4147) + 0.0964822; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  13693. }
  13694. }
  13695. else {
  13696. if (this.constants.clustering.enabled == true &&
  13697. numberOfNodes >= this.constants.clustering.initialMaxNodes) {
  13698. zoomLevel = 77.5271985 / (numberOfNodes + 187.266146) + 4.76710517e-05; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  13699. }
  13700. else {
  13701. zoomLevel = 30.5062972 / (numberOfNodes + 19.93597763) + 0.08413486; // this is obtained from fitting a dataset from 5 points with scale levels that looked good.
  13702. }
  13703. }
  13704. // correct for larger canvasses.
  13705. var factor = Math.min(this.frame.canvas.clientWidth / 600, this.frame.canvas.clientHeight / 600);
  13706. zoomLevel *= factor;
  13707. }
  13708. else {
  13709. var xDistance = Math.abs(range.maxX - range.minX) * 1.1;
  13710. var yDistance = Math.abs(range.maxY - range.minY) * 1.1;
  13711. var xZoomLevel = this.frame.canvas.clientWidth / xDistance;
  13712. var yZoomLevel = this.frame.canvas.clientHeight / yDistance;
  13713. zoomLevel = (xZoomLevel <= yZoomLevel) ? xZoomLevel : yZoomLevel;
  13714. }
  13715. if (zoomLevel > 1.0) {
  13716. zoomLevel = 1.0;
  13717. }
  13718. var center = this._findCenter(range);
  13719. if (disableStart == false) {
  13720. var options = {position: center, scale: zoomLevel, animation: animationOptions};
  13721. this.moveTo(options);
  13722. this.moving = true;
  13723. this.start();
  13724. }
  13725. else {
  13726. center.x *= zoomLevel;
  13727. center.y *= zoomLevel;
  13728. center.x -= 0.5 * this.frame.canvas.clientWidth;
  13729. center.y -= 0.5 * this.frame.canvas.clientHeight;
  13730. this._setScale(zoomLevel);
  13731. this._setTranslation(-center.x,-center.y);
  13732. }
  13733. };
  13734. /**
  13735. * Update the this.nodeIndices with the most recent node index list
  13736. * @private
  13737. */
  13738. Network.prototype._updateNodeIndexList = function() {
  13739. this._clearNodeIndexList();
  13740. for (var idx in this.nodes) {
  13741. if (this.nodes.hasOwnProperty(idx)) {
  13742. this.nodeIndices.push(idx);
  13743. }
  13744. }
  13745. };
  13746. /**
  13747. * Set nodes and edges, and optionally options as well.
  13748. *
  13749. * @param {Object} data Object containing parameters:
  13750. * {Array | DataSet | DataView} [nodes] Array with nodes
  13751. * {Array | DataSet | DataView} [edges] Array with edges
  13752. * {String} [dot] String containing data in DOT format
  13753. * {String} [gephi] String containing data in gephi JSON format
  13754. * {Options} [options] Object with options
  13755. * @param {Boolean} [disableStart] | optional: disable the calling of the start function.
  13756. */
  13757. Network.prototype.setData = function(data, disableStart) {
  13758. if (disableStart === undefined) {
  13759. disableStart = false;
  13760. }
  13761. // we set initializing to true to ensure that the hierarchical layout is not performed until both nodes and edges are added.
  13762. this.initializing = true;
  13763. if (data && data.dot && (data.nodes || data.edges)) {
  13764. throw new SyntaxError('Data must contain either parameter "dot" or ' +
  13765. ' parameter pair "nodes" and "edges", but not both.');
  13766. }
  13767. // set options
  13768. this.setOptions(data && data.options);
  13769. // set all data
  13770. if (data && data.dot) {
  13771. // parse DOT file
  13772. if(data && data.dot) {
  13773. var dotData = dotparser.DOTToGraph(data.dot);
  13774. this.setData(dotData);
  13775. return;
  13776. }
  13777. }
  13778. else if (data && data.gephi) {
  13779. // parse DOT file
  13780. if(data && data.gephi) {
  13781. var gephiData = gephiParser.parseGephi(data.gephi);
  13782. this.setData(gephiData);
  13783. return;
  13784. }
  13785. }
  13786. else {
  13787. this._setNodes(data && data.nodes);
  13788. this._setEdges(data && data.edges);
  13789. }
  13790. this._putDataInSector();
  13791. if (disableStart == false) {
  13792. if (this.constants.hierarchicalLayout.enabled == true) {
  13793. this._resetLevels();
  13794. this._setupHierarchicalLayout();
  13795. }
  13796. else {
  13797. // find a stable position or start animating to a stable position
  13798. if (this.constants.stabilize) {
  13799. this._stabilize();
  13800. }
  13801. }
  13802. this.start();
  13803. }
  13804. this.initializing = false;
  13805. };
  13806. /**
  13807. * Set options
  13808. * @param {Object} options
  13809. */
  13810. Network.prototype.setOptions = function (options) {
  13811. if (options) {
  13812. var prop;
  13813. var fields = ['nodes','edges','smoothCurves','hierarchicalLayout','clustering','navigation','keyboard','dataManipulation',
  13814. 'onAdd','onEdit','onEditEdge','onConnect','onDelete','clickToUse'
  13815. ];
  13816. util.selectiveNotDeepExtend(fields,this.constants, options);
  13817. util.selectiveNotDeepExtend(['color'],this.constants.nodes, options.nodes);
  13818. util.selectiveNotDeepExtend(['color','length'],this.constants.edges, options.edges);
  13819. if (options.physics) {
  13820. util.mergeOptions(this.constants.physics, options.physics,'barnesHut');
  13821. util.mergeOptions(this.constants.physics, options.physics,'repulsion');
  13822. if (options.physics.hierarchicalRepulsion) {
  13823. this.constants.hierarchicalLayout.enabled = true;
  13824. this.constants.physics.hierarchicalRepulsion.enabled = true;
  13825. this.constants.physics.barnesHut.enabled = false;
  13826. for (prop in options.physics.hierarchicalRepulsion) {
  13827. if (options.physics.hierarchicalRepulsion.hasOwnProperty(prop)) {
  13828. this.constants.physics.hierarchicalRepulsion[prop] = options.physics.hierarchicalRepulsion[prop];
  13829. }
  13830. }
  13831. }
  13832. }
  13833. if (options.onAdd) {this.triggerFunctions.add = options.onAdd;}
  13834. if (options.onEdit) {this.triggerFunctions.edit = options.onEdit;}
  13835. if (options.onEditEdge) {this.triggerFunctions.editEdge = options.onEditEdge;}
  13836. if (options.onConnect) {this.triggerFunctions.connect = options.onConnect;}
  13837. if (options.onDelete) {this.triggerFunctions.del = options.onDelete;}
  13838. util.mergeOptions(this.constants, options,'smoothCurves');
  13839. util.mergeOptions(this.constants, options,'hierarchicalLayout');
  13840. util.mergeOptions(this.constants, options,'clustering');
  13841. util.mergeOptions(this.constants, options,'navigation');
  13842. util.mergeOptions(this.constants, options,'keyboard');
  13843. util.mergeOptions(this.constants, options,'dataManipulation');
  13844. if (options.dataManipulation) {
  13845. this.editMode = this.constants.dataManipulation.initiallyVisible;
  13846. }
  13847. // TODO: work out these options and document them
  13848. if (options.edges) {
  13849. if (options.edges.color !== undefined) {
  13850. if (util.isString(options.edges.color)) {
  13851. this.constants.edges.color = {};
  13852. this.constants.edges.color.color = options.edges.color;
  13853. this.constants.edges.color.highlight = options.edges.color;
  13854. this.constants.edges.color.hover = options.edges.color;
  13855. }
  13856. else {
  13857. if (options.edges.color.color !== undefined) {this.constants.edges.color.color = options.edges.color.color;}
  13858. if (options.edges.color.highlight !== undefined) {this.constants.edges.color.highlight = options.edges.color.highlight;}
  13859. if (options.edges.color.hover !== undefined) {this.constants.edges.color.hover = options.edges.color.hover;}
  13860. }
  13861. }
  13862. if (!options.edges.fontColor) {
  13863. if (options.edges.color !== undefined) {
  13864. if (util.isString(options.edges.color)) {this.constants.edges.fontColor = options.edges.color;}
  13865. else if (options.edges.color.color !== undefined) {this.constants.edges.fontColor = options.edges.color.color;}
  13866. }
  13867. }
  13868. }
  13869. if (options.nodes) {
  13870. if (options.nodes.color) {
  13871. var newColorObj = util.parseColor(options.nodes.color);
  13872. this.constants.nodes.color.background = newColorObj.background;
  13873. this.constants.nodes.color.border = newColorObj.border;
  13874. this.constants.nodes.color.highlight.background = newColorObj.highlight.background;
  13875. this.constants.nodes.color.highlight.border = newColorObj.highlight.border;
  13876. this.constants.nodes.color.hover.background = newColorObj.hover.background;
  13877. this.constants.nodes.color.hover.border = newColorObj.hover.border;
  13878. }
  13879. }
  13880. if (options.groups) {
  13881. for (var groupname in options.groups) {
  13882. if (options.groups.hasOwnProperty(groupname)) {
  13883. var group = options.groups[groupname];
  13884. this.groups.add(groupname, group);
  13885. }
  13886. }
  13887. }
  13888. if (options.tooltip) {
  13889. for (prop in options.tooltip) {
  13890. if (options.tooltip.hasOwnProperty(prop)) {
  13891. this.constants.tooltip[prop] = options.tooltip[prop];
  13892. }
  13893. }
  13894. if (options.tooltip.color) {
  13895. this.constants.tooltip.color = util.parseColor(options.tooltip.color);
  13896. }
  13897. }
  13898. if ('clickToUse' in options) {
  13899. if (options.clickToUse) {
  13900. this.activator = new Activator(this.frame);
  13901. this.activator.on('change', this._createKeyBinds.bind(this));
  13902. }
  13903. else {
  13904. if (this.activator) {
  13905. this.activator.destroy();
  13906. delete this.activator;
  13907. }
  13908. }
  13909. }
  13910. if (options.labels) {
  13911. throw new Error('Option "labels" is deprecated. Use options "locale" and "locales" instead.');
  13912. }
  13913. }
  13914. // (Re)loading the mixins that can be enabled or disabled in the options.
  13915. // load the force calculation functions, grouped under the physics system.
  13916. this._loadPhysicsSystem();
  13917. // load the navigation system.
  13918. this._loadNavigationControls();
  13919. // load the data manipulation system
  13920. this._loadManipulationSystem();
  13921. // configure the smooth curves
  13922. this._configureSmoothCurves();
  13923. // bind keys. If disabled, this will not do anything;
  13924. this._createKeyBinds();
  13925. this.setSize(this.constants.width, this.constants.height);
  13926. this.moving = true;
  13927. this.start();
  13928. };
  13929. /**
  13930. * Create the main frame for the Network.
  13931. * This function is executed once when a Network object is created. The frame
  13932. * contains a canvas, and this canvas contains all objects like the axis and
  13933. * nodes.
  13934. * @private
  13935. */
  13936. Network.prototype._create = function () {
  13937. // remove all elements from the container element.
  13938. while (this.containerElement.hasChildNodes()) {
  13939. this.containerElement.removeChild(this.containerElement.firstChild);
  13940. }
  13941. this.frame = document.createElement('div');
  13942. this.frame.className = 'vis network-frame';
  13943. this.frame.style.position = 'relative';
  13944. this.frame.style.overflow = 'hidden';
  13945. // create the network canvas (HTML canvas element)
  13946. this.frame.canvas = document.createElement( 'canvas' );
  13947. this.frame.canvas.style.position = 'relative';
  13948. this.frame.appendChild(this.frame.canvas);
  13949. if (!this.frame.canvas.getContext) {
  13950. var noCanvas = document.createElement( 'DIV' );
  13951. noCanvas.style.color = 'red';
  13952. noCanvas.style.fontWeight = 'bold' ;
  13953. noCanvas.style.padding = '10px';
  13954. noCanvas.innerHTML = 'Error: your browser does not support HTML canvas';
  13955. this.frame.canvas.appendChild(noCanvas);
  13956. }
  13957. var me = this;
  13958. this.drag = {};
  13959. this.pinch = {};
  13960. this.hammer = Hammer(this.frame.canvas, {
  13961. prevent_default: true
  13962. });
  13963. this.hammer.on('tap', me._onTap.bind(me) );
  13964. this.hammer.on('doubletap', me._onDoubleTap.bind(me) );
  13965. this.hammer.on('hold', me._onHold.bind(me) );
  13966. this.hammer.on('pinch', me._onPinch.bind(me) );
  13967. this.hammer.on('touch', me._onTouch.bind(me) );
  13968. this.hammer.on('dragstart', me._onDragStart.bind(me) );
  13969. this.hammer.on('drag', me._onDrag.bind(me) );
  13970. this.hammer.on('dragend', me._onDragEnd.bind(me) );
  13971. this.hammer.on('release', me._onRelease.bind(me) );
  13972. this.hammer.on('mousewheel',me._onMouseWheel.bind(me) );
  13973. this.hammer.on('DOMMouseScroll',me._onMouseWheel.bind(me) ); // for FF
  13974. this.hammer.on('mousemove', me._onMouseMoveTitle.bind(me) );
  13975. // add the frame to the container element
  13976. this.containerElement.appendChild(this.frame);
  13977. };
  13978. /**
  13979. * Binding the keys for keyboard navigation. These functions are defined in the NavigationMixin
  13980. * @private
  13981. */
  13982. Network.prototype._createKeyBinds = function() {
  13983. var me = this;
  13984. this.mousetrap = mousetrap;
  13985. this.mousetrap.reset();
  13986. if (this.constants.keyboard.enabled && this.isActive()) {
  13987. this.mousetrap.bind("up", this._moveUp.bind(me) , "keydown");
  13988. this.mousetrap.bind("up", this._yStopMoving.bind(me), "keyup");
  13989. this.mousetrap.bind("down", this._moveDown.bind(me) , "keydown");
  13990. this.mousetrap.bind("down", this._yStopMoving.bind(me), "keyup");
  13991. this.mousetrap.bind("left", this._moveLeft.bind(me) , "keydown");
  13992. this.mousetrap.bind("left", this._xStopMoving.bind(me), "keyup");
  13993. this.mousetrap.bind("right",this._moveRight.bind(me), "keydown");
  13994. this.mousetrap.bind("right",this._xStopMoving.bind(me), "keyup");
  13995. this.mousetrap.bind("=", this._zoomIn.bind(me), "keydown");
  13996. this.mousetrap.bind("=", this._stopZoom.bind(me), "keyup");
  13997. this.mousetrap.bind("-", this._zoomOut.bind(me), "keydown");
  13998. this.mousetrap.bind("-", this._stopZoom.bind(me), "keyup");
  13999. this.mousetrap.bind("[", this._zoomIn.bind(me), "keydown");
  14000. this.mousetrap.bind("[", this._stopZoom.bind(me), "keyup");
  14001. this.mousetrap.bind("]", this._zoomOut.bind(me), "keydown");
  14002. this.mousetrap.bind("]", this._stopZoom.bind(me), "keyup");
  14003. this.mousetrap.bind("pageup",this._zoomIn.bind(me), "keydown");
  14004. this.mousetrap.bind("pageup",this._stopZoom.bind(me), "keyup");
  14005. this.mousetrap.bind("pagedown",this._zoomOut.bind(me),"keydown");
  14006. this.mousetrap.bind("pagedown",this._stopZoom.bind(me), "keyup");
  14007. }
  14008. if (this.constants.dataManipulation.enabled == true) {
  14009. this.mousetrap.bind("escape",this._createManipulatorBar.bind(me));
  14010. this.mousetrap.bind("del",this._deleteSelected.bind(me));
  14011. }
  14012. };
  14013. /**
  14014. * Get the pointer location from a touch location
  14015. * @param {{pageX: Number, pageY: Number}} touch
  14016. * @return {{x: Number, y: Number}} pointer
  14017. * @private
  14018. */
  14019. Network.prototype._getPointer = function (touch) {
  14020. return {
  14021. x: touch.pageX - util.getAbsoluteLeft(this.frame.canvas),
  14022. y: touch.pageY - util.getAbsoluteTop(this.frame.canvas)
  14023. };
  14024. };
  14025. /**
  14026. * On start of a touch gesture, store the pointer
  14027. * @param event
  14028. * @private
  14029. */
  14030. Network.prototype._onTouch = function (event) {
  14031. this.drag.pointer = this._getPointer(event.gesture.center);
  14032. this.drag.pinched = false;
  14033. this.pinch.scale = this._getScale();
  14034. this._handleTouch(this.drag.pointer);
  14035. };
  14036. /**
  14037. * handle drag start event
  14038. * @private
  14039. */
  14040. Network.prototype._onDragStart = function () {
  14041. this._handleDragStart();
  14042. };
  14043. /**
  14044. * This function is called by _onDragStart.
  14045. * It is separated out because we can then overload it for the datamanipulation system.
  14046. *
  14047. * @private
  14048. */
  14049. Network.prototype._handleDragStart = function() {
  14050. var drag = this.drag;
  14051. var node = this._getNodeAt(drag.pointer);
  14052. // note: drag.pointer is set in _onTouch to get the initial touch location
  14053. drag.dragging = true;
  14054. drag.selection = [];
  14055. drag.translation = this._getTranslation();
  14056. drag.nodeId = null;
  14057. if (node != null && this.constants.dragNodes == true) {
  14058. drag.nodeId = node.id;
  14059. // select the clicked node if not yet selected
  14060. if (!node.isSelected()) {
  14061. this._selectObject(node,false);
  14062. }
  14063. this.emit("dragStart",{nodeIds:this.getSelection().nodes});
  14064. // create an array with the selected nodes and their original location and status
  14065. for (var objectId in this.selectionObj.nodes) {
  14066. if (this.selectionObj.nodes.hasOwnProperty(objectId)) {
  14067. var object = this.selectionObj.nodes[objectId];
  14068. var s = {
  14069. id: object.id,
  14070. node: object,
  14071. // store original x, y, xFixed and yFixed, make the node temporarily Fixed
  14072. x: object.x,
  14073. y: object.y,
  14074. xFixed: object.xFixed,
  14075. yFixed: object.yFixed
  14076. };
  14077. object.xFixed = true;
  14078. object.yFixed = true;
  14079. drag.selection.push(s);
  14080. }
  14081. }
  14082. }
  14083. };
  14084. /**
  14085. * handle drag event
  14086. * @private
  14087. */
  14088. Network.prototype._onDrag = function (event) {
  14089. this._handleOnDrag(event)
  14090. };
  14091. /**
  14092. * This function is called by _onDrag.
  14093. * It is separated out because we can then overload it for the datamanipulation system.
  14094. *
  14095. * @private
  14096. */
  14097. Network.prototype._handleOnDrag = function(event) {
  14098. if (this.drag.pinched) {
  14099. return;
  14100. }
  14101. // remove the focus on node if it is focussed on by the focusOnNode
  14102. this.releaseNode();
  14103. var pointer = this._getPointer(event.gesture.center);
  14104. var me = this;
  14105. var drag = this.drag;
  14106. var selection = drag.selection;
  14107. if (selection && selection.length && this.constants.dragNodes == true) {
  14108. // calculate delta's and new location
  14109. var deltaX = pointer.x - drag.pointer.x;
  14110. var deltaY = pointer.y - drag.pointer.y;
  14111. // update position of all selected nodes
  14112. selection.forEach(function (s) {
  14113. var node = s.node;
  14114. if (!s.xFixed) {
  14115. node.x = me._XconvertDOMtoCanvas(me._XconvertCanvasToDOM(s.x) + deltaX);
  14116. }
  14117. if (!s.yFixed) {
  14118. node.y = me._YconvertDOMtoCanvas(me._YconvertCanvasToDOM(s.y) + deltaY);
  14119. }
  14120. });
  14121. // start _animationStep if not yet running
  14122. if (!this.moving) {
  14123. this.moving = true;
  14124. this.start();
  14125. }
  14126. }
  14127. else {
  14128. if (this.constants.dragNetwork == true) {
  14129. // move the network
  14130. var diffX = pointer.x - this.drag.pointer.x;
  14131. var diffY = pointer.y - this.drag.pointer.y;
  14132. this._setTranslation(
  14133. this.drag.translation.x + diffX,
  14134. this.drag.translation.y + diffY
  14135. );
  14136. this._redraw();
  14137. // this.moving = true;
  14138. // this.start();
  14139. }
  14140. }
  14141. };
  14142. /**
  14143. * handle drag start event
  14144. * @private
  14145. */
  14146. Network.prototype._onDragEnd = function (event) {
  14147. this._handleDragEnd(event);
  14148. };
  14149. Network.prototype._handleDragEnd = function(event) {
  14150. this.drag.dragging = false;
  14151. var selection = this.drag.selection;
  14152. if (selection && selection.length) {
  14153. selection.forEach(function (s) {
  14154. // restore original xFixed and yFixed
  14155. s.node.xFixed = s.xFixed;
  14156. s.node.yFixed = s.yFixed;
  14157. });
  14158. this.moving = true;
  14159. this.start();
  14160. }
  14161. else {
  14162. this._redraw();
  14163. }
  14164. this.emit("dragEnd",{nodeIds:this.getSelection().nodes});
  14165. }
  14166. /**
  14167. * handle tap/click event: select/unselect a node
  14168. * @private
  14169. */
  14170. Network.prototype._onTap = function (event) {
  14171. var pointer = this._getPointer(event.gesture.center);
  14172. this.pointerPosition = pointer;
  14173. this._handleTap(pointer);
  14174. };
  14175. /**
  14176. * handle doubletap event
  14177. * @private
  14178. */
  14179. Network.prototype._onDoubleTap = function (event) {
  14180. var pointer = this._getPointer(event.gesture.center);
  14181. this._handleDoubleTap(pointer);
  14182. };
  14183. /**
  14184. * handle long tap event: multi select nodes
  14185. * @private
  14186. */
  14187. Network.prototype._onHold = function (event) {
  14188. var pointer = this._getPointer(event.gesture.center);
  14189. this.pointerPosition = pointer;
  14190. this._handleOnHold(pointer);
  14191. };
  14192. /**
  14193. * handle the release of the screen
  14194. *
  14195. * @private
  14196. */
  14197. Network.prototype._onRelease = function (event) {
  14198. var pointer = this._getPointer(event.gesture.center);
  14199. this._handleOnRelease(pointer);
  14200. };
  14201. /**
  14202. * Handle pinch event
  14203. * @param event
  14204. * @private
  14205. */
  14206. Network.prototype._onPinch = function (event) {
  14207. var pointer = this._getPointer(event.gesture.center);
  14208. this.drag.pinched = true;
  14209. if (!('scale' in this.pinch)) {
  14210. this.pinch.scale = 1;
  14211. }
  14212. // TODO: enabled moving while pinching?
  14213. var scale = this.pinch.scale * event.gesture.scale;
  14214. this._zoom(scale, pointer)
  14215. };
  14216. /**
  14217. * Zoom the network in or out
  14218. * @param {Number} scale a number around 1, and between 0.01 and 10
  14219. * @param {{x: Number, y: Number}} pointer Position on screen
  14220. * @return {Number} appliedScale scale is limited within the boundaries
  14221. * @private
  14222. */
  14223. Network.prototype._zoom = function(scale, pointer) {
  14224. if (this.constants.zoomable == true) {
  14225. var scaleOld = this._getScale();
  14226. if (scale < 0.00001) {
  14227. scale = 0.00001;
  14228. }
  14229. if (scale > 10) {
  14230. scale = 10;
  14231. }
  14232. var preScaleDragPointer = null;
  14233. if (this.drag !== undefined) {
  14234. if (this.drag.dragging == true) {
  14235. preScaleDragPointer = this.DOMtoCanvas(this.drag.pointer);
  14236. }
  14237. }
  14238. // + this.frame.canvas.clientHeight / 2
  14239. var translation = this._getTranslation();
  14240. var scaleFrac = scale / scaleOld;
  14241. var tx = (1 - scaleFrac) * pointer.x + translation.x * scaleFrac;
  14242. var ty = (1 - scaleFrac) * pointer.y + translation.y * scaleFrac;
  14243. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  14244. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  14245. this._setScale(scale);
  14246. this._setTranslation(tx, ty);
  14247. this.updateClustersDefault();
  14248. if (preScaleDragPointer != null) {
  14249. var postScaleDragPointer = this.canvasToDOM(preScaleDragPointer);
  14250. this.drag.pointer.x = postScaleDragPointer.x;
  14251. this.drag.pointer.y = postScaleDragPointer.y;
  14252. }
  14253. this._redraw();
  14254. if (scaleOld < scale) {
  14255. this.emit("zoom", {direction:"+"});
  14256. }
  14257. else {
  14258. this.emit("zoom", {direction:"-"});
  14259. }
  14260. return scale;
  14261. }
  14262. };
  14263. /**
  14264. * Event handler for mouse wheel event, used to zoom the timeline
  14265. * See http://adomas.org/javascript-mouse-wheel/
  14266. * https://github.com/EightMedia/hammer.js/issues/256
  14267. * @param {MouseEvent} event
  14268. * @private
  14269. */
  14270. Network.prototype._onMouseWheel = function(event) {
  14271. // retrieve delta
  14272. var delta = 0;
  14273. if (event.wheelDelta) { /* IE/Opera. */
  14274. delta = event.wheelDelta/120;
  14275. } else if (event.detail) { /* Mozilla case. */
  14276. // In Mozilla, sign of delta is different than in IE.
  14277. // Also, delta is multiple of 3.
  14278. delta = -event.detail/3;
  14279. }
  14280. // If delta is nonzero, handle it.
  14281. // Basically, delta is now positive if wheel was scrolled up,
  14282. // and negative, if wheel was scrolled down.
  14283. if (delta) {
  14284. // calculate the new scale
  14285. var scale = this._getScale();
  14286. var zoom = delta / 10;
  14287. if (delta < 0) {
  14288. zoom = zoom / (1 - zoom);
  14289. }
  14290. scale *= (1 + zoom);
  14291. // calculate the pointer location
  14292. var gesture = hammerUtil.fakeGesture(this, event);
  14293. var pointer = this._getPointer(gesture.center);
  14294. // apply the new scale
  14295. this._zoom(scale, pointer);
  14296. }
  14297. // Prevent default actions caused by mouse wheel.
  14298. event.preventDefault();
  14299. };
  14300. /**
  14301. * Mouse move handler for checking whether the title moves over a node with a title.
  14302. * @param {Event} event
  14303. * @private
  14304. */
  14305. Network.prototype._onMouseMoveTitle = function (event) {
  14306. var gesture = hammerUtil.fakeGesture(this, event);
  14307. var pointer = this._getPointer(gesture.center);
  14308. // check if the previously selected node is still selected
  14309. if (this.popupObj) {
  14310. this._checkHidePopup(pointer);
  14311. }
  14312. // start a timeout that will check if the mouse is positioned above
  14313. // an element
  14314. var me = this;
  14315. var checkShow = function() {
  14316. me._checkShowPopup(pointer);
  14317. };
  14318. if (this.popupTimer) {
  14319. clearInterval(this.popupTimer); // stop any running calculationTimer
  14320. }
  14321. if (!this.drag.dragging) {
  14322. this.popupTimer = setTimeout(checkShow, this.constants.tooltip.delay);
  14323. }
  14324. /**
  14325. * Adding hover highlights
  14326. */
  14327. if (this.constants.hover == true) {
  14328. // removing all hover highlights
  14329. for (var edgeId in this.hoverObj.edges) {
  14330. if (this.hoverObj.edges.hasOwnProperty(edgeId)) {
  14331. this.hoverObj.edges[edgeId].hover = false;
  14332. delete this.hoverObj.edges[edgeId];
  14333. }
  14334. }
  14335. // adding hover highlights
  14336. var obj = this._getNodeAt(pointer);
  14337. if (obj == null) {
  14338. obj = this._getEdgeAt(pointer);
  14339. }
  14340. if (obj != null) {
  14341. this._hoverObject(obj);
  14342. }
  14343. // removing all node hover highlights except for the selected one.
  14344. for (var nodeId in this.hoverObj.nodes) {
  14345. if (this.hoverObj.nodes.hasOwnProperty(nodeId)) {
  14346. if (obj instanceof Node && obj.id != nodeId || obj instanceof Edge || obj == null) {
  14347. this._blurObject(this.hoverObj.nodes[nodeId]);
  14348. delete this.hoverObj.nodes[nodeId];
  14349. }
  14350. }
  14351. }
  14352. this.redraw();
  14353. }
  14354. };
  14355. /**
  14356. * Check if there is an element on the given position in the network
  14357. * (a node or edge). If so, and if this element has a title,
  14358. * show a popup window with its title.
  14359. *
  14360. * @param {{x:Number, y:Number}} pointer
  14361. * @private
  14362. */
  14363. Network.prototype._checkShowPopup = function (pointer) {
  14364. var obj = {
  14365. left: this._XconvertDOMtoCanvas(pointer.x),
  14366. top: this._YconvertDOMtoCanvas(pointer.y),
  14367. right: this._XconvertDOMtoCanvas(pointer.x),
  14368. bottom: this._YconvertDOMtoCanvas(pointer.y)
  14369. };
  14370. var id;
  14371. var lastPopupNode = this.popupObj;
  14372. if (this.popupObj == undefined) {
  14373. // search the nodes for overlap, select the top one in case of multiple nodes
  14374. var nodes = this.nodes;
  14375. for (id in nodes) {
  14376. if (nodes.hasOwnProperty(id)) {
  14377. var node = nodes[id];
  14378. if (node.getTitle() !== undefined && node.isOverlappingWith(obj)) {
  14379. this.popupObj = node;
  14380. break;
  14381. }
  14382. }
  14383. }
  14384. }
  14385. if (this.popupObj === undefined) {
  14386. // search the edges for overlap
  14387. var edges = this.edges;
  14388. for (id in edges) {
  14389. if (edges.hasOwnProperty(id)) {
  14390. var edge = edges[id];
  14391. if (edge.connected && (edge.getTitle() !== undefined) &&
  14392. edge.isOverlappingWith(obj)) {
  14393. this.popupObj = edge;
  14394. break;
  14395. }
  14396. }
  14397. }
  14398. }
  14399. if (this.popupObj) {
  14400. // show popup message window
  14401. if (this.popupObj != lastPopupNode) {
  14402. var me = this;
  14403. if (!me.popup) {
  14404. me.popup = new Popup(me.frame, me.constants.tooltip);
  14405. }
  14406. // adjust a small offset such that the mouse cursor is located in the
  14407. // bottom left location of the popup, and you can easily move over the
  14408. // popup area
  14409. me.popup.setPosition(pointer.x - 3, pointer.y - 3);
  14410. me.popup.setText(me.popupObj.getTitle());
  14411. me.popup.show();
  14412. }
  14413. }
  14414. else {
  14415. if (this.popup) {
  14416. this.popup.hide();
  14417. }
  14418. }
  14419. };
  14420. /**
  14421. * Check if the popup must be hided, which is the case when the mouse is no
  14422. * longer hovering on the object
  14423. * @param {{x:Number, y:Number}} pointer
  14424. * @private
  14425. */
  14426. Network.prototype._checkHidePopup = function (pointer) {
  14427. if (!this.popupObj || !this._getNodeAt(pointer) ) {
  14428. this.popupObj = undefined;
  14429. if (this.popup) {
  14430. this.popup.hide();
  14431. }
  14432. }
  14433. };
  14434. /**
  14435. * Set a new size for the network
  14436. * @param {string} width Width in pixels or percentage (for example '800px'
  14437. * or '50%')
  14438. * @param {string} height Height in pixels or percentage (for example '400px'
  14439. * or '30%')
  14440. */
  14441. Network.prototype.setSize = function(width, height) {
  14442. var emitEvent = false;
  14443. var oldWidth = this.frame.canvas.width;
  14444. var oldHeight = this.frame.canvas.height;
  14445. if (width != this.constants.width || height != this.constants.height || this.frame.style.width != width || this.frame.style.height != height) {
  14446. this.frame.style.width = width;
  14447. this.frame.style.height = height;
  14448. this.frame.canvas.style.width = '100%';
  14449. this.frame.canvas.style.height = '100%';
  14450. this.frame.canvas.width = this.frame.canvas.clientWidth;
  14451. this.frame.canvas.height = this.frame.canvas.clientHeight;
  14452. this.constants.width = width;
  14453. this.constants.height = height;
  14454. emitEvent = true;
  14455. }
  14456. else {
  14457. // this would adapt the width of the canvas to the width from 100% if and only if
  14458. // there is a change.
  14459. if (this.frame.canvas.width != this.frame.canvas.clientWidth) {
  14460. this.frame.canvas.width = this.frame.canvas.clientWidth;
  14461. emitEvent = true;
  14462. }
  14463. if (this.frame.canvas.height != this.frame.canvas.clientHeight) {
  14464. this.frame.canvas.height = this.frame.canvas.clientHeight;
  14465. emitEvent = true;
  14466. }
  14467. }
  14468. if (emitEvent == true) {
  14469. this.emit('resize', {width:this.frame.canvas.width,height:this.frame.canvas.height, oldWidth: oldWidth, oldHeight: oldHeight});
  14470. }
  14471. };
  14472. /**
  14473. * Set a data set with nodes for the network
  14474. * @param {Array | DataSet | DataView} nodes The data containing the nodes.
  14475. * @private
  14476. */
  14477. Network.prototype._setNodes = function(nodes) {
  14478. var oldNodesData = this.nodesData;
  14479. if (nodes instanceof DataSet || nodes instanceof DataView) {
  14480. this.nodesData = nodes;
  14481. }
  14482. else if (Array.isArray(nodes)) {
  14483. this.nodesData = new DataSet();
  14484. this.nodesData.add(nodes);
  14485. }
  14486. else if (!nodes) {
  14487. this.nodesData = new DataSet();
  14488. }
  14489. else {
  14490. throw new TypeError('Array or DataSet expected');
  14491. }
  14492. if (oldNodesData) {
  14493. // unsubscribe from old dataset
  14494. util.forEach(this.nodesListeners, function (callback, event) {
  14495. oldNodesData.off(event, callback);
  14496. });
  14497. }
  14498. // remove drawn nodes
  14499. this.nodes = {};
  14500. if (this.nodesData) {
  14501. // subscribe to new dataset
  14502. var me = this;
  14503. util.forEach(this.nodesListeners, function (callback, event) {
  14504. me.nodesData.on(event, callback);
  14505. });
  14506. // draw all new nodes
  14507. var ids = this.nodesData.getIds();
  14508. this._addNodes(ids);
  14509. }
  14510. this._updateSelection();
  14511. };
  14512. /**
  14513. * Add nodes
  14514. * @param {Number[] | String[]} ids
  14515. * @private
  14516. */
  14517. Network.prototype._addNodes = function(ids) {
  14518. var id;
  14519. for (var i = 0, len = ids.length; i < len; i++) {
  14520. id = ids[i];
  14521. var data = this.nodesData.get(id);
  14522. var node = new Node(data, this.images, this.groups, this.constants);
  14523. this.nodes[id] = node; // note: this may replace an existing node
  14524. if ((node.xFixed == false || node.yFixed == false) && (node.x === null || node.y === null)) {
  14525. var radius = 10 * 0.1*ids.length + 10;
  14526. var angle = 2 * Math.PI * Math.random();
  14527. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  14528. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  14529. }
  14530. this.moving = true;
  14531. }
  14532. this._updateNodeIndexList();
  14533. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14534. this._resetLevels();
  14535. this._setupHierarchicalLayout();
  14536. }
  14537. this._updateCalculationNodes();
  14538. this._reconnectEdges();
  14539. this._updateValueRange(this.nodes);
  14540. this.updateLabels();
  14541. };
  14542. /**
  14543. * Update existing nodes, or create them when not yet existing
  14544. * @param {Number[] | String[]} ids
  14545. * @private
  14546. */
  14547. Network.prototype._updateNodes = function(ids,changedData) {
  14548. var nodes = this.nodes;
  14549. for (var i = 0, len = ids.length; i < len; i++) {
  14550. var id = ids[i];
  14551. var node = nodes[id];
  14552. var data = changedData[i];
  14553. if (node) {
  14554. // update node
  14555. node.setProperties(data, this.constants);
  14556. }
  14557. else {
  14558. // create node
  14559. node = new Node(properties, this.images, this.groups, this.constants);
  14560. nodes[id] = node;
  14561. }
  14562. }
  14563. this.moving = true;
  14564. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14565. this._resetLevels();
  14566. this._setupHierarchicalLayout();
  14567. }
  14568. this._updateNodeIndexList();
  14569. this._updateValueRange(nodes);
  14570. };
  14571. /**
  14572. * Remove existing nodes. If nodes do not exist, the method will just ignore it.
  14573. * @param {Number[] | String[]} ids
  14574. * @private
  14575. */
  14576. Network.prototype._removeNodes = function(ids) {
  14577. var nodes = this.nodes;
  14578. for (var i = 0, len = ids.length; i < len; i++) {
  14579. var id = ids[i];
  14580. delete nodes[id];
  14581. }
  14582. this._updateNodeIndexList();
  14583. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14584. this._resetLevels();
  14585. this._setupHierarchicalLayout();
  14586. }
  14587. this._updateCalculationNodes();
  14588. this._reconnectEdges();
  14589. this._updateSelection();
  14590. this._updateValueRange(nodes);
  14591. };
  14592. /**
  14593. * Load edges by reading the data table
  14594. * @param {Array | DataSet | DataView} edges The data containing the edges.
  14595. * @private
  14596. * @private
  14597. */
  14598. Network.prototype._setEdges = function(edges) {
  14599. var oldEdgesData = this.edgesData;
  14600. if (edges instanceof DataSet || edges instanceof DataView) {
  14601. this.edgesData = edges;
  14602. }
  14603. else if (Array.isArray(edges)) {
  14604. this.edgesData = new DataSet();
  14605. this.edgesData.add(edges);
  14606. }
  14607. else if (!edges) {
  14608. this.edgesData = new DataSet();
  14609. }
  14610. else {
  14611. throw new TypeError('Array or DataSet expected');
  14612. }
  14613. if (oldEdgesData) {
  14614. // unsubscribe from old dataset
  14615. util.forEach(this.edgesListeners, function (callback, event) {
  14616. oldEdgesData.off(event, callback);
  14617. });
  14618. }
  14619. // remove drawn edges
  14620. this.edges = {};
  14621. if (this.edgesData) {
  14622. // subscribe to new dataset
  14623. var me = this;
  14624. util.forEach(this.edgesListeners, function (callback, event) {
  14625. me.edgesData.on(event, callback);
  14626. });
  14627. // draw all new nodes
  14628. var ids = this.edgesData.getIds();
  14629. this._addEdges(ids);
  14630. }
  14631. this._reconnectEdges();
  14632. };
  14633. /**
  14634. * Add edges
  14635. * @param {Number[] | String[]} ids
  14636. * @private
  14637. */
  14638. Network.prototype._addEdges = function (ids) {
  14639. var edges = this.edges,
  14640. edgesData = this.edgesData;
  14641. for (var i = 0, len = ids.length; i < len; i++) {
  14642. var id = ids[i];
  14643. var oldEdge = edges[id];
  14644. if (oldEdge) {
  14645. oldEdge.disconnect();
  14646. }
  14647. var data = edgesData.get(id, {"showInternalIds" : true});
  14648. edges[id] = new Edge(data, this, this.constants);
  14649. }
  14650. this.moving = true;
  14651. this._updateValueRange(edges);
  14652. this._createBezierNodes();
  14653. this._updateCalculationNodes();
  14654. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14655. this._resetLevels();
  14656. this._setupHierarchicalLayout();
  14657. }
  14658. };
  14659. /**
  14660. * Update existing edges, or create them when not yet existing
  14661. * @param {Number[] | String[]} ids
  14662. * @private
  14663. */
  14664. Network.prototype._updateEdges = function (ids) {
  14665. var edges = this.edges,
  14666. edgesData = this.edgesData;
  14667. for (var i = 0, len = ids.length; i < len; i++) {
  14668. var id = ids[i];
  14669. var data = edgesData.get(id);
  14670. var edge = edges[id];
  14671. if (edge) {
  14672. // update edge
  14673. edge.disconnect();
  14674. edge.setProperties(data, this.constants);
  14675. edge.connect();
  14676. }
  14677. else {
  14678. // create edge
  14679. edge = new Edge(data, this, this.constants);
  14680. this.edges[id] = edge;
  14681. }
  14682. }
  14683. this._createBezierNodes();
  14684. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14685. this._resetLevels();
  14686. this._setupHierarchicalLayout();
  14687. }
  14688. this.moving = true;
  14689. this._updateValueRange(edges);
  14690. };
  14691. /**
  14692. * Remove existing edges. Non existing ids will be ignored
  14693. * @param {Number[] | String[]} ids
  14694. * @private
  14695. */
  14696. Network.prototype._removeEdges = function (ids) {
  14697. var edges = this.edges;
  14698. for (var i = 0, len = ids.length; i < len; i++) {
  14699. var id = ids[i];
  14700. var edge = edges[id];
  14701. if (edge) {
  14702. if (edge.via != null) {
  14703. delete this.sectors['support']['nodes'][edge.via.id];
  14704. }
  14705. edge.disconnect();
  14706. delete edges[id];
  14707. }
  14708. }
  14709. this.moving = true;
  14710. this._updateValueRange(edges);
  14711. if (this.constants.hierarchicalLayout.enabled == true && this.initializing == false) {
  14712. this._resetLevels();
  14713. this._setupHierarchicalLayout();
  14714. }
  14715. this._updateCalculationNodes();
  14716. };
  14717. /**
  14718. * Reconnect all edges
  14719. * @private
  14720. */
  14721. Network.prototype._reconnectEdges = function() {
  14722. var id,
  14723. nodes = this.nodes,
  14724. edges = this.edges;
  14725. for (id in nodes) {
  14726. if (nodes.hasOwnProperty(id)) {
  14727. nodes[id].edges = [];
  14728. nodes[id].dynamicEdges = [];
  14729. }
  14730. }
  14731. for (id in edges) {
  14732. if (edges.hasOwnProperty(id)) {
  14733. var edge = edges[id];
  14734. edge.from = null;
  14735. edge.to = null;
  14736. edge.connect();
  14737. }
  14738. }
  14739. };
  14740. /**
  14741. * Update the values of all object in the given array according to the current
  14742. * value range of the objects in the array.
  14743. * @param {Object} obj An object containing a set of Edges or Nodes
  14744. * The objects must have a method getValue() and
  14745. * setValueRange(min, max).
  14746. * @private
  14747. */
  14748. Network.prototype._updateValueRange = function(obj) {
  14749. var id;
  14750. // determine the range of the objects
  14751. var valueMin = undefined;
  14752. var valueMax = undefined;
  14753. for (id in obj) {
  14754. if (obj.hasOwnProperty(id)) {
  14755. var value = obj[id].getValue();
  14756. if (value !== undefined) {
  14757. valueMin = (valueMin === undefined) ? value : Math.min(value, valueMin);
  14758. valueMax = (valueMax === undefined) ? value : Math.max(value, valueMax);
  14759. }
  14760. }
  14761. }
  14762. // adjust the range of all objects
  14763. if (valueMin !== undefined && valueMax !== undefined) {
  14764. for (id in obj) {
  14765. if (obj.hasOwnProperty(id)) {
  14766. obj[id].setValueRange(valueMin, valueMax);
  14767. }
  14768. }
  14769. }
  14770. };
  14771. /**
  14772. * Redraw the network with the current data
  14773. * chart will be resized too.
  14774. */
  14775. Network.prototype.redraw = function() {
  14776. this.setSize(this.constants.width, this.constants.height);
  14777. this._redraw();
  14778. };
  14779. /**
  14780. * Redraw the network with the current data
  14781. * @private
  14782. */
  14783. Network.prototype._redraw = function() {
  14784. var ctx = this.frame.canvas.getContext('2d');
  14785. // clear the canvas
  14786. var w = this.frame.canvas.width;
  14787. var h = this.frame.canvas.height;
  14788. ctx.clearRect(0, 0, w, h);
  14789. // set scaling and translation
  14790. ctx.save();
  14791. ctx.translate(this.translation.x, this.translation.y);
  14792. ctx.scale(this.scale, this.scale);
  14793. this.canvasTopLeft = {
  14794. "x": this._XconvertDOMtoCanvas(0),
  14795. "y": this._YconvertDOMtoCanvas(0)
  14796. };
  14797. this.canvasBottomRight = {
  14798. "x": this._XconvertDOMtoCanvas(this.frame.canvas.clientWidth),
  14799. "y": this._YconvertDOMtoCanvas(this.frame.canvas.clientHeight)
  14800. };
  14801. this._doInAllSectors("_drawAllSectorNodes",ctx);
  14802. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideEdgesOnDrag == false) {
  14803. this._doInAllSectors("_drawEdges",ctx);
  14804. }
  14805. if (this.drag.dragging == false || this.drag.dragging === undefined || this.constants.hideNodesOnDrag == false) {
  14806. this._doInAllSectors("_drawNodes",ctx,false);
  14807. }
  14808. if (this.controlNodesActive == true) {
  14809. this._doInAllSectors("_drawControlNodes",ctx);
  14810. }
  14811. // this._doInSupportSector("_drawNodes",ctx,true);
  14812. // this._drawTree(ctx,"#F00F0F");
  14813. // restore original scaling and translation
  14814. ctx.restore();
  14815. };
  14816. /**
  14817. * Set the translation of the network
  14818. * @param {Number} offsetX Horizontal offset
  14819. * @param {Number} offsetY Vertical offset
  14820. * @private
  14821. */
  14822. Network.prototype._setTranslation = function(offsetX, offsetY) {
  14823. if (this.translation === undefined) {
  14824. this.translation = {
  14825. x: 0,
  14826. y: 0
  14827. };
  14828. }
  14829. if (offsetX !== undefined) {
  14830. this.translation.x = offsetX;
  14831. }
  14832. if (offsetY !== undefined) {
  14833. this.translation.y = offsetY;
  14834. }
  14835. this.emit('viewChanged');
  14836. };
  14837. /**
  14838. * Get the translation of the network
  14839. * @return {Object} translation An object with parameters x and y, both a number
  14840. * @private
  14841. */
  14842. Network.prototype._getTranslation = function() {
  14843. return {
  14844. x: this.translation.x,
  14845. y: this.translation.y
  14846. };
  14847. };
  14848. /**
  14849. * Scale the network
  14850. * @param {Number} scale Scaling factor 1.0 is unscaled
  14851. * @private
  14852. */
  14853. Network.prototype._setScale = function(scale) {
  14854. this.scale = scale;
  14855. };
  14856. /**
  14857. * Get the current scale of the network
  14858. * @return {Number} scale Scaling factor 1.0 is unscaled
  14859. * @private
  14860. */
  14861. Network.prototype._getScale = function() {
  14862. return this.scale;
  14863. };
  14864. /**
  14865. * Convert the X coordinate in DOM-space (coordinate point in browser relative to the container div) to
  14866. * the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  14867. * @param {number} x
  14868. * @returns {number}
  14869. * @private
  14870. */
  14871. Network.prototype._XconvertDOMtoCanvas = function(x) {
  14872. return (x - this.translation.x) / this.scale;
  14873. };
  14874. /**
  14875. * Convert the X coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  14876. * the X coordinate in DOM-space (coordinate point in browser relative to the container div)
  14877. * @param {number} x
  14878. * @returns {number}
  14879. * @private
  14880. */
  14881. Network.prototype._XconvertCanvasToDOM = function(x) {
  14882. return x * this.scale + this.translation.x;
  14883. };
  14884. /**
  14885. * Convert the Y coordinate in DOM-space (coordinate point in browser relative to the container div) to
  14886. * the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon)
  14887. * @param {number} y
  14888. * @returns {number}
  14889. * @private
  14890. */
  14891. Network.prototype._YconvertDOMtoCanvas = function(y) {
  14892. return (y - this.translation.y) / this.scale;
  14893. };
  14894. /**
  14895. * Convert the Y coordinate in canvas-space (the simulation sandbox, which the camera looks upon) to
  14896. * the Y coordinate in DOM-space (coordinate point in browser relative to the container div)
  14897. * @param {number} y
  14898. * @returns {number}
  14899. * @private
  14900. */
  14901. Network.prototype._YconvertCanvasToDOM = function(y) {
  14902. return y * this.scale + this.translation.y ;
  14903. };
  14904. /**
  14905. *
  14906. * @param {object} pos = {x: number, y: number}
  14907. * @returns {{x: number, y: number}}
  14908. * @constructor
  14909. */
  14910. Network.prototype.canvasToDOM = function (pos) {
  14911. return {x: this._XconvertCanvasToDOM(pos.x), y: this._YconvertCanvasToDOM(pos.y)};
  14912. };
  14913. /**
  14914. *
  14915. * @param {object} pos = {x: number, y: number}
  14916. * @returns {{x: number, y: number}}
  14917. * @constructor
  14918. */
  14919. Network.prototype.DOMtoCanvas = function (pos) {
  14920. return {x: this._XconvertDOMtoCanvas(pos.x), y: this._YconvertDOMtoCanvas(pos.y)};
  14921. };
  14922. /**
  14923. * Redraw all nodes
  14924. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  14925. * @param {CanvasRenderingContext2D} ctx
  14926. * @param {Boolean} [alwaysShow]
  14927. * @private
  14928. */
  14929. Network.prototype._drawNodes = function(ctx,alwaysShow) {
  14930. if (alwaysShow === undefined) {
  14931. alwaysShow = false;
  14932. }
  14933. // first draw the unselected nodes
  14934. var nodes = this.nodes;
  14935. var selected = [];
  14936. for (var id in nodes) {
  14937. if (nodes.hasOwnProperty(id)) {
  14938. nodes[id].setScaleAndPos(this.scale,this.canvasTopLeft,this.canvasBottomRight);
  14939. if (nodes[id].isSelected()) {
  14940. selected.push(id);
  14941. }
  14942. else {
  14943. if (nodes[id].inArea() || alwaysShow) {
  14944. nodes[id].draw(ctx);
  14945. }
  14946. }
  14947. }
  14948. }
  14949. // draw the selected nodes on top
  14950. for (var s = 0, sMax = selected.length; s < sMax; s++) {
  14951. if (nodes[selected[s]].inArea() || alwaysShow) {
  14952. nodes[selected[s]].draw(ctx);
  14953. }
  14954. }
  14955. };
  14956. /**
  14957. * Redraw all edges
  14958. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  14959. * @param {CanvasRenderingContext2D} ctx
  14960. * @private
  14961. */
  14962. Network.prototype._drawEdges = function(ctx) {
  14963. var edges = this.edges;
  14964. for (var id in edges) {
  14965. if (edges.hasOwnProperty(id)) {
  14966. var edge = edges[id];
  14967. edge.setScale(this.scale);
  14968. if (edge.connected) {
  14969. edges[id].draw(ctx);
  14970. }
  14971. }
  14972. }
  14973. };
  14974. /**
  14975. * Redraw all edges
  14976. * The 2d context of a HTML canvas can be retrieved by canvas.getContext('2d');
  14977. * @param {CanvasRenderingContext2D} ctx
  14978. * @private
  14979. */
  14980. Network.prototype._drawControlNodes = function(ctx) {
  14981. var edges = this.edges;
  14982. for (var id in edges) {
  14983. if (edges.hasOwnProperty(id)) {
  14984. edges[id]._drawControlNodes(ctx);
  14985. }
  14986. }
  14987. };
  14988. /**
  14989. * Find a stable position for all nodes
  14990. * @private
  14991. */
  14992. Network.prototype._stabilize = function() {
  14993. if (this.constants.freezeForStabilization == true) {
  14994. this._freezeDefinedNodes();
  14995. }
  14996. // find stable position
  14997. var count = 0;
  14998. while (this.moving && count < this.constants.stabilizationIterations) {
  14999. this._physicsTick();
  15000. count++;
  15001. }
  15002. this.zoomExtent(undefined,false,true);
  15003. if (this.constants.freezeForStabilization == true) {
  15004. this._restoreFrozenNodes();
  15005. }
  15006. };
  15007. /**
  15008. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  15009. * because only the supportnodes for the smoothCurves have to settle.
  15010. *
  15011. * @private
  15012. */
  15013. Network.prototype._freezeDefinedNodes = function() {
  15014. var nodes = this.nodes;
  15015. for (var id in nodes) {
  15016. if (nodes.hasOwnProperty(id)) {
  15017. if (nodes[id].x != null && nodes[id].y != null) {
  15018. nodes[id].fixedData.x = nodes[id].xFixed;
  15019. nodes[id].fixedData.y = nodes[id].yFixed;
  15020. nodes[id].xFixed = true;
  15021. nodes[id].yFixed = true;
  15022. }
  15023. }
  15024. }
  15025. };
  15026. /**
  15027. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  15028. *
  15029. * @private
  15030. */
  15031. Network.prototype._restoreFrozenNodes = function() {
  15032. var nodes = this.nodes;
  15033. for (var id in nodes) {
  15034. if (nodes.hasOwnProperty(id)) {
  15035. if (nodes[id].fixedData.x != null) {
  15036. nodes[id].xFixed = nodes[id].fixedData.x;
  15037. nodes[id].yFixed = nodes[id].fixedData.y;
  15038. }
  15039. }
  15040. }
  15041. };
  15042. /**
  15043. * Check if any of the nodes is still moving
  15044. * @param {number} vmin the minimum velocity considered as 'moving'
  15045. * @return {boolean} true if moving, false if non of the nodes is moving
  15046. * @private
  15047. */
  15048. Network.prototype._isMoving = function(vmin) {
  15049. var nodes = this.nodes;
  15050. for (var id in nodes) {
  15051. if (nodes.hasOwnProperty(id) && nodes[id].isMoving(vmin)) {
  15052. return true;
  15053. }
  15054. }
  15055. return false;
  15056. };
  15057. /**
  15058. * /**
  15059. * Perform one discrete step for all nodes
  15060. *
  15061. * @private
  15062. */
  15063. Network.prototype._discreteStepNodes = function() {
  15064. var interval = this.physicsDiscreteStepsize;
  15065. var nodes = this.nodes;
  15066. var nodeId;
  15067. var nodesPresent = false;
  15068. if (this.constants.maxVelocity > 0) {
  15069. for (nodeId in nodes) {
  15070. if (nodes.hasOwnProperty(nodeId)) {
  15071. nodes[nodeId].discreteStepLimited(interval, this.constants.maxVelocity);
  15072. nodesPresent = true;
  15073. }
  15074. }
  15075. }
  15076. else {
  15077. for (nodeId in nodes) {
  15078. if (nodes.hasOwnProperty(nodeId)) {
  15079. nodes[nodeId].discreteStep(interval);
  15080. nodesPresent = true;
  15081. }
  15082. }
  15083. }
  15084. if (nodesPresent == true) {
  15085. var vminCorrected = this.constants.minVelocity / Math.max(this.scale,0.05);
  15086. if (vminCorrected > 0.5*this.constants.maxVelocity) {
  15087. return true;
  15088. }
  15089. else {
  15090. return this._isMoving(vminCorrected);
  15091. }
  15092. }
  15093. return false;
  15094. };
  15095. /**
  15096. * A single simulation step (or "tick") in the physics simulation
  15097. *
  15098. * @private
  15099. */
  15100. Network.prototype._physicsTick = function() {
  15101. if (!this.freezeSimulation) {
  15102. if (this.moving == true) {
  15103. var mainMovingStatus = false;
  15104. var supportMovingStatus = false;
  15105. this._doInAllActiveSectors("_initializeForceCalculation");
  15106. var mainMoving = this._doInAllActiveSectors("_discreteStepNodes");
  15107. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  15108. supportMovingStatus = this._doInSupportSector("_discreteStepNodes");
  15109. }
  15110. // gather movement data from all sectors, if one moves, we are NOT stabilzied
  15111. for (var i = 0; i < mainMoving.length; i++) {mainMovingStatus = mainMoving[0] || mainMovingStatus;}
  15112. // determine if the network has stabilzied
  15113. this.moving = mainMovingStatus || supportMovingStatus;
  15114. this.stabilizationIterations++;
  15115. }
  15116. }
  15117. };
  15118. /**
  15119. * This function runs one step of the animation. It calls an x amount of physics ticks and one render tick.
  15120. * It reschedules itself at the beginning of the function
  15121. *
  15122. * @private
  15123. */
  15124. Network.prototype._animationStep = function() {
  15125. // reset the timer so a new scheduled animation step can be set
  15126. this.timer = undefined;
  15127. // handle the keyboad movement
  15128. this._handleNavigation();
  15129. // this schedules a new animation step
  15130. this.start();
  15131. // start the physics simulation
  15132. var calculationTime = Date.now();
  15133. var maxSteps = 1;
  15134. this._physicsTick();
  15135. var timeRequired = Date.now() - calculationTime;
  15136. while (timeRequired < 0.9*(this.renderTimestep - this.renderTime) && maxSteps < this.maxPhysicsTicksPerRender) {
  15137. this._physicsTick();
  15138. timeRequired = Date.now() - calculationTime;
  15139. maxSteps++;
  15140. }
  15141. // start the rendering process
  15142. var renderTime = Date.now();
  15143. this._redraw();
  15144. this.renderTime = Date.now() - renderTime;
  15145. };
  15146. if (typeof window !== 'undefined') {
  15147. window.requestAnimationFrame = window.requestAnimationFrame || window.mozRequestAnimationFrame ||
  15148. window.webkitRequestAnimationFrame || window.msRequestAnimationFrame;
  15149. }
  15150. /**
  15151. * Schedule a animation step with the refreshrate interval.
  15152. */
  15153. Network.prototype.start = function() {
  15154. if (this.moving == true || this.xIncrement != 0 || this.yIncrement != 0 || this.zoomIncrement != 0) {
  15155. if (this.startedStabilization == false) {
  15156. this.emit("startStabilization");
  15157. this.startedStabilization = true;
  15158. }
  15159. if (!this.timer) {
  15160. var ua = navigator.userAgent.toLowerCase();
  15161. var requiresTimeout = false;
  15162. if (ua.indexOf('msie 9.0') != -1) { // IE 9
  15163. requiresTimeout = true;
  15164. }
  15165. else if (ua.indexOf('safari') != -1) { // safari
  15166. if (ua.indexOf('chrome') <= -1) {
  15167. requiresTimeout = true;
  15168. }
  15169. }
  15170. if (requiresTimeout == true) {
  15171. this.timer = window.setTimeout(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  15172. }
  15173. else{
  15174. this.timer = window.requestAnimationFrame(this._animationStep.bind(this), this.renderTimestep); // wait this.renderTimeStep milliseconds and perform the animation step function
  15175. }
  15176. }
  15177. }
  15178. else {
  15179. this._redraw();
  15180. if (this.stabilizationIterations > 0) {
  15181. // trigger the "stabilized" event.
  15182. // The event is triggered on the next tick, to prevent the case that
  15183. // it is fired while initializing the Network, in which case you would not
  15184. // be able to catch it
  15185. var me = this;
  15186. var params = {
  15187. iterations: me.stabilizationIterations
  15188. };
  15189. me.stabilizationIterations = 0;
  15190. me.startedStabilization = false;
  15191. setTimeout(function () {
  15192. me.emit("stabilized", params);
  15193. }, 0);
  15194. }
  15195. }
  15196. };
  15197. /**
  15198. * Move the network according to the keyboard presses.
  15199. *
  15200. * @private
  15201. */
  15202. Network.prototype._handleNavigation = function() {
  15203. if (this.xIncrement != 0 || this.yIncrement != 0) {
  15204. var translation = this._getTranslation();
  15205. this._setTranslation(translation.x+this.xIncrement, translation.y+this.yIncrement);
  15206. }
  15207. if (this.zoomIncrement != 0) {
  15208. var center = {
  15209. x: this.frame.canvas.clientWidth / 2,
  15210. y: this.frame.canvas.clientHeight / 2
  15211. };
  15212. this._zoom(this.scale*(1 + this.zoomIncrement), center);
  15213. }
  15214. };
  15215. /**
  15216. * Freeze the _animationStep
  15217. */
  15218. Network.prototype.toggleFreeze = function() {
  15219. if (this.freezeSimulation == false) {
  15220. this.freezeSimulation = true;
  15221. }
  15222. else {
  15223. this.freezeSimulation = false;
  15224. this.start();
  15225. }
  15226. };
  15227. /**
  15228. * This function cleans the support nodes if they are not needed and adds them when they are.
  15229. *
  15230. * @param {boolean} [disableStart]
  15231. * @private
  15232. */
  15233. Network.prototype._configureSmoothCurves = function(disableStart) {
  15234. if (disableStart === undefined) {
  15235. disableStart = true;
  15236. }
  15237. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  15238. this._createBezierNodes();
  15239. // cleanup unused support nodes
  15240. for (var nodeId in this.sectors['support']['nodes']) {
  15241. if (this.sectors['support']['nodes'].hasOwnProperty(nodeId)) {
  15242. if (this.edges[this.sectors['support']['nodes'][nodeId].parentEdgeId] === undefined) {
  15243. delete this.sectors['support']['nodes'][nodeId];
  15244. }
  15245. }
  15246. }
  15247. }
  15248. else {
  15249. // delete the support nodes
  15250. this.sectors['support']['nodes'] = {};
  15251. for (var edgeId in this.edges) {
  15252. if (this.edges.hasOwnProperty(edgeId)) {
  15253. this.edges[edgeId].via = null;
  15254. }
  15255. }
  15256. }
  15257. this._updateCalculationNodes();
  15258. if (!disableStart) {
  15259. this.moving = true;
  15260. this.start();
  15261. }
  15262. };
  15263. /**
  15264. * Bezier curves require an anchor point to calculate the smooth flow. These points are nodes. These nodes are invisible but
  15265. * are used for the force calculation.
  15266. *
  15267. * @private
  15268. */
  15269. Network.prototype._createBezierNodes = function() {
  15270. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  15271. for (var edgeId in this.edges) {
  15272. if (this.edges.hasOwnProperty(edgeId)) {
  15273. var edge = this.edges[edgeId];
  15274. if (edge.via == null) {
  15275. var nodeId = "edgeId:".concat(edge.id);
  15276. this.sectors['support']['nodes'][nodeId] = new Node(
  15277. {id:nodeId,
  15278. mass:1,
  15279. shape:'circle',
  15280. image:"",
  15281. internalMultiplier:1
  15282. },{},{},this.constants);
  15283. edge.via = this.sectors['support']['nodes'][nodeId];
  15284. edge.via.parentEdgeId = edge.id;
  15285. edge.positionBezierNode();
  15286. }
  15287. }
  15288. }
  15289. }
  15290. };
  15291. /**
  15292. * load the functions that load the mixins into the prototype.
  15293. *
  15294. * @private
  15295. */
  15296. Network.prototype._initializeMixinLoaders = function () {
  15297. for (var mixin in MixinLoader) {
  15298. if (MixinLoader.hasOwnProperty(mixin)) {
  15299. Network.prototype[mixin] = MixinLoader[mixin];
  15300. }
  15301. }
  15302. };
  15303. /**
  15304. * Load the XY positions of the nodes into the dataset.
  15305. */
  15306. Network.prototype.storePosition = function() {
  15307. console.log("storePosition is depricated: use .storePositions() from now on.")
  15308. this.storePositions();
  15309. };
  15310. /**
  15311. * Load the XY positions of the nodes into the dataset.
  15312. */
  15313. Network.prototype.storePositions = function() {
  15314. var dataArray = [];
  15315. for (var nodeId in this.nodes) {
  15316. if (this.nodes.hasOwnProperty(nodeId)) {
  15317. var node = this.nodes[nodeId];
  15318. var allowedToMoveX = !this.nodes.xFixed;
  15319. var allowedToMoveY = !this.nodes.yFixed;
  15320. if (this.nodesData._data[nodeId].x != Math.round(node.x) || this.nodesData._data[nodeId].y != Math.round(node.y)) {
  15321. dataArray.push({id:nodeId,x:Math.round(node.x),y:Math.round(node.y),allowedToMoveX:allowedToMoveX,allowedToMoveY:allowedToMoveY});
  15322. }
  15323. }
  15324. }
  15325. this.nodesData.update(dataArray);
  15326. };
  15327. /**
  15328. * Return the positions of the nodes.
  15329. */
  15330. Network.prototype.getPositions = function(ids) {
  15331. var dataArray = {};
  15332. if (ids !== undefined) {
  15333. if (Array.isArray(ids) == true) {
  15334. for (var i = 0; i < ids.length; i++) {
  15335. if (this.nodes[ids[i]] !== undefined) {
  15336. var node = this.nodes[ids[i]];
  15337. dataArray[ids[i]] = {x: Math.round(node.x), y: Math.round(node.y)};
  15338. }
  15339. }
  15340. }
  15341. else {
  15342. if (this.nodes[ids] !== undefined) {
  15343. var node = this.nodes[ids];
  15344. dataArray[ids] = {x: Math.round(node.x), y: Math.round(node.y)};
  15345. }
  15346. }
  15347. }
  15348. else {
  15349. for (var nodeId in this.nodes) {
  15350. if (this.nodes.hasOwnProperty(nodeId)) {
  15351. var node = this.nodes[nodeId];
  15352. dataArray[nodeId] = {x: Math.round(node.x), y: Math.round(node.y)};
  15353. }
  15354. }
  15355. }
  15356. return dataArray;
  15357. };
  15358. /**
  15359. * Center a node in view.
  15360. *
  15361. * @param {Number} nodeId
  15362. * @param {Number} [options]
  15363. */
  15364. Network.prototype.focusOnNode = function (nodeId, options) {
  15365. if (this.nodes.hasOwnProperty(nodeId)) {
  15366. if (options === undefined) {
  15367. options = {};
  15368. }
  15369. var nodePosition = {x: this.nodes[nodeId].x, y: this.nodes[nodeId].y};
  15370. options.position = nodePosition;
  15371. options.lockedOnNode = nodeId;
  15372. this.moveTo(options)
  15373. }
  15374. else {
  15375. console.log("This nodeId cannot be found.");
  15376. }
  15377. };
  15378. /**
  15379. *
  15380. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  15381. * | options.scale = Number // scale to move to
  15382. * | options.position = {x:Number, y:Number} // position to move to
  15383. * | options.animation = {duration:Number, easingFunction:String} || Boolean // position to move to
  15384. */
  15385. Network.prototype.moveTo = function (options) {
  15386. if (options === undefined) {
  15387. options = {};
  15388. return;
  15389. }
  15390. if (options.offset === undefined) {options.offset = {x: 0, y: 0}; }
  15391. if (options.offset.x === undefined) {options.offset.x = 0; }
  15392. if (options.offset.y === undefined) {options.offset.y = 0; }
  15393. if (options.scale === undefined) {options.scale = this._getScale(); }
  15394. if (options.position === undefined) {options.position = this._getTranslation();}
  15395. if (options.animation === undefined) {options.animation = {duration:0}; }
  15396. if (options.animation === false ) {options.animation = {duration:0}; }
  15397. if (options.animation === true ) {options.animation = {}; }
  15398. if (options.animation.duration === undefined) {options.animation.duration = 1000; } // default duration
  15399. if (options.animation.easingFunction === undefined) {options.animation.easingFunction = "easeInOutQuad"; } // default easing function
  15400. this.animateView(options);
  15401. };
  15402. /**
  15403. *
  15404. * @param {Object} options | options.offset = {x:Number, y:Number} // offset from the center in DOM pixels
  15405. * | options.time = Number // animation time in milliseconds
  15406. * | options.scale = Number // scale to animate to
  15407. * | options.position = {x:Number, y:Number} // position to animate to
  15408. * | options.easingFunction = String // linear, easeInQuad, easeOutQuad, easeInOutQuad,
  15409. * // easeInCubic, easeOutCubic, easeInOutCubic,
  15410. * // easeInQuart, easeOutQuart, easeInOutQuart,
  15411. * // easeInQuint, easeOutQuint, easeInOutQuint
  15412. */
  15413. Network.prototype.animateView = function (options) {
  15414. if (options === undefined) {
  15415. options = {};
  15416. return;
  15417. }
  15418. // release if something focussed on the node
  15419. this.releaseNode();
  15420. if (options.locked == true) {
  15421. this.lockedOnNodeId = options.lockedOnNode;
  15422. this.lockedOnNodeOffset = options.offset;
  15423. }
  15424. // forcefully complete the old animation if it was still running
  15425. if (this.easingTime != 0) {
  15426. this._transitionRedraw(1); // by setting easingtime to 1, we finish the animation.
  15427. }
  15428. this.sourceScale = this._getScale();
  15429. this.sourceTranslation = this._getTranslation();
  15430. this.targetScale = options.scale;
  15431. // set the scale so the viewCenter is based on the correct zoom level. This is overridden in the transitionRedraw
  15432. // but at least then we'll have the target transition
  15433. this._setScale(this.targetScale);
  15434. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  15435. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  15436. x: viewCenter.x - options.position.x,
  15437. y: viewCenter.y - options.position.y
  15438. };
  15439. this.targetTranslation = {
  15440. x: this.sourceTranslation.x + distanceFromCenter.x * this.targetScale + options.offset.x,
  15441. y: this.sourceTranslation.y + distanceFromCenter.y * this.targetScale + options.offset.y
  15442. };
  15443. // if the time is set to 0, don't do an animation
  15444. if (options.animation.duration == 0) {
  15445. if (this.lockedOnNodeId != null) {
  15446. this._classicRedraw = this._redraw;
  15447. this._redraw = this._lockedRedraw;
  15448. }
  15449. else {
  15450. this._setScale(this.targetScale);
  15451. this._setTranslation(this.targetTranslation.x, this.targetTranslation.y);
  15452. this._redraw();
  15453. }
  15454. }
  15455. else {
  15456. this.animationSpeed = 1 / (this.renderRefreshRate * options.animation.duration * 0.001) || 1 / this.renderRefreshRate;
  15457. this.animationEasingFunction = options.animation.easingFunction;
  15458. this._classicRedraw = this._redraw;
  15459. this._redraw = this._transitionRedraw;
  15460. this._redraw();
  15461. this.moving = true;
  15462. this.start();
  15463. }
  15464. };
  15465. Network.prototype._lockedRedraw = function () {
  15466. var nodePosition = {x: this.nodes[this.lockedOnNodeId].x, y: this.nodes[this.lockedOnNodeId].y};
  15467. var viewCenter = this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  15468. var distanceFromCenter = { // offset from view, distance view has to change by these x and y to center the node
  15469. x: viewCenter.x - nodePosition.x,
  15470. y: viewCenter.y - nodePosition.y
  15471. };
  15472. var sourceTranslation = this._getTranslation();
  15473. var targetTranslation = {
  15474. x: sourceTranslation.x + distanceFromCenter.x * this.scale + this.lockedOnNodeOffset.x,
  15475. y: sourceTranslation.y + distanceFromCenter.y * this.scale + this.lockedOnNodeOffset.y
  15476. };
  15477. this._setTranslation(targetTranslation.x,targetTranslation.y);
  15478. this._classicRedraw();
  15479. }
  15480. Network.prototype.releaseNode = function () {
  15481. if (this.lockedOnNodeId != null) {
  15482. this._redraw = this._classicRedraw;
  15483. this.lockedOnNodeId = null;
  15484. this.lockedOnNodeOffset = null;
  15485. }
  15486. }
  15487. /**
  15488. *
  15489. * @param easingTime
  15490. * @private
  15491. */
  15492. Network.prototype._transitionRedraw = function (easingTime) {
  15493. this.easingTime = easingTime || this.easingTime + this.animationSpeed;
  15494. this.easingTime += this.animationSpeed;
  15495. var progress = util.easingFunctions[this.animationEasingFunction](this.easingTime);
  15496. this._setScale(this.sourceScale + (this.targetScale - this.sourceScale) * progress);
  15497. this._setTranslation(
  15498. this.sourceTranslation.x + (this.targetTranslation.x - this.sourceTranslation.x) * progress,
  15499. this.sourceTranslation.y + (this.targetTranslation.y - this.sourceTranslation.y) * progress
  15500. );
  15501. this._classicRedraw();
  15502. this.moving = true;
  15503. // cleanup
  15504. if (this.easingTime >= 1.0) {
  15505. this.easingTime = 0;
  15506. if (this.lockedOnNodeId != null) {
  15507. this._redraw = this._lockedRedraw;
  15508. }
  15509. else {
  15510. this._redraw = this._classicRedraw;
  15511. }
  15512. this.emit("animationFinished");
  15513. }
  15514. };
  15515. Network.prototype._classicRedraw = function () {
  15516. // placeholder function to be overloaded by animations;
  15517. };
  15518. /**
  15519. * Returns true when the Network is active.
  15520. * @returns {boolean}
  15521. */
  15522. Network.prototype.isActive = function () {
  15523. return !this.activator || this.activator.active;
  15524. };
  15525. /**
  15526. * Sets the scale
  15527. * @returns {Number}
  15528. */
  15529. Network.prototype.setScale = function () {
  15530. return this._setScale();
  15531. };
  15532. /**
  15533. * Returns the scale
  15534. * @returns {Number}
  15535. */
  15536. Network.prototype.getScale = function () {
  15537. return this._getScale();
  15538. };
  15539. /**
  15540. * Returns the scale
  15541. * @returns {Number}
  15542. */
  15543. Network.prototype.getCenterCoordinates = function () {
  15544. return this.DOMtoCanvas({x: 0.5 * this.frame.canvas.clientWidth, y: 0.5 * this.frame.canvas.clientHeight});
  15545. };
  15546. module.exports = Network;
  15547. /***/ },
  15548. /* 37 */
  15549. /***/ function(module, exports, __webpack_require__) {
  15550. var util = __webpack_require__(1);
  15551. var Node = __webpack_require__(40);
  15552. /**
  15553. * @class Edge
  15554. *
  15555. * A edge connects two nodes
  15556. * @param {Object} properties Object with properties. Must contain
  15557. * At least properties from and to.
  15558. * Available properties: from (number),
  15559. * to (number), label (string, color (string),
  15560. * width (number), style (string),
  15561. * length (number), title (string)
  15562. * @param {Network} network A Network object, used to find and edge to
  15563. * nodes.
  15564. * @param {Object} constants An object with default values for
  15565. * example for the color
  15566. */
  15567. function Edge (properties, network, networkConstants) {
  15568. if (!network) {
  15569. throw "No network provided";
  15570. }
  15571. var fields = ['edges','physics'];
  15572. var constants = util.selectiveBridgeObject(fields,networkConstants);
  15573. this.options = constants.edges;
  15574. this.physics = constants.physics;
  15575. this.options['smoothCurves'] = networkConstants['smoothCurves'];
  15576. this.network = network;
  15577. // initialize variables
  15578. this.id = undefined;
  15579. this.fromId = undefined;
  15580. this.toId = undefined;
  15581. this.title = undefined;
  15582. this.widthSelected = this.options.width * this.options.widthSelectionMultiplier;
  15583. this.value = undefined;
  15584. this.selected = false;
  15585. this.hover = false;
  15586. this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached
  15587. this.dirtyLabel = true;
  15588. this.from = null; // a node
  15589. this.to = null; // a node
  15590. this.via = null; // a temp node
  15591. // we use this to be able to reconnect the edge to a cluster if its node is put into a cluster
  15592. // by storing the original information we can revert to the original connection when the cluser is opened.
  15593. this.originalFromId = [];
  15594. this.originalToId = [];
  15595. this.connected = false;
  15596. this.widthFixed = false;
  15597. this.lengthFixed = false;
  15598. this.setProperties(properties);
  15599. this.controlNodesEnabled = false;
  15600. this.controlNodes = {from:null, to:null, positions:{}};
  15601. this.connectedNode = null;
  15602. }
  15603. /**
  15604. * Set or overwrite properties for the edge
  15605. * @param {Object} properties an object with properties
  15606. * @param {Object} constants and object with default, global properties
  15607. */
  15608. Edge.prototype.setProperties = function(properties) {
  15609. if (!properties) {
  15610. return;
  15611. }
  15612. var fields = ['style','fontSize','fontFace','fontColor','fontFill','width',
  15613. 'widthSelectionMultiplier','hoverWidth','arrowScaleFactor','dash','inheritColor'
  15614. ];
  15615. util.selectiveDeepExtend(fields, this.options, properties);
  15616. if (properties.from !== undefined) {this.fromId = properties.from;}
  15617. if (properties.to !== undefined) {this.toId = properties.to;}
  15618. if (properties.id !== undefined) {this.id = properties.id;}
  15619. if (properties.label !== undefined) {this.label = properties.label; this.dirtyLabel = true;}
  15620. if (properties.title !== undefined) {this.title = properties.title;}
  15621. if (properties.value !== undefined) {this.value = properties.value;}
  15622. if (properties.length !== undefined) {this.physics.springLength = properties.length;}
  15623. if (properties.color !== undefined) {
  15624. this.options.inheritColor = false;
  15625. if (util.isString(properties.color)) {
  15626. this.options.color.color = properties.color;
  15627. this.options.color.highlight = properties.color;
  15628. }
  15629. else {
  15630. if (properties.color.color !== undefined) {this.options.color.color = properties.color.color;}
  15631. if (properties.color.highlight !== undefined) {this.options.color.highlight = properties.color.highlight;}
  15632. if (properties.color.hover !== undefined) {this.options.color.hover = properties.color.hover;}
  15633. }
  15634. }
  15635. // A node is connected when it has a from and to node.
  15636. this.connect();
  15637. this.widthFixed = this.widthFixed || (properties.width !== undefined);
  15638. this.lengthFixed = this.lengthFixed || (properties.length !== undefined);
  15639. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  15640. // set draw method based on style
  15641. switch (this.options.style) {
  15642. case 'line': this.draw = this._drawLine; break;
  15643. case 'arrow': this.draw = this._drawArrow; break;
  15644. case 'arrow-center': this.draw = this._drawArrowCenter; break;
  15645. case 'dash-line': this.draw = this._drawDashLine; break;
  15646. default: this.draw = this._drawLine; break;
  15647. }
  15648. };
  15649. /**
  15650. * Connect an edge to its nodes
  15651. */
  15652. Edge.prototype.connect = function () {
  15653. this.disconnect();
  15654. this.from = this.network.nodes[this.fromId] || null;
  15655. this.to = this.network.nodes[this.toId] || null;
  15656. this.connected = (this.from && this.to);
  15657. if (this.connected) {
  15658. this.from.attachEdge(this);
  15659. this.to.attachEdge(this);
  15660. }
  15661. else {
  15662. if (this.from) {
  15663. this.from.detachEdge(this);
  15664. }
  15665. if (this.to) {
  15666. this.to.detachEdge(this);
  15667. }
  15668. }
  15669. };
  15670. /**
  15671. * Disconnect an edge from its nodes
  15672. */
  15673. Edge.prototype.disconnect = function () {
  15674. if (this.from) {
  15675. this.from.detachEdge(this);
  15676. this.from = null;
  15677. }
  15678. if (this.to) {
  15679. this.to.detachEdge(this);
  15680. this.to = null;
  15681. }
  15682. this.connected = false;
  15683. };
  15684. /**
  15685. * get the title of this edge.
  15686. * @return {string} title The title of the edge, or undefined when no title
  15687. * has been set.
  15688. */
  15689. Edge.prototype.getTitle = function() {
  15690. return typeof this.title === "function" ? this.title() : this.title;
  15691. };
  15692. /**
  15693. * Retrieve the value of the edge. Can be undefined
  15694. * @return {Number} value
  15695. */
  15696. Edge.prototype.getValue = function() {
  15697. return this.value;
  15698. };
  15699. /**
  15700. * Adjust the value range of the edge. The edge will adjust it's width
  15701. * based on its value.
  15702. * @param {Number} min
  15703. * @param {Number} max
  15704. */
  15705. Edge.prototype.setValueRange = function(min, max) {
  15706. if (!this.widthFixed && this.value !== undefined) {
  15707. var scale = (this.options.widthMax - this.options.widthMin) / (max - min);
  15708. this.options.width= (this.value - min) * scale + this.options.widthMin;
  15709. this.widthSelected = this.options.width* this.options.widthSelectionMultiplier;
  15710. }
  15711. };
  15712. /**
  15713. * Redraw a edge
  15714. * Draw this edge in the given canvas
  15715. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15716. * @param {CanvasRenderingContext2D} ctx
  15717. */
  15718. Edge.prototype.draw = function(ctx) {
  15719. throw "Method draw not initialized in edge";
  15720. };
  15721. /**
  15722. * Check if this object is overlapping with the provided object
  15723. * @param {Object} obj an object with parameters left, top
  15724. * @return {boolean} True if location is located on the edge
  15725. */
  15726. Edge.prototype.isOverlappingWith = function(obj) {
  15727. if (this.connected) {
  15728. var distMax = 10;
  15729. var xFrom = this.from.x;
  15730. var yFrom = this.from.y;
  15731. var xTo = this.to.x;
  15732. var yTo = this.to.y;
  15733. var xObj = obj.left;
  15734. var yObj = obj.top;
  15735. var dist = this._getDistanceToEdge(xFrom, yFrom, xTo, yTo, xObj, yObj);
  15736. return (dist < distMax);
  15737. }
  15738. else {
  15739. return false
  15740. }
  15741. };
  15742. Edge.prototype._getColor = function() {
  15743. var colorObj = this.options.color;
  15744. if (this.options.inheritColor == "to") {
  15745. colorObj = {
  15746. highlight: this.to.options.color.highlight.border,
  15747. hover: this.to.options.color.hover.border,
  15748. color: this.to.options.color.border
  15749. };
  15750. }
  15751. else if (this.options.inheritColor == "from" || this.options.inheritColor == true) {
  15752. colorObj = {
  15753. highlight: this.from.options.color.highlight.border,
  15754. hover: this.from.options.color.hover.border,
  15755. color: this.from.options.color.border
  15756. };
  15757. }
  15758. if (this.selected == true) {return colorObj.highlight;}
  15759. else if (this.hover == true) {return colorObj.hover;}
  15760. else {return colorObj.color;}
  15761. };
  15762. /**
  15763. * Redraw a edge as a line
  15764. * Draw this edge in the given canvas
  15765. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  15766. * @param {CanvasRenderingContext2D} ctx
  15767. * @private
  15768. */
  15769. Edge.prototype._drawLine = function(ctx) {
  15770. // set style
  15771. ctx.strokeStyle = this._getColor();
  15772. ctx.lineWidth = this._getLineWidth();
  15773. if (this.from != this.to) {
  15774. // draw line
  15775. var via = this._line(ctx);
  15776. // draw label
  15777. var point;
  15778. if (this.label) {
  15779. if (this.options.smoothCurves.enabled == true && via != null) {
  15780. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  15781. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  15782. point = {x:midpointX, y:midpointY};
  15783. }
  15784. else {
  15785. point = this._pointOnLine(0.5);
  15786. }
  15787. this._label(ctx, this.label, point.x, point.y);
  15788. }
  15789. }
  15790. else {
  15791. var x, y;
  15792. var radius = this.physics.springLength / 4;
  15793. var node = this.from;
  15794. if (!node.width) {
  15795. node.resize(ctx);
  15796. }
  15797. if (node.width > node.height) {
  15798. x = node.x + node.width / 2;
  15799. y = node.y - radius;
  15800. }
  15801. else {
  15802. x = node.x + radius;
  15803. y = node.y - node.height / 2;
  15804. }
  15805. this._circle(ctx, x, y, radius);
  15806. point = this._pointOnCircle(x, y, radius, 0.5);
  15807. this._label(ctx, this.label, point.x, point.y);
  15808. }
  15809. };
  15810. /**
  15811. * Get the line width of the edge. Depends on width and whether one of the
  15812. * connected nodes is selected.
  15813. * @return {Number} width
  15814. * @private
  15815. */
  15816. Edge.prototype._getLineWidth = function() {
  15817. if (this.selected == true) {
  15818. return Math.max(Math.min(this.widthSelected, this.options.widthMax), 0.3*this.networkScaleInv);
  15819. }
  15820. else {
  15821. if (this.hover == true) {
  15822. return Math.max(Math.min(this.options.hoverWidth, this.options.widthMax), 0.3*this.networkScaleInv);
  15823. }
  15824. else {
  15825. return Math.max(this.options.width, 0.3*this.networkScaleInv);
  15826. }
  15827. }
  15828. };
  15829. Edge.prototype._getViaCoordinates = function () {
  15830. var xVia = null;
  15831. var yVia = null;
  15832. var factor = this.options.smoothCurves.roundness;
  15833. var type = this.options.smoothCurves.type;
  15834. var dx = Math.abs(this.from.x - this.to.x);
  15835. var dy = Math.abs(this.from.y - this.to.y);
  15836. if (type == 'discrete' || type == 'diagonalCross') {
  15837. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  15838. if (this.from.y > this.to.y) {
  15839. if (this.from.x < this.to.x) {
  15840. xVia = this.from.x + factor * dy;
  15841. yVia = this.from.y - factor * dy;
  15842. }
  15843. else if (this.from.x > this.to.x) {
  15844. xVia = this.from.x - factor * dy;
  15845. yVia = this.from.y - factor * dy;
  15846. }
  15847. }
  15848. else if (this.from.y < this.to.y) {
  15849. if (this.from.x < this.to.x) {
  15850. xVia = this.from.x + factor * dy;
  15851. yVia = this.from.y + factor * dy;
  15852. }
  15853. else if (this.from.x > this.to.x) {
  15854. xVia = this.from.x - factor * dy;
  15855. yVia = this.from.y + factor * dy;
  15856. }
  15857. }
  15858. if (type == "discrete") {
  15859. xVia = dx < factor * dy ? this.from.x : xVia;
  15860. }
  15861. }
  15862. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  15863. if (this.from.y > this.to.y) {
  15864. if (this.from.x < this.to.x) {
  15865. xVia = this.from.x + factor * dx;
  15866. yVia = this.from.y - factor * dx;
  15867. }
  15868. else if (this.from.x > this.to.x) {
  15869. xVia = this.from.x - factor * dx;
  15870. yVia = this.from.y - factor * dx;
  15871. }
  15872. }
  15873. else if (this.from.y < this.to.y) {
  15874. if (this.from.x < this.to.x) {
  15875. xVia = this.from.x + factor * dx;
  15876. yVia = this.from.y + factor * dx;
  15877. }
  15878. else if (this.from.x > this.to.x) {
  15879. xVia = this.from.x - factor * dx;
  15880. yVia = this.from.y + factor * dx;
  15881. }
  15882. }
  15883. if (type == "discrete") {
  15884. yVia = dy < factor * dx ? this.from.y : yVia;
  15885. }
  15886. }
  15887. }
  15888. else if (type == "straightCross") {
  15889. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) { // up - down
  15890. xVia = this.from.x;
  15891. if (this.from.y < this.to.y) {
  15892. yVia = this.to.y - (1-factor) * dy;
  15893. }
  15894. else {
  15895. yVia = this.to.y + (1-factor) * dy;
  15896. }
  15897. }
  15898. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) { // left - right
  15899. if (this.from.x < this.to.x) {
  15900. xVia = this.to.x - (1-factor) * dx;
  15901. }
  15902. else {
  15903. xVia = this.to.x + (1-factor) * dx;
  15904. }
  15905. yVia = this.from.y;
  15906. }
  15907. }
  15908. else if (type == 'horizontal') {
  15909. if (this.from.x < this.to.x) {
  15910. xVia = this.to.x - (1-factor) * dx;
  15911. }
  15912. else {
  15913. xVia = this.to.x + (1-factor) * dx;
  15914. }
  15915. yVia = this.from.y;
  15916. }
  15917. else if (type == 'vertical') {
  15918. xVia = this.from.x;
  15919. if (this.from.y < this.to.y) {
  15920. yVia = this.to.y - (1-factor) * dy;
  15921. }
  15922. else {
  15923. yVia = this.to.y + (1-factor) * dy;
  15924. }
  15925. }
  15926. else { // continuous
  15927. if (Math.abs(this.from.x - this.to.x) < Math.abs(this.from.y - this.to.y)) {
  15928. if (this.from.y > this.to.y) {
  15929. if (this.from.x < this.to.x) {
  15930. // console.log(1)
  15931. xVia = this.from.x + factor * dy;
  15932. yVia = this.from.y - factor * dy;
  15933. xVia = this.to.x < xVia ? this.to.x : xVia;
  15934. }
  15935. else if (this.from.x > this.to.x) {
  15936. // console.log(2)
  15937. xVia = this.from.x - factor * dy;
  15938. yVia = this.from.y - factor * dy;
  15939. xVia = this.to.x > xVia ? this.to.x :xVia;
  15940. }
  15941. }
  15942. else if (this.from.y < this.to.y) {
  15943. if (this.from.x < this.to.x) {
  15944. // console.log(3)
  15945. xVia = this.from.x + factor * dy;
  15946. yVia = this.from.y + factor * dy;
  15947. xVia = this.to.x < xVia ? this.to.x : xVia;
  15948. }
  15949. else if (this.from.x > this.to.x) {
  15950. // console.log(4, this.from.x, this.to.x)
  15951. xVia = this.from.x - factor * dy;
  15952. yVia = this.from.y + factor * dy;
  15953. xVia = this.to.x > xVia ? this.to.x : xVia;
  15954. }
  15955. }
  15956. }
  15957. else if (Math.abs(this.from.x - this.to.x) > Math.abs(this.from.y - this.to.y)) {
  15958. if (this.from.y > this.to.y) {
  15959. if (this.from.x < this.to.x) {
  15960. // console.log(5)
  15961. xVia = this.from.x + factor * dx;
  15962. yVia = this.from.y - factor * dx;
  15963. yVia = this.to.y > yVia ? this.to.y : yVia;
  15964. }
  15965. else if (this.from.x > this.to.x) {
  15966. // console.log(6)
  15967. xVia = this.from.x - factor * dx;
  15968. yVia = this.from.y - factor * dx;
  15969. yVia = this.to.y > yVia ? this.to.y : yVia;
  15970. }
  15971. }
  15972. else if (this.from.y < this.to.y) {
  15973. if (this.from.x < this.to.x) {
  15974. // console.log(7)
  15975. xVia = this.from.x + factor * dx;
  15976. yVia = this.from.y + factor * dx;
  15977. yVia = this.to.y < yVia ? this.to.y : yVia;
  15978. }
  15979. else if (this.from.x > this.to.x) {
  15980. // console.log(8)
  15981. xVia = this.from.x - factor * dx;
  15982. yVia = this.from.y + factor * dx;
  15983. yVia = this.to.y < yVia ? this.to.y : yVia;
  15984. }
  15985. }
  15986. }
  15987. }
  15988. return {x:xVia, y:yVia};
  15989. };
  15990. /**
  15991. * Draw a line between two nodes
  15992. * @param {CanvasRenderingContext2D} ctx
  15993. * @private
  15994. */
  15995. Edge.prototype._line = function (ctx) {
  15996. // draw a straight line
  15997. ctx.beginPath();
  15998. ctx.moveTo(this.from.x, this.from.y);
  15999. if (this.options.smoothCurves.enabled == true) {
  16000. if (this.options.smoothCurves.dynamic == false) {
  16001. var via = this._getViaCoordinates();
  16002. if (via.x == null) {
  16003. ctx.lineTo(this.to.x, this.to.y);
  16004. ctx.stroke();
  16005. return null;
  16006. }
  16007. else {
  16008. // this.via.x = via.x;
  16009. // this.via.y = via.y;
  16010. ctx.quadraticCurveTo(via.x,via.y,this.to.x, this.to.y);
  16011. ctx.stroke();
  16012. return via;
  16013. }
  16014. }
  16015. else {
  16016. ctx.quadraticCurveTo(this.via.x,this.via.y,this.to.x, this.to.y);
  16017. ctx.stroke();
  16018. return this.via;
  16019. }
  16020. }
  16021. else {
  16022. ctx.lineTo(this.to.x, this.to.y);
  16023. ctx.stroke();
  16024. return null;
  16025. }
  16026. };
  16027. /**
  16028. * Draw a line from a node to itself, a circle
  16029. * @param {CanvasRenderingContext2D} ctx
  16030. * @param {Number} x
  16031. * @param {Number} y
  16032. * @param {Number} radius
  16033. * @private
  16034. */
  16035. Edge.prototype._circle = function (ctx, x, y, radius) {
  16036. // draw a circle
  16037. ctx.beginPath();
  16038. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  16039. ctx.stroke();
  16040. };
  16041. /**
  16042. * Draw label with white background and with the middle at (x, y)
  16043. * @param {CanvasRenderingContext2D} ctx
  16044. * @param {String} text
  16045. * @param {Number} x
  16046. * @param {Number} y
  16047. * @private
  16048. */
  16049. Edge.prototype._label = function (ctx, text, x, y) {
  16050. if (text) {
  16051. ctx.font = ((this.from.selected || this.to.selected) ? "bold " : "") +
  16052. this.options.fontSize + "px " + this.options.fontFace;
  16053. var yLine;
  16054. if (this.dirtyLabel == true) {
  16055. var lines = String(text).split('\n');
  16056. var lineCount = lines.length;
  16057. var fontSize = (Number(this.options.fontSize) + 4);
  16058. yLine = y + (1 - lineCount) / 2 * fontSize;
  16059. var width = ctx.measureText(lines[0]).width;
  16060. for (var i = 1; i < lineCount; i++) {
  16061. var lineWidth = ctx.measureText(lines[i]).width;
  16062. width = lineWidth > width ? lineWidth : width;
  16063. }
  16064. var height = this.options.fontSize * lineCount;
  16065. var left = x - width / 2;
  16066. var top = y - height / 2;
  16067. // cache
  16068. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  16069. }
  16070. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  16071. ctx.fillStyle = this.options.fontFill;
  16072. ctx.fillRect(this.labelDimensions.left,
  16073. this.labelDimensions.top,
  16074. this.labelDimensions.width,
  16075. this.labelDimensions.height);
  16076. }
  16077. // draw text
  16078. ctx.fillStyle = this.options.fontColor || "black";
  16079. ctx.textAlign = "center";
  16080. ctx.textBaseline = "middle";
  16081. yLine = this.labelDimensions.yLine;
  16082. for (var i = 0; i < lineCount; i++) {
  16083. ctx.fillText(lines[i], x, yLine);
  16084. yLine += fontSize;
  16085. }
  16086. }
  16087. };
  16088. /**
  16089. * Redraw a edge as a dashed line
  16090. * Draw this edge in the given canvas
  16091. * @author David Jordan
  16092. * @date 2012-08-08
  16093. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16094. * @param {CanvasRenderingContext2D} ctx
  16095. * @private
  16096. */
  16097. Edge.prototype._drawDashLine = function(ctx) {
  16098. // set style
  16099. ctx.strokeStyle = this._getColor();
  16100. ctx.lineWidth = this._getLineWidth();
  16101. var via = null;
  16102. // only firefox and chrome support this method, else we use the legacy one.
  16103. if (ctx.mozDash !== undefined || ctx.setLineDash !== undefined) {
  16104. // configure the dash pattern
  16105. var pattern = [0];
  16106. if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) {
  16107. pattern = [this.options.dash.length,this.options.dash.gap];
  16108. }
  16109. else {
  16110. pattern = [5,5];
  16111. }
  16112. // set dash settings for chrome or firefox
  16113. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  16114. ctx.setLineDash(pattern);
  16115. ctx.lineDashOffset = 0;
  16116. } else { //Firefox
  16117. ctx.mozDash = pattern;
  16118. ctx.mozDashOffset = 0;
  16119. }
  16120. // draw the line
  16121. via = this._line(ctx);
  16122. // restore the dash settings.
  16123. if (typeof ctx.setLineDash !== 'undefined') { //Chrome
  16124. ctx.setLineDash([0]);
  16125. ctx.lineDashOffset = 0;
  16126. } else { //Firefox
  16127. ctx.mozDash = [0];
  16128. ctx.mozDashOffset = 0;
  16129. }
  16130. }
  16131. else { // unsupporting smooth lines
  16132. // draw dashed line
  16133. ctx.beginPath();
  16134. ctx.lineCap = 'round';
  16135. if (this.options.dash.altLength !== undefined) //If an alt dash value has been set add to the array this value
  16136. {
  16137. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  16138. [this.options.dash.length,this.options.dash.gap,this.options.dash.altLength,this.options.dash.gap]);
  16139. }
  16140. else if (this.options.dash.length !== undefined && this.options.dash.gap !== undefined) //If a dash and gap value has been set add to the array this value
  16141. {
  16142. ctx.dashedLine(this.from.x,this.from.y,this.to.x,this.to.y,
  16143. [this.options.dash.length,this.options.dash.gap]);
  16144. }
  16145. else //If all else fails draw a line
  16146. {
  16147. ctx.moveTo(this.from.x, this.from.y);
  16148. ctx.lineTo(this.to.x, this.to.y);
  16149. }
  16150. ctx.stroke();
  16151. }
  16152. // draw label
  16153. if (this.label) {
  16154. var point;
  16155. if (this.options.smoothCurves.enabled == true && via != null) {
  16156. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  16157. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  16158. point = {x:midpointX, y:midpointY};
  16159. }
  16160. else {
  16161. point = this._pointOnLine(0.5);
  16162. }
  16163. this._label(ctx, this.label, point.x, point.y);
  16164. }
  16165. };
  16166. /**
  16167. * Get a point on a line
  16168. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  16169. * @return {Object} point
  16170. * @private
  16171. */
  16172. Edge.prototype._pointOnLine = function (percentage) {
  16173. return {
  16174. x: (1 - percentage) * this.from.x + percentage * this.to.x,
  16175. y: (1 - percentage) * this.from.y + percentage * this.to.y
  16176. }
  16177. };
  16178. /**
  16179. * Get a point on a circle
  16180. * @param {Number} x
  16181. * @param {Number} y
  16182. * @param {Number} radius
  16183. * @param {Number} percentage. Value between 0 (line start) and 1 (line end)
  16184. * @return {Object} point
  16185. * @private
  16186. */
  16187. Edge.prototype._pointOnCircle = function (x, y, radius, percentage) {
  16188. var angle = (percentage - 3/8) * 2 * Math.PI;
  16189. return {
  16190. x: x + radius * Math.cos(angle),
  16191. y: y - radius * Math.sin(angle)
  16192. }
  16193. };
  16194. /**
  16195. * Redraw a edge as a line with an arrow halfway the line
  16196. * Draw this edge in the given canvas
  16197. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16198. * @param {CanvasRenderingContext2D} ctx
  16199. * @private
  16200. */
  16201. Edge.prototype._drawArrowCenter = function(ctx) {
  16202. var point;
  16203. // set style
  16204. ctx.strokeStyle = this._getColor();
  16205. ctx.lineWidth = this._getLineWidth();
  16206. if (this.from != this.to) {
  16207. // draw line
  16208. var via = this._line(ctx);
  16209. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  16210. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  16211. // draw an arrow halfway the line
  16212. if (this.options.smoothCurves.enabled == true && via != null) {
  16213. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  16214. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  16215. point = {x:midpointX, y:midpointY};
  16216. }
  16217. else {
  16218. point = this._pointOnLine(0.5);
  16219. }
  16220. ctx.arrow(point.x, point.y, angle, length);
  16221. ctx.fill();
  16222. ctx.stroke();
  16223. // draw label
  16224. if (this.label) {
  16225. this._label(ctx, this.label, point.x, point.y);
  16226. }
  16227. }
  16228. else {
  16229. // draw circle
  16230. var x, y;
  16231. var radius = 0.25 * Math.max(100,this.physics.springLength);
  16232. var node = this.from;
  16233. if (!node.width) {
  16234. node.resize(ctx);
  16235. }
  16236. if (node.width > node.height) {
  16237. x = node.x + node.width * 0.5;
  16238. y = node.y - radius;
  16239. }
  16240. else {
  16241. x = node.x + radius;
  16242. y = node.y - node.height * 0.5;
  16243. }
  16244. this._circle(ctx, x, y, radius);
  16245. // draw all arrows
  16246. var angle = 0.2 * Math.PI;
  16247. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  16248. point = this._pointOnCircle(x, y, radius, 0.5);
  16249. ctx.arrow(point.x, point.y, angle, length);
  16250. ctx.fill();
  16251. ctx.stroke();
  16252. // draw label
  16253. if (this.label) {
  16254. point = this._pointOnCircle(x, y, radius, 0.5);
  16255. this._label(ctx, this.label, point.x, point.y);
  16256. }
  16257. }
  16258. };
  16259. /**
  16260. * Redraw a edge as a line with an arrow
  16261. * Draw this edge in the given canvas
  16262. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  16263. * @param {CanvasRenderingContext2D} ctx
  16264. * @private
  16265. */
  16266. Edge.prototype._drawArrow = function(ctx) {
  16267. // set style
  16268. ctx.strokeStyle = this._getColor();
  16269. ctx.lineWidth = this._getLineWidth();
  16270. var angle, length;
  16271. //draw a line
  16272. if (this.from != this.to) {
  16273. angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  16274. var dx = (this.to.x - this.from.x);
  16275. var dy = (this.to.y - this.from.y);
  16276. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  16277. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  16278. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  16279. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  16280. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  16281. var via;
  16282. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true ) {
  16283. via = this.via;
  16284. }
  16285. else if (this.options.smoothCurves.enabled == true) {
  16286. via = this._getViaCoordinates();
  16287. }
  16288. if (this.options.smoothCurves.enabled == true && via.x != null) {
  16289. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  16290. dx = (this.to.x - via.x);
  16291. dy = (this.to.y - via.y);
  16292. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  16293. }
  16294. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  16295. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  16296. var xTo,yTo;
  16297. if (this.options.smoothCurves.enabled == true && via.x != null) {
  16298. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  16299. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  16300. }
  16301. else {
  16302. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  16303. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  16304. }
  16305. ctx.beginPath();
  16306. ctx.moveTo(xFrom,yFrom);
  16307. if (this.options.smoothCurves.enabled == true && via.x != null) {
  16308. ctx.quadraticCurveTo(via.x,via.y,xTo, yTo);
  16309. }
  16310. else {
  16311. ctx.lineTo(xTo, yTo);
  16312. }
  16313. ctx.stroke();
  16314. // draw arrow at the end of the line
  16315. length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  16316. ctx.arrow(xTo, yTo, angle, length);
  16317. ctx.fill();
  16318. ctx.stroke();
  16319. // draw label
  16320. if (this.label) {
  16321. var point;
  16322. if (this.options.smoothCurves.enabled == true && via != null) {
  16323. var midpointX = 0.5*(0.5*(this.from.x + via.x) + 0.5*(this.to.x + via.x));
  16324. var midpointY = 0.5*(0.5*(this.from.y + via.y) + 0.5*(this.to.y + via.y));
  16325. point = {x:midpointX, y:midpointY};
  16326. }
  16327. else {
  16328. point = this._pointOnLine(0.5);
  16329. }
  16330. this._label(ctx, this.label, point.x, point.y);
  16331. }
  16332. }
  16333. else {
  16334. // draw circle
  16335. var node = this.from;
  16336. var x, y, arrow;
  16337. var radius = 0.25 * Math.max(100,this.physics.springLength);
  16338. if (!node.width) {
  16339. node.resize(ctx);
  16340. }
  16341. if (node.width > node.height) {
  16342. x = node.x + node.width * 0.5;
  16343. y = node.y - radius;
  16344. arrow = {
  16345. x: x,
  16346. y: node.y,
  16347. angle: 0.9 * Math.PI
  16348. };
  16349. }
  16350. else {
  16351. x = node.x + radius;
  16352. y = node.y - node.height * 0.5;
  16353. arrow = {
  16354. x: node.x,
  16355. y: y,
  16356. angle: 0.6 * Math.PI
  16357. };
  16358. }
  16359. ctx.beginPath();
  16360. // TODO: similarly, for a line without arrows, draw to the border of the nodes instead of the center
  16361. ctx.arc(x, y, radius, 0, 2 * Math.PI, false);
  16362. ctx.stroke();
  16363. // draw all arrows
  16364. var length = (10 + 5 * this.options.width) * this.options.arrowScaleFactor;
  16365. ctx.arrow(arrow.x, arrow.y, arrow.angle, length);
  16366. ctx.fill();
  16367. ctx.stroke();
  16368. // draw label
  16369. if (this.label) {
  16370. point = this._pointOnCircle(x, y, radius, 0.5);
  16371. this._label(ctx, this.label, point.x, point.y);
  16372. }
  16373. }
  16374. };
  16375. /**
  16376. * Calculate the distance between a point (x3,y3) and a line segment from
  16377. * (x1,y1) to (x2,y2).
  16378. * http://stackoverflow.com/questions/849211/shortest-distancae-between-a-point-and-a-line-segment
  16379. * @param {number} x1
  16380. * @param {number} y1
  16381. * @param {number} x2
  16382. * @param {number} y2
  16383. * @param {number} x3
  16384. * @param {number} y3
  16385. * @private
  16386. */
  16387. Edge.prototype._getDistanceToEdge = function (x1,y1, x2,y2, x3,y3) { // x3,y3 is the point
  16388. var returnValue = 0;
  16389. if (this.from != this.to) {
  16390. if (this.options.smoothCurves.enabled == true) {
  16391. var xVia, yVia;
  16392. if (this.options.smoothCurves.enabled == true && this.options.smoothCurves.dynamic == true) {
  16393. xVia = this.via.x;
  16394. yVia = this.via.y;
  16395. }
  16396. else {
  16397. var via = this._getViaCoordinates();
  16398. xVia = via.x;
  16399. yVia = via.y;
  16400. }
  16401. var minDistance = 1e9;
  16402. var distance;
  16403. var i,t,x,y, lastX, lastY;
  16404. for (i = 0; i < 10; i++) {
  16405. t = 0.1*i;
  16406. x = Math.pow(1-t,2)*x1 + (2*t*(1 - t))*xVia + Math.pow(t,2)*x2;
  16407. y = Math.pow(1-t,2)*y1 + (2*t*(1 - t))*yVia + Math.pow(t,2)*y2;
  16408. if (i > 0) {
  16409. distance = this._getDistanceToLine(lastX,lastY,x,y, x3,y3);
  16410. minDistance = distance < minDistance ? distance : minDistance;
  16411. }
  16412. lastX = x; lastY = y;
  16413. }
  16414. returnValue = minDistance;
  16415. }
  16416. else {
  16417. returnValue = this._getDistanceToLine(x1,y1,x2,y2,x3,y3);
  16418. }
  16419. }
  16420. else {
  16421. var x, y, dx, dy;
  16422. var radius = 0.25 * this.physics.springLength;
  16423. var node = this.from;
  16424. if (node.width > node.height) {
  16425. x = node.x + 0.5 * node.width;
  16426. y = node.y - radius;
  16427. }
  16428. else {
  16429. x = node.x + radius;
  16430. y = node.y - 0.5 * node.height;
  16431. }
  16432. dx = x - x3;
  16433. dy = y - y3;
  16434. returnValue = Math.abs(Math.sqrt(dx*dx + dy*dy) - radius);
  16435. }
  16436. if (this.labelDimensions.left < x3 &&
  16437. this.labelDimensions.left + this.labelDimensions.width > x3 &&
  16438. this.labelDimensions.top < y3 &&
  16439. this.labelDimensions.top + this.labelDimensions.height > y3) {
  16440. return 0;
  16441. }
  16442. else {
  16443. return returnValue;
  16444. }
  16445. };
  16446. Edge.prototype._getDistanceToLine = function(x1,y1,x2,y2,x3,y3) {
  16447. var px = x2-x1,
  16448. py = y2-y1,
  16449. something = px*px + py*py,
  16450. u = ((x3 - x1) * px + (y3 - y1) * py) / something;
  16451. if (u > 1) {
  16452. u = 1;
  16453. }
  16454. else if (u < 0) {
  16455. u = 0;
  16456. }
  16457. var x = x1 + u * px,
  16458. y = y1 + u * py,
  16459. dx = x - x3,
  16460. dy = y - y3;
  16461. //# Note: If the actual distance does not matter,
  16462. //# if you only want to compare what this function
  16463. //# returns to other results of this function, you
  16464. //# can just return the squared distance instead
  16465. //# (i.e. remove the sqrt) to gain a little performance
  16466. return Math.sqrt(dx*dx + dy*dy);
  16467. };
  16468. /**
  16469. * This allows the zoom level of the network to influence the rendering
  16470. *
  16471. * @param scale
  16472. */
  16473. Edge.prototype.setScale = function(scale) {
  16474. this.networkScaleInv = 1.0/scale;
  16475. };
  16476. Edge.prototype.select = function() {
  16477. this.selected = true;
  16478. };
  16479. Edge.prototype.unselect = function() {
  16480. this.selected = false;
  16481. };
  16482. Edge.prototype.positionBezierNode = function() {
  16483. if (this.via !== null && this.from !== null && this.to !== null) {
  16484. this.via.x = 0.5 * (this.from.x + this.to.x);
  16485. this.via.y = 0.5 * (this.from.y + this.to.y);
  16486. }
  16487. };
  16488. /**
  16489. * This function draws the control nodes for the manipulator. In order to enable this, only set the this.controlNodesEnabled to true.
  16490. * @param ctx
  16491. */
  16492. Edge.prototype._drawControlNodes = function(ctx) {
  16493. if (this.controlNodesEnabled == true) {
  16494. if (this.controlNodes.from === null && this.controlNodes.to === null) {
  16495. var nodeIdFrom = "edgeIdFrom:".concat(this.id);
  16496. var nodeIdTo = "edgeIdTo:".concat(this.id);
  16497. var constants = {
  16498. nodes:{group:'', radius:8},
  16499. physics:{damping:0},
  16500. clustering: {maxNodeSizeIncrements: 0 ,nodeScaling: {width:0, height: 0, radius:0}}
  16501. };
  16502. this.controlNodes.from = new Node(
  16503. {id:nodeIdFrom,
  16504. shape:'dot',
  16505. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  16506. },{},{},constants);
  16507. this.controlNodes.to = new Node(
  16508. {id:nodeIdTo,
  16509. shape:'dot',
  16510. color:{background:'#ff4e00', border:'#3c3c3c', highlight: {background:'#07f968'}}
  16511. },{},{},constants);
  16512. }
  16513. if (this.controlNodes.from.selected == false && this.controlNodes.to.selected == false) {
  16514. this.controlNodes.positions = this.getControlNodePositions(ctx);
  16515. this.controlNodes.from.x = this.controlNodes.positions.from.x;
  16516. this.controlNodes.from.y = this.controlNodes.positions.from.y;
  16517. this.controlNodes.to.x = this.controlNodes.positions.to.x;
  16518. this.controlNodes.to.y = this.controlNodes.positions.to.y;
  16519. }
  16520. this.controlNodes.from.draw(ctx);
  16521. this.controlNodes.to.draw(ctx);
  16522. }
  16523. else {
  16524. this.controlNodes = {from:null, to:null, positions:{}};
  16525. }
  16526. };
  16527. /**
  16528. * Enable control nodes.
  16529. * @private
  16530. */
  16531. Edge.prototype._enableControlNodes = function() {
  16532. this.controlNodesEnabled = true;
  16533. };
  16534. /**
  16535. * disable control nodes
  16536. * @private
  16537. */
  16538. Edge.prototype._disableControlNodes = function() {
  16539. this.controlNodesEnabled = false;
  16540. };
  16541. /**
  16542. * This checks if one of the control nodes is selected and if so, returns the control node object. Else it returns null.
  16543. * @param x
  16544. * @param y
  16545. * @returns {null}
  16546. * @private
  16547. */
  16548. Edge.prototype._getSelectedControlNode = function(x,y) {
  16549. var positions = this.controlNodes.positions;
  16550. var fromDistance = Math.sqrt(Math.pow(x - positions.from.x,2) + Math.pow(y - positions.from.y,2));
  16551. var toDistance = Math.sqrt(Math.pow(x - positions.to.x ,2) + Math.pow(y - positions.to.y ,2));
  16552. if (fromDistance < 15) {
  16553. this.connectedNode = this.from;
  16554. this.from = this.controlNodes.from;
  16555. return this.controlNodes.from;
  16556. }
  16557. else if (toDistance < 15) {
  16558. this.connectedNode = this.to;
  16559. this.to = this.controlNodes.to;
  16560. return this.controlNodes.to;
  16561. }
  16562. else {
  16563. return null;
  16564. }
  16565. };
  16566. /**
  16567. * this resets the control nodes to their original position.
  16568. * @private
  16569. */
  16570. Edge.prototype._restoreControlNodes = function() {
  16571. if (this.controlNodes.from.selected == true) {
  16572. this.from = this.connectedNode;
  16573. this.connectedNode = null;
  16574. this.controlNodes.from.unselect();
  16575. }
  16576. if (this.controlNodes.to.selected == true) {
  16577. this.to = this.connectedNode;
  16578. this.connectedNode = null;
  16579. this.controlNodes.to.unselect();
  16580. }
  16581. };
  16582. /**
  16583. * this calculates the position of the control nodes on the edges of the parent nodes.
  16584. *
  16585. * @param ctx
  16586. * @returns {{from: {x: number, y: number}, to: {x: *, y: *}}}
  16587. */
  16588. Edge.prototype.getControlNodePositions = function(ctx) {
  16589. var angle = Math.atan2((this.to.y - this.from.y), (this.to.x - this.from.x));
  16590. var dx = (this.to.x - this.from.x);
  16591. var dy = (this.to.y - this.from.y);
  16592. var edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  16593. var fromBorderDist = this.from.distanceToBorder(ctx, angle + Math.PI);
  16594. var fromBorderPoint = (edgeSegmentLength - fromBorderDist) / edgeSegmentLength;
  16595. var xFrom = (fromBorderPoint) * this.from.x + (1 - fromBorderPoint) * this.to.x;
  16596. var yFrom = (fromBorderPoint) * this.from.y + (1 - fromBorderPoint) * this.to.y;
  16597. var via;
  16598. if (this.options.smoothCurves.dynamic == true && this.options.smoothCurves.enabled == true) {
  16599. via = this.via;
  16600. }
  16601. else if (this.options.smoothCurves.enabled == true) {
  16602. via = this._getViaCoordinates();
  16603. }
  16604. if (this.options.smoothCurves.enabled == true && via.x != null) {
  16605. angle = Math.atan2((this.to.y - via.y), (this.to.x - via.x));
  16606. dx = (this.to.x - via.x);
  16607. dy = (this.to.y - via.y);
  16608. edgeSegmentLength = Math.sqrt(dx * dx + dy * dy);
  16609. }
  16610. var toBorderDist = this.to.distanceToBorder(ctx, angle);
  16611. var toBorderPoint = (edgeSegmentLength - toBorderDist) / edgeSegmentLength;
  16612. var xTo,yTo;
  16613. if (this.options.smoothCurves.enabled == true && via.x != null) {
  16614. xTo = (1 - toBorderPoint) * via.x + toBorderPoint * this.to.x;
  16615. yTo = (1 - toBorderPoint) * via.y + toBorderPoint * this.to.y;
  16616. }
  16617. else {
  16618. xTo = (1 - toBorderPoint) * this.from.x + toBorderPoint * this.to.x;
  16619. yTo = (1 - toBorderPoint) * this.from.y + toBorderPoint * this.to.y;
  16620. }
  16621. return {from:{x:xFrom,y:yFrom},to:{x:xTo,y:yTo}};
  16622. };
  16623. module.exports = Edge;
  16624. /***/ },
  16625. /* 38 */
  16626. /***/ function(module, exports, __webpack_require__) {
  16627. var util = __webpack_require__(1);
  16628. /**
  16629. * @class Groups
  16630. * This class can store groups and properties specific for groups.
  16631. */
  16632. function Groups() {
  16633. this.clear();
  16634. this.defaultIndex = 0;
  16635. }
  16636. /**
  16637. * default constants for group colors
  16638. */
  16639. Groups.DEFAULT = [
  16640. {border: "#2B7CE9", background: "#97C2FC", highlight: {border: "#2B7CE9", background: "#D2E5FF"}, hover: {border: "#2B7CE9", background: "#D2E5FF"}}, // blue
  16641. {border: "#FFA500", background: "#FFFF00", highlight: {border: "#FFA500", background: "#FFFFA3"}, hover: {border: "#FFA500", background: "#FFFFA3"}}, // yellow
  16642. {border: "#FA0A10", background: "#FB7E81", highlight: {border: "#FA0A10", background: "#FFAFB1"}, hover: {border: "#FA0A10", background: "#FFAFB1"}}, // red
  16643. {border: "#41A906", background: "#7BE141", highlight: {border: "#41A906", background: "#A1EC76"}, hover: {border: "#41A906", background: "#A1EC76"}}, // green
  16644. {border: "#E129F0", background: "#EB7DF4", highlight: {border: "#E129F0", background: "#F0B3F5"}, hover: {border: "#E129F0", background: "#F0B3F5"}}, // magenta
  16645. {border: "#7C29F0", background: "#AD85E4", highlight: {border: "#7C29F0", background: "#D3BDF0"}, hover: {border: "#7C29F0", background: "#D3BDF0"}}, // purple
  16646. {border: "#C37F00", background: "#FFA807", highlight: {border: "#C37F00", background: "#FFCA66"}, hover: {border: "#C37F00", background: "#FFCA66"}}, // orange
  16647. {border: "#4220FB", background: "#6E6EFD", highlight: {border: "#4220FB", background: "#9B9BFD"}, hover: {border: "#4220FB", background: "#9B9BFD"}}, // darkblue
  16648. {border: "#FD5A77", background: "#FFC0CB", highlight: {border: "#FD5A77", background: "#FFD1D9"}, hover: {border: "#FD5A77", background: "#FFD1D9"}}, // pink
  16649. {border: "#4AD63A", background: "#C2FABC", highlight: {border: "#4AD63A", background: "#E6FFE3"}, hover: {border: "#4AD63A", background: "#E6FFE3"}} // mint
  16650. ];
  16651. /**
  16652. * Clear all groups
  16653. */
  16654. Groups.prototype.clear = function () {
  16655. this.groups = {};
  16656. this.groups.length = function()
  16657. {
  16658. var i = 0;
  16659. for ( var p in this ) {
  16660. if (this.hasOwnProperty(p)) {
  16661. i++;
  16662. }
  16663. }
  16664. return i;
  16665. }
  16666. };
  16667. /**
  16668. * get group properties of a groupname. If groupname is not found, a new group
  16669. * is added.
  16670. * @param {*} groupname Can be a number, string, Date, etc.
  16671. * @return {Object} group The created group, containing all group properties
  16672. */
  16673. Groups.prototype.get = function (groupname) {
  16674. var group = this.groups[groupname];
  16675. if (group == undefined) {
  16676. // create new group
  16677. var index = this.defaultIndex % Groups.DEFAULT.length;
  16678. this.defaultIndex++;
  16679. group = {};
  16680. group.color = Groups.DEFAULT[index];
  16681. this.groups[groupname] = group;
  16682. }
  16683. return group;
  16684. };
  16685. /**
  16686. * Add a custom group style
  16687. * @param {String} groupname
  16688. * @param {Object} style An object containing borderColor,
  16689. * backgroundColor, etc.
  16690. * @return {Object} group The created group object
  16691. */
  16692. Groups.prototype.add = function (groupname, style) {
  16693. this.groups[groupname] = style;
  16694. if (style.color) {
  16695. style.color = util.parseColor(style.color);
  16696. }
  16697. return style;
  16698. };
  16699. module.exports = Groups;
  16700. /***/ },
  16701. /* 39 */
  16702. /***/ function(module, exports, __webpack_require__) {
  16703. /**
  16704. * @class Images
  16705. * This class loads images and keeps them stored.
  16706. */
  16707. function Images() {
  16708. this.images = {};
  16709. this.callback = undefined;
  16710. }
  16711. /**
  16712. * Set an onload callback function. This will be called each time an image
  16713. * is loaded
  16714. * @param {function} callback
  16715. */
  16716. Images.prototype.setOnloadCallback = function(callback) {
  16717. this.callback = callback;
  16718. };
  16719. /**
  16720. *
  16721. * @param {string} url Url of the image
  16722. * @param {string} url Url of an image to use if the url image is not found
  16723. * @return {Image} img The image object
  16724. */
  16725. Images.prototype.load = function(url, brokenUrl) {
  16726. var img = this.images[url];
  16727. if (img == undefined) {
  16728. // create the image
  16729. var images = this;
  16730. img = new Image();
  16731. this.images[url] = img;
  16732. img.onload = function() {
  16733. if (images.callback) {
  16734. images.callback(this);
  16735. }
  16736. };
  16737. img.onerror = function () {
  16738. this.src = brokenUrl;
  16739. if (images.callback) {
  16740. images.callback(this);
  16741. }
  16742. };
  16743. img.src = url;
  16744. }
  16745. return img;
  16746. };
  16747. module.exports = Images;
  16748. /***/ },
  16749. /* 40 */
  16750. /***/ function(module, exports, __webpack_require__) {
  16751. var util = __webpack_require__(1);
  16752. /**
  16753. * @class Node
  16754. * A node. A node can be connected to other nodes via one or multiple edges.
  16755. * @param {object} properties An object containing properties for the node. All
  16756. * properties are optional, except for the id.
  16757. * {number} id Id of the node. Required
  16758. * {string} label Text label for the node
  16759. * {number} x Horizontal position of the node
  16760. * {number} y Vertical position of the node
  16761. * {string} shape Node shape, available:
  16762. * "database", "circle", "ellipse",
  16763. * "box", "image", "text", "dot",
  16764. * "star", "triangle", "triangleDown",
  16765. * "square"
  16766. * {string} image An image url
  16767. * {string} title An title text, can be HTML
  16768. * {anytype} group A group name or number
  16769. * @param {Network.Images} imagelist A list with images. Only needed
  16770. * when the node has an image
  16771. * @param {Network.Groups} grouplist A list with groups. Needed for
  16772. * retrieving group properties
  16773. * @param {Object} constants An object with default values for
  16774. * example for the color
  16775. *
  16776. */
  16777. function Node(properties, imagelist, grouplist, networkConstants) {
  16778. var constants = util.selectiveBridgeObject(['nodes'],networkConstants);
  16779. this.options = constants.nodes;
  16780. this.selected = false;
  16781. this.hover = false;
  16782. this.edges = []; // all edges connected to this node
  16783. this.dynamicEdges = [];
  16784. this.reroutedEdges = {};
  16785. this.fontDrawThreshold = 3;
  16786. // set defaults for the properties
  16787. this.id = undefined;
  16788. this.x = null;
  16789. this.y = null;
  16790. this.allowedToMoveX = false;
  16791. this.allowedToMoveY = false;
  16792. this.xFixed = false;
  16793. this.yFixed = false;
  16794. this.horizontalAlignLeft = true; // these are for the navigation controls
  16795. this.verticalAlignTop = true; // these are for the navigation controls
  16796. this.baseRadiusValue = networkConstants.nodes.radius;
  16797. this.radiusFixed = false;
  16798. this.level = -1;
  16799. this.preassignedLevel = false;
  16800. this.hierarchyEnumerated = false;
  16801. this.labelDimensions = {top:0,left:0,width:0,height:0,yLine:0}; // could be cached
  16802. this.imagelist = imagelist;
  16803. this.grouplist = grouplist;
  16804. // physics properties
  16805. this.fx = 0.0; // external force x
  16806. this.fy = 0.0; // external force y
  16807. this.vx = 0.0; // velocity x
  16808. this.vy = 0.0; // velocity y
  16809. this.damping = networkConstants.physics.damping; // written every time gravity is calculated
  16810. this.fixedData = {x:null,y:null};
  16811. this.setProperties(properties, constants);
  16812. // creating the variables for clustering
  16813. this.resetCluster();
  16814. this.dynamicEdgesLength = 0;
  16815. this.clusterSession = 0;
  16816. this.clusterSizeWidthFactor = networkConstants.clustering.nodeScaling.width;
  16817. this.clusterSizeHeightFactor = networkConstants.clustering.nodeScaling.height;
  16818. this.clusterSizeRadiusFactor = networkConstants.clustering.nodeScaling.radius;
  16819. this.maxNodeSizeIncrements = networkConstants.clustering.maxNodeSizeIncrements;
  16820. this.growthIndicator = 0;
  16821. // variables to tell the node about the network.
  16822. this.networkScaleInv = 1;
  16823. this.networkScale = 1;
  16824. this.canvasTopLeft = {"x": -300, "y": -300};
  16825. this.canvasBottomRight = {"x": 300, "y": 300};
  16826. this.parentEdgeId = null;
  16827. }
  16828. /**
  16829. * (re)setting the clustering variables and objects
  16830. */
  16831. Node.prototype.resetCluster = function() {
  16832. // clustering variables
  16833. this.formationScale = undefined; // this is used to determine when to open the cluster
  16834. this.clusterSize = 1; // this signifies the total amount of nodes in this cluster
  16835. this.containedNodes = {};
  16836. this.containedEdges = {};
  16837. this.clusterSessions = [];
  16838. };
  16839. /**
  16840. * Attach a edge to the node
  16841. * @param {Edge} edge
  16842. */
  16843. Node.prototype.attachEdge = function(edge) {
  16844. if (this.edges.indexOf(edge) == -1) {
  16845. this.edges.push(edge);
  16846. }
  16847. if (this.dynamicEdges.indexOf(edge) == -1) {
  16848. this.dynamicEdges.push(edge);
  16849. }
  16850. this.dynamicEdgesLength = this.dynamicEdges.length;
  16851. };
  16852. /**
  16853. * Detach a edge from the node
  16854. * @param {Edge} edge
  16855. */
  16856. Node.prototype.detachEdge = function(edge) {
  16857. var index = this.edges.indexOf(edge);
  16858. if (index != -1) {
  16859. this.edges.splice(index, 1);
  16860. }
  16861. index = this.dynamicEdges.indexOf(edge);
  16862. if (index != -1) {
  16863. this.dynamicEdges.splice(index, 1);
  16864. }
  16865. this.dynamicEdgesLength = this.dynamicEdges.length;
  16866. };
  16867. /**
  16868. * Set or overwrite properties for the node
  16869. * @param {Object} properties an object with properties
  16870. * @param {Object} constants and object with default, global properties
  16871. */
  16872. Node.prototype.setProperties = function(properties, constants) {
  16873. if (!properties) {
  16874. return;
  16875. }
  16876. var fields = ['borderWidth','borderWidthSelected','shape','image','brokenImage','radius','fontColor',
  16877. 'fontSize','fontFace','fontFill','group','mass'
  16878. ];
  16879. util.selectiveDeepExtend(fields, this.options, properties);
  16880. // basic properties
  16881. if (properties.id !== undefined) {this.id = properties.id;}
  16882. if (properties.label !== undefined) {this.label = properties.label; this.originalLabel = properties.label;}
  16883. if (properties.title !== undefined) {this.title = properties.title;}
  16884. if (properties.x !== undefined) {this.x = properties.x;}
  16885. if (properties.y !== undefined) {this.y = properties.y;}
  16886. if (properties.value !== undefined) {this.value = properties.value;}
  16887. if (properties.level !== undefined) {this.level = properties.level; this.preassignedLevel = true;}
  16888. // navigation controls properties
  16889. if (properties.horizontalAlignLeft !== undefined) {this.horizontalAlignLeft = properties.horizontalAlignLeft;}
  16890. if (properties.verticalAlignTop !== undefined) {this.verticalAlignTop = properties.verticalAlignTop;}
  16891. if (properties.triggerFunction !== undefined) {this.triggerFunction = properties.triggerFunction;}
  16892. if (this.id === undefined) {
  16893. throw "Node must have an id";
  16894. }
  16895. // copy group properties
  16896. if (typeof this.options.group === 'number' || (typeof this.options.group === 'string' && this.options.group != '')) {
  16897. var groupObj = this.grouplist.get(this.options.group);
  16898. for (var prop in groupObj) {
  16899. if (groupObj.hasOwnProperty(prop)) {
  16900. this.options[prop] = groupObj[prop];
  16901. }
  16902. }
  16903. }
  16904. // individual shape properties
  16905. if (properties.radius !== undefined) {this.baseRadiusValue = this.options.radius;}
  16906. if (properties.color !== undefined) {this.options.color = util.parseColor(properties.color);}
  16907. if (this.options.image!== undefined && this.options.image!= "") {
  16908. if (this.imagelist) {
  16909. this.imageObj = this.imagelist.load(this.options.image, this.options.brokenImage);
  16910. }
  16911. else {
  16912. throw "No imagelist provided";
  16913. }
  16914. }
  16915. if (properties.allowedToMoveX !== undefined) {
  16916. this.xFixed = !properties.allowedToMoveX;
  16917. this.allowedToMoveX = properties.allowedToMoveX;
  16918. }
  16919. else if (properties.x !== undefined && this.allowedToMoveX == false) {
  16920. this.xFixed = true;
  16921. }
  16922. if (properties.allowedToMoveY !== undefined) {
  16923. this.yFixed = !properties.allowedToMoveY;
  16924. this.allowedToMoveY = properties.allowedToMoveY;
  16925. }
  16926. else if (properties.y !== undefined && this.allowedToMoveY == false) {
  16927. this.yFixed = true;
  16928. }
  16929. this.radiusFixed = this.radiusFixed || (properties.radius !== undefined);
  16930. if (this.options.shape == 'image') {
  16931. this.options.radiusMin = constants.nodes.widthMin;
  16932. this.options.radiusMax = constants.nodes.widthMax;
  16933. }
  16934. // choose draw method depending on the shape
  16935. switch (this.options.shape) {
  16936. case 'database': this.draw = this._drawDatabase; this.resize = this._resizeDatabase; break;
  16937. case 'box': this.draw = this._drawBox; this.resize = this._resizeBox; break;
  16938. case 'circle': this.draw = this._drawCircle; this.resize = this._resizeCircle; break;
  16939. case 'ellipse': this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  16940. // TODO: add diamond shape
  16941. case 'image': this.draw = this._drawImage; this.resize = this._resizeImage; break;
  16942. case 'text': this.draw = this._drawText; this.resize = this._resizeText; break;
  16943. case 'dot': this.draw = this._drawDot; this.resize = this._resizeShape; break;
  16944. case 'square': this.draw = this._drawSquare; this.resize = this._resizeShape; break;
  16945. case 'triangle': this.draw = this._drawTriangle; this.resize = this._resizeShape; break;
  16946. case 'triangleDown': this.draw = this._drawTriangleDown; this.resize = this._resizeShape; break;
  16947. case 'star': this.draw = this._drawStar; this.resize = this._resizeShape; break;
  16948. default: this.draw = this._drawEllipse; this.resize = this._resizeEllipse; break;
  16949. }
  16950. // reset the size of the node, this can be changed
  16951. this._reset();
  16952. };
  16953. /**
  16954. * select this node
  16955. */
  16956. Node.prototype.select = function() {
  16957. this.selected = true;
  16958. this._reset();
  16959. };
  16960. /**
  16961. * unselect this node
  16962. */
  16963. Node.prototype.unselect = function() {
  16964. this.selected = false;
  16965. this._reset();
  16966. };
  16967. /**
  16968. * Reset the calculated size of the node, forces it to recalculate its size
  16969. */
  16970. Node.prototype.clearSizeCache = function() {
  16971. this._reset();
  16972. };
  16973. /**
  16974. * Reset the calculated size of the node, forces it to recalculate its size
  16975. * @private
  16976. */
  16977. Node.prototype._reset = function() {
  16978. this.width = undefined;
  16979. this.height = undefined;
  16980. };
  16981. /**
  16982. * get the title of this node.
  16983. * @return {string} title The title of the node, or undefined when no title
  16984. * has been set.
  16985. */
  16986. Node.prototype.getTitle = function() {
  16987. return typeof this.title === "function" ? this.title() : this.title;
  16988. };
  16989. /**
  16990. * Calculate the distance to the border of the Node
  16991. * @param {CanvasRenderingContext2D} ctx
  16992. * @param {Number} angle Angle in radians
  16993. * @returns {number} distance Distance to the border in pixels
  16994. */
  16995. Node.prototype.distanceToBorder = function (ctx, angle) {
  16996. var borderWidth = 1;
  16997. if (!this.width) {
  16998. this.resize(ctx);
  16999. }
  17000. switch (this.options.shape) {
  17001. case 'circle':
  17002. case 'dot':
  17003. return this.options.radius+ borderWidth;
  17004. case 'ellipse':
  17005. var a = this.width / 2;
  17006. var b = this.height / 2;
  17007. var w = (Math.sin(angle) * a);
  17008. var h = (Math.cos(angle) * b);
  17009. return a * b / Math.sqrt(w * w + h * h);
  17010. // TODO: implement distanceToBorder for database
  17011. // TODO: implement distanceToBorder for triangle
  17012. // TODO: implement distanceToBorder for triangleDown
  17013. case 'box':
  17014. case 'image':
  17015. case 'text':
  17016. default:
  17017. if (this.width) {
  17018. return Math.min(
  17019. Math.abs(this.width / 2 / Math.cos(angle)),
  17020. Math.abs(this.height / 2 / Math.sin(angle))) + borderWidth;
  17021. // TODO: reckon with border radius too in case of box
  17022. }
  17023. else {
  17024. return 0;
  17025. }
  17026. }
  17027. // TODO: implement calculation of distance to border for all shapes
  17028. };
  17029. /**
  17030. * Set forces acting on the node
  17031. * @param {number} fx Force in horizontal direction
  17032. * @param {number} fy Force in vertical direction
  17033. */
  17034. Node.prototype._setForce = function(fx, fy) {
  17035. this.fx = fx;
  17036. this.fy = fy;
  17037. };
  17038. /**
  17039. * Add forces acting on the node
  17040. * @param {number} fx Force in horizontal direction
  17041. * @param {number} fy Force in vertical direction
  17042. * @private
  17043. */
  17044. Node.prototype._addForce = function(fx, fy) {
  17045. this.fx += fx;
  17046. this.fy += fy;
  17047. };
  17048. /**
  17049. * Perform one discrete step for the node
  17050. * @param {number} interval Time interval in seconds
  17051. */
  17052. Node.prototype.discreteStep = function(interval) {
  17053. if (!this.xFixed) {
  17054. var dx = this.damping * this.vx; // damping force
  17055. var ax = (this.fx - dx) / this.options.mass; // acceleration
  17056. this.vx += ax * interval; // velocity
  17057. this.x += this.vx * interval; // position
  17058. }
  17059. else {
  17060. this.fx = 0;
  17061. this.vx = 0;
  17062. }
  17063. if (!this.yFixed) {
  17064. var dy = this.damping * this.vy; // damping force
  17065. var ay = (this.fy - dy) / this.options.mass; // acceleration
  17066. this.vy += ay * interval; // velocity
  17067. this.y += this.vy * interval; // position
  17068. }
  17069. else {
  17070. this.fy = 0;
  17071. this.vy = 0;
  17072. }
  17073. };
  17074. /**
  17075. * Perform one discrete step for the node
  17076. * @param {number} interval Time interval in seconds
  17077. * @param {number} maxVelocity The speed limit imposed on the velocity
  17078. */
  17079. Node.prototype.discreteStepLimited = function(interval, maxVelocity) {
  17080. if (!this.xFixed) {
  17081. var dx = this.damping * this.vx; // damping force
  17082. var ax = (this.fx - dx) / this.options.mass; // acceleration
  17083. this.vx += ax * interval; // velocity
  17084. this.vx = (Math.abs(this.vx) > maxVelocity) ? ((this.vx > 0) ? maxVelocity : -maxVelocity) : this.vx;
  17085. this.x += this.vx * interval; // position
  17086. }
  17087. else {
  17088. this.fx = 0;
  17089. this.vx = 0;
  17090. }
  17091. if (!this.yFixed) {
  17092. var dy = this.damping * this.vy; // damping force
  17093. var ay = (this.fy - dy) / this.options.mass; // acceleration
  17094. this.vy += ay * interval; // velocity
  17095. this.vy = (Math.abs(this.vy) > maxVelocity) ? ((this.vy > 0) ? maxVelocity : -maxVelocity) : this.vy;
  17096. this.y += this.vy * interval; // position
  17097. }
  17098. else {
  17099. this.fy = 0;
  17100. this.vy = 0;
  17101. }
  17102. };
  17103. /**
  17104. * Check if this node has a fixed x and y position
  17105. * @return {boolean} true if fixed, false if not
  17106. */
  17107. Node.prototype.isFixed = function() {
  17108. return (this.xFixed && this.yFixed);
  17109. };
  17110. /**
  17111. * Check if this node is moving
  17112. * @param {number} vmin the minimum velocity considered as "moving"
  17113. * @return {boolean} true if moving, false if it has no velocity
  17114. */
  17115. Node.prototype.isMoving = function(vmin) {
  17116. var velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2));
  17117. // this.velocity = Math.sqrt(Math.pow(this.vx,2) + Math.pow(this.vy,2))
  17118. return (velocity > vmin);
  17119. };
  17120. /**
  17121. * check if this node is selecte
  17122. * @return {boolean} selected True if node is selected, else false
  17123. */
  17124. Node.prototype.isSelected = function() {
  17125. return this.selected;
  17126. };
  17127. /**
  17128. * Retrieve the value of the node. Can be undefined
  17129. * @return {Number} value
  17130. */
  17131. Node.prototype.getValue = function() {
  17132. return this.value;
  17133. };
  17134. /**
  17135. * Calculate the distance from the nodes location to the given location (x,y)
  17136. * @param {Number} x
  17137. * @param {Number} y
  17138. * @return {Number} value
  17139. */
  17140. Node.prototype.getDistance = function(x, y) {
  17141. var dx = this.x - x,
  17142. dy = this.y - y;
  17143. return Math.sqrt(dx * dx + dy * dy);
  17144. };
  17145. /**
  17146. * Adjust the value range of the node. The node will adjust it's radius
  17147. * based on its value.
  17148. * @param {Number} min
  17149. * @param {Number} max
  17150. */
  17151. Node.prototype.setValueRange = function(min, max) {
  17152. if (!this.radiusFixed && this.value !== undefined) {
  17153. if (max == min) {
  17154. this.options.radius= (this.options.radiusMin + this.options.radiusMax) / 2;
  17155. }
  17156. else {
  17157. var scale = (this.options.radiusMax - this.options.radiusMin) / (max - min);
  17158. this.options.radius= (this.value - min) * scale + this.options.radiusMin;
  17159. }
  17160. }
  17161. this.baseRadiusValue = this.options.radius;
  17162. };
  17163. /**
  17164. * Draw this node in the given canvas
  17165. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  17166. * @param {CanvasRenderingContext2D} ctx
  17167. */
  17168. Node.prototype.draw = function(ctx) {
  17169. throw "Draw method not initialized for node";
  17170. };
  17171. /**
  17172. * Recalculate the size of this node in the given canvas
  17173. * The 2d context of a HTML canvas can be retrieved by canvas.getContext("2d");
  17174. * @param {CanvasRenderingContext2D} ctx
  17175. */
  17176. Node.prototype.resize = function(ctx) {
  17177. throw "Resize method not initialized for node";
  17178. };
  17179. /**
  17180. * Check if this object is overlapping with the provided object
  17181. * @param {Object} obj an object with parameters left, top, right, bottom
  17182. * @return {boolean} True if location is located on node
  17183. */
  17184. Node.prototype.isOverlappingWith = function(obj) {
  17185. return (this.left < obj.right &&
  17186. this.left + this.width > obj.left &&
  17187. this.top < obj.bottom &&
  17188. this.top + this.height > obj.top);
  17189. };
  17190. Node.prototype._resizeImage = function (ctx) {
  17191. // TODO: pre calculate the image size
  17192. if (!this.width || !this.height) { // undefined or 0
  17193. var width, height;
  17194. if (this.value) {
  17195. this.options.radius= this.baseRadiusValue;
  17196. var scale = this.imageObj.height / this.imageObj.width;
  17197. if (scale !== undefined) {
  17198. width = this.options.radius|| this.imageObj.width;
  17199. height = this.options.radius* scale || this.imageObj.height;
  17200. }
  17201. else {
  17202. width = 0;
  17203. height = 0;
  17204. }
  17205. }
  17206. else {
  17207. width = this.imageObj.width;
  17208. height = this.imageObj.height;
  17209. }
  17210. this.width = width;
  17211. this.height = height;
  17212. this.growthIndicator = 0;
  17213. if (this.width > 0 && this.height > 0) {
  17214. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  17215. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  17216. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  17217. this.growthIndicator = this.width - width;
  17218. }
  17219. }
  17220. };
  17221. Node.prototype._drawImage = function (ctx) {
  17222. this._resizeImage(ctx);
  17223. this.left = this.x - this.width / 2;
  17224. this.top = this.y - this.height / 2;
  17225. var yLabel;
  17226. if (this.imageObj.width != 0 ) {
  17227. // draw the shade
  17228. if (this.clusterSize > 1) {
  17229. var lineWidth = ((this.clusterSize > 1) ? 10 : 0.0);
  17230. lineWidth *= this.networkScaleInv;
  17231. lineWidth = Math.min(0.2 * this.width,lineWidth);
  17232. ctx.globalAlpha = 0.5;
  17233. ctx.drawImage(this.imageObj, this.left - lineWidth, this.top - lineWidth, this.width + 2*lineWidth, this.height + 2*lineWidth);
  17234. }
  17235. // draw the image
  17236. ctx.globalAlpha = 1.0;
  17237. ctx.drawImage(this.imageObj, this.left, this.top, this.width, this.height);
  17238. yLabel = this.y + this.height / 2;
  17239. }
  17240. else {
  17241. // image still loading... just draw the label for now
  17242. yLabel = this.y;
  17243. }
  17244. this._label(ctx, this.label, this.x, yLabel, undefined, "top");
  17245. };
  17246. Node.prototype._resizeBox = function (ctx) {
  17247. if (!this.width) {
  17248. var margin = 5;
  17249. var textSize = this.getTextSize(ctx);
  17250. this.width = textSize.width + 2 * margin;
  17251. this.height = textSize.height + 2 * margin;
  17252. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  17253. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  17254. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  17255. // this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  17256. }
  17257. };
  17258. Node.prototype._drawBox = function (ctx) {
  17259. this._resizeBox(ctx);
  17260. this.left = this.x - this.width / 2;
  17261. this.top = this.y - this.height / 2;
  17262. var clusterLineWidth = 2.5;
  17263. var borderWidth = this.options.borderWidth;
  17264. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  17265. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  17266. // draw the outer border
  17267. if (this.clusterSize > 1) {
  17268. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17269. ctx.lineWidth *= this.networkScaleInv;
  17270. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17271. ctx.roundRect(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth, this.options.radius);
  17272. ctx.stroke();
  17273. }
  17274. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17275. ctx.lineWidth *= this.networkScaleInv;
  17276. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17277. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.options.color.background;
  17278. ctx.roundRect(this.left, this.top, this.width, this.height, this.options.radius);
  17279. ctx.fill();
  17280. ctx.stroke();
  17281. this._label(ctx, this.label, this.x, this.y);
  17282. };
  17283. Node.prototype._resizeDatabase = function (ctx) {
  17284. if (!this.width) {
  17285. var margin = 5;
  17286. var textSize = this.getTextSize(ctx);
  17287. var size = textSize.width + 2 * margin;
  17288. this.width = size;
  17289. this.height = size;
  17290. // scaling used for clustering
  17291. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  17292. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  17293. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  17294. this.growthIndicator = this.width - size;
  17295. }
  17296. };
  17297. Node.prototype._drawDatabase = function (ctx) {
  17298. this._resizeDatabase(ctx);
  17299. this.left = this.x - this.width / 2;
  17300. this.top = this.y - this.height / 2;
  17301. var clusterLineWidth = 2.5;
  17302. var borderWidth = this.options.borderWidth;
  17303. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  17304. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  17305. // draw the outer border
  17306. if (this.clusterSize > 1) {
  17307. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17308. ctx.lineWidth *= this.networkScaleInv;
  17309. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17310. ctx.database(this.x - this.width/2 - 2*ctx.lineWidth, this.y - this.height*0.5 - 2*ctx.lineWidth, this.width + 4*ctx.lineWidth, this.height + 4*ctx.lineWidth);
  17311. ctx.stroke();
  17312. }
  17313. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17314. ctx.lineWidth *= this.networkScaleInv;
  17315. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17316. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  17317. ctx.database(this.x - this.width/2, this.y - this.height*0.5, this.width, this.height);
  17318. ctx.fill();
  17319. ctx.stroke();
  17320. this._label(ctx, this.label, this.x, this.y);
  17321. };
  17322. Node.prototype._resizeCircle = function (ctx) {
  17323. if (!this.width) {
  17324. var margin = 5;
  17325. var textSize = this.getTextSize(ctx);
  17326. var diameter = Math.max(textSize.width, textSize.height) + 2 * margin;
  17327. this.options.radius = diameter / 2;
  17328. this.width = diameter;
  17329. this.height = diameter;
  17330. // scaling used for clustering
  17331. // this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeWidthFactor;
  17332. // this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeHeightFactor;
  17333. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  17334. this.growthIndicator = this.options.radius- 0.5*diameter;
  17335. }
  17336. };
  17337. Node.prototype._drawCircle = function (ctx) {
  17338. this._resizeCircle(ctx);
  17339. this.left = this.x - this.width / 2;
  17340. this.top = this.y - this.height / 2;
  17341. var clusterLineWidth = 2.5;
  17342. var borderWidth = this.options.borderWidth;
  17343. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  17344. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  17345. // draw the outer border
  17346. if (this.clusterSize > 1) {
  17347. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17348. ctx.lineWidth *= this.networkScaleInv;
  17349. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17350. ctx.circle(this.x, this.y, this.options.radius+2*ctx.lineWidth);
  17351. ctx.stroke();
  17352. }
  17353. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17354. ctx.lineWidth *= this.networkScaleInv;
  17355. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17356. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  17357. ctx.circle(this.x, this.y, this.options.radius);
  17358. ctx.fill();
  17359. ctx.stroke();
  17360. this._label(ctx, this.label, this.x, this.y);
  17361. };
  17362. Node.prototype._resizeEllipse = function (ctx) {
  17363. if (!this.width) {
  17364. var textSize = this.getTextSize(ctx);
  17365. this.width = textSize.width * 1.5;
  17366. this.height = textSize.height * 2;
  17367. if (this.width < this.height) {
  17368. this.width = this.height;
  17369. }
  17370. var defaultSize = this.width;
  17371. // scaling used for clustering
  17372. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  17373. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  17374. this.options.radius += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  17375. this.growthIndicator = this.width - defaultSize;
  17376. }
  17377. };
  17378. Node.prototype._drawEllipse = function (ctx) {
  17379. this._resizeEllipse(ctx);
  17380. this.left = this.x - this.width / 2;
  17381. this.top = this.y - this.height / 2;
  17382. var clusterLineWidth = 2.5;
  17383. var borderWidth = this.options.borderWidth;
  17384. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  17385. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  17386. // draw the outer border
  17387. if (this.clusterSize > 1) {
  17388. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17389. ctx.lineWidth *= this.networkScaleInv;
  17390. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17391. ctx.ellipse(this.left-2*ctx.lineWidth, this.top-2*ctx.lineWidth, this.width+4*ctx.lineWidth, this.height+4*ctx.lineWidth);
  17392. ctx.stroke();
  17393. }
  17394. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17395. ctx.lineWidth *= this.networkScaleInv;
  17396. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17397. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  17398. ctx.ellipse(this.left, this.top, this.width, this.height);
  17399. ctx.fill();
  17400. ctx.stroke();
  17401. this._label(ctx, this.label, this.x, this.y);
  17402. };
  17403. Node.prototype._drawDot = function (ctx) {
  17404. this._drawShape(ctx, 'circle');
  17405. };
  17406. Node.prototype._drawTriangle = function (ctx) {
  17407. this._drawShape(ctx, 'triangle');
  17408. };
  17409. Node.prototype._drawTriangleDown = function (ctx) {
  17410. this._drawShape(ctx, 'triangleDown');
  17411. };
  17412. Node.prototype._drawSquare = function (ctx) {
  17413. this._drawShape(ctx, 'square');
  17414. };
  17415. Node.prototype._drawStar = function (ctx) {
  17416. this._drawShape(ctx, 'star');
  17417. };
  17418. Node.prototype._resizeShape = function (ctx) {
  17419. if (!this.width) {
  17420. this.options.radius= this.baseRadiusValue;
  17421. var size = 2 * this.options.radius;
  17422. this.width = size;
  17423. this.height = size;
  17424. // scaling used for clustering
  17425. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  17426. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  17427. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * 0.5 * this.clusterSizeRadiusFactor;
  17428. this.growthIndicator = this.width - size;
  17429. }
  17430. };
  17431. Node.prototype._drawShape = function (ctx, shape) {
  17432. this._resizeShape(ctx);
  17433. this.left = this.x - this.width / 2;
  17434. this.top = this.y - this.height / 2;
  17435. var clusterLineWidth = 2.5;
  17436. var borderWidth = this.options.borderWidth;
  17437. var selectionLineWidth = this.options.borderWidthSelected || 2 * this.options.borderWidth;
  17438. var radiusMultiplier = 2;
  17439. // choose draw method depending on the shape
  17440. switch (shape) {
  17441. case 'dot': radiusMultiplier = 2; break;
  17442. case 'square': radiusMultiplier = 2; break;
  17443. case 'triangle': radiusMultiplier = 3; break;
  17444. case 'triangleDown': radiusMultiplier = 3; break;
  17445. case 'star': radiusMultiplier = 4; break;
  17446. }
  17447. ctx.strokeStyle = this.selected ? this.options.color.highlight.border : this.hover ? this.options.color.hover.border : this.options.color.border;
  17448. // draw the outer border
  17449. if (this.clusterSize > 1) {
  17450. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17451. ctx.lineWidth *= this.networkScaleInv;
  17452. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17453. ctx[shape](this.x, this.y, this.options.radius+ radiusMultiplier * ctx.lineWidth);
  17454. ctx.stroke();
  17455. }
  17456. ctx.lineWidth = (this.selected ? selectionLineWidth : borderWidth) + ((this.clusterSize > 1) ? clusterLineWidth : 0.0);
  17457. ctx.lineWidth *= this.networkScaleInv;
  17458. ctx.lineWidth = Math.min(this.width,ctx.lineWidth);
  17459. ctx.fillStyle = this.selected ? this.options.color.highlight.background : this.hover ? this.options.color.hover.background : this.options.color.background;
  17460. ctx[shape](this.x, this.y, this.options.radius);
  17461. ctx.fill();
  17462. ctx.stroke();
  17463. if (this.label) {
  17464. this._label(ctx, this.label, this.x, this.y + this.height / 2, undefined, 'top',true);
  17465. }
  17466. };
  17467. Node.prototype._resizeText = function (ctx) {
  17468. if (!this.width) {
  17469. var margin = 5;
  17470. var textSize = this.getTextSize(ctx);
  17471. this.width = textSize.width + 2 * margin;
  17472. this.height = textSize.height + 2 * margin;
  17473. // scaling used for clustering
  17474. this.width += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeWidthFactor;
  17475. this.height += Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeHeightFactor;
  17476. this.options.radius+= Math.min(this.clusterSize - 1, this.maxNodeSizeIncrements) * this.clusterSizeRadiusFactor;
  17477. this.growthIndicator = this.width - (textSize.width + 2 * margin);
  17478. }
  17479. };
  17480. Node.prototype._drawText = function (ctx) {
  17481. this._resizeText(ctx);
  17482. this.left = this.x - this.width / 2;
  17483. this.top = this.y - this.height / 2;
  17484. this._label(ctx, this.label, this.x, this.y);
  17485. };
  17486. Node.prototype._label = function (ctx, text, x, y, align, baseline, labelUnderNode) {
  17487. if (text && Number(this.options.fontSize) * this.networkScale > this.fontDrawThreshold) {
  17488. ctx.font = (this.selected ? "bold " : "") + this.options.fontSize + "px " + this.options.fontFace;
  17489. var lines = text.split('\n');
  17490. var lineCount = lines.length;
  17491. var fontSize = (Number(this.options.fontSize) + 4); // TODO: why is this +4 ?
  17492. var yLine = y + (1 - lineCount) / 2 * fontSize;
  17493. if (labelUnderNode == true) {
  17494. yLine = y + (1 - lineCount) / (2 * fontSize);
  17495. }
  17496. // font fill from edges now for nodes!
  17497. var width = ctx.measureText(lines[0]).width;
  17498. for (var i = 1; i < lineCount; i++) {
  17499. var lineWidth = ctx.measureText(lines[i]).width;
  17500. width = lineWidth > width ? lineWidth : width;
  17501. }
  17502. var height = this.options.fontSize * lineCount;
  17503. var left = x - width / 2;
  17504. var top = y - height / 2;
  17505. if (baseline == "top") {
  17506. top += 0.5 * fontSize;
  17507. }
  17508. this.labelDimensions = {top:top,left:left,width:width,height:height,yLine:yLine};
  17509. // create the fontfill background
  17510. if (this.options.fontFill !== undefined && this.options.fontFill !== null && this.options.fontFill !== "none") {
  17511. ctx.fillStyle = this.options.fontFill;
  17512. ctx.fillRect(left, top, width, height);
  17513. }
  17514. // draw text
  17515. ctx.fillStyle = this.options.fontColor || "black";
  17516. ctx.textAlign = align || "center";
  17517. ctx.textBaseline = baseline || "middle";
  17518. for (var i = 0; i < lineCount; i++) {
  17519. ctx.fillText(lines[i], x, yLine);
  17520. yLine += fontSize;
  17521. }
  17522. }
  17523. };
  17524. Node.prototype.getTextSize = function(ctx) {
  17525. if (this.label !== undefined) {
  17526. ctx.font = (this.selected ? "bold " : "") + this.options.fontSize + "px " + this.options.fontFace;
  17527. var lines = this.label.split('\n'),
  17528. height = (Number(this.options.fontSize) + 4) * lines.length,
  17529. width = 0;
  17530. for (var i = 0, iMax = lines.length; i < iMax; i++) {
  17531. width = Math.max(width, ctx.measureText(lines[i]).width);
  17532. }
  17533. return {"width": width, "height": height};
  17534. }
  17535. else {
  17536. return {"width": 0, "height": 0};
  17537. }
  17538. };
  17539. /**
  17540. * this is used to determine if a node is visible at all. this is used to determine when it needs to be drawn.
  17541. * there is a safety margin of 0.3 * width;
  17542. *
  17543. * @returns {boolean}
  17544. */
  17545. Node.prototype.inArea = function() {
  17546. if (this.width !== undefined) {
  17547. return (this.x + this.width *this.networkScaleInv >= this.canvasTopLeft.x &&
  17548. this.x - this.width *this.networkScaleInv < this.canvasBottomRight.x &&
  17549. this.y + this.height*this.networkScaleInv >= this.canvasTopLeft.y &&
  17550. this.y - this.height*this.networkScaleInv < this.canvasBottomRight.y);
  17551. }
  17552. else {
  17553. return true;
  17554. }
  17555. };
  17556. /**
  17557. * checks if the core of the node is in the display area, this is used for opening clusters around zoom
  17558. * @returns {boolean}
  17559. */
  17560. Node.prototype.inView = function() {
  17561. return (this.x >= this.canvasTopLeft.x &&
  17562. this.x < this.canvasBottomRight.x &&
  17563. this.y >= this.canvasTopLeft.y &&
  17564. this.y < this.canvasBottomRight.y);
  17565. };
  17566. /**
  17567. * This allows the zoom level of the network to influence the rendering
  17568. * We store the inverted scale and the coordinates of the top left, and bottom right points of the canvas
  17569. *
  17570. * @param scale
  17571. * @param canvasTopLeft
  17572. * @param canvasBottomRight
  17573. */
  17574. Node.prototype.setScaleAndPos = function(scale,canvasTopLeft,canvasBottomRight) {
  17575. this.networkScaleInv = 1.0/scale;
  17576. this.networkScale = scale;
  17577. this.canvasTopLeft = canvasTopLeft;
  17578. this.canvasBottomRight = canvasBottomRight;
  17579. };
  17580. /**
  17581. * This allows the zoom level of the network to influence the rendering
  17582. *
  17583. * @param scale
  17584. */
  17585. Node.prototype.setScale = function(scale) {
  17586. this.networkScaleInv = 1.0/scale;
  17587. this.networkScale = scale;
  17588. };
  17589. /**
  17590. * set the velocity at 0. Is called when this node is contained in another during clustering
  17591. */
  17592. Node.prototype.clearVelocity = function() {
  17593. this.vx = 0;
  17594. this.vy = 0;
  17595. };
  17596. /**
  17597. * Basic preservation of (kinectic) energy
  17598. *
  17599. * @param massBeforeClustering
  17600. */
  17601. Node.prototype.updateVelocity = function(massBeforeClustering) {
  17602. var energyBefore = this.vx * this.vx * massBeforeClustering;
  17603. //this.vx = (this.vx < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  17604. this.vx = Math.sqrt(energyBefore/this.options.mass);
  17605. energyBefore = this.vy * this.vy * massBeforeClustering;
  17606. //this.vy = (this.vy < 0) ? -Math.sqrt(energyBefore/this.options.mass) : Math.sqrt(energyBefore/this.options.mass);
  17607. this.vy = Math.sqrt(energyBefore/this.options.mass);
  17608. };
  17609. module.exports = Node;
  17610. /***/ },
  17611. /* 41 */
  17612. /***/ function(module, exports, __webpack_require__) {
  17613. /**
  17614. * Popup is a class to create a popup window with some text
  17615. * @param {Element} container The container object.
  17616. * @param {Number} [x]
  17617. * @param {Number} [y]
  17618. * @param {String} [text]
  17619. * @param {Object} [style] An object containing borderColor,
  17620. * backgroundColor, etc.
  17621. */
  17622. function Popup(container, x, y, text, style) {
  17623. if (container) {
  17624. this.container = container;
  17625. }
  17626. else {
  17627. this.container = document.body;
  17628. }
  17629. // x, y and text are optional, see if a style object was passed in their place
  17630. if (style === undefined) {
  17631. if (typeof x === "object") {
  17632. style = x;
  17633. x = undefined;
  17634. } else if (typeof text === "object") {
  17635. style = text;
  17636. text = undefined;
  17637. } else {
  17638. // for backwards compatibility, in case clients other than Network are creating Popup directly
  17639. style = {
  17640. fontColor: 'black',
  17641. fontSize: 14, // px
  17642. fontFace: 'verdana',
  17643. color: {
  17644. border: '#666',
  17645. background: '#FFFFC6'
  17646. }
  17647. }
  17648. }
  17649. }
  17650. this.x = 0;
  17651. this.y = 0;
  17652. this.padding = 5;
  17653. if (x !== undefined && y !== undefined ) {
  17654. this.setPosition(x, y);
  17655. }
  17656. if (text !== undefined) {
  17657. this.setText(text);
  17658. }
  17659. // create the frame
  17660. this.frame = document.createElement("div");
  17661. var styleAttr = this.frame.style;
  17662. styleAttr.position = "absolute";
  17663. styleAttr.visibility = "hidden";
  17664. styleAttr.border = "1px solid " + style.color.border;
  17665. styleAttr.color = style.fontColor;
  17666. styleAttr.fontSize = style.fontSize + "px";
  17667. styleAttr.fontFamily = style.fontFace;
  17668. styleAttr.padding = this.padding + "px";
  17669. styleAttr.backgroundColor = style.color.background;
  17670. styleAttr.borderRadius = "3px";
  17671. styleAttr.MozBorderRadius = "3px";
  17672. styleAttr.WebkitBorderRadius = "3px";
  17673. styleAttr.boxShadow = "3px 3px 10px rgba(128, 128, 128, 0.5)";
  17674. styleAttr.whiteSpace = "nowrap";
  17675. this.container.appendChild(this.frame);
  17676. }
  17677. /**
  17678. * @param {number} x Horizontal position of the popup window
  17679. * @param {number} y Vertical position of the popup window
  17680. */
  17681. Popup.prototype.setPosition = function(x, y) {
  17682. this.x = parseInt(x);
  17683. this.y = parseInt(y);
  17684. };
  17685. /**
  17686. * Set the content for the popup window. This can be HTML code or text.
  17687. * @param {string | Element} content
  17688. */
  17689. Popup.prototype.setText = function(content) {
  17690. if (content instanceof Element) {
  17691. this.frame.innerHTML = '';
  17692. this.frame.appendChild(content);
  17693. }
  17694. else {
  17695. this.frame.innerHTML = content; // string containing text or HTML
  17696. }
  17697. };
  17698. /**
  17699. * Show the popup window
  17700. * @param {boolean} show Optional. Show or hide the window
  17701. */
  17702. Popup.prototype.show = function (show) {
  17703. if (show === undefined) {
  17704. show = true;
  17705. }
  17706. if (show) {
  17707. var height = this.frame.clientHeight;
  17708. var width = this.frame.clientWidth;
  17709. var maxHeight = this.frame.parentNode.clientHeight;
  17710. var maxWidth = this.frame.parentNode.clientWidth;
  17711. var top = (this.y - height);
  17712. if (top + height + this.padding > maxHeight) {
  17713. top = maxHeight - height - this.padding;
  17714. }
  17715. if (top < this.padding) {
  17716. top = this.padding;
  17717. }
  17718. var left = this.x;
  17719. if (left + width + this.padding > maxWidth) {
  17720. left = maxWidth - width - this.padding;
  17721. }
  17722. if (left < this.padding) {
  17723. left = this.padding;
  17724. }
  17725. this.frame.style.left = left + "px";
  17726. this.frame.style.top = top + "px";
  17727. this.frame.style.visibility = "visible";
  17728. }
  17729. else {
  17730. this.hide();
  17731. }
  17732. };
  17733. /**
  17734. * Hide the popup window
  17735. */
  17736. Popup.prototype.hide = function () {
  17737. this.frame.style.visibility = "hidden";
  17738. };
  17739. module.exports = Popup;
  17740. /***/ },
  17741. /* 42 */
  17742. /***/ function(module, exports, __webpack_require__) {
  17743. /**
  17744. * Parse a text source containing data in DOT language into a JSON object.
  17745. * The object contains two lists: one with nodes and one with edges.
  17746. *
  17747. * DOT language reference: http://www.graphviz.org/doc/info/lang.html
  17748. *
  17749. * @param {String} data Text containing a graph in DOT-notation
  17750. * @return {Object} graph An object containing two parameters:
  17751. * {Object[]} nodes
  17752. * {Object[]} edges
  17753. */
  17754. function parseDOT (data) {
  17755. dot = data;
  17756. return parseGraph();
  17757. }
  17758. // token types enumeration
  17759. var TOKENTYPE = {
  17760. NULL : 0,
  17761. DELIMITER : 1,
  17762. IDENTIFIER: 2,
  17763. UNKNOWN : 3
  17764. };
  17765. // map with all delimiters
  17766. var DELIMITERS = {
  17767. '{': true,
  17768. '}': true,
  17769. '[': true,
  17770. ']': true,
  17771. ';': true,
  17772. '=': true,
  17773. ',': true,
  17774. '->': true,
  17775. '--': true
  17776. };
  17777. var dot = ''; // current dot file
  17778. var index = 0; // current index in dot file
  17779. var c = ''; // current token character in expr
  17780. var token = ''; // current token
  17781. var tokenType = TOKENTYPE.NULL; // type of the token
  17782. /**
  17783. * Get the first character from the dot file.
  17784. * The character is stored into the char c. If the end of the dot file is
  17785. * reached, the function puts an empty string in c.
  17786. */
  17787. function first() {
  17788. index = 0;
  17789. c = dot.charAt(0);
  17790. }
  17791. /**
  17792. * Get the next character from the dot file.
  17793. * The character is stored into the char c. If the end of the dot file is
  17794. * reached, the function puts an empty string in c.
  17795. */
  17796. function next() {
  17797. index++;
  17798. c = dot.charAt(index);
  17799. }
  17800. /**
  17801. * Preview the next character from the dot file.
  17802. * @return {String} cNext
  17803. */
  17804. function nextPreview() {
  17805. return dot.charAt(index + 1);
  17806. }
  17807. /**
  17808. * Test whether given character is alphabetic or numeric
  17809. * @param {String} c
  17810. * @return {Boolean} isAlphaNumeric
  17811. */
  17812. var regexAlphaNumeric = /[a-zA-Z_0-9.:#]/;
  17813. function isAlphaNumeric(c) {
  17814. return regexAlphaNumeric.test(c);
  17815. }
  17816. /**
  17817. * Merge all properties of object b into object b
  17818. * @param {Object} a
  17819. * @param {Object} b
  17820. * @return {Object} a
  17821. */
  17822. function merge (a, b) {
  17823. if (!a) {
  17824. a = {};
  17825. }
  17826. if (b) {
  17827. for (var name in b) {
  17828. if (b.hasOwnProperty(name)) {
  17829. a[name] = b[name];
  17830. }
  17831. }
  17832. }
  17833. return a;
  17834. }
  17835. /**
  17836. * Set a value in an object, where the provided parameter name can be a
  17837. * path with nested parameters. For example:
  17838. *
  17839. * var obj = {a: 2};
  17840. * setValue(obj, 'b.c', 3); // obj = {a: 2, b: {c: 3}}
  17841. *
  17842. * @param {Object} obj
  17843. * @param {String} path A parameter name or dot-separated parameter path,
  17844. * like "color.highlight.border".
  17845. * @param {*} value
  17846. */
  17847. function setValue(obj, path, value) {
  17848. var keys = path.split('.');
  17849. var o = obj;
  17850. while (keys.length) {
  17851. var key = keys.shift();
  17852. if (keys.length) {
  17853. // this isn't the end point
  17854. if (!o[key]) {
  17855. o[key] = {};
  17856. }
  17857. o = o[key];
  17858. }
  17859. else {
  17860. // this is the end point
  17861. o[key] = value;
  17862. }
  17863. }
  17864. }
  17865. /**
  17866. * Add a node to a graph object. If there is already a node with
  17867. * the same id, their attributes will be merged.
  17868. * @param {Object} graph
  17869. * @param {Object} node
  17870. */
  17871. function addNode(graph, node) {
  17872. var i, len;
  17873. var current = null;
  17874. // find root graph (in case of subgraph)
  17875. var graphs = [graph]; // list with all graphs from current graph to root graph
  17876. var root = graph;
  17877. while (root.parent) {
  17878. graphs.push(root.parent);
  17879. root = root.parent;
  17880. }
  17881. // find existing node (at root level) by its id
  17882. if (root.nodes) {
  17883. for (i = 0, len = root.nodes.length; i < len; i++) {
  17884. if (node.id === root.nodes[i].id) {
  17885. current = root.nodes[i];
  17886. break;
  17887. }
  17888. }
  17889. }
  17890. if (!current) {
  17891. // this is a new node
  17892. current = {
  17893. id: node.id
  17894. };
  17895. if (graph.node) {
  17896. // clone default attributes
  17897. current.attr = merge(current.attr, graph.node);
  17898. }
  17899. }
  17900. // add node to this (sub)graph and all its parent graphs
  17901. for (i = graphs.length - 1; i >= 0; i--) {
  17902. var g = graphs[i];
  17903. if (!g.nodes) {
  17904. g.nodes = [];
  17905. }
  17906. if (g.nodes.indexOf(current) == -1) {
  17907. g.nodes.push(current);
  17908. }
  17909. }
  17910. // merge attributes
  17911. if (node.attr) {
  17912. current.attr = merge(current.attr, node.attr);
  17913. }
  17914. }
  17915. /**
  17916. * Add an edge to a graph object
  17917. * @param {Object} graph
  17918. * @param {Object} edge
  17919. */
  17920. function addEdge(graph, edge) {
  17921. if (!graph.edges) {
  17922. graph.edges = [];
  17923. }
  17924. graph.edges.push(edge);
  17925. if (graph.edge) {
  17926. var attr = merge({}, graph.edge); // clone default attributes
  17927. edge.attr = merge(attr, edge.attr); // merge attributes
  17928. }
  17929. }
  17930. /**
  17931. * Create an edge to a graph object
  17932. * @param {Object} graph
  17933. * @param {String | Number | Object} from
  17934. * @param {String | Number | Object} to
  17935. * @param {String} type
  17936. * @param {Object | null} attr
  17937. * @return {Object} edge
  17938. */
  17939. function createEdge(graph, from, to, type, attr) {
  17940. var edge = {
  17941. from: from,
  17942. to: to,
  17943. type: type
  17944. };
  17945. if (graph.edge) {
  17946. edge.attr = merge({}, graph.edge); // clone default attributes
  17947. }
  17948. edge.attr = merge(edge.attr || {}, attr); // merge attributes
  17949. return edge;
  17950. }
  17951. /**
  17952. * Get next token in the current dot file.
  17953. * The token and token type are available as token and tokenType
  17954. */
  17955. function getToken() {
  17956. tokenType = TOKENTYPE.NULL;
  17957. token = '';
  17958. // skip over whitespaces
  17959. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  17960. next();
  17961. }
  17962. do {
  17963. var isComment = false;
  17964. // skip comment
  17965. if (c == '#') {
  17966. // find the previous non-space character
  17967. var i = index - 1;
  17968. while (dot.charAt(i) == ' ' || dot.charAt(i) == '\t') {
  17969. i--;
  17970. }
  17971. if (dot.charAt(i) == '\n' || dot.charAt(i) == '') {
  17972. // the # is at the start of a line, this is indeed a line comment
  17973. while (c != '' && c != '\n') {
  17974. next();
  17975. }
  17976. isComment = true;
  17977. }
  17978. }
  17979. if (c == '/' && nextPreview() == '/') {
  17980. // skip line comment
  17981. while (c != '' && c != '\n') {
  17982. next();
  17983. }
  17984. isComment = true;
  17985. }
  17986. if (c == '/' && nextPreview() == '*') {
  17987. // skip block comment
  17988. while (c != '') {
  17989. if (c == '*' && nextPreview() == '/') {
  17990. // end of block comment found. skip these last two characters
  17991. next();
  17992. next();
  17993. break;
  17994. }
  17995. else {
  17996. next();
  17997. }
  17998. }
  17999. isComment = true;
  18000. }
  18001. // skip over whitespaces
  18002. while (c == ' ' || c == '\t' || c == '\n' || c == '\r') { // space, tab, enter
  18003. next();
  18004. }
  18005. }
  18006. while (isComment);
  18007. // check for end of dot file
  18008. if (c == '') {
  18009. // token is still empty
  18010. tokenType = TOKENTYPE.DELIMITER;
  18011. return;
  18012. }
  18013. // check for delimiters consisting of 2 characters
  18014. var c2 = c + nextPreview();
  18015. if (DELIMITERS[c2]) {
  18016. tokenType = TOKENTYPE.DELIMITER;
  18017. token = c2;
  18018. next();
  18019. next();
  18020. return;
  18021. }
  18022. // check for delimiters consisting of 1 character
  18023. if (DELIMITERS[c]) {
  18024. tokenType = TOKENTYPE.DELIMITER;
  18025. token = c;
  18026. next();
  18027. return;
  18028. }
  18029. // check for an identifier (number or string)
  18030. // TODO: more precise parsing of numbers/strings (and the port separator ':')
  18031. if (isAlphaNumeric(c) || c == '-') {
  18032. token += c;
  18033. next();
  18034. while (isAlphaNumeric(c)) {
  18035. token += c;
  18036. next();
  18037. }
  18038. if (token == 'false') {
  18039. token = false; // convert to boolean
  18040. }
  18041. else if (token == 'true') {
  18042. token = true; // convert to boolean
  18043. }
  18044. else if (!isNaN(Number(token))) {
  18045. token = Number(token); // convert to number
  18046. }
  18047. tokenType = TOKENTYPE.IDENTIFIER;
  18048. return;
  18049. }
  18050. // check for a string enclosed by double quotes
  18051. if (c == '"') {
  18052. next();
  18053. while (c != '' && (c != '"' || (c == '"' && nextPreview() == '"'))) {
  18054. token += c;
  18055. if (c == '"') { // skip the escape character
  18056. next();
  18057. }
  18058. next();
  18059. }
  18060. if (c != '"') {
  18061. throw newSyntaxError('End of string " expected');
  18062. }
  18063. next();
  18064. tokenType = TOKENTYPE.IDENTIFIER;
  18065. return;
  18066. }
  18067. // something unknown is found, wrong characters, a syntax error
  18068. tokenType = TOKENTYPE.UNKNOWN;
  18069. while (c != '') {
  18070. token += c;
  18071. next();
  18072. }
  18073. throw new SyntaxError('Syntax error in part "' + chop(token, 30) + '"');
  18074. }
  18075. /**
  18076. * Parse a graph.
  18077. * @returns {Object} graph
  18078. */
  18079. function parseGraph() {
  18080. var graph = {};
  18081. first();
  18082. getToken();
  18083. // optional strict keyword
  18084. if (token == 'strict') {
  18085. graph.strict = true;
  18086. getToken();
  18087. }
  18088. // graph or digraph keyword
  18089. if (token == 'graph' || token == 'digraph') {
  18090. graph.type = token;
  18091. getToken();
  18092. }
  18093. // optional graph id
  18094. if (tokenType == TOKENTYPE.IDENTIFIER) {
  18095. graph.id = token;
  18096. getToken();
  18097. }
  18098. // open angle bracket
  18099. if (token != '{') {
  18100. throw newSyntaxError('Angle bracket { expected');
  18101. }
  18102. getToken();
  18103. // statements
  18104. parseStatements(graph);
  18105. // close angle bracket
  18106. if (token != '}') {
  18107. throw newSyntaxError('Angle bracket } expected');
  18108. }
  18109. getToken();
  18110. // end of file
  18111. if (token !== '') {
  18112. throw newSyntaxError('End of file expected');
  18113. }
  18114. getToken();
  18115. // remove temporary default properties
  18116. delete graph.node;
  18117. delete graph.edge;
  18118. delete graph.graph;
  18119. return graph;
  18120. }
  18121. /**
  18122. * Parse a list with statements.
  18123. * @param {Object} graph
  18124. */
  18125. function parseStatements (graph) {
  18126. while (token !== '' && token != '}') {
  18127. parseStatement(graph);
  18128. if (token == ';') {
  18129. getToken();
  18130. }
  18131. }
  18132. }
  18133. /**
  18134. * Parse a single statement. Can be a an attribute statement, node
  18135. * statement, a series of node statements and edge statements, or a
  18136. * parameter.
  18137. * @param {Object} graph
  18138. */
  18139. function parseStatement(graph) {
  18140. // parse subgraph
  18141. var subgraph = parseSubgraph(graph);
  18142. if (subgraph) {
  18143. // edge statements
  18144. parseEdge(graph, subgraph);
  18145. return;
  18146. }
  18147. // parse an attribute statement
  18148. var attr = parseAttributeStatement(graph);
  18149. if (attr) {
  18150. return;
  18151. }
  18152. // parse node
  18153. if (tokenType != TOKENTYPE.IDENTIFIER) {
  18154. throw newSyntaxError('Identifier expected');
  18155. }
  18156. var id = token; // id can be a string or a number
  18157. getToken();
  18158. if (token == '=') {
  18159. // id statement
  18160. getToken();
  18161. if (tokenType != TOKENTYPE.IDENTIFIER) {
  18162. throw newSyntaxError('Identifier expected');
  18163. }
  18164. graph[id] = token;
  18165. getToken();
  18166. // TODO: implement comma separated list with "a_list: ID=ID [','] [a_list] "
  18167. }
  18168. else {
  18169. parseNodeStatement(graph, id);
  18170. }
  18171. }
  18172. /**
  18173. * Parse a subgraph
  18174. * @param {Object} graph parent graph object
  18175. * @return {Object | null} subgraph
  18176. */
  18177. function parseSubgraph (graph) {
  18178. var subgraph = null;
  18179. // optional subgraph keyword
  18180. if (token == 'subgraph') {
  18181. subgraph = {};
  18182. subgraph.type = 'subgraph';
  18183. getToken();
  18184. // optional graph id
  18185. if (tokenType == TOKENTYPE.IDENTIFIER) {
  18186. subgraph.id = token;
  18187. getToken();
  18188. }
  18189. }
  18190. // open angle bracket
  18191. if (token == '{') {
  18192. getToken();
  18193. if (!subgraph) {
  18194. subgraph = {};
  18195. }
  18196. subgraph.parent = graph;
  18197. subgraph.node = graph.node;
  18198. subgraph.edge = graph.edge;
  18199. subgraph.graph = graph.graph;
  18200. // statements
  18201. parseStatements(subgraph);
  18202. // close angle bracket
  18203. if (token != '}') {
  18204. throw newSyntaxError('Angle bracket } expected');
  18205. }
  18206. getToken();
  18207. // remove temporary default properties
  18208. delete subgraph.node;
  18209. delete subgraph.edge;
  18210. delete subgraph.graph;
  18211. delete subgraph.parent;
  18212. // register at the parent graph
  18213. if (!graph.subgraphs) {
  18214. graph.subgraphs = [];
  18215. }
  18216. graph.subgraphs.push(subgraph);
  18217. }
  18218. return subgraph;
  18219. }
  18220. /**
  18221. * parse an attribute statement like "node [shape=circle fontSize=16]".
  18222. * Available keywords are 'node', 'edge', 'graph'.
  18223. * The previous list with default attributes will be replaced
  18224. * @param {Object} graph
  18225. * @returns {String | null} keyword Returns the name of the parsed attribute
  18226. * (node, edge, graph), or null if nothing
  18227. * is parsed.
  18228. */
  18229. function parseAttributeStatement (graph) {
  18230. // attribute statements
  18231. if (token == 'node') {
  18232. getToken();
  18233. // node attributes
  18234. graph.node = parseAttributeList();
  18235. return 'node';
  18236. }
  18237. else if (token == 'edge') {
  18238. getToken();
  18239. // edge attributes
  18240. graph.edge = parseAttributeList();
  18241. return 'edge';
  18242. }
  18243. else if (token == 'graph') {
  18244. getToken();
  18245. // graph attributes
  18246. graph.graph = parseAttributeList();
  18247. return 'graph';
  18248. }
  18249. return null;
  18250. }
  18251. /**
  18252. * parse a node statement
  18253. * @param {Object} graph
  18254. * @param {String | Number} id
  18255. */
  18256. function parseNodeStatement(graph, id) {
  18257. // node statement
  18258. var node = {
  18259. id: id
  18260. };
  18261. var attr = parseAttributeList();
  18262. if (attr) {
  18263. node.attr = attr;
  18264. }
  18265. addNode(graph, node);
  18266. // edge statements
  18267. parseEdge(graph, id);
  18268. }
  18269. /**
  18270. * Parse an edge or a series of edges
  18271. * @param {Object} graph
  18272. * @param {String | Number} from Id of the from node
  18273. */
  18274. function parseEdge(graph, from) {
  18275. while (token == '->' || token == '--') {
  18276. var to;
  18277. var type = token;
  18278. getToken();
  18279. var subgraph = parseSubgraph(graph);
  18280. if (subgraph) {
  18281. to = subgraph;
  18282. }
  18283. else {
  18284. if (tokenType != TOKENTYPE.IDENTIFIER) {
  18285. throw newSyntaxError('Identifier or subgraph expected');
  18286. }
  18287. to = token;
  18288. addNode(graph, {
  18289. id: to
  18290. });
  18291. getToken();
  18292. }
  18293. // parse edge attributes
  18294. var attr = parseAttributeList();
  18295. // create edge
  18296. var edge = createEdge(graph, from, to, type, attr);
  18297. addEdge(graph, edge);
  18298. from = to;
  18299. }
  18300. }
  18301. /**
  18302. * Parse a set with attributes,
  18303. * for example [label="1.000", shape=solid]
  18304. * @return {Object | null} attr
  18305. */
  18306. function parseAttributeList() {
  18307. var attr = null;
  18308. while (token == '[') {
  18309. getToken();
  18310. attr = {};
  18311. while (token !== '' && token != ']') {
  18312. if (tokenType != TOKENTYPE.IDENTIFIER) {
  18313. throw newSyntaxError('Attribute name expected');
  18314. }
  18315. var name = token;
  18316. getToken();
  18317. if (token != '=') {
  18318. throw newSyntaxError('Equal sign = expected');
  18319. }
  18320. getToken();
  18321. if (tokenType != TOKENTYPE.IDENTIFIER) {
  18322. throw newSyntaxError('Attribute value expected');
  18323. }
  18324. var value = token;
  18325. setValue(attr, name, value); // name can be a path
  18326. getToken();
  18327. if (token ==',') {
  18328. getToken();
  18329. }
  18330. }
  18331. if (token != ']') {
  18332. throw newSyntaxError('Bracket ] expected');
  18333. }
  18334. getToken();
  18335. }
  18336. return attr;
  18337. }
  18338. /**
  18339. * Create a syntax error with extra information on current token and index.
  18340. * @param {String} message
  18341. * @returns {SyntaxError} err
  18342. */
  18343. function newSyntaxError(message) {
  18344. return new SyntaxError(message + ', got "' + chop(token, 30) + '" (char ' + index + ')');
  18345. }
  18346. /**
  18347. * Chop off text after a maximum length
  18348. * @param {String} text
  18349. * @param {Number} maxLength
  18350. * @returns {String}
  18351. */
  18352. function chop (text, maxLength) {
  18353. return (text.length <= maxLength) ? text : (text.substr(0, 27) + '...');
  18354. }
  18355. /**
  18356. * Execute a function fn for each pair of elements in two arrays
  18357. * @param {Array | *} array1
  18358. * @param {Array | *} array2
  18359. * @param {function} fn
  18360. */
  18361. function forEach2(array1, array2, fn) {
  18362. if (Array.isArray(array1)) {
  18363. array1.forEach(function (elem1) {
  18364. if (Array.isArray(array2)) {
  18365. array2.forEach(function (elem2) {
  18366. fn(elem1, elem2);
  18367. });
  18368. }
  18369. else {
  18370. fn(elem1, array2);
  18371. }
  18372. });
  18373. }
  18374. else {
  18375. if (Array.isArray(array2)) {
  18376. array2.forEach(function (elem2) {
  18377. fn(array1, elem2);
  18378. });
  18379. }
  18380. else {
  18381. fn(array1, array2);
  18382. }
  18383. }
  18384. }
  18385. /**
  18386. * Convert a string containing a graph in DOT language into a map containing
  18387. * with nodes and edges in the format of graph.
  18388. * @param {String} data Text containing a graph in DOT-notation
  18389. * @return {Object} graphData
  18390. */
  18391. function DOTToGraph (data) {
  18392. // parse the DOT file
  18393. var dotData = parseDOT(data);
  18394. var graphData = {
  18395. nodes: [],
  18396. edges: [],
  18397. options: {}
  18398. };
  18399. // copy the nodes
  18400. if (dotData.nodes) {
  18401. dotData.nodes.forEach(function (dotNode) {
  18402. var graphNode = {
  18403. id: dotNode.id,
  18404. label: String(dotNode.label || dotNode.id)
  18405. };
  18406. merge(graphNode, dotNode.attr);
  18407. if (graphNode.image) {
  18408. graphNode.shape = 'image';
  18409. }
  18410. graphData.nodes.push(graphNode);
  18411. });
  18412. }
  18413. // copy the edges
  18414. if (dotData.edges) {
  18415. /**
  18416. * Convert an edge in DOT format to an edge with VisGraph format
  18417. * @param {Object} dotEdge
  18418. * @returns {Object} graphEdge
  18419. */
  18420. function convertEdge(dotEdge) {
  18421. var graphEdge = {
  18422. from: dotEdge.from,
  18423. to: dotEdge.to
  18424. };
  18425. merge(graphEdge, dotEdge.attr);
  18426. graphEdge.style = (dotEdge.type == '->') ? 'arrow' : 'line';
  18427. return graphEdge;
  18428. }
  18429. dotData.edges.forEach(function (dotEdge) {
  18430. var from, to;
  18431. if (dotEdge.from instanceof Object) {
  18432. from = dotEdge.from.nodes;
  18433. }
  18434. else {
  18435. from = {
  18436. id: dotEdge.from
  18437. }
  18438. }
  18439. if (dotEdge.to instanceof Object) {
  18440. to = dotEdge.to.nodes;
  18441. }
  18442. else {
  18443. to = {
  18444. id: dotEdge.to
  18445. }
  18446. }
  18447. if (dotEdge.from instanceof Object && dotEdge.from.edges) {
  18448. dotEdge.from.edges.forEach(function (subEdge) {
  18449. var graphEdge = convertEdge(subEdge);
  18450. graphData.edges.push(graphEdge);
  18451. });
  18452. }
  18453. forEach2(from, to, function (from, to) {
  18454. var subEdge = createEdge(graphData, from.id, to.id, dotEdge.type, dotEdge.attr);
  18455. var graphEdge = convertEdge(subEdge);
  18456. graphData.edges.push(graphEdge);
  18457. });
  18458. if (dotEdge.to instanceof Object && dotEdge.to.edges) {
  18459. dotEdge.to.edges.forEach(function (subEdge) {
  18460. var graphEdge = convertEdge(subEdge);
  18461. graphData.edges.push(graphEdge);
  18462. });
  18463. }
  18464. });
  18465. }
  18466. // copy the options
  18467. if (dotData.attr) {
  18468. graphData.options = dotData.attr;
  18469. }
  18470. return graphData;
  18471. }
  18472. // exports
  18473. exports.parseDOT = parseDOT;
  18474. exports.DOTToGraph = DOTToGraph;
  18475. /***/ },
  18476. /* 43 */
  18477. /***/ function(module, exports, __webpack_require__) {
  18478. function parseGephi(gephiJSON, options) {
  18479. var edges = [];
  18480. var nodes = [];
  18481. this.options = {
  18482. edges: {
  18483. inheritColor: true
  18484. },
  18485. nodes: {
  18486. allowedToMove: false,
  18487. parseColor: false
  18488. }
  18489. };
  18490. if (options !== undefined) {
  18491. this.options.nodes['allowedToMove'] = options.allowedToMove | false;
  18492. this.options.nodes['parseColor'] = options.parseColor | false;
  18493. this.options.edges['inheritColor'] = options.inheritColor | true;
  18494. }
  18495. var gEdges = gephiJSON.edges;
  18496. var gNodes = gephiJSON.nodes;
  18497. for (var i = 0; i < gEdges.length; i++) {
  18498. var edge = {};
  18499. var gEdge = gEdges[i];
  18500. edge['id'] = gEdge.id;
  18501. edge['from'] = gEdge.source;
  18502. edge['to'] = gEdge.target;
  18503. edge['attributes'] = gEdge.attributes;
  18504. // edge['value'] = gEdge.attributes !== undefined ? gEdge.attributes.Weight : undefined;
  18505. // edge['width'] = edge['value'] !== undefined ? undefined : edgegEdge.size;
  18506. edge['color'] = gEdge.color;
  18507. edge['inheritColor'] = edge['color'] !== undefined ? false : this.options.inheritColor;
  18508. edges.push(edge);
  18509. }
  18510. for (var i = 0; i < gNodes.length; i++) {
  18511. var node = {};
  18512. var gNode = gNodes[i];
  18513. node['id'] = gNode.id;
  18514. node['attributes'] = gNode.attributes;
  18515. node['x'] = gNode.x;
  18516. node['y'] = gNode.y;
  18517. node['label'] = gNode.label;
  18518. if (this.options.nodes.parseColor == true) {
  18519. node['color'] = gNode.color;
  18520. }
  18521. else {
  18522. node['color'] = gNode.color !== undefined ? {background:gNode.color, border:gNode.color} : undefined;
  18523. }
  18524. node['radius'] = gNode.size;
  18525. node['allowedToMoveX'] = this.options.nodes.allowedToMove;
  18526. node['allowedToMoveY'] = this.options.nodes.allowedToMove;
  18527. nodes.push(node);
  18528. }
  18529. return {nodes:nodes, edges:edges};
  18530. }
  18531. exports.parseGephi = parseGephi;
  18532. /***/ },
  18533. /* 44 */
  18534. /***/ function(module, exports, __webpack_require__) {
  18535. // first check if moment.js is already loaded in the browser window, if so,
  18536. // use this instance. Else, load via commonjs.
  18537. module.exports = (typeof window !== 'undefined') && window['moment'] || __webpack_require__(54);
  18538. /***/ },
  18539. /* 45 */
  18540. /***/ function(module, exports, __webpack_require__) {
  18541. // Only load hammer.js when in a browser environment
  18542. // (loading hammer.js in a node.js environment gives errors)
  18543. if (typeof window !== 'undefined') {
  18544. module.exports = window['Hammer'] || __webpack_require__(55);
  18545. }
  18546. else {
  18547. module.exports = function () {
  18548. throw Error('hammer.js is only available in a browser, not in node.js.');
  18549. }
  18550. }
  18551. /***/ },
  18552. /* 46 */
  18553. /***/ function(module, exports, __webpack_require__) {
  18554. var Emitter = __webpack_require__(53);
  18555. var Hammer = __webpack_require__(45);
  18556. var util = __webpack_require__(1);
  18557. var DataSet = __webpack_require__(3);
  18558. var DataView = __webpack_require__(4);
  18559. var Range = __webpack_require__(10);
  18560. var TimeAxis = __webpack_require__(30);
  18561. var CurrentTime = __webpack_require__(21);
  18562. var CustomTime = __webpack_require__(22);
  18563. var ItemSet = __webpack_require__(27);
  18564. var Activator = __webpack_require__(52);
  18565. var DateUtil = __webpack_require__(8);
  18566. /**
  18567. * Create a timeline visualization
  18568. * @param {HTMLElement} container
  18569. * @param {vis.DataSet | Array | google.visualization.DataTable} [items]
  18570. * @param {Object} [options] See Core.setOptions for the available options.
  18571. * @constructor
  18572. */
  18573. function Core () {}
  18574. // turn Core into an event emitter
  18575. Emitter(Core.prototype);
  18576. /**
  18577. * Create the main DOM for the Core: a root panel containing left, right,
  18578. * top, bottom, content, and background panel.
  18579. * @param {Element} container The container element where the Core will
  18580. * be attached.
  18581. * @private
  18582. */
  18583. Core.prototype._create = function (container) {
  18584. this.dom = {};
  18585. this.dom.root = document.createElement('div');
  18586. this.dom.background = document.createElement('div');
  18587. this.dom.backgroundVertical = document.createElement('div');
  18588. this.dom.backgroundHorizontal = document.createElement('div');
  18589. this.dom.centerContainer = document.createElement('div');
  18590. this.dom.leftContainer = document.createElement('div');
  18591. this.dom.rightContainer = document.createElement('div');
  18592. this.dom.center = document.createElement('div');
  18593. this.dom.left = document.createElement('div');
  18594. this.dom.right = document.createElement('div');
  18595. this.dom.top = document.createElement('div');
  18596. this.dom.bottom = document.createElement('div');
  18597. this.dom.shadowTop = document.createElement('div');
  18598. this.dom.shadowBottom = document.createElement('div');
  18599. this.dom.shadowTopLeft = document.createElement('div');
  18600. this.dom.shadowBottomLeft = document.createElement('div');
  18601. this.dom.shadowTopRight = document.createElement('div');
  18602. this.dom.shadowBottomRight = document.createElement('div');
  18603. this.dom.root.className = 'vis timeline root';
  18604. this.dom.background.className = 'vispanel background';
  18605. this.dom.backgroundVertical.className = 'vispanel background vertical';
  18606. this.dom.backgroundHorizontal.className = 'vispanel background horizontal';
  18607. this.dom.centerContainer.className = 'vispanel center';
  18608. this.dom.leftContainer.className = 'vispanel left';
  18609. this.dom.rightContainer.className = 'vispanel right';
  18610. this.dom.top.className = 'vispanel top';
  18611. this.dom.bottom.className = 'vispanel bottom';
  18612. this.dom.left.className = 'content';
  18613. this.dom.center.className = 'content';
  18614. this.dom.right.className = 'content';
  18615. this.dom.shadowTop.className = 'shadow top';
  18616. this.dom.shadowBottom.className = 'shadow bottom';
  18617. this.dom.shadowTopLeft.className = 'shadow top';
  18618. this.dom.shadowBottomLeft.className = 'shadow bottom';
  18619. this.dom.shadowTopRight.className = 'shadow top';
  18620. this.dom.shadowBottomRight.className = 'shadow bottom';
  18621. this.dom.root.appendChild(this.dom.background);
  18622. this.dom.root.appendChild(this.dom.backgroundVertical);
  18623. this.dom.root.appendChild(this.dom.backgroundHorizontal);
  18624. this.dom.root.appendChild(this.dom.centerContainer);
  18625. this.dom.root.appendChild(this.dom.leftContainer);
  18626. this.dom.root.appendChild(this.dom.rightContainer);
  18627. this.dom.root.appendChild(this.dom.top);
  18628. this.dom.root.appendChild(this.dom.bottom);
  18629. this.dom.centerContainer.appendChild(this.dom.center);
  18630. this.dom.leftContainer.appendChild(this.dom.left);
  18631. this.dom.rightContainer.appendChild(this.dom.right);
  18632. this.dom.centerContainer.appendChild(this.dom.shadowTop);
  18633. this.dom.centerContainer.appendChild(this.dom.shadowBottom);
  18634. this.dom.leftContainer.appendChild(this.dom.shadowTopLeft);
  18635. this.dom.leftContainer.appendChild(this.dom.shadowBottomLeft);
  18636. this.dom.rightContainer.appendChild(this.dom.shadowTopRight);
  18637. this.dom.rightContainer.appendChild(this.dom.shadowBottomRight);
  18638. this.on('rangechange', this.redraw.bind(this));
  18639. this.on('touch', this._onTouch.bind(this));
  18640. this.on('pinch', this._onPinch.bind(this));
  18641. this.on('dragstart', this._onDragStart.bind(this));
  18642. this.on('drag', this._onDrag.bind(this));
  18643. var me = this;
  18644. this.on('change', function (properties) {
  18645. if (properties && properties.queue == true) {
  18646. // redraw once on next tick
  18647. if (!me._redrawTimer) {
  18648. me._redrawTimer = setTimeout(function () {
  18649. me._redrawTimer = null;
  18650. me.redraw();
  18651. }, 0)
  18652. }
  18653. }
  18654. else {
  18655. // redraw immediately
  18656. me.redraw();
  18657. }
  18658. });
  18659. // create event listeners for all interesting events, these events will be
  18660. // emitted via emitter
  18661. this.hammer = Hammer(this.dom.root, {
  18662. preventDefault: true
  18663. });
  18664. this.listeners = {};
  18665. var events = [
  18666. 'touch', 'pinch',
  18667. 'tap', 'doubletap', 'hold',
  18668. 'dragstart', 'drag', 'dragend',
  18669. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  18670. ];
  18671. events.forEach(function (event) {
  18672. var listener = function () {
  18673. var args = [event].concat(Array.prototype.slice.call(arguments, 0));
  18674. if (me.isActive()) {
  18675. me.emit.apply(me, args);
  18676. }
  18677. };
  18678. me.hammer.on(event, listener);
  18679. me.listeners[event] = listener;
  18680. });
  18681. // size properties of each of the panels
  18682. this.props = {
  18683. root: {},
  18684. background: {},
  18685. centerContainer: {},
  18686. leftContainer: {},
  18687. rightContainer: {},
  18688. center: {},
  18689. left: {},
  18690. right: {},
  18691. top: {},
  18692. bottom: {},
  18693. border: {},
  18694. scrollTop: 0,
  18695. scrollTopMin: 0
  18696. };
  18697. this.touch = {}; // store state information needed for touch events
  18698. // attach the root panel to the provided container
  18699. if (!container) throw new Error('No container provided');
  18700. container.appendChild(this.dom.root);
  18701. };
  18702. /**
  18703. * Set options. Options will be passed to all components loaded in the Timeline.
  18704. * @param {Object} [options]
  18705. * {String} orientation
  18706. * Vertical orientation for the Timeline,
  18707. * can be 'bottom' (default) or 'top'.
  18708. * {String | Number} width
  18709. * Width for the timeline, a number in pixels or
  18710. * a css string like '1000px' or '75%'. '100%' by default.
  18711. * {String | Number} height
  18712. * Fixed height for the Timeline, a number in pixels or
  18713. * a css string like '400px' or '75%'. If undefined,
  18714. * The Timeline will automatically size such that
  18715. * its contents fit.
  18716. * {String | Number} minHeight
  18717. * Minimum height for the Timeline, a number in pixels or
  18718. * a css string like '400px' or '75%'.
  18719. * {String | Number} maxHeight
  18720. * Maximum height for the Timeline, a number in pixels or
  18721. * a css string like '400px' or '75%'.
  18722. * {Number | Date | String} start
  18723. * Start date for the visible window
  18724. * {Number | Date | String} end
  18725. * End date for the visible window
  18726. */
  18727. Core.prototype.setOptions = function (options) {
  18728. if (options) {
  18729. // copy the known options
  18730. var fields = ['width', 'height', 'minHeight', 'maxHeight', 'autoResize', 'start', 'end', 'orientation', 'clickToUse', 'dataAttributes', 'hiddenDates'];
  18731. util.selectiveExtend(fields, this.options, options);
  18732. if ('hiddenDates' in this.options) {
  18733. DateUtil.convertHiddenOptions(this.body, this.options.hiddenDates);
  18734. }
  18735. if ('clickToUse' in options) {
  18736. if (options.clickToUse) {
  18737. this.activator = new Activator(this.dom.root);
  18738. }
  18739. else {
  18740. if (this.activator) {
  18741. this.activator.destroy();
  18742. delete this.activator;
  18743. }
  18744. }
  18745. }
  18746. // enable/disable autoResize
  18747. this._initAutoResize();
  18748. }
  18749. // propagate options to all components
  18750. this.components.forEach(function (component) {
  18751. component.setOptions(options);
  18752. });
  18753. // TODO: remove deprecation error one day (deprecated since version 0.8.0)
  18754. if (options && options.order) {
  18755. throw new Error('Option order is deprecated. There is no replacement for this feature.');
  18756. }
  18757. // redraw everything
  18758. this.redraw();
  18759. };
  18760. /**
  18761. * Returns true when the Timeline is active.
  18762. * @returns {boolean}
  18763. */
  18764. Core.prototype.isActive = function () {
  18765. return !this.activator || this.activator.active;
  18766. };
  18767. /**
  18768. * Destroy the Core, clean up all DOM elements and event listeners.
  18769. */
  18770. Core.prototype.destroy = function () {
  18771. // unbind datasets
  18772. this.clear();
  18773. // remove all event listeners
  18774. this.off();
  18775. // stop checking for changed size
  18776. this._stopAutoResize();
  18777. // remove from DOM
  18778. if (this.dom.root.parentNode) {
  18779. this.dom.root.parentNode.removeChild(this.dom.root);
  18780. }
  18781. this.dom = null;
  18782. // remove Activator
  18783. if (this.activator) {
  18784. this.activator.destroy();
  18785. delete this.activator;
  18786. }
  18787. // cleanup hammer touch events
  18788. for (var event in this.listeners) {
  18789. if (this.listeners.hasOwnProperty(event)) {
  18790. delete this.listeners[event];
  18791. }
  18792. }
  18793. this.listeners = null;
  18794. this.hammer = null;
  18795. // give all components the opportunity to cleanup
  18796. this.components.forEach(function (component) {
  18797. component.destroy();
  18798. });
  18799. this.body = null;
  18800. };
  18801. /**
  18802. * Set a custom time bar
  18803. * @param {Date} time
  18804. */
  18805. Core.prototype.setCustomTime = function (time) {
  18806. if (!this.customTime) {
  18807. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  18808. }
  18809. this.customTime.setCustomTime(time);
  18810. };
  18811. /**
  18812. * Retrieve the current custom time.
  18813. * @return {Date} customTime
  18814. */
  18815. Core.prototype.getCustomTime = function() {
  18816. if (!this.customTime) {
  18817. throw new Error('Cannot get custom time: Custom time bar is not enabled');
  18818. }
  18819. return this.customTime.getCustomTime();
  18820. };
  18821. /**
  18822. * Get the id's of the currently visible items.
  18823. * @returns {Array} The ids of the visible items
  18824. */
  18825. Core.prototype.getVisibleItems = function() {
  18826. return this.itemSet && this.itemSet.getVisibleItems() || [];
  18827. };
  18828. /**
  18829. * Clear the Core. By Default, items, groups and options are cleared.
  18830. * Example usage:
  18831. *
  18832. * timeline.clear(); // clear items, groups, and options
  18833. * timeline.clear({options: true}); // clear options only
  18834. *
  18835. * @param {Object} [what] Optionally specify what to clear. By default:
  18836. * {items: true, groups: true, options: true}
  18837. */
  18838. Core.prototype.clear = function(what) {
  18839. // clear items
  18840. if (!what || what.items) {
  18841. this.setItems(null);
  18842. }
  18843. // clear groups
  18844. if (!what || what.groups) {
  18845. this.setGroups(null);
  18846. }
  18847. // clear options of timeline and of each of the components
  18848. if (!what || what.options) {
  18849. this.components.forEach(function (component) {
  18850. component.setOptions(component.defaultOptions);
  18851. });
  18852. this.setOptions(this.defaultOptions); // this will also do a redraw
  18853. }
  18854. };
  18855. /**
  18856. * Set Core window such that it fits all items
  18857. * @param {Object} [options] Available options:
  18858. * `animate: boolean | number`
  18859. * If true (default), the range is animated
  18860. * smoothly to the new window.
  18861. * If a number, the number is taken as duration
  18862. * for the animation. Default duration is 500 ms.
  18863. */
  18864. Core.prototype.fit = function(options) {
  18865. // apply the data range as range
  18866. var dataRange = this.getItemRange();
  18867. // add 5% space on both sides
  18868. var start = dataRange.min;
  18869. var end = dataRange.max;
  18870. if (start != null && end != null) {
  18871. var interval = (end.valueOf() - start.valueOf());
  18872. if (interval <= 0) {
  18873. // prevent an empty interval
  18874. interval = 24 * 60 * 60 * 1000; // 1 day
  18875. }
  18876. start = new Date(start.valueOf() - interval * 0.05);
  18877. end = new Date(end.valueOf() + interval * 0.05);
  18878. }
  18879. // skip range set if there is no start and end date
  18880. if (start === null && end === null) {
  18881. return;
  18882. }
  18883. var animate = (options && options.animate !== undefined) ? options.animate : true;
  18884. this.range.setRange(start, end, animate);
  18885. };
  18886. /**
  18887. * Set the visible window. Both parameters are optional, you can change only
  18888. * start or only end. Syntax:
  18889. *
  18890. * TimeLine.setWindow(start, end)
  18891. * TimeLine.setWindow(range)
  18892. *
  18893. * Where start and end can be a Date, number, or string, and range is an
  18894. * object with properties start and end.
  18895. *
  18896. * @param {Date | Number | String | Object} [start] Start date of visible window
  18897. * @param {Date | Number | String} [end] End date of visible window
  18898. * @param {Object} [options] Available options:
  18899. * `animate: boolean | number`
  18900. * If true (default), the range is animated
  18901. * smoothly to the new window.
  18902. * If a number, the number is taken as duration
  18903. * for the animation. Default duration is 500 ms.
  18904. */
  18905. Core.prototype.setWindow = function(start, end, options) {
  18906. var animate = (options && options.animate !== undefined) ? options.animate : true;
  18907. if (arguments.length == 1) {
  18908. var range = arguments[0];
  18909. this.range.setRange(range.start, range.end, animate);
  18910. }
  18911. else {
  18912. this.range.setRange(start, end, animate);
  18913. }
  18914. };
  18915. /**
  18916. * Move the window such that given time is centered on screen.
  18917. * @param {Date | Number | String} time
  18918. * @param {Object} [options] Available options:
  18919. * `animate: boolean | number`
  18920. * If true (default), the range is animated
  18921. * smoothly to the new window.
  18922. * If a number, the number is taken as duration
  18923. * for the animation. Default duration is 500 ms.
  18924. */
  18925. Core.prototype.moveTo = function(time, options) {
  18926. var interval = this.range.end - this.range.start;
  18927. var t = util.convert(time, 'Date').valueOf();
  18928. var start = t - interval / 2;
  18929. var end = t + interval / 2;
  18930. var animate = (options && options.animate !== undefined) ? options.animate : true;
  18931. this.range.setRange(start, end, animate);
  18932. };
  18933. /**
  18934. * Get the visible window
  18935. * @return {{start: Date, end: Date}} Visible range
  18936. */
  18937. Core.prototype.getWindow = function() {
  18938. var range = this.range.getRange();
  18939. return {
  18940. start: new Date(range.start),
  18941. end: new Date(range.end)
  18942. };
  18943. };
  18944. /**
  18945. * Force a redraw of the Core. Can be useful to manually redraw when
  18946. * option autoResize=false
  18947. */
  18948. Core.prototype.redraw = function() {
  18949. var resized = false;
  18950. var options = this.options;
  18951. var props = this.props;
  18952. var dom = this.dom;
  18953. if (!dom) return; // when destroyed
  18954. DateUtil.updateHiddenDates(this.body, this.options.hiddenDates);
  18955. // update class names
  18956. if (options.orientation == 'top') {
  18957. util.addClassName(dom.root, 'top');
  18958. util.removeClassName(dom.root, 'bottom');
  18959. }
  18960. else {
  18961. util.removeClassName(dom.root, 'top');
  18962. util.addClassName(dom.root, 'bottom');
  18963. }
  18964. // update root width and height options
  18965. dom.root.style.maxHeight = util.option.asSize(options.maxHeight, '');
  18966. dom.root.style.minHeight = util.option.asSize(options.minHeight, '');
  18967. dom.root.style.width = util.option.asSize(options.width, '');
  18968. // calculate border widths
  18969. props.border.left = (dom.centerContainer.offsetWidth - dom.centerContainer.clientWidth) / 2;
  18970. props.border.right = props.border.left;
  18971. props.border.top = (dom.centerContainer.offsetHeight - dom.centerContainer.clientHeight) / 2;
  18972. props.border.bottom = props.border.top;
  18973. var borderRootHeight= dom.root.offsetHeight - dom.root.clientHeight;
  18974. var borderRootWidth = dom.root.offsetWidth - dom.root.clientWidth;
  18975. // workaround for a bug in IE: the clientWidth of an element with
  18976. // a height:0px and overflow:hidden is not calculated and always has value 0
  18977. if (dom.centerContainer.clientHeight === 0) {
  18978. props.border.left = props.border.top;
  18979. props.border.right = props.border.left;
  18980. }
  18981. if (dom.root.clientHeight === 0) {
  18982. borderRootWidth = borderRootHeight;
  18983. }
  18984. // calculate the heights. If any of the side panels is empty, we set the height to
  18985. // minus the border width, such that the border will be invisible
  18986. props.center.height = dom.center.offsetHeight;
  18987. props.left.height = dom.left.offsetHeight;
  18988. props.right.height = dom.right.offsetHeight;
  18989. props.top.height = dom.top.clientHeight || -props.border.top;
  18990. props.bottom.height = dom.bottom.clientHeight || -props.border.bottom;
  18991. // TODO: compensate borders when any of the panels is empty.
  18992. // apply auto height
  18993. // TODO: only calculate autoHeight when needed (else we cause an extra reflow/repaint of the DOM)
  18994. var contentHeight = Math.max(props.left.height, props.center.height, props.right.height);
  18995. var autoHeight = props.top.height + contentHeight + props.bottom.height +
  18996. borderRootHeight + props.border.top + props.border.bottom;
  18997. dom.root.style.height = util.option.asSize(options.height, autoHeight + 'px');
  18998. // calculate heights of the content panels
  18999. props.root.height = dom.root.offsetHeight;
  19000. props.background.height = props.root.height - borderRootHeight;
  19001. var containerHeight = props.root.height - props.top.height - props.bottom.height -
  19002. borderRootHeight;
  19003. props.centerContainer.height = containerHeight;
  19004. props.leftContainer.height = containerHeight;
  19005. props.rightContainer.height = props.leftContainer.height;
  19006. // calculate the widths of the panels
  19007. props.root.width = dom.root.offsetWidth;
  19008. props.background.width = props.root.width - borderRootWidth;
  19009. props.left.width = dom.leftContainer.clientWidth || -props.border.left;
  19010. props.leftContainer.width = props.left.width;
  19011. props.right.width = dom.rightContainer.clientWidth || -props.border.right;
  19012. props.rightContainer.width = props.right.width;
  19013. var centerWidth = props.root.width - props.left.width - props.right.width - borderRootWidth;
  19014. props.center.width = centerWidth;
  19015. props.centerContainer.width = centerWidth;
  19016. props.top.width = centerWidth;
  19017. props.bottom.width = centerWidth;
  19018. // resize the panels
  19019. dom.background.style.height = props.background.height + 'px';
  19020. dom.backgroundVertical.style.height = props.background.height + 'px';
  19021. dom.backgroundHorizontal.style.height = props.centerContainer.height + 'px';
  19022. dom.centerContainer.style.height = props.centerContainer.height + 'px';
  19023. dom.leftContainer.style.height = props.leftContainer.height + 'px';
  19024. dom.rightContainer.style.height = props.rightContainer.height + 'px';
  19025. dom.background.style.width = props.background.width + 'px';
  19026. dom.backgroundVertical.style.width = props.centerContainer.width + 'px';
  19027. dom.backgroundHorizontal.style.width = props.background.width + 'px';
  19028. dom.centerContainer.style.width = props.center.width + 'px';
  19029. dom.top.style.width = props.top.width + 'px';
  19030. dom.bottom.style.width = props.bottom.width + 'px';
  19031. // reposition the panels
  19032. dom.background.style.left = '0';
  19033. dom.background.style.top = '0';
  19034. dom.backgroundVertical.style.left = (props.left.width + props.border.left) + 'px';
  19035. dom.backgroundVertical.style.top = '0';
  19036. dom.backgroundHorizontal.style.left = '0';
  19037. dom.backgroundHorizontal.style.top = props.top.height + 'px';
  19038. dom.centerContainer.style.left = props.left.width + 'px';
  19039. dom.centerContainer.style.top = props.top.height + 'px';
  19040. dom.leftContainer.style.left = '0';
  19041. dom.leftContainer.style.top = props.top.height + 'px';
  19042. dom.rightContainer.style.left = (props.left.width + props.center.width) + 'px';
  19043. dom.rightContainer.style.top = props.top.height + 'px';
  19044. dom.top.style.left = props.left.width + 'px';
  19045. dom.top.style.top = '0';
  19046. dom.bottom.style.left = props.left.width + 'px';
  19047. dom.bottom.style.top = (props.top.height + props.centerContainer.height) + 'px';
  19048. // update the scrollTop, feasible range for the offset can be changed
  19049. // when the height of the Core or of the contents of the center changed
  19050. this._updateScrollTop();
  19051. // reposition the scrollable contents
  19052. var offset = this.props.scrollTop;
  19053. if (options.orientation == 'bottom') {
  19054. offset += Math.max(this.props.centerContainer.height - this.props.center.height -
  19055. this.props.border.top - this.props.border.bottom, 0);
  19056. }
  19057. dom.center.style.left = '0';
  19058. dom.center.style.top = offset + 'px';
  19059. dom.left.style.left = '0';
  19060. dom.left.style.top = offset + 'px';
  19061. dom.right.style.left = '0';
  19062. dom.right.style.top = offset + 'px';
  19063. // show shadows when vertical scrolling is available
  19064. var visibilityTop = this.props.scrollTop == 0 ? 'hidden' : '';
  19065. var visibilityBottom = this.props.scrollTop == this.props.scrollTopMin ? 'hidden' : '';
  19066. dom.shadowTop.style.visibility = visibilityTop;
  19067. dom.shadowBottom.style.visibility = visibilityBottom;
  19068. dom.shadowTopLeft.style.visibility = visibilityTop;
  19069. dom.shadowBottomLeft.style.visibility = visibilityBottom;
  19070. dom.shadowTopRight.style.visibility = visibilityTop;
  19071. dom.shadowBottomRight.style.visibility = visibilityBottom;
  19072. // redraw all components
  19073. this.components.forEach(function (component) {
  19074. resized = component.redraw() || resized;
  19075. });
  19076. if (resized) {
  19077. // keep repainting until all sizes are settled
  19078. this.redraw();
  19079. }
  19080. };
  19081. // TODO: deprecated since version 1.1.0, remove some day
  19082. Core.prototype.repaint = function () {
  19083. throw new Error('Function repaint is deprecated. Use redraw instead.');
  19084. };
  19085. /**
  19086. * Set a current time. This can be used for example to ensure that a client's
  19087. * time is synchronized with a shared server time.
  19088. * Only applicable when option `showCurrentTime` is true.
  19089. * @param {Date | String | Number} time A Date, unix timestamp, or
  19090. * ISO date string.
  19091. */
  19092. Core.prototype.setCurrentTime = function(time) {
  19093. if (!this.currentTime) {
  19094. throw new Error('Option showCurrentTime must be true');
  19095. }
  19096. this.currentTime.setCurrentTime(time);
  19097. };
  19098. /**
  19099. * Get the current time.
  19100. * Only applicable when option `showCurrentTime` is true.
  19101. * @return {Date} Returns the current time.
  19102. */
  19103. Core.prototype.getCurrentTime = function() {
  19104. if (!this.currentTime) {
  19105. throw new Error('Option showCurrentTime must be true');
  19106. }
  19107. return this.currentTime.getCurrentTime();
  19108. };
  19109. /**
  19110. * Convert a position on screen (pixels) to a datetime
  19111. * @param {int} x Position on the screen in pixels
  19112. * @return {Date} time The datetime the corresponds with given position x
  19113. * @private
  19114. */
  19115. // TODO: move this function to Range
  19116. Core.prototype._toTime = function(x) {
  19117. return DateUtil.toTime(this.body, this.range, x, this.props.center.width);
  19118. };
  19119. /**
  19120. * Convert a position on the global screen (pixels) to a datetime
  19121. * @param {int} x Position on the screen in pixels
  19122. * @return {Date} time The datetime the corresponds with given position x
  19123. * @private
  19124. */
  19125. // TODO: move this function to Range
  19126. Core.prototype._toGlobalTime = function(x) {
  19127. return DateUtil.toTime(this.body, this.range, x, this.props.root.width);
  19128. //var conversion = this.range.conversion(this.props.root.width);
  19129. //return new Date(x / conversion.scale + conversion.offset);
  19130. };
  19131. /**
  19132. * Convert a datetime (Date object) into a position on the screen
  19133. * @param {Date} time A date
  19134. * @return {int} x The position on the screen in pixels which corresponds
  19135. * with the given date.
  19136. * @private
  19137. */
  19138. // TODO: move this function to Range
  19139. Core.prototype._toScreen = function(time) {
  19140. return DateUtil.toScreen(this, time, this.props.center.width);
  19141. };
  19142. /**
  19143. * Convert a datetime (Date object) into a position on the root
  19144. * This is used to get the pixel density estimate for the screen, not the center panel
  19145. * @param {Date} time A date
  19146. * @return {int} x The position on root in pixels which corresponds
  19147. * with the given date.
  19148. * @private
  19149. */
  19150. // TODO: move this function to Range
  19151. Core.prototype._toGlobalScreen = function(time) {
  19152. return DateUtil.toScreen(this, time, this.props.root.width);
  19153. //var conversion = this.range.conversion(this.props.root.width);
  19154. //return (time.valueOf() - conversion.offset) * conversion.scale;
  19155. };
  19156. /**
  19157. * Initialize watching when option autoResize is true
  19158. * @private
  19159. */
  19160. Core.prototype._initAutoResize = function () {
  19161. if (this.options.autoResize == true) {
  19162. this._startAutoResize();
  19163. }
  19164. else {
  19165. this._stopAutoResize();
  19166. }
  19167. };
  19168. /**
  19169. * Watch for changes in the size of the container. On resize, the Panel will
  19170. * automatically redraw itself.
  19171. * @private
  19172. */
  19173. Core.prototype._startAutoResize = function () {
  19174. var me = this;
  19175. this._stopAutoResize();
  19176. this._onResize = function() {
  19177. if (me.options.autoResize != true) {
  19178. // stop watching when the option autoResize is changed to false
  19179. me._stopAutoResize();
  19180. return;
  19181. }
  19182. if (me.dom.root) {
  19183. // check whether the frame is resized
  19184. // Note: we compare offsetWidth here, not clientWidth. For some reason,
  19185. // IE does not restore the clientWidth from 0 to the actual width after
  19186. // changing the timeline's container display style from none to visible
  19187. if ((me.dom.root.offsetWidth != me.props.lastWidth) ||
  19188. (me.dom.root.offsetHeight != me.props.lastHeight)) {
  19189. me.props.lastWidth = me.dom.root.offsetWidth;
  19190. me.props.lastHeight = me.dom.root.offsetHeight;
  19191. me.emit('change');
  19192. }
  19193. }
  19194. };
  19195. // add event listener to window resize
  19196. util.addEventListener(window, 'resize', this._onResize);
  19197. this.watchTimer = setInterval(this._onResize, 1000);
  19198. };
  19199. /**
  19200. * Stop watching for a resize of the frame.
  19201. * @private
  19202. */
  19203. Core.prototype._stopAutoResize = function () {
  19204. if (this.watchTimer) {
  19205. clearInterval(this.watchTimer);
  19206. this.watchTimer = undefined;
  19207. }
  19208. // remove event listener on window.resize
  19209. util.removeEventListener(window, 'resize', this._onResize);
  19210. this._onResize = null;
  19211. };
  19212. /**
  19213. * Start moving the timeline vertically
  19214. * @param {Event} event
  19215. * @private
  19216. */
  19217. Core.prototype._onTouch = function (event) {
  19218. this.touch.allowDragging = true;
  19219. };
  19220. /**
  19221. * Start moving the timeline vertically
  19222. * @param {Event} event
  19223. * @private
  19224. */
  19225. Core.prototype._onPinch = function (event) {
  19226. this.touch.allowDragging = false;
  19227. };
  19228. /**
  19229. * Start moving the timeline vertically
  19230. * @param {Event} event
  19231. * @private
  19232. */
  19233. Core.prototype._onDragStart = function (event) {
  19234. this.touch.initialScrollTop = this.props.scrollTop;
  19235. };
  19236. /**
  19237. * Move the timeline vertically
  19238. * @param {Event} event
  19239. * @private
  19240. */
  19241. Core.prototype._onDrag = function (event) {
  19242. // refuse to drag when we where pinching to prevent the timeline make a jump
  19243. // when releasing the fingers in opposite order from the touch screen
  19244. if (!this.touch.allowDragging) return;
  19245. var delta = event.gesture.deltaY;
  19246. var oldScrollTop = this._getScrollTop();
  19247. var newScrollTop = this._setScrollTop(this.touch.initialScrollTop + delta);
  19248. if (newScrollTop != oldScrollTop) {
  19249. this.redraw(); // TODO: this causes two redraws when dragging, the other is triggered by rangechange already
  19250. }
  19251. };
  19252. /**
  19253. * Apply a scrollTop
  19254. * @param {Number} scrollTop
  19255. * @returns {Number} scrollTop Returns the applied scrollTop
  19256. * @private
  19257. */
  19258. Core.prototype._setScrollTop = function (scrollTop) {
  19259. this.props.scrollTop = scrollTop;
  19260. this._updateScrollTop();
  19261. return this.props.scrollTop;
  19262. };
  19263. /**
  19264. * Update the current scrollTop when the height of the containers has been changed
  19265. * @returns {Number} scrollTop Returns the applied scrollTop
  19266. * @private
  19267. */
  19268. Core.prototype._updateScrollTop = function () {
  19269. // recalculate the scrollTopMin
  19270. var scrollTopMin = Math.min(this.props.centerContainer.height - this.props.center.height, 0); // is negative or zero
  19271. if (scrollTopMin != this.props.scrollTopMin) {
  19272. // in case of bottom orientation, change the scrollTop such that the contents
  19273. // do not move relative to the time axis at the bottom
  19274. if (this.options.orientation == 'bottom') {
  19275. this.props.scrollTop += (scrollTopMin - this.props.scrollTopMin);
  19276. }
  19277. this.props.scrollTopMin = scrollTopMin;
  19278. }
  19279. // limit the scrollTop to the feasible scroll range
  19280. if (this.props.scrollTop > 0) this.props.scrollTop = 0;
  19281. if (this.props.scrollTop < scrollTopMin) this.props.scrollTop = scrollTopMin;
  19282. return this.props.scrollTop;
  19283. };
  19284. /**
  19285. * Get the current scrollTop
  19286. * @returns {number} scrollTop
  19287. * @private
  19288. */
  19289. Core.prototype._getScrollTop = function () {
  19290. return this.props.scrollTop;
  19291. };
  19292. module.exports = Core;
  19293. /***/ },
  19294. /* 47 */
  19295. /***/ function(module, exports, __webpack_require__) {
  19296. var Hammer = __webpack_require__(45);
  19297. /**
  19298. * Fake a hammer.js gesture. Event can be a ScrollEvent or MouseMoveEvent
  19299. * @param {Element} element
  19300. * @param {Event} event
  19301. */
  19302. exports.fakeGesture = function(element, event) {
  19303. var eventType = null;
  19304. // for hammer.js 1.0.5
  19305. // var gesture = Hammer.event.collectEventData(this, eventType, event);
  19306. // for hammer.js 1.0.6+
  19307. var touches = Hammer.event.getTouchList(event, eventType);
  19308. var gesture = Hammer.event.collectEventData(this, eventType, touches, event);
  19309. // on IE in standards mode, no touches are recognized by hammer.js,
  19310. // resulting in NaN values for center.pageX and center.pageY
  19311. if (isNaN(gesture.center.pageX)) {
  19312. gesture.center.pageX = event.pageX;
  19313. }
  19314. if (isNaN(gesture.center.pageY)) {
  19315. gesture.center.pageY = event.pageY;
  19316. }
  19317. return gesture;
  19318. };
  19319. /***/ },
  19320. /* 48 */
  19321. /***/ function(module, exports, __webpack_require__) {
  19322. // English
  19323. exports['en'] = {
  19324. current: 'current',
  19325. time: 'time'
  19326. };
  19327. exports['en_EN'] = exports['en'];
  19328. exports['en_US'] = exports['en'];
  19329. // Dutch
  19330. exports['nl'] = {
  19331. custom: 'aangepaste',
  19332. time: 'tijd'
  19333. };
  19334. exports['nl_NL'] = exports['nl'];
  19335. exports['nl_BE'] = exports['nl'];
  19336. /***/ },
  19337. /* 49 */
  19338. /***/ function(module, exports, __webpack_require__) {
  19339. // English
  19340. exports['en'] = {
  19341. edit: 'Edit',
  19342. del: 'Delete selected',
  19343. back: 'Back',
  19344. addNode: 'Add Node',
  19345. addEdge: 'Add Edge',
  19346. editNode: 'Edit Node',
  19347. editEdge: 'Edit Edge',
  19348. addDescription: 'Click in an empty space to place a new node.',
  19349. edgeDescription: 'Click on a node and drag the edge to another node to connect them.',
  19350. editEdgeDescription: 'Click on the control points and drag them to a node to connect to it.',
  19351. createEdgeError: 'Cannot link edges to a cluster.',
  19352. deleteClusterError: 'Clusters cannot be deleted.'
  19353. };
  19354. exports['en_EN'] = exports['en'];
  19355. exports['en_US'] = exports['en'];
  19356. // Dutch
  19357. exports['nl'] = {
  19358. edit: 'Wijzigen',
  19359. del: 'Selectie verwijderen',
  19360. back: 'Terug',
  19361. addNode: 'Node toevoegen',
  19362. addEdge: 'Link toevoegen',
  19363. editNode: 'Node wijzigen',
  19364. editEdge: 'Link wijzigen',
  19365. addDescription: 'Klik op een leeg gebied om een nieuwe node te maken.',
  19366. edgeDescription: 'Klik op een node en sleep de link naar een andere node om ze te verbinden.',
  19367. editEdgeDescription: 'Klik op de verbindingspunten en sleep ze naar een node om daarmee te verbinden.',
  19368. createEdgeError: 'Kan geen link maken naar een cluster.',
  19369. deleteClusterError: 'Clusters kunnen niet worden verwijderd.'
  19370. };
  19371. exports['nl_NL'] = exports['nl'];
  19372. exports['nl_BE'] = exports['nl'];
  19373. /***/ },
  19374. /* 50 */
  19375. /***/ function(module, exports, __webpack_require__) {
  19376. /**
  19377. * Canvas shapes used by Network
  19378. */
  19379. if (typeof CanvasRenderingContext2D !== 'undefined') {
  19380. /**
  19381. * Draw a circle shape
  19382. */
  19383. CanvasRenderingContext2D.prototype.circle = function(x, y, r) {
  19384. this.beginPath();
  19385. this.arc(x, y, r, 0, 2*Math.PI, false);
  19386. };
  19387. /**
  19388. * Draw a square shape
  19389. * @param {Number} x horizontal center
  19390. * @param {Number} y vertical center
  19391. * @param {Number} r size, width and height of the square
  19392. */
  19393. CanvasRenderingContext2D.prototype.square = function(x, y, r) {
  19394. this.beginPath();
  19395. this.rect(x - r, y - r, r * 2, r * 2);
  19396. };
  19397. /**
  19398. * Draw a triangle shape
  19399. * @param {Number} x horizontal center
  19400. * @param {Number} y vertical center
  19401. * @param {Number} r radius, half the length of the sides of the triangle
  19402. */
  19403. CanvasRenderingContext2D.prototype.triangle = function(x, y, r) {
  19404. // http://en.wikipedia.org/wiki/Equilateral_triangle
  19405. this.beginPath();
  19406. var s = r * 2;
  19407. var s2 = s / 2;
  19408. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  19409. var h = Math.sqrt(s * s - s2 * s2); // height
  19410. this.moveTo(x, y - (h - ir));
  19411. this.lineTo(x + s2, y + ir);
  19412. this.lineTo(x - s2, y + ir);
  19413. this.lineTo(x, y - (h - ir));
  19414. this.closePath();
  19415. };
  19416. /**
  19417. * Draw a triangle shape in downward orientation
  19418. * @param {Number} x horizontal center
  19419. * @param {Number} y vertical center
  19420. * @param {Number} r radius
  19421. */
  19422. CanvasRenderingContext2D.prototype.triangleDown = function(x, y, r) {
  19423. // http://en.wikipedia.org/wiki/Equilateral_triangle
  19424. this.beginPath();
  19425. var s = r * 2;
  19426. var s2 = s / 2;
  19427. var ir = Math.sqrt(3) / 6 * s; // radius of inner circle
  19428. var h = Math.sqrt(s * s - s2 * s2); // height
  19429. this.moveTo(x, y + (h - ir));
  19430. this.lineTo(x + s2, y - ir);
  19431. this.lineTo(x - s2, y - ir);
  19432. this.lineTo(x, y + (h - ir));
  19433. this.closePath();
  19434. };
  19435. /**
  19436. * Draw a star shape, a star with 5 points
  19437. * @param {Number} x horizontal center
  19438. * @param {Number} y vertical center
  19439. * @param {Number} r radius, half the length of the sides of the triangle
  19440. */
  19441. CanvasRenderingContext2D.prototype.star = function(x, y, r) {
  19442. // http://www.html5canvastutorials.com/labs/html5-canvas-star-spinner/
  19443. this.beginPath();
  19444. for (var n = 0; n < 10; n++) {
  19445. var radius = (n % 2 === 0) ? r * 1.3 : r * 0.5;
  19446. this.lineTo(
  19447. x + radius * Math.sin(n * 2 * Math.PI / 10),
  19448. y - radius * Math.cos(n * 2 * Math.PI / 10)
  19449. );
  19450. }
  19451. this.closePath();
  19452. };
  19453. /**
  19454. * http://stackoverflow.com/questions/1255512/how-to-draw-a-rounded-rectangle-on-html-canvas
  19455. */
  19456. CanvasRenderingContext2D.prototype.roundRect = function(x, y, w, h, r) {
  19457. var r2d = Math.PI/180;
  19458. if( w - ( 2 * r ) < 0 ) { r = ( w / 2 ); } //ensure that the radius isn't too large for x
  19459. if( h - ( 2 * r ) < 0 ) { r = ( h / 2 ); } //ensure that the radius isn't too large for y
  19460. this.beginPath();
  19461. this.moveTo(x+r,y);
  19462. this.lineTo(x+w-r,y);
  19463. this.arc(x+w-r,y+r,r,r2d*270,r2d*360,false);
  19464. this.lineTo(x+w,y+h-r);
  19465. this.arc(x+w-r,y+h-r,r,0,r2d*90,false);
  19466. this.lineTo(x+r,y+h);
  19467. this.arc(x+r,y+h-r,r,r2d*90,r2d*180,false);
  19468. this.lineTo(x,y+r);
  19469. this.arc(x+r,y+r,r,r2d*180,r2d*270,false);
  19470. };
  19471. /**
  19472. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  19473. */
  19474. CanvasRenderingContext2D.prototype.ellipse = function(x, y, w, h) {
  19475. var kappa = .5522848,
  19476. ox = (w / 2) * kappa, // control point offset horizontal
  19477. oy = (h / 2) * kappa, // control point offset vertical
  19478. xe = x + w, // x-end
  19479. ye = y + h, // y-end
  19480. xm = x + w / 2, // x-middle
  19481. ym = y + h / 2; // y-middle
  19482. this.beginPath();
  19483. this.moveTo(x, ym);
  19484. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  19485. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  19486. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  19487. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  19488. };
  19489. /**
  19490. * http://stackoverflow.com/questions/2172798/how-to-draw-an-oval-in-html5-canvas
  19491. */
  19492. CanvasRenderingContext2D.prototype.database = function(x, y, w, h) {
  19493. var f = 1/3;
  19494. var wEllipse = w;
  19495. var hEllipse = h * f;
  19496. var kappa = .5522848,
  19497. ox = (wEllipse / 2) * kappa, // control point offset horizontal
  19498. oy = (hEllipse / 2) * kappa, // control point offset vertical
  19499. xe = x + wEllipse, // x-end
  19500. ye = y + hEllipse, // y-end
  19501. xm = x + wEllipse / 2, // x-middle
  19502. ym = y + hEllipse / 2, // y-middle
  19503. ymb = y + (h - hEllipse/2), // y-midlle, bottom ellipse
  19504. yeb = y + h; // y-end, bottom ellipse
  19505. this.beginPath();
  19506. this.moveTo(xe, ym);
  19507. this.bezierCurveTo(xe, ym + oy, xm + ox, ye, xm, ye);
  19508. this.bezierCurveTo(xm - ox, ye, x, ym + oy, x, ym);
  19509. this.bezierCurveTo(x, ym - oy, xm - ox, y, xm, y);
  19510. this.bezierCurveTo(xm + ox, y, xe, ym - oy, xe, ym);
  19511. this.lineTo(xe, ymb);
  19512. this.bezierCurveTo(xe, ymb + oy, xm + ox, yeb, xm, yeb);
  19513. this.bezierCurveTo(xm - ox, yeb, x, ymb + oy, x, ymb);
  19514. this.lineTo(x, ym);
  19515. };
  19516. /**
  19517. * Draw an arrow point (no line)
  19518. */
  19519. CanvasRenderingContext2D.prototype.arrow = function(x, y, angle, length) {
  19520. // tail
  19521. var xt = x - length * Math.cos(angle);
  19522. var yt = y - length * Math.sin(angle);
  19523. // inner tail
  19524. // TODO: allow to customize different shapes
  19525. var xi = x - length * 0.9 * Math.cos(angle);
  19526. var yi = y - length * 0.9 * Math.sin(angle);
  19527. // left
  19528. var xl = xt + length / 3 * Math.cos(angle + 0.5 * Math.PI);
  19529. var yl = yt + length / 3 * Math.sin(angle + 0.5 * Math.PI);
  19530. // right
  19531. var xr = xt + length / 3 * Math.cos(angle - 0.5 * Math.PI);
  19532. var yr = yt + length / 3 * Math.sin(angle - 0.5 * Math.PI);
  19533. this.beginPath();
  19534. this.moveTo(x, y);
  19535. this.lineTo(xl, yl);
  19536. this.lineTo(xi, yi);
  19537. this.lineTo(xr, yr);
  19538. this.closePath();
  19539. };
  19540. /**
  19541. * Sets up the dashedLine functionality for drawing
  19542. * Original code came from http://stackoverflow.com/questions/4576724/dotted-stroke-in-canvas
  19543. * @author David Jordan
  19544. * @date 2012-08-08
  19545. */
  19546. CanvasRenderingContext2D.prototype.dashedLine = function(x,y,x2,y2,dashArray){
  19547. if (!dashArray) dashArray=[10,5];
  19548. if (dashLength==0) dashLength = 0.001; // Hack for Safari
  19549. var dashCount = dashArray.length;
  19550. this.moveTo(x, y);
  19551. var dx = (x2-x), dy = (y2-y);
  19552. var slope = dy/dx;
  19553. var distRemaining = Math.sqrt( dx*dx + dy*dy );
  19554. var dashIndex=0, draw=true;
  19555. while (distRemaining>=0.1){
  19556. var dashLength = dashArray[dashIndex++%dashCount];
  19557. if (dashLength > distRemaining) dashLength = distRemaining;
  19558. var xStep = Math.sqrt( dashLength*dashLength / (1 + slope*slope) );
  19559. if (dx<0) xStep = -xStep;
  19560. x += xStep;
  19561. y += slope*xStep;
  19562. this[draw ? 'lineTo' : 'moveTo'](x,y);
  19563. distRemaining -= dashLength;
  19564. draw = !draw;
  19565. }
  19566. };
  19567. // TODO: add diamond shape
  19568. }
  19569. /***/ },
  19570. /* 51 */
  19571. /***/ function(module, exports, __webpack_require__) {
  19572. var PhysicsMixin = __webpack_require__(63);
  19573. var ClusterMixin = __webpack_require__(57);
  19574. var SectorsMixin = __webpack_require__(58);
  19575. var SelectionMixin = __webpack_require__(59);
  19576. var ManipulationMixin = __webpack_require__(60);
  19577. var NavigationMixin = __webpack_require__(61);
  19578. var HierarchicalLayoutMixin = __webpack_require__(62);
  19579. /**
  19580. * Load a mixin into the network object
  19581. *
  19582. * @param {Object} sourceVariable | this object has to contain functions.
  19583. * @private
  19584. */
  19585. exports._loadMixin = function (sourceVariable) {
  19586. for (var mixinFunction in sourceVariable) {
  19587. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  19588. this[mixinFunction] = sourceVariable[mixinFunction];
  19589. }
  19590. }
  19591. };
  19592. /**
  19593. * removes a mixin from the network object.
  19594. *
  19595. * @param {Object} sourceVariable | this object has to contain functions.
  19596. * @private
  19597. */
  19598. exports._clearMixin = function (sourceVariable) {
  19599. for (var mixinFunction in sourceVariable) {
  19600. if (sourceVariable.hasOwnProperty(mixinFunction)) {
  19601. this[mixinFunction] = undefined;
  19602. }
  19603. }
  19604. };
  19605. /**
  19606. * Mixin the physics system and initialize the parameters required.
  19607. *
  19608. * @private
  19609. */
  19610. exports._loadPhysicsSystem = function () {
  19611. this._loadMixin(PhysicsMixin);
  19612. this._loadSelectedForceSolver();
  19613. if (this.constants.configurePhysics == true) {
  19614. this._loadPhysicsConfiguration();
  19615. }
  19616. };
  19617. /**
  19618. * Mixin the cluster system and initialize the parameters required.
  19619. *
  19620. * @private
  19621. */
  19622. exports._loadClusterSystem = function () {
  19623. this.clusterSession = 0;
  19624. this.hubThreshold = 5;
  19625. this._loadMixin(ClusterMixin);
  19626. };
  19627. /**
  19628. * Mixin the sector system and initialize the parameters required
  19629. *
  19630. * @private
  19631. */
  19632. exports._loadSectorSystem = function () {
  19633. this.sectors = {};
  19634. this.activeSector = ["default"];
  19635. this.sectors["active"] = {};
  19636. this.sectors["active"]["default"] = {"nodes": {},
  19637. "edges": {},
  19638. "nodeIndices": [],
  19639. "formationScale": 1.0,
  19640. "drawingNode": undefined };
  19641. this.sectors["frozen"] = {};
  19642. this.sectors["support"] = {"nodes": {},
  19643. "edges": {},
  19644. "nodeIndices": [],
  19645. "formationScale": 1.0,
  19646. "drawingNode": undefined };
  19647. this.nodeIndices = this.sectors["active"]["default"]["nodeIndices"]; // the node indices list is used to speed up the computation of the repulsion fields
  19648. this._loadMixin(SectorsMixin);
  19649. };
  19650. /**
  19651. * Mixin the selection system and initialize the parameters required
  19652. *
  19653. * @private
  19654. */
  19655. exports._loadSelectionSystem = function () {
  19656. this.selectionObj = {nodes: {}, edges: {}};
  19657. this._loadMixin(SelectionMixin);
  19658. };
  19659. /**
  19660. * Mixin the navigationUI (User Interface) system and initialize the parameters required
  19661. *
  19662. * @private
  19663. */
  19664. exports._loadManipulationSystem = function () {
  19665. // reset global variables -- these are used by the selection of nodes and edges.
  19666. this.blockConnectingEdgeSelection = false;
  19667. this.forceAppendSelection = false;
  19668. if (this.constants.dataManipulation.enabled == true) {
  19669. // load the manipulator HTML elements. All styling done in css.
  19670. if (this.manipulationDiv === undefined) {
  19671. this.manipulationDiv = document.createElement('div');
  19672. this.manipulationDiv.className = 'network-manipulationDiv';
  19673. this.manipulationDiv.id = 'network-manipulationDiv';
  19674. if (this.editMode == true) {
  19675. this.manipulationDiv.style.display = "block";
  19676. }
  19677. else {
  19678. this.manipulationDiv.style.display = "none";
  19679. }
  19680. this.frame.appendChild(this.manipulationDiv);
  19681. }
  19682. if (this.editModeDiv === undefined) {
  19683. this.editModeDiv = document.createElement('div');
  19684. this.editModeDiv.className = 'network-manipulation-editMode';
  19685. this.editModeDiv.id = 'network-manipulation-editMode';
  19686. if (this.editMode == true) {
  19687. this.editModeDiv.style.display = "none";
  19688. }
  19689. else {
  19690. this.editModeDiv.style.display = "block";
  19691. }
  19692. this.frame.appendChild(this.editModeDiv);
  19693. }
  19694. if (this.closeDiv === undefined) {
  19695. this.closeDiv = document.createElement('div');
  19696. this.closeDiv.className = 'network-manipulation-closeDiv';
  19697. this.closeDiv.id = 'network-manipulation-closeDiv';
  19698. this.closeDiv.style.display = this.manipulationDiv.style.display;
  19699. this.frame.appendChild(this.closeDiv);
  19700. }
  19701. // load the manipulation functions
  19702. this._loadMixin(ManipulationMixin);
  19703. // create the manipulator toolbar
  19704. this._createManipulatorBar();
  19705. }
  19706. else {
  19707. if (this.manipulationDiv !== undefined) {
  19708. // removes all the bindings and overloads
  19709. this._createManipulatorBar();
  19710. // remove the manipulation divs
  19711. this.frame.removeChild(this.manipulationDiv);
  19712. this.frame.removeChild(this.editModeDiv);
  19713. this.frame.removeChild(this.closeDiv);
  19714. this.manipulationDiv = undefined;
  19715. this.editModeDiv = undefined;
  19716. this.closeDiv = undefined;
  19717. // remove the mixin functions
  19718. this._clearMixin(ManipulationMixin);
  19719. }
  19720. }
  19721. };
  19722. /**
  19723. * Mixin the navigation (User Interface) system and initialize the parameters required
  19724. *
  19725. * @private
  19726. */
  19727. exports._loadNavigationControls = function () {
  19728. this._loadMixin(NavigationMixin);
  19729. // the clean function removes the button divs, this is done to remove the bindings.
  19730. this._cleanNavigation();
  19731. if (this.constants.navigation.enabled == true) {
  19732. this._loadNavigationElements();
  19733. }
  19734. };
  19735. /**
  19736. * Mixin the hierarchical layout system.
  19737. *
  19738. * @private
  19739. */
  19740. exports._loadHierarchySystem = function () {
  19741. this._loadMixin(HierarchicalLayoutMixin);
  19742. };
  19743. /***/ },
  19744. /* 52 */
  19745. /***/ function(module, exports, __webpack_require__) {
  19746. var mousetrap = __webpack_require__(56);
  19747. var Emitter = __webpack_require__(53);
  19748. var Hammer = __webpack_require__(45);
  19749. var util = __webpack_require__(1);
  19750. /**
  19751. * Turn an element into an clickToUse element.
  19752. * When not active, the element has a transparent overlay. When the overlay is
  19753. * clicked, the mode is changed to active.
  19754. * When active, the element is displayed with a blue border around it, and
  19755. * the interactive contents of the element can be used. When clicked outside
  19756. * the element, the elements mode is changed to inactive.
  19757. * @param {Element} container
  19758. * @constructor
  19759. */
  19760. function Activator(container) {
  19761. this.active = false;
  19762. this.dom = {
  19763. container: container
  19764. };
  19765. this.dom.overlay = document.createElement('div');
  19766. this.dom.overlay.className = 'overlay';
  19767. this.dom.container.appendChild(this.dom.overlay);
  19768. this.hammer = Hammer(this.dom.overlay, {prevent_default: false});
  19769. this.hammer.on('tap', this._onTapOverlay.bind(this));
  19770. // block all touch events (except tap)
  19771. var me = this;
  19772. var events = [
  19773. 'touch', 'pinch',
  19774. 'doubletap', 'hold',
  19775. 'dragstart', 'drag', 'dragend',
  19776. 'mousewheel', 'DOMMouseScroll' // DOMMouseScroll is needed for Firefox
  19777. ];
  19778. events.forEach(function (event) {
  19779. me.hammer.on(event, function (event) {
  19780. event.stopPropagation();
  19781. });
  19782. });
  19783. // attach a tap event to the window, in order to deactivate when clicking outside the timeline
  19784. this.windowHammer = Hammer(window, {prevent_default: false});
  19785. this.windowHammer.on('tap', function (event) {
  19786. // deactivate when clicked outside the container
  19787. if (!_hasParent(event.target, container)) {
  19788. me.deactivate();
  19789. }
  19790. });
  19791. // mousetrap listener only bounded when active)
  19792. this.escListener = this.deactivate.bind(this);
  19793. }
  19794. // turn into an event emitter
  19795. Emitter(Activator.prototype);
  19796. // The currently active activator
  19797. Activator.current = null;
  19798. /**
  19799. * Destroy the activator. Cleans up all created DOM and event listeners
  19800. */
  19801. Activator.prototype.destroy = function () {
  19802. this.deactivate();
  19803. // remove dom
  19804. this.dom.overlay.parentNode.removeChild(this.dom.overlay);
  19805. // cleanup hammer instances
  19806. this.hammer = null;
  19807. this.windowHammer = null;
  19808. // FIXME: cleaning up hammer instances doesn't work (Timeline not removed from memory)
  19809. };
  19810. /**
  19811. * Activate the element
  19812. * Overlay is hidden, element is decorated with a blue shadow border
  19813. */
  19814. Activator.prototype.activate = function () {
  19815. // we allow only one active activator at a time
  19816. if (Activator.current) {
  19817. Activator.current.deactivate();
  19818. }
  19819. Activator.current = this;
  19820. this.active = true;
  19821. this.dom.overlay.style.display = 'none';
  19822. util.addClassName(this.dom.container, 'vis-active');
  19823. this.emit('change');
  19824. this.emit('activate');
  19825. // ugly hack: bind ESC after emitting the events, as the Network rebinds all
  19826. // keyboard events on a 'change' event
  19827. mousetrap.bind('esc', this.escListener);
  19828. };
  19829. /**
  19830. * Deactivate the element
  19831. * Overlay is displayed on top of the element
  19832. */
  19833. Activator.prototype.deactivate = function () {
  19834. this.active = false;
  19835. this.dom.overlay.style.display = '';
  19836. util.removeClassName(this.dom.container, 'vis-active');
  19837. mousetrap.unbind('esc', this.escListener);
  19838. this.emit('change');
  19839. this.emit('deactivate');
  19840. };
  19841. /**
  19842. * Handle a tap event: activate the container
  19843. * @param event
  19844. * @private
  19845. */
  19846. Activator.prototype._onTapOverlay = function (event) {
  19847. // activate the container
  19848. this.activate();
  19849. event.stopPropagation();
  19850. };
  19851. /**
  19852. * Test whether the element has the requested parent element somewhere in
  19853. * its chain of parent nodes.
  19854. * @param {HTMLElement} element
  19855. * @param {HTMLElement} parent
  19856. * @returns {boolean} Returns true when the parent is found somewhere in the
  19857. * chain of parent nodes.
  19858. * @private
  19859. */
  19860. function _hasParent(element, parent) {
  19861. while (element) {
  19862. if (element === parent) {
  19863. return true
  19864. }
  19865. element = element.parentNode;
  19866. }
  19867. return false;
  19868. }
  19869. module.exports = Activator;
  19870. /***/ },
  19871. /* 53 */
  19872. /***/ function(module, exports, __webpack_require__) {
  19873. /**
  19874. * Expose `Emitter`.
  19875. */
  19876. module.exports = Emitter;
  19877. /**
  19878. * Initialize a new `Emitter`.
  19879. *
  19880. * @api public
  19881. */
  19882. function Emitter(obj) {
  19883. if (obj) return mixin(obj);
  19884. };
  19885. /**
  19886. * Mixin the emitter properties.
  19887. *
  19888. * @param {Object} obj
  19889. * @return {Object}
  19890. * @api private
  19891. */
  19892. function mixin(obj) {
  19893. for (var key in Emitter.prototype) {
  19894. obj[key] = Emitter.prototype[key];
  19895. }
  19896. return obj;
  19897. }
  19898. /**
  19899. * Listen on the given `event` with `fn`.
  19900. *
  19901. * @param {String} event
  19902. * @param {Function} fn
  19903. * @return {Emitter}
  19904. * @api public
  19905. */
  19906. Emitter.prototype.on =
  19907. Emitter.prototype.addEventListener = function(event, fn){
  19908. this._callbacks = this._callbacks || {};
  19909. (this._callbacks[event] = this._callbacks[event] || [])
  19910. .push(fn);
  19911. return this;
  19912. };
  19913. /**
  19914. * Adds an `event` listener that will be invoked a single
  19915. * time then automatically removed.
  19916. *
  19917. * @param {String} event
  19918. * @param {Function} fn
  19919. * @return {Emitter}
  19920. * @api public
  19921. */
  19922. Emitter.prototype.once = function(event, fn){
  19923. var self = this;
  19924. this._callbacks = this._callbacks || {};
  19925. function on() {
  19926. self.off(event, on);
  19927. fn.apply(this, arguments);
  19928. }
  19929. on.fn = fn;
  19930. this.on(event, on);
  19931. return this;
  19932. };
  19933. /**
  19934. * Remove the given callback for `event` or all
  19935. * registered callbacks.
  19936. *
  19937. * @param {String} event
  19938. * @param {Function} fn
  19939. * @return {Emitter}
  19940. * @api public
  19941. */
  19942. Emitter.prototype.off =
  19943. Emitter.prototype.removeListener =
  19944. Emitter.prototype.removeAllListeners =
  19945. Emitter.prototype.removeEventListener = function(event, fn){
  19946. this._callbacks = this._callbacks || {};
  19947. // all
  19948. if (0 == arguments.length) {
  19949. this._callbacks = {};
  19950. return this;
  19951. }
  19952. // specific event
  19953. var callbacks = this._callbacks[event];
  19954. if (!callbacks) return this;
  19955. // remove all handlers
  19956. if (1 == arguments.length) {
  19957. delete this._callbacks[event];
  19958. return this;
  19959. }
  19960. // remove specific handler
  19961. var cb;
  19962. for (var i = 0; i < callbacks.length; i++) {
  19963. cb = callbacks[i];
  19964. if (cb === fn || cb.fn === fn) {
  19965. callbacks.splice(i, 1);
  19966. break;
  19967. }
  19968. }
  19969. return this;
  19970. };
  19971. /**
  19972. * Emit `event` with the given args.
  19973. *
  19974. * @param {String} event
  19975. * @param {Mixed} ...
  19976. * @return {Emitter}
  19977. */
  19978. Emitter.prototype.emit = function(event){
  19979. this._callbacks = this._callbacks || {};
  19980. var args = [].slice.call(arguments, 1)
  19981. , callbacks = this._callbacks[event];
  19982. if (callbacks) {
  19983. callbacks = callbacks.slice(0);
  19984. for (var i = 0, len = callbacks.length; i < len; ++i) {
  19985. callbacks[i].apply(this, args);
  19986. }
  19987. }
  19988. return this;
  19989. };
  19990. /**
  19991. * Return array of callbacks for `event`.
  19992. *
  19993. * @param {String} event
  19994. * @return {Array}
  19995. * @api public
  19996. */
  19997. Emitter.prototype.listeners = function(event){
  19998. this._callbacks = this._callbacks || {};
  19999. return this._callbacks[event] || [];
  20000. };
  20001. /**
  20002. * Check if this emitter has `event` handlers.
  20003. *
  20004. * @param {String} event
  20005. * @return {Boolean}
  20006. * @api public
  20007. */
  20008. Emitter.prototype.hasListeners = function(event){
  20009. return !! this.listeners(event).length;
  20010. };
  20011. /***/ },
  20012. /* 54 */
  20013. /***/ function(module, exports, __webpack_require__) {
  20014. var __WEBPACK_AMD_DEFINE_RESULT__;/* WEBPACK VAR INJECTION */(function(global, module) {//! moment.js
  20015. //! version : 2.8.3
  20016. //! authors : Tim Wood, Iskren Chernev, Moment.js contributors
  20017. //! license : MIT
  20018. //! momentjs.com
  20019. (function (undefined) {
  20020. /************************************
  20021. Constants
  20022. ************************************/
  20023. var moment,
  20024. VERSION = '2.8.3',
  20025. // the global-scope this is NOT the global object in Node.js
  20026. globalScope = typeof global !== 'undefined' ? global : this,
  20027. oldGlobalMoment,
  20028. round = Math.round,
  20029. hasOwnProperty = Object.prototype.hasOwnProperty,
  20030. i,
  20031. YEAR = 0,
  20032. MONTH = 1,
  20033. DATE = 2,
  20034. HOUR = 3,
  20035. MINUTE = 4,
  20036. SECOND = 5,
  20037. MILLISECOND = 6,
  20038. // internal storage for locale config files
  20039. locales = {},
  20040. // extra moment internal properties (plugins register props here)
  20041. momentProperties = [],
  20042. // check for nodeJS
  20043. hasModule = (typeof module !== 'undefined' && module.exports),
  20044. // ASP.NET json date format regex
  20045. aspNetJsonRegex = /^\/?Date\((\-?\d+)/i,
  20046. aspNetTimeSpanJsonRegex = /(\-)?(?:(\d*)\.)?(\d+)\:(\d+)(?:\:(\d+)\.?(\d{3})?)?/,
  20047. // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html
  20048. // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere
  20049. isoDurationRegex = /^(-)?P(?:(?:([0-9,.]*)Y)?(?:([0-9,.]*)M)?(?:([0-9,.]*)D)?(?:T(?:([0-9,.]*)H)?(?:([0-9,.]*)M)?(?:([0-9,.]*)S)?)?|([0-9,.]*)W)$/,
  20050. // format tokens
  20051. formattingTokens = /(\[[^\[]*\])|(\\)?(Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Q|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|mm?|ss?|S{1,4}|X|zz?|ZZ?|.)/g,
  20052. localFormattingTokens = /(\[[^\[]*\])|(\\)?(LT|LL?L?L?|l{1,4})/g,
  20053. // parsing token regexes
  20054. parseTokenOneOrTwoDigits = /\d\d?/, // 0 - 99
  20055. parseTokenOneToThreeDigits = /\d{1,3}/, // 0 - 999
  20056. parseTokenOneToFourDigits = /\d{1,4}/, // 0 - 9999
  20057. parseTokenOneToSixDigits = /[+\-]?\d{1,6}/, // -999,999 - 999,999
  20058. parseTokenDigits = /\d+/, // nonzero number of digits
  20059. parseTokenWord = /[0-9]*['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]+|[\u0600-\u06FF\/]+(\s*?[\u0600-\u06FF]+){1,2}/i, // any word (or two) characters or numbers including two/three word month in arabic.
  20060. parseTokenTimezone = /Z|[\+\-]\d\d:?\d\d/gi, // +00:00 -00:00 +0000 -0000 or Z
  20061. parseTokenT = /T/i, // T (ISO separator)
  20062. parseTokenTimestampMs = /[\+\-]?\d+(\.\d{1,3})?/, // 123456789 123456789.123
  20063. parseTokenOrdinal = /\d{1,2}/,
  20064. //strict parsing regexes
  20065. parseTokenOneDigit = /\d/, // 0 - 9
  20066. parseTokenTwoDigits = /\d\d/, // 00 - 99
  20067. parseTokenThreeDigits = /\d{3}/, // 000 - 999
  20068. parseTokenFourDigits = /\d{4}/, // 0000 - 9999
  20069. parseTokenSixDigits = /[+-]?\d{6}/, // -999,999 - 999,999
  20070. parseTokenSignedNumber = /[+-]?\d+/, // -inf - inf
  20071. // iso 8601 regex
  20072. // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00)
  20073. isoRegex = /^\s*(?:[+-]\d{6}|\d{4})-(?:(\d\d-\d\d)|(W\d\d$)|(W\d\d-\d)|(\d\d\d))((T| )(\d\d(:\d\d(:\d\d(\.\d+)?)?)?)?([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/,
  20074. isoFormat = 'YYYY-MM-DDTHH:mm:ssZ',
  20075. isoDates = [
  20076. ['YYYYYY-MM-DD', /[+-]\d{6}-\d{2}-\d{2}/],
  20077. ['YYYY-MM-DD', /\d{4}-\d{2}-\d{2}/],
  20078. ['GGGG-[W]WW-E', /\d{4}-W\d{2}-\d/],
  20079. ['GGGG-[W]WW', /\d{4}-W\d{2}/],
  20080. ['YYYY-DDD', /\d{4}-\d{3}/]
  20081. ],
  20082. // iso time formats and regexes
  20083. isoTimes = [
  20084. ['HH:mm:ss.SSSS', /(T| )\d\d:\d\d:\d\d\.\d+/],
  20085. ['HH:mm:ss', /(T| )\d\d:\d\d:\d\d/],
  20086. ['HH:mm', /(T| )\d\d:\d\d/],
  20087. ['HH', /(T| )\d\d/]
  20088. ],
  20089. // timezone chunker '+10:00' > ['10', '00'] or '-1530' > ['-15', '30']
  20090. parseTimezoneChunker = /([\+\-]|\d\d)/gi,
  20091. // getter and setter names
  20092. proxyGettersAndSetters = 'Date|Hours|Minutes|Seconds|Milliseconds'.split('|'),
  20093. unitMillisecondFactors = {
  20094. 'Milliseconds' : 1,
  20095. 'Seconds' : 1e3,
  20096. 'Minutes' : 6e4,
  20097. 'Hours' : 36e5,
  20098. 'Days' : 864e5,
  20099. 'Months' : 2592e6,
  20100. 'Years' : 31536e6
  20101. },
  20102. unitAliases = {
  20103. ms : 'millisecond',
  20104. s : 'second',
  20105. m : 'minute',
  20106. h : 'hour',
  20107. d : 'day',
  20108. D : 'date',
  20109. w : 'week',
  20110. W : 'isoWeek',
  20111. M : 'month',
  20112. Q : 'quarter',
  20113. y : 'year',
  20114. DDD : 'dayOfYear',
  20115. e : 'weekday',
  20116. E : 'isoWeekday',
  20117. gg: 'weekYear',
  20118. GG: 'isoWeekYear'
  20119. },
  20120. camelFunctions = {
  20121. dayofyear : 'dayOfYear',
  20122. isoweekday : 'isoWeekday',
  20123. isoweek : 'isoWeek',
  20124. weekyear : 'weekYear',
  20125. isoweekyear : 'isoWeekYear'
  20126. },
  20127. // format function strings
  20128. formatFunctions = {},
  20129. // default relative time thresholds
  20130. relativeTimeThresholds = {
  20131. s: 45, // seconds to minute
  20132. m: 45, // minutes to hour
  20133. h: 22, // hours to day
  20134. d: 26, // days to month
  20135. M: 11 // months to year
  20136. },
  20137. // tokens to ordinalize and pad
  20138. ordinalizeTokens = 'DDD w W M D d'.split(' '),
  20139. paddedTokens = 'M D H h m s w W'.split(' '),
  20140. formatTokenFunctions = {
  20141. M : function () {
  20142. return this.month() + 1;
  20143. },
  20144. MMM : function (format) {
  20145. return this.localeData().monthsShort(this, format);
  20146. },
  20147. MMMM : function (format) {
  20148. return this.localeData().months(this, format);
  20149. },
  20150. D : function () {
  20151. return this.date();
  20152. },
  20153. DDD : function () {
  20154. return this.dayOfYear();
  20155. },
  20156. d : function () {
  20157. return this.day();
  20158. },
  20159. dd : function (format) {
  20160. return this.localeData().weekdaysMin(this, format);
  20161. },
  20162. ddd : function (format) {
  20163. return this.localeData().weekdaysShort(this, format);
  20164. },
  20165. dddd : function (format) {
  20166. return this.localeData().weekdays(this, format);
  20167. },
  20168. w : function () {
  20169. return this.week();
  20170. },
  20171. W : function () {
  20172. return this.isoWeek();
  20173. },
  20174. YY : function () {
  20175. return leftZeroFill(this.year() % 100, 2);
  20176. },
  20177. YYYY : function () {
  20178. return leftZeroFill(this.year(), 4);
  20179. },
  20180. YYYYY : function () {
  20181. return leftZeroFill(this.year(), 5);
  20182. },
  20183. YYYYYY : function () {
  20184. var y = this.year(), sign = y >= 0 ? '+' : '-';
  20185. return sign + leftZeroFill(Math.abs(y), 6);
  20186. },
  20187. gg : function () {
  20188. return leftZeroFill(this.weekYear() % 100, 2);
  20189. },
  20190. gggg : function () {
  20191. return leftZeroFill(this.weekYear(), 4);
  20192. },
  20193. ggggg : function () {
  20194. return leftZeroFill(this.weekYear(), 5);
  20195. },
  20196. GG : function () {
  20197. return leftZeroFill(this.isoWeekYear() % 100, 2);
  20198. },
  20199. GGGG : function () {
  20200. return leftZeroFill(this.isoWeekYear(), 4);
  20201. },
  20202. GGGGG : function () {
  20203. return leftZeroFill(this.isoWeekYear(), 5);
  20204. },
  20205. e : function () {
  20206. return this.weekday();
  20207. },
  20208. E : function () {
  20209. return this.isoWeekday();
  20210. },
  20211. a : function () {
  20212. return this.localeData().meridiem(this.hours(), this.minutes(), true);
  20213. },
  20214. A : function () {
  20215. return this.localeData().meridiem(this.hours(), this.minutes(), false);
  20216. },
  20217. H : function () {
  20218. return this.hours();
  20219. },
  20220. h : function () {
  20221. return this.hours() % 12 || 12;
  20222. },
  20223. m : function () {
  20224. return this.minutes();
  20225. },
  20226. s : function () {
  20227. return this.seconds();
  20228. },
  20229. S : function () {
  20230. return toInt(this.milliseconds() / 100);
  20231. },
  20232. SS : function () {
  20233. return leftZeroFill(toInt(this.milliseconds() / 10), 2);
  20234. },
  20235. SSS : function () {
  20236. return leftZeroFill(this.milliseconds(), 3);
  20237. },
  20238. SSSS : function () {
  20239. return leftZeroFill(this.milliseconds(), 3);
  20240. },
  20241. Z : function () {
  20242. var a = -this.zone(),
  20243. b = '+';
  20244. if (a < 0) {
  20245. a = -a;
  20246. b = '-';
  20247. }
  20248. return b + leftZeroFill(toInt(a / 60), 2) + ':' + leftZeroFill(toInt(a) % 60, 2);
  20249. },
  20250. ZZ : function () {
  20251. var a = -this.zone(),
  20252. b = '+';
  20253. if (a < 0) {
  20254. a = -a;
  20255. b = '-';
  20256. }
  20257. return b + leftZeroFill(toInt(a / 60), 2) + leftZeroFill(toInt(a) % 60, 2);
  20258. },
  20259. z : function () {
  20260. return this.zoneAbbr();
  20261. },
  20262. zz : function () {
  20263. return this.zoneName();
  20264. },
  20265. X : function () {
  20266. return this.unix();
  20267. },
  20268. Q : function () {
  20269. return this.quarter();
  20270. }
  20271. },
  20272. deprecations = {},
  20273. lists = ['months', 'monthsShort', 'weekdays', 'weekdaysShort', 'weekdaysMin'];
  20274. // Pick the first defined of two or three arguments. dfl comes from
  20275. // default.
  20276. function dfl(a, b, c) {
  20277. switch (arguments.length) {
  20278. case 2: return a != null ? a : b;
  20279. case 3: return a != null ? a : b != null ? b : c;
  20280. default: throw new Error('Implement me');
  20281. }
  20282. }
  20283. function hasOwnProp(a, b) {
  20284. return hasOwnProperty.call(a, b);
  20285. }
  20286. function defaultParsingFlags() {
  20287. // We need to deep clone this object, and es5 standard is not very
  20288. // helpful.
  20289. return {
  20290. empty : false,
  20291. unusedTokens : [],
  20292. unusedInput : [],
  20293. overflow : -2,
  20294. charsLeftOver : 0,
  20295. nullInput : false,
  20296. invalidMonth : null,
  20297. invalidFormat : false,
  20298. userInvalidated : false,
  20299. iso: false
  20300. };
  20301. }
  20302. function printMsg(msg) {
  20303. if (moment.suppressDeprecationWarnings === false &&
  20304. typeof console !== 'undefined' && console.warn) {
  20305. console.warn('Deprecation warning: ' + msg);
  20306. }
  20307. }
  20308. function deprecate(msg, fn) {
  20309. var firstTime = true;
  20310. return extend(function () {
  20311. if (firstTime) {
  20312. printMsg(msg);
  20313. firstTime = false;
  20314. }
  20315. return fn.apply(this, arguments);
  20316. }, fn);
  20317. }
  20318. function deprecateSimple(name, msg) {
  20319. if (!deprecations[name]) {
  20320. printMsg(msg);
  20321. deprecations[name] = true;
  20322. }
  20323. }
  20324. function padToken(func, count) {
  20325. return function (a) {
  20326. return leftZeroFill(func.call(this, a), count);
  20327. };
  20328. }
  20329. function ordinalizeToken(func, period) {
  20330. return function (a) {
  20331. return this.localeData().ordinal(func.call(this, a), period);
  20332. };
  20333. }
  20334. while (ordinalizeTokens.length) {
  20335. i = ordinalizeTokens.pop();
  20336. formatTokenFunctions[i + 'o'] = ordinalizeToken(formatTokenFunctions[i], i);
  20337. }
  20338. while (paddedTokens.length) {
  20339. i = paddedTokens.pop();
  20340. formatTokenFunctions[i + i] = padToken(formatTokenFunctions[i], 2);
  20341. }
  20342. formatTokenFunctions.DDDD = padToken(formatTokenFunctions.DDD, 3);
  20343. /************************************
  20344. Constructors
  20345. ************************************/
  20346. function Locale() {
  20347. }
  20348. // Moment prototype object
  20349. function Moment(config, skipOverflow) {
  20350. if (skipOverflow !== false) {
  20351. checkOverflow(config);
  20352. }
  20353. copyConfig(this, config);
  20354. this._d = new Date(+config._d);
  20355. }
  20356. // Duration Constructor
  20357. function Duration(duration) {
  20358. var normalizedInput = normalizeObjectUnits(duration),
  20359. years = normalizedInput.year || 0,
  20360. quarters = normalizedInput.quarter || 0,
  20361. months = normalizedInput.month || 0,
  20362. weeks = normalizedInput.week || 0,
  20363. days = normalizedInput.day || 0,
  20364. hours = normalizedInput.hour || 0,
  20365. minutes = normalizedInput.minute || 0,
  20366. seconds = normalizedInput.second || 0,
  20367. milliseconds = normalizedInput.millisecond || 0;
  20368. // representation for dateAddRemove
  20369. this._milliseconds = +milliseconds +
  20370. seconds * 1e3 + // 1000
  20371. minutes * 6e4 + // 1000 * 60
  20372. hours * 36e5; // 1000 * 60 * 60
  20373. // Because of dateAddRemove treats 24 hours as different from a
  20374. // day when working around DST, we need to store them separately
  20375. this._days = +days +
  20376. weeks * 7;
  20377. // It is impossible translate months into days without knowing
  20378. // which months you are are talking about, so we have to store
  20379. // it separately.
  20380. this._months = +months +
  20381. quarters * 3 +
  20382. years * 12;
  20383. this._data = {};
  20384. this._locale = moment.localeData();
  20385. this._bubble();
  20386. }
  20387. /************************************
  20388. Helpers
  20389. ************************************/
  20390. function extend(a, b) {
  20391. for (var i in b) {
  20392. if (hasOwnProp(b, i)) {
  20393. a[i] = b[i];
  20394. }
  20395. }
  20396. if (hasOwnProp(b, 'toString')) {
  20397. a.toString = b.toString;
  20398. }
  20399. if (hasOwnProp(b, 'valueOf')) {
  20400. a.valueOf = b.valueOf;
  20401. }
  20402. return a;
  20403. }
  20404. function copyConfig(to, from) {
  20405. var i, prop, val;
  20406. if (typeof from._isAMomentObject !== 'undefined') {
  20407. to._isAMomentObject = from._isAMomentObject;
  20408. }
  20409. if (typeof from._i !== 'undefined') {
  20410. to._i = from._i;
  20411. }
  20412. if (typeof from._f !== 'undefined') {
  20413. to._f = from._f;
  20414. }
  20415. if (typeof from._l !== 'undefined') {
  20416. to._l = from._l;
  20417. }
  20418. if (typeof from._strict !== 'undefined') {
  20419. to._strict = from._strict;
  20420. }
  20421. if (typeof from._tzm !== 'undefined') {
  20422. to._tzm = from._tzm;
  20423. }
  20424. if (typeof from._isUTC !== 'undefined') {
  20425. to._isUTC = from._isUTC;
  20426. }
  20427. if (typeof from._offset !== 'undefined') {
  20428. to._offset = from._offset;
  20429. }
  20430. if (typeof from._pf !== 'undefined') {
  20431. to._pf = from._pf;
  20432. }
  20433. if (typeof from._locale !== 'undefined') {
  20434. to._locale = from._locale;
  20435. }
  20436. if (momentProperties.length > 0) {
  20437. for (i in momentProperties) {
  20438. prop = momentProperties[i];
  20439. val = from[prop];
  20440. if (typeof val !== 'undefined') {
  20441. to[prop] = val;
  20442. }
  20443. }
  20444. }
  20445. return to;
  20446. }
  20447. function absRound(number) {
  20448. if (number < 0) {
  20449. return Math.ceil(number);
  20450. } else {
  20451. return Math.floor(number);
  20452. }
  20453. }
  20454. // left zero fill a number
  20455. // see http://jsperf.com/left-zero-filling for performance comparison
  20456. function leftZeroFill(number, targetLength, forceSign) {
  20457. var output = '' + Math.abs(number),
  20458. sign = number >= 0;
  20459. while (output.length < targetLength) {
  20460. output = '0' + output;
  20461. }
  20462. return (sign ? (forceSign ? '+' : '') : '-') + output;
  20463. }
  20464. function positiveMomentsDifference(base, other) {
  20465. var res = {milliseconds: 0, months: 0};
  20466. res.months = other.month() - base.month() +
  20467. (other.year() - base.year()) * 12;
  20468. if (base.clone().add(res.months, 'M').isAfter(other)) {
  20469. --res.months;
  20470. }
  20471. res.milliseconds = +other - +(base.clone().add(res.months, 'M'));
  20472. return res;
  20473. }
  20474. function momentsDifference(base, other) {
  20475. var res;
  20476. other = makeAs(other, base);
  20477. if (base.isBefore(other)) {
  20478. res = positiveMomentsDifference(base, other);
  20479. } else {
  20480. res = positiveMomentsDifference(other, base);
  20481. res.milliseconds = -res.milliseconds;
  20482. res.months = -res.months;
  20483. }
  20484. return res;
  20485. }
  20486. // TODO: remove 'name' arg after deprecation is removed
  20487. function createAdder(direction, name) {
  20488. return function (val, period) {
  20489. var dur, tmp;
  20490. //invert the arguments, but complain about it
  20491. if (period !== null && !isNaN(+period)) {
  20492. deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period).');
  20493. tmp = val; val = period; period = tmp;
  20494. }
  20495. val = typeof val === 'string' ? +val : val;
  20496. dur = moment.duration(val, period);
  20497. addOrSubtractDurationFromMoment(this, dur, direction);
  20498. return this;
  20499. };
  20500. }
  20501. function addOrSubtractDurationFromMoment(mom, duration, isAdding, updateOffset) {
  20502. var milliseconds = duration._milliseconds,
  20503. days = duration._days,
  20504. months = duration._months;
  20505. updateOffset = updateOffset == null ? true : updateOffset;
  20506. if (milliseconds) {
  20507. mom._d.setTime(+mom._d + milliseconds * isAdding);
  20508. }
  20509. if (days) {
  20510. rawSetter(mom, 'Date', rawGetter(mom, 'Date') + days * isAdding);
  20511. }
  20512. if (months) {
  20513. rawMonthSetter(mom, rawGetter(mom, 'Month') + months * isAdding);
  20514. }
  20515. if (updateOffset) {
  20516. moment.updateOffset(mom, days || months);
  20517. }
  20518. }
  20519. // check if is an array
  20520. function isArray(input) {
  20521. return Object.prototype.toString.call(input) === '[object Array]';
  20522. }
  20523. function isDate(input) {
  20524. return Object.prototype.toString.call(input) === '[object Date]' ||
  20525. input instanceof Date;
  20526. }
  20527. // compare two arrays, return the number of differences
  20528. function compareArrays(array1, array2, dontConvert) {
  20529. var len = Math.min(array1.length, array2.length),
  20530. lengthDiff = Math.abs(array1.length - array2.length),
  20531. diffs = 0,
  20532. i;
  20533. for (i = 0; i < len; i++) {
  20534. if ((dontConvert && array1[i] !== array2[i]) ||
  20535. (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) {
  20536. diffs++;
  20537. }
  20538. }
  20539. return diffs + lengthDiff;
  20540. }
  20541. function normalizeUnits(units) {
  20542. if (units) {
  20543. var lowered = units.toLowerCase().replace(/(.)s$/, '$1');
  20544. units = unitAliases[units] || camelFunctions[lowered] || lowered;
  20545. }
  20546. return units;
  20547. }
  20548. function normalizeObjectUnits(inputObject) {
  20549. var normalizedInput = {},
  20550. normalizedProp,
  20551. prop;
  20552. for (prop in inputObject) {
  20553. if (hasOwnProp(inputObject, prop)) {
  20554. normalizedProp = normalizeUnits(prop);
  20555. if (normalizedProp) {
  20556. normalizedInput[normalizedProp] = inputObject[prop];
  20557. }
  20558. }
  20559. }
  20560. return normalizedInput;
  20561. }
  20562. function makeList(field) {
  20563. var count, setter;
  20564. if (field.indexOf('week') === 0) {
  20565. count = 7;
  20566. setter = 'day';
  20567. }
  20568. else if (field.indexOf('month') === 0) {
  20569. count = 12;
  20570. setter = 'month';
  20571. }
  20572. else {
  20573. return;
  20574. }
  20575. moment[field] = function (format, index) {
  20576. var i, getter,
  20577. method = moment._locale[field],
  20578. results = [];
  20579. if (typeof format === 'number') {
  20580. index = format;
  20581. format = undefined;
  20582. }
  20583. getter = function (i) {
  20584. var m = moment().utc().set(setter, i);
  20585. return method.call(moment._locale, m, format || '');
  20586. };
  20587. if (index != null) {
  20588. return getter(index);
  20589. }
  20590. else {
  20591. for (i = 0; i < count; i++) {
  20592. results.push(getter(i));
  20593. }
  20594. return results;
  20595. }
  20596. };
  20597. }
  20598. function toInt(argumentForCoercion) {
  20599. var coercedNumber = +argumentForCoercion,
  20600. value = 0;
  20601. if (coercedNumber !== 0 && isFinite(coercedNumber)) {
  20602. if (coercedNumber >= 0) {
  20603. value = Math.floor(coercedNumber);
  20604. } else {
  20605. value = Math.ceil(coercedNumber);
  20606. }
  20607. }
  20608. return value;
  20609. }
  20610. function daysInMonth(year, month) {
  20611. return new Date(Date.UTC(year, month + 1, 0)).getUTCDate();
  20612. }
  20613. function weeksInYear(year, dow, doy) {
  20614. return weekOfYear(moment([year, 11, 31 + dow - doy]), dow, doy).week;
  20615. }
  20616. function daysInYear(year) {
  20617. return isLeapYear(year) ? 366 : 365;
  20618. }
  20619. function isLeapYear(year) {
  20620. return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
  20621. }
  20622. function checkOverflow(m) {
  20623. var overflow;
  20624. if (m._a && m._pf.overflow === -2) {
  20625. overflow =
  20626. m._a[MONTH] < 0 || m._a[MONTH] > 11 ? MONTH :
  20627. m._a[DATE] < 1 || m._a[DATE] > daysInMonth(m._a[YEAR], m._a[MONTH]) ? DATE :
  20628. m._a[HOUR] < 0 || m._a[HOUR] > 23 ? HOUR :
  20629. m._a[MINUTE] < 0 || m._a[MINUTE] > 59 ? MINUTE :
  20630. m._a[SECOND] < 0 || m._a[SECOND] > 59 ? SECOND :
  20631. m._a[MILLISECOND] < 0 || m._a[MILLISECOND] > 999 ? MILLISECOND :
  20632. -1;
  20633. if (m._pf._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) {
  20634. overflow = DATE;
  20635. }
  20636. m._pf.overflow = overflow;
  20637. }
  20638. }
  20639. function isValid(m) {
  20640. if (m._isValid == null) {
  20641. m._isValid = !isNaN(m._d.getTime()) &&
  20642. m._pf.overflow < 0 &&
  20643. !m._pf.empty &&
  20644. !m._pf.invalidMonth &&
  20645. !m._pf.nullInput &&
  20646. !m._pf.invalidFormat &&
  20647. !m._pf.userInvalidated;
  20648. if (m._strict) {
  20649. m._isValid = m._isValid &&
  20650. m._pf.charsLeftOver === 0 &&
  20651. m._pf.unusedTokens.length === 0;
  20652. }
  20653. }
  20654. return m._isValid;
  20655. }
  20656. function normalizeLocale(key) {
  20657. return key ? key.toLowerCase().replace('_', '-') : key;
  20658. }
  20659. // pick the locale from the array
  20660. // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each
  20661. // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root
  20662. function chooseLocale(names) {
  20663. var i = 0, j, next, locale, split;
  20664. while (i < names.length) {
  20665. split = normalizeLocale(names[i]).split('-');
  20666. j = split.length;
  20667. next = normalizeLocale(names[i + 1]);
  20668. next = next ? next.split('-') : null;
  20669. while (j > 0) {
  20670. locale = loadLocale(split.slice(0, j).join('-'));
  20671. if (locale) {
  20672. return locale;
  20673. }
  20674. if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) {
  20675. //the next array item is better than a shallower substring of this one
  20676. break;
  20677. }
  20678. j--;
  20679. }
  20680. i++;
  20681. }
  20682. return null;
  20683. }
  20684. function loadLocale(name) {
  20685. var oldLocale = null;
  20686. if (!locales[name] && hasModule) {
  20687. try {
  20688. oldLocale = moment.locale();
  20689. !(function webpackMissingModule() { var e = new Error("Cannot find module \"./locale\""); e.code = 'MODULE_NOT_FOUND'; throw e; }());
  20690. // because defineLocale currently also sets the global locale, we want to undo that for lazy loaded locales
  20691. moment.locale(oldLocale);
  20692. } catch (e) { }
  20693. }
  20694. return locales[name];
  20695. }
  20696. // Return a moment from input, that is local/utc/zone equivalent to model.
  20697. function makeAs(input, model) {
  20698. return model._isUTC ? moment(input).zone(model._offset || 0) :
  20699. moment(input).local();
  20700. }
  20701. /************************************
  20702. Locale
  20703. ************************************/
  20704. extend(Locale.prototype, {
  20705. set : function (config) {
  20706. var prop, i;
  20707. for (i in config) {
  20708. prop = config[i];
  20709. if (typeof prop === 'function') {
  20710. this[i] = prop;
  20711. } else {
  20712. this['_' + i] = prop;
  20713. }
  20714. }
  20715. },
  20716. _months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'),
  20717. months : function (m) {
  20718. return this._months[m.month()];
  20719. },
  20720. _monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'),
  20721. monthsShort : function (m) {
  20722. return this._monthsShort[m.month()];
  20723. },
  20724. monthsParse : function (monthName) {
  20725. var i, mom, regex;
  20726. if (!this._monthsParse) {
  20727. this._monthsParse = [];
  20728. }
  20729. for (i = 0; i < 12; i++) {
  20730. // make the regex if we don't have it already
  20731. if (!this._monthsParse[i]) {
  20732. mom = moment.utc([2000, i]);
  20733. regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, '');
  20734. this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i');
  20735. }
  20736. // test the regex
  20737. if (this._monthsParse[i].test(monthName)) {
  20738. return i;
  20739. }
  20740. }
  20741. },
  20742. _weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'),
  20743. weekdays : function (m) {
  20744. return this._weekdays[m.day()];
  20745. },
  20746. _weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'),
  20747. weekdaysShort : function (m) {
  20748. return this._weekdaysShort[m.day()];
  20749. },
  20750. _weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'),
  20751. weekdaysMin : function (m) {
  20752. return this._weekdaysMin[m.day()];
  20753. },
  20754. weekdaysParse : function (weekdayName) {
  20755. var i, mom, regex;
  20756. if (!this._weekdaysParse) {
  20757. this._weekdaysParse = [];
  20758. }
  20759. for (i = 0; i < 7; i++) {
  20760. // make the regex if we don't have it already
  20761. if (!this._weekdaysParse[i]) {
  20762. mom = moment([2000, 1]).day(i);
  20763. regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, '');
  20764. this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i');
  20765. }
  20766. // test the regex
  20767. if (this._weekdaysParse[i].test(weekdayName)) {
  20768. return i;
  20769. }
  20770. }
  20771. },
  20772. _longDateFormat : {
  20773. LT : 'h:mm A',
  20774. L : 'MM/DD/YYYY',
  20775. LL : 'MMMM D, YYYY',
  20776. LLL : 'MMMM D, YYYY LT',
  20777. LLLL : 'dddd, MMMM D, YYYY LT'
  20778. },
  20779. longDateFormat : function (key) {
  20780. var output = this._longDateFormat[key];
  20781. if (!output && this._longDateFormat[key.toUpperCase()]) {
  20782. output = this._longDateFormat[key.toUpperCase()].replace(/MMMM|MM|DD|dddd/g, function (val) {
  20783. return val.slice(1);
  20784. });
  20785. this._longDateFormat[key] = output;
  20786. }
  20787. return output;
  20788. },
  20789. isPM : function (input) {
  20790. // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays
  20791. // Using charAt should be more compatible.
  20792. return ((input + '').toLowerCase().charAt(0) === 'p');
  20793. },
  20794. _meridiemParse : /[ap]\.?m?\.?/i,
  20795. meridiem : function (hours, minutes, isLower) {
  20796. if (hours > 11) {
  20797. return isLower ? 'pm' : 'PM';
  20798. } else {
  20799. return isLower ? 'am' : 'AM';
  20800. }
  20801. },
  20802. _calendar : {
  20803. sameDay : '[Today at] LT',
  20804. nextDay : '[Tomorrow at] LT',
  20805. nextWeek : 'dddd [at] LT',
  20806. lastDay : '[Yesterday at] LT',
  20807. lastWeek : '[Last] dddd [at] LT',
  20808. sameElse : 'L'
  20809. },
  20810. calendar : function (key, mom) {
  20811. var output = this._calendar[key];
  20812. return typeof output === 'function' ? output.apply(mom) : output;
  20813. },
  20814. _relativeTime : {
  20815. future : 'in %s',
  20816. past : '%s ago',
  20817. s : 'a few seconds',
  20818. m : 'a minute',
  20819. mm : '%d minutes',
  20820. h : 'an hour',
  20821. hh : '%d hours',
  20822. d : 'a day',
  20823. dd : '%d days',
  20824. M : 'a month',
  20825. MM : '%d months',
  20826. y : 'a year',
  20827. yy : '%d years'
  20828. },
  20829. relativeTime : function (number, withoutSuffix, string, isFuture) {
  20830. var output = this._relativeTime[string];
  20831. return (typeof output === 'function') ?
  20832. output(number, withoutSuffix, string, isFuture) :
  20833. output.replace(/%d/i, number);
  20834. },
  20835. pastFuture : function (diff, output) {
  20836. var format = this._relativeTime[diff > 0 ? 'future' : 'past'];
  20837. return typeof format === 'function' ? format(output) : format.replace(/%s/i, output);
  20838. },
  20839. ordinal : function (number) {
  20840. return this._ordinal.replace('%d', number);
  20841. },
  20842. _ordinal : '%d',
  20843. preparse : function (string) {
  20844. return string;
  20845. },
  20846. postformat : function (string) {
  20847. return string;
  20848. },
  20849. week : function (mom) {
  20850. return weekOfYear(mom, this._week.dow, this._week.doy).week;
  20851. },
  20852. _week : {
  20853. dow : 0, // Sunday is the first day of the week.
  20854. doy : 6 // The week that contains Jan 1st is the first week of the year.
  20855. },
  20856. _invalidDate: 'Invalid date',
  20857. invalidDate: function () {
  20858. return this._invalidDate;
  20859. }
  20860. });
  20861. /************************************
  20862. Formatting
  20863. ************************************/
  20864. function removeFormattingTokens(input) {
  20865. if (input.match(/\[[\s\S]/)) {
  20866. return input.replace(/^\[|\]$/g, '');
  20867. }
  20868. return input.replace(/\\/g, '');
  20869. }
  20870. function makeFormatFunction(format) {
  20871. var array = format.match(formattingTokens), i, length;
  20872. for (i = 0, length = array.length; i < length; i++) {
  20873. if (formatTokenFunctions[array[i]]) {
  20874. array[i] = formatTokenFunctions[array[i]];
  20875. } else {
  20876. array[i] = removeFormattingTokens(array[i]);
  20877. }
  20878. }
  20879. return function (mom) {
  20880. var output = '';
  20881. for (i = 0; i < length; i++) {
  20882. output += array[i] instanceof Function ? array[i].call(mom, format) : array[i];
  20883. }
  20884. return output;
  20885. };
  20886. }
  20887. // format date using native date object
  20888. function formatMoment(m, format) {
  20889. if (!m.isValid()) {
  20890. return m.localeData().invalidDate();
  20891. }
  20892. format = expandFormat(format, m.localeData());
  20893. if (!formatFunctions[format]) {
  20894. formatFunctions[format] = makeFormatFunction(format);
  20895. }
  20896. return formatFunctions[format](m);
  20897. }
  20898. function expandFormat(format, locale) {
  20899. var i = 5;
  20900. function replaceLongDateFormatTokens(input) {
  20901. return locale.longDateFormat(input) || input;
  20902. }
  20903. localFormattingTokens.lastIndex = 0;
  20904. while (i >= 0 && localFormattingTokens.test(format)) {
  20905. format = format.replace(localFormattingTokens, replaceLongDateFormatTokens);
  20906. localFormattingTokens.lastIndex = 0;
  20907. i -= 1;
  20908. }
  20909. return format;
  20910. }
  20911. /************************************
  20912. Parsing
  20913. ************************************/
  20914. // get the regex to find the next token
  20915. function getParseRegexForToken(token, config) {
  20916. var a, strict = config._strict;
  20917. switch (token) {
  20918. case 'Q':
  20919. return parseTokenOneDigit;
  20920. case 'DDDD':
  20921. return parseTokenThreeDigits;
  20922. case 'YYYY':
  20923. case 'GGGG':
  20924. case 'gggg':
  20925. return strict ? parseTokenFourDigits : parseTokenOneToFourDigits;
  20926. case 'Y':
  20927. case 'G':
  20928. case 'g':
  20929. return parseTokenSignedNumber;
  20930. case 'YYYYYY':
  20931. case 'YYYYY':
  20932. case 'GGGGG':
  20933. case 'ggggg':
  20934. return strict ? parseTokenSixDigits : parseTokenOneToSixDigits;
  20935. case 'S':
  20936. if (strict) {
  20937. return parseTokenOneDigit;
  20938. }
  20939. /* falls through */
  20940. case 'SS':
  20941. if (strict) {
  20942. return parseTokenTwoDigits;
  20943. }
  20944. /* falls through */
  20945. case 'SSS':
  20946. if (strict) {
  20947. return parseTokenThreeDigits;
  20948. }
  20949. /* falls through */
  20950. case 'DDD':
  20951. return parseTokenOneToThreeDigits;
  20952. case 'MMM':
  20953. case 'MMMM':
  20954. case 'dd':
  20955. case 'ddd':
  20956. case 'dddd':
  20957. return parseTokenWord;
  20958. case 'a':
  20959. case 'A':
  20960. return config._locale._meridiemParse;
  20961. case 'X':
  20962. return parseTokenTimestampMs;
  20963. case 'Z':
  20964. case 'ZZ':
  20965. return parseTokenTimezone;
  20966. case 'T':
  20967. return parseTokenT;
  20968. case 'SSSS':
  20969. return parseTokenDigits;
  20970. case 'MM':
  20971. case 'DD':
  20972. case 'YY':
  20973. case 'GG':
  20974. case 'gg':
  20975. case 'HH':
  20976. case 'hh':
  20977. case 'mm':
  20978. case 'ss':
  20979. case 'ww':
  20980. case 'WW':
  20981. return strict ? parseTokenTwoDigits : parseTokenOneOrTwoDigits;
  20982. case 'M':
  20983. case 'D':
  20984. case 'd':
  20985. case 'H':
  20986. case 'h':
  20987. case 'm':
  20988. case 's':
  20989. case 'w':
  20990. case 'W':
  20991. case 'e':
  20992. case 'E':
  20993. return parseTokenOneOrTwoDigits;
  20994. case 'Do':
  20995. return parseTokenOrdinal;
  20996. default :
  20997. a = new RegExp(regexpEscape(unescapeFormat(token.replace('\\', '')), 'i'));
  20998. return a;
  20999. }
  21000. }
  21001. function timezoneMinutesFromString(string) {
  21002. string = string || '';
  21003. var possibleTzMatches = (string.match(parseTokenTimezone) || []),
  21004. tzChunk = possibleTzMatches[possibleTzMatches.length - 1] || [],
  21005. parts = (tzChunk + '').match(parseTimezoneChunker) || ['-', 0, 0],
  21006. minutes = +(parts[1] * 60) + toInt(parts[2]);
  21007. return parts[0] === '+' ? -minutes : minutes;
  21008. }
  21009. // function to convert string input to date
  21010. function addTimeToArrayFromToken(token, input, config) {
  21011. var a, datePartArray = config._a;
  21012. switch (token) {
  21013. // QUARTER
  21014. case 'Q':
  21015. if (input != null) {
  21016. datePartArray[MONTH] = (toInt(input) - 1) * 3;
  21017. }
  21018. break;
  21019. // MONTH
  21020. case 'M' : // fall through to MM
  21021. case 'MM' :
  21022. if (input != null) {
  21023. datePartArray[MONTH] = toInt(input) - 1;
  21024. }
  21025. break;
  21026. case 'MMM' : // fall through to MMMM
  21027. case 'MMMM' :
  21028. a = config._locale.monthsParse(input);
  21029. // if we didn't find a month name, mark the date as invalid.
  21030. if (a != null) {
  21031. datePartArray[MONTH] = a;
  21032. } else {
  21033. config._pf.invalidMonth = input;
  21034. }
  21035. break;
  21036. // DAY OF MONTH
  21037. case 'D' : // fall through to DD
  21038. case 'DD' :
  21039. if (input != null) {
  21040. datePartArray[DATE] = toInt(input);
  21041. }
  21042. break;
  21043. case 'Do' :
  21044. if (input != null) {
  21045. datePartArray[DATE] = toInt(parseInt(input, 10));
  21046. }
  21047. break;
  21048. // DAY OF YEAR
  21049. case 'DDD' : // fall through to DDDD
  21050. case 'DDDD' :
  21051. if (input != null) {
  21052. config._dayOfYear = toInt(input);
  21053. }
  21054. break;
  21055. // YEAR
  21056. case 'YY' :
  21057. datePartArray[YEAR] = moment.parseTwoDigitYear(input);
  21058. break;
  21059. case 'YYYY' :
  21060. case 'YYYYY' :
  21061. case 'YYYYYY' :
  21062. datePartArray[YEAR] = toInt(input);
  21063. break;
  21064. // AM / PM
  21065. case 'a' : // fall through to A
  21066. case 'A' :
  21067. config._isPm = config._locale.isPM(input);
  21068. break;
  21069. // 24 HOUR
  21070. case 'H' : // fall through to hh
  21071. case 'HH' : // fall through to hh
  21072. case 'h' : // fall through to hh
  21073. case 'hh' :
  21074. datePartArray[HOUR] = toInt(input);
  21075. break;
  21076. // MINUTE
  21077. case 'm' : // fall through to mm
  21078. case 'mm' :
  21079. datePartArray[MINUTE] = toInt(input);
  21080. break;
  21081. // SECOND
  21082. case 's' : // fall through to ss
  21083. case 'ss' :
  21084. datePartArray[SECOND] = toInt(input);
  21085. break;
  21086. // MILLISECOND
  21087. case 'S' :
  21088. case 'SS' :
  21089. case 'SSS' :
  21090. case 'SSSS' :
  21091. datePartArray[MILLISECOND] = toInt(('0.' + input) * 1000);
  21092. break;
  21093. // UNIX TIMESTAMP WITH MS
  21094. case 'X':
  21095. config._d = new Date(parseFloat(input) * 1000);
  21096. break;
  21097. // TIMEZONE
  21098. case 'Z' : // fall through to ZZ
  21099. case 'ZZ' :
  21100. config._useUTC = true;
  21101. config._tzm = timezoneMinutesFromString(input);
  21102. break;
  21103. // WEEKDAY - human
  21104. case 'dd':
  21105. case 'ddd':
  21106. case 'dddd':
  21107. a = config._locale.weekdaysParse(input);
  21108. // if we didn't get a weekday name, mark the date as invalid
  21109. if (a != null) {
  21110. config._w = config._w || {};
  21111. config._w['d'] = a;
  21112. } else {
  21113. config._pf.invalidWeekday = input;
  21114. }
  21115. break;
  21116. // WEEK, WEEK DAY - numeric
  21117. case 'w':
  21118. case 'ww':
  21119. case 'W':
  21120. case 'WW':
  21121. case 'd':
  21122. case 'e':
  21123. case 'E':
  21124. token = token.substr(0, 1);
  21125. /* falls through */
  21126. case 'gggg':
  21127. case 'GGGG':
  21128. case 'GGGGG':
  21129. token = token.substr(0, 2);
  21130. if (input) {
  21131. config._w = config._w || {};
  21132. config._w[token] = toInt(input);
  21133. }
  21134. break;
  21135. case 'gg':
  21136. case 'GG':
  21137. config._w = config._w || {};
  21138. config._w[token] = moment.parseTwoDigitYear(input);
  21139. }
  21140. }
  21141. function dayOfYearFromWeekInfo(config) {
  21142. var w, weekYear, week, weekday, dow, doy, temp;
  21143. w = config._w;
  21144. if (w.GG != null || w.W != null || w.E != null) {
  21145. dow = 1;
  21146. doy = 4;
  21147. // TODO: We need to take the current isoWeekYear, but that depends on
  21148. // how we interpret now (local, utc, fixed offset). So create
  21149. // a now version of current config (take local/utc/offset flags, and
  21150. // create now).
  21151. weekYear = dfl(w.GG, config._a[YEAR], weekOfYear(moment(), 1, 4).year);
  21152. week = dfl(w.W, 1);
  21153. weekday = dfl(w.E, 1);
  21154. } else {
  21155. dow = config._locale._week.dow;
  21156. doy = config._locale._week.doy;
  21157. weekYear = dfl(w.gg, config._a[YEAR], weekOfYear(moment(), dow, doy).year);
  21158. week = dfl(w.w, 1);
  21159. if (w.d != null) {
  21160. // weekday -- low day numbers are considered next week
  21161. weekday = w.d;
  21162. if (weekday < dow) {
  21163. ++week;
  21164. }
  21165. } else if (w.e != null) {
  21166. // local weekday -- counting starts from begining of week
  21167. weekday = w.e + dow;
  21168. } else {
  21169. // default to begining of week
  21170. weekday = dow;
  21171. }
  21172. }
  21173. temp = dayOfYearFromWeeks(weekYear, week, weekday, doy, dow);
  21174. config._a[YEAR] = temp.year;
  21175. config._dayOfYear = temp.dayOfYear;
  21176. }
  21177. // convert an array to a date.
  21178. // the array should mirror the parameters below
  21179. // note: all values past the year are optional and will default to the lowest possible value.
  21180. // [year, month, day , hour, minute, second, millisecond]
  21181. function dateFromConfig(config) {
  21182. var i, date, input = [], currentDate, yearToUse;
  21183. if (config._d) {
  21184. return;
  21185. }
  21186. currentDate = currentDateArray(config);
  21187. //compute day of the year from weeks and weekdays
  21188. if (config._w && config._a[DATE] == null && config._a[MONTH] == null) {
  21189. dayOfYearFromWeekInfo(config);
  21190. }
  21191. //if the day of the year is set, figure out what it is
  21192. if (config._dayOfYear) {
  21193. yearToUse = dfl(config._a[YEAR], currentDate[YEAR]);
  21194. if (config._dayOfYear > daysInYear(yearToUse)) {
  21195. config._pf._overflowDayOfYear = true;
  21196. }
  21197. date = makeUTCDate(yearToUse, 0, config._dayOfYear);
  21198. config._a[MONTH] = date.getUTCMonth();
  21199. config._a[DATE] = date.getUTCDate();
  21200. }
  21201. // Default to current date.
  21202. // * if no year, month, day of month are given, default to today
  21203. // * if day of month is given, default month and year
  21204. // * if month is given, default only year
  21205. // * if year is given, don't default anything
  21206. for (i = 0; i < 3 && config._a[i] == null; ++i) {
  21207. config._a[i] = input[i] = currentDate[i];
  21208. }
  21209. // Zero out whatever was not defaulted, including time
  21210. for (; i < 7; i++) {
  21211. config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i];
  21212. }
  21213. config._d = (config._useUTC ? makeUTCDate : makeDate).apply(null, input);
  21214. // Apply timezone offset from input. The actual zone can be changed
  21215. // with parseZone.
  21216. if (config._tzm != null) {
  21217. config._d.setUTCMinutes(config._d.getUTCMinutes() + config._tzm);
  21218. }
  21219. }
  21220. function dateFromObject(config) {
  21221. var normalizedInput;
  21222. if (config._d) {
  21223. return;
  21224. }
  21225. normalizedInput = normalizeObjectUnits(config._i);
  21226. config._a = [
  21227. normalizedInput.year,
  21228. normalizedInput.month,
  21229. normalizedInput.day,
  21230. normalizedInput.hour,
  21231. normalizedInput.minute,
  21232. normalizedInput.second,
  21233. normalizedInput.millisecond
  21234. ];
  21235. dateFromConfig(config);
  21236. }
  21237. function currentDateArray(config) {
  21238. var now = new Date();
  21239. if (config._useUTC) {
  21240. return [
  21241. now.getUTCFullYear(),
  21242. now.getUTCMonth(),
  21243. now.getUTCDate()
  21244. ];
  21245. } else {
  21246. return [now.getFullYear(), now.getMonth(), now.getDate()];
  21247. }
  21248. }
  21249. // date from string and format string
  21250. function makeDateFromStringAndFormat(config) {
  21251. if (config._f === moment.ISO_8601) {
  21252. parseISO(config);
  21253. return;
  21254. }
  21255. config._a = [];
  21256. config._pf.empty = true;
  21257. // This array is used to make a Date, either with `new Date` or `Date.UTC`
  21258. var string = '' + config._i,
  21259. i, parsedInput, tokens, token, skipped,
  21260. stringLength = string.length,
  21261. totalParsedInputLength = 0;
  21262. tokens = expandFormat(config._f, config._locale).match(formattingTokens) || [];
  21263. for (i = 0; i < tokens.length; i++) {
  21264. token = tokens[i];
  21265. parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0];
  21266. if (parsedInput) {
  21267. skipped = string.substr(0, string.indexOf(parsedInput));
  21268. if (skipped.length > 0) {
  21269. config._pf.unusedInput.push(skipped);
  21270. }
  21271. string = string.slice(string.indexOf(parsedInput) + parsedInput.length);
  21272. totalParsedInputLength += parsedInput.length;
  21273. }
  21274. // don't parse if it's not a known token
  21275. if (formatTokenFunctions[token]) {
  21276. if (parsedInput) {
  21277. config._pf.empty = false;
  21278. }
  21279. else {
  21280. config._pf.unusedTokens.push(token);
  21281. }
  21282. addTimeToArrayFromToken(token, parsedInput, config);
  21283. }
  21284. else if (config._strict && !parsedInput) {
  21285. config._pf.unusedTokens.push(token);
  21286. }
  21287. }
  21288. // add remaining unparsed input length to the string
  21289. config._pf.charsLeftOver = stringLength - totalParsedInputLength;
  21290. if (string.length > 0) {
  21291. config._pf.unusedInput.push(string);
  21292. }
  21293. // handle am pm
  21294. if (config._isPm && config._a[HOUR] < 12) {
  21295. config._a[HOUR] += 12;
  21296. }
  21297. // if is 12 am, change hours to 0
  21298. if (config._isPm === false && config._a[HOUR] === 12) {
  21299. config._a[HOUR] = 0;
  21300. }
  21301. dateFromConfig(config);
  21302. checkOverflow(config);
  21303. }
  21304. function unescapeFormat(s) {
  21305. return s.replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) {
  21306. return p1 || p2 || p3 || p4;
  21307. });
  21308. }
  21309. // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript
  21310. function regexpEscape(s) {
  21311. return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
  21312. }
  21313. // date from string and array of format strings
  21314. function makeDateFromStringAndArray(config) {
  21315. var tempConfig,
  21316. bestMoment,
  21317. scoreToBeat,
  21318. i,
  21319. currentScore;
  21320. if (config._f.length === 0) {
  21321. config._pf.invalidFormat = true;
  21322. config._d = new Date(NaN);
  21323. return;
  21324. }
  21325. for (i = 0; i < config._f.length; i++) {
  21326. currentScore = 0;
  21327. tempConfig = copyConfig({}, config);
  21328. if (config._useUTC != null) {
  21329. tempConfig._useUTC = config._useUTC;
  21330. }
  21331. tempConfig._pf = defaultParsingFlags();
  21332. tempConfig._f = config._f[i];
  21333. makeDateFromStringAndFormat(tempConfig);
  21334. if (!isValid(tempConfig)) {
  21335. continue;
  21336. }
  21337. // if there is any input that was not parsed add a penalty for that format
  21338. currentScore += tempConfig._pf.charsLeftOver;
  21339. //or tokens
  21340. currentScore += tempConfig._pf.unusedTokens.length * 10;
  21341. tempConfig._pf.score = currentScore;
  21342. if (scoreToBeat == null || currentScore < scoreToBeat) {
  21343. scoreToBeat = currentScore;
  21344. bestMoment = tempConfig;
  21345. }
  21346. }
  21347. extend(config, bestMoment || tempConfig);
  21348. }
  21349. // date from iso format
  21350. function parseISO(config) {
  21351. var i, l,
  21352. string = config._i,
  21353. match = isoRegex.exec(string);
  21354. if (match) {
  21355. config._pf.iso = true;
  21356. for (i = 0, l = isoDates.length; i < l; i++) {
  21357. if (isoDates[i][1].exec(string)) {
  21358. // match[5] should be 'T' or undefined
  21359. config._f = isoDates[i][0] + (match[6] || ' ');
  21360. break;
  21361. }
  21362. }
  21363. for (i = 0, l = isoTimes.length; i < l; i++) {
  21364. if (isoTimes[i][1].exec(string)) {
  21365. config._f += isoTimes[i][0];
  21366. break;
  21367. }
  21368. }
  21369. if (string.match(parseTokenTimezone)) {
  21370. config._f += 'Z';
  21371. }
  21372. makeDateFromStringAndFormat(config);
  21373. } else {
  21374. config._isValid = false;
  21375. }
  21376. }
  21377. // date from iso format or fallback
  21378. function makeDateFromString(config) {
  21379. parseISO(config);
  21380. if (config._isValid === false) {
  21381. delete config._isValid;
  21382. moment.createFromInputFallback(config);
  21383. }
  21384. }
  21385. function map(arr, fn) {
  21386. var res = [], i;
  21387. for (i = 0; i < arr.length; ++i) {
  21388. res.push(fn(arr[i], i));
  21389. }
  21390. return res;
  21391. }
  21392. function makeDateFromInput(config) {
  21393. var input = config._i, matched;
  21394. if (input === undefined) {
  21395. config._d = new Date();
  21396. } else if (isDate(input)) {
  21397. config._d = new Date(+input);
  21398. } else if ((matched = aspNetJsonRegex.exec(input)) !== null) {
  21399. config._d = new Date(+matched[1]);
  21400. } else if (typeof input === 'string') {
  21401. makeDateFromString(config);
  21402. } else if (isArray(input)) {
  21403. config._a = map(input.slice(0), function (obj) {
  21404. return parseInt(obj, 10);
  21405. });
  21406. dateFromConfig(config);
  21407. } else if (typeof(input) === 'object') {
  21408. dateFromObject(config);
  21409. } else if (typeof(input) === 'number') {
  21410. // from milliseconds
  21411. config._d = new Date(input);
  21412. } else {
  21413. moment.createFromInputFallback(config);
  21414. }
  21415. }
  21416. function makeDate(y, m, d, h, M, s, ms) {
  21417. //can't just apply() to create a date:
  21418. //http://stackoverflow.com/questions/181348/instantiating-a-javascript-object-by-calling-prototype-constructor-apply
  21419. var date = new Date(y, m, d, h, M, s, ms);
  21420. //the date constructor doesn't accept years < 1970
  21421. if (y < 1970) {
  21422. date.setFullYear(y);
  21423. }
  21424. return date;
  21425. }
  21426. function makeUTCDate(y) {
  21427. var date = new Date(Date.UTC.apply(null, arguments));
  21428. if (y < 1970) {
  21429. date.setUTCFullYear(y);
  21430. }
  21431. return date;
  21432. }
  21433. function parseWeekday(input, locale) {
  21434. if (typeof input === 'string') {
  21435. if (!isNaN(input)) {
  21436. input = parseInt(input, 10);
  21437. }
  21438. else {
  21439. input = locale.weekdaysParse(input);
  21440. if (typeof input !== 'number') {
  21441. return null;
  21442. }
  21443. }
  21444. }
  21445. return input;
  21446. }
  21447. /************************************
  21448. Relative Time
  21449. ************************************/
  21450. // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize
  21451. function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) {
  21452. return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture);
  21453. }
  21454. function relativeTime(posNegDuration, withoutSuffix, locale) {
  21455. var duration = moment.duration(posNegDuration).abs(),
  21456. seconds = round(duration.as('s')),
  21457. minutes = round(duration.as('m')),
  21458. hours = round(duration.as('h')),
  21459. days = round(duration.as('d')),
  21460. months = round(duration.as('M')),
  21461. years = round(duration.as('y')),
  21462. args = seconds < relativeTimeThresholds.s && ['s', seconds] ||
  21463. minutes === 1 && ['m'] ||
  21464. minutes < relativeTimeThresholds.m && ['mm', minutes] ||
  21465. hours === 1 && ['h'] ||
  21466. hours < relativeTimeThresholds.h && ['hh', hours] ||
  21467. days === 1 && ['d'] ||
  21468. days < relativeTimeThresholds.d && ['dd', days] ||
  21469. months === 1 && ['M'] ||
  21470. months < relativeTimeThresholds.M && ['MM', months] ||
  21471. years === 1 && ['y'] || ['yy', years];
  21472. args[2] = withoutSuffix;
  21473. args[3] = +posNegDuration > 0;
  21474. args[4] = locale;
  21475. return substituteTimeAgo.apply({}, args);
  21476. }
  21477. /************************************
  21478. Week of Year
  21479. ************************************/
  21480. // firstDayOfWeek 0 = sun, 6 = sat
  21481. // the day of the week that starts the week
  21482. // (usually sunday or monday)
  21483. // firstDayOfWeekOfYear 0 = sun, 6 = sat
  21484. // the first week is the week that contains the first
  21485. // of this day of the week
  21486. // (eg. ISO weeks use thursday (4))
  21487. function weekOfYear(mom, firstDayOfWeek, firstDayOfWeekOfYear) {
  21488. var end = firstDayOfWeekOfYear - firstDayOfWeek,
  21489. daysToDayOfWeek = firstDayOfWeekOfYear - mom.day(),
  21490. adjustedMoment;
  21491. if (daysToDayOfWeek > end) {
  21492. daysToDayOfWeek -= 7;
  21493. }
  21494. if (daysToDayOfWeek < end - 7) {
  21495. daysToDayOfWeek += 7;
  21496. }
  21497. adjustedMoment = moment(mom).add(daysToDayOfWeek, 'd');
  21498. return {
  21499. week: Math.ceil(adjustedMoment.dayOfYear() / 7),
  21500. year: adjustedMoment.year()
  21501. };
  21502. }
  21503. //http://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday
  21504. function dayOfYearFromWeeks(year, week, weekday, firstDayOfWeekOfYear, firstDayOfWeek) {
  21505. var d = makeUTCDate(year, 0, 1).getUTCDay(), daysToAdd, dayOfYear;
  21506. d = d === 0 ? 7 : d;
  21507. weekday = weekday != null ? weekday : firstDayOfWeek;
  21508. daysToAdd = firstDayOfWeek - d + (d > firstDayOfWeekOfYear ? 7 : 0) - (d < firstDayOfWeek ? 7 : 0);
  21509. dayOfYear = 7 * (week - 1) + (weekday - firstDayOfWeek) + daysToAdd + 1;
  21510. return {
  21511. year: dayOfYear > 0 ? year : year - 1,
  21512. dayOfYear: dayOfYear > 0 ? dayOfYear : daysInYear(year - 1) + dayOfYear
  21513. };
  21514. }
  21515. /************************************
  21516. Top Level Functions
  21517. ************************************/
  21518. function makeMoment(config) {
  21519. var input = config._i,
  21520. format = config._f;
  21521. config._locale = config._locale || moment.localeData(config._l);
  21522. if (input === null || (format === undefined && input === '')) {
  21523. return moment.invalid({nullInput: true});
  21524. }
  21525. if (typeof input === 'string') {
  21526. config._i = input = config._locale.preparse(input);
  21527. }
  21528. if (moment.isMoment(input)) {
  21529. return new Moment(input, true);
  21530. } else if (format) {
  21531. if (isArray(format)) {
  21532. makeDateFromStringAndArray(config);
  21533. } else {
  21534. makeDateFromStringAndFormat(config);
  21535. }
  21536. } else {
  21537. makeDateFromInput(config);
  21538. }
  21539. return new Moment(config);
  21540. }
  21541. moment = function (input, format, locale, strict) {
  21542. var c;
  21543. if (typeof(locale) === 'boolean') {
  21544. strict = locale;
  21545. locale = undefined;
  21546. }
  21547. // object construction must be done this way.
  21548. // https://github.com/moment/moment/issues/1423
  21549. c = {};
  21550. c._isAMomentObject = true;
  21551. c._i = input;
  21552. c._f = format;
  21553. c._l = locale;
  21554. c._strict = strict;
  21555. c._isUTC = false;
  21556. c._pf = defaultParsingFlags();
  21557. return makeMoment(c);
  21558. };
  21559. moment.suppressDeprecationWarnings = false;
  21560. moment.createFromInputFallback = deprecate(
  21561. 'moment construction falls back to js Date. This is ' +
  21562. 'discouraged and will be removed in upcoming major ' +
  21563. 'release. Please refer to ' +
  21564. 'https://github.com/moment/moment/issues/1407 for more info.',
  21565. function (config) {
  21566. config._d = new Date(config._i);
  21567. }
  21568. );
  21569. // Pick a moment m from moments so that m[fn](other) is true for all
  21570. // other. This relies on the function fn to be transitive.
  21571. //
  21572. // moments should either be an array of moment objects or an array, whose
  21573. // first element is an array of moment objects.
  21574. function pickBy(fn, moments) {
  21575. var res, i;
  21576. if (moments.length === 1 && isArray(moments[0])) {
  21577. moments = moments[0];
  21578. }
  21579. if (!moments.length) {
  21580. return moment();
  21581. }
  21582. res = moments[0];
  21583. for (i = 1; i < moments.length; ++i) {
  21584. if (moments[i][fn](res)) {
  21585. res = moments[i];
  21586. }
  21587. }
  21588. return res;
  21589. }
  21590. moment.min = function () {
  21591. var args = [].slice.call(arguments, 0);
  21592. return pickBy('isBefore', args);
  21593. };
  21594. moment.max = function () {
  21595. var args = [].slice.call(arguments, 0);
  21596. return pickBy('isAfter', args);
  21597. };
  21598. // creating with utc
  21599. moment.utc = function (input, format, locale, strict) {
  21600. var c;
  21601. if (typeof(locale) === 'boolean') {
  21602. strict = locale;
  21603. locale = undefined;
  21604. }
  21605. // object construction must be done this way.
  21606. // https://github.com/moment/moment/issues/1423
  21607. c = {};
  21608. c._isAMomentObject = true;
  21609. c._useUTC = true;
  21610. c._isUTC = true;
  21611. c._l = locale;
  21612. c._i = input;
  21613. c._f = format;
  21614. c._strict = strict;
  21615. c._pf = defaultParsingFlags();
  21616. return makeMoment(c).utc();
  21617. };
  21618. // creating with unix timestamp (in seconds)
  21619. moment.unix = function (input) {
  21620. return moment(input * 1000);
  21621. };
  21622. // duration
  21623. moment.duration = function (input, key) {
  21624. var duration = input,
  21625. // matching against regexp is expensive, do it on demand
  21626. match = null,
  21627. sign,
  21628. ret,
  21629. parseIso,
  21630. diffRes;
  21631. if (moment.isDuration(input)) {
  21632. duration = {
  21633. ms: input._milliseconds,
  21634. d: input._days,
  21635. M: input._months
  21636. };
  21637. } else if (typeof input === 'number') {
  21638. duration = {};
  21639. if (key) {
  21640. duration[key] = input;
  21641. } else {
  21642. duration.milliseconds = input;
  21643. }
  21644. } else if (!!(match = aspNetTimeSpanJsonRegex.exec(input))) {
  21645. sign = (match[1] === '-') ? -1 : 1;
  21646. duration = {
  21647. y: 0,
  21648. d: toInt(match[DATE]) * sign,
  21649. h: toInt(match[HOUR]) * sign,
  21650. m: toInt(match[MINUTE]) * sign,
  21651. s: toInt(match[SECOND]) * sign,
  21652. ms: toInt(match[MILLISECOND]) * sign
  21653. };
  21654. } else if (!!(match = isoDurationRegex.exec(input))) {
  21655. sign = (match[1] === '-') ? -1 : 1;
  21656. parseIso = function (inp) {
  21657. // We'd normally use ~~inp for this, but unfortunately it also
  21658. // converts floats to ints.
  21659. // inp may be undefined, so careful calling replace on it.
  21660. var res = inp && parseFloat(inp.replace(',', '.'));
  21661. // apply sign while we're at it
  21662. return (isNaN(res) ? 0 : res) * sign;
  21663. };
  21664. duration = {
  21665. y: parseIso(match[2]),
  21666. M: parseIso(match[3]),
  21667. d: parseIso(match[4]),
  21668. h: parseIso(match[5]),
  21669. m: parseIso(match[6]),
  21670. s: parseIso(match[7]),
  21671. w: parseIso(match[8])
  21672. };
  21673. } else if (typeof duration === 'object' &&
  21674. ('from' in duration || 'to' in duration)) {
  21675. diffRes = momentsDifference(moment(duration.from), moment(duration.to));
  21676. duration = {};
  21677. duration.ms = diffRes.milliseconds;
  21678. duration.M = diffRes.months;
  21679. }
  21680. ret = new Duration(duration);
  21681. if (moment.isDuration(input) && hasOwnProp(input, '_locale')) {
  21682. ret._locale = input._locale;
  21683. }
  21684. return ret;
  21685. };
  21686. // version number
  21687. moment.version = VERSION;
  21688. // default format
  21689. moment.defaultFormat = isoFormat;
  21690. // constant that refers to the ISO standard
  21691. moment.ISO_8601 = function () {};
  21692. // Plugins that add properties should also add the key here (null value),
  21693. // so we can properly clone ourselves.
  21694. moment.momentProperties = momentProperties;
  21695. // This function will be called whenever a moment is mutated.
  21696. // It is intended to keep the offset in sync with the timezone.
  21697. moment.updateOffset = function () {};
  21698. // This function allows you to set a threshold for relative time strings
  21699. moment.relativeTimeThreshold = function (threshold, limit) {
  21700. if (relativeTimeThresholds[threshold] === undefined) {
  21701. return false;
  21702. }
  21703. if (limit === undefined) {
  21704. return relativeTimeThresholds[threshold];
  21705. }
  21706. relativeTimeThresholds[threshold] = limit;
  21707. return true;
  21708. };
  21709. moment.lang = deprecate(
  21710. 'moment.lang is deprecated. Use moment.locale instead.',
  21711. function (key, value) {
  21712. return moment.locale(key, value);
  21713. }
  21714. );
  21715. // This function will load locale and then set the global locale. If
  21716. // no arguments are passed in, it will simply return the current global
  21717. // locale key.
  21718. moment.locale = function (key, values) {
  21719. var data;
  21720. if (key) {
  21721. if (typeof(values) !== 'undefined') {
  21722. data = moment.defineLocale(key, values);
  21723. }
  21724. else {
  21725. data = moment.localeData(key);
  21726. }
  21727. if (data) {
  21728. moment.duration._locale = moment._locale = data;
  21729. }
  21730. }
  21731. return moment._locale._abbr;
  21732. };
  21733. moment.defineLocale = function (name, values) {
  21734. if (values !== null) {
  21735. values.abbr = name;
  21736. if (!locales[name]) {
  21737. locales[name] = new Locale();
  21738. }
  21739. locales[name].set(values);
  21740. // backwards compat for now: also set the locale
  21741. moment.locale(name);
  21742. return locales[name];
  21743. } else {
  21744. // useful for testing
  21745. delete locales[name];
  21746. return null;
  21747. }
  21748. };
  21749. moment.langData = deprecate(
  21750. 'moment.langData is deprecated. Use moment.localeData instead.',
  21751. function (key) {
  21752. return moment.localeData(key);
  21753. }
  21754. );
  21755. // returns locale data
  21756. moment.localeData = function (key) {
  21757. var locale;
  21758. if (key && key._locale && key._locale._abbr) {
  21759. key = key._locale._abbr;
  21760. }
  21761. if (!key) {
  21762. return moment._locale;
  21763. }
  21764. if (!isArray(key)) {
  21765. //short-circuit everything else
  21766. locale = loadLocale(key);
  21767. if (locale) {
  21768. return locale;
  21769. }
  21770. key = [key];
  21771. }
  21772. return chooseLocale(key);
  21773. };
  21774. // compare moment object
  21775. moment.isMoment = function (obj) {
  21776. return obj instanceof Moment ||
  21777. (obj != null && hasOwnProp(obj, '_isAMomentObject'));
  21778. };
  21779. // for typechecking Duration objects
  21780. moment.isDuration = function (obj) {
  21781. return obj instanceof Duration;
  21782. };
  21783. for (i = lists.length - 1; i >= 0; --i) {
  21784. makeList(lists[i]);
  21785. }
  21786. moment.normalizeUnits = function (units) {
  21787. return normalizeUnits(units);
  21788. };
  21789. moment.invalid = function (flags) {
  21790. var m = moment.utc(NaN);
  21791. if (flags != null) {
  21792. extend(m._pf, flags);
  21793. }
  21794. else {
  21795. m._pf.userInvalidated = true;
  21796. }
  21797. return m;
  21798. };
  21799. moment.parseZone = function () {
  21800. return moment.apply(null, arguments).parseZone();
  21801. };
  21802. moment.parseTwoDigitYear = function (input) {
  21803. return toInt(input) + (toInt(input) > 68 ? 1900 : 2000);
  21804. };
  21805. /************************************
  21806. Moment Prototype
  21807. ************************************/
  21808. extend(moment.fn = Moment.prototype, {
  21809. clone : function () {
  21810. return moment(this);
  21811. },
  21812. valueOf : function () {
  21813. return +this._d + ((this._offset || 0) * 60000);
  21814. },
  21815. unix : function () {
  21816. return Math.floor(+this / 1000);
  21817. },
  21818. toString : function () {
  21819. return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ');
  21820. },
  21821. toDate : function () {
  21822. return this._offset ? new Date(+this) : this._d;
  21823. },
  21824. toISOString : function () {
  21825. var m = moment(this).utc();
  21826. if (0 < m.year() && m.year() <= 9999) {
  21827. return formatMoment(m, 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  21828. } else {
  21829. return formatMoment(m, 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]');
  21830. }
  21831. },
  21832. toArray : function () {
  21833. var m = this;
  21834. return [
  21835. m.year(),
  21836. m.month(),
  21837. m.date(),
  21838. m.hours(),
  21839. m.minutes(),
  21840. m.seconds(),
  21841. m.milliseconds()
  21842. ];
  21843. },
  21844. isValid : function () {
  21845. return isValid(this);
  21846. },
  21847. isDSTShifted : function () {
  21848. if (this._a) {
  21849. return this.isValid() && compareArrays(this._a, (this._isUTC ? moment.utc(this._a) : moment(this._a)).toArray()) > 0;
  21850. }
  21851. return false;
  21852. },
  21853. parsingFlags : function () {
  21854. return extend({}, this._pf);
  21855. },
  21856. invalidAt: function () {
  21857. return this._pf.overflow;
  21858. },
  21859. utc : function (keepLocalTime) {
  21860. return this.zone(0, keepLocalTime);
  21861. },
  21862. local : function (keepLocalTime) {
  21863. if (this._isUTC) {
  21864. this.zone(0, keepLocalTime);
  21865. this._isUTC = false;
  21866. if (keepLocalTime) {
  21867. this.add(this._dateTzOffset(), 'm');
  21868. }
  21869. }
  21870. return this;
  21871. },
  21872. format : function (inputString) {
  21873. var output = formatMoment(this, inputString || moment.defaultFormat);
  21874. return this.localeData().postformat(output);
  21875. },
  21876. add : createAdder(1, 'add'),
  21877. subtract : createAdder(-1, 'subtract'),
  21878. diff : function (input, units, asFloat) {
  21879. var that = makeAs(input, this),
  21880. zoneDiff = (this.zone() - that.zone()) * 6e4,
  21881. diff, output, daysAdjust;
  21882. units = normalizeUnits(units);
  21883. if (units === 'year' || units === 'month') {
  21884. // average number of days in the months in the given dates
  21885. diff = (this.daysInMonth() + that.daysInMonth()) * 432e5; // 24 * 60 * 60 * 1000 / 2
  21886. // difference in months
  21887. output = ((this.year() - that.year()) * 12) + (this.month() - that.month());
  21888. // adjust by taking difference in days, average number of days
  21889. // and dst in the given months.
  21890. daysAdjust = (this - moment(this).startOf('month')) -
  21891. (that - moment(that).startOf('month'));
  21892. // same as above but with zones, to negate all dst
  21893. daysAdjust -= ((this.zone() - moment(this).startOf('month').zone()) -
  21894. (that.zone() - moment(that).startOf('month').zone())) * 6e4;
  21895. output += daysAdjust / diff;
  21896. if (units === 'year') {
  21897. output = output / 12;
  21898. }
  21899. } else {
  21900. diff = (this - that);
  21901. output = units === 'second' ? diff / 1e3 : // 1000
  21902. units === 'minute' ? diff / 6e4 : // 1000 * 60
  21903. units === 'hour' ? diff / 36e5 : // 1000 * 60 * 60
  21904. units === 'day' ? (diff - zoneDiff) / 864e5 : // 1000 * 60 * 60 * 24, negate dst
  21905. units === 'week' ? (diff - zoneDiff) / 6048e5 : // 1000 * 60 * 60 * 24 * 7, negate dst
  21906. diff;
  21907. }
  21908. return asFloat ? output : absRound(output);
  21909. },
  21910. from : function (time, withoutSuffix) {
  21911. return moment.duration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix);
  21912. },
  21913. fromNow : function (withoutSuffix) {
  21914. return this.from(moment(), withoutSuffix);
  21915. },
  21916. calendar : function (time) {
  21917. // We want to compare the start of today, vs this.
  21918. // Getting start-of-today depends on whether we're zone'd or not.
  21919. var now = time || moment(),
  21920. sod = makeAs(now, this).startOf('day'),
  21921. diff = this.diff(sod, 'days', true),
  21922. format = diff < -6 ? 'sameElse' :
  21923. diff < -1 ? 'lastWeek' :
  21924. diff < 0 ? 'lastDay' :
  21925. diff < 1 ? 'sameDay' :
  21926. diff < 2 ? 'nextDay' :
  21927. diff < 7 ? 'nextWeek' : 'sameElse';
  21928. return this.format(this.localeData().calendar(format, this));
  21929. },
  21930. isLeapYear : function () {
  21931. return isLeapYear(this.year());
  21932. },
  21933. isDST : function () {
  21934. return (this.zone() < this.clone().month(0).zone() ||
  21935. this.zone() < this.clone().month(5).zone());
  21936. },
  21937. day : function (input) {
  21938. var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay();
  21939. if (input != null) {
  21940. input = parseWeekday(input, this.localeData());
  21941. return this.add(input - day, 'd');
  21942. } else {
  21943. return day;
  21944. }
  21945. },
  21946. month : makeAccessor('Month', true),
  21947. startOf : function (units) {
  21948. units = normalizeUnits(units);
  21949. // the following switch intentionally omits break keywords
  21950. // to utilize falling through the cases.
  21951. switch (units) {
  21952. case 'year':
  21953. this.month(0);
  21954. /* falls through */
  21955. case 'quarter':
  21956. case 'month':
  21957. this.date(1);
  21958. /* falls through */
  21959. case 'week':
  21960. case 'isoWeek':
  21961. case 'day':
  21962. this.hours(0);
  21963. /* falls through */
  21964. case 'hour':
  21965. this.minutes(0);
  21966. /* falls through */
  21967. case 'minute':
  21968. this.seconds(0);
  21969. /* falls through */
  21970. case 'second':
  21971. this.milliseconds(0);
  21972. /* falls through */
  21973. }
  21974. // weeks are a special case
  21975. if (units === 'week') {
  21976. this.weekday(0);
  21977. } else if (units === 'isoWeek') {
  21978. this.isoWeekday(1);
  21979. }
  21980. // quarters are also special
  21981. if (units === 'quarter') {
  21982. this.month(Math.floor(this.month() / 3) * 3);
  21983. }
  21984. return this;
  21985. },
  21986. endOf: function (units) {
  21987. units = normalizeUnits(units);
  21988. return this.startOf(units).add(1, (units === 'isoWeek' ? 'week' : units)).subtract(1, 'ms');
  21989. },
  21990. isAfter: function (input, units) {
  21991. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  21992. if (units === 'millisecond') {
  21993. input = moment.isMoment(input) ? input : moment(input);
  21994. return +this > +input;
  21995. } else {
  21996. return +this.clone().startOf(units) > +moment(input).startOf(units);
  21997. }
  21998. },
  21999. isBefore: function (input, units) {
  22000. units = normalizeUnits(typeof units !== 'undefined' ? units : 'millisecond');
  22001. if (units === 'millisecond') {
  22002. input = moment.isMoment(input) ? input : moment(input);
  22003. return +this < +input;
  22004. } else {
  22005. return +this.clone().startOf(units) < +moment(input).startOf(units);
  22006. }
  22007. },
  22008. isSame: function (input, units) {
  22009. units = normalizeUnits(units || 'millisecond');
  22010. if (units === 'millisecond') {
  22011. input = moment.isMoment(input) ? input : moment(input);
  22012. return +this === +input;
  22013. } else {
  22014. return +this.clone().startOf(units) === +makeAs(input, this).startOf(units);
  22015. }
  22016. },
  22017. min: deprecate(
  22018. 'moment().min is deprecated, use moment.min instead. https://github.com/moment/moment/issues/1548',
  22019. function (other) {
  22020. other = moment.apply(null, arguments);
  22021. return other < this ? this : other;
  22022. }
  22023. ),
  22024. max: deprecate(
  22025. 'moment().max is deprecated, use moment.max instead. https://github.com/moment/moment/issues/1548',
  22026. function (other) {
  22027. other = moment.apply(null, arguments);
  22028. return other > this ? this : other;
  22029. }
  22030. ),
  22031. // keepLocalTime = true means only change the timezone, without
  22032. // affecting the local hour. So 5:31:26 +0300 --[zone(2, true)]-->
  22033. // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist int zone
  22034. // +0200, so we adjust the time as needed, to be valid.
  22035. //
  22036. // Keeping the time actually adds/subtracts (one hour)
  22037. // from the actual represented time. That is why we call updateOffset
  22038. // a second time. In case it wants us to change the offset again
  22039. // _changeInProgress == true case, then we have to adjust, because
  22040. // there is no such time in the given timezone.
  22041. zone : function (input, keepLocalTime) {
  22042. var offset = this._offset || 0,
  22043. localAdjust;
  22044. if (input != null) {
  22045. if (typeof input === 'string') {
  22046. input = timezoneMinutesFromString(input);
  22047. }
  22048. if (Math.abs(input) < 16) {
  22049. input = input * 60;
  22050. }
  22051. if (!this._isUTC && keepLocalTime) {
  22052. localAdjust = this._dateTzOffset();
  22053. }
  22054. this._offset = input;
  22055. this._isUTC = true;
  22056. if (localAdjust != null) {
  22057. this.subtract(localAdjust, 'm');
  22058. }
  22059. if (offset !== input) {
  22060. if (!keepLocalTime || this._changeInProgress) {
  22061. addOrSubtractDurationFromMoment(this,
  22062. moment.duration(offset - input, 'm'), 1, false);
  22063. } else if (!this._changeInProgress) {
  22064. this._changeInProgress = true;
  22065. moment.updateOffset(this, true);
  22066. this._changeInProgress = null;
  22067. }
  22068. }
  22069. } else {
  22070. return this._isUTC ? offset : this._dateTzOffset();
  22071. }
  22072. return this;
  22073. },
  22074. zoneAbbr : function () {
  22075. return this._isUTC ? 'UTC' : '';
  22076. },
  22077. zoneName : function () {
  22078. return this._isUTC ? 'Coordinated Universal Time' : '';
  22079. },
  22080. parseZone : function () {
  22081. if (this._tzm) {
  22082. this.zone(this._tzm);
  22083. } else if (typeof this._i === 'string') {
  22084. this.zone(this._i);
  22085. }
  22086. return this;
  22087. },
  22088. hasAlignedHourOffset : function (input) {
  22089. if (!input) {
  22090. input = 0;
  22091. }
  22092. else {
  22093. input = moment(input).zone();
  22094. }
  22095. return (this.zone() - input) % 60 === 0;
  22096. },
  22097. daysInMonth : function () {
  22098. return daysInMonth(this.year(), this.month());
  22099. },
  22100. dayOfYear : function (input) {
  22101. var dayOfYear = round((moment(this).startOf('day') - moment(this).startOf('year')) / 864e5) + 1;
  22102. return input == null ? dayOfYear : this.add((input - dayOfYear), 'd');
  22103. },
  22104. quarter : function (input) {
  22105. return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3);
  22106. },
  22107. weekYear : function (input) {
  22108. var year = weekOfYear(this, this.localeData()._week.dow, this.localeData()._week.doy).year;
  22109. return input == null ? year : this.add((input - year), 'y');
  22110. },
  22111. isoWeekYear : function (input) {
  22112. var year = weekOfYear(this, 1, 4).year;
  22113. return input == null ? year : this.add((input - year), 'y');
  22114. },
  22115. week : function (input) {
  22116. var week = this.localeData().week(this);
  22117. return input == null ? week : this.add((input - week) * 7, 'd');
  22118. },
  22119. isoWeek : function (input) {
  22120. var week = weekOfYear(this, 1, 4).week;
  22121. return input == null ? week : this.add((input - week) * 7, 'd');
  22122. },
  22123. weekday : function (input) {
  22124. var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7;
  22125. return input == null ? weekday : this.add(input - weekday, 'd');
  22126. },
  22127. isoWeekday : function (input) {
  22128. // behaves the same as moment#day except
  22129. // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6)
  22130. // as a setter, sunday should belong to the previous week.
  22131. return input == null ? this.day() || 7 : this.day(this.day() % 7 ? input : input - 7);
  22132. },
  22133. isoWeeksInYear : function () {
  22134. return weeksInYear(this.year(), 1, 4);
  22135. },
  22136. weeksInYear : function () {
  22137. var weekInfo = this.localeData()._week;
  22138. return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy);
  22139. },
  22140. get : function (units) {
  22141. units = normalizeUnits(units);
  22142. return this[units]();
  22143. },
  22144. set : function (units, value) {
  22145. units = normalizeUnits(units);
  22146. if (typeof this[units] === 'function') {
  22147. this[units](value);
  22148. }
  22149. return this;
  22150. },
  22151. // If passed a locale key, it will set the locale for this
  22152. // instance. Otherwise, it will return the locale configuration
  22153. // variables for this instance.
  22154. locale : function (key) {
  22155. var newLocaleData;
  22156. if (key === undefined) {
  22157. return this._locale._abbr;
  22158. } else {
  22159. newLocaleData = moment.localeData(key);
  22160. if (newLocaleData != null) {
  22161. this._locale = newLocaleData;
  22162. }
  22163. return this;
  22164. }
  22165. },
  22166. lang : deprecate(
  22167. 'moment().lang() is deprecated. Use moment().localeData() instead.',
  22168. function (key) {
  22169. if (key === undefined) {
  22170. return this.localeData();
  22171. } else {
  22172. return this.locale(key);
  22173. }
  22174. }
  22175. ),
  22176. localeData : function () {
  22177. return this._locale;
  22178. },
  22179. _dateTzOffset : function () {
  22180. // On Firefox.24 Date#getTimezoneOffset returns a floating point.
  22181. // https://github.com/moment/moment/pull/1871
  22182. return Math.round(this._d.getTimezoneOffset() / 15) * 15;
  22183. }
  22184. });
  22185. function rawMonthSetter(mom, value) {
  22186. var dayOfMonth;
  22187. // TODO: Move this out of here!
  22188. if (typeof value === 'string') {
  22189. value = mom.localeData().monthsParse(value);
  22190. // TODO: Another silent failure?
  22191. if (typeof value !== 'number') {
  22192. return mom;
  22193. }
  22194. }
  22195. dayOfMonth = Math.min(mom.date(),
  22196. daysInMonth(mom.year(), value));
  22197. mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth);
  22198. return mom;
  22199. }
  22200. function rawGetter(mom, unit) {
  22201. return mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]();
  22202. }
  22203. function rawSetter(mom, unit, value) {
  22204. if (unit === 'Month') {
  22205. return rawMonthSetter(mom, value);
  22206. } else {
  22207. return mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value);
  22208. }
  22209. }
  22210. function makeAccessor(unit, keepTime) {
  22211. return function (value) {
  22212. if (value != null) {
  22213. rawSetter(this, unit, value);
  22214. moment.updateOffset(this, keepTime);
  22215. return this;
  22216. } else {
  22217. return rawGetter(this, unit);
  22218. }
  22219. };
  22220. }
  22221. moment.fn.millisecond = moment.fn.milliseconds = makeAccessor('Milliseconds', false);
  22222. moment.fn.second = moment.fn.seconds = makeAccessor('Seconds', false);
  22223. moment.fn.minute = moment.fn.minutes = makeAccessor('Minutes', false);
  22224. // Setting the hour should keep the time, because the user explicitly
  22225. // specified which hour he wants. So trying to maintain the same hour (in
  22226. // a new timezone) makes sense. Adding/subtracting hours does not follow
  22227. // this rule.
  22228. moment.fn.hour = moment.fn.hours = makeAccessor('Hours', true);
  22229. // moment.fn.month is defined separately
  22230. moment.fn.date = makeAccessor('Date', true);
  22231. moment.fn.dates = deprecate('dates accessor is deprecated. Use date instead.', makeAccessor('Date', true));
  22232. moment.fn.year = makeAccessor('FullYear', true);
  22233. moment.fn.years = deprecate('years accessor is deprecated. Use year instead.', makeAccessor('FullYear', true));
  22234. // add plural methods
  22235. moment.fn.days = moment.fn.day;
  22236. moment.fn.months = moment.fn.month;
  22237. moment.fn.weeks = moment.fn.week;
  22238. moment.fn.isoWeeks = moment.fn.isoWeek;
  22239. moment.fn.quarters = moment.fn.quarter;
  22240. // add aliased format methods
  22241. moment.fn.toJSON = moment.fn.toISOString;
  22242. /************************************
  22243. Duration Prototype
  22244. ************************************/
  22245. function daysToYears (days) {
  22246. // 400 years have 146097 days (taking into account leap year rules)
  22247. return days * 400 / 146097;
  22248. }
  22249. function yearsToDays (years) {
  22250. // years * 365 + absRound(years / 4) -
  22251. // absRound(years / 100) + absRound(years / 400);
  22252. return years * 146097 / 400;
  22253. }
  22254. extend(moment.duration.fn = Duration.prototype, {
  22255. _bubble : function () {
  22256. var milliseconds = this._milliseconds,
  22257. days = this._days,
  22258. months = this._months,
  22259. data = this._data,
  22260. seconds, minutes, hours, years = 0;
  22261. // The following code bubbles up values, see the tests for
  22262. // examples of what that means.
  22263. data.milliseconds = milliseconds % 1000;
  22264. seconds = absRound(milliseconds / 1000);
  22265. data.seconds = seconds % 60;
  22266. minutes = absRound(seconds / 60);
  22267. data.minutes = minutes % 60;
  22268. hours = absRound(minutes / 60);
  22269. data.hours = hours % 24;
  22270. days += absRound(hours / 24);
  22271. // Accurately convert days to years, assume start from year 0.
  22272. years = absRound(daysToYears(days));
  22273. days -= absRound(yearsToDays(years));
  22274. // 30 days to a month
  22275. // TODO (iskren): Use anchor date (like 1st Jan) to compute this.
  22276. months += absRound(days / 30);
  22277. days %= 30;
  22278. // 12 months -> 1 year
  22279. years += absRound(months / 12);
  22280. months %= 12;
  22281. data.days = days;
  22282. data.months = months;
  22283. data.years = years;
  22284. },
  22285. abs : function () {
  22286. this._milliseconds = Math.abs(this._milliseconds);
  22287. this._days = Math.abs(this._days);
  22288. this._months = Math.abs(this._months);
  22289. this._data.milliseconds = Math.abs(this._data.milliseconds);
  22290. this._data.seconds = Math.abs(this._data.seconds);
  22291. this._data.minutes = Math.abs(this._data.minutes);
  22292. this._data.hours = Math.abs(this._data.hours);
  22293. this._data.months = Math.abs(this._data.months);
  22294. this._data.years = Math.abs(this._data.years);
  22295. return this;
  22296. },
  22297. weeks : function () {
  22298. return absRound(this.days() / 7);
  22299. },
  22300. valueOf : function () {
  22301. return this._milliseconds +
  22302. this._days * 864e5 +
  22303. (this._months % 12) * 2592e6 +
  22304. toInt(this._months / 12) * 31536e6;
  22305. },
  22306. humanize : function (withSuffix) {
  22307. var output = relativeTime(this, !withSuffix, this.localeData());
  22308. if (withSuffix) {
  22309. output = this.localeData().pastFuture(+this, output);
  22310. }
  22311. return this.localeData().postformat(output);
  22312. },
  22313. add : function (input, val) {
  22314. // supports only 2.0-style add(1, 's') or add(moment)
  22315. var dur = moment.duration(input, val);
  22316. this._milliseconds += dur._milliseconds;
  22317. this._days += dur._days;
  22318. this._months += dur._months;
  22319. this._bubble();
  22320. return this;
  22321. },
  22322. subtract : function (input, val) {
  22323. var dur = moment.duration(input, val);
  22324. this._milliseconds -= dur._milliseconds;
  22325. this._days -= dur._days;
  22326. this._months -= dur._months;
  22327. this._bubble();
  22328. return this;
  22329. },
  22330. get : function (units) {
  22331. units = normalizeUnits(units);
  22332. return this[units.toLowerCase() + 's']();
  22333. },
  22334. as : function (units) {
  22335. var days, months;
  22336. units = normalizeUnits(units);
  22337. if (units === 'month' || units === 'year') {
  22338. days = this._days + this._milliseconds / 864e5;
  22339. months = this._months + daysToYears(days) * 12;
  22340. return units === 'month' ? months : months / 12;
  22341. } else {
  22342. // handle milliseconds separately because of floating point math errors (issue #1867)
  22343. days = this._days + yearsToDays(this._months / 12);
  22344. switch (units) {
  22345. case 'week': return days / 7 + this._milliseconds / 6048e5;
  22346. case 'day': return days + this._milliseconds / 864e5;
  22347. case 'hour': return days * 24 + this._milliseconds / 36e5;
  22348. case 'minute': return days * 24 * 60 + this._milliseconds / 6e4;
  22349. case 'second': return days * 24 * 60 * 60 + this._milliseconds / 1000;
  22350. // Math.floor prevents floating point math errors here
  22351. case 'millisecond': return Math.floor(days * 24 * 60 * 60 * 1000) + this._milliseconds;
  22352. default: throw new Error('Unknown unit ' + units);
  22353. }
  22354. }
  22355. },
  22356. lang : moment.fn.lang,
  22357. locale : moment.fn.locale,
  22358. toIsoString : deprecate(
  22359. 'toIsoString() is deprecated. Please use toISOString() instead ' +
  22360. '(notice the capitals)',
  22361. function () {
  22362. return this.toISOString();
  22363. }
  22364. ),
  22365. toISOString : function () {
  22366. // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js
  22367. var years = Math.abs(this.years()),
  22368. months = Math.abs(this.months()),
  22369. days = Math.abs(this.days()),
  22370. hours = Math.abs(this.hours()),
  22371. minutes = Math.abs(this.minutes()),
  22372. seconds = Math.abs(this.seconds() + this.milliseconds() / 1000);
  22373. if (!this.asSeconds()) {
  22374. // this is the same as C#'s (Noda) and python (isodate)...
  22375. // but not other JS (goog.date)
  22376. return 'P0D';
  22377. }
  22378. return (this.asSeconds() < 0 ? '-' : '') +
  22379. 'P' +
  22380. (years ? years + 'Y' : '') +
  22381. (months ? months + 'M' : '') +
  22382. (days ? days + 'D' : '') +
  22383. ((hours || minutes || seconds) ? 'T' : '') +
  22384. (hours ? hours + 'H' : '') +
  22385. (minutes ? minutes + 'M' : '') +
  22386. (seconds ? seconds + 'S' : '');
  22387. },
  22388. localeData : function () {
  22389. return this._locale;
  22390. }
  22391. });
  22392. moment.duration.fn.toString = moment.duration.fn.toISOString;
  22393. function makeDurationGetter(name) {
  22394. moment.duration.fn[name] = function () {
  22395. return this._data[name];
  22396. };
  22397. }
  22398. for (i in unitMillisecondFactors) {
  22399. if (hasOwnProp(unitMillisecondFactors, i)) {
  22400. makeDurationGetter(i.toLowerCase());
  22401. }
  22402. }
  22403. moment.duration.fn.asMilliseconds = function () {
  22404. return this.as('ms');
  22405. };
  22406. moment.duration.fn.asSeconds = function () {
  22407. return this.as('s');
  22408. };
  22409. moment.duration.fn.asMinutes = function () {
  22410. return this.as('m');
  22411. };
  22412. moment.duration.fn.asHours = function () {
  22413. return this.as('h');
  22414. };
  22415. moment.duration.fn.asDays = function () {
  22416. return this.as('d');
  22417. };
  22418. moment.duration.fn.asWeeks = function () {
  22419. return this.as('weeks');
  22420. };
  22421. moment.duration.fn.asMonths = function () {
  22422. return this.as('M');
  22423. };
  22424. moment.duration.fn.asYears = function () {
  22425. return this.as('y');
  22426. };
  22427. /************************************
  22428. Default Locale
  22429. ************************************/
  22430. // Set default locale, other locale will inherit from English.
  22431. moment.locale('en', {
  22432. ordinal : function (number) {
  22433. var b = number % 10,
  22434. output = (toInt(number % 100 / 10) === 1) ? 'th' :
  22435. (b === 1) ? 'st' :
  22436. (b === 2) ? 'nd' :
  22437. (b === 3) ? 'rd' : 'th';
  22438. return number + output;
  22439. }
  22440. });
  22441. /* EMBED_LOCALES */
  22442. /************************************
  22443. Exposing Moment
  22444. ************************************/
  22445. function makeGlobal(shouldDeprecate) {
  22446. /*global ender:false */
  22447. if (typeof ender !== 'undefined') {
  22448. return;
  22449. }
  22450. oldGlobalMoment = globalScope.moment;
  22451. if (shouldDeprecate) {
  22452. globalScope.moment = deprecate(
  22453. 'Accessing Moment through the global scope is ' +
  22454. 'deprecated, and will be removed in an upcoming ' +
  22455. 'release.',
  22456. moment);
  22457. } else {
  22458. globalScope.moment = moment;
  22459. }
  22460. }
  22461. // CommonJS module is defined
  22462. if (hasModule) {
  22463. module.exports = moment;
  22464. } else if (true) {
  22465. !(__WEBPACK_AMD_DEFINE_RESULT__ = function (require, exports, module) {
  22466. if (module.config && module.config() && module.config().noGlobal === true) {
  22467. // release the global variable
  22468. globalScope.moment = oldGlobalMoment;
  22469. }
  22470. return moment;
  22471. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  22472. makeGlobal(true);
  22473. } else {
  22474. makeGlobal();
  22475. }
  22476. }).call(this);
  22477. /* WEBPACK VAR INJECTION */}.call(exports, (function() { return this; }()), __webpack_require__(68)(module)))
  22478. /***/ },
  22479. /* 55 */
  22480. /***/ function(module, exports, __webpack_require__) {
  22481. var __WEBPACK_AMD_DEFINE_RESULT__;/*! Hammer.JS - v1.1.3 - 2014-05-20
  22482. * http://eightmedia.github.io/hammer.js
  22483. *
  22484. * Copyright (c) 2014 Jorik Tangelder <j.tangelder@gmail.com>;
  22485. * Licensed under the MIT license */
  22486. (function(window, undefined) {
  22487. 'use strict';
  22488. /**
  22489. * @main
  22490. * @module hammer
  22491. *
  22492. * @class Hammer
  22493. * @static
  22494. */
  22495. /**
  22496. * Hammer, use this to create instances
  22497. * ````
  22498. * var hammertime = new Hammer(myElement);
  22499. * ````
  22500. *
  22501. * @method Hammer
  22502. * @param {HTMLElement} element
  22503. * @param {Object} [options={}]
  22504. * @return {Hammer.Instance}
  22505. */
  22506. var Hammer = function Hammer(element, options) {
  22507. return new Hammer.Instance(element, options || {});
  22508. };
  22509. /**
  22510. * version, as defined in package.json
  22511. * the value will be set at each build
  22512. * @property VERSION
  22513. * @final
  22514. * @type {String}
  22515. */
  22516. Hammer.VERSION = '1.1.3';
  22517. /**
  22518. * default settings.
  22519. * more settings are defined per gesture at `/gestures`. Each gesture can be disabled/enabled
  22520. * by setting it's name (like `swipe`) to false.
  22521. * You can set the defaults for all instances by changing this object before creating an instance.
  22522. * @example
  22523. * ````
  22524. * Hammer.defaults.drag = false;
  22525. * Hammer.defaults.behavior.touchAction = 'pan-y';
  22526. * delete Hammer.defaults.behavior.userSelect;
  22527. * ````
  22528. * @property defaults
  22529. * @type {Object}
  22530. */
  22531. Hammer.defaults = {
  22532. /**
  22533. * this setting object adds styles and attributes to the element to prevent the browser from doing
  22534. * its native behavior. The css properties are auto prefixed for the browsers when needed.
  22535. * @property defaults.behavior
  22536. * @type {Object}
  22537. */
  22538. behavior: {
  22539. /**
  22540. * Disables text selection to improve the dragging gesture. When the value is `none` it also sets
  22541. * `onselectstart=false` for IE on the element. Mainly for desktop browsers.
  22542. * @property defaults.behavior.userSelect
  22543. * @type {String}
  22544. * @default 'none'
  22545. */
  22546. userSelect: 'none',
  22547. /**
  22548. * Specifies whether and how a given region can be manipulated by the user (for instance, by panning or zooming).
  22549. * Used by Chrome 35> and IE10>. By default this makes the element blocking any touch event.
  22550. * @property defaults.behavior.touchAction
  22551. * @type {String}
  22552. * @default: 'pan-y'
  22553. */
  22554. touchAction: 'pan-y',
  22555. /**
  22556. * Disables the default callout shown when you touch and hold a touch target.
  22557. * On iOS, when you touch and hold a touch target such as a link, Safari displays
  22558. * a callout containing information about the link. This property allows you to disable that callout.
  22559. * @property defaults.behavior.touchCallout
  22560. * @type {String}
  22561. * @default 'none'
  22562. */
  22563. touchCallout: 'none',
  22564. /**
  22565. * Specifies whether zooming is enabled. Used by IE10>
  22566. * @property defaults.behavior.contentZooming
  22567. * @type {String}
  22568. * @default 'none'
  22569. */
  22570. contentZooming: 'none',
  22571. /**
  22572. * Specifies that an entire element should be draggable instead of its contents.
  22573. * Mainly for desktop browsers.
  22574. * @property defaults.behavior.userDrag
  22575. * @type {String}
  22576. * @default 'none'
  22577. */
  22578. userDrag: 'none',
  22579. /**
  22580. * Overrides the highlight color shown when the user taps a link or a JavaScript
  22581. * clickable element in Safari on iPhone. This property obeys the alpha value, if specified.
  22582. *
  22583. * If you don't specify an alpha value, Safari on iPhone applies a default alpha value
  22584. * to the color. To disable tap highlighting, set the alpha value to 0 (invisible).
  22585. * If you set the alpha value to 1.0 (opaque), the element is not visible when tapped.
  22586. * @property defaults.behavior.tapHighlightColor
  22587. * @type {String}
  22588. * @default 'rgba(0,0,0,0)'
  22589. */
  22590. tapHighlightColor: 'rgba(0,0,0,0)'
  22591. }
  22592. };
  22593. /**
  22594. * hammer document where the base events are added at
  22595. * @property DOCUMENT
  22596. * @type {HTMLElement}
  22597. * @default window.document
  22598. */
  22599. Hammer.DOCUMENT = document;
  22600. /**
  22601. * detect support for pointer events
  22602. * @property HAS_POINTEREVENTS
  22603. * @type {Boolean}
  22604. */
  22605. Hammer.HAS_POINTEREVENTS = navigator.pointerEnabled || navigator.msPointerEnabled;
  22606. /**
  22607. * detect support for touch events
  22608. * @property HAS_TOUCHEVENTS
  22609. * @type {Boolean}
  22610. */
  22611. Hammer.HAS_TOUCHEVENTS = ('ontouchstart' in window);
  22612. /**
  22613. * detect mobile browsers
  22614. * @property IS_MOBILE
  22615. * @type {Boolean}
  22616. */
  22617. Hammer.IS_MOBILE = /mobile|tablet|ip(ad|hone|od)|android|silk/i.test(navigator.userAgent);
  22618. /**
  22619. * detect if we want to support mouseevents at all
  22620. * @property NO_MOUSEEVENTS
  22621. * @type {Boolean}
  22622. */
  22623. Hammer.NO_MOUSEEVENTS = (Hammer.HAS_TOUCHEVENTS && Hammer.IS_MOBILE) || Hammer.HAS_POINTEREVENTS;
  22624. /**
  22625. * interval in which Hammer recalculates current velocity/direction/angle in ms
  22626. * @property CALCULATE_INTERVAL
  22627. * @type {Number}
  22628. * @default 25
  22629. */
  22630. Hammer.CALCULATE_INTERVAL = 25;
  22631. /**
  22632. * eventtypes per touchevent (start, move, end) are filled by `Event.determineEventTypes` on `setup`
  22633. * the object contains the DOM event names per type (`EVENT_START`, `EVENT_MOVE`, `EVENT_END`)
  22634. * @property EVENT_TYPES
  22635. * @private
  22636. * @writeOnce
  22637. * @type {Object}
  22638. */
  22639. var EVENT_TYPES = {};
  22640. /**
  22641. * direction strings, for safe comparisons
  22642. * @property DIRECTION_DOWN|LEFT|UP|RIGHT
  22643. * @final
  22644. * @type {String}
  22645. * @default 'down' 'left' 'up' 'right'
  22646. */
  22647. var DIRECTION_DOWN = Hammer.DIRECTION_DOWN = 'down';
  22648. var DIRECTION_LEFT = Hammer.DIRECTION_LEFT = 'left';
  22649. var DIRECTION_UP = Hammer.DIRECTION_UP = 'up';
  22650. var DIRECTION_RIGHT = Hammer.DIRECTION_RIGHT = 'right';
  22651. /**
  22652. * pointertype strings, for safe comparisons
  22653. * @property POINTER_MOUSE|TOUCH|PEN
  22654. * @final
  22655. * @type {String}
  22656. * @default 'mouse' 'touch' 'pen'
  22657. */
  22658. var POINTER_MOUSE = Hammer.POINTER_MOUSE = 'mouse';
  22659. var POINTER_TOUCH = Hammer.POINTER_TOUCH = 'touch';
  22660. var POINTER_PEN = Hammer.POINTER_PEN = 'pen';
  22661. /**
  22662. * eventtypes
  22663. * @property EVENT_START|MOVE|END|RELEASE|TOUCH
  22664. * @final
  22665. * @type {String}
  22666. * @default 'start' 'change' 'move' 'end' 'release' 'touch'
  22667. */
  22668. var EVENT_START = Hammer.EVENT_START = 'start';
  22669. var EVENT_MOVE = Hammer.EVENT_MOVE = 'move';
  22670. var EVENT_END = Hammer.EVENT_END = 'end';
  22671. var EVENT_RELEASE = Hammer.EVENT_RELEASE = 'release';
  22672. var EVENT_TOUCH = Hammer.EVENT_TOUCH = 'touch';
  22673. /**
  22674. * if the window events are set...
  22675. * @property READY
  22676. * @writeOnce
  22677. * @type {Boolean}
  22678. * @default false
  22679. */
  22680. Hammer.READY = false;
  22681. /**
  22682. * plugins namespace
  22683. * @property plugins
  22684. * @type {Object}
  22685. */
  22686. Hammer.plugins = Hammer.plugins || {};
  22687. /**
  22688. * gestures namespace
  22689. * see `/gestures` for the definitions
  22690. * @property gestures
  22691. * @type {Object}
  22692. */
  22693. Hammer.gestures = Hammer.gestures || {};
  22694. /**
  22695. * setup events to detect gestures on the document
  22696. * this function is called when creating an new instance
  22697. * @private
  22698. */
  22699. function setup() {
  22700. if(Hammer.READY) {
  22701. return;
  22702. }
  22703. // find what eventtypes we add listeners to
  22704. Event.determineEventTypes();
  22705. // Register all gestures inside Hammer.gestures
  22706. Utils.each(Hammer.gestures, function(gesture) {
  22707. Detection.register(gesture);
  22708. });
  22709. // Add touch events on the document
  22710. Event.onTouch(Hammer.DOCUMENT, EVENT_MOVE, Detection.detect);
  22711. Event.onTouch(Hammer.DOCUMENT, EVENT_END, Detection.detect);
  22712. // Hammer is ready...!
  22713. Hammer.READY = true;
  22714. }
  22715. /**
  22716. * @module hammer
  22717. *
  22718. * @class Utils
  22719. * @static
  22720. */
  22721. var Utils = Hammer.utils = {
  22722. /**
  22723. * extend method, could also be used for cloning when `dest` is an empty object.
  22724. * changes the dest object
  22725. * @method extend
  22726. * @param {Object} dest
  22727. * @param {Object} src
  22728. * @param {Boolean} [merge=false] do a merge
  22729. * @return {Object} dest
  22730. */
  22731. extend: function extend(dest, src, merge) {
  22732. for(var key in src) {
  22733. if(!src.hasOwnProperty(key) || (dest[key] !== undefined && merge)) {
  22734. continue;
  22735. }
  22736. dest[key] = src[key];
  22737. }
  22738. return dest;
  22739. },
  22740. /**
  22741. * simple addEventListener wrapper
  22742. * @method on
  22743. * @param {HTMLElement} element
  22744. * @param {String} type
  22745. * @param {Function} handler
  22746. */
  22747. on: function on(element, type, handler) {
  22748. element.addEventListener(type, handler, false);
  22749. },
  22750. /**
  22751. * simple removeEventListener wrapper
  22752. * @method off
  22753. * @param {HTMLElement} element
  22754. * @param {String} type
  22755. * @param {Function} handler
  22756. */
  22757. off: function off(element, type, handler) {
  22758. element.removeEventListener(type, handler, false);
  22759. },
  22760. /**
  22761. * forEach over arrays and objects
  22762. * @method each
  22763. * @param {Object|Array} obj
  22764. * @param {Function} iterator
  22765. * @param {any} iterator.item
  22766. * @param {Number} iterator.index
  22767. * @param {Object|Array} iterator.obj the source object
  22768. * @param {Object} context value to use as `this` in the iterator
  22769. */
  22770. each: function each(obj, iterator, context) {
  22771. var i, len;
  22772. // native forEach on arrays
  22773. if('forEach' in obj) {
  22774. obj.forEach(iterator, context);
  22775. // arrays
  22776. } else if(obj.length !== undefined) {
  22777. for(i = 0, len = obj.length; i < len; i++) {
  22778. if(iterator.call(context, obj[i], i, obj) === false) {
  22779. return;
  22780. }
  22781. }
  22782. // objects
  22783. } else {
  22784. for(i in obj) {
  22785. if(obj.hasOwnProperty(i) &&
  22786. iterator.call(context, obj[i], i, obj) === false) {
  22787. return;
  22788. }
  22789. }
  22790. }
  22791. },
  22792. /**
  22793. * find if a string contains the string using indexOf
  22794. * @method inStr
  22795. * @param {String} src
  22796. * @param {String} find
  22797. * @return {Boolean} found
  22798. */
  22799. inStr: function inStr(src, find) {
  22800. return src.indexOf(find) > -1;
  22801. },
  22802. /**
  22803. * find if a array contains the object using indexOf or a simple polyfill
  22804. * @method inArray
  22805. * @param {String} src
  22806. * @param {String} find
  22807. * @return {Boolean|Number} false when not found, or the index
  22808. */
  22809. inArray: function inArray(src, find) {
  22810. if(src.indexOf) {
  22811. var index = src.indexOf(find);
  22812. return (index === -1) ? false : index;
  22813. } else {
  22814. for(var i = 0, len = src.length; i < len; i++) {
  22815. if(src[i] === find) {
  22816. return i;
  22817. }
  22818. }
  22819. return false;
  22820. }
  22821. },
  22822. /**
  22823. * convert an array-like object (`arguments`, `touchlist`) to an array
  22824. * @method toArray
  22825. * @param {Object} obj
  22826. * @return {Array}
  22827. */
  22828. toArray: function toArray(obj) {
  22829. return Array.prototype.slice.call(obj, 0);
  22830. },
  22831. /**
  22832. * find if a node is in the given parent
  22833. * @method hasParent
  22834. * @param {HTMLElement} node
  22835. * @param {HTMLElement} parent
  22836. * @return {Boolean} found
  22837. */
  22838. hasParent: function hasParent(node, parent) {
  22839. while(node) {
  22840. if(node == parent) {
  22841. return true;
  22842. }
  22843. node = node.parentNode;
  22844. }
  22845. return false;
  22846. },
  22847. /**
  22848. * get the center of all the touches
  22849. * @method getCenter
  22850. * @param {Array} touches
  22851. * @return {Object} center contains `pageX`, `pageY`, `clientX` and `clientY` properties
  22852. */
  22853. getCenter: function getCenter(touches) {
  22854. var pageX = [],
  22855. pageY = [],
  22856. clientX = [],
  22857. clientY = [],
  22858. min = Math.min,
  22859. max = Math.max;
  22860. // no need to loop when only one touch
  22861. if(touches.length === 1) {
  22862. return {
  22863. pageX: touches[0].pageX,
  22864. pageY: touches[0].pageY,
  22865. clientX: touches[0].clientX,
  22866. clientY: touches[0].clientY
  22867. };
  22868. }
  22869. Utils.each(touches, function(touch) {
  22870. pageX.push(touch.pageX);
  22871. pageY.push(touch.pageY);
  22872. clientX.push(touch.clientX);
  22873. clientY.push(touch.clientY);
  22874. });
  22875. return {
  22876. pageX: (min.apply(Math, pageX) + max.apply(Math, pageX)) / 2,
  22877. pageY: (min.apply(Math, pageY) + max.apply(Math, pageY)) / 2,
  22878. clientX: (min.apply(Math, clientX) + max.apply(Math, clientX)) / 2,
  22879. clientY: (min.apply(Math, clientY) + max.apply(Math, clientY)) / 2
  22880. };
  22881. },
  22882. /**
  22883. * calculate the velocity between two points. unit is in px per ms.
  22884. * @method getVelocity
  22885. * @param {Number} deltaTime
  22886. * @param {Number} deltaX
  22887. * @param {Number} deltaY
  22888. * @return {Object} velocity `x` and `y`
  22889. */
  22890. getVelocity: function getVelocity(deltaTime, deltaX, deltaY) {
  22891. return {
  22892. x: Math.abs(deltaX / deltaTime) || 0,
  22893. y: Math.abs(deltaY / deltaTime) || 0
  22894. };
  22895. },
  22896. /**
  22897. * calculate the angle between two coordinates
  22898. * @method getAngle
  22899. * @param {Touch} touch1
  22900. * @param {Touch} touch2
  22901. * @return {Number} angle
  22902. */
  22903. getAngle: function getAngle(touch1, touch2) {
  22904. var x = touch2.clientX - touch1.clientX,
  22905. y = touch2.clientY - touch1.clientY;
  22906. return Math.atan2(y, x) * 180 / Math.PI;
  22907. },
  22908. /**
  22909. * do a small comparision to get the direction between two touches.
  22910. * @method getDirection
  22911. * @param {Touch} touch1
  22912. * @param {Touch} touch2
  22913. * @return {String} direction matches `DIRECTION_LEFT|RIGHT|UP|DOWN`
  22914. */
  22915. getDirection: function getDirection(touch1, touch2) {
  22916. var x = Math.abs(touch1.clientX - touch2.clientX),
  22917. y = Math.abs(touch1.clientY - touch2.clientY);
  22918. if(x >= y) {
  22919. return touch1.clientX - touch2.clientX > 0 ? DIRECTION_LEFT : DIRECTION_RIGHT;
  22920. }
  22921. return touch1.clientY - touch2.clientY > 0 ? DIRECTION_UP : DIRECTION_DOWN;
  22922. },
  22923. /**
  22924. * calculate the distance between two touches
  22925. * @method getDistance
  22926. * @param {Touch}touch1
  22927. * @param {Touch} touch2
  22928. * @return {Number} distance
  22929. */
  22930. getDistance: function getDistance(touch1, touch2) {
  22931. var x = touch2.clientX - touch1.clientX,
  22932. y = touch2.clientY - touch1.clientY;
  22933. return Math.sqrt((x * x) + (y * y));
  22934. },
  22935. /**
  22936. * calculate the scale factor between two touchLists
  22937. * no scale is 1, and goes down to 0 when pinched together, and bigger when pinched out
  22938. * @method getScale
  22939. * @param {Array} start array of touches
  22940. * @param {Array} end array of touches
  22941. * @return {Number} scale
  22942. */
  22943. getScale: function getScale(start, end) {
  22944. // need two fingers...
  22945. if(start.length >= 2 && end.length >= 2) {
  22946. return this.getDistance(end[0], end[1]) / this.getDistance(start[0], start[1]);
  22947. }
  22948. return 1;
  22949. },
  22950. /**
  22951. * calculate the rotation degrees between two touchLists
  22952. * @method getRotation
  22953. * @param {Array} start array of touches
  22954. * @param {Array} end array of touches
  22955. * @return {Number} rotation
  22956. */
  22957. getRotation: function getRotation(start, end) {
  22958. // need two fingers
  22959. if(start.length >= 2 && end.length >= 2) {
  22960. return this.getAngle(end[1], end[0]) - this.getAngle(start[1], start[0]);
  22961. }
  22962. return 0;
  22963. },
  22964. /**
  22965. * find out if the direction is vertical *
  22966. * @method isVertical
  22967. * @param {String} direction matches `DIRECTION_UP|DOWN`
  22968. * @return {Boolean} is_vertical
  22969. */
  22970. isVertical: function isVertical(direction) {
  22971. return direction == DIRECTION_UP || direction == DIRECTION_DOWN;
  22972. },
  22973. /**
  22974. * set css properties with their prefixes
  22975. * @param {HTMLElement} element
  22976. * @param {String} prop
  22977. * @param {String} value
  22978. * @param {Boolean} [toggle=true]
  22979. * @return {Boolean}
  22980. */
  22981. setPrefixedCss: function setPrefixedCss(element, prop, value, toggle) {
  22982. var prefixes = ['', 'Webkit', 'Moz', 'O', 'ms'];
  22983. prop = Utils.toCamelCase(prop);
  22984. for(var i = 0; i < prefixes.length; i++) {
  22985. var p = prop;
  22986. // prefixes
  22987. if(prefixes[i]) {
  22988. p = prefixes[i] + p.slice(0, 1).toUpperCase() + p.slice(1);
  22989. }
  22990. // test the style
  22991. if(p in element.style) {
  22992. element.style[p] = (toggle == null || toggle) && value || '';
  22993. break;
  22994. }
  22995. }
  22996. },
  22997. /**
  22998. * toggle browser default behavior by setting css properties.
  22999. * `userSelect='none'` also sets `element.onselectstart` to false
  23000. * `userDrag='none'` also sets `element.ondragstart` to false
  23001. *
  23002. * @method toggleBehavior
  23003. * @param {HtmlElement} element
  23004. * @param {Object} props
  23005. * @param {Boolean} [toggle=true]
  23006. */
  23007. toggleBehavior: function toggleBehavior(element, props, toggle) {
  23008. if(!props || !element || !element.style) {
  23009. return;
  23010. }
  23011. // set the css properties
  23012. Utils.each(props, function(value, prop) {
  23013. Utils.setPrefixedCss(element, prop, value, toggle);
  23014. });
  23015. var falseFn = toggle && function() {
  23016. return false;
  23017. };
  23018. // also the disable onselectstart
  23019. if(props.userSelect == 'none') {
  23020. element.onselectstart = falseFn;
  23021. }
  23022. // and disable ondragstart
  23023. if(props.userDrag == 'none') {
  23024. element.ondragstart = falseFn;
  23025. }
  23026. },
  23027. /**
  23028. * convert a string with underscores to camelCase
  23029. * so prevent_default becomes preventDefault
  23030. * @param {String} str
  23031. * @return {String} camelCaseStr
  23032. */
  23033. toCamelCase: function toCamelCase(str) {
  23034. return str.replace(/[_-]([a-z])/g, function(s) {
  23035. return s[1].toUpperCase();
  23036. });
  23037. }
  23038. };
  23039. /**
  23040. * @module hammer
  23041. */
  23042. /**
  23043. * @class Event
  23044. * @static
  23045. */
  23046. var Event = Hammer.event = {
  23047. /**
  23048. * when touch events have been fired, this is true
  23049. * this is used to stop mouse events
  23050. * @property prevent_mouseevents
  23051. * @private
  23052. * @type {Boolean}
  23053. */
  23054. preventMouseEvents: false,
  23055. /**
  23056. * if EVENT_START has been fired
  23057. * @property started
  23058. * @private
  23059. * @type {Boolean}
  23060. */
  23061. started: false,
  23062. /**
  23063. * when the mouse is hold down, this is true
  23064. * @property should_detect
  23065. * @private
  23066. * @type {Boolean}
  23067. */
  23068. shouldDetect: false,
  23069. /**
  23070. * simple event binder with a hook and support for multiple types
  23071. * @method on
  23072. * @param {HTMLElement} element
  23073. * @param {String} type
  23074. * @param {Function} handler
  23075. * @param {Function} [hook]
  23076. * @param {Object} hook.type
  23077. */
  23078. on: function on(element, type, handler, hook) {
  23079. var types = type.split(' ');
  23080. Utils.each(types, function(type) {
  23081. Utils.on(element, type, handler);
  23082. hook && hook(type);
  23083. });
  23084. },
  23085. /**
  23086. * simple event unbinder with a hook and support for multiple types
  23087. * @method off
  23088. * @param {HTMLElement} element
  23089. * @param {String} type
  23090. * @param {Function} handler
  23091. * @param {Function} [hook]
  23092. * @param {Object} hook.type
  23093. */
  23094. off: function off(element, type, handler, hook) {
  23095. var types = type.split(' ');
  23096. Utils.each(types, function(type) {
  23097. Utils.off(element, type, handler);
  23098. hook && hook(type);
  23099. });
  23100. },
  23101. /**
  23102. * the core touch event handler.
  23103. * this finds out if we should to detect gestures
  23104. * @method onTouch
  23105. * @param {HTMLElement} element
  23106. * @param {String} eventType matches `EVENT_START|MOVE|END`
  23107. * @param {Function} handler
  23108. * @return onTouchHandler {Function} the core event handler
  23109. */
  23110. onTouch: function onTouch(element, eventType, handler) {
  23111. var self = this;
  23112. var onTouchHandler = function onTouchHandler(ev) {
  23113. var srcType = ev.type.toLowerCase(),
  23114. isPointer = Hammer.HAS_POINTEREVENTS,
  23115. isMouse = Utils.inStr(srcType, 'mouse'),
  23116. triggerType;
  23117. // if we are in a mouseevent, but there has been a touchevent triggered in this session
  23118. // we want to do nothing. simply break out of the event.
  23119. if(isMouse && self.preventMouseEvents) {
  23120. return;
  23121. // mousebutton must be down
  23122. } else if(isMouse && eventType == EVENT_START && ev.button === 0) {
  23123. self.preventMouseEvents = false;
  23124. self.shouldDetect = true;
  23125. } else if(isPointer && eventType == EVENT_START) {
  23126. self.shouldDetect = (ev.buttons === 1 || PointerEvent.matchType(POINTER_TOUCH, ev));
  23127. // just a valid start event, but no mouse
  23128. } else if(!isMouse && eventType == EVENT_START) {
  23129. self.preventMouseEvents = true;
  23130. self.shouldDetect = true;
  23131. }
  23132. // update the pointer event before entering the detection
  23133. if(isPointer && eventType != EVENT_END) {
  23134. PointerEvent.updatePointer(eventType, ev);
  23135. }
  23136. // we are in a touch/down state, so allowed detection of gestures
  23137. if(self.shouldDetect) {
  23138. triggerType = self.doDetect.call(self, ev, eventType, element, handler);
  23139. }
  23140. // ...and we are done with the detection
  23141. // so reset everything to start each detection totally fresh
  23142. if(triggerType == EVENT_END) {
  23143. self.preventMouseEvents = false;
  23144. self.shouldDetect = false;
  23145. PointerEvent.reset();
  23146. // update the pointerevent object after the detection
  23147. }
  23148. if(isPointer && eventType == EVENT_END) {
  23149. PointerEvent.updatePointer(eventType, ev);
  23150. }
  23151. };
  23152. this.on(element, EVENT_TYPES[eventType], onTouchHandler);
  23153. return onTouchHandler;
  23154. },
  23155. /**
  23156. * the core detection method
  23157. * this finds out what hammer-touch-events to trigger
  23158. * @method doDetect
  23159. * @param {Object} ev
  23160. * @param {String} eventType matches `EVENT_START|MOVE|END`
  23161. * @param {HTMLElement} element
  23162. * @param {Function} handler
  23163. * @return {String} triggerType matches `EVENT_START|MOVE|END`
  23164. */
  23165. doDetect: function doDetect(ev, eventType, element, handler) {
  23166. var touchList = this.getTouchList(ev, eventType);
  23167. var touchListLength = touchList.length;
  23168. var triggerType = eventType;
  23169. var triggerChange = touchList.trigger; // used by fakeMultitouch plugin
  23170. var changedLength = touchListLength;
  23171. // at each touchstart-like event we want also want to trigger a TOUCH event...
  23172. if(eventType == EVENT_START) {
  23173. triggerChange = EVENT_TOUCH;
  23174. // ...the same for a touchend-like event
  23175. } else if(eventType == EVENT_END) {
  23176. triggerChange = EVENT_RELEASE;
  23177. // keep track of how many touches have been removed
  23178. changedLength = touchList.length - ((ev.changedTouches) ? ev.changedTouches.length : 1);
  23179. }
  23180. // after there are still touches on the screen,
  23181. // we just want to trigger a MOVE event. so change the START or END to a MOVE
  23182. // but only after detection has been started, the first time we actualy want a START
  23183. if(changedLength > 0 && this.started) {
  23184. triggerType = EVENT_MOVE;
  23185. }
  23186. // detection has been started, we keep track of this, see above
  23187. this.started = true;
  23188. // generate some event data, some basic information
  23189. var evData = this.collectEventData(element, triggerType, touchList, ev);
  23190. // trigger the triggerType event before the change (TOUCH, RELEASE) events
  23191. // but the END event should be at last
  23192. if(eventType != EVENT_END) {
  23193. handler.call(Detection, evData);
  23194. }
  23195. // trigger a change (TOUCH, RELEASE) event, this means the length of the touches changed
  23196. if(triggerChange) {
  23197. evData.changedLength = changedLength;
  23198. evData.eventType = triggerChange;
  23199. handler.call(Detection, evData);
  23200. evData.eventType = triggerType;
  23201. delete evData.changedLength;
  23202. }
  23203. // trigger the END event
  23204. if(triggerType == EVENT_END) {
  23205. handler.call(Detection, evData);
  23206. // ...and we are done with the detection
  23207. // so reset everything to start each detection totally fresh
  23208. this.started = false;
  23209. }
  23210. return triggerType;
  23211. },
  23212. /**
  23213. * we have different events for each device/browser
  23214. * determine what we need and set them in the EVENT_TYPES constant
  23215. * the `onTouch` method is bind to these properties.
  23216. * @method determineEventTypes
  23217. * @return {Object} events
  23218. */
  23219. determineEventTypes: function determineEventTypes() {
  23220. var types;
  23221. if(Hammer.HAS_POINTEREVENTS) {
  23222. if(window.PointerEvent) {
  23223. types = [
  23224. 'pointerdown',
  23225. 'pointermove',
  23226. 'pointerup pointercancel lostpointercapture'
  23227. ];
  23228. } else {
  23229. types = [
  23230. 'MSPointerDown',
  23231. 'MSPointerMove',
  23232. 'MSPointerUp MSPointerCancel MSLostPointerCapture'
  23233. ];
  23234. }
  23235. } else if(Hammer.NO_MOUSEEVENTS) {
  23236. types = [
  23237. 'touchstart',
  23238. 'touchmove',
  23239. 'touchend touchcancel'
  23240. ];
  23241. } else {
  23242. types = [
  23243. 'touchstart mousedown',
  23244. 'touchmove mousemove',
  23245. 'touchend touchcancel mouseup'
  23246. ];
  23247. }
  23248. EVENT_TYPES[EVENT_START] = types[0];
  23249. EVENT_TYPES[EVENT_MOVE] = types[1];
  23250. EVENT_TYPES[EVENT_END] = types[2];
  23251. return EVENT_TYPES;
  23252. },
  23253. /**
  23254. * create touchList depending on the event
  23255. * @method getTouchList
  23256. * @param {Object} ev
  23257. * @param {String} eventType
  23258. * @return {Array} touches
  23259. */
  23260. getTouchList: function getTouchList(ev, eventType) {
  23261. // get the fake pointerEvent touchlist
  23262. if(Hammer.HAS_POINTEREVENTS) {
  23263. return PointerEvent.getTouchList();
  23264. }
  23265. // get the touchlist
  23266. if(ev.touches) {
  23267. if(eventType == EVENT_MOVE) {
  23268. return ev.touches;
  23269. }
  23270. var identifiers = [];
  23271. var concat = [].concat(Utils.toArray(ev.touches), Utils.toArray(ev.changedTouches));
  23272. var touchList = [];
  23273. Utils.each(concat, function(touch) {
  23274. if(Utils.inArray(identifiers, touch.identifier) === false) {
  23275. touchList.push(touch);
  23276. }
  23277. identifiers.push(touch.identifier);
  23278. });
  23279. return touchList;
  23280. }
  23281. // make fake touchList from mouse position
  23282. ev.identifier = 1;
  23283. return [ev];
  23284. },
  23285. /**
  23286. * collect basic event data
  23287. * @method collectEventData
  23288. * @param {HTMLElement} element
  23289. * @param {String} eventType matches `EVENT_START|MOVE|END`
  23290. * @param {Array} touches
  23291. * @param {Object} ev
  23292. * @return {Object} ev
  23293. */
  23294. collectEventData: function collectEventData(element, eventType, touches, ev) {
  23295. // find out pointerType
  23296. var pointerType = POINTER_TOUCH;
  23297. if(Utils.inStr(ev.type, 'mouse') || PointerEvent.matchType(POINTER_MOUSE, ev)) {
  23298. pointerType = POINTER_MOUSE;
  23299. } else if(PointerEvent.matchType(POINTER_PEN, ev)) {
  23300. pointerType = POINTER_PEN;
  23301. }
  23302. return {
  23303. center: Utils.getCenter(touches),
  23304. timeStamp: Date.now(),
  23305. target: ev.target,
  23306. touches: touches,
  23307. eventType: eventType,
  23308. pointerType: pointerType,
  23309. srcEvent: ev,
  23310. /**
  23311. * prevent the browser default actions
  23312. * mostly used to disable scrolling of the browser
  23313. */
  23314. preventDefault: function() {
  23315. var srcEvent = this.srcEvent;
  23316. srcEvent.preventManipulation && srcEvent.preventManipulation();
  23317. srcEvent.preventDefault && srcEvent.preventDefault();
  23318. },
  23319. /**
  23320. * stop bubbling the event up to its parents
  23321. */
  23322. stopPropagation: function() {
  23323. this.srcEvent.stopPropagation();
  23324. },
  23325. /**
  23326. * immediately stop gesture detection
  23327. * might be useful after a swipe was detected
  23328. * @return {*}
  23329. */
  23330. stopDetect: function() {
  23331. return Detection.stopDetect();
  23332. }
  23333. };
  23334. }
  23335. };
  23336. /**
  23337. * @module hammer
  23338. *
  23339. * @class PointerEvent
  23340. * @static
  23341. */
  23342. var PointerEvent = Hammer.PointerEvent = {
  23343. /**
  23344. * holds all pointers, by `identifier`
  23345. * @property pointers
  23346. * @type {Object}
  23347. */
  23348. pointers: {},
  23349. /**
  23350. * get the pointers as an array
  23351. * @method getTouchList
  23352. * @return {Array} touchlist
  23353. */
  23354. getTouchList: function getTouchList() {
  23355. var touchlist = [];
  23356. // we can use forEach since pointerEvents only is in IE10
  23357. Utils.each(this.pointers, function(pointer) {
  23358. touchlist.push(pointer);
  23359. });
  23360. return touchlist;
  23361. },
  23362. /**
  23363. * update the position of a pointer
  23364. * @method updatePointer
  23365. * @param {String} eventType matches `EVENT_START|MOVE|END`
  23366. * @param {Object} pointerEvent
  23367. */
  23368. updatePointer: function updatePointer(eventType, pointerEvent) {
  23369. if(eventType == EVENT_END || (eventType != EVENT_END && pointerEvent.buttons !== 1)) {
  23370. delete this.pointers[pointerEvent.pointerId];
  23371. } else {
  23372. pointerEvent.identifier = pointerEvent.pointerId;
  23373. this.pointers[pointerEvent.pointerId] = pointerEvent;
  23374. }
  23375. },
  23376. /**
  23377. * check if ev matches pointertype
  23378. * @method matchType
  23379. * @param {String} pointerType matches `POINTER_MOUSE|TOUCH|PEN`
  23380. * @param {PointerEvent} ev
  23381. */
  23382. matchType: function matchType(pointerType, ev) {
  23383. if(!ev.pointerType) {
  23384. return false;
  23385. }
  23386. var pt = ev.pointerType,
  23387. types = {};
  23388. types[POINTER_MOUSE] = (pt === (ev.MSPOINTER_TYPE_MOUSE || POINTER_MOUSE));
  23389. types[POINTER_TOUCH] = (pt === (ev.MSPOINTER_TYPE_TOUCH || POINTER_TOUCH));
  23390. types[POINTER_PEN] = (pt === (ev.MSPOINTER_TYPE_PEN || POINTER_PEN));
  23391. return types[pointerType];
  23392. },
  23393. /**
  23394. * reset the stored pointers
  23395. * @method reset
  23396. */
  23397. reset: function resetList() {
  23398. this.pointers = {};
  23399. }
  23400. };
  23401. /**
  23402. * @module hammer
  23403. *
  23404. * @class Detection
  23405. * @static
  23406. */
  23407. var Detection = Hammer.detection = {
  23408. // contains all registred Hammer.gestures in the correct order
  23409. gestures: [],
  23410. // data of the current Hammer.gesture detection session
  23411. current: null,
  23412. // the previous Hammer.gesture session data
  23413. // is a full clone of the previous gesture.current object
  23414. previous: null,
  23415. // when this becomes true, no gestures are fired
  23416. stopped: false,
  23417. /**
  23418. * start Hammer.gesture detection
  23419. * @method startDetect
  23420. * @param {Hammer.Instance} inst
  23421. * @param {Object} eventData
  23422. */
  23423. startDetect: function startDetect(inst, eventData) {
  23424. // already busy with a Hammer.gesture detection on an element
  23425. if(this.current) {
  23426. return;
  23427. }
  23428. this.stopped = false;
  23429. // holds current session
  23430. this.current = {
  23431. inst: inst, // reference to HammerInstance we're working for
  23432. startEvent: Utils.extend({}, eventData), // start eventData for distances, timing etc
  23433. lastEvent: false, // last eventData
  23434. lastCalcEvent: false, // last eventData for calculations.
  23435. futureCalcEvent: false, // last eventData for calculations.
  23436. lastCalcData: {}, // last lastCalcData
  23437. name: '' // current gesture we're in/detected, can be 'tap', 'hold' etc
  23438. };
  23439. this.detect(eventData);
  23440. },
  23441. /**
  23442. * Hammer.gesture detection
  23443. * @method detect
  23444. * @param {Object} eventData
  23445. * @return {any}
  23446. */
  23447. detect: function detect(eventData) {
  23448. if(!this.current || this.stopped) {
  23449. return;
  23450. }
  23451. // extend event data with calculations about scale, distance etc
  23452. eventData = this.extendEventData(eventData);
  23453. // hammer instance and instance options
  23454. var inst = this.current.inst,
  23455. instOptions = inst.options;
  23456. // call Hammer.gesture handlers
  23457. Utils.each(this.gestures, function triggerGesture(gesture) {
  23458. // only when the instance options have enabled this gesture
  23459. if(!this.stopped && inst.enabled && instOptions[gesture.name]) {
  23460. gesture.handler.call(gesture, eventData, inst);
  23461. }
  23462. }, this);
  23463. // store as previous event event
  23464. if(this.current) {
  23465. this.current.lastEvent = eventData;
  23466. }
  23467. if(eventData.eventType == EVENT_END) {
  23468. this.stopDetect();
  23469. }
  23470. return eventData;
  23471. },
  23472. /**
  23473. * clear the Hammer.gesture vars
  23474. * this is called on endDetect, but can also be used when a final Hammer.gesture has been detected
  23475. * to stop other Hammer.gestures from being fired
  23476. * @method stopDetect
  23477. */
  23478. stopDetect: function stopDetect() {
  23479. // clone current data to the store as the previous gesture
  23480. // used for the double tap gesture, since this is an other gesture detect session
  23481. this.previous = Utils.extend({}, this.current);
  23482. // reset the current
  23483. this.current = null;
  23484. this.stopped = true;
  23485. },
  23486. /**
  23487. * calculate velocity, angle and direction
  23488. * @method getVelocityData
  23489. * @param {Object} ev
  23490. * @param {Object} center
  23491. * @param {Number} deltaTime
  23492. * @param {Number} deltaX
  23493. * @param {Number} deltaY
  23494. */
  23495. getCalculatedData: function getCalculatedData(ev, center, deltaTime, deltaX, deltaY) {
  23496. var cur = this.current,
  23497. recalc = false,
  23498. calcEv = cur.lastCalcEvent,
  23499. calcData = cur.lastCalcData;
  23500. if(calcEv && ev.timeStamp - calcEv.timeStamp > Hammer.CALCULATE_INTERVAL) {
  23501. center = calcEv.center;
  23502. deltaTime = ev.timeStamp - calcEv.timeStamp;
  23503. deltaX = ev.center.clientX - calcEv.center.clientX;
  23504. deltaY = ev.center.clientY - calcEv.center.clientY;
  23505. recalc = true;
  23506. }
  23507. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  23508. cur.futureCalcEvent = ev;
  23509. }
  23510. if(!cur.lastCalcEvent || recalc) {
  23511. calcData.velocity = Utils.getVelocity(deltaTime, deltaX, deltaY);
  23512. calcData.angle = Utils.getAngle(center, ev.center);
  23513. calcData.direction = Utils.getDirection(center, ev.center);
  23514. cur.lastCalcEvent = cur.futureCalcEvent || ev;
  23515. cur.futureCalcEvent = ev;
  23516. }
  23517. ev.velocityX = calcData.velocity.x;
  23518. ev.velocityY = calcData.velocity.y;
  23519. ev.interimAngle = calcData.angle;
  23520. ev.interimDirection = calcData.direction;
  23521. },
  23522. /**
  23523. * extend eventData for Hammer.gestures
  23524. * @method extendEventData
  23525. * @param {Object} ev
  23526. * @return {Object} ev
  23527. */
  23528. extendEventData: function extendEventData(ev) {
  23529. var cur = this.current,
  23530. startEv = cur.startEvent,
  23531. lastEv = cur.lastEvent || startEv;
  23532. // update the start touchlist to calculate the scale/rotation
  23533. if(ev.eventType == EVENT_TOUCH || ev.eventType == EVENT_RELEASE) {
  23534. startEv.touches = [];
  23535. Utils.each(ev.touches, function(touch) {
  23536. startEv.touches.push({
  23537. clientX: touch.clientX,
  23538. clientY: touch.clientY
  23539. });
  23540. });
  23541. }
  23542. var deltaTime = ev.timeStamp - startEv.timeStamp,
  23543. deltaX = ev.center.clientX - startEv.center.clientX,
  23544. deltaY = ev.center.clientY - startEv.center.clientY;
  23545. this.getCalculatedData(ev, lastEv.center, deltaTime, deltaX, deltaY);
  23546. Utils.extend(ev, {
  23547. startEvent: startEv,
  23548. deltaTime: deltaTime,
  23549. deltaX: deltaX,
  23550. deltaY: deltaY,
  23551. distance: Utils.getDistance(startEv.center, ev.center),
  23552. angle: Utils.getAngle(startEv.center, ev.center),
  23553. direction: Utils.getDirection(startEv.center, ev.center),
  23554. scale: Utils.getScale(startEv.touches, ev.touches),
  23555. rotation: Utils.getRotation(startEv.touches, ev.touches)
  23556. });
  23557. return ev;
  23558. },
  23559. /**
  23560. * register new gesture
  23561. * @method register
  23562. * @param {Object} gesture object, see `gestures/` for documentation
  23563. * @return {Array} gestures
  23564. */
  23565. register: function register(gesture) {
  23566. // add an enable gesture options if there is no given
  23567. var options = gesture.defaults || {};
  23568. if(options[gesture.name] === undefined) {
  23569. options[gesture.name] = true;
  23570. }
  23571. // extend Hammer default options with the Hammer.gesture options
  23572. Utils.extend(Hammer.defaults, options, true);
  23573. // set its index
  23574. gesture.index = gesture.index || 1000;
  23575. // add Hammer.gesture to the list
  23576. this.gestures.push(gesture);
  23577. // sort the list by index
  23578. this.gestures.sort(function(a, b) {
  23579. if(a.index < b.index) {
  23580. return -1;
  23581. }
  23582. if(a.index > b.index) {
  23583. return 1;
  23584. }
  23585. return 0;
  23586. });
  23587. return this.gestures;
  23588. }
  23589. };
  23590. /**
  23591. * @module hammer
  23592. */
  23593. /**
  23594. * create new hammer instance
  23595. * all methods should return the instance itself, so it is chainable.
  23596. *
  23597. * @class Instance
  23598. * @constructor
  23599. * @param {HTMLElement} element
  23600. * @param {Object} [options={}] options are merged with `Hammer.defaults`
  23601. * @return {Hammer.Instance}
  23602. */
  23603. Hammer.Instance = function(element, options) {
  23604. var self = this;
  23605. // setup HammerJS window events and register all gestures
  23606. // this also sets up the default options
  23607. setup();
  23608. /**
  23609. * @property element
  23610. * @type {HTMLElement}
  23611. */
  23612. this.element = element;
  23613. /**
  23614. * @property enabled
  23615. * @type {Boolean}
  23616. * @protected
  23617. */
  23618. this.enabled = true;
  23619. /**
  23620. * options, merged with the defaults
  23621. * options with an _ are converted to camelCase
  23622. * @property options
  23623. * @type {Object}
  23624. */
  23625. Utils.each(options, function(value, name) {
  23626. delete options[name];
  23627. options[Utils.toCamelCase(name)] = value;
  23628. });
  23629. this.options = Utils.extend(Utils.extend({}, Hammer.defaults), options || {});
  23630. // add some css to the element to prevent the browser from doing its native behavoir
  23631. if(this.options.behavior) {
  23632. Utils.toggleBehavior(this.element, this.options.behavior, true);
  23633. }
  23634. /**
  23635. * event start handler on the element to start the detection
  23636. * @property eventStartHandler
  23637. * @type {Object}
  23638. */
  23639. this.eventStartHandler = Event.onTouch(element, EVENT_START, function(ev) {
  23640. if(self.enabled && ev.eventType == EVENT_START) {
  23641. Detection.startDetect(self, ev);
  23642. } else if(ev.eventType == EVENT_TOUCH) {
  23643. Detection.detect(ev);
  23644. }
  23645. });
  23646. /**
  23647. * keep a list of user event handlers which needs to be removed when calling 'dispose'
  23648. * @property eventHandlers
  23649. * @type {Array}
  23650. */
  23651. this.eventHandlers = [];
  23652. };
  23653. Hammer.Instance.prototype = {
  23654. /**
  23655. * bind events to the instance
  23656. * @method on
  23657. * @chainable
  23658. * @param {String} gestures multiple gestures by splitting with a space
  23659. * @param {Function} handler
  23660. * @param {Object} handler.ev event object
  23661. */
  23662. on: function onEvent(gestures, handler) {
  23663. var self = this;
  23664. Event.on(self.element, gestures, handler, function(type) {
  23665. self.eventHandlers.push({ gesture: type, handler: handler });
  23666. });
  23667. return self;
  23668. },
  23669. /**
  23670. * unbind events to the instance
  23671. * @method off
  23672. * @chainable
  23673. * @param {String} gestures
  23674. * @param {Function} handler
  23675. */
  23676. off: function offEvent(gestures, handler) {
  23677. var self = this;
  23678. Event.off(self.element, gestures, handler, function(type) {
  23679. var index = Utils.inArray({ gesture: type, handler: handler });
  23680. if(index !== false) {
  23681. self.eventHandlers.splice(index, 1);
  23682. }
  23683. });
  23684. return self;
  23685. },
  23686. /**
  23687. * trigger gesture event
  23688. * @method trigger
  23689. * @chainable
  23690. * @param {String} gesture
  23691. * @param {Object} [eventData]
  23692. */
  23693. trigger: function triggerEvent(gesture, eventData) {
  23694. // optional
  23695. if(!eventData) {
  23696. eventData = {};
  23697. }
  23698. // create DOM event
  23699. var event = Hammer.DOCUMENT.createEvent('Event');
  23700. event.initEvent(gesture, true, true);
  23701. event.gesture = eventData;
  23702. // trigger on the target if it is in the instance element,
  23703. // this is for event delegation tricks
  23704. var element = this.element;
  23705. if(Utils.hasParent(eventData.target, element)) {
  23706. element = eventData.target;
  23707. }
  23708. element.dispatchEvent(event);
  23709. return this;
  23710. },
  23711. /**
  23712. * enable of disable hammer.js detection
  23713. * @method enable
  23714. * @chainable
  23715. * @param {Boolean} state
  23716. */
  23717. enable: function enable(state) {
  23718. this.enabled = state;
  23719. return this;
  23720. },
  23721. /**
  23722. * dispose this hammer instance
  23723. * @method dispose
  23724. * @return {Null}
  23725. */
  23726. dispose: function dispose() {
  23727. var i, eh;
  23728. // undo all changes made by stop_browser_behavior
  23729. Utils.toggleBehavior(this.element, this.options.behavior, false);
  23730. // unbind all custom event handlers
  23731. for(i = -1; (eh = this.eventHandlers[++i]);) {
  23732. Utils.off(this.element, eh.gesture, eh.handler);
  23733. }
  23734. this.eventHandlers = [];
  23735. // unbind the start event listener
  23736. Event.off(this.element, EVENT_TYPES[EVENT_START], this.eventStartHandler);
  23737. return null;
  23738. }
  23739. };
  23740. /**
  23741. * @module gestures
  23742. */
  23743. /**
  23744. * Move with x fingers (default 1) around on the page.
  23745. * Preventing the default browser behavior is a good way to improve feel and working.
  23746. * ````
  23747. * hammertime.on("drag", function(ev) {
  23748. * console.log(ev);
  23749. * ev.gesture.preventDefault();
  23750. * });
  23751. * ````
  23752. *
  23753. * @class Drag
  23754. * @static
  23755. */
  23756. /**
  23757. * @event drag
  23758. * @param {Object} ev
  23759. */
  23760. /**
  23761. * @event dragstart
  23762. * @param {Object} ev
  23763. */
  23764. /**
  23765. * @event dragend
  23766. * @param {Object} ev
  23767. */
  23768. /**
  23769. * @event drapleft
  23770. * @param {Object} ev
  23771. */
  23772. /**
  23773. * @event dragright
  23774. * @param {Object} ev
  23775. */
  23776. /**
  23777. * @event dragup
  23778. * @param {Object} ev
  23779. */
  23780. /**
  23781. * @event dragdown
  23782. * @param {Object} ev
  23783. */
  23784. /**
  23785. * @param {String} name
  23786. */
  23787. (function(name) {
  23788. var triggered = false;
  23789. function dragGesture(ev, inst) {
  23790. var cur = Detection.current;
  23791. // max touches
  23792. if(inst.options.dragMaxTouches > 0 &&
  23793. ev.touches.length > inst.options.dragMaxTouches) {
  23794. return;
  23795. }
  23796. switch(ev.eventType) {
  23797. case EVENT_START:
  23798. triggered = false;
  23799. break;
  23800. case EVENT_MOVE:
  23801. // when the distance we moved is too small we skip this gesture
  23802. // or we can be already in dragging
  23803. if(ev.distance < inst.options.dragMinDistance &&
  23804. cur.name != name) {
  23805. return;
  23806. }
  23807. var startCenter = cur.startEvent.center;
  23808. // we are dragging!
  23809. if(cur.name != name) {
  23810. cur.name = name;
  23811. if(inst.options.dragDistanceCorrection && ev.distance > 0) {
  23812. // When a drag is triggered, set the event center to dragMinDistance pixels from the original event center.
  23813. // Without this correction, the dragged distance would jumpstart at dragMinDistance pixels instead of at 0.
  23814. // It might be useful to save the original start point somewhere
  23815. var factor = Math.abs(inst.options.dragMinDistance / ev.distance);
  23816. startCenter.pageX += ev.deltaX * factor;
  23817. startCenter.pageY += ev.deltaY * factor;
  23818. startCenter.clientX += ev.deltaX * factor;
  23819. startCenter.clientY += ev.deltaY * factor;
  23820. // recalculate event data using new start point
  23821. ev = Detection.extendEventData(ev);
  23822. }
  23823. }
  23824. // lock drag to axis?
  23825. if(cur.lastEvent.dragLockToAxis ||
  23826. ( inst.options.dragLockToAxis &&
  23827. inst.options.dragLockMinDistance <= ev.distance
  23828. )) {
  23829. ev.dragLockToAxis = true;
  23830. }
  23831. // keep direction on the axis that the drag gesture started on
  23832. var lastDirection = cur.lastEvent.direction;
  23833. if(ev.dragLockToAxis && lastDirection !== ev.direction) {
  23834. if(Utils.isVertical(lastDirection)) {
  23835. ev.direction = (ev.deltaY < 0) ? DIRECTION_UP : DIRECTION_DOWN;
  23836. } else {
  23837. ev.direction = (ev.deltaX < 0) ? DIRECTION_LEFT : DIRECTION_RIGHT;
  23838. }
  23839. }
  23840. // first time, trigger dragstart event
  23841. if(!triggered) {
  23842. inst.trigger(name + 'start', ev);
  23843. triggered = true;
  23844. }
  23845. // trigger events
  23846. inst.trigger(name, ev);
  23847. inst.trigger(name + ev.direction, ev);
  23848. var isVertical = Utils.isVertical(ev.direction);
  23849. // block the browser events
  23850. if((inst.options.dragBlockVertical && isVertical) ||
  23851. (inst.options.dragBlockHorizontal && !isVertical)) {
  23852. ev.preventDefault();
  23853. }
  23854. break;
  23855. case EVENT_RELEASE:
  23856. if(triggered && ev.changedLength <= inst.options.dragMaxTouches) {
  23857. inst.trigger(name + 'end', ev);
  23858. triggered = false;
  23859. }
  23860. break;
  23861. case EVENT_END:
  23862. triggered = false;
  23863. break;
  23864. }
  23865. }
  23866. Hammer.gestures.Drag = {
  23867. name: name,
  23868. index: 50,
  23869. handler: dragGesture,
  23870. defaults: {
  23871. /**
  23872. * minimal movement that have to be made before the drag event gets triggered
  23873. * @property dragMinDistance
  23874. * @type {Number}
  23875. * @default 10
  23876. */
  23877. dragMinDistance: 10,
  23878. /**
  23879. * Set dragDistanceCorrection to true to make the starting point of the drag
  23880. * be calculated from where the drag was triggered, not from where the touch started.
  23881. * Useful to avoid a jerk-starting drag, which can make fine-adjustments
  23882. * through dragging difficult, and be visually unappealing.
  23883. * @property dragDistanceCorrection
  23884. * @type {Boolean}
  23885. * @default true
  23886. */
  23887. dragDistanceCorrection: true,
  23888. /**
  23889. * set 0 for unlimited, but this can conflict with transform
  23890. * @property dragMaxTouches
  23891. * @type {Number}
  23892. * @default 1
  23893. */
  23894. dragMaxTouches: 1,
  23895. /**
  23896. * prevent default browser behavior when dragging occurs
  23897. * be careful with it, it makes the element a blocking element
  23898. * when you are using the drag gesture, it is a good practice to set this true
  23899. * @property dragBlockHorizontal
  23900. * @type {Boolean}
  23901. * @default false
  23902. */
  23903. dragBlockHorizontal: false,
  23904. /**
  23905. * same as `dragBlockHorizontal`, but for vertical movement
  23906. * @property dragBlockVertical
  23907. * @type {Boolean}
  23908. * @default false
  23909. */
  23910. dragBlockVertical: false,
  23911. /**
  23912. * dragLockToAxis keeps the drag gesture on the axis that it started on,
  23913. * It disallows vertical directions if the initial direction was horizontal, and vice versa.
  23914. * @property dragLockToAxis
  23915. * @type {Boolean}
  23916. * @default false
  23917. */
  23918. dragLockToAxis: false,
  23919. /**
  23920. * drag lock only kicks in when distance > dragLockMinDistance
  23921. * This way, locking occurs only when the distance has become large enough to reliably determine the direction
  23922. * @property dragLockMinDistance
  23923. * @type {Number}
  23924. * @default 25
  23925. */
  23926. dragLockMinDistance: 25
  23927. }
  23928. };
  23929. })('drag');
  23930. /**
  23931. * @module gestures
  23932. */
  23933. /**
  23934. * trigger a simple gesture event, so you can do anything in your handler.
  23935. * only usable if you know what your doing...
  23936. *
  23937. * @class Gesture
  23938. * @static
  23939. */
  23940. /**
  23941. * @event gesture
  23942. * @param {Object} ev
  23943. */
  23944. Hammer.gestures.Gesture = {
  23945. name: 'gesture',
  23946. index: 1337,
  23947. handler: function releaseGesture(ev, inst) {
  23948. inst.trigger(this.name, ev);
  23949. }
  23950. };
  23951. /**
  23952. * @module gestures
  23953. */
  23954. /**
  23955. * Touch stays at the same place for x time
  23956. *
  23957. * @class Hold
  23958. * @static
  23959. */
  23960. /**
  23961. * @event hold
  23962. * @param {Object} ev
  23963. */
  23964. /**
  23965. * @param {String} name
  23966. */
  23967. (function(name) {
  23968. var timer;
  23969. function holdGesture(ev, inst) {
  23970. var options = inst.options,
  23971. current = Detection.current;
  23972. switch(ev.eventType) {
  23973. case EVENT_START:
  23974. clearTimeout(timer);
  23975. // set the gesture so we can check in the timeout if it still is
  23976. current.name = name;
  23977. // set timer and if after the timeout it still is hold,
  23978. // we trigger the hold event
  23979. timer = setTimeout(function() {
  23980. if(current && current.name == name) {
  23981. inst.trigger(name, ev);
  23982. }
  23983. }, options.holdTimeout);
  23984. break;
  23985. case EVENT_MOVE:
  23986. if(ev.distance > options.holdThreshold) {
  23987. clearTimeout(timer);
  23988. }
  23989. break;
  23990. case EVENT_RELEASE:
  23991. clearTimeout(timer);
  23992. break;
  23993. }
  23994. }
  23995. Hammer.gestures.Hold = {
  23996. name: name,
  23997. index: 10,
  23998. defaults: {
  23999. /**
  24000. * @property holdTimeout
  24001. * @type {Number}
  24002. * @default 500
  24003. */
  24004. holdTimeout: 500,
  24005. /**
  24006. * movement allowed while holding
  24007. * @property holdThreshold
  24008. * @type {Number}
  24009. * @default 2
  24010. */
  24011. holdThreshold: 2
  24012. },
  24013. handler: holdGesture
  24014. };
  24015. })('hold');
  24016. /**
  24017. * @module gestures
  24018. */
  24019. /**
  24020. * when a touch is being released from the page
  24021. *
  24022. * @class Release
  24023. * @static
  24024. */
  24025. /**
  24026. * @event release
  24027. * @param {Object} ev
  24028. */
  24029. Hammer.gestures.Release = {
  24030. name: 'release',
  24031. index: Infinity,
  24032. handler: function releaseGesture(ev, inst) {
  24033. if(ev.eventType == EVENT_RELEASE) {
  24034. inst.trigger(this.name, ev);
  24035. }
  24036. }
  24037. };
  24038. /**
  24039. * @module gestures
  24040. */
  24041. /**
  24042. * triggers swipe events when the end velocity is above the threshold
  24043. * for best usage, set `preventDefault` (on the drag gesture) to `true`
  24044. * ````
  24045. * hammertime.on("dragleft swipeleft", function(ev) {
  24046. * console.log(ev);
  24047. * ev.gesture.preventDefault();
  24048. * });
  24049. * ````
  24050. *
  24051. * @class Swipe
  24052. * @static
  24053. */
  24054. /**
  24055. * @event swipe
  24056. * @param {Object} ev
  24057. */
  24058. /**
  24059. * @event swipeleft
  24060. * @param {Object} ev
  24061. */
  24062. /**
  24063. * @event swiperight
  24064. * @param {Object} ev
  24065. */
  24066. /**
  24067. * @event swipeup
  24068. * @param {Object} ev
  24069. */
  24070. /**
  24071. * @event swipedown
  24072. * @param {Object} ev
  24073. */
  24074. Hammer.gestures.Swipe = {
  24075. name: 'swipe',
  24076. index: 40,
  24077. defaults: {
  24078. /**
  24079. * @property swipeMinTouches
  24080. * @type {Number}
  24081. * @default 1
  24082. */
  24083. swipeMinTouches: 1,
  24084. /**
  24085. * @property swipeMaxTouches
  24086. * @type {Number}
  24087. * @default 1
  24088. */
  24089. swipeMaxTouches: 1,
  24090. /**
  24091. * horizontal swipe velocity
  24092. * @property swipeVelocityX
  24093. * @type {Number}
  24094. * @default 0.6
  24095. */
  24096. swipeVelocityX: 0.6,
  24097. /**
  24098. * vertical swipe velocity
  24099. * @property swipeVelocityY
  24100. * @type {Number}
  24101. * @default 0.6
  24102. */
  24103. swipeVelocityY: 0.6
  24104. },
  24105. handler: function swipeGesture(ev, inst) {
  24106. if(ev.eventType == EVENT_RELEASE) {
  24107. var touches = ev.touches.length,
  24108. options = inst.options;
  24109. // max touches
  24110. if(touches < options.swipeMinTouches ||
  24111. touches > options.swipeMaxTouches) {
  24112. return;
  24113. }
  24114. // when the distance we moved is too small we skip this gesture
  24115. // or we can be already in dragging
  24116. if(ev.velocityX > options.swipeVelocityX ||
  24117. ev.velocityY > options.swipeVelocityY) {
  24118. // trigger swipe events
  24119. inst.trigger(this.name, ev);
  24120. inst.trigger(this.name + ev.direction, ev);
  24121. }
  24122. }
  24123. }
  24124. };
  24125. /**
  24126. * @module gestures
  24127. */
  24128. /**
  24129. * Single tap and a double tap on a place
  24130. *
  24131. * @class Tap
  24132. * @static
  24133. */
  24134. /**
  24135. * @event tap
  24136. * @param {Object} ev
  24137. */
  24138. /**
  24139. * @event doubletap
  24140. * @param {Object} ev
  24141. */
  24142. /**
  24143. * @param {String} name
  24144. */
  24145. (function(name) {
  24146. var hasMoved = false;
  24147. function tapGesture(ev, inst) {
  24148. var options = inst.options,
  24149. current = Detection.current,
  24150. prev = Detection.previous,
  24151. sincePrev,
  24152. didDoubleTap;
  24153. switch(ev.eventType) {
  24154. case EVENT_START:
  24155. hasMoved = false;
  24156. break;
  24157. case EVENT_MOVE:
  24158. hasMoved = hasMoved || (ev.distance > options.tapMaxDistance);
  24159. break;
  24160. case EVENT_END:
  24161. if(!Utils.inStr(ev.srcEvent.type, 'cancel') && ev.deltaTime < options.tapMaxTime && !hasMoved) {
  24162. // previous gesture, for the double tap since these are two different gesture detections
  24163. sincePrev = prev && prev.lastEvent && ev.timeStamp - prev.lastEvent.timeStamp;
  24164. didDoubleTap = false;
  24165. // check if double tap
  24166. if(prev && prev.name == name &&
  24167. (sincePrev && sincePrev < options.doubleTapInterval) &&
  24168. ev.distance < options.doubleTapDistance) {
  24169. inst.trigger('doubletap', ev);
  24170. didDoubleTap = true;
  24171. }
  24172. // do a single tap
  24173. if(!didDoubleTap || options.tapAlways) {
  24174. current.name = name;
  24175. inst.trigger(current.name, ev);
  24176. }
  24177. }
  24178. break;
  24179. }
  24180. }
  24181. Hammer.gestures.Tap = {
  24182. name: name,
  24183. index: 100,
  24184. handler: tapGesture,
  24185. defaults: {
  24186. /**
  24187. * max time of a tap, this is for the slow tappers
  24188. * @property tapMaxTime
  24189. * @type {Number}
  24190. * @default 250
  24191. */
  24192. tapMaxTime: 250,
  24193. /**
  24194. * max distance of movement of a tap, this is for the slow tappers
  24195. * @property tapMaxDistance
  24196. * @type {Number}
  24197. * @default 10
  24198. */
  24199. tapMaxDistance: 10,
  24200. /**
  24201. * always trigger the `tap` event, even while double-tapping
  24202. * @property tapAlways
  24203. * @type {Boolean}
  24204. * @default true
  24205. */
  24206. tapAlways: true,
  24207. /**
  24208. * max distance between two taps
  24209. * @property doubleTapDistance
  24210. * @type {Number}
  24211. * @default 20
  24212. */
  24213. doubleTapDistance: 20,
  24214. /**
  24215. * max time between two taps
  24216. * @property doubleTapInterval
  24217. * @type {Number}
  24218. * @default 300
  24219. */
  24220. doubleTapInterval: 300
  24221. }
  24222. };
  24223. })('tap');
  24224. /**
  24225. * @module gestures
  24226. */
  24227. /**
  24228. * when a touch is being touched at the page
  24229. *
  24230. * @class Touch
  24231. * @static
  24232. */
  24233. /**
  24234. * @event touch
  24235. * @param {Object} ev
  24236. */
  24237. Hammer.gestures.Touch = {
  24238. name: 'touch',
  24239. index: -Infinity,
  24240. defaults: {
  24241. /**
  24242. * call preventDefault at touchstart, and makes the element blocking by disabling the scrolling of the page,
  24243. * but it improves gestures like transforming and dragging.
  24244. * be careful with using this, it can be very annoying for users to be stuck on the page
  24245. * @property preventDefault
  24246. * @type {Boolean}
  24247. * @default false
  24248. */
  24249. preventDefault: false,
  24250. /**
  24251. * disable mouse events, so only touch (or pen!) input triggers events
  24252. * @property preventMouse
  24253. * @type {Boolean}
  24254. * @default false
  24255. */
  24256. preventMouse: false
  24257. },
  24258. handler: function touchGesture(ev, inst) {
  24259. if(inst.options.preventMouse && ev.pointerType == POINTER_MOUSE) {
  24260. ev.stopDetect();
  24261. return;
  24262. }
  24263. if(inst.options.preventDefault) {
  24264. ev.preventDefault();
  24265. }
  24266. if(ev.eventType == EVENT_TOUCH) {
  24267. inst.trigger('touch', ev);
  24268. }
  24269. }
  24270. };
  24271. /**
  24272. * @module gestures
  24273. */
  24274. /**
  24275. * User want to scale or rotate with 2 fingers
  24276. * Preventing the default browser behavior is a good way to improve feel and working. This can be done with the
  24277. * `preventDefault` option.
  24278. *
  24279. * @class Transform
  24280. * @static
  24281. */
  24282. /**
  24283. * @event transform
  24284. * @param {Object} ev
  24285. */
  24286. /**
  24287. * @event transformstart
  24288. * @param {Object} ev
  24289. */
  24290. /**
  24291. * @event transformend
  24292. * @param {Object} ev
  24293. */
  24294. /**
  24295. * @event pinchin
  24296. * @param {Object} ev
  24297. */
  24298. /**
  24299. * @event pinchout
  24300. * @param {Object} ev
  24301. */
  24302. /**
  24303. * @event rotate
  24304. * @param {Object} ev
  24305. */
  24306. /**
  24307. * @param {String} name
  24308. */
  24309. (function(name) {
  24310. var triggered = false;
  24311. function transformGesture(ev, inst) {
  24312. switch(ev.eventType) {
  24313. case EVENT_START:
  24314. triggered = false;
  24315. break;
  24316. case EVENT_MOVE:
  24317. // at least multitouch
  24318. if(ev.touches.length < 2) {
  24319. return;
  24320. }
  24321. var scaleThreshold = Math.abs(1 - ev.scale);
  24322. var rotationThreshold = Math.abs(ev.rotation);
  24323. // when the distance we moved is too small we skip this gesture
  24324. // or we can be already in dragging
  24325. if(scaleThreshold < inst.options.transformMinScale &&
  24326. rotationThreshold < inst.options.transformMinRotation) {
  24327. return;
  24328. }
  24329. // we are transforming!
  24330. Detection.current.name = name;
  24331. // first time, trigger dragstart event
  24332. if(!triggered) {
  24333. inst.trigger(name + 'start', ev);
  24334. triggered = true;
  24335. }
  24336. inst.trigger(name, ev); // basic transform event
  24337. // trigger rotate event
  24338. if(rotationThreshold > inst.options.transformMinRotation) {
  24339. inst.trigger('rotate', ev);
  24340. }
  24341. // trigger pinch event
  24342. if(scaleThreshold > inst.options.transformMinScale) {
  24343. inst.trigger('pinch', ev);
  24344. inst.trigger('pinch' + (ev.scale < 1 ? 'in' : 'out'), ev);
  24345. }
  24346. break;
  24347. case EVENT_RELEASE:
  24348. if(triggered && ev.changedLength < 2) {
  24349. inst.trigger(name + 'end', ev);
  24350. triggered = false;
  24351. }
  24352. break;
  24353. }
  24354. }
  24355. Hammer.gestures.Transform = {
  24356. name: name,
  24357. index: 45,
  24358. defaults: {
  24359. /**
  24360. * minimal scale factor, no scale is 1, zoomin is to 0 and zoomout until higher then 1
  24361. * @property transformMinScale
  24362. * @type {Number}
  24363. * @default 0.01
  24364. */
  24365. transformMinScale: 0.01,
  24366. /**
  24367. * rotation in degrees
  24368. * @property transformMinRotation
  24369. * @type {Number}
  24370. * @default 1
  24371. */
  24372. transformMinRotation: 1
  24373. },
  24374. handler: transformGesture
  24375. };
  24376. })('transform');
  24377. /**
  24378. * @module hammer
  24379. */
  24380. // AMD export
  24381. if(true) {
  24382. !(__WEBPACK_AMD_DEFINE_RESULT__ = function() {
  24383. return Hammer;
  24384. }.call(exports, __webpack_require__, exports, module), __WEBPACK_AMD_DEFINE_RESULT__ !== undefined && (module.exports = __WEBPACK_AMD_DEFINE_RESULT__));
  24385. // commonjs export
  24386. } else if(typeof module !== 'undefined' && module.exports) {
  24387. module.exports = Hammer;
  24388. // browser export
  24389. } else {
  24390. window.Hammer = Hammer;
  24391. }
  24392. })(window);
  24393. /***/ },
  24394. /* 56 */
  24395. /***/ function(module, exports, __webpack_require__) {
  24396. /**
  24397. * Copyright 2012 Craig Campbell
  24398. *
  24399. * Licensed under the Apache License, Version 2.0 (the "License");
  24400. * you may not use this file except in compliance with the License.
  24401. * You may obtain a copy of the License at
  24402. *
  24403. * http://www.apache.org/licenses/LICENSE-2.0
  24404. *
  24405. * Unless required by applicable law or agreed to in writing, software
  24406. * distributed under the License is distributed on an "AS IS" BASIS,
  24407. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  24408. * See the License for the specific language governing permissions and
  24409. * limitations under the License.
  24410. *
  24411. * Mousetrap is a simple keyboard shortcut library for Javascript with
  24412. * no external dependencies
  24413. *
  24414. * @version 1.1.2
  24415. * @url craig.is/killing/mice
  24416. */
  24417. /**
  24418. * mapping of special keycodes to their corresponding keys
  24419. *
  24420. * everything in this dictionary cannot use keypress events
  24421. * so it has to be here to map to the correct keycodes for
  24422. * keyup/keydown events
  24423. *
  24424. * @type {Object}
  24425. */
  24426. var _MAP = {
  24427. 8: 'backspace',
  24428. 9: 'tab',
  24429. 13: 'enter',
  24430. 16: 'shift',
  24431. 17: 'ctrl',
  24432. 18: 'alt',
  24433. 20: 'capslock',
  24434. 27: 'esc',
  24435. 32: 'space',
  24436. 33: 'pageup',
  24437. 34: 'pagedown',
  24438. 35: 'end',
  24439. 36: 'home',
  24440. 37: 'left',
  24441. 38: 'up',
  24442. 39: 'right',
  24443. 40: 'down',
  24444. 45: 'ins',
  24445. 46: 'del',
  24446. 91: 'meta',
  24447. 93: 'meta',
  24448. 224: 'meta'
  24449. },
  24450. /**
  24451. * mapping for special characters so they can support
  24452. *
  24453. * this dictionary is only used incase you want to bind a
  24454. * keyup or keydown event to one of these keys
  24455. *
  24456. * @type {Object}
  24457. */
  24458. _KEYCODE_MAP = {
  24459. 106: '*',
  24460. 107: '+',
  24461. 109: '-',
  24462. 110: '.',
  24463. 111 : '/',
  24464. 186: ';',
  24465. 187: '=',
  24466. 188: ',',
  24467. 189: '-',
  24468. 190: '.',
  24469. 191: '/',
  24470. 192: '`',
  24471. 219: '[',
  24472. 220: '\\',
  24473. 221: ']',
  24474. 222: '\''
  24475. },
  24476. /**
  24477. * this is a mapping of keys that require shift on a US keypad
  24478. * back to the non shift equivelents
  24479. *
  24480. * this is so you can use keyup events with these keys
  24481. *
  24482. * note that this will only work reliably on US keyboards
  24483. *
  24484. * @type {Object}
  24485. */
  24486. _SHIFT_MAP = {
  24487. '~': '`',
  24488. '!': '1',
  24489. '@': '2',
  24490. '#': '3',
  24491. '$': '4',
  24492. '%': '5',
  24493. '^': '6',
  24494. '&': '7',
  24495. '*': '8',
  24496. '(': '9',
  24497. ')': '0',
  24498. '_': '-',
  24499. '+': '=',
  24500. ':': ';',
  24501. '\"': '\'',
  24502. '<': ',',
  24503. '>': '.',
  24504. '?': '/',
  24505. '|': '\\'
  24506. },
  24507. /**
  24508. * this is a list of special strings you can use to map
  24509. * to modifier keys when you specify your keyboard shortcuts
  24510. *
  24511. * @type {Object}
  24512. */
  24513. _SPECIAL_ALIASES = {
  24514. 'option': 'alt',
  24515. 'command': 'meta',
  24516. 'return': 'enter',
  24517. 'escape': 'esc'
  24518. },
  24519. /**
  24520. * variable to store the flipped version of _MAP from above
  24521. * needed to check if we should use keypress or not when no action
  24522. * is specified
  24523. *
  24524. * @type {Object|undefined}
  24525. */
  24526. _REVERSE_MAP,
  24527. /**
  24528. * a list of all the callbacks setup via Mousetrap.bind()
  24529. *
  24530. * @type {Object}
  24531. */
  24532. _callbacks = {},
  24533. /**
  24534. * direct map of string combinations to callbacks used for trigger()
  24535. *
  24536. * @type {Object}
  24537. */
  24538. _direct_map = {},
  24539. /**
  24540. * keeps track of what level each sequence is at since multiple
  24541. * sequences can start out with the same sequence
  24542. *
  24543. * @type {Object}
  24544. */
  24545. _sequence_levels = {},
  24546. /**
  24547. * variable to store the setTimeout call
  24548. *
  24549. * @type {null|number}
  24550. */
  24551. _reset_timer,
  24552. /**
  24553. * temporary state where we will ignore the next keyup
  24554. *
  24555. * @type {boolean|string}
  24556. */
  24557. _ignore_next_keyup = false,
  24558. /**
  24559. * are we currently inside of a sequence?
  24560. * type of action ("keyup" or "keydown" or "keypress") or false
  24561. *
  24562. * @type {boolean|string}
  24563. */
  24564. _inside_sequence = false;
  24565. /**
  24566. * loop through the f keys, f1 to f19 and add them to the map
  24567. * programatically
  24568. */
  24569. for (var i = 1; i < 20; ++i) {
  24570. _MAP[111 + i] = 'f' + i;
  24571. }
  24572. /**
  24573. * loop through to map numbers on the numeric keypad
  24574. */
  24575. for (i = 0; i <= 9; ++i) {
  24576. _MAP[i + 96] = i;
  24577. }
  24578. /**
  24579. * cross browser add event method
  24580. *
  24581. * @param {Element|HTMLDocument} object
  24582. * @param {string} type
  24583. * @param {Function} callback
  24584. * @returns void
  24585. */
  24586. function _addEvent(object, type, callback) {
  24587. if (object.addEventListener) {
  24588. return object.addEventListener(type, callback, false);
  24589. }
  24590. object.attachEvent('on' + type, callback);
  24591. }
  24592. /**
  24593. * takes the event and returns the key character
  24594. *
  24595. * @param {Event} e
  24596. * @return {string}
  24597. */
  24598. function _characterFromEvent(e) {
  24599. // for keypress events we should return the character as is
  24600. if (e.type == 'keypress') {
  24601. return String.fromCharCode(e.which);
  24602. }
  24603. // for non keypress events the special maps are needed
  24604. if (_MAP[e.which]) {
  24605. return _MAP[e.which];
  24606. }
  24607. if (_KEYCODE_MAP[e.which]) {
  24608. return _KEYCODE_MAP[e.which];
  24609. }
  24610. // if it is not in the special map
  24611. return String.fromCharCode(e.which).toLowerCase();
  24612. }
  24613. /**
  24614. * should we stop this event before firing off callbacks
  24615. *
  24616. * @param {Event} e
  24617. * @return {boolean}
  24618. */
  24619. function _stop(e) {
  24620. var element = e.target || e.srcElement,
  24621. tag_name = element.tagName;
  24622. // if the element has the class "mousetrap" then no need to stop
  24623. if ((' ' + element.className + ' ').indexOf(' mousetrap ') > -1) {
  24624. return false;
  24625. }
  24626. // stop for input, select, and textarea
  24627. return tag_name == 'INPUT' || tag_name == 'SELECT' || tag_name == 'TEXTAREA' || (element.contentEditable && element.contentEditable == 'true');
  24628. }
  24629. /**
  24630. * checks if two arrays are equal
  24631. *
  24632. * @param {Array} modifiers1
  24633. * @param {Array} modifiers2
  24634. * @returns {boolean}
  24635. */
  24636. function _modifiersMatch(modifiers1, modifiers2) {
  24637. return modifiers1.sort().join(',') === modifiers2.sort().join(',');
  24638. }
  24639. /**
  24640. * resets all sequence counters except for the ones passed in
  24641. *
  24642. * @param {Object} do_not_reset
  24643. * @returns void
  24644. */
  24645. function _resetSequences(do_not_reset) {
  24646. do_not_reset = do_not_reset || {};
  24647. var active_sequences = false,
  24648. key;
  24649. for (key in _sequence_levels) {
  24650. if (do_not_reset[key]) {
  24651. active_sequences = true;
  24652. continue;
  24653. }
  24654. _sequence_levels[key] = 0;
  24655. }
  24656. if (!active_sequences) {
  24657. _inside_sequence = false;
  24658. }
  24659. }
  24660. /**
  24661. * finds all callbacks that match based on the keycode, modifiers,
  24662. * and action
  24663. *
  24664. * @param {string} character
  24665. * @param {Array} modifiers
  24666. * @param {string} action
  24667. * @param {boolean=} remove - should we remove any matches
  24668. * @param {string=} combination
  24669. * @returns {Array}
  24670. */
  24671. function _getMatches(character, modifiers, action, remove, combination) {
  24672. var i,
  24673. callback,
  24674. matches = [];
  24675. // if there are no events related to this keycode
  24676. if (!_callbacks[character]) {
  24677. return [];
  24678. }
  24679. // if a modifier key is coming up on its own we should allow it
  24680. if (action == 'keyup' && _isModifier(character)) {
  24681. modifiers = [character];
  24682. }
  24683. // loop through all callbacks for the key that was pressed
  24684. // and see if any of them match
  24685. for (i = 0; i < _callbacks[character].length; ++i) {
  24686. callback = _callbacks[character][i];
  24687. // if this is a sequence but it is not at the right level
  24688. // then move onto the next match
  24689. if (callback.seq && _sequence_levels[callback.seq] != callback.level) {
  24690. continue;
  24691. }
  24692. // if the action we are looking for doesn't match the action we got
  24693. // then we should keep going
  24694. if (action != callback.action) {
  24695. continue;
  24696. }
  24697. // if this is a keypress event that means that we need to only
  24698. // look at the character, otherwise check the modifiers as
  24699. // well
  24700. if (action == 'keypress' || _modifiersMatch(modifiers, callback.modifiers)) {
  24701. // remove is used so if you change your mind and call bind a
  24702. // second time with a new function the first one is overwritten
  24703. if (remove && callback.combo == combination) {
  24704. _callbacks[character].splice(i, 1);
  24705. }
  24706. matches.push(callback);
  24707. }
  24708. }
  24709. return matches;
  24710. }
  24711. /**
  24712. * takes a key event and figures out what the modifiers are
  24713. *
  24714. * @param {Event} e
  24715. * @returns {Array}
  24716. */
  24717. function _eventModifiers(e) {
  24718. var modifiers = [];
  24719. if (e.shiftKey) {
  24720. modifiers.push('shift');
  24721. }
  24722. if (e.altKey) {
  24723. modifiers.push('alt');
  24724. }
  24725. if (e.ctrlKey) {
  24726. modifiers.push('ctrl');
  24727. }
  24728. if (e.metaKey) {
  24729. modifiers.push('meta');
  24730. }
  24731. return modifiers;
  24732. }
  24733. /**
  24734. * actually calls the callback function
  24735. *
  24736. * if your callback function returns false this will use the jquery
  24737. * convention - prevent default and stop propogation on the event
  24738. *
  24739. * @param {Function} callback
  24740. * @param {Event} e
  24741. * @returns void
  24742. */
  24743. function _fireCallback(callback, e) {
  24744. if (callback(e) === false) {
  24745. if (e.preventDefault) {
  24746. e.preventDefault();
  24747. }
  24748. if (e.stopPropagation) {
  24749. e.stopPropagation();
  24750. }
  24751. e.returnValue = false;
  24752. e.cancelBubble = true;
  24753. }
  24754. }
  24755. /**
  24756. * handles a character key event
  24757. *
  24758. * @param {string} character
  24759. * @param {Event} e
  24760. * @returns void
  24761. */
  24762. function _handleCharacter(character, e) {
  24763. // if this event should not happen stop here
  24764. if (_stop(e)) {
  24765. return;
  24766. }
  24767. var callbacks = _getMatches(character, _eventModifiers(e), e.type),
  24768. i,
  24769. do_not_reset = {},
  24770. processed_sequence_callback = false;
  24771. // loop through matching callbacks for this key event
  24772. for (i = 0; i < callbacks.length; ++i) {
  24773. // fire for all sequence callbacks
  24774. // this is because if for example you have multiple sequences
  24775. // bound such as "g i" and "g t" they both need to fire the
  24776. // callback for matching g cause otherwise you can only ever
  24777. // match the first one
  24778. if (callbacks[i].seq) {
  24779. processed_sequence_callback = true;
  24780. // keep a list of which sequences were matches for later
  24781. do_not_reset[callbacks[i].seq] = 1;
  24782. _fireCallback(callbacks[i].callback, e);
  24783. continue;
  24784. }
  24785. // if there were no sequence matches but we are still here
  24786. // that means this is a regular match so we should fire that
  24787. if (!processed_sequence_callback && !_inside_sequence) {
  24788. _fireCallback(callbacks[i].callback, e);
  24789. }
  24790. }
  24791. // if you are inside of a sequence and the key you are pressing
  24792. // is not a modifier key then we should reset all sequences
  24793. // that were not matched by this key event
  24794. if (e.type == _inside_sequence && !_isModifier(character)) {
  24795. _resetSequences(do_not_reset);
  24796. }
  24797. }
  24798. /**
  24799. * handles a keydown event
  24800. *
  24801. * @param {Event} e
  24802. * @returns void
  24803. */
  24804. function _handleKey(e) {
  24805. // normalize e.which for key events
  24806. // @see http://stackoverflow.com/questions/4285627/javascript-keycode-vs-charcode-utter-confusion
  24807. e.which = typeof e.which == "number" ? e.which : e.keyCode;
  24808. var character = _characterFromEvent(e);
  24809. // no character found then stop
  24810. if (!character) {
  24811. return;
  24812. }
  24813. if (e.type == 'keyup' && _ignore_next_keyup == character) {
  24814. _ignore_next_keyup = false;
  24815. return;
  24816. }
  24817. _handleCharacter(character, e);
  24818. }
  24819. /**
  24820. * determines if the keycode specified is a modifier key or not
  24821. *
  24822. * @param {string} key
  24823. * @returns {boolean}
  24824. */
  24825. function _isModifier(key) {
  24826. return key == 'shift' || key == 'ctrl' || key == 'alt' || key == 'meta';
  24827. }
  24828. /**
  24829. * called to set a 1 second timeout on the specified sequence
  24830. *
  24831. * this is so after each key press in the sequence you have 1 second
  24832. * to press the next key before you have to start over
  24833. *
  24834. * @returns void
  24835. */
  24836. function _resetSequenceTimer() {
  24837. clearTimeout(_reset_timer);
  24838. _reset_timer = setTimeout(_resetSequences, 1000);
  24839. }
  24840. /**
  24841. * reverses the map lookup so that we can look for specific keys
  24842. * to see what can and can't use keypress
  24843. *
  24844. * @return {Object}
  24845. */
  24846. function _getReverseMap() {
  24847. if (!_REVERSE_MAP) {
  24848. _REVERSE_MAP = {};
  24849. for (var key in _MAP) {
  24850. // pull out the numeric keypad from here cause keypress should
  24851. // be able to detect the keys from the character
  24852. if (key > 95 && key < 112) {
  24853. continue;
  24854. }
  24855. if (_MAP.hasOwnProperty(key)) {
  24856. _REVERSE_MAP[_MAP[key]] = key;
  24857. }
  24858. }
  24859. }
  24860. return _REVERSE_MAP;
  24861. }
  24862. /**
  24863. * picks the best action based on the key combination
  24864. *
  24865. * @param {string} key - character for key
  24866. * @param {Array} modifiers
  24867. * @param {string=} action passed in
  24868. */
  24869. function _pickBestAction(key, modifiers, action) {
  24870. // if no action was picked in we should try to pick the one
  24871. // that we think would work best for this key
  24872. if (!action) {
  24873. action = _getReverseMap()[key] ? 'keydown' : 'keypress';
  24874. }
  24875. // modifier keys don't work as expected with keypress,
  24876. // switch to keydown
  24877. if (action == 'keypress' && modifiers.length) {
  24878. action = 'keydown';
  24879. }
  24880. return action;
  24881. }
  24882. /**
  24883. * binds a key sequence to an event
  24884. *
  24885. * @param {string} combo - combo specified in bind call
  24886. * @param {Array} keys
  24887. * @param {Function} callback
  24888. * @param {string=} action
  24889. * @returns void
  24890. */
  24891. function _bindSequence(combo, keys, callback, action) {
  24892. // start off by adding a sequence level record for this combination
  24893. // and setting the level to 0
  24894. _sequence_levels[combo] = 0;
  24895. // if there is no action pick the best one for the first key
  24896. // in the sequence
  24897. if (!action) {
  24898. action = _pickBestAction(keys[0], []);
  24899. }
  24900. /**
  24901. * callback to increase the sequence level for this sequence and reset
  24902. * all other sequences that were active
  24903. *
  24904. * @param {Event} e
  24905. * @returns void
  24906. */
  24907. var _increaseSequence = function(e) {
  24908. _inside_sequence = action;
  24909. ++_sequence_levels[combo];
  24910. _resetSequenceTimer();
  24911. },
  24912. /**
  24913. * wraps the specified callback inside of another function in order
  24914. * to reset all sequence counters as soon as this sequence is done
  24915. *
  24916. * @param {Event} e
  24917. * @returns void
  24918. */
  24919. _callbackAndReset = function(e) {
  24920. _fireCallback(callback, e);
  24921. // we should ignore the next key up if the action is key down
  24922. // or keypress. this is so if you finish a sequence and
  24923. // release the key the final key will not trigger a keyup
  24924. if (action !== 'keyup') {
  24925. _ignore_next_keyup = _characterFromEvent(e);
  24926. }
  24927. // weird race condition if a sequence ends with the key
  24928. // another sequence begins with
  24929. setTimeout(_resetSequences, 10);
  24930. },
  24931. i;
  24932. // loop through keys one at a time and bind the appropriate callback
  24933. // function. for any key leading up to the final one it should
  24934. // increase the sequence. after the final, it should reset all sequences
  24935. for (i = 0; i < keys.length; ++i) {
  24936. _bindSingle(keys[i], i < keys.length - 1 ? _increaseSequence : _callbackAndReset, action, combo, i);
  24937. }
  24938. }
  24939. /**
  24940. * binds a single keyboard combination
  24941. *
  24942. * @param {string} combination
  24943. * @param {Function} callback
  24944. * @param {string=} action
  24945. * @param {string=} sequence_name - name of sequence if part of sequence
  24946. * @param {number=} level - what part of the sequence the command is
  24947. * @returns void
  24948. */
  24949. function _bindSingle(combination, callback, action, sequence_name, level) {
  24950. // make sure multiple spaces in a row become a single space
  24951. combination = combination.replace(/\s+/g, ' ');
  24952. var sequence = combination.split(' '),
  24953. i,
  24954. key,
  24955. keys,
  24956. modifiers = [];
  24957. // if this pattern is a sequence of keys then run through this method
  24958. // to reprocess each pattern one key at a time
  24959. if (sequence.length > 1) {
  24960. return _bindSequence(combination, sequence, callback, action);
  24961. }
  24962. // take the keys from this pattern and figure out what the actual
  24963. // pattern is all about
  24964. keys = combination === '+' ? ['+'] : combination.split('+');
  24965. for (i = 0; i < keys.length; ++i) {
  24966. key = keys[i];
  24967. // normalize key names
  24968. if (_SPECIAL_ALIASES[key]) {
  24969. key = _SPECIAL_ALIASES[key];
  24970. }
  24971. // if this is not a keypress event then we should
  24972. // be smart about using shift keys
  24973. // this will only work for US keyboards however
  24974. if (action && action != 'keypress' && _SHIFT_MAP[key]) {
  24975. key = _SHIFT_MAP[key];
  24976. modifiers.push('shift');
  24977. }
  24978. // if this key is a modifier then add it to the list of modifiers
  24979. if (_isModifier(key)) {
  24980. modifiers.push(key);
  24981. }
  24982. }
  24983. // depending on what the key combination is
  24984. // we will try to pick the best event for it
  24985. action = _pickBestAction(key, modifiers, action);
  24986. // make sure to initialize array if this is the first time
  24987. // a callback is added for this key
  24988. if (!_callbacks[key]) {
  24989. _callbacks[key] = [];
  24990. }
  24991. // remove an existing match if there is one
  24992. _getMatches(key, modifiers, action, !sequence_name, combination);
  24993. // add this call back to the array
  24994. // if it is a sequence put it at the beginning
  24995. // if not put it at the end
  24996. //
  24997. // this is important because the way these are processed expects
  24998. // the sequence ones to come first
  24999. _callbacks[key][sequence_name ? 'unshift' : 'push']({
  25000. callback: callback,
  25001. modifiers: modifiers,
  25002. action: action,
  25003. seq: sequence_name,
  25004. level: level,
  25005. combo: combination
  25006. });
  25007. }
  25008. /**
  25009. * binds multiple combinations to the same callback
  25010. *
  25011. * @param {Array} combinations
  25012. * @param {Function} callback
  25013. * @param {string|undefined} action
  25014. * @returns void
  25015. */
  25016. function _bindMultiple(combinations, callback, action) {
  25017. for (var i = 0; i < combinations.length; ++i) {
  25018. _bindSingle(combinations[i], callback, action);
  25019. }
  25020. }
  25021. // start!
  25022. _addEvent(document, 'keypress', _handleKey);
  25023. _addEvent(document, 'keydown', _handleKey);
  25024. _addEvent(document, 'keyup', _handleKey);
  25025. var mousetrap = {
  25026. /**
  25027. * binds an event to mousetrap
  25028. *
  25029. * can be a single key, a combination of keys separated with +,
  25030. * a comma separated list of keys, an array of keys, or
  25031. * a sequence of keys separated by spaces
  25032. *
  25033. * be sure to list the modifier keys first to make sure that the
  25034. * correct key ends up getting bound (the last key in the pattern)
  25035. *
  25036. * @param {string|Array} keys
  25037. * @param {Function} callback
  25038. * @param {string=} action - 'keypress', 'keydown', or 'keyup'
  25039. * @returns void
  25040. */
  25041. bind: function(keys, callback, action) {
  25042. _bindMultiple(keys instanceof Array ? keys : [keys], callback, action);
  25043. _direct_map[keys + ':' + action] = callback;
  25044. return this;
  25045. },
  25046. /**
  25047. * unbinds an event to mousetrap
  25048. *
  25049. * the unbinding sets the callback function of the specified key combo
  25050. * to an empty function and deletes the corresponding key in the
  25051. * _direct_map dict.
  25052. *
  25053. * the keycombo+action has to be exactly the same as
  25054. * it was defined in the bind method
  25055. *
  25056. * TODO: actually remove this from the _callbacks dictionary instead
  25057. * of binding an empty function
  25058. *
  25059. * @param {string|Array} keys
  25060. * @param {string} action
  25061. * @returns void
  25062. */
  25063. unbind: function(keys, action) {
  25064. if (_direct_map[keys + ':' + action]) {
  25065. delete _direct_map[keys + ':' + action];
  25066. this.bind(keys, function() {}, action);
  25067. }
  25068. return this;
  25069. },
  25070. /**
  25071. * triggers an event that has already been bound
  25072. *
  25073. * @param {string} keys
  25074. * @param {string=} action
  25075. * @returns void
  25076. */
  25077. trigger: function(keys, action) {
  25078. _direct_map[keys + ':' + action]();
  25079. return this;
  25080. },
  25081. /**
  25082. * resets the library back to its initial state. this is useful
  25083. * if you want to clear out the current keyboard shortcuts and bind
  25084. * new ones - for example if you switch to another page
  25085. *
  25086. * @returns void
  25087. */
  25088. reset: function() {
  25089. _callbacks = {};
  25090. _direct_map = {};
  25091. return this;
  25092. }
  25093. };
  25094. module.exports = mousetrap;
  25095. /***/ },
  25096. /* 57 */
  25097. /***/ function(module, exports, __webpack_require__) {
  25098. /**
  25099. * Creation of the ClusterMixin var.
  25100. *
  25101. * This contains all the functions the Network object can use to employ clustering
  25102. */
  25103. /**
  25104. * This is only called in the constructor of the network object
  25105. *
  25106. */
  25107. exports.startWithClustering = function() {
  25108. // cluster if the data set is big
  25109. this.clusterToFit(this.constants.clustering.initialMaxNodes, true);
  25110. // updates the lables after clustering
  25111. this.updateLabels();
  25112. // this is called here because if clusterin is disabled, the start and stabilize are called in
  25113. // the setData function.
  25114. if (this.stabilize) {
  25115. this._stabilize();
  25116. }
  25117. this.start();
  25118. };
  25119. /**
  25120. * This function clusters until the initialMaxNodes has been reached
  25121. *
  25122. * @param {Number} maxNumberOfNodes
  25123. * @param {Boolean} reposition
  25124. */
  25125. exports.clusterToFit = function(maxNumberOfNodes, reposition) {
  25126. var numberOfNodes = this.nodeIndices.length;
  25127. var maxLevels = 50;
  25128. var level = 0;
  25129. // we first cluster the hubs, then we pull in the outliers, repeat
  25130. while (numberOfNodes > maxNumberOfNodes && level < maxLevels) {
  25131. if (level % 3 == 0) {
  25132. this.forceAggregateHubs(true);
  25133. this.normalizeClusterLevels();
  25134. }
  25135. else {
  25136. this.increaseClusterLevel(); // this also includes a cluster normalization
  25137. }
  25138. numberOfNodes = this.nodeIndices.length;
  25139. level += 1;
  25140. }
  25141. // after the clustering we reposition the nodes to reduce the initial chaos
  25142. if (level > 0 && reposition == true) {
  25143. this.repositionNodes();
  25144. }
  25145. this._updateCalculationNodes();
  25146. };
  25147. /**
  25148. * This function can be called to open up a specific cluster. It is only called by
  25149. * It will unpack the cluster back one level.
  25150. *
  25151. * @param node | Node object: cluster to open.
  25152. */
  25153. exports.openCluster = function(node) {
  25154. var isMovingBeforeClustering = this.moving;
  25155. if (node.clusterSize > this.constants.clustering.sectorThreshold && this._nodeInActiveArea(node) &&
  25156. !(this._sector() == "default" && this.nodeIndices.length == 1)) {
  25157. // this loads a new sector, loads the nodes and edges and nodeIndices of it.
  25158. this._addSector(node);
  25159. var level = 0;
  25160. // we decluster until we reach a decent number of nodes
  25161. while ((this.nodeIndices.length < this.constants.clustering.initialMaxNodes) && (level < 10)) {
  25162. this.decreaseClusterLevel();
  25163. level += 1;
  25164. }
  25165. }
  25166. else {
  25167. this._expandClusterNode(node,false,true);
  25168. // update the index list, dynamic edges and labels
  25169. this._updateNodeIndexList();
  25170. this._updateDynamicEdges();
  25171. this._updateCalculationNodes();
  25172. this.updateLabels();
  25173. }
  25174. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  25175. if (this.moving != isMovingBeforeClustering) {
  25176. this.start();
  25177. }
  25178. };
  25179. /**
  25180. * This calls the updateClustes with default arguments
  25181. */
  25182. exports.updateClustersDefault = function() {
  25183. if (this.constants.clustering.enabled == true) {
  25184. this.updateClusters(0,false,false);
  25185. }
  25186. };
  25187. /**
  25188. * This function can be called to increase the cluster level. This means that the nodes with only one edge connection will
  25189. * be clustered with their connected node. This can be repeated as many times as needed.
  25190. * This can be called externally (by a keybind for instance) to reduce the complexity of big datasets.
  25191. */
  25192. exports.increaseClusterLevel = function() {
  25193. this.updateClusters(-1,false,true);
  25194. };
  25195. /**
  25196. * This function can be called to decrease the cluster level. This means that the nodes with only one edge connection will
  25197. * be unpacked if they are a cluster. This can be repeated as many times as needed.
  25198. * This can be called externally (by a key-bind for instance) to look into clusters without zooming.
  25199. */
  25200. exports.decreaseClusterLevel = function() {
  25201. this.updateClusters(1,false,true);
  25202. };
  25203. /**
  25204. * This is the main clustering function. It clusters and declusters on zoom or forced
  25205. * This function clusters on zoom, it can be called with a predefined zoom direction
  25206. * If out, check if we can form clusters, if in, check if we can open clusters.
  25207. * This function is only called from _zoom()
  25208. *
  25209. * @param {Number} zoomDirection | -1 / 0 / +1 for zoomOut / determineByZoom / zoomIn
  25210. * @param {Boolean} recursive | enabled or disable recursive calling of the opening of clusters
  25211. * @param {Boolean} force | enabled or disable forcing
  25212. * @param {Boolean} doNotStart | if true do not call start
  25213. *
  25214. */
  25215. exports.updateClusters = function(zoomDirection,recursive,force,doNotStart) {
  25216. var isMovingBeforeClustering = this.moving;
  25217. var amountOfNodes = this.nodeIndices.length;
  25218. // on zoom out collapse the sector if the scale is at the level the sector was made
  25219. if (this.previousScale > this.scale && zoomDirection == 0) {
  25220. this._collapseSector();
  25221. }
  25222. // check if we zoom in or out
  25223. if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
  25224. // forming clusters when forced pulls outliers in. When not forced, the edge length of the
  25225. // outer nodes determines if it is being clustered
  25226. this._formClusters(force);
  25227. }
  25228. else if (this.previousScale < this.scale || zoomDirection == 1) { // zoom in
  25229. if (force == true) {
  25230. // _openClusters checks for each node if the formationScale of the cluster is smaller than
  25231. // the current scale and if so, declusters. When forced, all clusters are reduced by one step
  25232. this._openClusters(recursive,force);
  25233. }
  25234. else {
  25235. // if a cluster takes up a set percentage of the active window
  25236. this._openClustersBySize();
  25237. }
  25238. }
  25239. this._updateNodeIndexList();
  25240. // if a cluster was NOT formed and the user zoomed out, we try clustering by hubs
  25241. if (this.nodeIndices.length == amountOfNodes && (this.previousScale > this.scale || zoomDirection == -1)) {
  25242. this._aggregateHubs(force);
  25243. this._updateNodeIndexList();
  25244. }
  25245. // we now reduce chains.
  25246. if (this.previousScale > this.scale || zoomDirection == -1) { // zoom out
  25247. this.handleChains();
  25248. this._updateNodeIndexList();
  25249. }
  25250. this.previousScale = this.scale;
  25251. // rest of the update the index list, dynamic edges and labels
  25252. this._updateDynamicEdges();
  25253. this.updateLabels();
  25254. // if a cluster was formed, we increase the clusterSession
  25255. if (this.nodeIndices.length < amountOfNodes) { // this means a clustering operation has taken place
  25256. this.clusterSession += 1;
  25257. // if clusters have been made, we normalize the cluster level
  25258. this.normalizeClusterLevels();
  25259. }
  25260. if (doNotStart == false || doNotStart === undefined) {
  25261. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  25262. if (this.moving != isMovingBeforeClustering) {
  25263. this.start();
  25264. }
  25265. }
  25266. this._updateCalculationNodes();
  25267. };
  25268. /**
  25269. * This function handles the chains. It is called on every updateClusters().
  25270. */
  25271. exports.handleChains = function() {
  25272. // after clustering we check how many chains there are
  25273. var chainPercentage = this._getChainFraction();
  25274. if (chainPercentage > this.constants.clustering.chainThreshold) {
  25275. this._reduceAmountOfChains(1 - this.constants.clustering.chainThreshold / chainPercentage)
  25276. }
  25277. };
  25278. /**
  25279. * this functions starts clustering by hubs
  25280. * The minimum hub threshold is set globally
  25281. *
  25282. * @private
  25283. */
  25284. exports._aggregateHubs = function(force) {
  25285. this._getHubSize();
  25286. this._formClustersByHub(force,false);
  25287. };
  25288. /**
  25289. * This function is fired by keypress. It forces hubs to form.
  25290. *
  25291. */
  25292. exports.forceAggregateHubs = function(doNotStart) {
  25293. var isMovingBeforeClustering = this.moving;
  25294. var amountOfNodes = this.nodeIndices.length;
  25295. this._aggregateHubs(true);
  25296. // update the index list, dynamic edges and labels
  25297. this._updateNodeIndexList();
  25298. this._updateDynamicEdges();
  25299. this.updateLabels();
  25300. // if a cluster was formed, we increase the clusterSession
  25301. if (this.nodeIndices.length != amountOfNodes) {
  25302. this.clusterSession += 1;
  25303. }
  25304. if (doNotStart == false || doNotStart === undefined) {
  25305. // if the simulation was settled, we restart the simulation if a cluster has been formed or expanded
  25306. if (this.moving != isMovingBeforeClustering) {
  25307. this.start();
  25308. }
  25309. }
  25310. };
  25311. /**
  25312. * If a cluster takes up more than a set percentage of the screen, open the cluster
  25313. *
  25314. * @private
  25315. */
  25316. exports._openClustersBySize = function() {
  25317. for (var nodeId in this.nodes) {
  25318. if (this.nodes.hasOwnProperty(nodeId)) {
  25319. var node = this.nodes[nodeId];
  25320. if (node.inView() == true) {
  25321. if ((node.width*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  25322. (node.height*this.scale > this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  25323. this.openCluster(node);
  25324. }
  25325. }
  25326. }
  25327. }
  25328. };
  25329. /**
  25330. * This function loops over all nodes in the nodeIndices list. For each node it checks if it is a cluster and if it
  25331. * has to be opened based on the current zoom level.
  25332. *
  25333. * @private
  25334. */
  25335. exports._openClusters = function(recursive,force) {
  25336. for (var i = 0; i < this.nodeIndices.length; i++) {
  25337. var node = this.nodes[this.nodeIndices[i]];
  25338. this._expandClusterNode(node,recursive,force);
  25339. this._updateCalculationNodes();
  25340. }
  25341. };
  25342. /**
  25343. * This function checks if a node has to be opened. This is done by checking the zoom level.
  25344. * If the node contains child nodes, this function is recursively called on the child nodes as well.
  25345. * This recursive behaviour is optional and can be set by the recursive argument.
  25346. *
  25347. * @param {Node} parentNode | to check for cluster and expand
  25348. * @param {Boolean} recursive | enabled or disable recursive calling
  25349. * @param {Boolean} force | enabled or disable forcing
  25350. * @param {Boolean} [openAll] | This will recursively force all nodes in the parent to be released
  25351. * @private
  25352. */
  25353. exports._expandClusterNode = function(parentNode, recursive, force, openAll) {
  25354. // first check if node is a cluster
  25355. if (parentNode.clusterSize > 1) {
  25356. // this means that on a double tap event or a zoom event, the cluster fully unpacks if it is smaller than 20
  25357. if (parentNode.clusterSize < this.constants.clustering.sectorThreshold) {
  25358. openAll = true;
  25359. }
  25360. recursive = openAll ? true : recursive;
  25361. // if the last child has been added on a smaller scale than current scale decluster
  25362. if (parentNode.formationScale < this.scale || force == true) {
  25363. // we will check if any of the contained child nodes should be removed from the cluster
  25364. for (var containedNodeId in parentNode.containedNodes) {
  25365. if (parentNode.containedNodes.hasOwnProperty(containedNodeId)) {
  25366. var childNode = parentNode.containedNodes[containedNodeId];
  25367. // force expand will expand the largest cluster size clusters. Since we cluster from outside in, we assume that
  25368. // the largest cluster is the one that comes from outside
  25369. if (force == true) {
  25370. if (childNode.clusterSession == parentNode.clusterSessions[parentNode.clusterSessions.length-1]
  25371. || openAll) {
  25372. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  25373. }
  25374. }
  25375. else {
  25376. if (this._nodeInActiveArea(parentNode)) {
  25377. this._expelChildFromParent(parentNode,containedNodeId,recursive,force,openAll);
  25378. }
  25379. }
  25380. }
  25381. }
  25382. }
  25383. }
  25384. };
  25385. /**
  25386. * ONLY CALLED FROM _expandClusterNode
  25387. *
  25388. * This function will expel a child_node from a parent_node. This is to de-cluster the node. This function will remove
  25389. * the child node from the parent contained_node object and put it back into the global nodes object.
  25390. * The same holds for the edge that was connected to the child node. It is moved back into the global edges object.
  25391. *
  25392. * @param {Node} parentNode | the parent node
  25393. * @param {String} containedNodeId | child_node id as it is contained in the containedNodes object of the parent node
  25394. * @param {Boolean} recursive | This will also check if the child needs to be expanded.
  25395. * With force and recursive both true, the entire cluster is unpacked
  25396. * @param {Boolean} force | This will disregard the zoom level and will expel this child from the parent
  25397. * @param {Boolean} openAll | This will recursively force all nodes in the parent to be released
  25398. * @private
  25399. */
  25400. exports._expelChildFromParent = function(parentNode, containedNodeId, recursive, force, openAll) {
  25401. var childNode = parentNode.containedNodes[containedNodeId];
  25402. // if child node has been added on smaller scale than current, kick out
  25403. if (childNode.formationScale < this.scale || force == true) {
  25404. // unselect all selected items
  25405. this._unselectAll();
  25406. // put the child node back in the global nodes object
  25407. this.nodes[containedNodeId] = childNode;
  25408. // release the contained edges from this childNode back into the global edges
  25409. this._releaseContainedEdges(parentNode,childNode);
  25410. // reconnect rerouted edges to the childNode
  25411. this._connectEdgeBackToChild(parentNode,childNode);
  25412. // validate all edges in dynamicEdges
  25413. this._validateEdges(parentNode);
  25414. // undo the changes from the clustering operation on the parent node
  25415. parentNode.options.mass -= childNode.options.mass;
  25416. parentNode.clusterSize -= childNode.clusterSize;
  25417. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*(parentNode.clusterSize-1));
  25418. parentNode.dynamicEdgesLength = parentNode.dynamicEdges.length;
  25419. // place the child node near the parent, not at the exact same location to avoid chaos in the system
  25420. childNode.x = parentNode.x + parentNode.growthIndicator * (0.5 - Math.random());
  25421. childNode.y = parentNode.y + parentNode.growthIndicator * (0.5 - Math.random());
  25422. // remove node from the list
  25423. delete parentNode.containedNodes[containedNodeId];
  25424. // check if there are other childs with this clusterSession in the parent.
  25425. var othersPresent = false;
  25426. for (var childNodeId in parentNode.containedNodes) {
  25427. if (parentNode.containedNodes.hasOwnProperty(childNodeId)) {
  25428. if (parentNode.containedNodes[childNodeId].clusterSession == childNode.clusterSession) {
  25429. othersPresent = true;
  25430. break;
  25431. }
  25432. }
  25433. }
  25434. // if there are no others, remove the cluster session from the list
  25435. if (othersPresent == false) {
  25436. parentNode.clusterSessions.pop();
  25437. }
  25438. this._repositionBezierNodes(childNode);
  25439. // this._repositionBezierNodes(parentNode);
  25440. // remove the clusterSession from the child node
  25441. childNode.clusterSession = 0;
  25442. // recalculate the size of the node on the next time the node is rendered
  25443. parentNode.clearSizeCache();
  25444. // restart the simulation to reorganise all nodes
  25445. this.moving = true;
  25446. }
  25447. // check if a further expansion step is possible if recursivity is enabled
  25448. if (recursive == true) {
  25449. this._expandClusterNode(childNode,recursive,force,openAll);
  25450. }
  25451. };
  25452. /**
  25453. * position the bezier nodes at the center of the edges
  25454. *
  25455. * @param node
  25456. * @private
  25457. */
  25458. exports._repositionBezierNodes = function(node) {
  25459. for (var i = 0; i < node.dynamicEdges.length; i++) {
  25460. node.dynamicEdges[i].positionBezierNode();
  25461. }
  25462. };
  25463. /**
  25464. * This function checks if any nodes at the end of their trees have edges below a threshold length
  25465. * This function is called only from updateClusters()
  25466. * forceLevelCollapse ignores the length of the edge and collapses one level
  25467. * This means that a node with only one edge will be clustered with its connected node
  25468. *
  25469. * @private
  25470. * @param {Boolean} force
  25471. */
  25472. exports._formClusters = function(force) {
  25473. if (force == false) {
  25474. this._formClustersByZoom();
  25475. }
  25476. else {
  25477. this._forceClustersByZoom();
  25478. }
  25479. };
  25480. /**
  25481. * This function handles the clustering by zooming out, this is based on a minimum edge distance
  25482. *
  25483. * @private
  25484. */
  25485. exports._formClustersByZoom = function() {
  25486. var dx,dy,length,
  25487. minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  25488. // check if any edges are shorter than minLength and start the clustering
  25489. // the clustering favours the node with the larger mass
  25490. for (var edgeId in this.edges) {
  25491. if (this.edges.hasOwnProperty(edgeId)) {
  25492. var edge = this.edges[edgeId];
  25493. if (edge.connected) {
  25494. if (edge.toId != edge.fromId) {
  25495. dx = (edge.to.x - edge.from.x);
  25496. dy = (edge.to.y - edge.from.y);
  25497. length = Math.sqrt(dx * dx + dy * dy);
  25498. if (length < minLength) {
  25499. // first check which node is larger
  25500. var parentNode = edge.from;
  25501. var childNode = edge.to;
  25502. if (edge.to.options.mass > edge.from.options.mass) {
  25503. parentNode = edge.to;
  25504. childNode = edge.from;
  25505. }
  25506. if (childNode.dynamicEdgesLength == 1) {
  25507. this._addToCluster(parentNode,childNode,false);
  25508. }
  25509. else if (parentNode.dynamicEdgesLength == 1) {
  25510. this._addToCluster(childNode,parentNode,false);
  25511. }
  25512. }
  25513. }
  25514. }
  25515. }
  25516. }
  25517. };
  25518. /**
  25519. * This function forces the network to cluster all nodes with only one connecting edge to their
  25520. * connected node.
  25521. *
  25522. * @private
  25523. */
  25524. exports._forceClustersByZoom = function() {
  25525. for (var nodeId in this.nodes) {
  25526. // another node could have absorbed this child.
  25527. if (this.nodes.hasOwnProperty(nodeId)) {
  25528. var childNode = this.nodes[nodeId];
  25529. // the edges can be swallowed by another decrease
  25530. if (childNode.dynamicEdgesLength == 1 && childNode.dynamicEdges.length != 0) {
  25531. var edge = childNode.dynamicEdges[0];
  25532. var parentNode = (edge.toId == childNode.id) ? this.nodes[edge.fromId] : this.nodes[edge.toId];
  25533. // group to the largest node
  25534. if (childNode.id != parentNode.id) {
  25535. if (parentNode.options.mass > childNode.options.mass) {
  25536. this._addToCluster(parentNode,childNode,true);
  25537. }
  25538. else {
  25539. this._addToCluster(childNode,parentNode,true);
  25540. }
  25541. }
  25542. }
  25543. }
  25544. }
  25545. };
  25546. /**
  25547. * To keep the nodes of roughly equal size we normalize the cluster levels.
  25548. * This function clusters a node to its smallest connected neighbour.
  25549. *
  25550. * @param node
  25551. * @private
  25552. */
  25553. exports._clusterToSmallestNeighbour = function(node) {
  25554. var smallestNeighbour = -1;
  25555. var smallestNeighbourNode = null;
  25556. for (var i = 0; i < node.dynamicEdges.length; i++) {
  25557. if (node.dynamicEdges[i] !== undefined) {
  25558. var neighbour = null;
  25559. if (node.dynamicEdges[i].fromId != node.id) {
  25560. neighbour = node.dynamicEdges[i].from;
  25561. }
  25562. else if (node.dynamicEdges[i].toId != node.id) {
  25563. neighbour = node.dynamicEdges[i].to;
  25564. }
  25565. if (neighbour != null && smallestNeighbour > neighbour.clusterSessions.length) {
  25566. smallestNeighbour = neighbour.clusterSessions.length;
  25567. smallestNeighbourNode = neighbour;
  25568. }
  25569. }
  25570. }
  25571. if (neighbour != null && this.nodes[neighbour.id] !== undefined) {
  25572. this._addToCluster(neighbour, node, true);
  25573. }
  25574. };
  25575. /**
  25576. * This function forms clusters from hubs, it loops over all nodes
  25577. *
  25578. * @param {Boolean} force | Disregard zoom level
  25579. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  25580. * @private
  25581. */
  25582. exports._formClustersByHub = function(force, onlyEqual) {
  25583. // we loop over all nodes in the list
  25584. for (var nodeId in this.nodes) {
  25585. // we check if it is still available since it can be used by the clustering in this loop
  25586. if (this.nodes.hasOwnProperty(nodeId)) {
  25587. this._formClusterFromHub(this.nodes[nodeId],force,onlyEqual);
  25588. }
  25589. }
  25590. };
  25591. /**
  25592. * This function forms a cluster from a specific preselected hub node
  25593. *
  25594. * @param {Node} hubNode | the node we will cluster as a hub
  25595. * @param {Boolean} force | Disregard zoom level
  25596. * @param {Boolean} onlyEqual | This only clusters a hub with a specific number of edges
  25597. * @param {Number} [absorptionSizeOffset] |
  25598. * @private
  25599. */
  25600. exports._formClusterFromHub = function(hubNode, force, onlyEqual, absorptionSizeOffset) {
  25601. if (absorptionSizeOffset === undefined) {
  25602. absorptionSizeOffset = 0;
  25603. }
  25604. // we decide if the node is a hub
  25605. if ((hubNode.dynamicEdgesLength >= this.hubThreshold && onlyEqual == false) ||
  25606. (hubNode.dynamicEdgesLength == this.hubThreshold && onlyEqual == true)) {
  25607. // initialize variables
  25608. var dx,dy,length;
  25609. var minLength = this.constants.clustering.clusterEdgeThreshold/this.scale;
  25610. var allowCluster = false;
  25611. // we create a list of edges because the dynamicEdges change over the course of this loop
  25612. var edgesIdarray = [];
  25613. var amountOfInitialEdges = hubNode.dynamicEdges.length;
  25614. for (var j = 0; j < amountOfInitialEdges; j++) {
  25615. edgesIdarray.push(hubNode.dynamicEdges[j].id);
  25616. }
  25617. // if the hub clustering is not forces, we check if one of the edges connected
  25618. // to a cluster is small enough based on the constants.clustering.clusterEdgeThreshold
  25619. if (force == false) {
  25620. allowCluster = false;
  25621. for (j = 0; j < amountOfInitialEdges; j++) {
  25622. var edge = this.edges[edgesIdarray[j]];
  25623. if (edge !== undefined) {
  25624. if (edge.connected) {
  25625. if (edge.toId != edge.fromId) {
  25626. dx = (edge.to.x - edge.from.x);
  25627. dy = (edge.to.y - edge.from.y);
  25628. length = Math.sqrt(dx * dx + dy * dy);
  25629. if (length < minLength) {
  25630. allowCluster = true;
  25631. break;
  25632. }
  25633. }
  25634. }
  25635. }
  25636. }
  25637. }
  25638. // start the clustering if allowed
  25639. if ((!force && allowCluster) || force) {
  25640. // we loop over all edges INITIALLY connected to this hub
  25641. for (j = 0; j < amountOfInitialEdges; j++) {
  25642. edge = this.edges[edgesIdarray[j]];
  25643. // the edge can be clustered by this function in a previous loop
  25644. if (edge !== undefined) {
  25645. var childNode = this.nodes[(edge.fromId == hubNode.id) ? edge.toId : edge.fromId];
  25646. // we do not want hubs to merge with other hubs nor do we want to cluster itself.
  25647. if ((childNode.dynamicEdges.length <= (this.hubThreshold + absorptionSizeOffset)) &&
  25648. (childNode.id != hubNode.id)) {
  25649. this._addToCluster(hubNode,childNode,force);
  25650. }
  25651. }
  25652. }
  25653. }
  25654. }
  25655. };
  25656. /**
  25657. * This function adds the child node to the parent node, creating a cluster if it is not already.
  25658. *
  25659. * @param {Node} parentNode | this is the node that will house the child node
  25660. * @param {Node} childNode | this node will be deleted from the global this.nodes and stored in the parent node
  25661. * @param {Boolean} force | true will only update the remainingEdges at the very end of the clustering, ensuring single level collapse
  25662. * @private
  25663. */
  25664. exports._addToCluster = function(parentNode, childNode, force) {
  25665. // join child node in the parent node
  25666. parentNode.containedNodes[childNode.id] = childNode;
  25667. // manage all the edges connected to the child and parent nodes
  25668. for (var i = 0; i < childNode.dynamicEdges.length; i++) {
  25669. var edge = childNode.dynamicEdges[i];
  25670. if (edge.toId == parentNode.id || edge.fromId == parentNode.id) { // edge connected to parentNode
  25671. this._addToContainedEdges(parentNode,childNode,edge);
  25672. }
  25673. else {
  25674. this._connectEdgeToCluster(parentNode,childNode,edge);
  25675. }
  25676. }
  25677. // a contained node has no dynamic edges.
  25678. childNode.dynamicEdges = [];
  25679. // remove circular edges from clusters
  25680. this._containCircularEdgesFromNode(parentNode,childNode);
  25681. // remove the childNode from the global nodes object
  25682. delete this.nodes[childNode.id];
  25683. // update the properties of the child and parent
  25684. var massBefore = parentNode.options.mass;
  25685. childNode.clusterSession = this.clusterSession;
  25686. parentNode.options.mass += childNode.options.mass;
  25687. parentNode.clusterSize += childNode.clusterSize;
  25688. parentNode.options.fontSize = Math.min(this.constants.clustering.maxFontSize, this.constants.nodes.fontSize + this.constants.clustering.fontSizeMultiplier*parentNode.clusterSize);
  25689. // keep track of the clustersessions so we can open the cluster up as it has been formed.
  25690. if (parentNode.clusterSessions[parentNode.clusterSessions.length - 1] != this.clusterSession) {
  25691. parentNode.clusterSessions.push(this.clusterSession);
  25692. }
  25693. // forced clusters only open from screen size and double tap
  25694. if (force == true) {
  25695. // parentNode.formationScale = Math.pow(1 - (1.0/11.0),this.clusterSession+3);
  25696. parentNode.formationScale = 0;
  25697. }
  25698. else {
  25699. parentNode.formationScale = this.scale; // The latest child has been added on this scale
  25700. }
  25701. // recalculate the size of the node on the next time the node is rendered
  25702. parentNode.clearSizeCache();
  25703. // set the pop-out scale for the childnode
  25704. parentNode.containedNodes[childNode.id].formationScale = parentNode.formationScale;
  25705. // nullify the movement velocity of the child, this is to avoid hectic behaviour
  25706. childNode.clearVelocity();
  25707. // the mass has altered, preservation of energy dictates the velocity to be updated
  25708. parentNode.updateVelocity(massBefore);
  25709. // restart the simulation to reorganise all nodes
  25710. this.moving = true;
  25711. };
  25712. /**
  25713. * This function will apply the changes made to the remainingEdges during the formation of the clusters.
  25714. * This is a seperate function to allow for level-wise collapsing of the node barnesHutTree.
  25715. * It has to be called if a level is collapsed. It is called by _formClusters().
  25716. * @private
  25717. */
  25718. exports._updateDynamicEdges = function() {
  25719. for (var i = 0; i < this.nodeIndices.length; i++) {
  25720. var node = this.nodes[this.nodeIndices[i]];
  25721. node.dynamicEdgesLength = node.dynamicEdges.length;
  25722. // this corrects for multiple edges pointing at the same other node
  25723. var correction = 0;
  25724. if (node.dynamicEdgesLength > 1) {
  25725. for (var j = 0; j < node.dynamicEdgesLength - 1; j++) {
  25726. var edgeToId = node.dynamicEdges[j].toId;
  25727. var edgeFromId = node.dynamicEdges[j].fromId;
  25728. for (var k = j+1; k < node.dynamicEdgesLength; k++) {
  25729. if ((node.dynamicEdges[k].toId == edgeToId && node.dynamicEdges[k].fromId == edgeFromId) ||
  25730. (node.dynamicEdges[k].fromId == edgeToId && node.dynamicEdges[k].toId == edgeFromId)) {
  25731. correction += 1;
  25732. }
  25733. }
  25734. }
  25735. }
  25736. node.dynamicEdgesLength -= correction;
  25737. }
  25738. };
  25739. /**
  25740. * This adds an edge from the childNode to the contained edges of the parent node
  25741. *
  25742. * @param parentNode | Node object
  25743. * @param childNode | Node object
  25744. * @param edge | Edge object
  25745. * @private
  25746. */
  25747. exports._addToContainedEdges = function(parentNode, childNode, edge) {
  25748. // create an array object if it does not yet exist for this childNode
  25749. if (!(parentNode.containedEdges.hasOwnProperty(childNode.id))) {
  25750. parentNode.containedEdges[childNode.id] = []
  25751. }
  25752. // add this edge to the list
  25753. parentNode.containedEdges[childNode.id].push(edge);
  25754. // remove the edge from the global edges object
  25755. delete this.edges[edge.id];
  25756. // remove the edge from the parent object
  25757. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  25758. if (parentNode.dynamicEdges[i].id == edge.id) {
  25759. parentNode.dynamicEdges.splice(i,1);
  25760. break;
  25761. }
  25762. }
  25763. };
  25764. /**
  25765. * This function connects an edge that was connected to a child node to the parent node.
  25766. * It keeps track of which nodes it has been connected to with the originalId array.
  25767. *
  25768. * @param {Node} parentNode | Node object
  25769. * @param {Node} childNode | Node object
  25770. * @param {Edge} edge | Edge object
  25771. * @private
  25772. */
  25773. exports._connectEdgeToCluster = function(parentNode, childNode, edge) {
  25774. // handle circular edges
  25775. if (edge.toId == edge.fromId) {
  25776. this._addToContainedEdges(parentNode, childNode, edge);
  25777. }
  25778. else {
  25779. if (edge.toId == childNode.id) { // edge connected to other node on the "to" side
  25780. edge.originalToId.push(childNode.id);
  25781. edge.to = parentNode;
  25782. edge.toId = parentNode.id;
  25783. }
  25784. else { // edge connected to other node with the "from" side
  25785. edge.originalFromId.push(childNode.id);
  25786. edge.from = parentNode;
  25787. edge.fromId = parentNode.id;
  25788. }
  25789. this._addToReroutedEdges(parentNode,childNode,edge);
  25790. }
  25791. };
  25792. /**
  25793. * If a node is connected to itself, a circular edge is drawn. When clustering we want to contain
  25794. * these edges inside of the cluster.
  25795. *
  25796. * @param parentNode
  25797. * @param childNode
  25798. * @private
  25799. */
  25800. exports._containCircularEdgesFromNode = function(parentNode, childNode) {
  25801. // manage all the edges connected to the child and parent nodes
  25802. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  25803. var edge = parentNode.dynamicEdges[i];
  25804. // handle circular edges
  25805. if (edge.toId == edge.fromId) {
  25806. this._addToContainedEdges(parentNode, childNode, edge);
  25807. }
  25808. }
  25809. };
  25810. /**
  25811. * This adds an edge from the childNode to the rerouted edges of the parent node
  25812. *
  25813. * @param parentNode | Node object
  25814. * @param childNode | Node object
  25815. * @param edge | Edge object
  25816. * @private
  25817. */
  25818. exports._addToReroutedEdges = function(parentNode, childNode, edge) {
  25819. // create an array object if it does not yet exist for this childNode
  25820. // we store the edge in the rerouted edges so we can restore it when the cluster pops open
  25821. if (!(parentNode.reroutedEdges.hasOwnProperty(childNode.id))) {
  25822. parentNode.reroutedEdges[childNode.id] = [];
  25823. }
  25824. parentNode.reroutedEdges[childNode.id].push(edge);
  25825. // this edge becomes part of the dynamicEdges of the cluster node
  25826. parentNode.dynamicEdges.push(edge);
  25827. };
  25828. /**
  25829. * This function connects an edge that was connected to a cluster node back to the child node.
  25830. *
  25831. * @param parentNode | Node object
  25832. * @param childNode | Node object
  25833. * @private
  25834. */
  25835. exports._connectEdgeBackToChild = function(parentNode, childNode) {
  25836. if (parentNode.reroutedEdges.hasOwnProperty(childNode.id)) {
  25837. for (var i = 0; i < parentNode.reroutedEdges[childNode.id].length; i++) {
  25838. var edge = parentNode.reroutedEdges[childNode.id][i];
  25839. if (edge.originalFromId[edge.originalFromId.length-1] == childNode.id) {
  25840. edge.originalFromId.pop();
  25841. edge.fromId = childNode.id;
  25842. edge.from = childNode;
  25843. }
  25844. else {
  25845. edge.originalToId.pop();
  25846. edge.toId = childNode.id;
  25847. edge.to = childNode;
  25848. }
  25849. // append this edge to the list of edges connecting to the childnode
  25850. childNode.dynamicEdges.push(edge);
  25851. // remove the edge from the parent object
  25852. for (var j = 0; j < parentNode.dynamicEdges.length; j++) {
  25853. if (parentNode.dynamicEdges[j].id == edge.id) {
  25854. parentNode.dynamicEdges.splice(j,1);
  25855. break;
  25856. }
  25857. }
  25858. }
  25859. // remove the entry from the rerouted edges
  25860. delete parentNode.reroutedEdges[childNode.id];
  25861. }
  25862. };
  25863. /**
  25864. * When loops are clustered, an edge can be both in the rerouted array and the contained array.
  25865. * This function is called last to verify that all edges in dynamicEdges are in fact connected to the
  25866. * parentNode
  25867. *
  25868. * @param parentNode | Node object
  25869. * @private
  25870. */
  25871. exports._validateEdges = function(parentNode) {
  25872. for (var i = 0; i < parentNode.dynamicEdges.length; i++) {
  25873. var edge = parentNode.dynamicEdges[i];
  25874. if (parentNode.id != edge.toId && parentNode.id != edge.fromId) {
  25875. parentNode.dynamicEdges.splice(i,1);
  25876. }
  25877. }
  25878. };
  25879. /**
  25880. * This function released the contained edges back into the global domain and puts them back into the
  25881. * dynamic edges of both parent and child.
  25882. *
  25883. * @param {Node} parentNode |
  25884. * @param {Node} childNode |
  25885. * @private
  25886. */
  25887. exports._releaseContainedEdges = function(parentNode, childNode) {
  25888. for (var i = 0; i < parentNode.containedEdges[childNode.id].length; i++) {
  25889. var edge = parentNode.containedEdges[childNode.id][i];
  25890. // put the edge back in the global edges object
  25891. this.edges[edge.id] = edge;
  25892. // put the edge back in the dynamic edges of the child and parent
  25893. childNode.dynamicEdges.push(edge);
  25894. parentNode.dynamicEdges.push(edge);
  25895. }
  25896. // remove the entry from the contained edges
  25897. delete parentNode.containedEdges[childNode.id];
  25898. };
  25899. // ------------------- UTILITY FUNCTIONS ---------------------------- //
  25900. /**
  25901. * This updates the node labels for all nodes (for debugging purposes)
  25902. */
  25903. exports.updateLabels = function() {
  25904. var nodeId;
  25905. // update node labels
  25906. for (nodeId in this.nodes) {
  25907. if (this.nodes.hasOwnProperty(nodeId)) {
  25908. var node = this.nodes[nodeId];
  25909. if (node.clusterSize > 1) {
  25910. node.label = "[".concat(String(node.clusterSize),"]");
  25911. }
  25912. }
  25913. }
  25914. // update node labels
  25915. for (nodeId in this.nodes) {
  25916. if (this.nodes.hasOwnProperty(nodeId)) {
  25917. node = this.nodes[nodeId];
  25918. if (node.clusterSize == 1) {
  25919. if (node.originalLabel !== undefined) {
  25920. node.label = node.originalLabel;
  25921. }
  25922. else {
  25923. node.label = String(node.id);
  25924. }
  25925. }
  25926. }
  25927. }
  25928. // /* Debug Override */
  25929. // for (nodeId in this.nodes) {
  25930. // if (this.nodes.hasOwnProperty(nodeId)) {
  25931. // node = this.nodes[nodeId];
  25932. // node.label = String(node.level);
  25933. // }
  25934. // }
  25935. };
  25936. /**
  25937. * We want to keep the cluster level distribution rather small. This means we do not want unclustered nodes
  25938. * if the rest of the nodes are already a few cluster levels in.
  25939. * To fix this we use this function. It determines the min and max cluster level and sends nodes that have not
  25940. * clustered enough to the clusterToSmallestNeighbours function.
  25941. */
  25942. exports.normalizeClusterLevels = function() {
  25943. var maxLevel = 0;
  25944. var minLevel = 1e9;
  25945. var clusterLevel = 0;
  25946. var nodeId;
  25947. // we loop over all nodes in the list
  25948. for (nodeId in this.nodes) {
  25949. if (this.nodes.hasOwnProperty(nodeId)) {
  25950. clusterLevel = this.nodes[nodeId].clusterSessions.length;
  25951. if (maxLevel < clusterLevel) {maxLevel = clusterLevel;}
  25952. if (minLevel > clusterLevel) {minLevel = clusterLevel;}
  25953. }
  25954. }
  25955. if (maxLevel - minLevel > this.constants.clustering.clusterLevelDifference) {
  25956. var amountOfNodes = this.nodeIndices.length;
  25957. var targetLevel = maxLevel - this.constants.clustering.clusterLevelDifference;
  25958. // we loop over all nodes in the list
  25959. for (nodeId in this.nodes) {
  25960. if (this.nodes.hasOwnProperty(nodeId)) {
  25961. if (this.nodes[nodeId].clusterSessions.length < targetLevel) {
  25962. this._clusterToSmallestNeighbour(this.nodes[nodeId]);
  25963. }
  25964. }
  25965. }
  25966. this._updateNodeIndexList();
  25967. this._updateDynamicEdges();
  25968. // if a cluster was formed, we increase the clusterSession
  25969. if (this.nodeIndices.length != amountOfNodes) {
  25970. this.clusterSession += 1;
  25971. }
  25972. }
  25973. };
  25974. /**
  25975. * This function determines if the cluster we want to decluster is in the active area
  25976. * this means around the zoom center
  25977. *
  25978. * @param {Node} node
  25979. * @returns {boolean}
  25980. * @private
  25981. */
  25982. exports._nodeInActiveArea = function(node) {
  25983. return (
  25984. Math.abs(node.x - this.areaCenter.x) <= this.constants.clustering.activeAreaBoxSize/this.scale
  25985. &&
  25986. Math.abs(node.y - this.areaCenter.y) <= this.constants.clustering.activeAreaBoxSize/this.scale
  25987. )
  25988. };
  25989. /**
  25990. * This is an adaptation of the original repositioning function. This is called if the system is clustered initially
  25991. * It puts large clusters away from the center and randomizes the order.
  25992. *
  25993. */
  25994. exports.repositionNodes = function() {
  25995. for (var i = 0; i < this.nodeIndices.length; i++) {
  25996. var node = this.nodes[this.nodeIndices[i]];
  25997. if ((node.xFixed == false || node.yFixed == false)) {
  25998. var radius = 10 * 0.1*this.nodeIndices.length * Math.min(100,node.options.mass);
  25999. var angle = 2 * Math.PI * Math.random();
  26000. if (node.xFixed == false) {node.x = radius * Math.cos(angle);}
  26001. if (node.yFixed == false) {node.y = radius * Math.sin(angle);}
  26002. this._repositionBezierNodes(node);
  26003. }
  26004. }
  26005. };
  26006. /**
  26007. * We determine how many connections denote an important hub.
  26008. * We take the mean + 2*std as the important hub size. (Assuming a normal distribution of data, ~2.2%)
  26009. *
  26010. * @private
  26011. */
  26012. exports._getHubSize = function() {
  26013. var average = 0;
  26014. var averageSquared = 0;
  26015. var hubCounter = 0;
  26016. var largestHub = 0;
  26017. for (var i = 0; i < this.nodeIndices.length; i++) {
  26018. var node = this.nodes[this.nodeIndices[i]];
  26019. if (node.dynamicEdgesLength > largestHub) {
  26020. largestHub = node.dynamicEdgesLength;
  26021. }
  26022. average += node.dynamicEdgesLength;
  26023. averageSquared += Math.pow(node.dynamicEdgesLength,2);
  26024. hubCounter += 1;
  26025. }
  26026. average = average / hubCounter;
  26027. averageSquared = averageSquared / hubCounter;
  26028. var variance = averageSquared - Math.pow(average,2);
  26029. var standardDeviation = Math.sqrt(variance);
  26030. this.hubThreshold = Math.floor(average + 2*standardDeviation);
  26031. // always have at least one to cluster
  26032. if (this.hubThreshold > largestHub) {
  26033. this.hubThreshold = largestHub;
  26034. }
  26035. // console.log("average",average,"averageSQ",averageSquared,"var",variance,"std",standardDeviation);
  26036. // console.log("hubThreshold:",this.hubThreshold);
  26037. };
  26038. /**
  26039. * We reduce the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  26040. * with this amount we can cluster specifically on these chains.
  26041. *
  26042. * @param {Number} fraction | between 0 and 1, the percentage of chains to reduce
  26043. * @private
  26044. */
  26045. exports._reduceAmountOfChains = function(fraction) {
  26046. this.hubThreshold = 2;
  26047. var reduceAmount = Math.floor(this.nodeIndices.length * fraction);
  26048. for (var nodeId in this.nodes) {
  26049. if (this.nodes.hasOwnProperty(nodeId)) {
  26050. if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
  26051. if (reduceAmount > 0) {
  26052. this._formClusterFromHub(this.nodes[nodeId],true,true,1);
  26053. reduceAmount -= 1;
  26054. }
  26055. }
  26056. }
  26057. }
  26058. };
  26059. /**
  26060. * We get the amount of "extension nodes" or chains. These are not quickly clustered with the outliers and hubs methods
  26061. * with this amount we can cluster specifically on these chains.
  26062. *
  26063. * @private
  26064. */
  26065. exports._getChainFraction = function() {
  26066. var chains = 0;
  26067. var total = 0;
  26068. for (var nodeId in this.nodes) {
  26069. if (this.nodes.hasOwnProperty(nodeId)) {
  26070. if (this.nodes[nodeId].dynamicEdgesLength == 2 && this.nodes[nodeId].dynamicEdges.length >= 2) {
  26071. chains += 1;
  26072. }
  26073. total += 1;
  26074. }
  26075. }
  26076. return chains/total;
  26077. };
  26078. /***/ },
  26079. /* 58 */
  26080. /***/ function(module, exports, __webpack_require__) {
  26081. var util = __webpack_require__(1);
  26082. var Node = __webpack_require__(40);
  26083. /**
  26084. * Creation of the SectorMixin var.
  26085. *
  26086. * This contains all the functions the Network object can use to employ the sector system.
  26087. * The sector system is always used by Network, though the benefits only apply to the use of clustering.
  26088. * If clustering is not used, there is no overhead except for a duplicate object with references to nodes and edges.
  26089. */
  26090. /**
  26091. * This function is only called by the setData function of the Network object.
  26092. * This loads the global references into the active sector. This initializes the sector.
  26093. *
  26094. * @private
  26095. */
  26096. exports._putDataInSector = function() {
  26097. this.sectors["active"][this._sector()].nodes = this.nodes;
  26098. this.sectors["active"][this._sector()].edges = this.edges;
  26099. this.sectors["active"][this._sector()].nodeIndices = this.nodeIndices;
  26100. };
  26101. /**
  26102. * /**
  26103. * This function sets the global references to nodes, edges and nodeIndices back to
  26104. * those of the supplied (active) sector. If a type is defined, do the specific type
  26105. *
  26106. * @param {String} sectorId
  26107. * @param {String} [sectorType] | "active" or "frozen"
  26108. * @private
  26109. */
  26110. exports._switchToSector = function(sectorId, sectorType) {
  26111. if (sectorType === undefined || sectorType == "active") {
  26112. this._switchToActiveSector(sectorId);
  26113. }
  26114. else {
  26115. this._switchToFrozenSector(sectorId);
  26116. }
  26117. };
  26118. /**
  26119. * This function sets the global references to nodes, edges and nodeIndices back to
  26120. * those of the supplied active sector.
  26121. *
  26122. * @param sectorId
  26123. * @private
  26124. */
  26125. exports._switchToActiveSector = function(sectorId) {
  26126. this.nodeIndices = this.sectors["active"][sectorId]["nodeIndices"];
  26127. this.nodes = this.sectors["active"][sectorId]["nodes"];
  26128. this.edges = this.sectors["active"][sectorId]["edges"];
  26129. };
  26130. /**
  26131. * This function sets the global references to nodes, edges and nodeIndices back to
  26132. * those of the supplied active sector.
  26133. *
  26134. * @private
  26135. */
  26136. exports._switchToSupportSector = function() {
  26137. this.nodeIndices = this.sectors["support"]["nodeIndices"];
  26138. this.nodes = this.sectors["support"]["nodes"];
  26139. this.edges = this.sectors["support"]["edges"];
  26140. };
  26141. /**
  26142. * This function sets the global references to nodes, edges and nodeIndices back to
  26143. * those of the supplied frozen sector.
  26144. *
  26145. * @param sectorId
  26146. * @private
  26147. */
  26148. exports._switchToFrozenSector = function(sectorId) {
  26149. this.nodeIndices = this.sectors["frozen"][sectorId]["nodeIndices"];
  26150. this.nodes = this.sectors["frozen"][sectorId]["nodes"];
  26151. this.edges = this.sectors["frozen"][sectorId]["edges"];
  26152. };
  26153. /**
  26154. * This function sets the global references to nodes, edges and nodeIndices back to
  26155. * those of the currently active sector.
  26156. *
  26157. * @private
  26158. */
  26159. exports._loadLatestSector = function() {
  26160. this._switchToSector(this._sector());
  26161. };
  26162. /**
  26163. * This function returns the currently active sector Id
  26164. *
  26165. * @returns {String}
  26166. * @private
  26167. */
  26168. exports._sector = function() {
  26169. return this.activeSector[this.activeSector.length-1];
  26170. };
  26171. /**
  26172. * This function returns the previously active sector Id
  26173. *
  26174. * @returns {String}
  26175. * @private
  26176. */
  26177. exports._previousSector = function() {
  26178. if (this.activeSector.length > 1) {
  26179. return this.activeSector[this.activeSector.length-2];
  26180. }
  26181. else {
  26182. throw new TypeError('there are not enough sectors in the this.activeSector array.');
  26183. }
  26184. };
  26185. /**
  26186. * We add the active sector at the end of the this.activeSector array
  26187. * This ensures it is the currently active sector returned by _sector() and it reaches the top
  26188. * of the activeSector stack. When we reverse our steps we move from the end to the beginning of this stack.
  26189. *
  26190. * @param newId
  26191. * @private
  26192. */
  26193. exports._setActiveSector = function(newId) {
  26194. this.activeSector.push(newId);
  26195. };
  26196. /**
  26197. * We remove the currently active sector id from the active sector stack. This happens when
  26198. * we reactivate the previously active sector
  26199. *
  26200. * @private
  26201. */
  26202. exports._forgetLastSector = function() {
  26203. this.activeSector.pop();
  26204. };
  26205. /**
  26206. * This function creates a new active sector with the supplied newId. This newId
  26207. * is the expanding node id.
  26208. *
  26209. * @param {String} newId | Id of the new active sector
  26210. * @private
  26211. */
  26212. exports._createNewSector = function(newId) {
  26213. // create the new sector
  26214. this.sectors["active"][newId] = {"nodes":{},
  26215. "edges":{},
  26216. "nodeIndices":[],
  26217. "formationScale": this.scale,
  26218. "drawingNode": undefined};
  26219. // create the new sector render node. This gives visual feedback that you are in a new sector.
  26220. this.sectors["active"][newId]['drawingNode'] = new Node(
  26221. {id:newId,
  26222. color: {
  26223. background: "#eaefef",
  26224. border: "495c5e"
  26225. }
  26226. },{},{},this.constants);
  26227. this.sectors["active"][newId]['drawingNode'].clusterSize = 2;
  26228. };
  26229. /**
  26230. * This function removes the currently active sector. This is called when we create a new
  26231. * active sector.
  26232. *
  26233. * @param {String} sectorId | Id of the active sector that will be removed
  26234. * @private
  26235. */
  26236. exports._deleteActiveSector = function(sectorId) {
  26237. delete this.sectors["active"][sectorId];
  26238. };
  26239. /**
  26240. * This function removes the currently active sector. This is called when we reactivate
  26241. * the previously active sector.
  26242. *
  26243. * @param {String} sectorId | Id of the active sector that will be removed
  26244. * @private
  26245. */
  26246. exports._deleteFrozenSector = function(sectorId) {
  26247. delete this.sectors["frozen"][sectorId];
  26248. };
  26249. /**
  26250. * Freezing an active sector means moving it from the "active" object to the "frozen" object.
  26251. * We copy the references, then delete the active entree.
  26252. *
  26253. * @param sectorId
  26254. * @private
  26255. */
  26256. exports._freezeSector = function(sectorId) {
  26257. // we move the set references from the active to the frozen stack.
  26258. this.sectors["frozen"][sectorId] = this.sectors["active"][sectorId];
  26259. // we have moved the sector data into the frozen set, we now remove it from the active set
  26260. this._deleteActiveSector(sectorId);
  26261. };
  26262. /**
  26263. * This is the reverse operation of _freezeSector. Activating means moving the sector from the "frozen"
  26264. * object to the "active" object.
  26265. *
  26266. * @param sectorId
  26267. * @private
  26268. */
  26269. exports._activateSector = function(sectorId) {
  26270. // we move the set references from the frozen to the active stack.
  26271. this.sectors["active"][sectorId] = this.sectors["frozen"][sectorId];
  26272. // we have moved the sector data into the active set, we now remove it from the frozen stack
  26273. this._deleteFrozenSector(sectorId);
  26274. };
  26275. /**
  26276. * This function merges the data from the currently active sector with a frozen sector. This is used
  26277. * in the process of reverting back to the previously active sector.
  26278. * The data that is placed in the frozen (the previously active) sector is the node that has been removed from it
  26279. * upon the creation of a new active sector.
  26280. *
  26281. * @param sectorId
  26282. * @private
  26283. */
  26284. exports._mergeThisWithFrozen = function(sectorId) {
  26285. // copy all nodes
  26286. for (var nodeId in this.nodes) {
  26287. if (this.nodes.hasOwnProperty(nodeId)) {
  26288. this.sectors["frozen"][sectorId]["nodes"][nodeId] = this.nodes[nodeId];
  26289. }
  26290. }
  26291. // copy all edges (if not fully clustered, else there are no edges)
  26292. for (var edgeId in this.edges) {
  26293. if (this.edges.hasOwnProperty(edgeId)) {
  26294. this.sectors["frozen"][sectorId]["edges"][edgeId] = this.edges[edgeId];
  26295. }
  26296. }
  26297. // merge the nodeIndices
  26298. for (var i = 0; i < this.nodeIndices.length; i++) {
  26299. this.sectors["frozen"][sectorId]["nodeIndices"].push(this.nodeIndices[i]);
  26300. }
  26301. };
  26302. /**
  26303. * This clusters the sector to one cluster. It was a single cluster before this process started so
  26304. * we revert to that state. The clusterToFit function with a maximum size of 1 node does this.
  26305. *
  26306. * @private
  26307. */
  26308. exports._collapseThisToSingleCluster = function() {
  26309. this.clusterToFit(1,false);
  26310. };
  26311. /**
  26312. * We create a new active sector from the node that we want to open.
  26313. *
  26314. * @param node
  26315. * @private
  26316. */
  26317. exports._addSector = function(node) {
  26318. // this is the currently active sector
  26319. var sector = this._sector();
  26320. // // this should allow me to select nodes from a frozen set.
  26321. // if (this.sectors['active'][sector]["nodes"].hasOwnProperty(node.id)) {
  26322. // console.log("the node is part of the active sector");
  26323. // }
  26324. // else {
  26325. // console.log("I dont know what the fuck happened!!");
  26326. // }
  26327. // when we switch to a new sector, we remove the node that will be expanded from the current nodes list.
  26328. delete this.nodes[node.id];
  26329. var unqiueIdentifier = util.randomUUID();
  26330. // we fully freeze the currently active sector
  26331. this._freezeSector(sector);
  26332. // we create a new active sector. This sector has the Id of the node to ensure uniqueness
  26333. this._createNewSector(unqiueIdentifier);
  26334. // we add the active sector to the sectors array to be able to revert these steps later on
  26335. this._setActiveSector(unqiueIdentifier);
  26336. // we redirect the global references to the new sector's references. this._sector() now returns unqiueIdentifier
  26337. this._switchToSector(this._sector());
  26338. // finally we add the node we removed from our previous active sector to the new active sector
  26339. this.nodes[node.id] = node;
  26340. };
  26341. /**
  26342. * We close the sector that is currently open and revert back to the one before.
  26343. * If the active sector is the "default" sector, nothing happens.
  26344. *
  26345. * @private
  26346. */
  26347. exports._collapseSector = function() {
  26348. // the currently active sector
  26349. var sector = this._sector();
  26350. // we cannot collapse the default sector
  26351. if (sector != "default") {
  26352. if ((this.nodeIndices.length == 1) ||
  26353. (this.sectors["active"][sector]["drawingNode"].width*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientWidth) ||
  26354. (this.sectors["active"][sector]["drawingNode"].height*this.scale < this.constants.clustering.screenSizeThreshold * this.frame.canvas.clientHeight)) {
  26355. var previousSector = this._previousSector();
  26356. // we collapse the sector back to a single cluster
  26357. this._collapseThisToSingleCluster();
  26358. // we move the remaining nodes, edges and nodeIndices to the previous sector.
  26359. // This previous sector is the one we will reactivate
  26360. this._mergeThisWithFrozen(previousSector);
  26361. // the previously active (frozen) sector now has all the data from the currently active sector.
  26362. // we can now delete the active sector.
  26363. this._deleteActiveSector(sector);
  26364. // we activate the previously active (and currently frozen) sector.
  26365. this._activateSector(previousSector);
  26366. // we load the references from the newly active sector into the global references
  26367. this._switchToSector(previousSector);
  26368. // we forget the previously active sector because we reverted to the one before
  26369. this._forgetLastSector();
  26370. // finally, we update the node index list.
  26371. this._updateNodeIndexList();
  26372. // we refresh the list with calulation nodes and calculation node indices.
  26373. this._updateCalculationNodes();
  26374. }
  26375. }
  26376. };
  26377. /**
  26378. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  26379. *
  26380. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  26381. * | we dont pass the function itself because then the "this" is the window object
  26382. * | instead of the Network object
  26383. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  26384. * @private
  26385. */
  26386. exports._doInAllActiveSectors = function(runFunction,argument) {
  26387. var returnValues = [];
  26388. if (argument === undefined) {
  26389. for (var sector in this.sectors["active"]) {
  26390. if (this.sectors["active"].hasOwnProperty(sector)) {
  26391. // switch the global references to those of this sector
  26392. this._switchToActiveSector(sector);
  26393. returnValues.push( this[runFunction]() );
  26394. }
  26395. }
  26396. }
  26397. else {
  26398. for (var sector in this.sectors["active"]) {
  26399. if (this.sectors["active"].hasOwnProperty(sector)) {
  26400. // switch the global references to those of this sector
  26401. this._switchToActiveSector(sector);
  26402. var args = Array.prototype.splice.call(arguments, 1);
  26403. if (args.length > 1) {
  26404. returnValues.push( this[runFunction](args[0],args[1]) );
  26405. }
  26406. else {
  26407. returnValues.push( this[runFunction](argument) );
  26408. }
  26409. }
  26410. }
  26411. }
  26412. // we revert the global references back to our active sector
  26413. this._loadLatestSector();
  26414. return returnValues;
  26415. };
  26416. /**
  26417. * This runs a function in all active sectors. This is used in _redraw() and the _initializeForceCalculation().
  26418. *
  26419. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  26420. * | we dont pass the function itself because then the "this" is the window object
  26421. * | instead of the Network object
  26422. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  26423. * @private
  26424. */
  26425. exports._doInSupportSector = function(runFunction,argument) {
  26426. var returnValues = false;
  26427. if (argument === undefined) {
  26428. this._switchToSupportSector();
  26429. returnValues = this[runFunction]();
  26430. }
  26431. else {
  26432. this._switchToSupportSector();
  26433. var args = Array.prototype.splice.call(arguments, 1);
  26434. if (args.length > 1) {
  26435. returnValues = this[runFunction](args[0],args[1]);
  26436. }
  26437. else {
  26438. returnValues = this[runFunction](argument);
  26439. }
  26440. }
  26441. // we revert the global references back to our active sector
  26442. this._loadLatestSector();
  26443. return returnValues;
  26444. };
  26445. /**
  26446. * This runs a function in all frozen sectors. This is used in the _redraw().
  26447. *
  26448. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  26449. * | we don't pass the function itself because then the "this" is the window object
  26450. * | instead of the Network object
  26451. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  26452. * @private
  26453. */
  26454. exports._doInAllFrozenSectors = function(runFunction,argument) {
  26455. if (argument === undefined) {
  26456. for (var sector in this.sectors["frozen"]) {
  26457. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  26458. // switch the global references to those of this sector
  26459. this._switchToFrozenSector(sector);
  26460. this[runFunction]();
  26461. }
  26462. }
  26463. }
  26464. else {
  26465. for (var sector in this.sectors["frozen"]) {
  26466. if (this.sectors["frozen"].hasOwnProperty(sector)) {
  26467. // switch the global references to those of this sector
  26468. this._switchToFrozenSector(sector);
  26469. var args = Array.prototype.splice.call(arguments, 1);
  26470. if (args.length > 1) {
  26471. this[runFunction](args[0],args[1]);
  26472. }
  26473. else {
  26474. this[runFunction](argument);
  26475. }
  26476. }
  26477. }
  26478. }
  26479. this._loadLatestSector();
  26480. };
  26481. /**
  26482. * This runs a function in all sectors. This is used in the _redraw().
  26483. *
  26484. * @param {String} runFunction | This is the NAME of a function we want to call in all active sectors
  26485. * | we don't pass the function itself because then the "this" is the window object
  26486. * | instead of the Network object
  26487. * @param {*} [argument] | Optional: arguments to pass to the runFunction
  26488. * @private
  26489. */
  26490. exports._doInAllSectors = function(runFunction,argument) {
  26491. var args = Array.prototype.splice.call(arguments, 1);
  26492. if (argument === undefined) {
  26493. this._doInAllActiveSectors(runFunction);
  26494. this._doInAllFrozenSectors(runFunction);
  26495. }
  26496. else {
  26497. if (args.length > 1) {
  26498. this._doInAllActiveSectors(runFunction,args[0],args[1]);
  26499. this._doInAllFrozenSectors(runFunction,args[0],args[1]);
  26500. }
  26501. else {
  26502. this._doInAllActiveSectors(runFunction,argument);
  26503. this._doInAllFrozenSectors(runFunction,argument);
  26504. }
  26505. }
  26506. };
  26507. /**
  26508. * This clears the nodeIndices list. We cannot use this.nodeIndices = [] because we would break the link with the
  26509. * active sector. Thus we clear the nodeIndices in the active sector, then reconnect the this.nodeIndices to it.
  26510. *
  26511. * @private
  26512. */
  26513. exports._clearNodeIndexList = function() {
  26514. var sector = this._sector();
  26515. this.sectors["active"][sector]["nodeIndices"] = [];
  26516. this.nodeIndices = this.sectors["active"][sector]["nodeIndices"];
  26517. };
  26518. /**
  26519. * Draw the encompassing sector node
  26520. *
  26521. * @param ctx
  26522. * @param sectorType
  26523. * @private
  26524. */
  26525. exports._drawSectorNodes = function(ctx,sectorType) {
  26526. var minY = 1e9, maxY = -1e9, minX = 1e9, maxX = -1e9, node;
  26527. for (var sector in this.sectors[sectorType]) {
  26528. if (this.sectors[sectorType].hasOwnProperty(sector)) {
  26529. if (this.sectors[sectorType][sector]["drawingNode"] !== undefined) {
  26530. this._switchToSector(sector,sectorType);
  26531. minY = 1e9; maxY = -1e9; minX = 1e9; maxX = -1e9;
  26532. for (var nodeId in this.nodes) {
  26533. if (this.nodes.hasOwnProperty(nodeId)) {
  26534. node = this.nodes[nodeId];
  26535. node.resize(ctx);
  26536. if (minX > node.x - 0.5 * node.width) {minX = node.x - 0.5 * node.width;}
  26537. if (maxX < node.x + 0.5 * node.width) {maxX = node.x + 0.5 * node.width;}
  26538. if (minY > node.y - 0.5 * node.height) {minY = node.y - 0.5 * node.height;}
  26539. if (maxY < node.y + 0.5 * node.height) {maxY = node.y + 0.5 * node.height;}
  26540. }
  26541. }
  26542. node = this.sectors[sectorType][sector]["drawingNode"];
  26543. node.x = 0.5 * (maxX + minX);
  26544. node.y = 0.5 * (maxY + minY);
  26545. node.width = 2 * (node.x - minX);
  26546. node.height = 2 * (node.y - minY);
  26547. node.options.radius = Math.sqrt(Math.pow(0.5*node.width,2) + Math.pow(0.5*node.height,2));
  26548. node.setScale(this.scale);
  26549. node._drawCircle(ctx);
  26550. }
  26551. }
  26552. }
  26553. };
  26554. exports._drawAllSectorNodes = function(ctx) {
  26555. this._drawSectorNodes(ctx,"frozen");
  26556. this._drawSectorNodes(ctx,"active");
  26557. this._loadLatestSector();
  26558. };
  26559. /***/ },
  26560. /* 59 */
  26561. /***/ function(module, exports, __webpack_require__) {
  26562. var Node = __webpack_require__(40);
  26563. /**
  26564. * This function can be called from the _doInAllSectors function
  26565. *
  26566. * @param object
  26567. * @param overlappingNodes
  26568. * @private
  26569. */
  26570. exports._getNodesOverlappingWith = function(object, overlappingNodes) {
  26571. var nodes = this.nodes;
  26572. for (var nodeId in nodes) {
  26573. if (nodes.hasOwnProperty(nodeId)) {
  26574. if (nodes[nodeId].isOverlappingWith(object)) {
  26575. overlappingNodes.push(nodeId);
  26576. }
  26577. }
  26578. }
  26579. };
  26580. /**
  26581. * retrieve all nodes overlapping with given object
  26582. * @param {Object} object An object with parameters left, top, right, bottom
  26583. * @return {Number[]} An array with id's of the overlapping nodes
  26584. * @private
  26585. */
  26586. exports._getAllNodesOverlappingWith = function (object) {
  26587. var overlappingNodes = [];
  26588. this._doInAllActiveSectors("_getNodesOverlappingWith",object,overlappingNodes);
  26589. return overlappingNodes;
  26590. };
  26591. /**
  26592. * Return a position object in canvasspace from a single point in screenspace
  26593. *
  26594. * @param pointer
  26595. * @returns {{left: number, top: number, right: number, bottom: number}}
  26596. * @private
  26597. */
  26598. exports._pointerToPositionObject = function(pointer) {
  26599. var x = this._XconvertDOMtoCanvas(pointer.x);
  26600. var y = this._YconvertDOMtoCanvas(pointer.y);
  26601. return {
  26602. left: x,
  26603. top: y,
  26604. right: x,
  26605. bottom: y
  26606. };
  26607. };
  26608. /**
  26609. * Get the top node at the a specific point (like a click)
  26610. *
  26611. * @param {{x: Number, y: Number}} pointer
  26612. * @return {Node | null} node
  26613. * @private
  26614. */
  26615. exports._getNodeAt = function (pointer) {
  26616. // we first check if this is an navigation controls element
  26617. var positionObject = this._pointerToPositionObject(pointer);
  26618. var overlappingNodes = this._getAllNodesOverlappingWith(positionObject);
  26619. // if there are overlapping nodes, select the last one, this is the
  26620. // one which is drawn on top of the others
  26621. if (overlappingNodes.length > 0) {
  26622. return this.nodes[overlappingNodes[overlappingNodes.length - 1]];
  26623. }
  26624. else {
  26625. return null;
  26626. }
  26627. };
  26628. /**
  26629. * retrieve all edges overlapping with given object, selector is around center
  26630. * @param {Object} object An object with parameters left, top, right, bottom
  26631. * @return {Number[]} An array with id's of the overlapping nodes
  26632. * @private
  26633. */
  26634. exports._getEdgesOverlappingWith = function (object, overlappingEdges) {
  26635. var edges = this.edges;
  26636. for (var edgeId in edges) {
  26637. if (edges.hasOwnProperty(edgeId)) {
  26638. if (edges[edgeId].isOverlappingWith(object)) {
  26639. overlappingEdges.push(edgeId);
  26640. }
  26641. }
  26642. }
  26643. };
  26644. /**
  26645. * retrieve all nodes overlapping with given object
  26646. * @param {Object} object An object with parameters left, top, right, bottom
  26647. * @return {Number[]} An array with id's of the overlapping nodes
  26648. * @private
  26649. */
  26650. exports._getAllEdgesOverlappingWith = function (object) {
  26651. var overlappingEdges = [];
  26652. this._doInAllActiveSectors("_getEdgesOverlappingWith",object,overlappingEdges);
  26653. return overlappingEdges;
  26654. };
  26655. /**
  26656. * Place holder. To implement change the _getNodeAt to a _getObjectAt. Have the _getObjectAt call
  26657. * _getNodeAt and _getEdgesAt, then priortize the selection to user preferences.
  26658. *
  26659. * @param pointer
  26660. * @returns {null}
  26661. * @private
  26662. */
  26663. exports._getEdgeAt = function(pointer) {
  26664. var positionObject = this._pointerToPositionObject(pointer);
  26665. var overlappingEdges = this._getAllEdgesOverlappingWith(positionObject);
  26666. if (overlappingEdges.length > 0) {
  26667. return this.edges[overlappingEdges[overlappingEdges.length - 1]];
  26668. }
  26669. else {
  26670. return null;
  26671. }
  26672. };
  26673. /**
  26674. * Add object to the selection array.
  26675. *
  26676. * @param obj
  26677. * @private
  26678. */
  26679. exports._addToSelection = function(obj) {
  26680. if (obj instanceof Node) {
  26681. this.selectionObj.nodes[obj.id] = obj;
  26682. }
  26683. else {
  26684. this.selectionObj.edges[obj.id] = obj;
  26685. }
  26686. };
  26687. /**
  26688. * Add object to the selection array.
  26689. *
  26690. * @param obj
  26691. * @private
  26692. */
  26693. exports._addToHover = function(obj) {
  26694. if (obj instanceof Node) {
  26695. this.hoverObj.nodes[obj.id] = obj;
  26696. }
  26697. else {
  26698. this.hoverObj.edges[obj.id] = obj;
  26699. }
  26700. };
  26701. /**
  26702. * Remove a single option from selection.
  26703. *
  26704. * @param {Object} obj
  26705. * @private
  26706. */
  26707. exports._removeFromSelection = function(obj) {
  26708. if (obj instanceof Node) {
  26709. delete this.selectionObj.nodes[obj.id];
  26710. }
  26711. else {
  26712. delete this.selectionObj.edges[obj.id];
  26713. }
  26714. };
  26715. /**
  26716. * Unselect all. The selectionObj is useful for this.
  26717. *
  26718. * @param {Boolean} [doNotTrigger] | ignore trigger
  26719. * @private
  26720. */
  26721. exports._unselectAll = function(doNotTrigger) {
  26722. if (doNotTrigger === undefined) {
  26723. doNotTrigger = false;
  26724. }
  26725. for(var nodeId in this.selectionObj.nodes) {
  26726. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  26727. this.selectionObj.nodes[nodeId].unselect();
  26728. }
  26729. }
  26730. for(var edgeId in this.selectionObj.edges) {
  26731. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  26732. this.selectionObj.edges[edgeId].unselect();
  26733. }
  26734. }
  26735. this.selectionObj = {nodes:{},edges:{}};
  26736. if (doNotTrigger == false) {
  26737. this.emit('select', this.getSelection());
  26738. }
  26739. };
  26740. /**
  26741. * Unselect all clusters. The selectionObj is useful for this.
  26742. *
  26743. * @param {Boolean} [doNotTrigger] | ignore trigger
  26744. * @private
  26745. */
  26746. exports._unselectClusters = function(doNotTrigger) {
  26747. if (doNotTrigger === undefined) {
  26748. doNotTrigger = false;
  26749. }
  26750. for (var nodeId in this.selectionObj.nodes) {
  26751. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  26752. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  26753. this.selectionObj.nodes[nodeId].unselect();
  26754. this._removeFromSelection(this.selectionObj.nodes[nodeId]);
  26755. }
  26756. }
  26757. }
  26758. if (doNotTrigger == false) {
  26759. this.emit('select', this.getSelection());
  26760. }
  26761. };
  26762. /**
  26763. * return the number of selected nodes
  26764. *
  26765. * @returns {number}
  26766. * @private
  26767. */
  26768. exports._getSelectedNodeCount = function() {
  26769. var count = 0;
  26770. for (var nodeId in this.selectionObj.nodes) {
  26771. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  26772. count += 1;
  26773. }
  26774. }
  26775. return count;
  26776. };
  26777. /**
  26778. * return the selected node
  26779. *
  26780. * @returns {number}
  26781. * @private
  26782. */
  26783. exports._getSelectedNode = function() {
  26784. for (var nodeId in this.selectionObj.nodes) {
  26785. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  26786. return this.selectionObj.nodes[nodeId];
  26787. }
  26788. }
  26789. return null;
  26790. };
  26791. /**
  26792. * return the selected edge
  26793. *
  26794. * @returns {number}
  26795. * @private
  26796. */
  26797. exports._getSelectedEdge = function() {
  26798. for (var edgeId in this.selectionObj.edges) {
  26799. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  26800. return this.selectionObj.edges[edgeId];
  26801. }
  26802. }
  26803. return null;
  26804. };
  26805. /**
  26806. * return the number of selected edges
  26807. *
  26808. * @returns {number}
  26809. * @private
  26810. */
  26811. exports._getSelectedEdgeCount = function() {
  26812. var count = 0;
  26813. for (var edgeId in this.selectionObj.edges) {
  26814. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  26815. count += 1;
  26816. }
  26817. }
  26818. return count;
  26819. };
  26820. /**
  26821. * return the number of selected objects.
  26822. *
  26823. * @returns {number}
  26824. * @private
  26825. */
  26826. exports._getSelectedObjectCount = function() {
  26827. var count = 0;
  26828. for(var nodeId in this.selectionObj.nodes) {
  26829. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  26830. count += 1;
  26831. }
  26832. }
  26833. for(var edgeId in this.selectionObj.edges) {
  26834. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  26835. count += 1;
  26836. }
  26837. }
  26838. return count;
  26839. };
  26840. /**
  26841. * Check if anything is selected
  26842. *
  26843. * @returns {boolean}
  26844. * @private
  26845. */
  26846. exports._selectionIsEmpty = function() {
  26847. for(var nodeId in this.selectionObj.nodes) {
  26848. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  26849. return false;
  26850. }
  26851. }
  26852. for(var edgeId in this.selectionObj.edges) {
  26853. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  26854. return false;
  26855. }
  26856. }
  26857. return true;
  26858. };
  26859. /**
  26860. * check if one of the selected nodes is a cluster.
  26861. *
  26862. * @returns {boolean}
  26863. * @private
  26864. */
  26865. exports._clusterInSelection = function() {
  26866. for(var nodeId in this.selectionObj.nodes) {
  26867. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  26868. if (this.selectionObj.nodes[nodeId].clusterSize > 1) {
  26869. return true;
  26870. }
  26871. }
  26872. }
  26873. return false;
  26874. };
  26875. /**
  26876. * select the edges connected to the node that is being selected
  26877. *
  26878. * @param {Node} node
  26879. * @private
  26880. */
  26881. exports._selectConnectedEdges = function(node) {
  26882. for (var i = 0; i < node.dynamicEdges.length; i++) {
  26883. var edge = node.dynamicEdges[i];
  26884. edge.select();
  26885. this._addToSelection(edge);
  26886. }
  26887. };
  26888. /**
  26889. * select the edges connected to the node that is being selected
  26890. *
  26891. * @param {Node} node
  26892. * @private
  26893. */
  26894. exports._hoverConnectedEdges = function(node) {
  26895. for (var i = 0; i < node.dynamicEdges.length; i++) {
  26896. var edge = node.dynamicEdges[i];
  26897. edge.hover = true;
  26898. this._addToHover(edge);
  26899. }
  26900. };
  26901. /**
  26902. * unselect the edges connected to the node that is being selected
  26903. *
  26904. * @param {Node} node
  26905. * @private
  26906. */
  26907. exports._unselectConnectedEdges = function(node) {
  26908. for (var i = 0; i < node.dynamicEdges.length; i++) {
  26909. var edge = node.dynamicEdges[i];
  26910. edge.unselect();
  26911. this._removeFromSelection(edge);
  26912. }
  26913. };
  26914. /**
  26915. * This is called when someone clicks on a node. either select or deselect it.
  26916. * If there is an existing selection and we don't want to append to it, clear the existing selection
  26917. *
  26918. * @param {Node || Edge} object
  26919. * @param {Boolean} append
  26920. * @param {Boolean} [doNotTrigger] | ignore trigger
  26921. * @private
  26922. */
  26923. exports._selectObject = function(object, append, doNotTrigger, highlightEdges, overrideSelectable) {
  26924. if (doNotTrigger === undefined) {
  26925. doNotTrigger = false;
  26926. }
  26927. if (highlightEdges === undefined) {
  26928. highlightEdges = true;
  26929. }
  26930. if (this._selectionIsEmpty() == false && append == false && this.forceAppendSelection == false) {
  26931. this._unselectAll(true);
  26932. }
  26933. // selectable allows the object to be selected. Override can be used if needed to bypass this.
  26934. if (object.selected == false && (this.constants.selectable == true || overrideSelectable)) {
  26935. object.select();
  26936. this._addToSelection(object);
  26937. if (object instanceof Node && this.blockConnectingEdgeSelection == false && highlightEdges == true) {
  26938. this._selectConnectedEdges(object);
  26939. }
  26940. }
  26941. // do not select the object if selectable is false, only add it to selection to allow drag to work
  26942. else if (object.selected == false) {
  26943. this._addToSelection(object);
  26944. doNotTrigger = true;
  26945. }
  26946. else {
  26947. object.unselect();
  26948. this._removeFromSelection(object);
  26949. }
  26950. if (doNotTrigger == false) {
  26951. this.emit('select', this.getSelection());
  26952. }
  26953. };
  26954. /**
  26955. * This is called when someone clicks on a node. either select or deselect it.
  26956. * If there is an existing selection and we don't want to append to it, clear the existing selection
  26957. *
  26958. * @param {Node || Edge} object
  26959. * @private
  26960. */
  26961. exports._blurObject = function(object) {
  26962. if (object.hover == true) {
  26963. object.hover = false;
  26964. this.emit("blurNode",{node:object.id});
  26965. }
  26966. };
  26967. /**
  26968. * This is called when someone clicks on a node. either select or deselect it.
  26969. * If there is an existing selection and we don't want to append to it, clear the existing selection
  26970. *
  26971. * @param {Node || Edge} object
  26972. * @private
  26973. */
  26974. exports._hoverObject = function(object) {
  26975. if (object.hover == false) {
  26976. object.hover = true;
  26977. this._addToHover(object);
  26978. if (object instanceof Node) {
  26979. this.emit("hoverNode",{node:object.id});
  26980. }
  26981. }
  26982. if (object instanceof Node) {
  26983. this._hoverConnectedEdges(object);
  26984. }
  26985. };
  26986. /**
  26987. * handles the selection part of the touch, only for navigation controls elements;
  26988. * Touch is triggered before tap, also before hold. Hold triggers after a while.
  26989. * This is the most responsive solution
  26990. *
  26991. * @param {Object} pointer
  26992. * @private
  26993. */
  26994. exports._handleTouch = function(pointer) {
  26995. };
  26996. /**
  26997. * handles the selection part of the tap;
  26998. *
  26999. * @param {Object} pointer
  27000. * @private
  27001. */
  27002. exports._handleTap = function(pointer) {
  27003. var node = this._getNodeAt(pointer);
  27004. if (node != null) {
  27005. this._selectObject(node, false);
  27006. }
  27007. else {
  27008. var edge = this._getEdgeAt(pointer);
  27009. if (edge != null) {
  27010. this._selectObject(edge, false);
  27011. }
  27012. else {
  27013. this._unselectAll();
  27014. }
  27015. }
  27016. this.emit("click", this.getSelection());
  27017. this._redraw();
  27018. };
  27019. /**
  27020. * handles the selection part of the double tap and opens a cluster if needed
  27021. *
  27022. * @param {Object} pointer
  27023. * @private
  27024. */
  27025. exports._handleDoubleTap = function(pointer) {
  27026. var node = this._getNodeAt(pointer);
  27027. if (node != null && node !== undefined) {
  27028. // we reset the areaCenter here so the opening of the node will occur
  27029. this.areaCenter = {"x" : this._XconvertDOMtoCanvas(pointer.x),
  27030. "y" : this._YconvertDOMtoCanvas(pointer.y)};
  27031. this.openCluster(node);
  27032. }
  27033. this.emit("doubleClick", this.getSelection());
  27034. };
  27035. /**
  27036. * Handle the onHold selection part
  27037. *
  27038. * @param pointer
  27039. * @private
  27040. */
  27041. exports._handleOnHold = function(pointer) {
  27042. var node = this._getNodeAt(pointer);
  27043. if (node != null) {
  27044. this._selectObject(node,true);
  27045. }
  27046. else {
  27047. var edge = this._getEdgeAt(pointer);
  27048. if (edge != null) {
  27049. this._selectObject(edge,true);
  27050. }
  27051. }
  27052. this._redraw();
  27053. };
  27054. /**
  27055. * handle the onRelease event. These functions are here for the navigation controls module
  27056. * and data manipulation module.
  27057. *
  27058. * @private
  27059. */
  27060. exports._handleOnRelease = function(pointer) {
  27061. this._manipulationReleaseOverload(pointer);
  27062. this._navigationReleaseOverload(pointer);
  27063. };
  27064. exports._manipulationReleaseOverload = function (pointer) {};
  27065. exports._navigationReleaseOverload = function (pointer) {};
  27066. /**
  27067. *
  27068. * retrieve the currently selected objects
  27069. * @return {{nodes: Array.<String>, edges: Array.<String>}} selection
  27070. */
  27071. exports.getSelection = function() {
  27072. var nodeIds = this.getSelectedNodes();
  27073. var edgeIds = this.getSelectedEdges();
  27074. return {nodes:nodeIds, edges:edgeIds};
  27075. };
  27076. /**
  27077. *
  27078. * retrieve the currently selected nodes
  27079. * @return {String[]} selection An array with the ids of the
  27080. * selected nodes.
  27081. */
  27082. exports.getSelectedNodes = function() {
  27083. var idArray = [];
  27084. if (this.constants.selectable == true) {
  27085. for (var nodeId in this.selectionObj.nodes) {
  27086. if (this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  27087. idArray.push(nodeId);
  27088. }
  27089. }
  27090. }
  27091. return idArray
  27092. };
  27093. /**
  27094. *
  27095. * retrieve the currently selected edges
  27096. * @return {Array} selection An array with the ids of the
  27097. * selected nodes.
  27098. */
  27099. exports.getSelectedEdges = function() {
  27100. var idArray = [];
  27101. if (this.constants.selectable == true) {
  27102. for (var edgeId in this.selectionObj.edges) {
  27103. if (this.selectionObj.edges.hasOwnProperty(edgeId)) {
  27104. idArray.push(edgeId);
  27105. }
  27106. }
  27107. }
  27108. return idArray;
  27109. };
  27110. /**
  27111. * select zero or more nodes DEPRICATED
  27112. * @param {Number[] | String[]} selection An array with the ids of the
  27113. * selected nodes.
  27114. */
  27115. exports.setSelection = function() {
  27116. console.log("setSelection is deprecated. Please use selectNodes instead.")
  27117. };
  27118. /**
  27119. * select zero or more nodes with the option to highlight edges
  27120. * @param {Number[] | String[]} selection An array with the ids of the
  27121. * selected nodes.
  27122. * @param {boolean} [highlightEdges]
  27123. */
  27124. exports.selectNodes = function(selection, highlightEdges) {
  27125. var i, iMax, id;
  27126. if (!selection || (selection.length == undefined))
  27127. throw 'Selection must be an array with ids';
  27128. // first unselect any selected node
  27129. this._unselectAll(true);
  27130. for (i = 0, iMax = selection.length; i < iMax; i++) {
  27131. id = selection[i];
  27132. var node = this.nodes[id];
  27133. if (!node) {
  27134. throw new RangeError('Node with id "' + id + '" not found');
  27135. }
  27136. this._selectObject(node,true,true,highlightEdges,true);
  27137. }
  27138. this.redraw();
  27139. };
  27140. /**
  27141. * select zero or more edges
  27142. * @param {Number[] | String[]} selection An array with the ids of the
  27143. * selected nodes.
  27144. */
  27145. exports.selectEdges = function(selection) {
  27146. var i, iMax, id;
  27147. if (!selection || (selection.length == undefined))
  27148. throw 'Selection must be an array with ids';
  27149. // first unselect any selected node
  27150. this._unselectAll(true);
  27151. for (i = 0, iMax = selection.length; i < iMax; i++) {
  27152. id = selection[i];
  27153. var edge = this.edges[id];
  27154. if (!edge) {
  27155. throw new RangeError('Edge with id "' + id + '" not found');
  27156. }
  27157. this._selectObject(edge,true,true,false,true);
  27158. }
  27159. this.redraw();
  27160. };
  27161. /**
  27162. * Validate the selection: remove ids of nodes which no longer exist
  27163. * @private
  27164. */
  27165. exports._updateSelection = function () {
  27166. for(var nodeId in this.selectionObj.nodes) {
  27167. if(this.selectionObj.nodes.hasOwnProperty(nodeId)) {
  27168. if (!this.nodes.hasOwnProperty(nodeId)) {
  27169. delete this.selectionObj.nodes[nodeId];
  27170. }
  27171. }
  27172. }
  27173. for(var edgeId in this.selectionObj.edges) {
  27174. if(this.selectionObj.edges.hasOwnProperty(edgeId)) {
  27175. if (!this.edges.hasOwnProperty(edgeId)) {
  27176. delete this.selectionObj.edges[edgeId];
  27177. }
  27178. }
  27179. }
  27180. };
  27181. /***/ },
  27182. /* 60 */
  27183. /***/ function(module, exports, __webpack_require__) {
  27184. var util = __webpack_require__(1);
  27185. var Node = __webpack_require__(40);
  27186. var Edge = __webpack_require__(37);
  27187. /**
  27188. * clears the toolbar div element of children
  27189. *
  27190. * @private
  27191. */
  27192. exports._clearManipulatorBar = function() {
  27193. while (this.manipulationDiv.hasChildNodes()) {
  27194. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  27195. }
  27196. this._manipulationReleaseOverload = function () {};
  27197. delete this.sectors['support']['nodes']['targetNode'];
  27198. delete this.sectors['support']['nodes']['targetViaNode'];
  27199. this.controlNodesActive = false;
  27200. };
  27201. /**
  27202. * Manipulation UI temporarily overloads certain functions to extend or replace them. To be able to restore
  27203. * these functions to their original functionality, we saved them in this.cachedFunctions.
  27204. * This function restores these functions to their original function.
  27205. *
  27206. * @private
  27207. */
  27208. exports._restoreOverloadedFunctions = function() {
  27209. for (var functionName in this.cachedFunctions) {
  27210. if (this.cachedFunctions.hasOwnProperty(functionName)) {
  27211. this[functionName] = this.cachedFunctions[functionName];
  27212. }
  27213. }
  27214. };
  27215. /**
  27216. * Enable or disable edit-mode.
  27217. *
  27218. * @private
  27219. */
  27220. exports._toggleEditMode = function() {
  27221. this.editMode = !this.editMode;
  27222. var toolbar = document.getElementById("network-manipulationDiv");
  27223. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  27224. var editModeDiv = document.getElementById("network-manipulation-editMode");
  27225. if (this.editMode == true) {
  27226. toolbar.style.display="block";
  27227. closeDiv.style.display="block";
  27228. editModeDiv.style.display="none";
  27229. closeDiv.onclick = this._toggleEditMode.bind(this);
  27230. }
  27231. else {
  27232. toolbar.style.display="none";
  27233. closeDiv.style.display="none";
  27234. editModeDiv.style.display="block";
  27235. closeDiv.onclick = null;
  27236. }
  27237. this._createManipulatorBar()
  27238. };
  27239. /**
  27240. * main function, creates the main toolbar. Removes functions bound to the select event. Binds all the buttons of the toolbar.
  27241. *
  27242. * @private
  27243. */
  27244. exports._createManipulatorBar = function() {
  27245. // remove bound functions
  27246. if (this.boundFunction) {
  27247. this.off('select', this.boundFunction);
  27248. }
  27249. var locale = this.constants.locales[this.constants.locale];
  27250. if (this.edgeBeingEdited !== undefined) {
  27251. this.edgeBeingEdited._disableControlNodes();
  27252. this.edgeBeingEdited = undefined;
  27253. this.selectedControlNode = null;
  27254. this.controlNodesActive = false;
  27255. }
  27256. // restore overloaded functions
  27257. this._restoreOverloadedFunctions();
  27258. // resume calculation
  27259. this.freezeSimulation = false;
  27260. // reset global variables
  27261. this.blockConnectingEdgeSelection = false;
  27262. this.forceAppendSelection = false;
  27263. if (this.editMode == true) {
  27264. while (this.manipulationDiv.hasChildNodes()) {
  27265. this.manipulationDiv.removeChild(this.manipulationDiv.firstChild);
  27266. }
  27267. // add the icons to the manipulator div
  27268. this.manipulationDiv.innerHTML = "" +
  27269. "<span class='network-manipulationUI add' id='network-manipulate-addNode'>" +
  27270. "<span class='network-manipulationLabel'>"+locale['addNode'] +"</span></span>" +
  27271. "<div class='network-seperatorLine'></div>" +
  27272. "<span class='network-manipulationUI connect' id='network-manipulate-connectNode'>" +
  27273. "<span class='network-manipulationLabel'>"+locale['addEdge'] +"</span></span>";
  27274. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  27275. this.manipulationDiv.innerHTML += "" +
  27276. "<div class='network-seperatorLine'></div>" +
  27277. "<span class='network-manipulationUI edit' id='network-manipulate-editNode'>" +
  27278. "<span class='network-manipulationLabel'>"+locale['editNode'] +"</span></span>";
  27279. }
  27280. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  27281. this.manipulationDiv.innerHTML += "" +
  27282. "<div class='network-seperatorLine'></div>" +
  27283. "<span class='network-manipulationUI edit' id='network-manipulate-editEdge'>" +
  27284. "<span class='network-manipulationLabel'>"+locale['editEdge'] +"</span></span>";
  27285. }
  27286. if (this._selectionIsEmpty() == false) {
  27287. this.manipulationDiv.innerHTML += "" +
  27288. "<div class='network-seperatorLine'></div>" +
  27289. "<span class='network-manipulationUI delete' id='network-manipulate-delete'>" +
  27290. "<span class='network-manipulationLabel'>"+locale['del'] +"</span></span>";
  27291. }
  27292. // bind the icons
  27293. var addNodeButton = document.getElementById("network-manipulate-addNode");
  27294. addNodeButton.onclick = this._createAddNodeToolbar.bind(this);
  27295. var addEdgeButton = document.getElementById("network-manipulate-connectNode");
  27296. addEdgeButton.onclick = this._createAddEdgeToolbar.bind(this);
  27297. if (this._getSelectedNodeCount() == 1 && this.triggerFunctions.edit) {
  27298. var editButton = document.getElementById("network-manipulate-editNode");
  27299. editButton.onclick = this._editNode.bind(this);
  27300. }
  27301. else if (this._getSelectedEdgeCount() == 1 && this._getSelectedNodeCount() == 0) {
  27302. var editButton = document.getElementById("network-manipulate-editEdge");
  27303. editButton.onclick = this._createEditEdgeToolbar.bind(this);
  27304. }
  27305. if (this._selectionIsEmpty() == false) {
  27306. var deleteButton = document.getElementById("network-manipulate-delete");
  27307. deleteButton.onclick = this._deleteSelected.bind(this);
  27308. }
  27309. var closeDiv = document.getElementById("network-manipulation-closeDiv");
  27310. closeDiv.onclick = this._toggleEditMode.bind(this);
  27311. this.boundFunction = this._createManipulatorBar.bind(this);
  27312. this.on('select', this.boundFunction);
  27313. }
  27314. else {
  27315. this.editModeDiv.innerHTML = "" +
  27316. "<span class='network-manipulationUI edit editmode' id='network-manipulate-editModeButton'>" +
  27317. "<span class='network-manipulationLabel'>" + locale['edit'] + "</span></span>";
  27318. var editModeButton = document.getElementById("network-manipulate-editModeButton");
  27319. editModeButton.onclick = this._toggleEditMode.bind(this);
  27320. }
  27321. };
  27322. /**
  27323. * Create the toolbar for adding Nodes
  27324. *
  27325. * @private
  27326. */
  27327. exports._createAddNodeToolbar = function() {
  27328. // clear the toolbar
  27329. this._clearManipulatorBar();
  27330. if (this.boundFunction) {
  27331. this.off('select', this.boundFunction);
  27332. }
  27333. var locale = this.constants.locales[this.constants.locale];
  27334. // create the toolbar contents
  27335. this.manipulationDiv.innerHTML = "" +
  27336. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  27337. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  27338. "<div class='network-seperatorLine'></div>" +
  27339. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  27340. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['addDescription'] + "</span></span>";
  27341. // bind the icon
  27342. var backButton = document.getElementById("network-manipulate-back");
  27343. backButton.onclick = this._createManipulatorBar.bind(this);
  27344. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  27345. this.boundFunction = this._addNode.bind(this);
  27346. this.on('select', this.boundFunction);
  27347. };
  27348. /**
  27349. * create the toolbar to connect nodes
  27350. *
  27351. * @private
  27352. */
  27353. exports._createAddEdgeToolbar = function() {
  27354. // clear the toolbar
  27355. this._clearManipulatorBar();
  27356. this._unselectAll(true);
  27357. this.freezeSimulation = true;
  27358. var locale = this.constants.locales[this.constants.locale];
  27359. if (this.boundFunction) {
  27360. this.off('select', this.boundFunction);
  27361. }
  27362. this._unselectAll();
  27363. this.forceAppendSelection = false;
  27364. this.blockConnectingEdgeSelection = true;
  27365. this.manipulationDiv.innerHTML = "" +
  27366. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  27367. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  27368. "<div class='network-seperatorLine'></div>" +
  27369. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  27370. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['edgeDescription'] + "</span></span>";
  27371. // bind the icon
  27372. var backButton = document.getElementById("network-manipulate-back");
  27373. backButton.onclick = this._createManipulatorBar.bind(this);
  27374. // we use the boundFunction so we can reference it when we unbind it from the "select" event.
  27375. this.boundFunction = this._handleConnect.bind(this);
  27376. this.on('select', this.boundFunction);
  27377. // temporarily overload functions
  27378. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  27379. this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
  27380. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  27381. this.cachedFunctions["_handleDragEnd"] = this._handleDragEnd;
  27382. this._handleTouch = this._handleConnect;
  27383. this._manipulationReleaseOverload = function () {};
  27384. this._handleDragStart = function () {};
  27385. this._handleDragEnd = this._finishConnect;
  27386. // redraw to show the unselect
  27387. this._redraw();
  27388. };
  27389. /**
  27390. * create the toolbar to edit edges
  27391. *
  27392. * @private
  27393. */
  27394. exports._createEditEdgeToolbar = function() {
  27395. // clear the toolbar
  27396. this._clearManipulatorBar();
  27397. this.controlNodesActive = true;
  27398. if (this.boundFunction) {
  27399. this.off('select', this.boundFunction);
  27400. }
  27401. this.edgeBeingEdited = this._getSelectedEdge();
  27402. this.edgeBeingEdited._enableControlNodes();
  27403. var locale = this.constants.locales[this.constants.locale];
  27404. this.manipulationDiv.innerHTML = "" +
  27405. "<span class='network-manipulationUI back' id='network-manipulate-back'>" +
  27406. "<span class='network-manipulationLabel'>" + locale['back'] + " </span></span>" +
  27407. "<div class='network-seperatorLine'></div>" +
  27408. "<span class='network-manipulationUI none' id='network-manipulate-back'>" +
  27409. "<span id='network-manipulatorLabel' class='network-manipulationLabel'>" + locale['editEdgeDescription'] + "</span></span>";
  27410. // bind the icon
  27411. var backButton = document.getElementById("network-manipulate-back");
  27412. backButton.onclick = this._createManipulatorBar.bind(this);
  27413. // temporarily overload functions
  27414. this.cachedFunctions["_handleTouch"] = this._handleTouch;
  27415. this.cachedFunctions["_manipulationReleaseOverload"] = this._manipulationReleaseOverload;
  27416. this.cachedFunctions["_handleTap"] = this._handleTap;
  27417. this.cachedFunctions["_handleDragStart"] = this._handleDragStart;
  27418. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  27419. this._handleTouch = this._selectControlNode;
  27420. this._handleTap = function () {};
  27421. this._handleOnDrag = this._controlNodeDrag;
  27422. this._handleDragStart = function () {}
  27423. this._manipulationReleaseOverload = this._releaseControlNode;
  27424. // redraw to show the unselect
  27425. this._redraw();
  27426. };
  27427. /**
  27428. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  27429. * to walk the user through the process.
  27430. *
  27431. * @private
  27432. */
  27433. exports._selectControlNode = function(pointer) {
  27434. this.edgeBeingEdited.controlNodes.from.unselect();
  27435. this.edgeBeingEdited.controlNodes.to.unselect();
  27436. this.selectedControlNode = this.edgeBeingEdited._getSelectedControlNode(this._XconvertDOMtoCanvas(pointer.x),this._YconvertDOMtoCanvas(pointer.y));
  27437. if (this.selectedControlNode !== null) {
  27438. this.selectedControlNode.select();
  27439. this.freezeSimulation = true;
  27440. }
  27441. this._redraw();
  27442. };
  27443. /**
  27444. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  27445. * to walk the user through the process.
  27446. *
  27447. * @private
  27448. */
  27449. exports._controlNodeDrag = function(event) {
  27450. var pointer = this._getPointer(event.gesture.center);
  27451. if (this.selectedControlNode !== null && this.selectedControlNode !== undefined) {
  27452. this.selectedControlNode.x = this._XconvertDOMtoCanvas(pointer.x);
  27453. this.selectedControlNode.y = this._YconvertDOMtoCanvas(pointer.y);
  27454. }
  27455. this._redraw();
  27456. };
  27457. exports._releaseControlNode = function(pointer) {
  27458. var newNode = this._getNodeAt(pointer);
  27459. if (newNode != null) {
  27460. if (this.edgeBeingEdited.controlNodes.from.selected == true) {
  27461. this._editEdge(newNode.id, this.edgeBeingEdited.to.id);
  27462. this.edgeBeingEdited.controlNodes.from.unselect();
  27463. }
  27464. if (this.edgeBeingEdited.controlNodes.to.selected == true) {
  27465. this._editEdge(this.edgeBeingEdited.from.id, newNode.id);
  27466. this.edgeBeingEdited.controlNodes.to.unselect();
  27467. }
  27468. }
  27469. else {
  27470. this.edgeBeingEdited._restoreControlNodes();
  27471. }
  27472. this.freezeSimulation = false;
  27473. this._redraw();
  27474. };
  27475. /**
  27476. * the function bound to the selection event. It checks if you want to connect a cluster and changes the description
  27477. * to walk the user through the process.
  27478. *
  27479. * @private
  27480. */
  27481. exports._handleConnect = function(pointer) {
  27482. if (this._getSelectedNodeCount() == 0) {
  27483. var node = this._getNodeAt(pointer);
  27484. if (node != null) {
  27485. if (node.clusterSize > 1) {
  27486. alert(this.constants.locales[this.constants.locale]['createEdgeError'])
  27487. }
  27488. else {
  27489. this._selectObject(node,false);
  27490. var supportNodes = this.sectors['support']['nodes'];
  27491. // create a node the temporary line can look at
  27492. supportNodes['targetNode'] = new Node({id:'targetNode'},{},{},this.constants);
  27493. var targetNode = supportNodes['targetNode'];
  27494. targetNode.x = node.x;
  27495. targetNode.y = node.y;
  27496. // create a temporary edge
  27497. this.edges['connectionEdge'] = new Edge({id:"connectionEdge",from:node.id,to:targetNode.id}, this, this.constants);
  27498. var connectionEdge = this.edges['connectionEdge'];
  27499. connectionEdge.from = node;
  27500. connectionEdge.connected = true;
  27501. connectionEdge.options.smoothCurves = {enabled: true,
  27502. dynamic: false,
  27503. type: "continuous",
  27504. roundness: 0.5
  27505. };
  27506. connectionEdge.selected = true;
  27507. connectionEdge.to = targetNode;
  27508. this.cachedFunctions["_handleOnDrag"] = this._handleOnDrag;
  27509. this._handleOnDrag = function(event) {
  27510. var pointer = this._getPointer(event.gesture.center);
  27511. var connectionEdge = this.edges['connectionEdge'];
  27512. connectionEdge.to.x = this._XconvertDOMtoCanvas(pointer.x);
  27513. connectionEdge.to.y = this._YconvertDOMtoCanvas(pointer.y);
  27514. };
  27515. this.moving = true;
  27516. this.start();
  27517. }
  27518. }
  27519. }
  27520. };
  27521. exports._finishConnect = function(event) {
  27522. if (this._getSelectedNodeCount() == 1) {
  27523. var pointer = this._getPointer(event.gesture.center);
  27524. // restore the drag function
  27525. this._handleOnDrag = this.cachedFunctions["_handleOnDrag"];
  27526. delete this.cachedFunctions["_handleOnDrag"];
  27527. // remember the edge id
  27528. var connectFromId = this.edges['connectionEdge'].fromId;
  27529. // remove the temporary nodes and edge
  27530. delete this.edges['connectionEdge'];
  27531. delete this.sectors['support']['nodes']['targetNode'];
  27532. delete this.sectors['support']['nodes']['targetViaNode'];
  27533. var node = this._getNodeAt(pointer);
  27534. if (node != null) {
  27535. if (node.clusterSize > 1) {
  27536. alert(this.constants.locales[this.constants.locale]["createEdgeError"])
  27537. }
  27538. else {
  27539. this._createEdge(connectFromId,node.id);
  27540. this._createManipulatorBar();
  27541. }
  27542. }
  27543. this._unselectAll();
  27544. }
  27545. };
  27546. /**
  27547. * Adds a node on the specified location
  27548. */
  27549. exports._addNode = function() {
  27550. if (this._selectionIsEmpty() && this.editMode == true) {
  27551. var positionObject = this._pointerToPositionObject(this.pointerPosition);
  27552. var defaultData = {id:util.randomUUID(),x:positionObject.left,y:positionObject.top,label:"new",allowedToMoveX:true,allowedToMoveY:true};
  27553. if (this.triggerFunctions.add) {
  27554. if (this.triggerFunctions.add.length == 2) {
  27555. var me = this;
  27556. this.triggerFunctions.add(defaultData, function(finalizedData) {
  27557. me.nodesData.add(finalizedData);
  27558. me._createManipulatorBar();
  27559. me.moving = true;
  27560. me.start();
  27561. });
  27562. }
  27563. else {
  27564. throw new Error('The function for add does not support two arguments (data,callback)');
  27565. this._createManipulatorBar();
  27566. this.moving = true;
  27567. this.start();
  27568. }
  27569. }
  27570. else {
  27571. this.nodesData.add(defaultData);
  27572. this._createManipulatorBar();
  27573. this.moving = true;
  27574. this.start();
  27575. }
  27576. }
  27577. };
  27578. /**
  27579. * connect two nodes with a new edge.
  27580. *
  27581. * @private
  27582. */
  27583. exports._createEdge = function(sourceNodeId,targetNodeId) {
  27584. if (this.editMode == true) {
  27585. var defaultData = {from:sourceNodeId, to:targetNodeId};
  27586. if (this.triggerFunctions.connect) {
  27587. if (this.triggerFunctions.connect.length == 2) {
  27588. var me = this;
  27589. this.triggerFunctions.connect(defaultData, function(finalizedData) {
  27590. me.edgesData.add(finalizedData);
  27591. me.moving = true;
  27592. me.start();
  27593. });
  27594. }
  27595. else {
  27596. throw new Error('The function for connect does not support two arguments (data,callback)');
  27597. this.moving = true;
  27598. this.start();
  27599. }
  27600. }
  27601. else {
  27602. this.edgesData.add(defaultData);
  27603. this.moving = true;
  27604. this.start();
  27605. }
  27606. }
  27607. };
  27608. /**
  27609. * connect two nodes with a new edge.
  27610. *
  27611. * @private
  27612. */
  27613. exports._editEdge = function(sourceNodeId,targetNodeId) {
  27614. if (this.editMode == true) {
  27615. var defaultData = {id: this.edgeBeingEdited.id, from:sourceNodeId, to:targetNodeId};
  27616. if (this.triggerFunctions.editEdge) {
  27617. if (this.triggerFunctions.editEdge.length == 2) {
  27618. var me = this;
  27619. this.triggerFunctions.editEdge(defaultData, function(finalizedData) {
  27620. me.edgesData.update(finalizedData);
  27621. me.moving = true;
  27622. me.start();
  27623. });
  27624. }
  27625. else {
  27626. throw new Error('The function for edit does not support two arguments (data, callback)');
  27627. this.moving = true;
  27628. this.start();
  27629. }
  27630. }
  27631. else {
  27632. this.edgesData.update(defaultData);
  27633. this.moving = true;
  27634. this.start();
  27635. }
  27636. }
  27637. };
  27638. /**
  27639. * Create the toolbar to edit the selected node. The label and the color can be changed. Other colors are derived from the chosen color.
  27640. *
  27641. * @private
  27642. */
  27643. exports._editNode = function() {
  27644. if (this.triggerFunctions.edit && this.editMode == true) {
  27645. var node = this._getSelectedNode();
  27646. var data = {id:node.id,
  27647. label: node.label,
  27648. group: node.options.group,
  27649. shape: node.options.shape,
  27650. color: {
  27651. background:node.options.color.background,
  27652. border:node.options.color.border,
  27653. highlight: {
  27654. background:node.options.color.highlight.background,
  27655. border:node.options.color.highlight.border
  27656. }
  27657. }};
  27658. if (this.triggerFunctions.edit.length == 2) {
  27659. var me = this;
  27660. this.triggerFunctions.edit(data, function (finalizedData) {
  27661. me.nodesData.update(finalizedData);
  27662. me._createManipulatorBar();
  27663. me.moving = true;
  27664. me.start();
  27665. });
  27666. }
  27667. else {
  27668. throw new Error('The function for edit does not support two arguments (data, callback)');
  27669. }
  27670. }
  27671. else {
  27672. throw new Error('No edit function has been bound to this button');
  27673. }
  27674. };
  27675. /**
  27676. * delete everything in the selection
  27677. *
  27678. * @private
  27679. */
  27680. exports._deleteSelected = function() {
  27681. if (!this._selectionIsEmpty() && this.editMode == true) {
  27682. if (!this._clusterInSelection()) {
  27683. var selectedNodes = this.getSelectedNodes();
  27684. var selectedEdges = this.getSelectedEdges();
  27685. if (this.triggerFunctions.del) {
  27686. var me = this;
  27687. var data = {nodes: selectedNodes, edges: selectedEdges};
  27688. if (this.triggerFunctions.del.length = 2) {
  27689. this.triggerFunctions.del(data, function (finalizedData) {
  27690. me.edgesData.remove(finalizedData.edges);
  27691. me.nodesData.remove(finalizedData.nodes);
  27692. me._unselectAll();
  27693. me.moving = true;
  27694. me.start();
  27695. });
  27696. }
  27697. else {
  27698. throw new Error('The function for delete does not support two arguments (data, callback)')
  27699. }
  27700. }
  27701. else {
  27702. this.edgesData.remove(selectedEdges);
  27703. this.nodesData.remove(selectedNodes);
  27704. this._unselectAll();
  27705. this.moving = true;
  27706. this.start();
  27707. }
  27708. }
  27709. else {
  27710. alert(this.constants.locales[this.constants.locale]["deleteClusterError"]);
  27711. }
  27712. }
  27713. };
  27714. /***/ },
  27715. /* 61 */
  27716. /***/ function(module, exports, __webpack_require__) {
  27717. var util = __webpack_require__(1);
  27718. var Hammer = __webpack_require__(45);
  27719. exports._cleanNavigation = function() {
  27720. // clean hammer bindings
  27721. if (this.navigationHammers.existing.length != 0) {
  27722. for (var i = 0; i < this.navigationHammers.existing.length; i++) {
  27723. this.navigationHammers.existing[i].dispose();
  27724. }
  27725. this.navigationHammers.existing = [];
  27726. }
  27727. this._navigationReleaseOverload = function () {};
  27728. // clean up previous navigation items
  27729. var wrapper = document.getElementById('network-navigation_wrapper');
  27730. if (wrapper && wrapper.parentNode) {
  27731. wrapper.parentNode.removeChild(wrapper);
  27732. }
  27733. };
  27734. /**
  27735. * Creation of the navigation controls nodes. They are drawn over the rest of the nodes and are not affected by scale and translation
  27736. * they have a triggerFunction which is called on click. If the position of the navigation controls is dependent
  27737. * on this.frame.canvas.clientWidth or this.frame.canvas.clientHeight, we flag horizontalAlignLeft and verticalAlignTop false.
  27738. * This means that the location will be corrected by the _relocateNavigation function on a size change of the canvas.
  27739. *
  27740. * @private
  27741. */
  27742. exports._loadNavigationElements = function() {
  27743. this._cleanNavigation();
  27744. this.navigationDivs = {};
  27745. var navigationDivs = ['up','down','left','right','zoomIn','zoomOut','zoomExtends'];
  27746. var navigationDivActions = ['_moveUp','_moveDown','_moveLeft','_moveRight','_zoomIn','_zoomOut','_zoomExtent'];
  27747. this.navigationDivs['wrapper'] = document.createElement('div');
  27748. this.navigationDivs['wrapper'].id = 'network-navigation_wrapper';
  27749. this.frame.appendChild(this.navigationDivs['wrapper']);
  27750. for (var i = 0; i < navigationDivs.length; i++) {
  27751. this.navigationDivs[navigationDivs[i]] = document.createElement('div');
  27752. this.navigationDivs[navigationDivs[i]].id = 'network-navigation_' + navigationDivs[i];
  27753. this.navigationDivs[navigationDivs[i]].className = 'network-navigation ' + navigationDivs[i];
  27754. this.navigationDivs['wrapper'].appendChild(this.navigationDivs[navigationDivs[i]]);
  27755. var hammer = Hammer(this.navigationDivs[navigationDivs[i]], {prevent_default: true});
  27756. hammer.on('touch', this[navigationDivActions[i]].bind(this));
  27757. this.navigationHammers.new.push(hammer);
  27758. }
  27759. this._navigationReleaseOverload = this._stopMovement;
  27760. this.navigationHammers.existing = this.navigationHammers.new;
  27761. };
  27762. /**
  27763. * this stops all movement induced by the navigation buttons
  27764. *
  27765. * @private
  27766. */
  27767. exports._zoomExtent = function(event) {
  27768. this.zoomExtent({duration:800});
  27769. event.stopPropagation();
  27770. };
  27771. /**
  27772. * this stops all movement induced by the navigation buttons
  27773. *
  27774. * @private
  27775. */
  27776. exports._stopMovement = function() {
  27777. this._xStopMoving();
  27778. this._yStopMoving();
  27779. this._stopZoom();
  27780. };
  27781. /**
  27782. * move the screen up
  27783. * By using the increments, instead of adding a fixed number to the translation, we keep fluent and
  27784. * instant movement. The onKeypress event triggers immediately, then pauses, then triggers frequently
  27785. * To avoid this behaviour, we do the translation in the start loop.
  27786. *
  27787. * @private
  27788. */
  27789. exports._moveUp = function(event) {
  27790. this.yIncrement = this.constants.keyboard.speed.y;
  27791. this.start(); // if there is no node movement, the calculation wont be done
  27792. event.preventDefault();
  27793. };
  27794. /**
  27795. * move the screen down
  27796. * @private
  27797. */
  27798. exports._moveDown = function(event) {
  27799. this.yIncrement = -this.constants.keyboard.speed.y;
  27800. this.start(); // if there is no node movement, the calculation wont be done
  27801. event.preventDefault();
  27802. };
  27803. /**
  27804. * move the screen left
  27805. * @private
  27806. */
  27807. exports._moveLeft = function(event) {
  27808. this.xIncrement = this.constants.keyboard.speed.x;
  27809. this.start(); // if there is no node movement, the calculation wont be done
  27810. event.preventDefault();
  27811. };
  27812. /**
  27813. * move the screen right
  27814. * @private
  27815. */
  27816. exports._moveRight = function(event) {
  27817. this.xIncrement = -this.constants.keyboard.speed.y;
  27818. this.start(); // if there is no node movement, the calculation wont be done
  27819. event.preventDefault();
  27820. };
  27821. /**
  27822. * Zoom in, using the same method as the movement.
  27823. * @private
  27824. */
  27825. exports._zoomIn = function(event) {
  27826. this.zoomIncrement = this.constants.keyboard.speed.zoom;
  27827. this.start(); // if there is no node movement, the calculation wont be done
  27828. event.preventDefault();
  27829. };
  27830. /**
  27831. * Zoom out
  27832. * @private
  27833. */
  27834. exports._zoomOut = function(event) {
  27835. this.zoomIncrement = -this.constants.keyboard.speed.zoom;
  27836. this.start(); // if there is no node movement, the calculation wont be done
  27837. event.preventDefault();
  27838. };
  27839. /**
  27840. * Stop zooming and unhighlight the zoom controls
  27841. * @private
  27842. */
  27843. exports._stopZoom = function(event) {
  27844. this.zoomIncrement = 0;
  27845. event && event.preventDefault();
  27846. };
  27847. /**
  27848. * Stop moving in the Y direction and unHighlight the up and down
  27849. * @private
  27850. */
  27851. exports._yStopMoving = function(event) {
  27852. this.yIncrement = 0;
  27853. event && event.preventDefault();
  27854. };
  27855. /**
  27856. * Stop moving in the X direction and unHighlight left and right.
  27857. * @private
  27858. */
  27859. exports._xStopMoving = function(event) {
  27860. this.xIncrement = 0;
  27861. event && event.preventDefault();
  27862. };
  27863. /***/ },
  27864. /* 62 */
  27865. /***/ function(module, exports, __webpack_require__) {
  27866. exports._resetLevels = function() {
  27867. for (var nodeId in this.nodes) {
  27868. if (this.nodes.hasOwnProperty(nodeId)) {
  27869. var node = this.nodes[nodeId];
  27870. if (node.preassignedLevel == false) {
  27871. node.level = -1;
  27872. node.hierarchyEnumerated = false;
  27873. }
  27874. }
  27875. }
  27876. };
  27877. /**
  27878. * This is the main function to layout the nodes in a hierarchical way.
  27879. * It checks if the node details are supplied correctly
  27880. *
  27881. * @private
  27882. */
  27883. exports._setupHierarchicalLayout = function() {
  27884. if (this.constants.hierarchicalLayout.enabled == true && this.nodeIndices.length > 0) {
  27885. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "DU") {
  27886. this.constants.hierarchicalLayout.levelSeparation *= -1;
  27887. }
  27888. else {
  27889. this.constants.hierarchicalLayout.levelSeparation = Math.abs(this.constants.hierarchicalLayout.levelSeparation);
  27890. }
  27891. if (this.constants.hierarchicalLayout.direction == "RL" || this.constants.hierarchicalLayout.direction == "LR") {
  27892. if (this.constants.smoothCurves.enabled == true) {
  27893. this.constants.smoothCurves.type = "vertical";
  27894. }
  27895. }
  27896. else {
  27897. if (this.constants.smoothCurves.enabled == true) {
  27898. this.constants.smoothCurves.type = "horizontal";
  27899. }
  27900. }
  27901. // get the size of the largest hubs and check if the user has defined a level for a node.
  27902. var hubsize = 0;
  27903. var node, nodeId;
  27904. var definedLevel = false;
  27905. var undefinedLevel = false;
  27906. for (nodeId in this.nodes) {
  27907. if (this.nodes.hasOwnProperty(nodeId)) {
  27908. node = this.nodes[nodeId];
  27909. if (node.level != -1) {
  27910. definedLevel = true;
  27911. }
  27912. else {
  27913. undefinedLevel = true;
  27914. }
  27915. if (hubsize < node.edges.length) {
  27916. hubsize = node.edges.length;
  27917. }
  27918. }
  27919. }
  27920. // if the user defined some levels but not all, alert and run without hierarchical layout
  27921. if (undefinedLevel == true && definedLevel == true) {
  27922. throw new Error("To use the hierarchical layout, nodes require either no predefined levels or levels have to be defined for all nodes.");
  27923. this.zoomExtent(undefined,true,this.constants.clustering.enabled);
  27924. if (!this.constants.clustering.enabled) {
  27925. this.start();
  27926. }
  27927. }
  27928. else {
  27929. // setup the system to use hierarchical method.
  27930. this._changeConstants();
  27931. // define levels if undefined by the users. Based on hubsize
  27932. if (undefinedLevel == true) {
  27933. if (this.constants.hierarchicalLayout.layout == "hubsize") {
  27934. this._determineLevels(hubsize);
  27935. }
  27936. else {
  27937. this._determineLevelsDirected();
  27938. }
  27939. }
  27940. // check the distribution of the nodes per level.
  27941. var distribution = this._getDistribution();
  27942. // place the nodes on the canvas. This also stablilizes the system.
  27943. this._placeNodesByHierarchy(distribution);
  27944. // start the simulation.
  27945. this.start();
  27946. }
  27947. }
  27948. };
  27949. /**
  27950. * This function places the nodes on the canvas based on the hierarchial distribution.
  27951. *
  27952. * @param {Object} distribution | obtained by the function this._getDistribution()
  27953. * @private
  27954. */
  27955. exports._placeNodesByHierarchy = function(distribution) {
  27956. var nodeId, node;
  27957. // start placing all the level 0 nodes first. Then recursively position their branches.
  27958. for (var level in distribution) {
  27959. if (distribution.hasOwnProperty(level)) {
  27960. for (nodeId in distribution[level].nodes) {
  27961. if (distribution[level].nodes.hasOwnProperty(nodeId)) {
  27962. node = distribution[level].nodes[nodeId];
  27963. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  27964. if (node.xFixed) {
  27965. node.x = distribution[level].minPos;
  27966. node.xFixed = false;
  27967. distribution[level].minPos += distribution[level].nodeSpacing;
  27968. }
  27969. }
  27970. else {
  27971. if (node.yFixed) {
  27972. node.y = distribution[level].minPos;
  27973. node.yFixed = false;
  27974. distribution[level].minPos += distribution[level].nodeSpacing;
  27975. }
  27976. }
  27977. this._placeBranchNodes(node.edges,node.id,distribution,node.level);
  27978. }
  27979. }
  27980. }
  27981. }
  27982. // stabilize the system after positioning. This function calls zoomExtent.
  27983. this._stabilize();
  27984. };
  27985. /**
  27986. * This function get the distribution of levels based on hubsize
  27987. *
  27988. * @returns {Object}
  27989. * @private
  27990. */
  27991. exports._getDistribution = function() {
  27992. var distribution = {};
  27993. var nodeId, node, level;
  27994. // we fix Y because the hierarchy is vertical, we fix X so we do not give a node an x position for a second time.
  27995. // the fix of X is removed after the x value has been set.
  27996. for (nodeId in this.nodes) {
  27997. if (this.nodes.hasOwnProperty(nodeId)) {
  27998. node = this.nodes[nodeId];
  27999. node.xFixed = true;
  28000. node.yFixed = true;
  28001. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  28002. node.y = this.constants.hierarchicalLayout.levelSeparation*node.level;
  28003. }
  28004. else {
  28005. node.x = this.constants.hierarchicalLayout.levelSeparation*node.level;
  28006. }
  28007. if (distribution[node.level] === undefined) {
  28008. distribution[node.level] = {amount: 0, nodes: {}, minPos:0, nodeSpacing:0};
  28009. }
  28010. distribution[node.level].amount += 1;
  28011. distribution[node.level].nodes[nodeId] = node;
  28012. }
  28013. }
  28014. // determine the largest amount of nodes of all levels
  28015. var maxCount = 0;
  28016. for (level in distribution) {
  28017. if (distribution.hasOwnProperty(level)) {
  28018. if (maxCount < distribution[level].amount) {
  28019. maxCount = distribution[level].amount;
  28020. }
  28021. }
  28022. }
  28023. // set the initial position and spacing of each nodes accordingly
  28024. for (level in distribution) {
  28025. if (distribution.hasOwnProperty(level)) {
  28026. distribution[level].nodeSpacing = (maxCount + 1) * this.constants.hierarchicalLayout.nodeSpacing;
  28027. distribution[level].nodeSpacing /= (distribution[level].amount + 1);
  28028. distribution[level].minPos = distribution[level].nodeSpacing - (0.5 * (distribution[level].amount + 1) * distribution[level].nodeSpacing);
  28029. }
  28030. }
  28031. return distribution;
  28032. };
  28033. /**
  28034. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  28035. *
  28036. * @param hubsize
  28037. * @private
  28038. */
  28039. exports._determineLevels = function(hubsize) {
  28040. var nodeId, node;
  28041. // determine hubs
  28042. for (nodeId in this.nodes) {
  28043. if (this.nodes.hasOwnProperty(nodeId)) {
  28044. node = this.nodes[nodeId];
  28045. if (node.edges.length == hubsize) {
  28046. node.level = 0;
  28047. }
  28048. }
  28049. }
  28050. // branch from hubs
  28051. for (nodeId in this.nodes) {
  28052. if (this.nodes.hasOwnProperty(nodeId)) {
  28053. node = this.nodes[nodeId];
  28054. if (node.level == 0) {
  28055. this._setLevel(1,node.edges,node.id);
  28056. }
  28057. }
  28058. }
  28059. };
  28060. /**
  28061. * this function allocates nodes in levels based on the recursive branching from the largest hubs.
  28062. *
  28063. * @param hubsize
  28064. * @private
  28065. */
  28066. exports._determineLevelsDirected = function() {
  28067. var nodeId, node;
  28068. // set first node to source
  28069. for (nodeId in this.nodes) {
  28070. if (this.nodes.hasOwnProperty(nodeId)) {
  28071. this.nodes[nodeId].level = 10000;
  28072. break;
  28073. }
  28074. }
  28075. // branch from hubs
  28076. for (nodeId in this.nodes) {
  28077. if (this.nodes.hasOwnProperty(nodeId)) {
  28078. node = this.nodes[nodeId];
  28079. if (node.level == 10000) {
  28080. this._setLevelDirected(10000,node.edges,node.id);
  28081. }
  28082. }
  28083. }
  28084. // branch from hubs
  28085. var minLevel = 10000;
  28086. for (nodeId in this.nodes) {
  28087. if (this.nodes.hasOwnProperty(nodeId)) {
  28088. node = this.nodes[nodeId];
  28089. minLevel = node.level < minLevel ? node.level : minLevel;
  28090. }
  28091. }
  28092. // branch from hubs
  28093. for (nodeId in this.nodes) {
  28094. if (this.nodes.hasOwnProperty(nodeId)) {
  28095. node = this.nodes[nodeId];
  28096. node.level -= minLevel;
  28097. }
  28098. }
  28099. };
  28100. /**
  28101. * Since hierarchical layout does not support:
  28102. * - smooth curves (based on the physics),
  28103. * - clustering (based on dynamic node counts)
  28104. *
  28105. * We disable both features so there will be no problems.
  28106. *
  28107. * @private
  28108. */
  28109. exports._changeConstants = function() {
  28110. this.constants.clustering.enabled = false;
  28111. this.constants.physics.barnesHut.enabled = false;
  28112. this.constants.physics.hierarchicalRepulsion.enabled = true;
  28113. this._loadSelectedForceSolver();
  28114. if (this.constants.smoothCurves.enabled == true) {
  28115. this.constants.smoothCurves.dynamic = false;
  28116. }
  28117. this._configureSmoothCurves();
  28118. };
  28119. /**
  28120. * This is a recursively called function to enumerate the branches from the largest hubs and place the nodes
  28121. * on a X position that ensures there will be no overlap.
  28122. *
  28123. * @param edges
  28124. * @param parentId
  28125. * @param distribution
  28126. * @param parentLevel
  28127. * @private
  28128. */
  28129. exports._placeBranchNodes = function(edges, parentId, distribution, parentLevel) {
  28130. for (var i = 0; i < edges.length; i++) {
  28131. var childNode = null;
  28132. if (edges[i].toId == parentId) {
  28133. childNode = edges[i].from;
  28134. }
  28135. else {
  28136. childNode = edges[i].to;
  28137. }
  28138. // if a node is conneceted to another node on the same level (or higher (means lower level))!, this is not handled here.
  28139. var nodeMoved = false;
  28140. if (this.constants.hierarchicalLayout.direction == "UD" || this.constants.hierarchicalLayout.direction == "DU") {
  28141. if (childNode.xFixed && childNode.level > parentLevel) {
  28142. childNode.xFixed = false;
  28143. childNode.x = distribution[childNode.level].minPos;
  28144. nodeMoved = true;
  28145. }
  28146. }
  28147. else {
  28148. if (childNode.yFixed && childNode.level > parentLevel) {
  28149. childNode.yFixed = false;
  28150. childNode.y = distribution[childNode.level].minPos;
  28151. nodeMoved = true;
  28152. }
  28153. }
  28154. if (nodeMoved == true) {
  28155. distribution[childNode.level].minPos += distribution[childNode.level].nodeSpacing;
  28156. if (childNode.edges.length > 1) {
  28157. this._placeBranchNodes(childNode.edges,childNode.id,distribution,childNode.level);
  28158. }
  28159. }
  28160. }
  28161. };
  28162. /**
  28163. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  28164. *
  28165. * @param level
  28166. * @param edges
  28167. * @param parentId
  28168. * @private
  28169. */
  28170. exports._setLevel = function(level, edges, parentId) {
  28171. for (var i = 0; i < edges.length; i++) {
  28172. var childNode = null;
  28173. if (edges[i].toId == parentId) {
  28174. childNode = edges[i].from;
  28175. }
  28176. else {
  28177. childNode = edges[i].to;
  28178. }
  28179. if (childNode.level == -1 || childNode.level > level) {
  28180. childNode.level = level;
  28181. if (childNode.edges.length > 1) {
  28182. this._setLevel(level+1, childNode.edges, childNode.id);
  28183. }
  28184. }
  28185. }
  28186. };
  28187. /**
  28188. * this function is called recursively to enumerate the barnches of the largest hubs and give each node a level.
  28189. *
  28190. * @param level
  28191. * @param edges
  28192. * @param parentId
  28193. * @private
  28194. */
  28195. exports._setLevelDirected = function(level, edges, parentId) {
  28196. this.nodes[parentId].hierarchyEnumerated = true;
  28197. for (var i = 0; i < edges.length; i++) {
  28198. var childNode = null;
  28199. var direction = 1;
  28200. if (edges[i].toId == parentId) {
  28201. childNode = edges[i].from;
  28202. direction = -1;
  28203. }
  28204. else {
  28205. childNode = edges[i].to;
  28206. }
  28207. if (childNode.level == -1) {
  28208. childNode.level = level + direction;
  28209. }
  28210. }
  28211. for (var i = 0; i < edges.length; i++) {
  28212. var childNode = null;
  28213. if (edges[i].toId == parentId) {childNode = edges[i].from;}
  28214. else {childNode = edges[i].to;}
  28215. if (childNode.edges.length > 1 && childNode.hierarchyEnumerated === false) {
  28216. this._setLevelDirected(childNode.level, childNode.edges, childNode.id);
  28217. }
  28218. }
  28219. };
  28220. /**
  28221. * Unfix nodes
  28222. *
  28223. * @private
  28224. */
  28225. exports._restoreNodes = function() {
  28226. for (var nodeId in this.nodes) {
  28227. if (this.nodes.hasOwnProperty(nodeId)) {
  28228. this.nodes[nodeId].xFixed = false;
  28229. this.nodes[nodeId].yFixed = false;
  28230. }
  28231. }
  28232. };
  28233. /***/ },
  28234. /* 63 */
  28235. /***/ function(module, exports, __webpack_require__) {
  28236. var util = __webpack_require__(1);
  28237. var RepulsionMixin = __webpack_require__(66);
  28238. var HierarchialRepulsionMixin = __webpack_require__(65);
  28239. var BarnesHutMixin = __webpack_require__(67);
  28240. /**
  28241. * Toggling barnes Hut calculation on and off.
  28242. *
  28243. * @private
  28244. */
  28245. exports._toggleBarnesHut = function () {
  28246. this.constants.physics.barnesHut.enabled = !this.constants.physics.barnesHut.enabled;
  28247. this._loadSelectedForceSolver();
  28248. this.moving = true;
  28249. this.start();
  28250. };
  28251. /**
  28252. * This loads the node force solver based on the barnes hut or repulsion algorithm
  28253. *
  28254. * @private
  28255. */
  28256. exports._loadSelectedForceSolver = function () {
  28257. // this overloads the this._calculateNodeForces
  28258. if (this.constants.physics.barnesHut.enabled == true) {
  28259. this._clearMixin(RepulsionMixin);
  28260. this._clearMixin(HierarchialRepulsionMixin);
  28261. this.constants.physics.centralGravity = this.constants.physics.barnesHut.centralGravity;
  28262. this.constants.physics.springLength = this.constants.physics.barnesHut.springLength;
  28263. this.constants.physics.springConstant = this.constants.physics.barnesHut.springConstant;
  28264. this.constants.physics.damping = this.constants.physics.barnesHut.damping;
  28265. this._loadMixin(BarnesHutMixin);
  28266. }
  28267. else if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  28268. this._clearMixin(BarnesHutMixin);
  28269. this._clearMixin(RepulsionMixin);
  28270. this.constants.physics.centralGravity = this.constants.physics.hierarchicalRepulsion.centralGravity;
  28271. this.constants.physics.springLength = this.constants.physics.hierarchicalRepulsion.springLength;
  28272. this.constants.physics.springConstant = this.constants.physics.hierarchicalRepulsion.springConstant;
  28273. this.constants.physics.damping = this.constants.physics.hierarchicalRepulsion.damping;
  28274. this._loadMixin(HierarchialRepulsionMixin);
  28275. }
  28276. else {
  28277. this._clearMixin(BarnesHutMixin);
  28278. this._clearMixin(HierarchialRepulsionMixin);
  28279. this.barnesHutTree = undefined;
  28280. this.constants.physics.centralGravity = this.constants.physics.repulsion.centralGravity;
  28281. this.constants.physics.springLength = this.constants.physics.repulsion.springLength;
  28282. this.constants.physics.springConstant = this.constants.physics.repulsion.springConstant;
  28283. this.constants.physics.damping = this.constants.physics.repulsion.damping;
  28284. this._loadMixin(RepulsionMixin);
  28285. }
  28286. };
  28287. /**
  28288. * Before calculating the forces, we check if we need to cluster to keep up performance and we check
  28289. * if there is more than one node. If it is just one node, we dont calculate anything.
  28290. *
  28291. * @private
  28292. */
  28293. exports._initializeForceCalculation = function () {
  28294. // stop calculation if there is only one node
  28295. if (this.nodeIndices.length == 1) {
  28296. this.nodes[this.nodeIndices[0]]._setForce(0, 0);
  28297. }
  28298. else {
  28299. // if there are too many nodes on screen, we cluster without repositioning
  28300. if (this.nodeIndices.length > this.constants.clustering.clusterThreshold && this.constants.clustering.enabled == true) {
  28301. this.clusterToFit(this.constants.clustering.reduceToNodes, false);
  28302. }
  28303. // we now start the force calculation
  28304. this._calculateForces();
  28305. }
  28306. };
  28307. /**
  28308. * Calculate the external forces acting on the nodes
  28309. * Forces are caused by: edges, repulsing forces between nodes, gravity
  28310. * @private
  28311. */
  28312. exports._calculateForces = function () {
  28313. // Gravity is required to keep separated groups from floating off
  28314. // the forces are reset to zero in this loop by using _setForce instead
  28315. // of _addForce
  28316. this._calculateGravitationalForces();
  28317. this._calculateNodeForces();
  28318. if (this.constants.physics.springConstant > 0) {
  28319. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  28320. this._calculateSpringForcesWithSupport();
  28321. }
  28322. else {
  28323. if (this.constants.physics.hierarchicalRepulsion.enabled == true) {
  28324. this._calculateHierarchicalSpringForces();
  28325. }
  28326. else {
  28327. this._calculateSpringForces();
  28328. }
  28329. }
  28330. }
  28331. };
  28332. /**
  28333. * Smooth curves are created by adding invisible nodes in the center of the edges. These nodes are also
  28334. * handled in the calculateForces function. We then use a quadratic curve with the center node as control.
  28335. * This function joins the datanodes and invisible (called support) nodes into one object.
  28336. * We do this so we do not contaminate this.nodes with the support nodes.
  28337. *
  28338. * @private
  28339. */
  28340. exports._updateCalculationNodes = function () {
  28341. if (this.constants.smoothCurves.enabled == true && this.constants.smoothCurves.dynamic == true) {
  28342. this.calculationNodes = {};
  28343. this.calculationNodeIndices = [];
  28344. for (var nodeId in this.nodes) {
  28345. if (this.nodes.hasOwnProperty(nodeId)) {
  28346. this.calculationNodes[nodeId] = this.nodes[nodeId];
  28347. }
  28348. }
  28349. var supportNodes = this.sectors['support']['nodes'];
  28350. for (var supportNodeId in supportNodes) {
  28351. if (supportNodes.hasOwnProperty(supportNodeId)) {
  28352. if (this.edges.hasOwnProperty(supportNodes[supportNodeId].parentEdgeId)) {
  28353. this.calculationNodes[supportNodeId] = supportNodes[supportNodeId];
  28354. }
  28355. else {
  28356. supportNodes[supportNodeId]._setForce(0, 0);
  28357. }
  28358. }
  28359. }
  28360. for (var idx in this.calculationNodes) {
  28361. if (this.calculationNodes.hasOwnProperty(idx)) {
  28362. this.calculationNodeIndices.push(idx);
  28363. }
  28364. }
  28365. }
  28366. else {
  28367. this.calculationNodes = this.nodes;
  28368. this.calculationNodeIndices = this.nodeIndices;
  28369. }
  28370. };
  28371. /**
  28372. * this function applies the central gravity effect to keep groups from floating off
  28373. *
  28374. * @private
  28375. */
  28376. exports._calculateGravitationalForces = function () {
  28377. var dx, dy, distance, node, i;
  28378. var nodes = this.calculationNodes;
  28379. var gravity = this.constants.physics.centralGravity;
  28380. var gravityForce = 0;
  28381. for (i = 0; i < this.calculationNodeIndices.length; i++) {
  28382. node = nodes[this.calculationNodeIndices[i]];
  28383. node.damping = this.constants.physics.damping; // possibly add function to alter damping properties of clusters.
  28384. // gravity does not apply when we are in a pocket sector
  28385. if (this._sector() == "default" && gravity != 0) {
  28386. dx = -node.x;
  28387. dy = -node.y;
  28388. distance = Math.sqrt(dx * dx + dy * dy);
  28389. gravityForce = (distance == 0) ? 0 : (gravity / distance);
  28390. node.fx = dx * gravityForce;
  28391. node.fy = dy * gravityForce;
  28392. }
  28393. else {
  28394. node.fx = 0;
  28395. node.fy = 0;
  28396. }
  28397. }
  28398. };
  28399. /**
  28400. * this function calculates the effects of the springs in the case of unsmooth curves.
  28401. *
  28402. * @private
  28403. */
  28404. exports._calculateSpringForces = function () {
  28405. var edgeLength, edge, edgeId;
  28406. var dx, dy, fx, fy, springForce, distance;
  28407. var edges = this.edges;
  28408. // forces caused by the edges, modelled as springs
  28409. for (edgeId in edges) {
  28410. if (edges.hasOwnProperty(edgeId)) {
  28411. edge = edges[edgeId];
  28412. if (edge.connected) {
  28413. // only calculate forces if nodes are in the same sector
  28414. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  28415. edgeLength = edge.physics.springLength;
  28416. // this implies that the edges between big clusters are longer
  28417. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  28418. dx = (edge.from.x - edge.to.x);
  28419. dy = (edge.from.y - edge.to.y);
  28420. distance = Math.sqrt(dx * dx + dy * dy);
  28421. if (distance == 0) {
  28422. distance = 0.01;
  28423. }
  28424. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  28425. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  28426. fx = dx * springForce;
  28427. fy = dy * springForce;
  28428. edge.from.fx += fx;
  28429. edge.from.fy += fy;
  28430. edge.to.fx -= fx;
  28431. edge.to.fy -= fy;
  28432. }
  28433. }
  28434. }
  28435. }
  28436. };
  28437. /**
  28438. * This function calculates the springforces on the nodes, accounting for the support nodes.
  28439. *
  28440. * @private
  28441. */
  28442. exports._calculateSpringForcesWithSupport = function () {
  28443. var edgeLength, edge, edgeId, combinedClusterSize;
  28444. var edges = this.edges;
  28445. // forces caused by the edges, modelled as springs
  28446. for (edgeId in edges) {
  28447. if (edges.hasOwnProperty(edgeId)) {
  28448. edge = edges[edgeId];
  28449. if (edge.connected) {
  28450. // only calculate forces if nodes are in the same sector
  28451. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  28452. if (edge.via != null) {
  28453. var node1 = edge.to;
  28454. var node2 = edge.via;
  28455. var node3 = edge.from;
  28456. edgeLength = edge.physics.springLength;
  28457. combinedClusterSize = node1.clusterSize + node3.clusterSize - 2;
  28458. // this implies that the edges between big clusters are longer
  28459. edgeLength += combinedClusterSize * this.constants.clustering.edgeGrowth;
  28460. this._calculateSpringForce(node1, node2, 0.5 * edgeLength);
  28461. this._calculateSpringForce(node2, node3, 0.5 * edgeLength);
  28462. }
  28463. }
  28464. }
  28465. }
  28466. }
  28467. };
  28468. /**
  28469. * This is the code actually performing the calculation for the function above. It is split out to avoid repetition.
  28470. *
  28471. * @param node1
  28472. * @param node2
  28473. * @param edgeLength
  28474. * @private
  28475. */
  28476. exports._calculateSpringForce = function (node1, node2, edgeLength) {
  28477. var dx, dy, fx, fy, springForce, distance;
  28478. dx = (node1.x - node2.x);
  28479. dy = (node1.y - node2.y);
  28480. distance = Math.sqrt(dx * dx + dy * dy);
  28481. if (distance == 0) {
  28482. distance = 0.01;
  28483. }
  28484. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  28485. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  28486. fx = dx * springForce;
  28487. fy = dy * springForce;
  28488. node1.fx += fx;
  28489. node1.fy += fy;
  28490. node2.fx -= fx;
  28491. node2.fy -= fy;
  28492. };
  28493. /**
  28494. * Load the HTML for the physics config and bind it
  28495. * @private
  28496. */
  28497. exports._loadPhysicsConfiguration = function () {
  28498. if (this.physicsConfiguration === undefined) {
  28499. this.backupConstants = {};
  28500. util.deepExtend(this.backupConstants,this.constants);
  28501. var hierarchicalLayoutDirections = ["LR", "RL", "UD", "DU"];
  28502. this.physicsConfiguration = document.createElement('div');
  28503. this.physicsConfiguration.className = "PhysicsConfiguration";
  28504. this.physicsConfiguration.innerHTML = '' +
  28505. '<table><tr><td><b>Simulation Mode:</b></td></tr>' +
  28506. '<tr>' +
  28507. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod1" value="BH" checked="checked">Barnes Hut</td>' +
  28508. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod2" value="R">Repulsion</td>' +
  28509. '<td width="120px"><input type="radio" name="graph_physicsMethod" id="graph_physicsMethod3" value="H">Hierarchical</td>' +
  28510. '</tr>' +
  28511. '</table>' +
  28512. '<table id="graph_BH_table" style="display:none">' +
  28513. '<tr><td><b>Barnes Hut</b></td></tr>' +
  28514. '<tr>' +
  28515. '<td width="150px">gravitationalConstant</td><td>0</td><td><input type="range" min="0" max="20000" value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" step="25" style="width:300px" id="graph_BH_gc"></td><td width="50px">-20000</td><td><input value="' + (-1 * this.constants.physics.barnesHut.gravitationalConstant) + '" id="graph_BH_gc_value" style="width:60px"></td>' +
  28516. '</tr>' +
  28517. '<tr>' +
  28518. '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.barnesHut.centralGravity + '" step="0.05" style="width:300px" id="graph_BH_cg"></td><td>3</td><td><input value="' + this.constants.physics.barnesHut.centralGravity + '" id="graph_BH_cg_value" style="width:60px"></td>' +
  28519. '</tr>' +
  28520. '<tr>' +
  28521. '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.barnesHut.springLength + '" step="1" style="width:300px" id="graph_BH_sl"></td><td>500</td><td><input value="' + this.constants.physics.barnesHut.springLength + '" id="graph_BH_sl_value" style="width:60px"></td>' +
  28522. '</tr>' +
  28523. '<tr>' +
  28524. '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.barnesHut.springConstant + '" step="0.001" style="width:300px" id="graph_BH_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.barnesHut.springConstant + '" id="graph_BH_sc_value" style="width:60px"></td>' +
  28525. '</tr>' +
  28526. '<tr>' +
  28527. '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.barnesHut.damping + '" step="0.005" style="width:300px" id="graph_BH_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.barnesHut.damping + '" id="graph_BH_damp_value" style="width:60px"></td>' +
  28528. '</tr>' +
  28529. '</table>' +
  28530. '<table id="graph_R_table" style="display:none">' +
  28531. '<tr><td><b>Repulsion</b></td></tr>' +
  28532. '<tr>' +
  28533. '<td width="150px">nodeDistance</td><td>0</td><td><input type="range" min="0" max="300" value="' + this.constants.physics.repulsion.nodeDistance + '" step="1" style="width:300px" id="graph_R_nd"></td><td width="50px">300</td><td><input value="' + this.constants.physics.repulsion.nodeDistance + '" id="graph_R_nd_value" style="width:60px"></td>' +
  28534. '</tr>' +
  28535. '<tr>' +
  28536. '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.repulsion.centralGravity + '" step="0.05" style="width:300px" id="graph_R_cg"></td><td>3</td><td><input value="' + this.constants.physics.repulsion.centralGravity + '" id="graph_R_cg_value" style="width:60px"></td>' +
  28537. '</tr>' +
  28538. '<tr>' +
  28539. '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.repulsion.springLength + '" step="1" style="width:300px" id="graph_R_sl"></td><td>500</td><td><input value="' + this.constants.physics.repulsion.springLength + '" id="graph_R_sl_value" style="width:60px"></td>' +
  28540. '</tr>' +
  28541. '<tr>' +
  28542. '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.repulsion.springConstant + '" step="0.001" style="width:300px" id="graph_R_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.repulsion.springConstant + '" id="graph_R_sc_value" style="width:60px"></td>' +
  28543. '</tr>' +
  28544. '<tr>' +
  28545. '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.repulsion.damping + '" step="0.005" style="width:300px" id="graph_R_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.repulsion.damping + '" id="graph_R_damp_value" style="width:60px"></td>' +
  28546. '</tr>' +
  28547. '</table>' +
  28548. '<table id="graph_H_table" style="display:none">' +
  28549. '<tr><td width="150"><b>Hierarchical</b></td></tr>' +
  28550. '<tr>' +
  28551. '<td width="150px">nodeDistance</td><td>0</td><td><input type="range" min="0" max="300" value="' + this.constants.physics.hierarchicalRepulsion.nodeDistance + '" step="1" style="width:300px" id="graph_H_nd"></td><td width="50px">300</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.nodeDistance + '" id="graph_H_nd_value" style="width:60px"></td>' +
  28552. '</tr>' +
  28553. '<tr>' +
  28554. '<td width="150px">centralGravity</td><td>0</td><td><input type="range" min="0" max="3" value="' + this.constants.physics.hierarchicalRepulsion.centralGravity + '" step="0.05" style="width:300px" id="graph_H_cg"></td><td>3</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.centralGravity + '" id="graph_H_cg_value" style="width:60px"></td>' +
  28555. '</tr>' +
  28556. '<tr>' +
  28557. '<td width="150px">springLength</td><td>0</td><td><input type="range" min="0" max="500" value="' + this.constants.physics.hierarchicalRepulsion.springLength + '" step="1" style="width:300px" id="graph_H_sl"></td><td>500</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.springLength + '" id="graph_H_sl_value" style="width:60px"></td>' +
  28558. '</tr>' +
  28559. '<tr>' +
  28560. '<td width="150px">springConstant</td><td>0</td><td><input type="range" min="0" max="0.5" value="' + this.constants.physics.hierarchicalRepulsion.springConstant + '" step="0.001" style="width:300px" id="graph_H_sc"></td><td>0.5</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.springConstant + '" id="graph_H_sc_value" style="width:60px"></td>' +
  28561. '</tr>' +
  28562. '<tr>' +
  28563. '<td width="150px">damping</td><td>0</td><td><input type="range" min="0" max="0.3" value="' + this.constants.physics.hierarchicalRepulsion.damping + '" step="0.005" style="width:300px" id="graph_H_damp"></td><td>0.3</td><td><input value="' + this.constants.physics.hierarchicalRepulsion.damping + '" id="graph_H_damp_value" style="width:60px"></td>' +
  28564. '</tr>' +
  28565. '<tr>' +
  28566. '<td width="150px">direction</td><td>1</td><td><input type="range" min="0" max="3" value="' + hierarchicalLayoutDirections.indexOf(this.constants.hierarchicalLayout.direction) + '" step="1" style="width:300px" id="graph_H_direction"></td><td>4</td><td><input value="' + this.constants.hierarchicalLayout.direction + '" id="graph_H_direction_value" style="width:60px"></td>' +
  28567. '</tr>' +
  28568. '<tr>' +
  28569. '<td width="150px">levelSeparation</td><td>1</td><td><input type="range" min="0" max="500" value="' + this.constants.hierarchicalLayout.levelSeparation + '" step="1" style="width:300px" id="graph_H_levsep"></td><td>500</td><td><input value="' + this.constants.hierarchicalLayout.levelSeparation + '" id="graph_H_levsep_value" style="width:60px"></td>' +
  28570. '</tr>' +
  28571. '<tr>' +
  28572. '<td width="150px">nodeSpacing</td><td>1</td><td><input type="range" min="0" max="500" value="' + this.constants.hierarchicalLayout.nodeSpacing + '" step="1" style="width:300px" id="graph_H_nspac"></td><td>500</td><td><input value="' + this.constants.hierarchicalLayout.nodeSpacing + '" id="graph_H_nspac_value" style="width:60px"></td>' +
  28573. '</tr>' +
  28574. '</table>' +
  28575. '<table><tr><td><b>Options:</b></td></tr>' +
  28576. '<tr>' +
  28577. '<td width="180px"><input type="button" id="graph_toggleSmooth" value="Toggle smoothCurves" style="width:150px"></td>' +
  28578. '<td width="180px"><input type="button" id="graph_repositionNodes" value="Reinitialize" style="width:150px"></td>' +
  28579. '<td width="180px"><input type="button" id="graph_generateOptions" value="Generate Options" style="width:150px"></td>' +
  28580. '</tr>' +
  28581. '</table>'
  28582. this.containerElement.parentElement.insertBefore(this.physicsConfiguration, this.containerElement);
  28583. this.optionsDiv = document.createElement("div");
  28584. this.optionsDiv.style.fontSize = "14px";
  28585. this.optionsDiv.style.fontFamily = "verdana";
  28586. this.containerElement.parentElement.insertBefore(this.optionsDiv, this.containerElement);
  28587. var rangeElement;
  28588. rangeElement = document.getElementById('graph_BH_gc');
  28589. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_gc', -1, "physics_barnesHut_gravitationalConstant");
  28590. rangeElement = document.getElementById('graph_BH_cg');
  28591. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_cg', 1, "physics_centralGravity");
  28592. rangeElement = document.getElementById('graph_BH_sc');
  28593. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sc', 1, "physics_springConstant");
  28594. rangeElement = document.getElementById('graph_BH_sl');
  28595. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_sl', 1, "physics_springLength");
  28596. rangeElement = document.getElementById('graph_BH_damp');
  28597. rangeElement.onchange = showValueOfRange.bind(this, 'graph_BH_damp', 1, "physics_damping");
  28598. rangeElement = document.getElementById('graph_R_nd');
  28599. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_nd', 1, "physics_repulsion_nodeDistance");
  28600. rangeElement = document.getElementById('graph_R_cg');
  28601. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_cg', 1, "physics_centralGravity");
  28602. rangeElement = document.getElementById('graph_R_sc');
  28603. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sc', 1, "physics_springConstant");
  28604. rangeElement = document.getElementById('graph_R_sl');
  28605. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_sl', 1, "physics_springLength");
  28606. rangeElement = document.getElementById('graph_R_damp');
  28607. rangeElement.onchange = showValueOfRange.bind(this, 'graph_R_damp', 1, "physics_damping");
  28608. rangeElement = document.getElementById('graph_H_nd');
  28609. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  28610. rangeElement = document.getElementById('graph_H_cg');
  28611. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_cg', 1, "physics_centralGravity");
  28612. rangeElement = document.getElementById('graph_H_sc');
  28613. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sc', 1, "physics_springConstant");
  28614. rangeElement = document.getElementById('graph_H_sl');
  28615. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_sl', 1, "physics_springLength");
  28616. rangeElement = document.getElementById('graph_H_damp');
  28617. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_damp', 1, "physics_damping");
  28618. rangeElement = document.getElementById('graph_H_direction');
  28619. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_direction', hierarchicalLayoutDirections, "hierarchicalLayout_direction");
  28620. rangeElement = document.getElementById('graph_H_levsep');
  28621. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_levsep', 1, "hierarchicalLayout_levelSeparation");
  28622. rangeElement = document.getElementById('graph_H_nspac');
  28623. rangeElement.onchange = showValueOfRange.bind(this, 'graph_H_nspac', 1, "hierarchicalLayout_nodeSpacing");
  28624. var radioButton1 = document.getElementById("graph_physicsMethod1");
  28625. var radioButton2 = document.getElementById("graph_physicsMethod2");
  28626. var radioButton3 = document.getElementById("graph_physicsMethod3");
  28627. radioButton2.checked = true;
  28628. if (this.constants.physics.barnesHut.enabled) {
  28629. radioButton1.checked = true;
  28630. }
  28631. if (this.constants.hierarchicalLayout.enabled) {
  28632. radioButton3.checked = true;
  28633. }
  28634. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  28635. var graph_repositionNodes = document.getElementById("graph_repositionNodes");
  28636. var graph_generateOptions = document.getElementById("graph_generateOptions");
  28637. graph_toggleSmooth.onclick = graphToggleSmoothCurves.bind(this);
  28638. graph_repositionNodes.onclick = graphRepositionNodes.bind(this);
  28639. graph_generateOptions.onclick = graphGenerateOptions.bind(this);
  28640. if (this.constants.smoothCurves == true && this.constants.dynamicSmoothCurves == false) {
  28641. graph_toggleSmooth.style.background = "#A4FF56";
  28642. }
  28643. else {
  28644. graph_toggleSmooth.style.background = "#FF8532";
  28645. }
  28646. switchConfigurations.apply(this);
  28647. radioButton1.onchange = switchConfigurations.bind(this);
  28648. radioButton2.onchange = switchConfigurations.bind(this);
  28649. radioButton3.onchange = switchConfigurations.bind(this);
  28650. }
  28651. };
  28652. /**
  28653. * This overwrites the this.constants.
  28654. *
  28655. * @param constantsVariableName
  28656. * @param value
  28657. * @private
  28658. */
  28659. exports._overWriteGraphConstants = function (constantsVariableName, value) {
  28660. var nameArray = constantsVariableName.split("_");
  28661. if (nameArray.length == 1) {
  28662. this.constants[nameArray[0]] = value;
  28663. }
  28664. else if (nameArray.length == 2) {
  28665. this.constants[nameArray[0]][nameArray[1]] = value;
  28666. }
  28667. else if (nameArray.length == 3) {
  28668. this.constants[nameArray[0]][nameArray[1]][nameArray[2]] = value;
  28669. }
  28670. };
  28671. /**
  28672. * this function is bound to the toggle smooth curves button. That is also why it is not in the prototype.
  28673. */
  28674. function graphToggleSmoothCurves () {
  28675. this.constants.smoothCurves.enabled = !this.constants.smoothCurves.enabled;
  28676. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  28677. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  28678. else {graph_toggleSmooth.style.background = "#FF8532";}
  28679. this._configureSmoothCurves(false);
  28680. }
  28681. /**
  28682. * this function is used to scramble the nodes
  28683. *
  28684. */
  28685. function graphRepositionNodes () {
  28686. for (var nodeId in this.calculationNodes) {
  28687. if (this.calculationNodes.hasOwnProperty(nodeId)) {
  28688. this.calculationNodes[nodeId].vx = 0; this.calculationNodes[nodeId].vy = 0;
  28689. this.calculationNodes[nodeId].fx = 0; this.calculationNodes[nodeId].fy = 0;
  28690. }
  28691. }
  28692. if (this.constants.hierarchicalLayout.enabled == true) {
  28693. this._setupHierarchicalLayout();
  28694. showValueOfRange.call(this, 'graph_H_nd', 1, "physics_hierarchicalRepulsion_nodeDistance");
  28695. showValueOfRange.call(this, 'graph_H_cg', 1, "physics_centralGravity");
  28696. showValueOfRange.call(this, 'graph_H_sc', 1, "physics_springConstant");
  28697. showValueOfRange.call(this, 'graph_H_sl', 1, "physics_springLength");
  28698. showValueOfRange.call(this, 'graph_H_damp', 1, "physics_damping");
  28699. }
  28700. else {
  28701. this.repositionNodes();
  28702. }
  28703. this.moving = true;
  28704. this.start();
  28705. }
  28706. /**
  28707. * this is used to generate an options file from the playing with physics system.
  28708. */
  28709. function graphGenerateOptions () {
  28710. var options = "No options are required, default values used.";
  28711. var optionsSpecific = [];
  28712. var radioButton1 = document.getElementById("graph_physicsMethod1");
  28713. var radioButton2 = document.getElementById("graph_physicsMethod2");
  28714. if (radioButton1.checked == true) {
  28715. if (this.constants.physics.barnesHut.gravitationalConstant != this.backupConstants.physics.barnesHut.gravitationalConstant) {optionsSpecific.push("gravitationalConstant: " + this.constants.physics.barnesHut.gravitationalConstant);}
  28716. if (this.constants.physics.centralGravity != this.backupConstants.physics.barnesHut.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  28717. if (this.constants.physics.springLength != this.backupConstants.physics.barnesHut.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  28718. if (this.constants.physics.springConstant != this.backupConstants.physics.barnesHut.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  28719. if (this.constants.physics.damping != this.backupConstants.physics.barnesHut.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  28720. if (optionsSpecific.length != 0) {
  28721. options = "var options = {";
  28722. options += "physics: {barnesHut: {";
  28723. for (var i = 0; i < optionsSpecific.length; i++) {
  28724. options += optionsSpecific[i];
  28725. if (i < optionsSpecific.length - 1) {
  28726. options += ", "
  28727. }
  28728. }
  28729. options += '}}'
  28730. }
  28731. if (this.constants.smoothCurves.enabled != this.backupConstants.smoothCurves.enabled) {
  28732. if (optionsSpecific.length == 0) {options = "var options = {";}
  28733. else {options += ", "}
  28734. options += "smoothCurves: " + this.constants.smoothCurves.enabled;
  28735. }
  28736. if (options != "No options are required, default values used.") {
  28737. options += '};'
  28738. }
  28739. }
  28740. else if (radioButton2.checked == true) {
  28741. options = "var options = {";
  28742. options += "physics: {barnesHut: {enabled: false}";
  28743. if (this.constants.physics.repulsion.nodeDistance != this.backupConstants.physics.repulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.repulsion.nodeDistance);}
  28744. if (this.constants.physics.centralGravity != this.backupConstants.physics.repulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  28745. if (this.constants.physics.springLength != this.backupConstants.physics.repulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  28746. if (this.constants.physics.springConstant != this.backupConstants.physics.repulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  28747. if (this.constants.physics.damping != this.backupConstants.physics.repulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  28748. if (optionsSpecific.length != 0) {
  28749. options += ", repulsion: {";
  28750. for (var i = 0; i < optionsSpecific.length; i++) {
  28751. options += optionsSpecific[i];
  28752. if (i < optionsSpecific.length - 1) {
  28753. options += ", "
  28754. }
  28755. }
  28756. options += '}}'
  28757. }
  28758. if (optionsSpecific.length == 0) {options += "}"}
  28759. if (this.constants.smoothCurves != this.backupConstants.smoothCurves) {
  28760. options += ", smoothCurves: " + this.constants.smoothCurves;
  28761. }
  28762. options += '};'
  28763. }
  28764. else {
  28765. options = "var options = {";
  28766. if (this.constants.physics.hierarchicalRepulsion.nodeDistance != this.backupConstants.physics.hierarchicalRepulsion.nodeDistance) {optionsSpecific.push("nodeDistance: " + this.constants.physics.hierarchicalRepulsion.nodeDistance);}
  28767. if (this.constants.physics.centralGravity != this.backupConstants.physics.hierarchicalRepulsion.centralGravity) {optionsSpecific.push("centralGravity: " + this.constants.physics.centralGravity);}
  28768. if (this.constants.physics.springLength != this.backupConstants.physics.hierarchicalRepulsion.springLength) {optionsSpecific.push("springLength: " + this.constants.physics.springLength);}
  28769. if (this.constants.physics.springConstant != this.backupConstants.physics.hierarchicalRepulsion.springConstant) {optionsSpecific.push("springConstant: " + this.constants.physics.springConstant);}
  28770. if (this.constants.physics.damping != this.backupConstants.physics.hierarchicalRepulsion.damping) {optionsSpecific.push("damping: " + this.constants.physics.damping);}
  28771. if (optionsSpecific.length != 0) {
  28772. options += "physics: {hierarchicalRepulsion: {";
  28773. for (var i = 0; i < optionsSpecific.length; i++) {
  28774. options += optionsSpecific[i];
  28775. if (i < optionsSpecific.length - 1) {
  28776. options += ", ";
  28777. }
  28778. }
  28779. options += '}},';
  28780. }
  28781. options += 'hierarchicalLayout: {';
  28782. optionsSpecific = [];
  28783. if (this.constants.hierarchicalLayout.direction != this.backupConstants.hierarchicalLayout.direction) {optionsSpecific.push("direction: " + this.constants.hierarchicalLayout.direction);}
  28784. if (Math.abs(this.constants.hierarchicalLayout.levelSeparation) != this.backupConstants.hierarchicalLayout.levelSeparation) {optionsSpecific.push("levelSeparation: " + this.constants.hierarchicalLayout.levelSeparation);}
  28785. if (this.constants.hierarchicalLayout.nodeSpacing != this.backupConstants.hierarchicalLayout.nodeSpacing) {optionsSpecific.push("nodeSpacing: " + this.constants.hierarchicalLayout.nodeSpacing);}
  28786. if (optionsSpecific.length != 0) {
  28787. for (var i = 0; i < optionsSpecific.length; i++) {
  28788. options += optionsSpecific[i];
  28789. if (i < optionsSpecific.length - 1) {
  28790. options += ", "
  28791. }
  28792. }
  28793. options += '}'
  28794. }
  28795. else {
  28796. options += "enabled:true}";
  28797. }
  28798. options += '};'
  28799. }
  28800. this.optionsDiv.innerHTML = options;
  28801. }
  28802. /**
  28803. * this is used to switch between barnesHut, repulsion and hierarchical.
  28804. *
  28805. */
  28806. function switchConfigurations () {
  28807. var ids = ["graph_BH_table", "graph_R_table", "graph_H_table"];
  28808. var radioButton = document.querySelector('input[name="graph_physicsMethod"]:checked').value;
  28809. var tableId = "graph_" + radioButton + "_table";
  28810. var table = document.getElementById(tableId);
  28811. table.style.display = "block";
  28812. for (var i = 0; i < ids.length; i++) {
  28813. if (ids[i] != tableId) {
  28814. table = document.getElementById(ids[i]);
  28815. table.style.display = "none";
  28816. }
  28817. }
  28818. this._restoreNodes();
  28819. if (radioButton == "R") {
  28820. this.constants.hierarchicalLayout.enabled = false;
  28821. this.constants.physics.hierarchicalRepulsion.enabled = false;
  28822. this.constants.physics.barnesHut.enabled = false;
  28823. }
  28824. else if (radioButton == "H") {
  28825. if (this.constants.hierarchicalLayout.enabled == false) {
  28826. this.constants.hierarchicalLayout.enabled = true;
  28827. this.constants.physics.hierarchicalRepulsion.enabled = true;
  28828. this.constants.physics.barnesHut.enabled = false;
  28829. this.constants.smoothCurves.enabled = false;
  28830. this._setupHierarchicalLayout();
  28831. }
  28832. }
  28833. else {
  28834. this.constants.hierarchicalLayout.enabled = false;
  28835. this.constants.physics.hierarchicalRepulsion.enabled = false;
  28836. this.constants.physics.barnesHut.enabled = true;
  28837. }
  28838. this._loadSelectedForceSolver();
  28839. var graph_toggleSmooth = document.getElementById("graph_toggleSmooth");
  28840. if (this.constants.smoothCurves.enabled == true) {graph_toggleSmooth.style.background = "#A4FF56";}
  28841. else {graph_toggleSmooth.style.background = "#FF8532";}
  28842. this.moving = true;
  28843. this.start();
  28844. }
  28845. /**
  28846. * this generates the ranges depending on the iniital values.
  28847. *
  28848. * @param id
  28849. * @param map
  28850. * @param constantsVariableName
  28851. */
  28852. function showValueOfRange (id,map,constantsVariableName) {
  28853. var valueId = id + "_value";
  28854. var rangeValue = document.getElementById(id).value;
  28855. if (Array.isArray(map)) {
  28856. document.getElementById(valueId).value = map[parseInt(rangeValue)];
  28857. this._overWriteGraphConstants(constantsVariableName,map[parseInt(rangeValue)]);
  28858. }
  28859. else {
  28860. document.getElementById(valueId).value = parseInt(map) * parseFloat(rangeValue);
  28861. this._overWriteGraphConstants(constantsVariableName, parseInt(map) * parseFloat(rangeValue));
  28862. }
  28863. if (constantsVariableName == "hierarchicalLayout_direction" ||
  28864. constantsVariableName == "hierarchicalLayout_levelSeparation" ||
  28865. constantsVariableName == "hierarchicalLayout_nodeSpacing") {
  28866. this._setupHierarchicalLayout();
  28867. }
  28868. this.moving = true;
  28869. this.start();
  28870. }
  28871. /***/ },
  28872. /* 64 */
  28873. /***/ function(module, exports, __webpack_require__) {
  28874. function webpackContext(req) {
  28875. throw new Error("Cannot find module '" + req + "'.");
  28876. }
  28877. webpackContext.keys = function() { return []; };
  28878. webpackContext.resolve = webpackContext;
  28879. module.exports = webpackContext;
  28880. webpackContext.id = 64;
  28881. /***/ },
  28882. /* 65 */
  28883. /***/ function(module, exports, __webpack_require__) {
  28884. /**
  28885. * Calculate the forces the nodes apply on eachother based on a repulsion field.
  28886. * This field is linearly approximated.
  28887. *
  28888. * @private
  28889. */
  28890. exports._calculateNodeForces = function () {
  28891. var dx, dy, distance, fx, fy,
  28892. repulsingForce, node1, node2, i, j;
  28893. var nodes = this.calculationNodes;
  28894. var nodeIndices = this.calculationNodeIndices;
  28895. // repulsing forces between nodes
  28896. var nodeDistance = this.constants.physics.hierarchicalRepulsion.nodeDistance;
  28897. // we loop from i over all but the last entree in the array
  28898. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  28899. for (i = 0; i < nodeIndices.length - 1; i++) {
  28900. node1 = nodes[nodeIndices[i]];
  28901. for (j = i + 1; j < nodeIndices.length; j++) {
  28902. node2 = nodes[nodeIndices[j]];
  28903. // nodes only affect nodes on their level
  28904. if (node1.level == node2.level) {
  28905. dx = node2.x - node1.x;
  28906. dy = node2.y - node1.y;
  28907. distance = Math.sqrt(dx * dx + dy * dy);
  28908. var steepness = 0.05;
  28909. if (distance < nodeDistance) {
  28910. repulsingForce = -Math.pow(steepness*distance,2) + Math.pow(steepness*nodeDistance,2);
  28911. }
  28912. else {
  28913. repulsingForce = 0;
  28914. }
  28915. // normalize force with
  28916. if (distance == 0) {
  28917. distance = 0.01;
  28918. }
  28919. else {
  28920. repulsingForce = repulsingForce / distance;
  28921. }
  28922. fx = dx * repulsingForce;
  28923. fy = dy * repulsingForce;
  28924. node1.fx -= fx;
  28925. node1.fy -= fy;
  28926. node2.fx += fx;
  28927. node2.fy += fy;
  28928. }
  28929. }
  28930. }
  28931. };
  28932. /**
  28933. * this function calculates the effects of the springs in the case of unsmooth curves.
  28934. *
  28935. * @private
  28936. */
  28937. exports._calculateHierarchicalSpringForces = function () {
  28938. var edgeLength, edge, edgeId;
  28939. var dx, dy, fx, fy, springForce, distance;
  28940. var edges = this.edges;
  28941. var nodes = this.calculationNodes;
  28942. var nodeIndices = this.calculationNodeIndices;
  28943. for (var i = 0; i < nodeIndices.length; i++) {
  28944. var node1 = nodes[nodeIndices[i]];
  28945. node1.springFx = 0;
  28946. node1.springFy = 0;
  28947. }
  28948. // forces caused by the edges, modelled as springs
  28949. for (edgeId in edges) {
  28950. if (edges.hasOwnProperty(edgeId)) {
  28951. edge = edges[edgeId];
  28952. if (edge.connected) {
  28953. // only calculate forces if nodes are in the same sector
  28954. if (this.nodes.hasOwnProperty(edge.toId) && this.nodes.hasOwnProperty(edge.fromId)) {
  28955. edgeLength = edge.physics.springLength;
  28956. // this implies that the edges between big clusters are longer
  28957. edgeLength += (edge.to.clusterSize + edge.from.clusterSize - 2) * this.constants.clustering.edgeGrowth;
  28958. dx = (edge.from.x - edge.to.x);
  28959. dy = (edge.from.y - edge.to.y);
  28960. distance = Math.sqrt(dx * dx + dy * dy);
  28961. if (distance == 0) {
  28962. distance = 0.01;
  28963. }
  28964. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  28965. springForce = this.constants.physics.springConstant * (edgeLength - distance) / distance;
  28966. fx = dx * springForce;
  28967. fy = dy * springForce;
  28968. if (edge.to.level != edge.from.level) {
  28969. edge.to.springFx -= fx;
  28970. edge.to.springFy -= fy;
  28971. edge.from.springFx += fx;
  28972. edge.from.springFy += fy;
  28973. }
  28974. else {
  28975. var factor = 0.5;
  28976. edge.to.fx -= factor*fx;
  28977. edge.to.fy -= factor*fy;
  28978. edge.from.fx += factor*fx;
  28979. edge.from.fy += factor*fy;
  28980. }
  28981. }
  28982. }
  28983. }
  28984. }
  28985. // normalize spring forces
  28986. var springForce = 1;
  28987. var springFx, springFy;
  28988. for (i = 0; i < nodeIndices.length; i++) {
  28989. var node = nodes[nodeIndices[i]];
  28990. springFx = Math.min(springForce,Math.max(-springForce,node.springFx));
  28991. springFy = Math.min(springForce,Math.max(-springForce,node.springFy));
  28992. node.fx += springFx;
  28993. node.fy += springFy;
  28994. }
  28995. // retain energy balance
  28996. var totalFx = 0;
  28997. var totalFy = 0;
  28998. for (i = 0; i < nodeIndices.length; i++) {
  28999. var node = nodes[nodeIndices[i]];
  29000. totalFx += node.fx;
  29001. totalFy += node.fy;
  29002. }
  29003. var correctionFx = totalFx / nodeIndices.length;
  29004. var correctionFy = totalFy / nodeIndices.length;
  29005. for (i = 0; i < nodeIndices.length; i++) {
  29006. var node = nodes[nodeIndices[i]];
  29007. node.fx -= correctionFx;
  29008. node.fy -= correctionFy;
  29009. }
  29010. };
  29011. /***/ },
  29012. /* 66 */
  29013. /***/ function(module, exports, __webpack_require__) {
  29014. /**
  29015. * Calculate the forces the nodes apply on each other based on a repulsion field.
  29016. * This field is linearly approximated.
  29017. *
  29018. * @private
  29019. */
  29020. exports._calculateNodeForces = function () {
  29021. var dx, dy, angle, distance, fx, fy, combinedClusterSize,
  29022. repulsingForce, node1, node2, i, j;
  29023. var nodes = this.calculationNodes;
  29024. var nodeIndices = this.calculationNodeIndices;
  29025. // approximation constants
  29026. var a_base = -2 / 3;
  29027. var b = 4 / 3;
  29028. // repulsing forces between nodes
  29029. var nodeDistance = this.constants.physics.repulsion.nodeDistance;
  29030. var minimumDistance = nodeDistance;
  29031. // we loop from i over all but the last entree in the array
  29032. // j loops from i+1 to the last. This way we do not double count any of the indices, nor i == j
  29033. for (i = 0; i < nodeIndices.length - 1; i++) {
  29034. node1 = nodes[nodeIndices[i]];
  29035. for (j = i + 1; j < nodeIndices.length; j++) {
  29036. node2 = nodes[nodeIndices[j]];
  29037. combinedClusterSize = node1.clusterSize + node2.clusterSize - 2;
  29038. dx = node2.x - node1.x;
  29039. dy = node2.y - node1.y;
  29040. distance = Math.sqrt(dx * dx + dy * dy);
  29041. minimumDistance = (combinedClusterSize == 0) ? nodeDistance : (nodeDistance * (1 + combinedClusterSize * this.constants.clustering.distanceAmplification));
  29042. var a = a_base / minimumDistance;
  29043. if (distance < 2 * minimumDistance) {
  29044. if (distance < 0.5 * minimumDistance) {
  29045. repulsingForce = 1.0;
  29046. }
  29047. else {
  29048. repulsingForce = a * distance + b; // linear approx of 1 / (1 + Math.exp((distance / minimumDistance - 1) * steepness))
  29049. }
  29050. // amplify the repulsion for clusters.
  29051. repulsingForce *= (combinedClusterSize == 0) ? 1 : 1 + combinedClusterSize * this.constants.clustering.forceAmplification;
  29052. repulsingForce = repulsingForce / distance;
  29053. fx = dx * repulsingForce;
  29054. fy = dy * repulsingForce;
  29055. node1.fx -= fx;
  29056. node1.fy -= fy;
  29057. node2.fx += fx;
  29058. node2.fy += fy;
  29059. }
  29060. }
  29061. }
  29062. };
  29063. /***/ },
  29064. /* 67 */
  29065. /***/ function(module, exports, __webpack_require__) {
  29066. /**
  29067. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  29068. * The Barnes Hut method is used to speed up this N-body simulation.
  29069. *
  29070. * @private
  29071. */
  29072. exports._calculateNodeForces = function() {
  29073. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  29074. var node;
  29075. var nodes = this.calculationNodes;
  29076. var nodeIndices = this.calculationNodeIndices;
  29077. var nodeCount = nodeIndices.length;
  29078. this._formBarnesHutTree(nodes,nodeIndices);
  29079. var barnesHutTree = this.barnesHutTree;
  29080. // place the nodes one by one recursively
  29081. for (var i = 0; i < nodeCount; i++) {
  29082. node = nodes[nodeIndices[i]];
  29083. if (node.options.mass > 0) {
  29084. // starting with root is irrelevant, it never passes the BarnesHut condition
  29085. this._getForceContribution(barnesHutTree.root.children.NW,node);
  29086. this._getForceContribution(barnesHutTree.root.children.NE,node);
  29087. this._getForceContribution(barnesHutTree.root.children.SW,node);
  29088. this._getForceContribution(barnesHutTree.root.children.SE,node);
  29089. }
  29090. }
  29091. }
  29092. };
  29093. /**
  29094. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  29095. * If a region contains a single node, we check if it is not itself, then we apply the force.
  29096. *
  29097. * @param parentBranch
  29098. * @param node
  29099. * @private
  29100. */
  29101. exports._getForceContribution = function(parentBranch,node) {
  29102. // we get no force contribution from an empty region
  29103. if (parentBranch.childrenCount > 0) {
  29104. var dx,dy,distance;
  29105. // get the distance from the center of mass to the node.
  29106. dx = parentBranch.centerOfMass.x - node.x;
  29107. dy = parentBranch.centerOfMass.y - node.y;
  29108. distance = Math.sqrt(dx * dx + dy * dy);
  29109. // BarnesHut condition
  29110. // original condition : s/d < theta = passed === d/s > 1/theta = passed
  29111. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  29112. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) {
  29113. // duplicate code to reduce function calls to speed up program
  29114. if (distance == 0) {
  29115. distance = 0.1*Math.random();
  29116. dx = distance;
  29117. }
  29118. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  29119. var fx = dx * gravityForce;
  29120. var fy = dy * gravityForce;
  29121. node.fx += fx;
  29122. node.fy += fy;
  29123. }
  29124. else {
  29125. // Did not pass the condition, go into children if available
  29126. if (parentBranch.childrenCount == 4) {
  29127. this._getForceContribution(parentBranch.children.NW,node);
  29128. this._getForceContribution(parentBranch.children.NE,node);
  29129. this._getForceContribution(parentBranch.children.SW,node);
  29130. this._getForceContribution(parentBranch.children.SE,node);
  29131. }
  29132. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  29133. if (parentBranch.children.data.id != node.id) { // if it is not self
  29134. // duplicate code to reduce function calls to speed up program
  29135. if (distance == 0) {
  29136. distance = 0.5*Math.random();
  29137. dx = distance;
  29138. }
  29139. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  29140. var fx = dx * gravityForce;
  29141. var fy = dy * gravityForce;
  29142. node.fx += fx;
  29143. node.fy += fy;
  29144. }
  29145. }
  29146. }
  29147. }
  29148. };
  29149. /**
  29150. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  29151. *
  29152. * @param nodes
  29153. * @param nodeIndices
  29154. * @private
  29155. */
  29156. exports._formBarnesHutTree = function(nodes,nodeIndices) {
  29157. var node;
  29158. var nodeCount = nodeIndices.length;
  29159. var minX = Number.MAX_VALUE,
  29160. minY = Number.MAX_VALUE,
  29161. maxX =-Number.MAX_VALUE,
  29162. maxY =-Number.MAX_VALUE;
  29163. // get the range of the nodes
  29164. for (var i = 0; i < nodeCount; i++) {
  29165. var x = nodes[nodeIndices[i]].x;
  29166. var y = nodes[nodeIndices[i]].y;
  29167. if (nodes[nodeIndices[i]].options.mass > 0) {
  29168. if (x < minX) { minX = x; }
  29169. if (x > maxX) { maxX = x; }
  29170. if (y < minY) { minY = y; }
  29171. if (y > maxY) { maxY = y; }
  29172. }
  29173. }
  29174. // make the range a square
  29175. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  29176. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  29177. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  29178. var minimumTreeSize = 1e-5;
  29179. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  29180. var halfRootSize = 0.5 * rootSize;
  29181. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  29182. // construct the barnesHutTree
  29183. var barnesHutTree = {
  29184. root:{
  29185. centerOfMass: {x:0, y:0},
  29186. mass:0,
  29187. range: {
  29188. minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
  29189. minY: centerY-halfRootSize,maxY:centerY+halfRootSize
  29190. },
  29191. size: rootSize,
  29192. calcSize: 1 / rootSize,
  29193. children: { data:null},
  29194. maxWidth: 0,
  29195. level: 0,
  29196. childrenCount: 4
  29197. }
  29198. };
  29199. this._splitBranch(barnesHutTree.root);
  29200. // place the nodes one by one recursively
  29201. for (i = 0; i < nodeCount; i++) {
  29202. node = nodes[nodeIndices[i]];
  29203. if (node.options.mass > 0) {
  29204. this._placeInTree(barnesHutTree.root,node);
  29205. }
  29206. }
  29207. // make global
  29208. this.barnesHutTree = barnesHutTree
  29209. };
  29210. /**
  29211. * this updates the mass of a branch. this is increased by adding a node.
  29212. *
  29213. * @param parentBranch
  29214. * @param node
  29215. * @private
  29216. */
  29217. exports._updateBranchMass = function(parentBranch, node) {
  29218. var totalMass = parentBranch.mass + node.options.mass;
  29219. var totalMassInv = 1/totalMass;
  29220. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
  29221. parentBranch.centerOfMass.x *= totalMassInv;
  29222. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
  29223. parentBranch.centerOfMass.y *= totalMassInv;
  29224. parentBranch.mass = totalMass;
  29225. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  29226. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  29227. };
  29228. /**
  29229. * determine in which branch the node will be placed.
  29230. *
  29231. * @param parentBranch
  29232. * @param node
  29233. * @param skipMassUpdate
  29234. * @private
  29235. */
  29236. exports._placeInTree = function(parentBranch,node,skipMassUpdate) {
  29237. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  29238. // update the mass of the branch.
  29239. this._updateBranchMass(parentBranch,node);
  29240. }
  29241. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  29242. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  29243. this._placeInRegion(parentBranch,node,"NW");
  29244. }
  29245. else { // in SW
  29246. this._placeInRegion(parentBranch,node,"SW");
  29247. }
  29248. }
  29249. else { // in NE or SE
  29250. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  29251. this._placeInRegion(parentBranch,node,"NE");
  29252. }
  29253. else { // in SE
  29254. this._placeInRegion(parentBranch,node,"SE");
  29255. }
  29256. }
  29257. };
  29258. /**
  29259. * actually place the node in a region (or branch)
  29260. *
  29261. * @param parentBranch
  29262. * @param node
  29263. * @param region
  29264. * @private
  29265. */
  29266. exports._placeInRegion = function(parentBranch,node,region) {
  29267. switch (parentBranch.children[region].childrenCount) {
  29268. case 0: // place node here
  29269. parentBranch.children[region].children.data = node;
  29270. parentBranch.children[region].childrenCount = 1;
  29271. this._updateBranchMass(parentBranch.children[region],node);
  29272. break;
  29273. case 1: // convert into children
  29274. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  29275. // we move one node a pixel and we do not put it in the tree.
  29276. if (parentBranch.children[region].children.data.x == node.x &&
  29277. parentBranch.children[region].children.data.y == node.y) {
  29278. node.x += Math.random();
  29279. node.y += Math.random();
  29280. }
  29281. else {
  29282. this._splitBranch(parentBranch.children[region]);
  29283. this._placeInTree(parentBranch.children[region],node);
  29284. }
  29285. break;
  29286. case 4: // place in branch
  29287. this._placeInTree(parentBranch.children[region],node);
  29288. break;
  29289. }
  29290. };
  29291. /**
  29292. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  29293. * after the split is complete.
  29294. *
  29295. * @param parentBranch
  29296. * @private
  29297. */
  29298. exports._splitBranch = function(parentBranch) {
  29299. // if the branch is shaded with a node, replace the node in the new subset.
  29300. var containedNode = null;
  29301. if (parentBranch.childrenCount == 1) {
  29302. containedNode = parentBranch.children.data;
  29303. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  29304. }
  29305. parentBranch.childrenCount = 4;
  29306. parentBranch.children.data = null;
  29307. this._insertRegion(parentBranch,"NW");
  29308. this._insertRegion(parentBranch,"NE");
  29309. this._insertRegion(parentBranch,"SW");
  29310. this._insertRegion(parentBranch,"SE");
  29311. if (containedNode != null) {
  29312. this._placeInTree(parentBranch,containedNode);
  29313. }
  29314. };
  29315. /**
  29316. * This function subdivides the region into four new segments.
  29317. * Specifically, this inserts a single new segment.
  29318. * It fills the children section of the parentBranch
  29319. *
  29320. * @param parentBranch
  29321. * @param region
  29322. * @param parentRange
  29323. * @private
  29324. */
  29325. exports._insertRegion = function(parentBranch, region) {
  29326. var minX,maxX,minY,maxY;
  29327. var childSize = 0.5 * parentBranch.size;
  29328. switch (region) {
  29329. case "NW":
  29330. minX = parentBranch.range.minX;
  29331. maxX = parentBranch.range.minX + childSize;
  29332. minY = parentBranch.range.minY;
  29333. maxY = parentBranch.range.minY + childSize;
  29334. break;
  29335. case "NE":
  29336. minX = parentBranch.range.minX + childSize;
  29337. maxX = parentBranch.range.maxX;
  29338. minY = parentBranch.range.minY;
  29339. maxY = parentBranch.range.minY + childSize;
  29340. break;
  29341. case "SW":
  29342. minX = parentBranch.range.minX;
  29343. maxX = parentBranch.range.minX + childSize;
  29344. minY = parentBranch.range.minY + childSize;
  29345. maxY = parentBranch.range.maxY;
  29346. break;
  29347. case "SE":
  29348. minX = parentBranch.range.minX + childSize;
  29349. maxX = parentBranch.range.maxX;
  29350. minY = parentBranch.range.minY + childSize;
  29351. maxY = parentBranch.range.maxY;
  29352. break;
  29353. }
  29354. parentBranch.children[region] = {
  29355. centerOfMass:{x:0,y:0},
  29356. mass:0,
  29357. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  29358. size: 0.5 * parentBranch.size,
  29359. calcSize: 2 * parentBranch.calcSize,
  29360. children: {data:null},
  29361. maxWidth: 0,
  29362. level: parentBranch.level+1,
  29363. childrenCount: 0
  29364. };
  29365. };
  29366. /**
  29367. * This function is for debugging purposed, it draws the tree.
  29368. *
  29369. * @param ctx
  29370. * @param color
  29371. * @private
  29372. */
  29373. exports._drawTree = function(ctx,color) {
  29374. if (this.barnesHutTree !== undefined) {
  29375. ctx.lineWidth = 1;
  29376. this._drawBranch(this.barnesHutTree.root,ctx,color);
  29377. }
  29378. };
  29379. /**
  29380. * This function is for debugging purposes. It draws the branches recursively.
  29381. *
  29382. * @param branch
  29383. * @param ctx
  29384. * @param color
  29385. * @private
  29386. */
  29387. exports._drawBranch = function(branch,ctx,color) {
  29388. if (color === undefined) {
  29389. color = "#FF0000";
  29390. }
  29391. if (branch.childrenCount == 4) {
  29392. this._drawBranch(branch.children.NW,ctx);
  29393. this._drawBranch(branch.children.NE,ctx);
  29394. this._drawBranch(branch.children.SE,ctx);
  29395. this._drawBranch(branch.children.SW,ctx);
  29396. }
  29397. ctx.strokeStyle = color;
  29398. ctx.beginPath();
  29399. ctx.moveTo(branch.range.minX,branch.range.minY);
  29400. ctx.lineTo(branch.range.maxX,branch.range.minY);
  29401. ctx.stroke();
  29402. ctx.beginPath();
  29403. ctx.moveTo(branch.range.maxX,branch.range.minY);
  29404. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  29405. ctx.stroke();
  29406. ctx.beginPath();
  29407. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  29408. ctx.lineTo(branch.range.minX,branch.range.maxY);
  29409. ctx.stroke();
  29410. ctx.beginPath();
  29411. ctx.moveTo(branch.range.minX,branch.range.maxY);
  29412. ctx.lineTo(branch.range.minX,branch.range.minY);
  29413. ctx.stroke();
  29414. /*
  29415. if (branch.mass > 0) {
  29416. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  29417. ctx.stroke();
  29418. }
  29419. */
  29420. };
  29421. /***/ },
  29422. /* 68 */
  29423. /***/ function(module, exports, __webpack_require__) {
  29424. module.exports = function(module) {
  29425. if(!module.webpackPolyfill) {
  29426. module.deprecate = function() {};
  29427. module.paths = [];
  29428. // module.parent = undefined by default
  29429. module.children = [];
  29430. module.webpackPolyfill = 1;
  29431. }
  29432. return module;
  29433. }
  29434. /***/ }
  29435. /******/ ])
  29436. });